Skip to content

Commit 78aa60e

Browse files
AdaWorldAPIclaude
andauthored
SoA row store: 512B rows, 32 facet lanes, ABI minor 2 (W1+W2) (#5)
* 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 --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 parent bd92c58 commit 78aa60e

17 files changed

Lines changed: 1534 additions & 28 deletions

.claude/board/EPIPHANIES.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,74 @@
44
> `**Status:**`/`**Confidence:**` line. A correction gets its own new,
55
> dated entry that references the one it corrects — the storno rule.
66
7+
## 2026-08-17 — E-LGJ-THE-MIDDLE-TIER-IS-DELETED-NOT-WRAPPED-1
8+
9+
**Status:** DOCTRINE (operator-stated, scope confirmed). **Confidence:** High —
10+
four directives + three posters, restated and confirmed in session.
11+
12+
The blast radius, recorded because a session that reads this repo as "a faster
13+
Java binding to a Rust library" will make locally-sensible decisions that are
14+
globally wrong:
15+
16+
1. **The middle of the Java data stack is deleted, not wrapped.** Today:
17+
App → DTO/ORM → Gremlin/TinkerPop → JanusGraph → Cassandra → Elastic /
18+
ClickHouse / Lucene = six components, five serialization boundaries, three
19+
mental models. After: **one** explicit ABI boundary, **zero** serialization
20+
boundaries. The middleware and side-car analytics tiers do not get wrapped —
21+
lance-graph + ndarray under one Panama membrane already *are* the traversal,
22+
analytics and search substrate. *"Java als low-code Oberfläche, ABI als
23+
Wahrheit."*
24+
2. **Objects are eliminated, not optimized.** 10⁹ logical entities ⇒ **0** Java
25+
objects: no header tax, no GC churn, masks instead of pointers, survivors
26+
only touch heavy data. Valhalla's role is narrow and already measured here —
27+
it makes the *tiny descriptor vocabulary* free (≤8 B flattens; the 16 B
28+
entity does not), which is exactly why entities stay native and descriptors
29+
stay `record`-shaped.
30+
3. **The trust boundary collapses with the data boundary.** Mask-first: the
31+
RBAC/ABAC clamp composes BEFORE execution, the scan runs on authorized lanes
32+
only, and only aggregates/projections leave. Security enforced at the source
33+
is a *consequence* of zero-copy, not a feature bolted on.
34+
4. **The migration asymmetry is the weapon.** The developer-visible diff is
35+
`stream().filter(λ)``.where(Field.gt(...))`; everything underneath changes
36+
universe. Hence the standing rule: **the ABI is a machine membrane and never
37+
the product API** — the product is the illusion that ordinary Java just works
38+
at 10⁹ objects.
39+
40+
Operator's compression: *"Java Panama and Valhalla become the supraconductor
41+
over lance-graph ABI shaped SoA substrate."* Supraconductor is precise — current
42+
(the query) flows with no resistance (no allocation, no GC, no serialization)
43+
through a thin familiar surface.
44+
45+
**Consequence for review:** any proposal that adds a serialization step, a
46+
per-element crossing, an object materialization, or a post-filter security check
47+
is not a tradeoff to weigh — it contradicts the thesis and is rejected.
48+
49+
## 2026-08-17 — E-LGJ-THE-FLAT-FIXTURE-WAS-SCAFFOLDING-NOT-THE-TARGET-1
50+
51+
**Status:** CORRECTION (of my own framing). **Confidence:** High — operator
52+
correction, acted on the same session.
53+
54+
I answered the `simd_soa` question by measuring `MultiLaneColumn` against the
55+
**flat three-lane fixture**, found two real API mismatches, and recorded a
56+
"declined for now" verdict. The operator corrected the frame: *"the whole point
57+
is Java should optimize the SoA layout — we won't dismiss the initial plans
58+
just because you found it doesn't apply for unorganized non-SoA."*
59+
60+
The technical findings were right and are unchanged (see the entry below); the
61+
**conclusion drawn from them was scoped wrong**. The flat fixture was always
62+
scaffolding — `docs/abi.md` §10 and `architecture.md` said so from PR #1 ("the
63+
generic fixture in this first slice was deliberately chosen … so the membrane's
64+
physics could be proven independent of graph semantics"). Measuring a
65+
substrate-shaped tool against the scaffolding and concluding "not yet" inverted
66+
which one was provisional.
67+
68+
**The generalizable failure:** when a proposal doesn't fit the *current* code,
69+
check whether the proposal is early or whether the **code is the placeholder**.
70+
Here the code was the placeholder, and the right move was to build the real
71+
shape (the 512-byte row store, W2, shipped same session) rather than defer the
72+
tool. A "declined, revisit later" verdict is only honest when the thing it was
73+
measured against is the thing that stays.
74+
775
## 2026-08-17 — E-LGJ-SIMD-SOA-IS-FOR-THE-ROW-STORE-NOT-THE-FLAT-LANES-1
876

977
**Status:** DECISION (declined refactor, with the trigger for revisiting named).

.claude/board/INTEGRATION_PLANS.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,48 @@
1+
## 2026-08-17 — consumer-{world-trades,bricks-analytics,graph-traversal}-v1 (PLANS; the three W5 consumer examples)
2+
3+
Plans: `.claude/plans/consumer-world-trades-v1.md`,
4+
`consumer-bricks-analytics-v1.md`, `consumer-graph-traversal-v1.md`.
5+
6+
One operator poster made runnable per plan, all three over the SAME
7+
substrate, each exercising a different face: the fluent domain API with
8+
zero object allocation (trades); mask-first authorization where the RBAC
9+
clamp composes BEFORE execution and only aggregates leave (bricks); and
10+
traversal as facet addressing with crossings that scale with HOPS, not
11+
rows (graph). Each carries its own falsifier set, including the
12+
anti-vacuity requirement that a result be neither empty nor total.
13+
14+
**Sequencing:** all three are gated on `lgj-soa-substrate-v1` W3 (the
15+
Java `RowStore` facade); after that they are independently shippable in
16+
any order and none blocks the others. **Iron rule recorded in all three:**
17+
a consumer example that needs a new ABI symbol goes back through the
18+
substrate plan's wave process — the membrane never grows from the consumer
19+
side.
20+
21+
**Status: PLANNED.**
22+
23+
## 2026-08-17 — lgj-soa-substrate-v1 (PLAN; the lance-graph-shaped SoA substrate)
24+
25+
Plan: `.claude/plans/lgj-soa-substrate-v1.md`. Successor to
26+
`lgj-vertical-slice-v1` (COMPLETE, PRs #1-#4).
27+
28+
**What it covers:** the real layout — 64K × 512-byte rows, 32 facet lanes
29+
of (4-byte classid + 12-byte payload) — wired end to end, in five waves:
30+
W1 ndarray primitives (`iter_u32x16`, `eq_u32_strided_to_mask`), W2 the
31+
Rust row store (`LGJ_RESOURCE_ROWSTORE`, `lgj_rowstore_open`,
32+
`lgj_op_eq_classid`, `lgj_row_facet_match`, ABI minor 2, `abi.md` §11),
33+
W3 the Java `RowStore` facade, W4 a Vector-API-vs-crossing bench on the
34+
REAL layout, W5 the three consumer examples.
35+
36+
**Framing decision on record:** this plan exists because the flat
37+
three-lane fixture was always scaffolding
38+
(`E-LGJ-THE-FLAT-FIXTURE-WAS-SCAFFOLDING-NOT-THE-TARGET-1`). The doctrine
39+
it serves — the middle tier is deleted rather than wrapped, objects are
40+
eliminated rather than optimized, security collapses into the data
41+
boundary — is `E-LGJ-THE-MIDDLE-TIER-IS-DELETED-NOT-WRAPPED-1`.
42+
43+
**Status: ACTIVE.** W1 shipped (ndarray PR #279); W2 shipped (84/84,
44+
18/18 symbols); W3 is the next action.
45+
146
## 2026-08-17 — lgj-vertical-slice-v1 (PLAN; the first Panama×Valhalla×ndarray::simd proof)
247

348
Plan: `.claude/plans/lgj-vertical-slice-v1.md`. Active plan index — this

.claude/board/LATEST_STATE.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,53 @@
1+
## 2026-08-17 (Slice 2) — the SoA row store is REAL: ABI minor 2, W1+W2 shipped
2+
3+
**The reframing that started it** (operator, three directives): the flat
4+
three-lane fixture was always scaffolding; Java is meant to optimize the *SoA
5+
layout*; serialization is abandoned outright in favor of lance-graph's 64K
6+
zero-copy concurrency + the ndarray SIMD polyfill; Panama+Valhalla are "the
7+
supraconductor over lance-graph ABI shaped SoA substrate". Doctrine on the
8+
board as `E-LGJ-THE-MIDDLE-TIER-IS-DELETED-NOT-WRAPPED-1`; my own mis-scoped
9+
"declined" verdict corrected in
10+
`E-LGJ-THE-FLAT-FIXTURE-WAS-SCAFFOLDING-NOT-THE-TARGET-1`.
11+
12+
**Layout now in code** (operator-stated reference): 64K × **512 B rows, 32
13+
facet lanes of 16 B = 4-byte LE classid + 12-byte payload**, the lance-graph
14+
V3 content-blind facet. Java's own view may differ — these bytes are the
15+
substrate truth. Full statement: `.claude/knowledge/soa-row-store-layout.md`.
16+
17+
- **W1 (ndarray PR #279, open):** `MultiLaneColumn::iter_u32x16`/`len_u32x16`
18+
(the u32 lane whose absence was the real blocker) + `eq_u32_strided_to_mask`
19+
(the AoS-facet classid scan, overflow-checked bounds). `simd_int_ops` 46/46,
20+
`simd_soa` 15/15, `simd` 263/263, clippy/fmt clean, both x86 arms.
21+
- **W2 (this repo):** `rowstore.rs` + `LGJ_RESOURCE_ROWSTORE` +
22+
`lgj_rowstore_open` + `lgj_op_eq_classid` + `lgj_row_facet_match`; facet
23+
lanes ride the **unchanged** `LgjLaneDesc` (`stride_bytes` has carried this
24+
since minor 1). ABI **minor 1→2**, `docs/abi.md` §11 written, and the §1/§7
25+
"14 symbols" count corrected (its own list already enumerated 15; the real
26+
number is now 18, verified by `nm -D`). `cargo test` **84/84**, clippy
27+
`-D warnings` + fmt clean.
28+
- **Parity, three independent ways:** each SIMD kernel vs its independent
29+
scalar reference over 10 row counts × 2 seeds × 4 facets × 4 needles, then
30+
both cross-checked against `RowStore::classid_at`. Two-sided falsifier proves
31+
payload bytes carrying the needle's bit pattern never satisfy a classid
32+
match, and that a real classid match does fire.
33+
- **`byte_len` semantics tightened** to the exact covered span
34+
`(len-1)*stride + elem_bytes` — a full-stride final window would have let
35+
Java bound a segment past the allocation's end on a facet lane. Contiguous
36+
lanes unchanged.
37+
- **Masks parent onto row stores**, so the entire existing mask algebra applies
38+
with no new surface (proven end-to-end through the membrane).
39+
40+
**Planned and written this session:** `.claude/plans/lgj-soa-substrate-v1.md`
41+
(the W1–W5 wave plan) plus one plan per consumer example —
42+
`consumer-world-trades-v1.md` (zero-object fluent domain API),
43+
`consumer-bricks-analytics-v1.md` (mask-first RBAC, fail-closed, aggregates
44+
only), `consumer-graph-traversal-v1.md` (traversal as facet addressing,
45+
crossings ∝ hops). Iron rule in all three: **a consumer example never grows the
46+
membrane** — a needed symbol goes back through the wave process.
47+
48+
**Next:** W3, the Java `RowStore` facade (structured `MemoryLayout`,
49+
minor-≥2 gate, `FacetMatchView`, generator-transcribing parity test).
50+
151
## 2026-08-17 (later) — Phase I docs written, fusion re-run merged, simd_soa question answered (PR #4)
252

353
- **All four synthesis docs shipped** (`docs/architecture.md`,

.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 #4 — Phase I synthesis docs + fusion re-run + board hygiene (merged 2026-08-17, squash `bd92c58`)
12+
13+
- **Added:** `docs/{architecture,panama,valhalla-lab,execution-boundary}.md`
14+
(D-LGJ-I DONE — synthesis, each claim tied to its proving artifact);
15+
the fusion-sweep 256-row re-run (`RESULTS.md` rewritten from
16+
`jmh-results-merged.csv`, `TABLES.md` mechanically generated from the
17+
same file); refreshed Valhalla lab result files (findings unchanged).
18+
- **Locked:** the fusion self-correction — "fused ≈ unfused" was true
19+
only at 65,536 rows; at 256 rows × 8 predicates unfused/fused = 2.99×.
20+
The `MultiLaneColumn` decision
21+
(`E-LGJ-SIMD-SOA-IS-FOR-THE-ROW-STORE-NOT-THE-FLAT-LANES-1`): declined
22+
for flat lanes, earmarked for the 512-byte row-store slice; operator
23+
layout reference (64K × 512 B, 32 × (4 B classid + 12 B)) recorded.
24+
- **Deferred:** `NodeRow`/`WideFieldMask` wiring (unchanged);
25+
`MultiLaneColumn` adoption gated on that slice.
26+
- **Docs:** the four docs ARE the deliverable; board updated in-commit,
27+
incl. this file's #1-#3 backfill (lapse owned above).
28+
- **Confidence:** High — docs-only + measured data; both bot reviewers
29+
(cursor, codex) hit usage limits and did not run.
30+
1131
## PR #3 — Vector API bench: real JMH, cross-checked (merged 2026-08-17, squash)
1232

1333
- **Added:** `bench/` — real JMH 1.37 suite (Components A/B/C/E:

.claude/board/STATUS_BOARD.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,16 @@ and D-LGJ-G are the only rows still open; they are deliberately NOT blocking
2828
PR #1 (the core slice is independently complete and green) and will land as
2929
their own PR once the Lab agent finishes and is reviewed with the same
3030
rigor.
31+
32+
## lgj-soa-substrate-v1 — the lance-graph-shaped SoA substrate (2026-08-17)
33+
34+
Plan: `.claude/plans/lgj-soa-substrate-v1.md`. The 512-byte row / 32-facet
35+
layout wired end to end. Doctrine: `E-LGJ-THE-MIDDLE-TIER-IS-DELETED-NOT-WRAPPED-1`.
36+
37+
| D-id | Deliverable | Status |
38+
|---|---|---|
39+
| 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 |
40+
| 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 |
41+
| D-LGJ-W3 | Java `RowStore` facade: structured `MemoryLayout`, minor-≥2 gate, `FacetMatchView`, parity test transcribing the generator | **NEXT** |
42+
| D-LGJ-W4 | Bench Component F: Vector API facet scan vs the crossing, on the REAL layout | Queued |
43+
| D-LGJ-W5 | Three consumer examples (trades / bricks / graph) — one plan file each | Planned, gated on W3 |
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# The SoA row store layout — the substrate everything converges on
2+
3+
> **READ BY:** `abi-membrane-warden`, `simd-savant`, `panama-bridge-engineer`,
4+
> `java-surface-warden`, and any session touching `rowstore.rs`,
5+
> `docs/abi.md` §11, the Java `RowStore` facade, or a consumer example.
6+
> **MANDATORY** before proposing any change to row geometry, facet
7+
> semantics, or the lane map.
8+
9+
## The layout (operator-stated, 2026-08-17)
10+
11+
> *"the 64k x 512 bytes SoA layout is enforced everywhere in lance-graph
12+
> (32 Lanes each 4 bytes classview+12 bytes). For Java the layout might
13+
> differ just for reference."*
14+
15+
```
16+
row (512 B) = 32 × facet (16 B)
17+
facet (16 B) = classid (4 B, little-endian u32) ++ payload (12 B)
18+
```
19+
20+
This is the lance-graph **V3 content-blind facet** — the same shape the
21+
sibling repos pin as canon (`E-V3-FACET-4-PLUS-12`; the 12 bytes are an
22+
axis-grouped byte register read as `6×(u8:u8)` / `4×(u8:u8:u8)` /
23+
`3×(u8:u8:u8:u8)` per the ClassView, never widened). `lgj-abi` treats the
24+
12 bytes as opaque **on purpose**: the ABI is a machine membrane and the
25+
payload's *reading* is a ClassView concern one layer up.
26+
27+
**"For Java the layout might differ" is load-bearing.** The Java side is
28+
free to project a different view (a structured `MemoryLayout`, a different
29+
field grouping, a Valhalla-shaped descriptor vocabulary). These bytes are
30+
the substrate truth; the Java view is a *reading* of them. Nothing in the
31+
Java facade may assume its own view is the storage layout.
32+
33+
## The two readings, one buffer, zero copies
34+
35+
| reading | how it is addressed | who uses it |
36+
|---|---|---|
37+
| **row** | row `r` = bytes `r*512..(r+1)*512`; facet `f` at `+f*16` | `MultiLaneColumn::iter_u32x16` (4 facets per 64-B chunk), Java's structured layout |
38+
| **facet lane** | strided u32 column: `first_offset = f*16`, `stride = 512` | `eq_u32_strided_to_mask`, `LgjLaneDesc` (which has carried `stride_bytes` since minor 1) |
39+
40+
Neither is a copy. The buffer is one `Arc<[u8]>`; a clone is a refcount
41+
bump. **There is no serialization anywhere in this stack** — that is the
42+
whole point (operator: *"abandon any use of serialization in favor of
43+
lance-graph 64k concurrency zero copy and ndarray SIMD polyfill"*), and it
44+
composes with lance-graph's own doctrine that an SoA envelope is zero-copy
45+
from creation to Lance tombstone.
46+
47+
## Facts a session must not re-derive
48+
49+
- **`n_rows * 512` is always a multiple of 64** — so
50+
`MultiLaneColumn::new` is infallible here *by construction*, not by
51+
luck. Pinned by `the_buffer_is_exactly_n_times_512_bytes`.
52+
- **Classids sit at `U32x16` positions 0, 4, 8, 12** of each 64-byte
53+
chunk. The `& 0x1111` mask in the facet-match kernel is what keeps
54+
payload bytes from ever satisfying a classid predicate — pinned
55+
two-sided by `facet_match_ignores_needle_patterns_in_payload_bytes`.
56+
- **`byte_len` is the EXACT covered span** `(len-1)*stride + elem_bytes`,
57+
never `len * stride`. A facet lane's base sits `f*16` into the buffer, so
58+
a full-stride final window would let Java bound a segment past the
59+
allocation's end. Contiguous lanes reduce to the old formula unchanged.
60+
- **`facet``lane_id`.** Lane 0 is the raw buffer; facet `f`'s lane id is
61+
`1 + f`. `lgj_op_eq_classid` takes a **facet index**. Pinned by the
62+
end-to-end test asserting facet 32 is invalid while lane 32 is valid.
63+
- **Masks parent onto row stores** exactly as onto patterns — both are
64+
read-only, row-shaped resources — so the entire existing mask algebra
65+
(`and`/`or`/`count`/`describe`, direct Java word writes) applies with no
66+
new surface.
67+
- **Alignment, honestly:** the base is `u8`-aligned (`Arc<[u8]>` promises
68+
no more on stable Rust). Rows are strided at 512. Nothing here needs
69+
more — Panama has unaligned value layouts, and every `ndarray::simd` load
70+
is a register fill. The `align(64)` base guarantee arrives with real
71+
`NodeRow` (`#[repr(C, align(64))]`) wiring, not before.
72+
73+
## Why `MultiLaneColumn` fits HERE and not in the flat fixture
74+
75+
Recorded because the answer flipped once already
76+
(`E-LGJ-SIMD-SOA-IS-FOR-THE-ROW-STORE-NOT-THE-FLAT-LANES-1`):
77+
78+
- It requires `len % 64 == 0` with **no tail arm**. The flat fixture's
79+
lanes are caller-sized `n_rows` of 4/8-byte elements — arbitrary. The
80+
row store's buffer is `n*512` — always conforming.
81+
- Its typed iterators are 64-byte chunk views, which is *exactly* a
82+
four-facet group and *not* a natural fit for a flat column scan (where
83+
`simd_int_ops`' own group-plus-scalar-tail loop is the right shape).
84+
85+
So both consumers are correct and neither is a workaround: use
86+
`simd_int_ops` primitives for flat columns, `MultiLaneColumn` for the row
87+
store. The u32 lane (`iter_u32x16`) was added to ndarray specifically to
88+
close the gap that made the second impossible.

0 commit comments

Comments
 (0)