Agent/stabilize foundation - #10
Merged
Merged
Conversation
- Updated loader.js to include taskPoolMode and sharedMemory in logs. - Enhanced bootstrap-wasi-sysroot.ps1 to download and extract compiler-rt builtins for selective wasi-libc kits. - Modified run-wasm-browser-task-pool.cmake to validate shared-memory task pool wiring in session workers. - Added run-wasm-shared-tasks.cmake to test shared-memory module with in-place task pool. - Introduced run-wasm-wasi-libc.cmake to validate coexistence of Absolute WASI runtime with selective wasi-libc objects. - Created wasi-libc-probe.c to provide a C probe for testing selective wasi-libc kits. - Updated absolute-wasm-browser-session-worker.js to support shared memory imports and task pool mode. - Implemented absolute-wasm-browser-shared-task-worker.js for shared-memory task execution in the browser. - Enhanced absolute-wasm-host.js to support shared memory in task pools and improved task management. - Created absolute-wasm-shared-task-worker.js for Node.js to handle shared-memory tasks without context copying.
…nds in developer CLI
…ilities - Implemented keyboard and mouse input APIs with edge detection (keyPressed/keyReleased, mousePressed/mouseReleased). - Added 2D drawing functions: drawLine, fillCircle, and blit for software rendering. - Updated examples to demonstrate new features: added a Pong game and enhanced the window example with delta time handling. - Improved README documentation to reflect new APIs and usage instructions. - Refactored runtime code to support input state tracking and frame timing.
… soft sprite functionality - Added gamepad input support using XInput for Windows, including connection status, button states, and axis values. - Implemented text input handling, allowing for Unicode code points to be captured and processed. - Introduced soft sprite functionality with the ability to load BMP images, clear, fill, and draw sprites. - Updated README documentation to reflect new input methods and sprite capabilities. - Refactored sprite management into a separate file for better organization and maintainability. - Ensured compatibility with headless and X11 runtimes by providing stubs for new features.
…pabilities - Implemented soft bitmap font rendering in the desktop plugin, allowing for text drawing using a built-in 8x8 ASCII font. - Added functions for measuring text width and height, as well as drawing text directly onto windows and sprites. - Created a new example (`text.abs`) demonstrating the usage of the soft font for HUD, multi-line text, and live typing. - Updated documentation to reflect new text rendering features and API changes.
…with strided blits
- Implemented a minimal OpenGL rendering hardware interface (RHI) for desktop platforms using WGL and OpenGL 3.3 core. - Added functions for GPU resource management including creation, destruction, and drawing operations. - Introduced a GPU class in the desktop plugin to manage OpenGL context and resources. - Enhanced the desktop runtime to provide native window handles for GPU backends. - Updated the manifest to include GPU support.
…th shaders, vertex buffers, and layouts - Updated README.md to reflect new GPU API structure and usage patterns. - Modified absolute-desktop.editor.json to provide accurate documentation and snippets for new GPU methods. - Refactored desktop_gpu_gl.cpp to implement a new frame model with beginFrame, endFrame, and pipeline binding. - Introduced VertexLayout, GpuShader, GpuBuffer, and GpuPipeline classes to encapsulate GPU resource management. - Updated Gpu class methods to support new pipeline-based rendering workflow. - Removed deprecated drawDemoTriangle method and integrated drawing logic into the new pipeline structure. - Enhanced error handling and validation across GPU resource creation and usage.
…nd related functionality
…u RHI - Added `shader-rhi.abs` example demonstrating the use of `absolute.shader` with Desktop.Gpu. - Updated shader creation in `gpu-sprites.abs`, `mesh.abs`, `triangle.abs` to use `program` instead of `shader`. - Enhanced `absolute.shader` plugin to emit GLSL 330 source and reflection metadata for RHI binding. - Introduced new functions for shader input/output reflection and layout creation in the Desktop plugin. - Updated documentation to reflect new shader capabilities and examples.
- Implemented D3D11 backend for Desktop.Gpu, supporting clear/present lifecycle. - Updated GPU creation API to allow backend selection (OpenGL, D3D11, or auto). - Added new examples demonstrating D3D11 clear/present functionality and backend validation. - Enhanced existing examples to specify OpenGL backend explicitly. - Updated CMake configuration to link against D3D11 and DXGI libraries. - Revised documentation to reflect new GPU backend options and usage.
…functionality - Implemented shader creation and destruction functions for D3D11. - Added support for vertex and index buffer creation, destruction, and binding. - Introduced pipeline creation and binding functions for D3D11, including layout handling. - Updated draw functions to support indexed drawing in D3D11. - Enhanced uniform setting functions for D3D11 to allow float and integer uniforms. - Updated documentation to reflect the new capabilities of the D3D11 backend.
- Implemented a new D3D12 backend for the Desktop.Gpu, allowing for clear and present functionality. - Added example applications: d3d12-clear.abs and d3d12-clear-smoke.abs to demonstrate D3D12 usage. - Updated CMakeLists.txt to link against d3d12.lib and include the new desktop_gpu_d3d12.cpp file. - Enhanced README.md to document the new D3D12 backend and its limitations. - Modified existing GPU handling code to support D3D12 alongside OpenGL and D3D11. - Introduced magic number for D3D12 device handles to facilitate multi-backend dispatch.
- Updated D3D12 backend to support shader creation, buffer management, and pipeline operations. - Added functions for creating and managing vertex and index buffers, shaders, and pipelines. - Modified existing functions to route calls to the appropriate D3D12 implementations. - Updated documentation to reflect the new capabilities of the D3D12 backend.
…p and build process
- Implemented a new example for D3D12 sprites using HLSL, textures, and samplers. - Enhanced the D3D12 GPU plugin to support texture and sampler creation, binding, and destruction. - Updated the README to reflect the new D3D12 sprite capabilities. - Added functions for creating textures from sprites and managing samplers in the D3D12 backend. - Modified the OpenGL and D3D11 GPU plugins to route texture and sampler operations to the new D3D12 functions where applicable.
Closes the two open items under TODO P2. A metamorphic test needs no oracle for the value a program computes, only two programs that must agree. metamorphic_differential.py emits each case in six shapes a correct compiler has to treat identically: baseline, every declared name substituted, an uncalled function plus a branch guarded by a condition false for every reachable state, permuted top-level declarations, literals replaced by values the program computes, and all of those at once so a transformation mishandled only in combination still shows up. The shapes are generated from one template rather than rewritten textually, so a transformation cannot quietly change meaning. All of them must produce the same stdout and exit code. On a mismatch the runner shrinks the case while the disagreement survives and writes every shape's source, command and output. The optimization runner reported only the level that tripped, leaving out the outputs it disagreed with, which are what identifies the divergence; it now records the full source text and each level that already ran. Both reporting paths were checked by injecting a fault: making the dead branch reachable is caught as a divergence, minimized from 53 iterations to 1, with every shape recorded; breaking the oracle comparison produces a report carrying the source and the completed levels. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Covers the harness, corpus, minimization and budget items under TODO P3. Two in-process targets, for the lexer and for the lexer plus parser, built only where Clang's libFuzzer runtime is available so every other toolchain is unaffected. run_fuzzer.py replays the regression corpus first, fuzzes for a budget that ABSOLUTE_FUZZ_SECONDS widens for nightly and weekly runs, and on a crash minimizes the input and prints the command that reproduces it. Two corpora are kept: seeds give the fuzzer real language shapes to mutate, regressions hold inputs that once crashed. The parser was not ready to be fuzzed, and fixing that is most of this change: * It ended the process with std::exit on malformed input in 27 places, so a syntax error killed the caller. Every one now throws like the rest of Consume, which the CLI already reports and exits non-zero on. An embedder such as an LSP could not have used the parser before this. * Recursive descent turns source nesting into stack nesting, and about 25000 levels segfaulted. A depth guard reports an ordinary syntax error instead; 3000-term expressions and 200-deep parentheses still compile, so real code is unaffected. Memory and time are bounded by the runner. The parser fuzzer then found two null dereferences within seconds, both where a token was used after the source had ended: the uninitialized variable declaration path, and the initializer check in an instance declaration, minimized by the runner to the three bytes "S a". Both are fixed and both inputs are in the regression corpus. After the fixes a four-way fork run of 35000 executions reports no crashes, timeouts or OOMs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Continues TODO P3. Raw bytes reach the analyzer only by accident: almost every random buffer fails to parse, so a byte-oriented target would exercise the parser's error path instead. This harness spends the fuzzer's input as decisions in a generator that emits only syntactically valid programs, so what arrives at the analyzer is a real AST whose shape the fuzzer controls. Semantic validity is deliberately not a goal. Type mismatches, bad calls and odd conversions are what the analyzer exists to report, and reporting them has to be a diagnostic rather than a crash. The difference shows in coverage: 1978 edges against 82 for the parser target on a comparable budget. No crashes so far in a four-way fork run. Seeds are now per target, because the analyzer consumes decision streams rather than source. Since such a buffer is not readable on its own, ABSOLUTE_FUZZ_DUMP_SOURCE prints the program it stands for, which is what a crash report actually needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Closes the coverage item under TODO P3. The edge count libFuzzer prints says how much instrumented code a corpus reached, not where the gaps are, so a target can sit at a healthy number while never entering whole areas of the front end. fuzz_coverage.py replays a corpus through an instrumented target and splits the result by component, because those fail independently: a corpus can saturate the lexer and never reach semantic analysis. The instrumentation has to cover the libraries the harness links, not just the harness, or the report describes one translation unit; the first attempt did exactly that and reported 210 regions with no source file in it. The flags are therefore applied to the whole build, behind an option that is off by default because it slows everything down. The first run turned into two concrete tasks: * The analyzer target reaches 20.7% of the analyzer and 32.4% of the parser, against 57.5% for the parser target, so its generator emits too narrow a language: no classes, interfaces, generics, pointers, arrays or exceptions. * The dedicated lexer target covers 30.0% of the lexer while the parser target covers 65.6% of it on the same corpus, so that harness is reaching less of its own subject than a target that only uses it in passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
The coverage build directory was staged before .gitignore covered it. Only tools/testing/fuzz_coverage.py, the CMake option and the TODO note belong in that change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Four independent failures kept "CI required gate" red: - OptimizationLevel was only declared in codegen.h, which pch.h includes under ABSOLUTE_HAS_LLVM. Absolute-Compiler.cpp uses it unconditionally, so every ABSOLUTE_ENABLE_LLVM=OFF build failed to compile the driver (build-and-test on windows-2022 Debug/Release and termux-host-contract). Move the enum to its own LLVM-free header, include it unconditionally, and put Absolute-CodeGen/include on the driver's include path so the header resolves without the CodeGen target. - llvm-compatibility ran in a bare ubuntu:24.04 container without zlib1g-dev. LLVM's exported LLVMSupport target lists ZLIB::ZLIB in its link interface, so find_package(LLVM) aborted at generate time for LLVM 18, 19 and 20. Install zlib1g-dev alongside libzstd-dev. - macos-smoke could not build Absolute-Runtime: Darwin's <ucontext.h> errors out unless _XOPEN_SOURCE is defined, leaving getcontext, makecontext and swapcontext undeclared. Define it for the Apple branch of scheduler_fiber.h. - windows-llvm-release timed out bootstrapping the ~1 GB LLVM SDK on a cache miss, and the always() diagnostics step then threw on the missing llvm-config.exe, failing the job at that step and hiding the timeout. Guard the lookup and give the bootstrap enough headroom. Verified locally by building the driver both with ABSOLUTE_ENABLE_LLVM=OFF and ON, and by reproducing the LLVM export failure through a missing zstd/zlib target. The Darwin and Windows paths are preprocessor- and workflow-only changes that cannot be compiled here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Fixing the zlib gap in llvm-compatibility let those jobs get past CMake and reach the compiler, which exposed two API guards that never matched the versions the project claims to support (18 through 21). - LazyValueInfo::getPredicateAt returns Constant* rather than the removed Tristate enum since LLVM 19, but the guard in codegen.cpp opened at 21. LLVM 19 and 20 therefore took the old branch and failed with "no member named 'True' in 'llvm::LazyValueInfo'". - LLVM 20 added a ThinOrFullLTOPhase argument to the optimizer-last extension point callback, so the sanitizer pass registration in codegen_module.cpp no longer converted to the expected std::function. Split the registration per version and keep the pass setup shared. The remaining >= 21 guards were checked and are genuinely LLVM 21 API changes (CaptureInfo replacing NoCapture, Triple-based TargetMachine entry points), so they are left alone. Verified by installing LLVM 18, 19 and 20 locally and building Absolute-CodeGen and the absolutec driver against each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
The LLVM and WASI bootstrap steps captured their output into a variable and only wrote it to the log after the command returned. When the LLVM step hit its timeout it produced no output at all for the full 25 minutes, so the job log showed only the timeout with no indication of how far the download had progressed. Pipe through Tee-Object instead, which writes as output arrives. This is a diagnostics change only. It does not address the underlying problem that a cold cache requires downloading an ~1 GB SDK inside the step timeout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
The previous commit adapted the sanitizer callback at its call site, which was the wrong layer and broke LLVM 21: codegen_pch.h already ships an OptimizerLastEPCallback shim plus a macro that rewrites the registration, so call sites are meant to keep passing the two-argument form and let the shim add the LTO phase. Passing three arguments made the shim call the callback with two. The real defect is the shim's guard. LLVM 20 added the LTO-phase argument, but the shim only activated at 21, so LLVM 20 had no adapter at all. That guard also covered the unrelated Triple-based target APIs, which really are LLVM 21, and bundling them hid the mismatch. Split the block: the optimizer callback shim now activates at 20, the Triple helpers stay at 21, and codegen_module.cpp goes back to the plain two-argument lambda. LLVM 21 therefore returns to exactly the configuration that was passing before, now shared with LLVM 20. Verified by building absolutec against LLVM 18, 19 and 20 locally. LLVM 21 is not installable in this environment; it is covered by the restored call site being byte-identical to the previously passing one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
With the build failures fixed, these jobs reached their test phase for the first time and exposed two test-side assumptions. Four debug-info tests emit IR, link through --build-exe and inspect the result with the LLVM tools, but they were registered unconditionally. In any ABSOLUTE_ENABLE_LLVM=OFF configuration they failed with "LLVM backend is unavailable in this build", which is the configuration working as intended rather than a defect. Guard them with ABSOLUTE_ENABLE_LLVM, the pattern already used elsewhere in this file; the neighbouring semantic and diagnostic debug-info tests are frontend only and stay unguarded. This covers termux-host-contract and the windows-2022 build-and-test jobs, all of which configure the backend off. The debug-info IR assertion also required the llvm.dbg.declare intrinsic spelling. LLVM 19 prints debug info as records instead, so the check fails from 19 onward even though the emitted debug info is correct. Accept either spelling. Verified locally: the termux contract configuration goes from 4 failures to 178/178, the debug-info tests pass against both LLVM 18 (24 intrinsics) and LLVM 19 (23 records), and the full suite is 501/501 on LLVM 18. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
The earlier fix defined _XOPEN_SOURCE inside scheduler_fiber.h, which tasks.cpp includes after about twenty standard library headers. That is enough to satisfy the #error in Darwin's <ucontext.h> and make the build succeed, but a feature macro only affects headers included after it: the Darwin headers libc++ had already pulled in were parsed without it. The declarations and the ucontext layout can therefore disagree within one translation unit, which is consistent with the Bus error and SIGTRAP the scheduler and async tests now hit on macOS. Set the macro from the command line for that source file instead, so it applies before any header, and pair it with _DARWIN_C_SOURCE so the stricter feature set does not hide BSD extensions from the rest of the unit. The header now states the requirement and fails loudly if it is missing, rather than silently defining it too late. This removes a real class of undefined behaviour. It is not confirmed to be the cause of the macOS failures: if they persist, the remaining explanation is that the ucontext routines are unusable on arm64 Darwin, which needs a different fiber backend and is a design decision rather than a fix. Verified on Linux, where the Apple branch is inert: tasks.cpp compiles and Absolute-Runtime links. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
The streamed bootstrap output shows the ~1 GB SDK download finishing in 16 seconds at ~56 MB/s, after which the step spends the remaining 24.5 minutes inside tar.exe unpacking the .tar.xz before being killed. The comment added with the timeout increase claimed the step was killed mid-download, which is wrong and would point a reader at the wrong phase. Comment only; the 25-minute value is left as is. Raising it further would not help, since the overrun is in extraction rather than transfer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
The streamed bootstrap output showed where windows-llvm-release actually spends its time: the ~1 GB SDK downloads in 16 seconds at ~56 MB/s, then tar.exe runs for the remaining 24.5 minutes and is killed. Real-time antivirus scanning of every file written is the usual cause of that ratio on CI runners, since the archive expands to several GB of mostly small headers. Add a best-effort Defender exclusion for the toolchains directory before extraction, and report how long extraction took. The exclusion is wrapped in a try/catch so a machine without Defender, or without the privileges to configure it, still extracts as before. The timing line makes the next run informative either way: if extraction is still slow, the number rules the antivirus theory out rather than leaving it open. Not verifiable from this environment, which has no Windows runner and no PowerShell to parse the script; CI is the test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
The instrumentation added in the previous commit settled where the time goes. The download finishes in 9 seconds at ~110 MB/s, the Defender exclusion is applied successfully, and tar.exe still consumes the entire remaining 24.8 minutes without returning. So neither transfer nor antivirus scanning explains it: bsdtar decoding this xz stream on a single thread does. 7-Zip ships on the hosted Windows images and decodes xz in parallel. Split extraction into an xz pass and a tar pass through it, timing each, and keep tar.exe as the fallback for any machine where 7-Zip is missing or the pass does not produce the expected archive. The Defender exclusion is retained: it was not the fix, but it is cheap and large file writes are genuinely slower under real-time scanning. The per-phase timings mean the next run distinguishes slow decompression from slow file creation, whichever way this lands. Not verifiable here: no Windows runner and no PowerShell to parse the script, so CI is the test. The fallback keeps the failure mode no worse than the current one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Building the stress programs with --sanitize=address surfaced a leak that reproduces on every run of collection-snapshot-stress: 6048 bytes in 12 allocations, attributed to std.collections.Map.put and Set.add. The growth paths allocate a larger array, copy the live elements across, and then assign it with copy(...). That stores a duplicate in the field and leaves the freshly allocated local unreferenced and unreleased. The arithmetic matches exactly: six doublings from capacity 4 to 256 leak 8*(8+16+32+64+128+256) = 4032 bytes for the 8-byte KeyValuePair entries and 4*(...) = 2016 for the 4-byte set items, 6048 together, in 6 objects each. Assign with move(...) so the field takes ownership of the array that was just built. vector.abs already does exactly this at its own growth sites, so this brings map, set, deque and priority_queue in line with the idiom the standard library had already settled on rather than introducing a new one. A plain assignment is rejected by the analyzer, which names move(...) among the accepted forms. The remaining copy(...) uses are left alone: iterator snapshots, the copy-on-write in ensureUnshared, and the rehash locals in hash_map are all genuine copies. Verified with ASan: collection-snapshot-stress goes from 6048 bytes leaked to a clean exit. The full suite is 501/501 on LLVM 18. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Fixes ten defects that kept the CI required gate red: OptimizationLevel unavailable in LLVM-free builds, missing zlib1g-dev in the llvm-compatibility container, Darwin _XOPEN_SOURCE for the ucontext fiber routines, LazyValueInfo and optimizer-callback version guards that never matched the supported LLVM 18-21 range, four debug-info tests registered without a backend guard, an LLVM-18-only IR assertion, a diagnostics step that failed its own job and masked the real cause, bootstrap output discarded on timeout, bsdtar extraction of the Windows SDK replaced with 7-Zip, and a copy-on-grow ownership bug in the std collections. That last one took macos-smoke from 17 failures to 1. Remaining and tracked separately: absolute.run-sanitizer-ownership-stability on macOS, and an intermittent Linux failure that reproduces only in CI.
build-and-test (ubuntu-24.04, Debug) failed on b286d07 without any test failing. It was killed at the 20-minute step limit with 464 of 474 tests already passed, mid-way into starting the 465th, and the orphaned ctest, tee and absolutec processes were reaped by the runner. The same commit passed in the parallel run of the same workflow, where the job took 14m13s against the 22m53s of the one that timed out. That 1.6x spread across two runners on identical input is the whole story: the Debug suite sits close enough to the cap that ordinary runner variance decides the outcome. Release is unaffected because it runs far quicker. Raise the Ubuntu test step to 35 minutes and the job to 60 so the step ceiling, not the job ceiling, is what a genuine hang hits. The step still fails fast on a real deadlock, and ctest keeps its own 180-second per-test timeout, so a single wedged test is still caught in seconds rather than after the step budget. This trades a longer worst case for not failing on healthy runs. Cutting the runtime itself, by giving ctest a --parallel, is the better fix but a larger change: the suite runs serially today and some tests bind ports and write shared paths, so parallelising needs each test checked for isolation first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
The backend selected a target CPU with getHostCPUName() and passed an empty feature string, so LLVM enabled every feature implied by that microarchitecture. The name only identifies a model; it says nothing about which instructions the machine actually exposes. A virtualised host can report a model whose default feature set includes instructions the hypervisor masks off, and generating for the name alone then emits code the CPU refuses to run, which surfaces as SIGILL in whatever executes the generated binary. Take the feature string from getHostCPUFeatures() instead. The signature changed in LLVM 19 from an out-parameter to a returned map, so the call is guarded the same way Host.h already is, and SubtargetFeature.h joins it in the PCH under the existing version guard. What prompted this: build-and-test (ubuntu-24.04, Debug) failed on 8f60a85 with nine tests, eight of them ILLEGAL, and both parallel runs produced an identical list. The same source passed all 474 tests an hour earlier in job 93122612240, so the source is not what changed. The failures cluster on tests that execute generated native code, which is the signature this defect produces. Also log lscpu on Linux, so the next run records the runner's model and feature bits instead of leaving them to be inferred from a signal number. Verification is honest about its limits: this cannot be reproduced locally, because getHostCPUName() here reports cascadelake on a machine that genuinely has AVX-512, so name and features agree and no illegal instruction is possible. The local Debug run confirms the change causes no regression; confirming it fixes the CI failure needs a hosted run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
…he Ubuntu test timeout Raise the Ubuntu test step timeout above its measured runtime
Three factual updates to the CI and ownership sections, no reassessment of existing checkboxes beyond one note. P0 CI matrix gains the SIGILL fix: the backend selected a target CPU by name and passed an empty feature string, so LLVM enabled everything the CPUID model implied without checking the machine exposed it. The entry records that the symptom looked intermittent but was deterministic, and that an environmental trigger means a fleet rotation cannot be excluded as the reason the runs went green. The both-gates item stays open but now says where it actually stands: the hardening gate is green in both parallel runs without rerun, and only macos-smoke holds the CI gate. P4 names the single remaining ownership-suite failure and that it is macOS-only, and P0 ownership records the std.collections copy-on-grow leak that was found and fixed in an area whose items were already checked off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
TypeNeedsCleanup looked for the member key "destroy()", but the methods map is keyed by CallableKey, which joins the bare name with "$"-separated parameter types and so stores "destroy". The literal was a key the map could never hold, leaving that branch dead: a type whose only resource was its own destroy() was classified as needing no cleanup, and the hook never ran. The analyzer's TypeOwnsResources matches the same literal correctly, because analyzer member maps really are keyed that way. Agreement between the two spellings is what hid this. Scope measured against the built compiler. A managed class with destroy() was already cleaned up, and so was a struct that additionally owned a managed field, because the field recursion decided those cases before the dead branch was reached. Only a struct whose sole resource was destroy() was skipped, and it was skipped everywhere: as a local at scope exit and as a field of a class being deleted, whose synthesized destructor was emitted with an empty body. tests/native-handle-wrapper.abs is the visible casualty. NativeHandle holds a raw void*, which is explicitly not a resource, so its destroy() never ran and every handle leaked. The test asserts tags before destruction and never that the release happened, so it passed throughout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Covers three P4 items that had no dedicated regression test: destroying an owner expires subscribers taken at any depth and weak aliases from either place, a weak back-edge neither keeps its target alive nor survives it, and generation-slot reuse never revives an expired handle across 500 rounds. Cleanup order is observed rather than inferred, using the digit-trace idiom from tests/defer.abs: each destroy() appends its tag to a shared int32, so the final value spells the order storage was released in. This is what exposed the destroy() defect fixed in the previous commit — the trace came back 0. Reverse declaration order is asserted exactly (321) for fields of one object, which is what docs/resource-ownership.md specifies. For a chain through a managed field the test asserts only that every level is released exactly once: the observed order is root-first (123), not the documented reverse, and pinning an interleaving that cannot be justified as intended would bake in behaviour that may itself be wrong. The discrepancy is noted in the test. Registered four ways, matching the existing ownership tests: semantic, IR emission, lli execution, and an AddressSanitizer build that must exit clean, which is a first step on the P4 item asking for the corpus under sanitizers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
The test observed the wrapper's tag before cleanup and described the release in a comment: "destroy() runs when a leaves this scope." Nothing checked it, so when destroy() silently stopped being called the test kept passing and the handle leaked on every run. The C side now counts live handles, and the Absolute side asserts the count: zero before the test, one while a handle is open, zero once its owner leaves scope, and one after a move, which also pins that a move transfers the handle rather than duplicating it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
A constructor leaving through an exception ran scope cleanup for its locals and returned. The fields it had already stored into `this` are not a scope, and no caller holds a name for them, so every resource acquired before the throw leaked. Measured on four shapes before the change: a throw after one field leaked one resource, after two fields two, a derived constructor throwing after a completed base leaked both, and a throwing base leaked what it had acquired. Ordinary scope unwinding was already correct. The exception path out of a constructor now releases the object itself. Two properties make that exactly right rather than approximately. Object storage is zero-initialized before the constructor runs, so destroying the whole object releases precisely the initialized prefix — uninitialized fields are null or zero and their cleanup is a no-op. And a derived class's field list starts as a copy of its parent's, so one destructor covers inherited fields too, which is what the throwing-base and throwing-derived cases need. Cleanup zeroes each field as it releases it, so a later destructor pass over the same object does nothing. That keeps this idempotent against whatever the allocation site does on failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Adds the P4 item for partially constructed objects and exceptions in constructor and base constructor, which nothing in the suite reached: no existing test throws from a constructor at all. Five shapes, each counting live resources through a raw int32 the caller owns so a leak fails an assertion instead of waiting for a sanitizer: a throw after one field, after two, in a derived constructor once its base completed, in the base itself, and a throw crossing two live scopes. The last one passed before the fix in the previous commit and is kept as the control — it is what shows the defect was specific to constructors rather than to unwinding in general. Registered semantic, IR, lli and AddressSanitizer, matching the graph torture test added earlier. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Adds the P4 item for move(owner) through return values, parameters, fields, generic wrappers and interface dispatch, plus the case the item singles out: an exception that lands after ownership has been taken but before the operation that took it finished. At that point the source is already invalidated and the destination does not exist, so the frame the exception leaves is the only place that can still release the object. Six shapes, counting live objects through a raw int32 the caller owns so a leak fails an assertion rather than waiting for a sanitizer. The interface case checks that deleting through an interface pointer reaches the most-derived destructor via slot zero rather than stopping at the interface. All six pass unchanged, so unlike the two defects the earlier torture tests found, transfer needed no compiler fix — the scope unwinding that already worked covers the throw-after-take case once a constructor no longer leaks. Registered semantic, IR, lli and AddressSanitizer, matching the other torture tests. Full suite 516/516. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
Review of the previous constructor-unwinding commit raised three defects in it, all real. This addresses them and adds the coverage that should have caught them. The exception path called the whole class destructor, and that destructor runs the type's own destroy() hook before touching fields. A constructor that threw never established the invariants such a hook assumes, so this executed user cleanup on an object that was never built — the zero-initialization argument justifies generated field cleanup only, never an arbitrary hook body. Fields are now released directly and the hook is not called. The same call also ran twice when a base constructor threw: the base cleaned its own fields on its own path, then the derived frame reached the exception check after the base call and cleaned them again through its flattened field list. Rather than rely on cleanup being idempotent, currentConstructorClass now stays empty until the base constructor has succeeded. Before that point no field of the derived class is initialized, so there is nothing for that frame to release. Third, the flag leaked into lambda bodies emitted inside a constructor. Those are separate functions that can run long after construction, and Visit(LambdaExpr) saved currentClassName and currentThis but not this one, so a throwing lambda would have released the enclosing object. It is now saved and cleared like the rest of the constructor state. Three cases added, each counting hook invocations through a caller-owned raw int32 with a deliberately non-idempotent hook so a second call is visible: a throw in a hooked constructor, a derived throw over a hooked base, and a hooked base that throws. Verified against the previous implementation, where they abort as expected. Separately, the clean sanitizer harness asked for detect_leaks on macOS, where LeakSanitizer does not exist. The runtime then fails at startup and abort_on_error turns that into a non-zero exit, which is why every run-sanitizer-clean case fails there — including run-sanitizer-ownership-stability, long read as an ownership defect. Darwin now joins the Windows and Termux guard. Unverified without a Mac; CI decides. Full suite 516/516 on Linux. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SPqntJmyXNSmZNzuQWj5QS
…the leaks it found Start the P4 ownership torture suite and fix the two leaks it found
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.