diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e911707..a9fd9b5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,21 @@ out-of-tree row (12 → 13). `kParserVer` 92 → 93 with the mirror (the branch re-pins with reasons in-file: `test/qschemetrip.hash`, `test/printf_parity.manifest` (the `--impact` help and legend name the two new closure kinds; the `--deps` legend's lazy definition gains the rescue class). `docs/COMMANDS.md` regenerated (2026-09-11). +### Changed — `VERIFY_NO_ALIAS` is a release optimizer fact, on LLVM 17 as well + +- **`VERIFY_NO_ALIAS` is now an optimizer fact in release, not an inert assume.** `src/infra/Diagnostics.h` §6 adds + `__builtin_assume_separate_storage` (clang 17+, `__has_builtin`-guarded, `( (void)0 )` elsewhere) beside the debug + check, so codegen matches `__restrict__` on the parameters (`out=a; out+=b; out+=a;` arm64 10 → 6 instructions); + `VERIFY_NO_ALIAS_BUF` is the form for two OWNING containers (the object form is inert for their loops; views — `std::span`, `std::string_view` — can share one allocation and are refused at compile time); the comment carries + the complete-object contract and the macOS `` trap that deletes bare `__restrict` in C++ — + `__restrict__` is the only spelling allowed in `src/`. `test/noaliascheck.sh` (eight arms, red against the old + definition) proves it. The optimizer half is a separate switch: BasicAA reads the bundle only when + `basic-aa-separate-storage` is on — `cl::init(false)` in LLVM 17 (AppleClang 16 / Xcode 16.2: the macos-14 CI + runners and the macos-arm64 release leg), `true` from LLVM 18 — so CMake now probes and passes + `-mllvm -basic-aa-separate-storage` to our targets (and to the ld64 link under LTO), and the gate classifies the + compiler by compiling the real slice three ways, with a `=false` negative control and a cross-check against the + cached CMake probe. + ## [0.6.0] — 2026-09-11 **Languages and integrations from outside the project, much faster on the largest trees, and answers that say where diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c383db9..46432c70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -914,6 +914,91 @@ if(RIPWIRE_TSAN) endforeach() endif() +# ---- separate-storage alias analysis: -mllvm -basic-aa-separate-storage (LLVM 17 keeps it OFF) ---- +# src/infra/Diagnostics.h §6's VERIFY_NO_ALIAS lowers, in release, to `llvm.assume [ "separate_storage"(a, b) ]` +# through __builtin_assume_separate_storage. The FRONT END has emitted that bundle since clang 17; whether BasicAA +# READS it is a separate switch inside the optimizer, llvm/lib/Analysis/BasicAliasAnalysis.cpp: +# static cl::opt EnableSeparateStorageAnalysis("basic-aa-separate-storage", cl::Hidden, cl::init(false)) +# in LLVM 17, and cl::init(true) from LLVM 18 on. So on an LLVM-17 toolchain — AppleClang 16.0.0.16000026 (Xcode +# 16.2), the macos-14 CI runners and release.yml's macos-arm64 leg — the header compiles, the IR carries the bundle, +# and codegen is byte-identical to no promise at all. Found by CI job "release (macos-14, plain, appleclang, shard +# 4/4)" on PR #200: test/noaliascheck.sh arms 2, 2/3 and 6 red ("accNew still reloads a after the store", "NO +# CONTRAST", "not at least 2 below plain"), arm 7 and the IR-bundle row green — the front-end half worked, the +# optimizer half was off. Reproduced on Apple clang 21: the arm-2 probe is 5 instructions by default, 9 with +# `-mllvm -basic-aa-separate-storage=false`, 5 with `=true`. +# +# Why LLVM 17 left it off, and why turning it on there is safe: llvm/llvm-project#76864 "[BasicAA] Enable separate +# storage hints by default" (merged 2024-01-03) flipped the default because "a few months of experimentation in a +# large codebase did not reveal any significant build speed regressions" — the LLVM-17 default was a compile-time +# hedge, not a soundness one; #76770 the same week only makes the hint fire in more contexts. LLVM 17 with the option +# on runs the analysis LLVM 18+ runs unconditionally. +# +# The option's name is the same from LLVM 17 through 21 and clang takes it as a compile flag, so it is passed +# wherever the compiler accepts it: on LLVM 17 it turns the analysis on, on 18+ it restates the default (a no-op). +# What the option buys on LLVM 17 is the SCALAR half only: the reload after a store through a possibly-aliasing +# pointer goes away, but the loop vectorizer's overlap checks stay, because LLVM 17 consults the hint only at the +# assume's own context and LoopAccessAnalysis never supplies one (llvm/llvm-project#64666, fixed in LLVM 18 by +# #76770). CI showed exactly that split on PR #200 head 5156d668: noaliascheck arm 2 green, arm 6 at plain. +# Probed, never assumed — GCC rejects -mllvm outright and a clang whose LLVM had dropped the name would reject the +# option; either way the check says no, the build keeps the compiler default, and the header's debug check still +# runs. The two-token option rides in CMAKE_REQUIRED_FLAGS (a whitespace-split string, the way the libFuzzer probe +# above passes its flags). Verified 2026-09-12 in a scratch configure: Apple clang 21 says yes, and the same probe +# over `-mllvm -bogus-option-xyz` says no (clang exits 1 on an unknown -mllvm name). test/noaliascheck.sh reads the +# cached answer back out of build/CMakeCache.txt and fails if its own probe of the same compiler disagrees. +# +# OUR TARGETS ONLY, via target_compile_options — never add_compile_options: the tree-sitter core and the grammar +# objects are C that never spells the builtin, and per-target attachment is the rule this file already follows for +# the remarks and sanitizer flags. RIPWIRE_OWNED_CXX_TARGETS is ripwire, ripwire_probe and the test executables, in +# every flavour (plain, Release, asan, tsan): the list is flavour-independent, so nothing here is. +# +# LTO. With -flto the compile step still runs the PRE-link pipeline (which already removes the arm-2 reload under +# this option — measured: compile default + link `=false` gives 5), and ld64's libLLVMLTO runs the POST-link one, +# whose BasicAA reads the same cl::opt. So under LTO the option must reach the link as well, or every inlining +# opportunity the link step creates on an LLVM-17 toolchain is analysed with the promise off. Apple ld64 takes it as +# `-Wl,-mllvm,-basic-aa-separate-storage` — verified: compile `=false` + link `=false` gives 9 instructions, compile +# `=false` + link `=true` gives 5, and a bogus name fails the link ("libLLVMLTO: Unknown command line argument") — +# and it is probed below with a real -flto link before it is attached. The ELF spelling +# (`-Wl,-plugin-opt=-basic-aa-separate-storage` for lld / gold) is deliberately NOT added: it could not be verified +# on this machine, and no CI or release leg pairs LTO with an LLVM-17 ELF toolchain (ubuntu-24.04's clang is 18, +# the Linux release leg is gcc, which never sees the builtin). An LTO build on such a toolchain keeps the compile-time +# option and the linker's default; that limit is stated here rather than papered over with an unverified flag. +include(CheckCXXCompilerFlag) +set(_ripwire_required_flags_save "${CMAKE_REQUIRED_FLAGS}") +set(CMAKE_REQUIRED_FLAGS "-mllvm -basic-aa-separate-storage") +check_cxx_compiler_flag("" RIPWIRE_CXX_HAS_BASIC_AA_SEPARATE_STORAGE) +set(CMAKE_REQUIRED_FLAGS "${_ripwire_required_flags_save}") +if(RIPWIRE_CXX_HAS_BASIC_AA_SEPARATE_STORAGE) + foreach(_t IN LISTS RIPWIRE_OWNED_CXX_TARGETS) + target_compile_options(${_t} PRIVATE -mllvm -basic-aa-separate-storage) + endforeach() + set(_ripwire_separate_storage_link "no LTO, so nothing runs at link") + if(RIPWIRE_LTO) + if(APPLE) + include(CheckCXXSourceCompiles) + set(_ripwire_required_flags_save "${CMAKE_REQUIRED_FLAGS}") + set(_ripwire_required_link_options_save "${CMAKE_REQUIRED_LINK_OPTIONS}") + set(CMAKE_REQUIRED_FLAGS "-flto") + set(CMAKE_REQUIRED_LINK_OPTIONS "-flto;-Wl,-mllvm,-basic-aa-separate-storage") + check_cxx_source_compiles("int main() { return 0; }" RIPWIRE_LD64_HAS_BASIC_AA_SEPARATE_STORAGE) + set(CMAKE_REQUIRED_FLAGS "${_ripwire_required_flags_save}") + set(CMAKE_REQUIRED_LINK_OPTIONS "${_ripwire_required_link_options_save}") + if(RIPWIRE_LD64_HAS_BASIC_AA_SEPARATE_STORAGE) + foreach(_t IN LISTS RIPWIRE_OWNED_CXX_TARGETS) + target_link_options(${_t} PRIVATE "-Wl,-mllvm,-basic-aa-separate-storage") + endforeach() + set(_ripwire_separate_storage_link "forced on at the LTO link too (-Wl,-mllvm,-basic-aa-separate-storage)") + else() + set(_ripwire_separate_storage_link "LTO link keeps the linker default (ld64 refused -Wl,-mllvm,-basic-aa-separate-storage)") + endif() + else() + set(_ripwire_separate_storage_link "LTO link keeps the linker default (the ELF -plugin-opt spelling is unverified — see the comment above)") + endif() + endif() + message(STATUS "separate-storage alias analysis: forced on (-mllvm -basic-aa-separate-storage — turns BasicAA's separate_storage reader on for LLVM 17 / AppleClang 16, a no-op on LLVM 18+); ${_ripwire_separate_storage_link}") +else() + message(STATUS "separate-storage alias analysis: compiler default (${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} does not accept -mllvm -basic-aa-separate-storage; VERIFY_NO_ALIAS keeps its debug check, the release promise is whatever this optimizer does by default)") +endif() + # ---- opt-in ingestion fuzzing: isolated coverage-instrumented parser objects ---- # Keep these copies separate from production objects: fuzzer-no-link coverage callbacks must never leak # into a normal executable. RIPWIRE_FUZZ is OFF by default and every target is EXCLUDE_FROM_ALL. diff --git a/README.md b/README.md index 69541a13..dccbe218 100644 --- a/README.md +++ b/README.md @@ -1793,9 +1793,9 @@ wrong, and it has. These are the results that say so, all in-tree, all published ### In the tests
-608 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures +609 gate scripts, five contracts no unit test can hold, and the house rule: write the gate before the code it measures -`test/regression.sh` names **608 gate scripts** and is the authoritative list; +`test/regression.sh` names **609 gate scripts** and is the authoritative list; `python3 test/pargates.py . ./build/ripwire -j 6` runs the same set in parallel. On top of them sit the contracts that do not fit a unit test: two runs byte-identical, warm output identical to cold, output that pipes clean through `xmllint --noout`, a sanitizer build with `-fno-sanitize-recover=all`, and a diff --git a/docs/EVALS.md b/docs/EVALS.md index f11bba16..077c25e1 100644 --- a/docs/EVALS.md +++ b/docs/EVALS.md @@ -21,7 +21,7 @@ section, and it is not an afterthought. | **Co-change / known-item evals** | `--eval`, `--eval-retrieval` (see `bench/ANSWERQUALITY.md`) | Whether the tool surfaces the other files a real historical commit touched; and known-item retrieval across four rankers. | | **Ensemble calibration harness** | `bench/ensemblecal/` | Whether `--ensemble`'s four evidence families are actually orthogonal, how often each fires, how stable each is across commits — and the preset ladder derived from that (§9). | | **Differential argv harness** | `test/argvdiffcheck.sh` | That a refactor changed *nothing observable*: two binaries, every argv vector, stdout + stderr + exit code byte-identical. | -| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 608 gate scripts plus the determinism, cache-transparency and golden contracts. | +| **The gate suite** | `test/regression.sh`, `test/pargates.py` | 609 gate scripts plus the determinism, cache-transparency and golden contracts. | | **`--quality-delta`** | `src/quality.h` | Ten measured code-quality failure modes, reported only where a change made them worse. | ### The labeling protocol (why the held-out eval is allowed to disagree with the ranker) @@ -5834,7 +5834,7 @@ copy here would be exactly the dialect divergence that gate exists to catch. Com tags, wrap, stable-order defaults), seven individually invoked standalone gates (`g1freshcheck`, `skillscan`, `htmlexport`, `compresscheck`, `handoffcheck`, `releaseinstallcheck`, `taskroutecheck`), and a single loop -naming **608 gate scripts**, all of which exist on disk. +naming **609 gate scripts**, all of which exist on disk. `python3 test/pargates.py . ./build/ripwire -j 6` runs the same scripts in parallel so a full verification fits in one sitting. It does not modify `regression.sh`. @@ -6846,7 +6846,7 @@ Listed because the reason is more useful than the silence. shipped**. See `bench/locbench/anchorhop_calib.json`. The mention anchor's reproducible numbers are the ablations in §4. - **A single round gate-count.** Two in-tree numbers disagree (`test/pargates.py`'s docstring says - ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 608. The + ~210; `test/argvdiffcheck.sh` says 200+), while the loop in `test/regression.sh` names 609. The loop is the authority; the stale docstrings are a known drift. Since 2026-09-10 the number is not written by hand anywhere: `docs/gatecount_build.py` derives it from the loop and rewrites every published site, `test/gatecountcheck.sh` fails if any of them drifts, and `test/manifestcheck.sh` diff --git a/present/deck5_ripwire_build.js b/present/deck5_ripwire_build.js index b89cf994..66f942d5 100644 --- a/present/deck5_ripwire_build.js +++ b/present/deck5_ripwire_build.js @@ -1068,7 +1068,7 @@ function storyCards(s, { kick, head, stories, footText }){ kicker(s, "// how it stays true", AMBER); title(s, "Proven, not promised"); const cards = [ - ["608 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount + ["609 gate scripts", "the suite runs on every push — plus determinism, cache-transparency and golden contracts; the gate count itself is gated against the runner's own loop"], // gatecount ["byte-identical, always", "two runs over the same tree produce the same bytes; warm equals cold. Enforced in CI, twice — Release AND a plain flavour, because NDEBUG once blinded a whole class of checks"], ["differential refactoring", "a refactor must prove it changed nothing observable: two binaries, hundreds of argv vectors, stdout + stderr + exit codes byte-identical"], ["held-out labels, authored blind", "eval labels were written by reading source before the ranker ever ran on them — so the eval is allowed to say the ranker is wrong. It has."], @@ -1092,7 +1092,7 @@ function storyCards(s, { kick, head, stories, footText }){ title(s, "Claims you can trust, because we publish what failed", { size: 32 }); card(s, MX, 1.72, 3.86, 1.72); - stat(s, "608", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount + stat(s, "609", "gate scripts named by test/regression.sh — and the COUNT itself is gated against the runner's own loop, so it cannot go stale quietly", // gatecount MX+0.15, 1.86, 3.56, CYAN, { bsize: 42, bh: 0.66, lsize: 9.5 }); card(s, 4.68, 1.72, 3.86, 1.72, CARD2); stat(s, "8", "registered NEGATIVES — changes built, gated green, measured against a band written before the code, and reverted rather than tuned", @@ -1342,7 +1342,7 @@ function storyCards(s, { kick, head, stories, footText }){ ["179 long flags · 33 slides", "bash test/deckclaimcheck.sh"], ["every --flag named here exists", "bash test/deckcheck.sh"], ["74.7% fewer element bytes", "bash test/showcasecapturecheck.sh"], - ["608 gate scripts", "bash test/manifestcheck.sh"], // gatecount + ["609 gate scripts", "bash test/manifestcheck.sh"], // gatecount ["49 repos · 70 papers · 237 surveyed","bash test/readmedriftcheck.sh"], ["the ten moments, any row", "ripwire . --callers=SYM | wc -c"], ["the head-to-head table", "bench/headtohead/r4-2026-08-06/"], diff --git a/src/infra/Diagnostics.h b/src/infra/Diagnostics.h index 434dc437..a69a42b6 100644 --- a/src/infra/Diagnostics.h +++ b/src/infra/Diagnostics.h @@ -45,6 +45,22 @@ #endif namespace Diagnostics { +namespace detail { +// A view can share one allocation with another view; VERIFY_NO_ALIAS_BUF refuses them at compile time (§6). +// Detected structurally so this header stays library-free (no //): std::span is +// the standard type with a static `extent`; std::basic_string_view has `traits_type` and, unlike basic_string, +// no `allocator_type`. A custom view is the author's own contract to keep. +template struct StripCvRef { using type = T; }; +template struct StripCvRef { using type = T; }; +template struct StripCvRef { using type = T; }; +template struct StripCvRef { using type = T; }; +template struct StripCvRef { using type = typename StripCvRef::type; }; +template struct StripCvRef { using type = typename StripCvRef::type; }; +template using Bare = typename StripCvRef::type; +template concept HasStaticExtent = requires { Bare::extent; }; +template concept HasTraitsNoAllocator = requires { typename Bare::traits_type; } && !requires { typename Bare::allocator_type; }; +template inline constexpr bool isView = HasStaticExtent || HasTraitsNoAllocator; +} // namespace detail class ConsoleLog { public: @@ -187,33 +203,141 @@ uint64_t currentThreadId() noexcept; #define TODO_IMPLEMENT() VERIFY_NOT_REACHED_TEXT("Feature not yet implemented.") // -------------------------------------------------------------------------- -// 6. VERIFY_NO_ALIAS — catch accidental self-aliasing in debug builds +// 6. VERIFY_NO_ALIAS — a debug check AND a release optimizer fact // // Use in functions that WRITE through one reference while READING another of // the same type, where passing the same object twice would silently produce a -// wrong result (e.g. out-parameters of decompose/extract functions, or a -// destination that is read mid-computation). +// wrong result (out-parameters of decompose/extract functions, a destination +// that is read mid-computation). // -// Takes two objects (not pointers); compares their addresses. Fires VERIFY if -// they are the same object. Compiles to nothing in release builds. +// Debug: VERIFY_TEXT fires if a and b are the same object (exact address +// equality — it does NOT catch partial overlap). +// Release: VERIFY_TEXT is an inert __builtin_assume that alias analysis never +// reads (measured 2026-09-12: codegen byte-identical to no macro at +// all). RW_ASSUME_SEPARATE_STORAGE is the part that does the work — it +// lowers to `llvm.assume [ "separate_storage"(a, b) ]`, which BasicAA +// consumes, so the codegen matches `__restrict__` on the parameters +// exactly. Measured at -O2 -DNDEBUG, instructions: +// `out=a; out+=b; out+=a;` arm64 10 -> 6, x86-64 11 -> 9 +// `dst[i] += k*src[i]` loop arm64 62 -> 56, x86-64 68 -> 45 +// (the loop delta is the runtime overlap check plus the scalar +// fallback loop LLVM emits when it cannot prove dst and src disjoint). +// On a compiler without __builtin_assume_separate_storage (GCC, clang +// < 17) the assumption is `( (void)0 )` and the debug check still runs. +// BasicAA reads the bundle only when its `basic-aa-separate-storage` +// option is on: off by default in LLVM 17 (AppleClang 16 / Xcode +// 16.2), on from LLVM 18. CMakeLists.txt passes +// `-mllvm -basic-aa-separate-storage` to our targets whenever the +// compiler accepts it, so LLVM 17 consumes the promise too (a no-op +// on 18+; test/noaliascheck.sh arm 8 is the `=false` control). +// Even with the option on, LLVM 17 consults the hint only at the +// assume's own context, which the loop vectorizer's alias queries +// never carry (llvm/llvm-project#64666, fixed in LLVM 18 by #76770): +// on AppleClang 16 the promise removes scalar reloads but leaves a +// loop's runtime overlap check in place. The gate classifies that +// loop path separately (LOOP_CONSUMED / LOOP_NOT_CONSUMED). // -// This is a CORRECTNESS guard, not an optimisation. It documents and enforces -// the no-alias contract that __restrict would assert — without the UB risk of -// __restrict (which would make self-aliasing undefined rather than caught). +// THE CONTRACT (clang/docs/LanguageExtensions.rst, release/19.x): the arguments +// "are assumed to point into separately allocated storage (either different +// variable definitions or different dynamic storage allocations) … 'storage' +// here refers to the outermost enclosing allocation of any particular object +// (so for example, it's never correct to call this function passing the +// addresses of fields in the same struct, elements of the same array, etc.)". +// LangRef: "no pointer based on one of its arguments can alias any pointer +// based on the other." Two elements of one array or two members of one struct +// are a LIE and undefined behaviour in release; the debug check cannot see it. +// +// TWO CONTAINERS need the _BUF form. `separate_storage( &dst, &src )` on two +// std::vector references says the 24-byte headers are separate; the loop body +// indexes the HEAP BUFFERS, reached through the begin_ pointers loaded from +// those headers, and the optimizer cannot infer buffer separation from header +// separation (measured: the object form leaves the loop at 65/65, the .data() +// form drops it to 61 arm64 / 41 x86-64). VERIFY_NO_ALIAS_BUF checks the +// OBJECTS (two live containers never share an allocation) and promises the +// BUFFERS. Empty containers are fine: nothing is ever accessed through a null +// data(), so the promise is vacuous there — the bundle is read only by alias +// queries, which need an access to ask about, and LLVM does not fold `p == q` +// from it (measured at -O3: the compare survives and answers true for two +// empty vectors). The two forms that would avoid the null — promising the +// object address when empty, or a branch around the builtin — both lose the +// whole loop effect (arm64 66/66 vs 61, x86-64 66/65 vs 41), so the plain +// .data() form stays; test/noaliascheck.sh arm 7 runs the release probe on +// two empty vectors. If the function already has a "nothing to do" early +// return on empty input, put the macro AFTER it: the promise then runs on +// non-null buffers and still dominates the loop (measured: 64 vs 61 arm64, +// 44 vs 41 x86-64 — the difference is the emptiness test itself, which costs +// the same without the promise). Do not add an early return for the macro's +// sake; the one line alone is the full effect. +// +// OWNING CONTAINERS ONLY: std::vector, std::string, std::array — anything +// whose .data() is its own allocation (or lies inside the object itself, as +// std::array's and a short std::string's do; two distinct objects are two +// allocations either way). NEVER a view: two std::span or std::string_view +// objects can look into ONE allocation, and the promise is per allocation, +// so even two non-overlapping views would be a lie the release build acts +// on while the object check passes. The macro refuses views at compile +// time (static_assert on Diagnostics::detail::isView); for a pair of views, +// promise the OWNERS they came from, or use VERIFY_NO_ALIAS on the views +// (the object check alone) and accept that the loop keeps its overlap check. +// +// WHY NOT `__restrict` ON THE SIGNATURE. Prefer this macro in the body: it is +// checked in debug, it is the same optimizer fact in release, and it does not +// change the API. If a signature ever does need the qualifier, `__restrict__` +// (double underscore BOTH sides) is the only spelling allowed in this tree. On +// macOS does +// #if __STDC_VERSION__ < 199901 +// #define __restrict +// #endif +// and __STDC_VERSION__ is undefined in C++, so every bare `__restrict` that +// follows any libc/libc++ include is silently deleted — verified by bisect: a +// function lost its noalias IR attributes the moment was included. +// `__restrict__` is a keyword and survives. test/noaliascheck.sh arm 4 sweeps +// src/ for the bare spelling. // // void decompose(const T& src, U& outA, U& outB) { // VERIFY_NO_ALIAS(outA, outB); // the two outputs must be distinct // ... // } +// void axpy(std::vector& dst, const std::vector& src) { +// VERIFY_NO_ALIAS_BUF(dst, src); // the two BUFFERS are separate storage +// ... +// } // -------------------------------------------------------------------------- +#if defined(__has_builtin) + #if __has_builtin(__builtin_assume_separate_storage) + #define RW_ASSUME_SEPARATE_STORAGE(p, q) __builtin_assume_separate_storage((p), (q)) + #endif +#endif +#if !defined(RW_ASSUME_SEPARATE_STORAGE) + #define RW_ASSUME_SEPARATE_STORAGE(p, q) ( (void)0 ) // GCC / clang < 17: no equivalent; the debug check still runs +#endif + +// Debug: VERIFY_TEXT fires if a and b are the same object. Release: VERIFY_TEXT +// is an inert assume, and RW_ASSUME_SEPARATE_STORAGE hands the optimizer what +// __restrict__ on the parameters would have. #define VERIFY_NO_ALIAS(a, b) \ - VERIFY_TEXT( static_cast(&(a)) != static_cast(&(b)), \ - "aliasing violation: '" #a "' and '" #b "' are the same object" ) + do { \ + VERIFY_TEXT( static_cast(&(a)) != static_cast(&(b)), \ + "aliasing violation: '" #a "' and '" #b "' are the same object" ); \ + RW_ASSUME_SEPARATE_STORAGE( &(a), &(b) ); \ + } while (0) // Three-way variant for functions with three outputs (e.g. decomposeToTRS). #define VERIFY_NO_ALIAS3(a, b, c) \ do { VERIFY_NO_ALIAS(a, b); VERIFY_NO_ALIAS(a, c); VERIFY_NO_ALIAS(b, c); } while (0) +// Two containers: the OBJECTS must be distinct (checked), and the fact the loop +// needs is that their BUFFERS are separate storage (promised via .data()). +#define VERIFY_NO_ALIAS_BUF(a, b) \ + do { \ + static_assert( !::Diagnostics::detail::isView \ + && !::Diagnostics::detail::isView, \ + "VERIFY_NO_ALIAS_BUF: a view (std::span / std::string_view) can share one allocation with another view; promise the owning containers instead" ); \ + VERIFY_TEXT( static_cast(&(a)) != static_cast(&(b)), \ + "aliasing violation: '" #a "' and '" #b "' are the same container" ); \ + RW_ASSUME_SEPARATE_STORAGE( (a).data(), (b).data() ); \ + } while (0) + // -------------------------------------------------------------------------- // 7. Benchmark micro-helpers — DoNotOptimize / ClobberMemory // diff --git a/test/binoverridecheck.sh b/test/binoverridecheck.sh index 8851b3f0..8799dd5b 100755 --- a/test/binoverridecheck.sh +++ b/test/binoverridecheck.sh @@ -106,6 +106,7 @@ EXEMPT = { "optremarkscheck.sh": "checks -DRIPWIRE_OPT_REMARKS/-DRIPWIRE_PGO CMake config text; no binary invocation", "optremarkshotcheck.sh": "audits scripts/optremarks.py's HOT_FILES/COLD_FILES against the SOURCE TREE (os.walk over src/, plus each file's own RIPWIRE__TU guard); the subject is a triage list versus the files it claims to cover, so no ripwire binary is bound or executed at all", "pargatescheck.sh": "meta-check of test/pargates.py's own source; pure file check", + "noaliascheck.sh": "compiles its OWN $CXX probes against src/infra/Diagnostics.h (debug trap, -O2 -DNDEBUG IR + objdump bands, the GCC-shape preprocess, the =false control) and greps src/ for a bare __restrict; READS build/CMakeCache.txt for the front end and CMake's -basic-aa-separate-storage probe result but never invokes build/ripwire — the file contains neither RIPWIRE_BIN nor $BIN", "pmccheck.sh": "builds its OWN standalone harness binary, independent of build/ripwire", "portablebuildcheck.sh": "CMake-configure-level gate only; the gate's own banner says 'no ripwire binary needed'", "qschemetripcheck.sh": "greps src/quality.h's tripwire comment against the test/*.sh manifest; pure file check", diff --git a/test/noaliascheck.sh b/test/noaliascheck.sh new file mode 100755 index 00000000..30b5e32d --- /dev/null +++ b/test/noaliascheck.sh @@ -0,0 +1,520 @@ +#!/usr/bin/env bash +# noaliascheck.sh — VERIFY_NO_ALIAS (src/infra/Diagnostics.h §6) is a DEBUG CHECK and a RELEASE OPTIMIZER FACT. +# +# THE DEFICIENCY THIS GATE EXISTS FOR (measured 2026-09-12, Apple clang 21, arm64 and x86-64). The macro +# used to be `VERIFY_TEXT( &a != &b, … )`, which -DNDEBUG lowers to `__builtin_assume( &a != &b )`. LLVM +# keeps that assume in the IR and alias analysis never reads it: codegen was BYTE-IDENTICAL to having no +# macro at all, while the header said "the no-alias contract that __restrict would assert". The +# replacement adds `__builtin_assume_separate_storage( &a, &b )` (clang 17+), which BasicAA does consume; +# codegen then matches `__restrict__` on the parameters exactly (`out=a; out+=b; out+=a;` arm64 10 -> 6). +# +# ARMS. Every optimizer arm compiles its OWN probe with the system compiler at -O2 -DNDEBUG, because the dev +# build (no build type) never defines NDEBUG and the release expansion is otherwise only ever exercised by +# CI's Release flavour: +# 1 debug catches: VERIFY_NO_ALIAS3( out, a, b ) with distinct objects exits 0; `acc( x, y, x )` traps and +# stderr names BOTH expressions ('out' and 'b'). +# 2 release optimizes: the function's IR carries "separate_storage" and NO reload of `a` after the store +# to `out`; the objdump instruction count sits in a band BELOW the plain function. +# 3 NEGATIVE CONTROL: the same body under the OLD definition, inlined in the probe as OLD_NO_ALIAS, must +# still carry the reload and NO "separate_storage". Arms 2 and 3 must DISAGREE — if +# they ever agree the gate examined one population twice and fails itself. +# 4 the macOS trap: zero bare `__restrict` in src/ CODE (comments and string literals stripped: on +# macOS #defines `__restrict` to nothing in C++, so only `__restrict__` +# survives). Positive control: a temp file with a bare qualifier the SAME scan catches. +# 5 GCC shape: with `__has_builtin` forced to 0 (and __clang__ undefined) the -DNDEBUG header must +# still compile, expanding to the `( (void)0 )` fallback and never to the builtin; the +# natural expansion on a builtin-capable compiler must contain the builtin (contrast). +# Release flavour only, and a probe with no library headers: libc++ itself spells +# `__has_builtin( __remove_reference_t )` and refuses to compile with the macro forced +# to 0, and the debug header includes . The debug fallback is compiled for real +# by arms 1 and 7 whenever $CXX is a compiler without the builtin (the gcc CI legs). +# 6 buffer form: VERIFY_NO_ALIAS_BUF( dst, src ) on two std::vector makes the release +# `dst[i] += src[i]*3` loop SHORTER than plain; VERIFY_NO_ALIAS on the same two OBJECTS +# must NOT (the header separation says nothing about the heap buffers) — the buffer +# form's negative control. +# 7 buffer debug: two EMPTY vectors pass (the promise over two null data() is vacuous); the same vector +# twice traps naming both expressions ('dst' and 'src'). +# +# 8 NEGATIVE CONTROL for the probe itself: the arm-2 probe compiled with `-mllvm -basic-aa-separate-storage=false` +# MUST bring the reload back and land at plain's instruction count. On LLVM 18+ (every dev +# machine here) forcing the option off is the only way to exercise the LLVM 17 path locally, +# and it shows the arm-2 measurement is able to fail. +# +# THE OPTIMIZER HALF IS A SEPARATE SWITCH (found 2026-09-12 by CI job "release (macos-14, plain, appleclang, shard +# 4/4)" on PR #200: arm 7 and the IR-bundle row green, arms 2, 2/3 and 6 red). The front end has emitted the +# "separate_storage" bundle since clang 17, but BasicAA reads it only when its `basic-aa-separate-storage` option is +# on — llvm/lib/Analysis/BasicAliasAnalysis.cpp has `cl::init(false)` in LLVM 17 and `cl::init(true)` from 18. Xcode +# 16.2's AppleClang 16 is LLVM 17: it accepts the builtin, emits the bundle, and generates the same code as no promise +# at all. CMakeLists.txt therefore passes `-mllvm -basic-aa-separate-storage` to our targets whenever the compiler +# accepts it. So `__has_builtin` is NOT the capability probe here — the real slice is. The arm-2 probe is compiled +# three ways at -O2 -DNDEBUG (default, `=true`, `=false`) and the compiler is CLASSIFIED by whether accBuiltin — +# the builtin called DIRECTLY, so the verdict is about the compiler and not about the header on disk — reloads `a` +# after the store: +# CONSUMED_DEFAULT default has no reload (LLVM 18+) +# CONSUMED_WITH_FLAG default reloads, `=true` does not (LLVM 17 / AppleClang 16 — the CMake flag is load-bearing) +# NOT_CONSUMED both reload — the optimizer cannot be made to read the bundle +# NO_BUILTIN no __builtin_assume_separate_storage (GCC, clang < 17) +# Arms 2, 3 and 6 then compile with exactly the option CMake adds, and CMake's own cached probe result +# (RIPWIRE_CXX_HAS_BASIC_AA_SEPARATE_STORAGE in build/CMakeCache.txt, or $RIPWIRE_CMAKE_CACHE) must AGREE with this +# gate's — a disagreement is a FAIL, because the gate exists to measure what build/ripwire was built with; for the +# same reason $CXX defaults to the cache's CMAKE_CXX_COMPILER, not to whatever `c++` is on PATH. On NO_BUILTIN and +# NOT_CONSUMED arms 2, 3, 6 and 8 are reported as WARN (skipped, naming the compiler), never as PASS: the gcc CI +# legs run arms 1, 4, 5, 7; the clang legs run all eight. +# +# A SECOND capability sits behind the first. Reading the bundle for a scalar reload (arm 2) and reaching the loop +# vectorizer's overlap analysis (arm 6) are different code paths: LLVM 17 consults the hint only at the assume's own +# context, which LoopAccessAnalysis never supplies, so on AppleClang 16 the flag fixes arm 2 and leaves arm 6 at +# plain (CI, PR #200, head 5156d668: arm 2 green, arm 6 "48 is not below plain (48)"). Upstream: llvm/llvm-project +# issue #64666 "[Loop Vectorizer] __builtin_assume_separate_storage is not propagated to LV AA", fixed by #76770 +# (LLVM 18). So arm 6 classifies the LOOP path on its own real slice — axpyBuiltin, the builtin on .data() called +# directly — LOOP_CONSUMED (axpyBuiltin at least 2 below plain: the macro form must match, hard FAIL otherwise) or +# LOOP_NOT_CONSUMED (WARN naming the compiler and #64666; the one row still asserted is that the macro form is no +# worse than the direct builtin, so a header regression cannot hide behind the compiler's limit). +# Counts are BANDS, never exact numbers — an LLVM release moves them by one or two. +# +# Usage: bash test/noaliascheck.sh (compiles with build/'s CMAKE_CXX_COMPILER; objdump = llvm-objdump or GNU) +# CXX=clang++ RIPWIRE_CMAKE_CACHE=/nonexistent bash test/noaliascheck.sh (classify ANOTHER compiler: the +# cache cross-check is then WARN/skipped; a CXX that is not the cached compiler with the cache present is +# a FAIL, because the gate exists to measure what build/ripwire was built with) +# Exit: 0 = clean · 1 = an arm failed · 2 = a prerequisite is missing + +set -u +ROOT="$( cd "$( dirname "$0" )/.." && pwd )" +# The build tree whose binary this gate must measure: its CMakeCache.txt names the front end and holds CMake's +# probe result. RIPWIRE_CMAKE_CACHE points at another tree's cache; CXX in the environment still wins. +CACHE="${RIPWIRE_CMAKE_CACHE:-$ROOT/build/CMakeCache.txt}" +cacheVar(){ [ -f "$CACHE" ] && sed -n "s/^$1:[A-Z]*=//p" "$CACHE" | head -1; return 0; } +CXX="${CXX:-$( cacheVar CMAKE_CXX_COMPILER )}"; CXX="${CXX:-c++}" +OBJDUMP="${OBJDUMP:-objdump}" +HDR="$ROOT/src/infra/Diagnostics.h" +WORK="$( mktemp -d )"; trap 'rm -rf "$WORK"' EXIT +fail=0 +ok(){ printf ' PASS %s\n' "$*" || { fail=1; printf ' FAIL could not write the PASS line for: %s\n' "$*"; }; return 0; } +no(){ printf ' FAIL %s\n' "$*"; fail=1; } +warn(){ printf ' WARN %s\n' "$*"; } + +# §CI-P3: ask THIS front end how it spells C++23 (scripts/cxxstd.sh — AppleClang 15 rejects -std=c++23). +. "$ROOT/scripts/cxxstd.sh" +CXXSTD="$( ripwire_cxx_std_flag "$CXX" )" +INC=( -I"$ROOT/src/infra" ) +# the -U__clang__ / -D__has_builtin overrides in arm 5 legitimately redefine builtin macros +QUIET=( -Wno-macro-redefined -Wno-builtin-macro-redefined ) + +[ -f "$HDR" ] || { echo "no $HDR"; exit 2; } +command -v "$CXX" >/dev/null 2>&1 || { echo "no C++ compiler at $CXX"; exit 2; } +command -v "$OBJDUMP" >/dev/null 2>&1 || { echo "no objdump at $OBJDUMP"; exit 2; } +grep -q 'define VERIFY_NO_ALIAS' "$HDR" || { echo " FAIL presence guard: $HDR defines no VERIFY_NO_ALIAS"; exit 1; } + +echo "noaliascheck: CXX=$CXX OBJDUMP=$OBJDUMP" + +# ── does THIS compiler have the builtin? decides which arms can fire; reported, never assumed ───────────────── +printf '#if __has_builtin(__builtin_assume_separate_storage)\n#error HAS_SEPARATE_STORAGE\n#endif\nint main(){}\n' > "$WORK/hb.cpp" +if "$CXX" "$CXXSTD" -fsyntax-only "$WORK/hb.cpp" 2>&1 | grep -q 'HAS_SEPARATE_STORAGE'; then HAS_BUILTIN=1; else HAS_BUILTIN=0; fi +echo " info __builtin_assume_separate_storage available: $HAS_BUILTIN" + +# instruction count of ONE function in an object file. --no-show-raw-insn keeps GNU objdump from wrapping +# long x86 encodings onto continuation lines that also start with an address. The probes open with an anchor +# function so no real function sits on the section's local label (`` on Mach-O), and the symbol may +# carry a leading underscore (Mach-O) or not (ELF). +insnCount(){ "$OBJDUMP" -d --no-show-raw-insn "$1" | awk -v want="$2" ' + /^[0-9a-f]+ :") ) } + inside && /^[[:space:]]*[0-9a-f]+:/ { n++ } + END { print n + 0 }'; } +# the IR block of ONE function: `define … @NAME(` through the closing brace +irBlock(){ awk -v want="$2" '/^define / { inside = index( $0, "@" want "(" ) > 0 } inside { print } /^}/ { inside = 0 }' "$1"; } +# 1 if the block loads from %a AFTER a store to %out (the reload the assumption is supposed to remove), else 0 +reloadAfterStore(){ awk '/store .*ptr %out/ { stored = 1 } stored && /load .*ptr %a,/ { hit = 1 } END { print hit + 0 }'; } + +# ── the scalar probe: header macro (accNew), the OLD definition inlined (accOld), no macro (accPlain) ──────── +cat > "$WORK/probe.cpp" <<'EOF' +#include +#include +#include +#include "Diagnostics.h" + +// The definition this tree shipped before 2026-09-12, inlined verbatim as the NEGATIVE CONTROL: in release it +// is a bare __builtin_assume( &a != &b ), which alias analysis never reads. +#define OLD_NO_ALIAS( a, b ) \ + VERIFY_TEXT( static_cast( &( a ) ) != static_cast( &( b ) ), \ + "aliasing violation: '" #a "' and '" #b "' are the same object" ) +#define OLD_NO_ALIAS3( a, b, c ) do { OLD_NO_ALIAS( a, b ); OLD_NO_ALIAS( a, c ); OLD_NO_ALIAS( b, c ); } while( 0 ) + +extern "C" void noalias_probe_anchor() {} // keeps the first real function off the section's local label + +extern "C" __attribute__(( noinline )) void accNew( uint32_t& out, const uint32_t& a, const uint32_t& b ) +{ + VERIFY_NO_ALIAS3( out, a, b ); + out = a; out += b; out += a; +} +extern "C" __attribute__(( noinline )) void accOld( uint32_t& out, const uint32_t& a, const uint32_t& b ) +{ + OLD_NO_ALIAS3( out, a, b ); + out = a; out += b; out += a; +} +extern "C" __attribute__(( noinline )) void accPlain( uint32_t& out, const uint32_t& a, const uint32_t& b ) +{ + out = a; out += b; out += a; +} +// The builtin called DIRECTLY, no header macro in the way: what the classification probe measures, so that it +// classifies the COMPILER and not whichever Diagnostics.h happens to be on disk (the old header must red arm 2, +// not turn the compiler into NOT_CONSUMED). +extern "C" __attribute__(( noinline )) void accBuiltin( uint32_t& out, const uint32_t& a, const uint32_t& b ) +{ +#if defined(__has_builtin) +#if __has_builtin(__builtin_assume_separate_storage) + __builtin_assume_separate_storage( &out, &a ); __builtin_assume_separate_storage( &out, &b ); __builtin_assume_separate_storage( &a, &b ); +#endif +#endif + out = a; out += b; out += a; +} + +int main( int argc, char** argv ) +{ + uint32_t x = 3u, y = 5u, z = 7u; + if( argc > 1 && std::strcmp( argv[ 1 ], "alias" ) == 0 ) { accNew( x, y, x ); } + else { accNew( x, y, z ); } + std::printf( "%u\n", x ); + return x == 5u + 7u + 5u ? 0 : 3; +} +EOF + +# ── does THIS compiler's OPTIMIZER consume the bundle? the real slice, three ways — see the header ─────────── +# SEP_OPT is byte-for-byte the option CMakeLists.txt attaches to our targets; SEP_ON / SEP_OFF are its explicit +# forms for the classification and the arm-8 control. On a compiler that rejects -mllvm none of them compiles. +SEP_OPT=( -mllvm -basic-aa-separate-storage ); SEP_ON=( -mllvm -basic-aa-separate-storage=true ); SEP_OFF=( -mllvm -basic-aa-separate-storage=false ) +CXXID="$( "$CXX" --version 2>/dev/null | head -1 )" +# acceptance is probed on a TU that compiles CLEAN without the option (hb.cpp above deliberately does not: its +# #error is the __has_builtin tell), and asserted so, or an unrelated compile error would read as "rejected" +printf 'int main() { return 0; }\n' > "$WORK/flag.cpp" +"$CXX" "$CXXSTD" -O2 -c "$WORK/flag.cpp" -o "$WORK/flag.o" 2>/dev/null || no "probe: the acceptance TU does not compile even WITHOUT the option — the acceptance probe cannot say anything" +FLAG_ACCEPTED=0 +if "$CXX" "$CXXSTD" -O2 "${SEP_OPT[@]}" -c "$WORK/flag.cpp" -o "$WORK/flag.o" 2>/dev/null; then FLAG_ACCEPTED=1; fi +# 0/1: does accBuiltin (the builtin called directly — the compiler, not the header, is what is classified) reload a +# after the store when the probe is compiled with the given extra flags? +probeReload(){ "$CXX" "$CXXSTD" -O2 -DNDEBUG -fno-discard-value-names "${INC[@]}" "$@" -S -emit-llvm "$WORK/probe.cpp" -o "$WORK/cls.ll" 2>/dev/null \ + && irBlock "$WORK/cls.ll" accBuiltin | reloadAfterStore; } +CLASS=NO_BUILTIN; rDefault=-; rOn=-; rOff=- +if [ "$HAS_BUILTIN" = 1 ]; then + rDefault="$( probeReload )"; rDefault="${rDefault:--}" + if [ "$FLAG_ACCEPTED" = 1 ]; then rOn="$( probeReload "${SEP_ON[@]}" )"; rOff="$( probeReload "${SEP_OFF[@]}" )"; rOn="${rOn:--}"; rOff="${rOff:--}"; fi + if [ "$rDefault" = 0 ]; then CLASS=CONSUMED_DEFAULT + elif [ "$rDefault" = 1 ] && [ "$rOn" = 0 ]; then CLASS=CONSUMED_WITH_FLAG + else CLASS=NOT_CONSUMED; fi +fi +echo " info compiler: $CXXID" +echo " info optimizer consumes \"separate_storage\": $CLASS (accBuiltin reload after store — default: $rDefault, =true: $rOn, =false: $rOff; -mllvm -basic-aa-separate-storage accepted: $FLAG_ACCEPTED)" +# the option arms 2, 3 and 6 compile with: exactly what CMake adds when the compiler accepts it, nothing otherwise +OPTFLAGS=(); if [ "$FLAG_ACCEPTED" = 1 ]; then OPTFLAGS=( "${SEP_OPT[@]}" ); fi +RELEASE_ARMS=0; case "$CLASS" in CONSUMED_DEFAULT|CONSUMED_WITH_FLAG) RELEASE_ARMS=1;; esac +# CMake's cached probe must agree with this one, or the gate is measuring a different toolchain than the binary +# ... and the cached boolean only describes the compiler CMake configured with. A CXX override that resolves to a +# DIFFERENT compiler can accept the same flag with different optimizer defaults, so the booleans agreeing would +# prove nothing: the identities (`--version` first line) must match before the booleans are compared. +cachedCXX="$( cacheVar CMAKE_CXX_COMPILER )"; cachedID="" +if [ -n "$cachedCXX" ] && [ -x "$cachedCXX" ]; then cachedID="$( "$cachedCXX" --version 2>/dev/null | head -1 )"; fi +if [ -f "$CACHE" ] && [ -n "$cachedCXX" ] && [ "$cachedID" != "$CXXID" ]; then + no "probe: CXX '$CXX' ($CXXID) is not the compiler build/ripwire was built with ('$cachedCXX': ${cachedID:-not runnable here}) — this gate measures the binary's toolchain; to classify another compiler point RIPWIRE_CMAKE_CACHE at a tree built with it (or at a nonexistent path to skip the cross-check)" +elif [ -f "$CACHE" ]; then + if grep -q '^RIPWIRE_CXX_HAS_BASIC_AA_SEPARATE_STORAGE:' "$CACHE"; then + cmakeAccepted=0; [ "$( cacheVar RIPWIRE_CXX_HAS_BASIC_AA_SEPARATE_STORAGE )" = 1 ] && cmakeAccepted=1 + if [ "$cmakeAccepted" = "$FLAG_ACCEPTED" ]; then ok "probe: CMake's cached probe agrees (RIPWIRE_CXX_HAS_BASIC_AA_SEPARATE_STORAGE=$cmakeAccepted, CMAKE_CXX_COMPILER=$( cacheVar CMAKE_CXX_COMPILER ); gate CXX=$CXX)" + else no "probe: CMake's cached probe DISAGREES — cache says accepted=$cmakeAccepted (CMAKE_CXX_COMPILER=$( cacheVar CMAKE_CXX_COMPILER )), this gate says $FLAG_ACCEPTED (CXX=$CXX); the gate is not measuring what the binary was built with"; fi + else + no "probe: $CACHE has no RIPWIRE_CXX_HAS_BASIC_AA_SEPARATE_STORAGE row — a configure older than the probe; reconfigure (cmake -S . -B build)" + fi +else + warn "probe: no $CACHE — CMake's side of the probe is unchecked (configure build/, or set RIPWIRE_CMAKE_CACHE)" +fi + +# ── arm 1: debug catches ───────────────────────────────────────────────────────────────────────────────────── +if "$CXX" "$CXXSTD" -O1 -g -Wall -Wextra "${INC[@]}" "$WORK/probe.cpp" "$ROOT/src/infra/diagnostics.cpp" -o "$WORK/probe_dbg" 2> "$WORK/cc1.log"; then + ok "arm 1: debug probe compiled against $HDR" +else + no "arm 1: debug probe failed to compile"; sed 's/^/ /' "$WORK/cc1.log" +fi +if [ -x "$WORK/probe_dbg" ]; then + "$WORK/probe_dbg" > "$WORK/d1.out" 2> "$WORK/d1.err"; rc=$? + if [ "$rc" = 0 ] && grep -q '^17$' "$WORK/d1.out"; then ok "arm 1: distinct objects -> exit 0, out = 17" + else no "arm 1: distinct objects: rc=$rc out=$( cat "$WORK/d1.out" )"; sed 's/^/ /' "$WORK/d1.err" | head -8; fi + # the two-command subshell (no exec optimisation) keeps bash's own "Trace/BPT trap" job message off the + # gate's output; the probe's stderr is the file + ( "$WORK/probe_dbg" alias > "$WORK/d2.out" 2> "$WORK/d2.err"; exit $? ) 2>/dev/null; rc=$? + if [ "$rc" != 0 ]; then ok "arm 1: acc( x, y, x ) traps in debug (rc=$rc)" + else no "arm 1: acc( x, y, x ) exited 0 in debug — VERIFY_NO_ALIAS3 did not fire"; fi + if grep -q "'out' and 'b' are the same object" "$WORK/d2.err"; then ok "arm 1: stderr names both expressions ('out' and 'b')" + else no "arm 1: stderr does not name 'out' and 'b'"; sed 's/^/ /' "$WORK/d2.err" | head -8; fi +fi + +# ── arms 2 + 3: release IR and codegen, header macro vs the OLD definition ─────────────────────────────────── +if [ "$RELEASE_ARMS" = 1 ]; then + if "$CXX" "$CXXSTD" -O2 -DNDEBUG -fno-discard-value-names "${INC[@]}" ${OPTFLAGS[@]+"${OPTFLAGS[@]}"} -S -emit-llvm "$WORK/probe.cpp" -o "$WORK/probe.ll" 2> "$WORK/cc2.log" \ + && "$CXX" "$CXXSTD" -O2 -DNDEBUG "${INC[@]}" ${OPTFLAGS[@]+"${OPTFLAGS[@]}"} -c "$WORK/probe.cpp" -o "$WORK/probe.o" 2>> "$WORK/cc2.log"; then + ok "arm 2: release probe compiled (-O2 -DNDEBUG${OPTFLAGS[@]+ ${OPTFLAGS[*]}}, IR + object)" + irBlock "$WORK/probe.ll" accNew > "$WORK/new.ll" + irBlock "$WORK/probe.ll" accOld > "$WORK/old.ll" + irBlock "$WORK/probe.ll" accPlain > "$WORK/plain.ll" + # presence guard: every block was actually extracted (an empty block would make every grep below vacuous) + for f in new old plain; do + grep -q '^define ' "$WORK/$f.ll" || no "arm 2: could not extract the IR block of acc${f} (wrong artifact)" + done + newLoadsA="$( grep -c 'load i32, ptr %a,' "$WORK/new.ll" )"; oldLoadsA="$( grep -c 'load i32, ptr %a,' "$WORK/old.ll" )" + newReload="$( reloadAfterStore < "$WORK/new.ll" )"; oldReload="$( reloadAfterStore < "$WORK/old.ll" )" + # arm 2 + if grep -q '"separate_storage"' "$WORK/new.ll"; then ok "arm 2: accNew IR carries the \"separate_storage\" assume bundle" + else no "arm 2: accNew IR has no \"separate_storage\" bundle — the macro is not an optimizer fact"; fi + if [ "$newReload" = 0 ] && [ "$newLoadsA" = 1 ]; then ok "arm 2: accNew loads a ONCE, no reload after the store to out (loads of a: $newLoadsA)" + else no "arm 2: accNew still reloads a after the store to out (loads of a: $newLoadsA, reload=$newReload)"; fi + # arm 3, the negative control + if ! grep -q '"separate_storage"' "$WORK/old.ll"; then ok "arm 3: accOld (old definition) IR carries no \"separate_storage\"" + else no "arm 3: accOld carries \"separate_storage\" — the control is not the old definition"; fi + if [ "$oldReload" = 1 ] && [ "$oldLoadsA" = 2 ]; then ok "arm 3: accOld still reloads a after the store (loads of a: $oldLoadsA) — the old assume was inert" + else no "arm 3: accOld does not show the reload (loads of a: $oldLoadsA, reload=$oldReload) — the control lost its defect"; fi + if [ "$newReload" != "$oldReload" ]; then ok "arm 2/3: contrast — the two populations DISAGREE (new reload=$newReload, old reload=$oldReload)" + else no "arm 2/3: NO CONTRAST — arms 2 and 3 agree (reload=$newReload); the gate examined one population twice"; fi + # objdump bands + cNew="$( insnCount "$WORK/probe.o" accNew )"; cOld="$( insnCount "$WORK/probe.o" accOld )"; cPlain="$( insnCount "$WORK/probe.o" accPlain )" + echo " info release instructions: accPlain=$cPlain accOld=$cOld accNew=$cNew (measured 2026-09-12: arm64 10/10/6, x86-64 11/11/9)" + if [ "$cPlain" -ge 6 ] && [ "$cPlain" -le 16 ]; then ok "arm 2: accPlain instruction count $cPlain in band [6,16]" + else no "arm 2: accPlain instruction count $cPlain outside band [6,16] — count the wrong function?"; fi + if [ "$cNew" -ge 3 ] && [ "$cNew" -le $(( cPlain - 2 )) ]; then ok "arm 2: accNew $cNew instructions, at least 2 below plain ($cPlain)" + else no "arm 2: accNew $cNew instructions is not at least 2 below plain ($cPlain) — the assumption bought nothing"; fi + if [ "$cOld" -ge $(( cPlain - 1 )) ]; then ok "arm 3: accOld $cOld instructions, no better than plain ($cPlain)" + else no "arm 3: accOld $cOld instructions beats plain ($cPlain) — the old definition is not inert on this compiler; re-examine the control"; fi + else + no "arm 2: release probe failed to compile"; sed 's/^/ /' "$WORK/cc2.log" + fi +elif [ "$CLASS" = NOT_CONSUMED ]; then + warn "arms 2 and 3 skipped: NOT_CONSUMED — $CXXID has the builtin but its optimizer never reads the bundle (reload: default $rDefault, =true $rOn); the codegen rows cannot pass here and are not claimed" +else + warn "arms 2 and 3 skipped: $CXX has no __builtin_assume_separate_storage (needs clang 17+); the debug and shape arms still run" +fi + +# ── arm 4: zero bare __restrict in src/ CODE, with a positive control ──────────────────────────────────────── +# Comments and string literals are stripped first (the §6 comment explains the trap by naming it, and +# src/layout.h lists the token in a keyword table); `__restrict__` is neutralised BEFORE the match, so a line +# carrying both spellings cannot hide the bare one behind a `grep -v`. +bareRestrict(){ grep -rnE '__restrict' --include='*.h' --include='*.hpp' --include='*.cpp' --include='*.cc' --include='*.c' --include='*.mm' --include='*.cu' --include='*.cuh' "$1" \ + | sed -E 's/"([^"\\]|\\.)*"//g; s#//.*$##; s#/\*[^*]*\*/##g; s/__restrict__/RESTRICT_OK/g' \ + | grep -E '__restrict([^A-Za-z0-9_]|$)'; } +mkdir -p "$WORK/pos" "$WORK/neg" +printf 'void f( int* __restrict p, const int* __restrict__ q );\n' > "$WORK/pos/bare.cpp" +printf '// the macOS trap: __restrict is #defined away\nstatic const char* kw[] = { "__restrict" };\nvoid g( int* __restrict__ p );\n' > "$WORK/neg/clean.cpp" +grep -q '__restrict p' "$WORK/pos/bare.cpp" || no "arm 4: positive-control fixture did not take" +if bareRestrict "$WORK/pos" > "$WORK/pos.hits" && grep -q 'bare.cpp:1:' "$WORK/pos.hits"; then ok "arm 4: positive control — the scan catches a bare __restrict beside a __restrict__" +else no "arm 4: positive control — the scan MISSED a bare __restrict (arm 4 cannot fail)"; fi +if bareRestrict "$WORK/neg" > "$WORK/neg.hits"; then no "arm 4: filter control — a comment / string literal / __restrict__ counted as bare:"; sed 's/^/ /' "$WORK/neg.hits" +else ok "arm 4: filter control — comment, string literal and __restrict__ are not bare"; fi +if bareRestrict "$ROOT/src" > "$WORK/src.hits"; then no "arm 4: bare __restrict in src/ code (macOS deletes it in C++; spell it __restrict__):"; sed 's/^/ /' "$WORK/src.hits" +else ok "arm 4: zero bare __restrict in src/ code"; fi + +# ── arm 5: the GCC shape — no builtin, the ( (void)0 ) fallback, still compiles ────────────────────────────── +# No library header but (which Diagnostics.h pulls anyway): the buffer form only needs a `.data()`. +cat > "$WORK/shape.cpp" <<'EOF' +#include +#include "Diagnostics.h" +struct Buf { uint32_t* p; uint32_t* data() const { return p; } }; +void shapeScalar( uint32_t& p, uint32_t& q, uint32_t& r ) { VERIFY_NO_ALIAS( p, q ); VERIFY_NO_ALIAS3( p, q, r ); } +void shapeBuf( Buf& d, Buf& s ) { VERIFY_NO_ALIAS_BUF( d, s ); } +EOF +GCCSHAPE=( -U__clang__ -D__GNUC__=13 '-D__has_builtin(x)=0' "${QUIET[@]}" ) +if "$CXX" "$CXXSTD" -fsyntax-only -DNDEBUG "${GCCSHAPE[@]}" "${INC[@]}" "$WORK/shape.cpp" 2> "$WORK/cc5.log"; then ok "arm 5: header compiles with __has_builtin forced 0 and __clang__ undefined (-DNDEBUG)" +else no "arm 5: header does not compile under the GCC shape (-DNDEBUG)"; sed 's/^/ /' "$WORK/cc5.log" | grep -E 'error' | head -8; fi +# The probe functions are the LAST thing in the TU, so "from shapeScalar to end of file" is exactly their +# expansion — clang's release VERIFY_TEXT carries _Pragma lines, which -E prints on lines of their own, so a +# per-line grep on the function name would see only the first line of each. +"$CXX" "$CXXSTD" -E -DNDEBUG "${GCCSHAPE[@]}" "${INC[@]}" "$WORK/shape.cpp" 2>/dev/null | sed -n '/shapeScalar/,$p' > "$WORK/shape.pp" +grep -q 'shapeScalar' "$WORK/shape.pp" || no "arm 5: preprocessed output lost the probe functions (wrong artifact)" +if grep -q '(void)0' "$WORK/shape.pp" && ! grep -q '__builtin_assume_separate_storage' "$WORK/shape.pp"; then ok "arm 5: GCC shape expands to the ( (void)0 ) fallback, never to the builtin" +else no "arm 5: GCC shape did not expand to the ( (void)0 ) fallback"; sed 's/^/ /' "$WORK/shape.pp" | cut -c1-200 | head -4; fi +if [ "$HAS_BUILTIN" = 1 ]; then + "$CXX" "$CXXSTD" -E -DNDEBUG "${INC[@]}" "$WORK/shape.cpp" 2>/dev/null | sed -n '/shapeScalar/,$p' > "$WORK/shape_nat.pp" + if grep -q '__builtin_assume_separate_storage' "$WORK/shape_nat.pp"; then ok "arm 5: contrast — the natural expansion on this compiler DOES use the builtin" + else no "arm 5: NO CONTRAST — the natural expansion never reaches the builtin either; the fallback arm proves nothing"; fi +fi + +# ── the buffer probe: VERIFY_NO_ALIAS_BUF (axpyBuf) vs VERIFY_NO_ALIAS on the objects (axpyObj) vs plain ────── +cat > "$WORK/bufprobe.cpp" <<'EOF' +#include +#include +#include +#include +#include "Diagnostics.h" + +extern "C" void noalias_buf_anchor() {} + +extern "C" __attribute__(( noinline )) void axpyPlain( std::vector& dst, const std::vector& src ) +{ + for( std::size_t i = 0; i < dst.size(); ++i ) { dst[ i ] += src[ i ] * 3u; } +} +extern "C" __attribute__(( noinline )) void axpyObj( std::vector& dst, const std::vector& src ) +{ + VERIFY_NO_ALIAS( dst, src ); + for( std::size_t i = 0; i < dst.size(); ++i ) { dst[ i ] += src[ i ] * 3u; } +} +extern "C" __attribute__(( noinline )) void axpyBuf( std::vector& dst, const std::vector& src ) +{ + VERIFY_NO_ALIAS_BUF( dst, src ); + for( std::size_t i = 0; i < dst.size(); ++i ) { dst[ i ] += src[ i ] * 3u; } +} +// The builtin on the BUFFERS called directly, no header macro: the loop-path classification probe (LOOP_CONSUMED +// vs LOOP_NOT_CONSUMED is a fact about the compiler, so it must not depend on whichever Diagnostics.h is on disk). +// Guarded: arm 7 compiles this file in DEBUG on the gcc legs too, where the builtin does not exist; there the +// classification never runs (RELEASE_ARMS=0), so the probe function is simply absent. +#if defined( __has_builtin ) +#if __has_builtin( __builtin_assume_separate_storage ) +extern "C" __attribute__(( noinline )) void axpyBuiltin( std::vector& dst, const std::vector& src ) +{ + __builtin_assume_separate_storage( dst.data(), src.data() ); + for( std::size_t i = 0; i < dst.size(); ++i ) { dst[ i ] += src[ i ] * 3u; } +} +#endif +#endif + +int main( int argc, char** argv ) +{ + if( argc > 1 && std::strcmp( argv[ 1 ], "same" ) == 0 ) + { + std::vector v( 4, 1u ); + axpyBuf( v, v ); + std::printf( "%u\n", v[ 0 ] ); + return 0; + } + std::vector d, s; // both empty: data() is null on both, the promise is vacuous + axpyBuf( d, s ); + std::vector d2( 8, 1u ), s2( 8, 2u ); + axpyBuf( d2, s2 ); + std::printf( "%u\n", d2[ 7 ] ); + return d2[ 7 ] == 7u && d.empty() ? 0 : 3; +} +EOF + +# ── arm 6: the buffer form shortens the release loop; the object form must not ────────────────────────────── +if [ "$RELEASE_ARMS" = 1 ]; then + if "$CXX" "$CXXSTD" -O2 -DNDEBUG "${INC[@]}" ${OPTFLAGS[@]+"${OPTFLAGS[@]}"} -c "$WORK/bufprobe.cpp" -o "$WORK/bufprobe.o" 2> "$WORK/cc6.log"; then + ok "arm 6: buffer probe compiled (-O2 -DNDEBUG${OPTFLAGS[@]+ ${OPTFLAGS[*]}})" + bPlain="$( insnCount "$WORK/bufprobe.o" axpyPlain )"; bObj="$( insnCount "$WORK/bufprobe.o" axpyObj )"; bBuf="$( insnCount "$WORK/bufprobe.o" axpyBuf )"; bBuiltin="$( insnCount "$WORK/bufprobe.o" axpyBuiltin )" + echo " info release instructions: axpyPlain=$bPlain axpyObj=$bObj axpyBuf=$bBuf axpyBuiltin=$bBuiltin (measured 2026-09-12: arm64 65/65/61/61, x86-64 65/65/41/41; AppleClang 16 + flag: 48/48/48/48)" + if [ "$bPlain" -ge 20 ] && [ "$bPlain" -le 160 ]; then ok "arm 6: axpyPlain instruction count $bPlain in band [20,160]" + else no "arm 6: axpyPlain instruction count $bPlain outside band [20,160] — count the wrong function?"; fi + LOOPCLASS=LOOP_NOT_CONSUMED; [ "$bBuiltin" -ge 8 ] && [ "$bBuiltin" -le $(( bPlain - 2 )) ] && LOOPCLASS=LOOP_CONSUMED + echo " info optimizer reaches the LOOP vectorizer with \"separate_storage\": $LOOPCLASS (axpyBuiltin $bBuiltin vs plain $bPlain)" + if [ "$LOOPCLASS" = LOOP_CONSUMED ]; then + if [ "$bBuf" -ge 8 ] && [ "$bBuf" -le $(( bPlain - 2 )) ]; then ok "arm 6: VERIFY_NO_ALIAS_BUF loop $bBuf instructions, at least 2 below plain ($bPlain)" + else no "arm 6: VERIFY_NO_ALIAS_BUF loop $bBuf instructions is not below plain ($bPlain) — the buffer promise bought nothing, and the direct builtin did ($bBuiltin): the header is what is wrong"; fi + if [ "$bObj" -ge $(( bPlain - 1 )) ]; then ok "arm 6: negative control — VERIFY_NO_ALIAS on the two OBJECTS leaves the loop at $bObj (plain $bPlain)" + else no "arm 6: negative control lost — the object form ALSO shortened the loop ($bObj vs plain $bPlain); the buffer form is no longer the discriminating one"; fi + else + warn "arm 6: LOOP_NOT_CONSUMED on $CXXID — the direct builtin leaves the loop at $bBuiltin (plain $bPlain): this optimizer does not carry \"separate_storage\" into the loop vectorizer (llvm/llvm-project#64666, fixed in LLVM 18 by #76770); the loop-shortening rows are not claimed here" + if [ "$bBuf" -le "$bBuiltin" ]; then ok "arm 6: VERIFY_NO_ALIAS_BUF loop $bBuf instructions is no worse than the direct builtin ($bBuiltin) — the header is not the limit" + else no "arm 6: VERIFY_NO_ALIAS_BUF loop $bBuf instructions is WORSE than the direct builtin ($bBuiltin) — the header costs something the builtin does not"; fi + fi + else + no "arm 6: buffer probe failed to compile"; sed 's/^/ /' "$WORK/cc6.log" + fi +elif [ "$CLASS" = NOT_CONSUMED ]; then + warn "arm 6 skipped: NOT_CONSUMED on $CXXID — the buffer promise cannot be shown to buy anything here" +else + warn "arm 6 skipped: $CXX has no __builtin_assume_separate_storage" +fi + +# ── arm 7: buffer form in debug — empty vectors pass, the same vector twice traps naming both ─────────────── +if "$CXX" "$CXXSTD" -O1 -g -Wall -Wextra "${INC[@]}" "$WORK/bufprobe.cpp" "$ROOT/src/infra/diagnostics.cpp" -o "$WORK/buf_dbg" 2> "$WORK/cc7.log"; then + ok "arm 7: debug buffer probe compiled" + "$WORK/buf_dbg" > "$WORK/b1.out" 2> "$WORK/b1.err"; rc=$? + if [ "$rc" = 0 ] && grep -q '^7$' "$WORK/b1.out"; then ok "arm 7: two empty vectors, then two distinct ones -> exit 0, d2[7] = 7" + else no "arm 7: distinct / empty vectors: rc=$rc out=$( cat "$WORK/b1.out" )"; sed 's/^/ /' "$WORK/b1.err" | head -8; fi + ( "$WORK/buf_dbg" same > "$WORK/b2.out" 2> "$WORK/b2.err"; exit $? ) 2>/dev/null; rc=$? + if [ "$rc" != 0 ]; then ok "arm 7: axpyBuf( v, v ) traps in debug (rc=$rc)" + else no "arm 7: axpyBuf( v, v ) exited 0 in debug — VERIFY_NO_ALIAS_BUF did not fire"; fi + if grep -q "'dst' and 'src' are the same container" "$WORK/b2.err"; then ok "arm 7: stderr names both expressions ('dst' and 'src')" + else no "arm 7: stderr does not name 'dst' and 'src'"; sed 's/^/ /' "$WORK/b2.err" | head -8; fi +else + no "arm 7: debug buffer probe failed to compile"; sed 's/^/ /' "$WORK/cc7.log" | head -12 +fi +# The RELEASE path on empty containers: data() is null on both, and the promise is made on those nulls. That is +# vacuous, not a lie the optimizer can act on: the bundle is read only by alias queries, which need an access to +# ask about, and no access exists through a null buffer; LLVM does not fold `p == q` from the bundle (measured +# 2026-09-12, -O3: the icmp survives and returns 1 for two empty vectors). The two forms that would avoid the +# null — promising the object address when empty, or a branch around the builtin — both lose the whole loop +# effect (arm64 66/66 vs 61, x86-64 66/65 vs 41), so the plain .data() form is the one the header keeps. +if "$CXX" "$CXXSTD" -O2 -DNDEBUG "${INC[@]}" ${OPTFLAGS[@]+"${OPTFLAGS[@]}"} "$WORK/bufprobe.cpp" "$ROOT/src/infra/diagnostics.cpp" -o "$WORK/buf_rel" 2> "$WORK/cc7r.log"; then + "$WORK/buf_rel" > "$WORK/b3.out" 2> "$WORK/b3.err"; rc=$? + if [ "$rc" = 0 ] && grep -q '^7$' "$WORK/b3.out"; then ok "arm 7: RELEASE probe — two empty vectors (null data() on both), then two distinct ones -> exit 0, d2[7] = 7" + else no "arm 7: RELEASE probe on empty / distinct vectors: rc=$rc out=$( cat "$WORK/b3.out" )"; sed 's/^/ /' "$WORK/b3.err" | head -8; fi +else + no "arm 7: release buffer probe failed to compile"; sed 's/^/ /' "$WORK/cc7r.log" | head -12 +fi + +# ── arm 8: NEGATIVE CONTROL for the probe — the analysis forced OFF must bring the reload back ────────────── +# `-mllvm -basic-aa-separate-storage=false` puts an LLVM 18+ compiler in exactly the state AppleClang 16 / LLVM 17 is +# in before CMake adds the option, so on every dev machine here it is the only way to exercise that path locally. +# Same probe, same extraction, one flag flipped: if the reload does NOT come back, arm 2 was never able to fail. +if [ "$RELEASE_ARMS" = 1 ] && [ "$FLAG_ACCEPTED" = 1 ]; then + if "$CXX" "$CXXSTD" -O2 -DNDEBUG -fno-discard-value-names "${INC[@]}" "${SEP_OFF[@]}" -S -emit-llvm "$WORK/probe.cpp" -o "$WORK/off.ll" 2> "$WORK/cc8.log" \ + && "$CXX" "$CXXSTD" -O2 -DNDEBUG "${INC[@]}" "${SEP_OFF[@]}" -c "$WORK/probe.cpp" -o "$WORK/off.o" 2>> "$WORK/cc8.log"; then + ok "arm 8: release probe compiled with ${SEP_OFF[*]}" + irBlock "$WORK/off.ll" accNew > "$WORK/off_new.ll"; irBlock "$WORK/off.ll" accBuiltin > "$WORK/off_builtin.ll" + for f in off_new off_builtin; do grep -q '^define ' "$WORK/$f.ll" || no "arm 8: could not extract the IR block for $f (wrong artifact)"; done + offLoadsA="$( grep -c 'load i32, ptr %a,' "$WORK/off_new.ll" )"; offReload="$( reloadAfterStore < "$WORK/off_new.ll" )" + if grep -q '"separate_storage"' "$WORK/off_builtin.ll"; then ok "arm 8: the front end still emits the \"separate_storage\" bundle with the analysis off (the flag flips the reader, not the writer)" + else no "arm 8: the bundle vanished with the analysis off — the flag changed the front end, not just BasicAA"; fi + if [ "$offReload" = 1 ] && [ "$offLoadsA" = 2 ]; then ok "arm 8: with the analysis off accNew reloads a after the store (loads of a: $offLoadsA) — the arm-2 measurement can fail" + else no "arm 8: with the analysis off accNew still shows no reload (loads of a: $offLoadsA, reload=$offReload) — the probe cannot distinguish consumed from ignored"; fi + cOffNew="$( insnCount "$WORK/off.o" accNew )"; cOffPlain="$( insnCount "$WORK/off.o" accPlain )" + echo " info release instructions with the analysis off: accPlain=$cOffPlain accNew=$cOffNew (measured 2026-09-12 Apple clang 21 arm64: 9/9)" + if [ "$cOffNew" -ge $(( cOffPlain - 1 )) ]; then ok "arm 8: accNew $cOffNew instructions, no better than plain ($cOffPlain) with the analysis off — what the LLVM 17 leg saw" + else no "arm 8: accNew $cOffNew instructions still beats plain ($cOffPlain) with the analysis off — the option did not take"; fi + if [ -n "${newReload:-}" ] && [ "$newReload" != "$offReload" ]; then ok "arm 2/8: contrast — on and off DISAGREE (on reload=$newReload, off reload=$offReload)" + else no "arm 2/8: NO CONTRAST — arm 2 (reload=${newReload:-unset}) and arm 8 (reload=$offReload) agree; the flag is not what separates them"; fi + # the LOOP classification must be able to fail too: with the analysis off the direct builtin buys the loop nothing + if [ "${LOOPCLASS:-}" = LOOP_CONSUMED ]; then + if "$CXX" "$CXXSTD" -O2 -DNDEBUG "${INC[@]}" "${SEP_OFF[@]}" -c "$WORK/bufprobe.cpp" -o "$WORK/bufoff.o" 2>> "$WORK/cc8.log"; then + lOffPlain="$( insnCount "$WORK/bufoff.o" axpyPlain )"; lOffBuiltin="$( insnCount "$WORK/bufoff.o" axpyBuiltin )" + if [ "$lOffBuiltin" -ge $(( lOffPlain - 1 )) ]; then ok "arm 6/8: with the analysis off axpyBuiltin is $lOffBuiltin, no better than plain ($lOffPlain) — LOOP_CONSUMED can fail" + else no "arm 6/8: axpyBuiltin $lOffBuiltin still beats plain ($lOffPlain) with the analysis off — the loop classification cannot distinguish consumed from ignored"; fi + else + no "arm 6/8: buffer probe failed to compile with ${SEP_OFF[*]}" + fi + fi + else + no "arm 8: release probe failed to compile with ${SEP_OFF[*]}"; sed 's/^/ /' "$WORK/cc8.log" + fi +elif [ "$CLASS" = NOT_CONSUMED ]; then + warn "arm 8 skipped: NOT_CONSUMED on $CXXID — there is no consumed state to contrast against" +else + warn "arm 8 skipped: $CXX has no __builtin_assume_separate_storage or rejects -mllvm (accepted: $FLAG_ACCEPTED)" +fi + +# ── arm 9: VERIFY_NO_ALIAS_BUF refuses views at compile time — two std::span can look into ONE allocation ─── +# separate_storage is a promise per ALLOCATION; two non-overlapping spans over one vector would make it a lie the +# release build acts on while the object check passes. The header static_asserts on Diagnostics::detail::isView. +cat > "$WORK/view.cpp" <<'EOF9' +#include +#include +#include +#include +#include "Diagnostics.h" +void spanPair( std::span d, std::span s ) { VERIFY_NO_ALIAS_BUF( d, s ); (void)d; (void)s; } +EOF9 +cat > "$WORK/view2.cpp" <<'EOF9' +#include +#include "Diagnostics.h" +void svPair( std::string_view a, std::string_view b ) { VERIFY_NO_ALIAS_BUF( a, b ); (void)a; (void)b; } +EOF9 +cat > "$WORK/owner.cpp" <<'EOF9' +#include +#include +#include +#include "Diagnostics.h" +void owners( std::vector& v, std::string& s, std::array& a, std::vector& w, std::string& t, std::array& b ) +{ VERIFY_NO_ALIAS_BUF( v, w ); VERIFY_NO_ALIAS_BUF( s, t ); VERIFY_NO_ALIAS_BUF( a, b ); } +EOF9 +for f in view view2; do + if "$CXX" "$CXXSTD" -fsyntax-only "${INC[@]}" "$WORK/$f.cpp" 2> "$WORK/cc9_$f.log"; then no "arm 9: $f.cpp (a pair of views) COMPILED — VERIFY_NO_ALIAS_BUF no longer refuses views" + elif grep -q 'can share one allocation with another view' "$WORK/cc9_$f.log"; then ok "arm 9: $f.cpp refused at compile time with the view message" + else no "arm 9: $f.cpp failed to compile for some OTHER reason"; sed 's/^/ /' "$WORK/cc9_$f.log" | head -6; fi +done +if "$CXX" "$CXXSTD" -fsyntax-only "${INC[@]}" "$WORK/owner.cpp" 2> "$WORK/cc9_owner.log"; then ok "arm 9: positive control — std::vector / std::string / std::array pairs still compile" +else no "arm 9: owning containers no longer compile"; sed 's/^/ /' "$WORK/cc9_owner.log" | head -6; fi + +[ "$fail" -eq 0 ] && echo "ALL PASS" || { echo "FAILURES ABOVE"; exit 1; } diff --git a/test/regression.sh b/test/regression.sh index 62875d80..0bc2b87c 100755 --- a/test/regression.sh +++ b/test/regression.sh @@ -268,7 +268,7 @@ else RIPWIRE_BIN="$BIN" bash "$ROOT/test/codexdoctorcheck.sh" 2>&1 | sed 's/^/ | /' fi # retired: cacheexclkeycheck — the per-configuration auto-cache key it pinned is a registered NEGATIVE (docs/EVALS.md, "The auto-cache key ignores --exclude", RUN 2026-09-03: a 158K-file root with >= 12 gate configurations thrashed the 2 GiB sweep); the retry design keeps ONE superset blob per root and will bring its own gate -for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck chacheck chaconecheck chainguardcheck chainidcheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck cppbenchcheck cppoperatorcheck cppqualcheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck deeptailcheck defaultceilingcheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check externalvetocheck fficheck fieldaffinitycheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnptrcheck forautobodycheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forlenscheck formatgatecheck formaxtokenscheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck genrecallcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactimportcheck impactpartitioncheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legobundlecheck legocheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck manifestcheck mapdiffcheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstrictschemacheck mcptoolprunecheck mcptranchecheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck outlinecheck overbudgetcommentcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pyshapecheck qackconcurrencycheck qackorigincheck qchurncheck qchurnmemocheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recalltablecheck recalltotalcheck receiptpostcheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyconstcheck rubymetricscheck rubyrecvcheck rubyrequirecheck rubyscopecheck rubysettercheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcontainedcheck shadowcheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck stdqualcheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck emitescapecheck strkerncheck astqueryregexcheck fieldidcheck cachereservecheck childwalkscalecheck preprocdeadscalecheck qddialscheck listingpagingcheck declinecheck extentcheck macroreparsecheck ppdeadrolescheck rubyargcheck sidecarsymlinkcheck crawlescapecheck decltodefcheck ceilingverdictcheck; do +for _g in a9disclosurecheck abicheck accessshapecheck ackonlycheck adaptivecheck adaptivecutshapecheck affectedcheck agentloopclaudecheck agentloopcodexcheck agentloopeditsuitecheck agentloopfollowupcheck agentloopgradercheck agentlooplockcheck agentloopopencodecheck agenttablecheck aiderbytescheck anchorbodycheck anchorcheck archcheck archmetricscheck argvdiffcheck arisefollowupcheck ariseshimcheck aritycheck artifactcheck astqueryregexcheck atcheck atomscheck attrvocabcheck baselinecheck baselinedirtycheck baselineportcheck bashsourcecheck batchcheck binoverridecheck blindspotcheck bm25boundcheck bm25check bodiesshowncheck bodydialectcheck budgetpolicycheck bundleidcheck cachefuzzcheck cachehashcheck cacheidentitycheck cacheisolationcheck cachelintcheck cacheoffsetcheck cachereservecheck cachesplitcheck callerscheck callformcheck callsrankordercheck candheadcheck candidatescheck canoncheck capdisclosurecheck capsweepcheck ccheck ccjsoncheck ceilingverdictcheck chacheck chaconecheck chainguardcheck chainidcheck childwalkscalecheck churndecaycheck churnjoincheck churnjsonstampcheck claudeconfigdircheck clicheck clonebandcheck clonecachecheck clonededupcheck cloneidiomcheck clonelexcheck clsrecvcheck cochangeboostcheck cochangecliocheck cochangesurprisecheck codexinstallhonestycheck codexplugincheck codexwrapcheck collectioncapcheck columnarattrcheck columnarcheck columnarcommacheck commentcoherencecheck communitydrillcheck communitylabelcheck compactlegendcheck compactroutecheck completecheck composelangcheck connectcheck connectcorecheck connectjoincheck constcheck contextratiocheck coplintcheck cppbenchcheck cppoperatorcheck cppqualcheck crawlescapecheck crossdirincludecheck crossrefcheck crossrefdegradecheck csharpcheck csharpcondcheck cudacheck cyclecutcheck dartcheck deadcheck deadfiltercheck deadprecisioncheck deckcheck deckclaimcheck declinecheck decltodefcheck deeptailcheck defaultceilingcheck defoverdeclcheck degradedhintcheck dependencypincheck deplangscheck depsprecisecheck detailcheck didyoumeancheck dispatchordercheck dmmcheck docanchorcheck docdemotecheck docdriftcheck docdriftcommentcheck docmdcachecheck docmentioncheck docscommandscheck doctorcheck donelegendcheck droppedpositivecheck duprowcheck dynmapsimdcheck editcheckanswercheck editcheckcheck editchecknotecheck edithandlehintcheck editpayloadbinarycheck editplancheck editplanpayloadconfinecheck editplanrecheckcheck editplanrollbackmsgcheck editpreviewcheck editroundtripcheck edittargetfileabscheck eliximportcheck elixircheck emitescapecheck emittertruthcheck emptycorpuscheck emptyvaluerefusecheck ensembleavailcheck ensemblecheck essentialcxcheck estchargecheck evalcheck evictioncheck exemplarcheck exemplarconfcheck exercisescheck expandcallscheck expandmodecheck expandrangecheck expandsibscheck expandtokencheck expandtopk0check extentcheck externalvetocheck fficheck fieldaffinitycheck fieldidcheck fieldnarrowcheck fieldusescheck filerootcheck fileselectorrefusecheck fillordercheck fixedbufsweep flagscheck flagsnoisecheck flagsurfacecheck flagtablecheck flipcheck floormarkcheck fnptrcheck forautobodycheck forbudgetmonotoncheck forcalibfactscheck forcompresscheck fordisclosurecheck forlenscheck formatgatecheck formaxtokenscheck fornotesbudgetcheck fornotesjsoncheck forrankordercheck forrootlegendcheck freshclonecheck freshnesscheck g1configcheck gateabilitycheck gatecountcheck gateexitcheck genrecallcheck githardencheck gitignorecheck gitquotepathcheck gitstampcheck goinstcheck gointerfacecheck graphlegendbudgetcheck graphqueryrefusecheck grepanchorcheck grepandcheck grepbytescheck grepcheck grepcontextcheck grepcorpuscheck grepfastcheck grepfollowupcheck grepignorecheck grepscancheck grepseamcheck greptiercheck guardmsgcheck hasacheck headbinstagecheck headsnapcachecheck helpbudgetcheck hermesinstallcheck historyoraclecheck hookcheck hostilecheck hotspotsincecheck htmlcolorcheck htmlhostcheck htmlrendercheck identitycheck impactimportcheck impactpartitioncheck importnarrowcheck includeanglecheck includeprecisecheck indexoutcheck infraportcheck isolateprovenancecheck javarubycheck jslangcheck jsmetricscheck jsnestedcheck jsoncheck jsonlangcheck jsonparitycheck jsonredactcheck jsonrefusallegendcheck jsonwalkcheck jsshapecheck jsverbscheck knownitemcheck kotlincheck landingcheck langcensuscheck langcheck layerquerycheck layoutcheck lb3namecheck legendcostcheck legendcoveragecheck legenddriftcheck legobundlecheck legocheck liftdisclosurecheck limitstablecheck lintbudgetcheck lintcatalogcheck lintcheck lintdedupcheck lintpayloadcapcheck lintprecisioncheck lintrulescheck lintscopecheck lintselectcheck listingpagingcheck localitycheck localscountcheck loopconservationcheck lpincheck luacheck luarequirecheck macroedgecheck macroreparsecheck manifestcheck mapdiffcheck matchcapturecheck matchgrammarcheck maxfilesizecheck mcpattrparitycheck mcpaudit4hardencheck mcpclidiffcheck mcpcodexmetacheck mcpcontractcheck mcpdegradedhintcheck mcpeditcheck mcpeditkindcheck mcpeditmodecheck mcpeditpresencecheck mcpeditracecheck mcpflagshipcheck mcpforparitycheck mcpframehonestycheck mcpgrepdegradedcheck mcphandlecheck mcpincrementalcheck mcpmanifestcheck mcprangeedgecheck mcpreadloopcheck mcpredactcheck mcpreloadcheck mcpremotecheck mcprobustcheck mcpslicecheck mcpstalecheck mcpstrictschemacheck mcptoolprunecheck mcptranchecheck mcpverbscheck mcpw2fixcheck mcpw3fixcheck mcpwatchercheck mdembedcheck mdsectioncheck mentioncapcheck mentioncheck mentionsverbcheck mergechurncheck mergescoutcheck mergescoutlonglinecheck metalcheck meterdisclosurecheck metricscheck modifierguardcheck moduleconstcheck morecontractcheck mrowalkcheck multirootcheck multiswecheck namedfileinputcheck nameinfocheck namingcalibrationcheck namingconsistencycheck naminglenscheck naminglocalscheck narrowcheck narrowlangcheck neighbourcapcheck nestedimportcheck nestedqualcheck nestprofilecheck nextverbcheck noaliascheck nodekindcheck nongitqmetricscheck nonlocalstatecheck notecanoncheck notescheck nsfiltercheck nulbytecheck numericrefusecheck objcfieldcheck objcsniffcheck opencodewrapcheck optremarkscheck optremarkshotcheck ordercheck outlinecheck overbudgetcommentcheck ownerscheck packcallersharecheck packtaskcheck packtaskmonotoncheck packtaskquotacheck padscalecheck paginationcheck pagingsweepcheck panellegendcheck pargatescheck parsehealthcheck partitioncheck patterncheck perfharnesscheck phpcheck pincensuscheck planlanescheck planlintcheck pmccheck portablebuildcheck portablecachecheck postingscheck ppaltcheck ppdeadrolescheck pranchorcheck prbudgetcheck prcheck prcontextcheck prconvergecheck precedencecheck preproccondcheck preprocdeadscalecheck prmaskanchorcheck prnestedcapcheck probecheck propcostcheck prrefsafecheck prrenamecheck pyimportprecisecheck pyshapecheck qackconcurrencycheck qackorigincheck qchurncheck qchurnmemocheck qddialscheck qdrefpaircheck qextractionkeycheck qoriginoraclecheck qrevtokencheck qrowlocatorcheck qschemetripcheck qsnapcachecheck qsnapprefetchcheck qualifiedresolvecheck qualitycheck qualitycrosslangcheck qualityexcludecheck qualitykeycheck qualitykindscheck qualityorigincheck qualitypanelcheck qualityscopecheck qualitysignalcheck qualitystalecheck qualitysymcheck qualnewcheck querycheck queryfilescancheck racymtimecheck radixsimdcheck rangecomposecheck rankbycheck reachcheck readabilitycheck readmedriftcheck readmeexamplecheck recallanchorcheck recallboundarycheck recallbudgetcheck recallbufcheck recallevalcheck recallparitycheck recallpassagecheck recallrankdepthcheck recallrelcheck recalltablecheck recalltotalcheck receiptpostcheck redactcheck redactfixcheck refusaltailcheck regexbombcheck regexcheck regexrefusecheck registermacrocheck relevancefloorcheck relinkcheck reportcheck resolvecheck resolverhonestycheck retrievalqualitycheck reusefirstworkflowcheck ripwirepubliccheck rootrelcheck rootrelemitcheck routecheck routeedgecheck routehookcheck routeoncecheck routingreportcheck rubyargcheck rubyconstcheck rubymetricscheck rubyrecvcheck rubyrequirecheck rubyscopecheck rubysettercheck runhintcheck runtracecheck rustanccheck rustimportprecisecheck rustqualcheck safedeletecheck sarifcheck savecachecheck scipcheck scipjoincheck scorecardcheck scoutheadconflictcheck scoutkeycheck seedboundscheck selectorchaincheck selectorhonestycheck selectorrefusecheck selectorscopecheck selfcontainedcheck shadowcheck shapingflagcheck shellgateindexcheck showcasecapturecheck sibliftcheck sidecarsymlinkcheck sigredactcheck sincecheck sincecochangecheck sincewindowcheck singledefcheck situdiffcheck skilldescbudgetcheck skillevalcheck skillevalsplitcheck skillinstallcheck skillroutingjudgedcheck skillscanreadcheck skilltruthcheck skippedcheck skipreasoncheck slicecheck slicediffcheck sliceflowcheck sliceflowsenscheck spectimingcheck staleackcheck statgatecheck stdqualcheck strkerncheck sublistcountcheck substrfiltercheck subtokencheck svectorcheck swiftcheck swiftmemberscheck swiftshapecheck taskechocheck termmargincheck testedreachcheck testgatecheck testgatelegendbudgetcheck testgatepagecheck testgaterefusecheck testmacrocheck testrowruncheck testscopecheck textdocscheck timsortcheck tokenbudgetcheck tomllangcheck toolcallroutecheck tornreadcheck tracecheck tracehandoffcapcheck tracehopcheck traceminecheck treecheck truncvocabcheck tsimportprecisecheck tsshapecheck type3check type3clonecheck typerefcheck unreachablecheck unresolvedcheck usescheck usesselectorcheck usingdeclcheck utf8scrubcheck vendoredassetcheck vendoredbundlecheck vendorpatchcheck verifycheck versioncheck w2verbscheck w3fixbudgetcheck w3fixlegendcheck weaksignalcheck withgraphcheck withprofilecheck worktreeleakcheck wrapverbscheck writetargetcheck xmlwellformed yamllangcheck zonecheck zoneconsistencycheck zoomcheck; do [ -f "$ROOT/test/$_g.sh" ] || continue if RIPWIRE_BIN="$BIN" bash "$ROOT/test/$_g.sh" >/dev/null 2>&1; then ok "absorb gate ($_g.sh)"