|
| 1 | +## E-PANAMA-TIMES-VALHALLA-IS-ONE-MEMBRANE-AND-THE-LAB-FINALLY-HAS-ONE-VARIABLE-1 (2026-09-19) |
| 2 | + |
| 3 | +**Operator reframing, and it is the correct one:** this was never a JDK 26→28 |
| 4 | +migration. It is **Panama-only production + Valhalla-lab arm → one production |
| 5 | +Panama × Valhalla membrane.** JDK 28 is the enabling toolchain, not the point. |
| 6 | +Panama answers *where are the bytes and how does Java reach them without |
| 7 | +copying*; Valhalla answers *how does Java name and carry semantic values over |
| 8 | +those bytes without identity-heavy heap objects*. lance-graph / T0 remains the |
| 9 | +only owner of canonical storage. |
| 10 | + |
| 11 | +### Measured on JDK 28 (`/opt/jdks/jdk-28+16`), production value-shaped |
| 12 | + |
| 13 | +| gate | result | |
| 14 | +|---|---| |
| 15 | +| native `lgj-abi` | **183 tests** green (182 + the new Range falsifier) | |
| 16 | +| Java `AllTests`, production as `value record` | **ALL PASSED (409 checks)** — identical to the unflipped baseline on the same JDK | |
| 17 | +| all six vocabulary types | `isValue() == true`; substitutability holds | |
| 18 | + |
| 19 | +### The flattening cliff is UNCHANGED on JDK 28 — and it bites one of our six |
| 20 | + |
| 21 | +Measured by the lab, both arms on one JDK: |
| 22 | + |
| 23 | +| type | payload | array | |
| 24 | +|---|---|---| |
| 25 | +| `LaneId` (1 int) | 4 B | **FLAT** | |
| 26 | +| `Ordinal` (1 int) | 4 B | **FLAT** | |
| 27 | +| `MaskId` (1 long) | 8 B | **FLAT** | |
| 28 | +| `RowRange` (2 long) | 16 B | **NOT-FLAT** | |
| 29 | +| `Row` (1 long + 2 int) | 16 B | **NOT-FLAT** | |
| 30 | + |
| 31 | +The ≤8-byte cliff measured on the 27-jep401ea3 build **reproduces on JDK 28**. |
| 32 | +So `value semantics` and `flattening` remain two different claims, and |
| 33 | +**`RowRange` is a real production type on the wrong side of the cliff.** No |
| 34 | +performance claim may be made for it from `isValue() == true`. |
| 35 | + |
| 36 | +### Allocation deltas, record arm → value arm (1M ops, same JDK) |
| 37 | + |
| 38 | +| measurement | record | value | delta | |
| 39 | +|---|---|---|---| |
| 40 | +| construct N `LaneId` into an array | 15.26 MiB | **1.87 MiB** | **8.2× less** (16 B → 1.96 B each) | |
| 41 | +| allocate+fill `LaneId[N]` | 19.07 MiB | **5.65 MiB** | 3.4× less | |
| 42 | +| construct N `Descriptor` (2 wrappers) | 45.78 MiB | **27.89 MiB** | 1.6× less | |
| 43 | +| construct N `LaneId`, never escaping | 4.73 MiB | **5.75 MiB** | ⊘ **value arm allocates MORE** — escape analysis already erased the record case | |
| 44 | +| hydrate 65,536 `Row` | 1.50 MiB | **2.00 MiB** | ⊘ **worse** — `Row` is 16 B, NOT-FLAT | |
| 45 | + |
| 46 | +**Two of the five went the wrong way, and both are honest.** Where EA already |
| 47 | +won, value classes add nothing; where the shape does not flatten, hydration |
| 48 | +costs more. Production does not hydrate rows, so the second is a warning about |
| 49 | +a path we do not take — not a regression we shipped. |
| 50 | + |
| 51 | +### The Panama path is byte-identical across the object model |
| 52 | + |
| 53 | +| FFM measurement | record arm | value arm | |
| 54 | +|---|---|---| |
| 55 | +| Java bytes allocated per query (warm) | 712 B | **712 B** | |
| 56 | +| Java objects per row | 0 | **0** | |
| 57 | +| native lane bytes / mask bytes | 1.00 MiB / 8.0 KiB | **identical** | |
| 58 | + |
| 59 | +**That is the integration's central invariant, measured rather than asserted:** |
| 60 | +Valhalla changes what the *vocabulary* costs; it does not touch the membrane. |
| 61 | +No second graph representation appeared, and no row hydration was introduced |
| 62 | +to "use Valhalla". |
| 63 | + |
| 64 | +### The lab's re-scope removed a confound it had carried since it was built |
| 65 | + |
| 66 | +The old A/B was `(record, JDK 26)` vs `(value record, JDK 27 EA)` — **two |
| 67 | +variables**. Both arms now run on JDK 28, so the object model is the only |
| 68 | +difference and the diff is attributable. The lab stops being |
| 69 | +present-vs-future and becomes a VM/representation probe suite over the real |
| 70 | +production vocabulary. |
| 71 | + |
| 72 | +Two things the re-scope forced, both real: |
| 73 | +- **Preview marking is transitive.** Production classfiles are preview-marked, |
| 74 | + so the `record` arm *also* runs `--enable-preview`. It is a record arm on a |
| 75 | + preview-enabled JVM, not a preview-free arm; the "stable" label survives only |
| 76 | + as a path. |
| 77 | +- **A JDK 28 internal-API change.** `jdk.internal.value.ValueClass.isFlatArray` |
| 78 | + narrowed from `Object` to `Object[]` since 27-jep401ea3; the flattening probe |
| 79 | + was adapted (a non-`Object[]` argument answers `NOT-FLAT` without reaching |
| 80 | + the internal API). |
| 81 | + |
| 82 | +### The `RangeOutOfBounds` fix is compatibility plumbing, with a falsifier |
| 83 | + |
| 84 | +`mask_risc::ExecError` gained `RangeOutOfBounds` with lance-graph's |
| 85 | +`Pred::Range` work and `exec_error_to_status` matches exhaustively, so |
| 86 | +`lgj-abi` **did not compile against lance-graph `main`**. Mapped into the |
| 87 | +documented *"bug in THIS file"* family — no new public status, no ABI bump — |
| 88 | +and backed by `plan_lower::range_falsifier::no_opcode_lowers_to_pred_range`, |
| 89 | +which sweeps every opcode through the real `lower_plan` and reads every |
| 90 | +emitted predicate. **Disable-verified red-then-green:** pointing `LGJ_OP_EQ_U32` |
| 91 | +at `Pred::Range` fails it with the intended message; restoring passes. When LGJ |
| 92 | +gains a Range lowering this goes red and forces a deliberate caller-visible |
| 93 | +mapping instead of inheriting `LGJ_ERR_ALLOCATION_FAILED`, which would then be |
| 94 | +a lie. |
| 95 | + |
| 96 | +### What could not be measured here, stated rather than implied |
| 97 | + |
| 98 | +`bench/` cannot run in this container — `bench/lib` does not exist, so the JMH |
| 99 | +jars are absent. The crossing-count and per-call allocation figures in |
| 100 | +`bench/RESULTS.md` are **historical JDK 26 measurements, correct when taken**, |
| 101 | +and are deliberately left untouched; they have NOT been re-taken on JDK 28. |
| 102 | +`gate-run.sh` carries the new pin and flags so the re-run is one command once |
| 103 | +the jars are present. |
| 104 | + |
1 | 105 | ## E-THE-VALHALLA-FLIP-COST-ONE-WORD-SIX-TIMES-AND-THE-GATES-DID-NOT-MOVE-1 (2026-09-19) |
2 | 106 |
|
3 | 107 | **Finding, measured end to end today.** The P0 mandate (JDK 28 + Valhalla + |
|
0 commit comments