Skip to content

Agent/stabilize foundation - #10

Merged
FuryBaM merged 325 commits into
masterfrom
agent/stabilize-foundation
Aug 9, 2026
Merged

Agent/stabilize foundation#10
FuryBaM merged 325 commits into
masterfrom
agent/stabilize-foundation

Conversation

@FuryBaM

@FuryBaM FuryBaM commented Jul 27, 2026

Copy link
Copy Markdown
Owner

No description provided.

FuryBaM and others added 30 commits July 24, 2026 19:53
- 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.
…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.
- 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.
…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.
- 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.
claude added 6 commits August 7, 2026 15:17
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
claude and others added 22 commits August 8, 2026 11:40
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
@FuryBaM
FuryBaM merged commit d910162 into master Aug 9, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants