Skip to content

Commit b28bd34

Browse files
AdaWorldAPIclaude
andauthored
Bench Component F: the boundary re-asked on the real row-store layout (W4) (#9)
* Core vertical slice: docs/abi.md contract, native/lgj-abi, Java facade Ships the fully verified core of the Panama x ndarray::simd x Valhalla vertical slice (Phases A-E of the mission plan): - docs/abi.md: the normative Rust<->Java ABI contract, written before either side was implemented so both could be checked against one frozen doc instead of each other. - Five new ndarray::simd primitives (eq_u32_to_mask, gt_i32_to_mask, mask_and/mask_or(_assign), masked_sum_i32), added under ndarray's own W1a consumer contract. - native/lgj-abi: the Rust ABI crate. Generation-checked handle registry, generic SoA fixture, bulk kernels routed exclusively through ndarray::simd, 14-symbol extern "C" surface. 72/72 tests green, clippy/fmt clean, and the registry's core safety check was disable-verified (short-circuited, confirmed exactly the two guarding tests go red, restored). - java/: the Panama membrane (internal/ffm, never exposed publicly) and the public semantic facade (NativePattern/View/Predicate/ Pattern/Mask). 132/132 checks green across 8 suites, including a reflection-enforced ApiSurfaceTest that mechanically proves zero FFM types ever reach a public signature, and a LazinessTest that empirically proves the thesis: building a chain costs zero crossings, evaluating it costs exactly one, independent of row count up to 1,000,000. - .claude/: a 6-agent ensemble, 6 knowledge docs, and a full board (LATEST_STATE/STATUS_BOARD/AGENT_LOG/EPIPHANIES/TECH_DEBT/ISSUES/ PR_ARC_INVENTORY/INTEGRATION_PLANS/CODEX_REVIEW_CHECKLIST), all scoped to this repo's actual seams. A mechanical audit (D-LGJ-AUDIT) found and fixed the one real rule violation before this commit: kernels.rs::simd_popcount was calling the internal ndarray::hpc::bitwise path instead of the sanctioned ndarray::simd re-export. Deliberately NOT included: the Valhalla lab (valhalla-lab/) and the Vector API benchmark harness (bench/) — still in flight, tracked as open STATUS_BOARD.md rows, to land in a follow-up PR once reviewed with the same rigor as this slice. Generated by [Claude Code](https://claude.ai/code) * Valhalla lab: three-truths method, causal isolation, 3 real reproducers Completes D-LGJ-F. One experiment source (src/shared/), compiled twice against real JDKs -- stable JDK 26 GA (record) and the official JEP 401 early-access binary (value record) -- via a self-verifying run.sh that mechanically diffs the two Vocab.java files modulo the 'value' keyword before trusting the A/B is honest. Experiments: IdentityExperiment (semantic truth -- is identity actually unobservable), FootprintExperiment (real per-object/array/field bytes via allocation-delta + JOL where available), FfmAddressingExperiment (is the wrapper free where it touches native memory), ThesisExperiment (the mandatory headline: 65,536 rows as one native lane vs hydrated Java objects, on both platforms). Causal isolation via three additional run.sh passes: escape analysis off, and UseArrayFlattening/UseFieldFlattening toggled independently -- isolates which flag actually drives the measured difference rather than inferring it. Three real Valhalla limitations reproduced and filed under reproducers/, none of which changed the production API: - R1: @NullRestricted field on an identity class is a VerifyError (javac's fault -- no source form expresses the required strict-field init order relative to super()) - R2: array flattening has a hard 8-byte payload cliff, confirmed via -XX:+PrintFlatArrayLayout. LaneId/Ordinal/MaskId (<=8B) flatten; RowRange/Row (16B) do not. This turns "Valhalla helps descriptors, not entities" from a hand-wave into a measured VM cutoff -- and RowRange landing on the wrong side is flagged as the one place the expectation was too optimistic. - R3: the densest null-restricted array form is jdk.internal-only and generics erase flattening entirely; Foo! null-restricted type syntax confirmed not to parse, matching the earlier archaeology finding. One real defect found and fixed before landing: IdentityExperiment and the stable Platform called Class::isValue() directly on four vocabulary types with a comment incorrectly claiming it was "final API on JDK 26" -- it does not exist there at all, confirmed by a real javac failure. Fixed by routing every query through Platform.isValueClass(Class<?>), answered honestly per platform. Generated by [Claude Code](https://claude.ai/code) * Vector API bench: real JMH, cross-checked; the crossing does not always win Completes D-LGJ-G, the mission's mandated "where does execution belong" comparison -- measured, not assumed to favor the Rust crossing. Real JMH 1.37 (fork+warmup+compiler-blackholes confirmed in the log, not a hand-rolled loop -- that lives in valhalla-lab and is labelled as such there). Four components, cost kept strictly separate per the mission brief: A_DowncallOverhead (bare crossing, no work), B_SegmentAccess (raw native-memory read throughput), C_ExecutionBoundary (native fused plan vs Java Vector API vs Java scalar, swept 64 to 4,194,304 rows), E_FusionAndPlanning (fused vs unfused vs the scalar reference kernel vs plan-construction-only, swept 1-8 predicates). 50/50 rows, 0 failures. Data.crossCheck() runs in @setup and throws if the three kernels disagree on count or sum, so a faster-but-wrong Vector kernel could not have won the comparison undetected. The headline complicates the thesis honestly: for a single predicate over one native lane, the Java Vector API -- reading the SAME native MemorySegment zero-copy via IntVector.fromMemorySegment, no byte[], no bounce buffer -- beats the native crossing at EVERY row count tested, 56.4x at small sizes down to 1.3-1.4x at 4M rows. A second crossover is also real: native beats a plain Java scalar loop only past roughly 4,096-16,384 rows. Component E shows why this doesn't overturn the project's premise: SIMD-vs-scalar is the largest lever measured anywhere in this suite (10.8x-31.1x, growing with predicate count), and fused/unfused land within this harness's own ~10% noise floor of each other at 65,536 rows -- the fused plan's real value is the structural one-crossing guarantee (already proven by LazinessTest), not a large measured time saving at this scale. Verdict: the crossing is worth paying for composed, multi-predicate work, not for reading one predicate off one lane, where Java on the same memory is simply faster. RESULTS.md was hand-written from the raw CSV, then independently cross-checked against summarise.sh -- a script the same PR ships that mechanically regenerates every table from results/jmh-results.csv, so a re-run's numbers can never silently drift from a hand-transcribed table. Both productions agreed to 3 decimal places on every cell checked. Generated by [Claude Code](https://claude.ai/code) * Phase I synthesis docs + fusion-sweep 256-row re-run + board hygiene Four synthesis docs close D-LGJ-I: architecture.md (the four layers and what each is provably responsible for), panama.md (manifest-over-header, belt-and-braces ownership, restricted-method discipline), valhalla-lab.md (three-truths synthesis, the 8-byte flattening cliff, zero API adoption), execution-boundary.md (the measured crossover picture + three structural hot-path facts, each checked rather than assumed). The fusion sweep was re-run with a 256-row arm after the first pass's 'fusion does nothing' finding proved true only at 65,536 rows: at 256 rows x 8 predicates unfused/fused reaches 2.99x. RESULTS.md is rewritten from jmh-results-merged.csv; TABLES.md is mechanically generated from the same file so the two cannot drift. MultiLaneColumn (ndarray::simd_soa) evaluated for the fixture kernels and declined on two concrete API mismatches (64-byte-multiple constraint, no u32 lane); earmarked for the future 512-byte row-store slice where it fits by construction. Operator layout reference recorded on the board. PR_ARC_INVENTORY backfilled for merged PRs 1-3; the lapse is owned in the file itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Board: PR #4 arc entry (post-merge) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * SoA row store: 512B rows, 32 facet lanes, ABI minor 2 (W1+W2) The lance-graph-shaped substrate, wired end to end. The flat three-lane fixture was always scaffolding (docs/abi.md 10, architecture.md said so from PR #1); this is the layout the stack actually converges on: 64K x 512-byte rows, 32 facet lanes of 16 bytes = 4-byte LE classid + 12-byte payload, the V3 content-blind facet. Rust (native/lgj-abi): - rowstore.rs: one Arc<[u8]>, two readings (row-major chunks and strided facet columns), zero copies, normative SplitMix64 generator. - LGJ_RESOURCE_ROWSTORE + lgj_rowstore_open; facet lanes described through the UNCHANGED LgjLaneDesc (stride_bytes carried this since minor 1); lgj_op_eq_classid produces ordinary masks that compose with the existing algebra; lgj_row_facet_match writes per-row 32-bit facet sets into a caller-owned buffer via MultiLaneColumn (Arc refcount bump, no copy). - byte_len tightened to the exact covered span (len-1)*stride + elem_bytes: a full-stride final window would let Java bound a segment past the allocation's end on a facet lane. - ABI minor 1 -> 2; docs/abi.md gains 11 and its symbol count is corrected (the 14 was drift; the list already enumerated 15, and the real number is now 18 per nm -D). Gates: cargo test 84/84, clippy -D warnings clean, fmt clean, release build exports 18/18 symbols. Both new kernels are parity-checked against independent scalar references over 10 row counts x 2 seeds x 4 facets x 4 needles, then cross-checked a third way against RowStore::classid_at; a two-sided falsifier proves payload bytes never satisfy a classid match and that a real match does fire. Docs: .claude/plans/lgj-soa-substrate-v1.md (W1-W5 waves) + one plan per consumer example (world-trades / bricks-analytics / graph-traversal), .claude/knowledge/soa-row-store-layout.md, and the board triple ledger. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Board: PR #5 arc entry (post-merge) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Knowledge: assess the archived layout-bridge discussion; name W6 The operator's pre-build ChatGPT discussion is assessed once, in .claude/knowledge/prior-art-and-the-layout-bridge-claim.md, so it is never re-mined or cited naively. Verdict: it converged independently on the architecture this repo then built and measured. Kept: the callability-vs-shared-executable-layout positioning, the schema-key-as- join-point extractable (now the named W6 consideration: an explicit schema/classid field on the descriptors when ClassView lands), and the baseline-dependent claims discipline for W5 comparisons. Pinned: its page-descriptor sketch has no liveness story (the registry's whole job), its native-always-wins assumption is measured false (Component C), and its ndarray paragraph describes upstream crates.io ndarray, not the AdaWorldAPI fork whose ndarray::simd polyfill this stack mandates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Plans: OGAR Machine (exploratory) + lance-graph-hydrate dependency note Captures the operator's second archived context as .claude/plans/ogar-machine-v1.md — a genuinely new workload for the shipped substrate, not convergent confirmation: one row = one machine STATE, control flow as population masks over 64K execution contexts, Ghidra P-code as the normalized guest ISA (repo attached and cloned), differential migration testing (legacy XOR replacement across 65,536 worlds) as the killer demo, Lance as the time machine. Strong claim vs weak claim separated per the discussion's own discipline; gated on W3 + one W5 example + Ghidra archaeology + a tiny falsifiable probe (P-M1). Also records lance-graph #957 (merged: lance-graph-hydrate, the generic SoA->S3->volume->Lance hydration crate minted for consumers to inherit) and #958 (its open hardening fast-follow) in the substrate plan: when this repo's persistence slice arrives, hydration is inherited from lance-graph-hydrate, never re-derived here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Board: PR #6 arc entry (post-merge) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Waves calcified: dispatch maps for every plan; Ghidra plan from real archaeology Operator ruling: calcify, don't execute. .claude/waves/ holds one dispatchable map per plan — README with the standing rules and the verbatim worker preamble, substrate W3+W4 (the only READY wave), three consumer waves stamped DO-NOT-DISPATCH, Ghidra G1+G2, and OGAR-Machine P-M1 (BLOCKED behind a 4-condition gate including an explicit operator go). Each map carries disjoint worker scopes, orchestrator-only steps, exact gate commands, disable-runs, and STOP triggers. ghidra-integration-v1.md is written from archaeology against the real clone, not the sketch: 74 P-code opcodes (CPUI_MAX=75), 12.2 DEV / Java 25+, analyzeHeadless entry, and Ghidra's own PcodeEmulator as the reference-implementation parity oracle (the tesseract-rs method). The ogar-machine plan is cross-updated to cite it. Mapping-time catches that would have burned a dispatch: the graph consumer needs a deliberate edge-bearing generator arm (today's payload is PRNG noise) - a substrate change, flagged in the wave; the hop has a real D1a/D1b design fork with ruling guidance recorded. Muscle memory pinned as E-LGJ-CALCIFY-THEN-DISPATCH-1: the eight earned-this-session rules and the plan->wave->shelf->dispatch rhythm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Board: PR #7 arc entry (post-merge) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Plan: lance-graph #958 merged (was open at last check) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Java RowStore facade: W3 shipped (185/185, one bug found+fixed) First real dispatch of the calcified wave system (wave-substrate-w3-w4.md Dispatch 1) — 3 Sonnet workers on disjoint scopes, Opus orchestrator integration and central gating, per the standing rules in .claude/waves/README.md. New public surface: RowStore (open/rowCount/isOpen/maskOfFacetClass/ facetMatches/close), FacetMatchView (rowCount/matchesOf/cardinality), FacetId (0..31-checked record) -- zero java.lang.foreign types in any public signature, ApiSurfaceTest passed unmodified. Mask.source() retyped NativePattern -> NativeResource (new minimal interface) so a mask parents onto either a pattern or a row store with the existing algebra unchanged; verified zero call-site breakage before the retype. One real bug caught by the test suite itself: FacetMatchView.rowCount() was missing the closed-store guard its sibling accessors both had -- found by RowStoreLifetimeTest on the first real run, fixed, re-verified. Gate: javac -Xlint:all clean (7 pre-existing [restricted] warnings, 0 new); AllTests 132 -> 185 (+53 checks: 29 parity + 24 lifetime). Both mandated disable-runs ran red-then-green with the exact expected blast radius: (1) Abi.requireMinor inflated by 1 -> exactly the two RowStore suites failed, 8 others stayed green; (2) the generator's a/b draw order swapped -> exactly RowStoreParityTest broke (17/29), the generator-independent RowStoreLifetimeTest stayed green. Board: STATUS_BOARD D-LGJ-W3 DONE, LATEST_STATE, and E-LGJ-WAVE-DISPATCH-VALIDATED-1 -- the wave system's first real dispatch, including an orchestrator-side false alarm (wrong env var name guessed instead of read from source) recorded so it isn't repeated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Board: PR #8 arc entry (post-merge) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Plan: r2sleigh recorded as third lift path + decompiler candidate Operator-flagged: AdaWorldAPI/r2sleigh (read-only clone verified, HEAD 60942f6) is a Rust workspace lifting Ghidra .sla specs to P-code via libsla, with typed IR, SSA, Z3 symbolic execution, and a P-code-to-C decompiler. The honest FFI fact is pinned: libsla-sys means the SLEIGH runtime underneath is Ghidra's C++ via FFI, not pure Rust -- acceptable on the same lift-time-only footing as running Ghidra itself. G1 gains candidate C (r2sleigh-cli lift, no JVM in the loop, and a STRONGER falsifier: cross-implementation P-code agreement between two independent consumers of one .sla spec); r2dec is named as the engine candidate for the semantic-shim direction; r2sym joins SymbolicSummaryZ3 as branch-population prior art. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs * Bench Component F: the boundary re-asked on the real row-store layout (W4) One Sonnet worker per wave-substrate-w3-w4.md Dispatch 2, orchestrator- run JMH (9/9 combos), the cross-check discipline intact: both Java facet-match kernels verified row-by-row against the native FacetMatchView in @setup at every row count before anything was timed. The finding: Component C's direction survives, its margin collapses. The Vector API wins the per-row 32-facet strided scan at every row count measured, but by 2.51x / 1.92x / 1.14x (4K / 65K / 1M rows) against C's 56x -- and at 512 MiB traversed all three arms converge on memory bandwidth. More work per byte narrows the boundary exactly as execution-boundary.md predicted; it now records that as measurement. Disclosed, not hidden: the native arm allocates its output segment per call where the Java arms reuse a @setup buffer; facetMatchesInto is the named follow-up if the small-row gap ever matters. Java kernels mirror the Rust chunk algorithm line-for-line (VectorMask.toLong() & 0x1111, same four-term fold) so the comparison is between implementations of ONE algorithm, not two algorithms. Mechanics: summarise.sh gains the F table (and its old 'E/F' section title -- a genuine collision with the new component -- is corrected to 'E'); TABLES.md regenerated from the merged CSV; RESULTS.md gains provenance-table update + full F section; RowStore gains a package-private handle() mirroring NativePattern's for the bench's split-package NativeAccess bridge; main suite re-verified 185/185 against the fresh minor-2 .so in the bench's expected location. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Pud4qpxFHwqyqDjSabQbs --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 320808d commit b28bd34

16 files changed

Lines changed: 749 additions & 1230 deletions

File tree

.claude/board/LATEST_STATE.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1+
## 2026-08-17 (dispatch 2) — W4 measured: the boundary re-asked on the REAL layout
2+
3+
`wave-substrate-w3-w4.md` Dispatch 2 executed: one Sonnet worker
4+
(Component F: `F_RowStoreFacetScan` + `RowStoreData` + the two
5+
`Kernels` facet-match arms, mirroring the Rust kernel's chunk algorithm
6+
line-for-line incl. the `& 0x1111` classid-position mask), orchestrator-run
7+
JMH, 9/9 combos, the cross-check green at every row count before anything
8+
was timed.
9+
10+
**The finding: Component C's direction survives; its margin collapses.**
11+
The Vector API still wins the per-row 32-facet scan at every row count —
12+
but by **2.51× / 1.92× / 1.14×** (4K / 65K / 1M rows) against C's 56×, and
13+
at 512 MiB traversed all three arms converge on memory bandwidth
14+
(~6–7 GB/s on this container). More work per byte narrows the boundary
15+
exactly as `execution-boundary.md` predicted; it now says so as
16+
measurement. One disclosed asymmetry: the native arm allocates its output
17+
per call (`facetMatchesInto` named as the follow-up if the small-row gap
18+
ever matters).
19+
20+
Also: `summarise.sh` gained the F table (and its old "E/F" section title —
21+
a real collision with the new component — was corrected to "E");
22+
`TABLES.md` regenerated from the merged CSV; `RESULTS.md` §F written;
23+
`RowStore` gained a package-private `handle()` (mirroring
24+
`NativePattern`'s, for the bench's split-package `NativeAccess` bridge
25+
only). Substrate wave file fully executed — both dispatches shipped.
26+
127
## 2026-08-17 (dispatch 1) — W3 shipped: the Java `RowStore` facade, from the calcified wave map
228

329
First real dispatch of the wave system: `wave-substrate-w3-w4.md` Dispatch 1

.claude/board/PR_ARC_INVENTORY.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,26 @@
88
> anti-pattern the imported board rules name. Backfilled below in one
99
> pass rather than left stale; PR #4 onward gets its entry at merge time.
1010
11+
## PR #8 — Java RowStore facade: W3 shipped (merged 2026-08-17, squash `320808d`)
12+
13+
- **Added:** `RowStore`/`FacetMatchView`/`FacetId`/`NativeResource` public
14+
API; `Mask.source()` retyped `NativePattern → NativeResource`;
15+
`RowStoreParityTest`/`RowStoreLifetimeTest` (53 new checks).
16+
- **Locked:** the wave-dispatch system works end to end — 3 disjoint
17+
Sonnet workers, zero merge conflicts, mutually consistent signatures
18+
with no coordination beyond the frozen briefs
19+
(`E-LGJ-WAVE-DISPATCH-VALIDATED-1`).
20+
- **Deferred:** W4 (bench Component F) — the wave file's second dispatch.
21+
- **Docs:** `STATUS_BOARD` D-LGJ-W3 DONE; `LATEST_STATE`; EPIPHANIES entry
22+
incl. an orchestrator-side false alarm (guessed env var name instead
23+
of reading `Abi.java`'s `ENV_LIBRARY` constant) recorded so it isn't
24+
repeated.
25+
- **Confidence:** High — 185/185 (was 132), 0 new lint warnings, one real
26+
bug (`FacetMatchView.rowCount()` missing its closed-store guard) caught
27+
by the mandated tests and fixed before merge, both disable-runs
28+
red-then-green with the exact predicted blast radius. Bot reviewers at
29+
usage limits, did not run.
30+
1131
## PR #7 — waves calcified: dispatch maps for every plan (merged 2026-08-17, squash `68f7add`)
1232

1333
- **Added:** `.claude/waves/` — README (standing rules + verbatim worker

.claude/board/STATUS_BOARD.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ layout wired end to end. Doctrine: `E-LGJ-THE-MIDDLE-TIER-IS-DELETED-NOT-WRAPPED
3939
| D-LGJ-W1 | ndarray: `MultiLaneColumn::iter_u32x16`/`len_u32x16` + `eq_u32_strided_to_mask` (W1a contract) | **DONE 2026-08-17** — ndarray PR #279; `simd_int_ops` 46/46 (5 new strided tests incl. two `should_panic` bounds/overflow arms + stride-4 parity against the contiguous primitive), `simd_soa` 15/15, full `simd` 263/263, doctests, clippy `-D warnings` + fmt clean |
4040
| D-LGJ-W2 | lgj-abi row store: `rowstore.rs`, `LGJ_RESOURCE_ROWSTORE`, `lgj_rowstore_open`, strided facet lanes through the unchanged `LgjLaneDesc`, `lgj_op_eq_classid`, `lgj_row_facet_match`, ABI minor 1→2, `docs/abi.md` §11 | **DONE 2026-08-17**`cargo test` **84/84**, clippy/fmt clean, release build exports **18/18** symbols (`nm -D`). Parity: both kernels vs independent scalar references over 10 row counts × 2 seeds × 4 facets × 4 needles, cross-checked a THIRD way against `RowStore::classid_at`. Two-sided payload-vs-classid falsifier. End-to-end membrane test covers describe → predicate → mask algebra → count → facet-match → lifecycle |
4141
| D-LGJ-W3 | Java `RowStore` facade: structured `MemoryLayout`, minor-≥2 gate, `FacetMatchView`, parity test transcribing the generator | **DONE 2026-08-17** — dispatched per `.claude/waves/wave-substrate-w3-w4.md` (3 Sonnet workers, disjoint scopes: FFM membrane extension / public facade / tests), orchestrator-integrated. `javac -Xlint:all` clean (same 7 pre-existing `[restricted]` warnings, zero new). `AllTests` **185/185** (was 132; +53 new checks: 29 parity + 24 lifetime). **One real bug caught by the suite and fixed**: `FacetMatchView.rowCount()` was missing the closed-store guard `matchesOf`/`cardinality` both had — a stale row count was readable after the owning store closed. Fixed, re-verified. Both mandated disable-runs ran red-then-green: (1) `Abi.requireMinor` inflated by 1 → exactly `RowStoreParityTest`+`RowStoreLifetimeTest` failed, all 8 other suites stayed green; (2) the pure-Java generator's a/b draw order swapped in `RowStoreParityTest` → exactly that suite broke (17/29), `RowStoreLifetimeTest` (generator-independent) stayed green — confirming the parity test is a real falsifier, not decorative. `Mask.source()` retyped `NativePattern → NativeResource` (new interface) so a `Mask` can parent onto either a `NativePattern` or a `RowStore` — zero call-site breakage (verified: no existing caller bound the narrower type) |
42-
| D-LGJ-W4 | Bench Component F: Vector API facet scan vs the crossing, on the REAL layout | Queued |
42+
| D-LGJ-W4 | Bench Component F: Vector API facet scan vs the crossing, on the REAL layout | **DONE 2026-08-17** — 1 Sonnet worker (F_RowStoreFacetScan + RowStoreData + Kernels facet-match arms, cross-check-in-@Setup discipline), orchestrator-run JMH: 9/9 combos, cross-checks green at every row count. **Finding: Component C's direction survives, its margin collapses** — Vector API wins the 32-facet strided scan at every row count but by 2.51×/1.92×/1.14× (4K/65K/1M rows) vs C's 56×; at 512 MiB traversed all three arms converge on memory bandwidth. Native arm's per-call allocation asymmetry disclosed in §F with a named follow-up (`facetMatchesInto`), not hidden. summarise.sh extended with the F table (and the old 'E/F' section retitled 'E' — a real naming collision); tables regenerated from the merged CSV |
4343
| D-LGJ-W5 | Three consumer examples (trades / bricks / graph) — one plan file each | Planned, gated on W3 |

.claude/plans/ghidra-integration-v1.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,43 @@ License note: Ghidra core is Apache-2.0 (compatible with everything in
2727
this stack); the `GPL/` subtree (demangler etc.) is not needed for
2828
lifting and stays untouched.
2929

30+
## G0 addendum (2026-08-17, operator-flagged): `AdaWorldAPI/r2sleigh` — the third lift path and the decompiler candidate
31+
32+
Read-only clone at `/workspace/adaworldapi/r2sleigh` (HEAD `60942f6`, 20 MB,
33+
Rust 1.93 workspace, 8 crates + an r2 plugin). What it is, verified from the
34+
tree, not the README alone:
35+
36+
- **Pipeline:** `.sla (Ghidra processor spec) → libsla → P-code → r2il
37+
(typed IR, 60+ opcodes) → {SSA (r2ssa) → taint/symbolic (r2sym, Z3-backed),
38+
decompiler-to-C (r2dec), type inference (r2types), ESIL}`.
39+
- **The honest FFI fact:** `Cargo.lock` carries `libsla` AND `libsla-sys`
40+
the SLEIGH runtime underneath is **Ghidra's native C++ via FFI**, not a
41+
pure-Rust SLEIGH. r2sleigh's own crates are Rust; the lifter core is not.
42+
Acceptable on exactly the same footing as running Ghidra itself: this is
43+
a LIFT-TIME tool, never at OGAR-Machine runtime (the no-C-at-runtime rule
44+
is about the execution path, and lift time is not on it).
45+
46+
**Consequences for the waves:**
47+
48+
1. **G1 gains a candidate C: lift via `r2sleigh-cli`** (Rust CLI consuming
49+
Ghidra's own `.sla` specs) alongside A (released-Ghidra
50+
`analyzeHeadless`) and B (fork build). No JVM in the lift loop, and the
51+
P-code comes from the SAME SLEIGH specs Ghidra uses — so the G1
52+
falsifier gets stronger, not weaker: r2sleigh's P-code dump vs Ghidra's
53+
own listing is a **cross-implementation** agreement check, two
54+
independent consumers of one spec.
55+
2. **Operator designation: future decompiler candidate.** `r2dec`
56+
(P-code→SSA→structured C) is the natural engine for the OGAR-Machine's
57+
"semantic shims erode the emulator" direction — recognizing and lifting
58+
stable call surfaces needs decompilation-grade structure recovery, and
59+
building that from scratch was never on any plan. Not scheduled; named.
60+
3. **A second symbolic-execution precedent** (`r2sym`, Z3) in Rust, next to
61+
Ghidra's `SymbolicSummaryZ3` — the branch-population direction now has
62+
two prior arts to study before designing anything.
63+
4. **Access boundary, recorded:** the clone is anonymous/read-only —
64+
pushing or PRs against r2sleigh require re-attaching with push access.
65+
Any fix we need upstream goes through the operator first.
66+
3067
## The integration shape — two roles, both offline
3168

3269
```

bench/RESULTS.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
> | file | what it is |
77
> |---|---|
88
> | `results/jmh-run-full.txt` / `jmh-results-full.csv` | the first full sweep (A, B, C/D, and E at 65,536 rows only) |
9-
> | `results/jmh-run.txt` / `jmh-results.csv` | the `./run.sh E_` re-run, after a 256-row arm was added to the fusion sweep |
9+
> | `results/jmh-run.txt` / `jmh-results.csv` | the latest selective run — currently `./run.sh F_` (the W4 row-store facet scan); the earlier `E_` re-run's rows live on in the merged file |
1010
> | `results/jmh-results-merged.csv` | A/B/C from the full sweep + E from the re-run — **the input to every table below** |
1111
> | `results/TABLES.md` | `./summarise.sh results/jmh-results-merged.csv` |
1212
>
@@ -192,7 +192,7 @@ most of the residual gap. Both are ABI-implementation changes, not ABI-contract
192192

193193
---
194194

195-
## E/F — fusion, and what the fluent API itself costs
195+
## E — fusion, and what the fluent API itself costs
196196

197197
| rows | predicates | `fused` | `unfused` | `fusedScalarKernel` | `planConstructionOnly` | unfused ÷ fused |
198198
|---:|---:|---:|---:|---:|---:|---:|
@@ -231,6 +231,38 @@ just a list of predicates" should look like.
231231

232232
---
233233

234+
## F — the row-store facet scan (the W4 question)
235+
236+
The question C could not answer: does "the Vector API beats the crossing" survive when the
237+
workload is the REAL substrate layout — 512-byte rows, 32 facets, a *strided* scan doing 32
238+
classid compares per row instead of one compare per 4-byte element? Three arms over the same
239+
`RowStore` (`classId == 9`), every arm's full per-row bitset output cross-checked equal in
240+
`@Setup` before anything was timed (`RowStoreData`'s constructor, at every row count):
241+
242+
| rows | bytes traversed | `native_facetMatch` | `java_vectorApi` | `java_scalar` | native/vector |
243+
|---:|---:|---:|---:|---:|---:|
244+
| 4,096 | 2 MiB | 191.3 ±24.3 µs | **76.1 ±5.3 µs** | 134.7 ±13.9 µs | 2.51× |
245+
| 65,536 | 32 MiB | 3,219.6 ±141.5 µs | **1,674.1 ±126.9 µs** | 3,354.4 ±118.4 µs | 1.92× |
246+
| 1,048,576 | 512 MiB | 79,016.9 ±9,239.4 µs | **69,613.1 ±1,624.1 µs** | 82,541.1 ±3,106.4 µs | 1.14× |
247+
248+
Three findings, in decreasing order of confidence:
249+
250+
1. **The direction survives; the margin collapses.** The Vector API still wins at every measured
251+
row count — but by 1.1–2.5×, not Component C's 56×. More work per byte (32 compares per
252+
512-byte row, the same four-facets-per-512-bit-register algorithm on both sides) is exactly
253+
the regime the § Verdict predicted would narrow the boundary, and now it is measured rather
254+
than predicted.
255+
2. **At 512 MiB everything converges on memory bandwidth.** 69–82 ms to traverse 512 MiB is
256+
~6–7 GB/s on this shared 4-vCPU container — all three arms are bandwidth-bound and the
257+
implementation difference shrinks toward noise (native's CI at 1M rows, ±9.2 ms, brackets much of the
258+
gap to vector).
259+
3. **The native arm carries a disclosed allocation asymmetry.** `RowStore.facetMatches()`
260+
allocates a fresh output segment + view per call, where `NativePattern`'s fused-plan path
261+
reuses a cached scratch mask and the Java arms reuse a `@Setup`-allocated array. At 4,096
262+
rows that fixed cost is a visible slice of 191 µs; at 1M rows it is noise. A
263+
`facetMatchesInto(classId, …)` reuse form is the named follow-up if the small-row gap ever
264+
matters — filed, not assumed to.
265+
234266
## Verdict — where does execution belong?
235267

236268
On the evidence, **not where the architecture currently puts it, for count-only queries.** Stated

bench/results/TABLES.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
| 1,048,576 | 4096 | 411.333 ±37.244 | 310.405 ±17.660 | 1623.313 ±26.973 | **java_vectorApi** | 1.33x |
3030
| 4,194,304 | 16384 | 1858.686 ±149.400 | 1319.107 ±37.240 | 6602.036 ±100.771 | **java_vectorApi** | 1.41x |
3131

32-
### E/F — fusion and the cost of the fluent API (µs/op)
32+
### E — fusion and the cost of the fluent API (µs/op)
3333

3434
| rows | predicates | `fused` | `unfused` | `fusedScalarKernel` | `planConstructionOnly` | unfused/fused |
3535
|---:|---:|---:|---:|---:|---:|---:|
@@ -42,3 +42,11 @@
4242
| 65,536 | 4 | 25.591 ±0.582 | 31.790 ±3.337 | 917.387 ±27.768 | 0.261 ±0.012 | **1.24x** |
4343
| 65,536 | 8 | 58.978 ±4.509 | 60.968 ±2.657 | 1825.916 ±125.373 | 0.601 ±0.032 | **1.03x** |
4444

45+
### F — the row-store facet scan (µs/op, mean ± 99.9% CI)
46+
47+
| rows | row KiB | `native_facetMatch` | `java_vectorApi` | `java_scalar` | fastest | native/vector |
48+
|---:|---:|---:|---:|---:|---:|---:|
49+
| 4,096 | 2048 | 191.285 ±24.278 | 76.108 ±5.316 | 134.712 ±13.871 | **java_vectorApi** | 2.51x |
50+
| 65,536 | 32768 | 3219.554 ±141.518 | 1674.064 ±126.854 | 3354.401 ±118.415 | **java_vectorApi** | 1.92x |
51+
| 1,048,576 | 524288 | 79016.873 ±9239.383 | 69613.100 ±1624.077 | 82541.075 ±3106.426 | **java_vectorApi** | 1.14x |
52+

bench/results/jmh-results-merged.csv

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,12 @@ com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,2.097204,0
6464
com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,31.789786,3.337085,us/op,4,65536
6565
com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,4.437382,0.204156,us/op,8,256
6666
com.adaworldapi.lancegraph.bench.E_FusionAndPlanning.unfused,avgt,1,8,60.967976,2.656815,us/op,8,65536
67+
com.adaworldapi.lancegraph.bench.F_RowStoreFacetScan.java_scalar,avgt,1,8,134.711904,13.870504,us/op,,4096
68+
com.adaworldapi.lancegraph.bench.F_RowStoreFacetScan.java_scalar,avgt,1,8,3354.401240,118.415079,us/op,,65536
69+
com.adaworldapi.lancegraph.bench.F_RowStoreFacetScan.java_scalar,avgt,1,8,82541.075065,3106.426103,us/op,,1048576
70+
com.adaworldapi.lancegraph.bench.F_RowStoreFacetScan.java_vectorApi,avgt,1,8,76.107543,5.315558,us/op,,4096
71+
com.adaworldapi.lancegraph.bench.F_RowStoreFacetScan.java_vectorApi,avgt,1,8,1674.064233,126.854113,us/op,,65536
72+
com.adaworldapi.lancegraph.bench.F_RowStoreFacetScan.java_vectorApi,avgt,1,8,69613.100438,1624.076656,us/op,,1048576
73+
com.adaworldapi.lancegraph.bench.F_RowStoreFacetScan.native_facetMatch,avgt,1,8,191.285057,24.277993,us/op,,4096
74+
com.adaworldapi.lancegraph.bench.F_RowStoreFacetScan.native_facetMatch,avgt,1,8,3219.553540,141.518033,us/op,,65536
75+
com.adaworldapi.lancegraph.bench.F_RowStoreFacetScan.native_facetMatch,avgt,1,8,79016.872595,9239.382837,us/op,,1048576

0 commit comments

Comments
 (0)