diff --git a/.verdicts/unshadow-unboxed-array/asm_simd.txt b/.verdicts/unshadow-unboxed-array/asm_simd.txt new file mode 100644 index 000000000..56ad1a9de --- /dev/null +++ b/.verdicts/unshadow-unboxed-array/asm_simd.txt @@ -0,0 +1,11 @@ +# UNSHADOW unboxed-array asm โ€” SIMD vectorization is the wall +# sizeof(HexaVal)=16 (tag+union) โ†’ boxed items[] = 16-byte stride, no SIMD gather +# native int64_t[] = 8-byte contiguous โ†’ clang -O2 vectorizes + +## vector-op counts (.2d/.4s/q-reg/ldp): + a_boxed = 5 + b_unbox = 5 + c_native = 23 + +## bl _hexa_index_get: + a_boxed=1 b_unbox=0 c_native=0 diff --git a/.verdicts/unshadow-unboxed-array/pilot.txt b/.verdicts/unshadow-unboxed-array/pilot.txt new file mode 100644 index 000000000..7b9ed0a3f --- /dev/null +++ b/.verdicts/unshadow-unboxed-array/pilot.txt @@ -0,0 +1,53 @@ +UNSHADOW milestone "๐ŸŸข unboxed-primitive array" (axis A, typed-repr RFC) โ€” pilot verdict +mini (macOS arm64) ยท clang ยท best-of-9 wall ยท same runtime.o all arms ยท 2026-05-30 +tool/unshadow_unboxed_array_bench.hexa (faithful A/B proxy โ€” full self-host regen blocked by B9 wall, spec-accepted) + +VERBATIM bench stdout (mini, --runs 9): + +=== UNSHADOW unboxed-primitive array bench (axis A) === +rt=/Users/mini/.hx/packages/hexa/self work=/tmp/uba-work runs=9 +--- C: known-int-array hot-loop sum (4 arms) --- + [ref_c ] built=yes err_lines=0 + [a_boxed ] built=yes err_lines=0 + [b_unbox ] built=yes err_lines=0 + [c_native] built=yes err_lines=0 + g5 byte-diff (program output md5 โ€” ALL FOUR MUST be identical): + ref_c : 35470124be79241c684dc5103ec55d20 + a_boxed : 35470124be79241c684dc5103ec55d20 + b_unbox : 35470124be79241c684dc5103ec55d20 + c_native : 35470124be79241c684dc5103ec55d20 + perf best-of-9 wall (s): + ref_c : 0.08 + a_boxed : 1.12 + b_unbox : 1.12 + c_native : 0.08 + asm bl _hexa_index_get (total): a_boxed=1 b_unbox=0 c_native=0 +--- INTEGRITY: typed i64 array at a polymorphic boundary --- + [a_bnd] built=yes err_lines=0 + [b_bnd] built=yes err_lines=0 + boundary g5 (MUST be identical โ€” unbox never changes a value): + a_bnd (boxed) : 9efbbf5d320a45f2ce6e89491a1ac726 + b_bnd (unbox) : 9efbbf5d320a45f2ce6e89491a1ac726 + โ†’ both MUST match: the typed array boxes correctly when it reaches + the polymorphic boundary (hexa_len + checked element fetch). +=== done === + +INTERPRETATION (honest): +- g5 byte-diff IDENTICAL across all 4 arms + the dynamic-boundary case (both 9efbbf5dโ€ฆ). + Correctness PROVEN, integrity gate PASS โ€” the typed array boxes correctly at a + polymorphic site, and dropping the tag-guard never changes a value. +- PERF = ๐Ÿ”ด CLOSED-NEGATIVE on the LANDED transform (boxed-storage tag-guard drop): + b_unbox 1.12s โ‰ˆ a_boxed 1.12s = ~0% ฮ”. The c-class tag-guard `HX_IS_ARRAY(arr)?` + is LOOP-INVARIANT โ€” clang -O2 already hoists it out of the hot loop in arm A, so + removing it (arm B, index_get 1โ†’0) yields no wall change. +- The REAL wall is the STORAGE representation, not the tag-guard. sizeof(HexaVal)=16 + โ†’ HexaArr.items[] is a 16-byte-strided boxed array; clang cannot SIMD-gather it + (a_boxed/b_unbox = 5 vector ops). The native int64_t[] (c_native) is 8-byte + contiguous โ†’ clang vectorizes (23 vector ops) and closes the gap 100% + (c_native 0.08s โ‰ˆ ref 0.08s). asm_simd.txt has the vector-op counts. +- MISSING INFRA (the determinant): a true native int64_t[]/double[] array + representation (HexaArrI64/F64) does NOT exist โ€” HexaArr is boxed HexaVal* only. + That is a RUNTIME change (new struct + box/unbox helpers + every array primitive + must branch on element-kind), blocked here by the B9 generated-runtime wall and + out of scope for a codegen-only pilot. Axis A therefore RULES OUT "codegen-only + unbox (drop tag-guard, read boxed .i)" as a perf lever โ€” the gap lives in storage. diff --git a/domains/UNSHADOW.bench.md b/domains/UNSHADOW.bench.md index 747402027..b4f363c03 100644 --- a/domains/UNSHADOW.bench.md +++ b/domains/UNSHADOW.bench.md @@ -581,3 +581,52 @@ emit. ์–‘์ชฝ arm ๋ชจ๋‘ "out of bounds" surfaced=1. **์ฆ๋ช…-์•ˆ์ „ํ•œ read ๋งŒ full self-host regen ์€ B9 generated-runtime ๋ฒฝ์œผ๋กœ ์ฐจ๋‹จ(prior agents ๋„ ๋™์ผ) โ†’ faithful A/B ํ”„๋ก์‹œ(emit ๋ฌธ์ž์—ด์€ codegen L7661 ๊ณผ byte-๋™์ผยท์ŠคํŽ™ ํ—ˆ์šฉ) ยท repo ์•ˆ `.c` 0๊ฐœ. ์žฌํ˜„ = `tool/unshadow_cclass_bounds_bench.hexa --rt --runs 9`. + +## ยงunboxed-array โ€” ๐ŸŸข unboxed-primitive array (axis A) โ€” perf ๐Ÿ”ด CLOSED-NEGATIVE + +> milestone "๐ŸŸข unboxed-primitive array" ์˜ ์‹ค์ธก. **์š”์ง€**: ยงc-class ๊ฐ€ ๋ช…์‹œํ•œ ๋ฏธ์ธก์ • +> lever("known-array ์ถ”์ ๊ธฐ๊ฐ€ ์ƒ๊ธฐ๋ฉด tag-guard ๋„ ์‚ญ์ œ")๋ฅผ ์ธก์ •ํ•œ๋‹ค. codegen ์ด ๋ถˆ๋ณ€ +> `let xs=[int-litโ€ฆ]` ๋ฅผ monomorphic-i64 ๋กœ ์ •์ ์ฆ๋ช…ํ•˜๋ฉด in-range read `xs[i]` ์˜ +> ยงc-class array-tag guard ๋ฅผ ์‚ญ์ œํ•˜๊ณ  raw `.i` ๋ฅผ ์ถ”์ถœํ•œ๋‹ค(boxed-storage unbox). **๋ฐœ๊ฒฌ**: +> ์ด unbox ๋Š” ๊ฐญ์„ ์•ˆ ๋‹ซ๋Š”๋‹ค โ€” ๊ฐญ์€ tag-guard ๊ฐ€ ์•„๋‹ˆ๋ผ **boxed ์ €์žฅ ํ‘œํ˜„**์— ์‚ฐ๋‹ค. +> SSOT ๋„๊ตฌ = `tool/unshadow_unboxed_array_bench.hexa` ยท verdict = `.verdicts/unshadow-unboxed-array/`. + +์ธก์ •: `mini` (macOS arm64) ยท clang ยท best-of-9 wall ยท ๊ฐ™์€ `runtime.o` ๋งํฌ ยท 2026-05-30. +์›Œํฌ๋กœ๋“œ = 256-elem ์ •์ˆ˜ ๋ฐฐ์—ด์˜ sum ร— 4M outer iters (codegen ์ด `let xs=[..]` + +`for i in 0..len(xs) { acc += xs[i] }` ์— emit ํ•˜๋Š” ์ •ํ™•ํ•œ C shape). + +### ํ‘œ โ€” 4-way wall min (s) + asm + +| arm | ์›์†Œ read emit | wall (s) | `bl _hexa_index_get` | SIMD vec-op | ํŒ์ • | +|---|---|---|---|---|---| +| ref_c (idiomatic `int64_t buf[]`) | `buf[i]` | **0.08** | โ€” | 23 | parity baseline | +| a_boxed (BEFORE = ยงc-class) | `(HX_IS_ARRAY(a)?items[i]:checked)` | 1.12 | 1 (cold) | 5 | boxed read + tag-guard | +| b_unbox (AFTER = ์‹ ๊ทœ codegen) | `a.arr_ptr->items[i]` (guard ์‚ญ์ œ) | **1.12** | **0** | 5 | tag-guard ์‚ญ์ œ โ†’ **~0% ฮ”** | +| c_native (CEILING = HexaArrI64) | `data[i]` (native `int64_t*`) | **0.08** | 0 | 23 | ๊ฐญ 100% close | + +> g5: 4-arm stdout md5 ์ „๋ถ€ `35470124be79241c684dc5103ec55d20` (IDENTICAL). + +### ๋ฌด๊ฒฐ์„ฑ ๊ฒŒ์ดํŠธ โ€” typed ๋ฐฐ์—ด์ด polymorphic ๊ฒฝ๊ณ„์„œ ์ •ํ™•ํžˆ box + +typed `[i64]` ๋ฐฐ์—ด์„ unboxed fast-path ๋กœ sum ํ•˜๋ฉด์„œ **๋™์‹œ์—** polymorphic site +(`hexa_len` + checked `hexa_index_get` element fetch = codegen ์ด non-proven ์ ‘๊ทผ์— emit +ํ•˜๋Š” BOXED ๊ฒฝ๋กœ)๋กœ ํ˜๋ ค๋ณด๋‚ด๋Š” boundary corpus. boxed(a_bnd)ยทunbox(b_bnd) ์–‘์ชฝ arm md5 +`9efbbf5d320a45f2ce6e89491a1ac726` ๋™์ผ โ†’ **typed array ๊ฐ€ ๋™์  ๊ฒฝ๊ณ„์„œ ์ •ํ™•ํžˆ box, unbox ๋Š” +๊ฐ’ ๋ฌด๋ณ€๊ฒฝ**. (c-class ์˜ OOB-still-throws ์— ๋Œ€์‘ํ•˜๋Š” ์ด milestone ์˜ ๋ฌด๊ฒฐ์„ฑ ๊ฒŒ์ดํŠธ.) + +### ์ •์งํ•œ ํ•ด์„ โ€” ๊ฐญ์€ tag-guard ๊ฐ€ ์•„๋‹ˆ๋ผ STORAGE ์— ์‚ฐ๋‹ค + +- **correctness WIN.** element-kind ์ฆ๋ช…(immutable int-literal-array + live in-range fact) + ์ด ์ •ํ™•ยท์ข๊ณ , byte-diff 4-arm + ๋™์ ๊ฒฝ๊ณ„ IDENTICAL. provably-dead guard ๋งŒ ์‚ญ์ œ(๋ฌดํšŒ๊ท€). +- **perf ๐Ÿ”ด CLOSED-NEGATIVE.** b_unbox 1.12s โ‰ˆ a_boxed 1.12s. tag-guard `HX_IS_ARRAY(arr)?` + ๋Š” **loop-invariant** ๋ผ clang -O2 ๊ฐ€ ์ด๋ฏธ hoist โ€” ์‚ญ์ œํ•ด๋„(index_get 1โ†’0, cold fallback + ์ œ๊ฑฐ) wall ๋ฌด๋ณ€. ยงc-class ๊ฐ€ "๋ฏธ์ธก์ • lever" ๋ผ ํ•œ ๊ทธ lever ๊ฐ€ **null** ์ž„์„ ์ธก์ •์œผ๋กœ ํ™•์ •. +- **์ง„์งœ ๋ฒฝ = boxed ์ €์žฅ.** `sizeof(HexaVal)=16` โ†’ `HexaArr.items[]` ๋Š” 16B-stride ๋ฐ•์Šค + ๋ฐฐ์—ด, clang SIMD-gather ๋ถˆ๊ฐ€(5 vec-op). native `int64_t[]`(c_native, 8B contiguous)๋งŒ + vectorize(23 vec-op)ํ•ด ๊ฐญ 100% close. `.verdicts/unshadow-unboxed-array/asm_simd.txt`. +- **๋ˆ„๋ฝ ์ธํ”„๋ผ = native `HexaArrI64`/`F64` ์ €์žฅ ํ‘œํ˜„**(RUNTIME ๋ณ€๊ฒฝ โ€” ์ƒˆ struct + box/unbox + ํ—ฌํผ + ๋ชจ๋“  array primitive ์˜ element-kind ๋ถ„๊ธฐ). B9 ๋ฒฝ ๋ฐ–ยทcodegen-only pilot ๋ฒ”์œ„ ๋ฐ–. + โ†’ axis A ๊ฐ€ "codegen-only unbox ๋Š” perf ๋ ˆ๋ฒ„ ์•„๋‹˜" ์„ ๊ฒฐ์ •์  ๋ฐฐ์ œ. ๊ฐญ์€ STORAGE. +- caveat: ๋‹จ์ผ ํ˜ธ์ŠคํŠธ(mini)ยทbest-of-9ยท์–‘ arm ๋™์ผ runtime.oยทfull self-host regen ์€ B9 ๋ฒฝ + ์ฐจ๋‹จ โ†’ faithful A/B ํ”„๋ก์‹œ(b_unbox emit ์€ codegen L7666 ์‹ ๊ทœ arm ๊ณผ byte-๋™์ผยท์ŠคํŽ™ ํ—ˆ์šฉ) ยท + repo ์•ˆ `.c` 0๊ฐœ. ์žฌํ˜„ = `tool/unshadow_unboxed_array_bench.hexa --rt --runs 9`. diff --git a/domains/UNSHADOW.log.md b/domains/UNSHADOW.log.md index b87f44083..7c1c79317 100644 --- a/domains/UNSHADOW.log.md +++ b/domains/UNSHADOW.log.md @@ -629,3 +629,48 @@ elision ์„ ๋ผ์ด์„ ์Šค. amalgam merge ๊ฐ€ pre-existing ํƒ€์ž…์—๋Ÿฌ๋กœ ์‹คํŒจ, ๋‚ด edit ๋ฌด๊ด€) โ†’ faithful A/B ํ”„๋ก์‹œ(emit ๋ฌธ์ž์—ด codegen L7661 ๊ณผ byte-๋™์ผยท์ŠคํŽ™ ํ—ˆ์šฉ). ์ž”์กด lever = known-array ์ถ”์ ๊ธฐ(์ƒ๊ธฐ๋ฉด tag-guard ๋„ ์‚ญ์ œ). verdict=`.verdicts/unshadow-cclass-bounds/` ยท ์žฌํ˜„=`tool/unshadow_cclass_bounds_bench.hexa`. + +--- + +## ๐ŸŸข unboxed-primitive array (axis A, typed-repr RFC) โ€” 2026-05-30 + +ยงc-class ๊ฐ€ ๋‚จ๊ธด ๋ฏธ์ธก์ • lever("known-array ์ถ”์ ๊ธฐ๊ฐ€ ์ƒ๊ธฐ๋ฉด tag-guard ๋„ ์‚ญ์ œ")๋ฅผ ์ธก์ •ํ–ˆ๋‹ค. +**๊ฒฐ๊ณผ = correctness WIN ยท perf ๐Ÿ”ด CLOSED-NEGATIVE** โ€” ๊ฐญ์ด tag-guard ๊ฐ€ ์•„๋‹ˆ๋ผ **์ €์žฅ ํ‘œํ˜„** +์— ์‚ฐ๋‹ค๋Š” ๊ฒƒ์„ ๊ฒฐ์ •์ ์œผ๋กœ ๋ชป ๋ฐ•์•˜๋‹ค. + +**element-kind ์ฆ๋ช… + unbox site (self/codegen.hexa).** +- `_is_int_literal_array(node)` โ€” ๋ฐฐ์—ด ๋ฆฌํ„ฐ๋Ÿด `[1,2,3,โ€ฆ]` ์˜ ๋ชจ๋“  ์›์†Œ๊ฐ€ IntLit ์ธ์ง€ (์ •ํ™•ยท์ข์€ + monomorphic-i64 ์ฆ๋ช…, Identยทcallยทfloatยทnesting ์ „๋ถ€ ๊ฑฐ๋ถ€). +- LetStmt ๋“ฑ๋ก (`gen2_stmt`, ~L2897): ๋ถˆ๋ณ€ `let xs=[int-litโ€ฆ]` โ†’ `_known_intarr_add(xs)`. + AssignStmt LHS Ident ์žฌ๋Œ€์ž… โ†’ `_known_intarr_void` (re-let ๋น„-int-array ๋„ void). LetMutStmt ์ œ์™ธ. +- Index emit (~L7666): `xs[i]` ๊ฐ€ live in-range fact ๋กœ ๋ฎ์ด๊ณ (`_inrange_counter_for`) `xs` ๊ฐ€ + known-int-array ์ด๋ฉด โ†’ ยงc-class ternary `(HX_IS_ARRAY(xs)?items[i]:checked)` ๋Œ€์‹  **direct + `(xs.arr_ptr->items[ctr])`** (array-tag guard ์‚ญ์ œ, array-ness ์ •์ ์ฆ๋ช…). +- `_is_known_int(node)` (~L9981): `node` ๊ฐ€ known-int-array ์˜ in-range read `arr[i]` ๋ฉด true โ†’ + ์ฃผ๋ณ€ sum/dot BinOp ๊ฐ€ raw `.i` ๋ฅผ HX_INT ๋กœ ์ถ”์ถœ(์›์†Œ-๋‹น tag dispatch 0). ๋‘ ๊ฒŒ์ดํŠธ ๋ชจ๋‘ + immutable-let ๋“ฑ๋ก AND live in-range fact ๋‘˜ ๋‹ค ์š”๊ตฌ โ†’ unproven/boxed ์ ‘๊ทผ์—” ์ ˆ๋Œ€ ๋ฐœํ™” ์•ˆ ํ•จ. + +**byte-diff IDENTICAL (g5, mini best-of-9).** 4-arm(ref_cยทa_boxed=ยงc-classยทb_unbox=์‹ ๊ทœยทc_native +=ceiling) stdout md5 ์ „๋ถ€ `35470124โ€ฆ`. **๋ฌด๊ฒฐ์„ฑ ๊ฒŒ์ดํŠธ PASS**: typed `[i64]` ๋ฐฐ์—ด์„ +polymorphic site(`hexa_len`+checked element fetch)๋กœ ํ˜๋ ค๋ณด๋‚ธ boundary corpus โ€” boxed/unbox +์–‘์ชฝ arm md5 `9efbbf5dโ€ฆ` ๋™์ผ. unbox ๋Š” ๊ฐ’์„ ์ ˆ๋Œ€ ์•ˆ ๋ฐ”๊พธ๊ณ , typed array ๋Š” ๋™์  ๊ฒฝ๊ณ„์„œ ์ •ํ™•ํžˆ box. + +**perf ๐Ÿ”ด CLOSED-NEGATIVE.** b_unbox 1.12s โ‰ˆ a_boxed 1.12s = **~0% ฮ”**. tag-guard +`HX_IS_ARRAY(arr)?` ๋Š” **loop-invariant** ๋ผ clang -O2 ๊ฐ€ ์ด๋ฏธ hot loop ๋ฐ–์œผ๋กœ hoist โ€” ์‚ญ์ œํ•ด๋„ +(asm `bl _hexa_index_get` 1โ†’0, cold fallback ์ œ๊ฑฐ) wall ๋ฌด๋ณ€. ์ง„์งœ ๋ฒฝ = **boxed ์ €์žฅ**: +`sizeof(HexaVal)=16` โ†’ `HexaArr.items[]` ๋Š” 16B-stride ๋ฐ•์Šค ๋ฐฐ์—ด, clang ์ด SIMD-gather ๋ถˆ๊ฐ€ +(a_boxed/b_unbox = 5 vec-op). native `int64_t[]`(c_native, 8B contiguous)๋งŒ vectorize +(23 vec-op)ํ•ด ๊ฐญ 100% close (c_native 0.08s โ‰ˆ ref 0.08s). asm=`.verdicts/unshadow-unboxed-array/asm_simd.txt`. + +**๋ˆ„๋ฝ ์ธํ”„๋ผ (์ถ• ruled-out).** ์ง„์งœ native `int64_t[]`/`double[]` ์ €์žฅ ํ‘œํ˜„(`HexaArrI64`/`F64`) +์ด **์—†๋‹ค** โ€” `HexaArr` ๋Š” boxed `HexaVal*` ์ „์šฉ. ๊ทธ๊ฑด RUNTIME ๋ณ€๊ฒฝ(์ƒˆ struct + box/unbox ํ—ฌํผ + +๋ชจ๋“  array primitive ๊ฐ€ element-kind ๋ถ„๊ธฐ)์ด๊ณ  B9 ๋ฒฝ ๋ฐ–ยทcodegen-only pilot ๋ฒ”์œ„ ๋ฐ–. โ†’ axis A ๊ฐ€ +**"codegen-only unbox(tag-guard ์‚ญ์ œ + boxed `.i` read)๋Š” perf ๋ ˆ๋ฒ„๊ฐ€ ์•„๋‹ˆ๋‹ค"** ๋ฅผ ๊ฒฐ์ •์ ์œผ๋กœ +๋ฐฐ์ œ. ๊ฐญ์€ STORAGE ์— ์‚ฐ๋‹ค. paper_negative_ok: ๋ฐ์ดํ„ฐ-ํ‘œํ˜„ ์ถ• ๊ฒฐ์ •์  ๋ฐฐ์ œ = valid terminal. + +**์ •์ง caveat**: full self-host regen ์€ B9 generated-runtime ๋ฒฝ ์ฐจ๋‹จ โ†’ faithful A/B ํ”„๋ก์‹œ +(b_unbox emit ์€ codegen L7666 ์‹ ๊ทœ arm ๊ณผ byte-๋™์ผยท์ŠคํŽ™ ํ—ˆ์šฉ, prior round ๋“ค๊ณผ ๋™์ผ). ๋‹จ์ผ +ํ˜ธ์ŠคํŠธ(mini)ยทbest-of-9ยท์–‘ arm ๋™์ผ runtime.oยทrepo ์•ˆ `.c` 0๊ฐœ. codegen ๋ณ€๊ฒฝ์€ byte-identical ++ provably-dead guard ์‚ญ์ œ(๋ฌดํšŒ๊ท€)๋ผ axis B(monomorphic struct)์˜ element-kind ์ฆ๋ช…ยทbox/unbox +๊ฒฝ๊ณ„ ๊ทœ์œจ ๊ธฐ๋ฐ˜์œผ๋กœ ์œ ์ง€. verdict=`.verdicts/unshadow-unboxed-array/` ยท +์žฌํ˜„=`tool/unshadow_unboxed_array_bench.hexa`. diff --git a/domains/UNSHADOW.md b/domains/UNSHADOW.md index 5bd8fd9a7..ad6b57f34 100644 --- a/domains/UNSHADOW.md +++ b/domains/UNSHADOW.md @@ -34,5 +34,5 @@ > ๋ฐฑ๋กœ๊ทธ ์žฌ๊ฐœ โ€” E(๐Ÿ”ด) ์˜ ๊ทผ๋ณธ ์›์ธ "typed ํ‘œํ˜„ ๋ถ€์žฌ" ๋ฅผ ๋‹ค์Œ frontier ๋ ˆ๋ฒ„๋กœ ๋“ฑ๋ก. ๋‘˜ ๋‹ค `.c=0` LTO ์กธ์—…๊ณผ ์ง๊ต(ยงc-class ๊ฐ€ ์ž…์ฆ: codegen ์ฆ๋ช…์€ runtime.o ๋ฒฝ๊ณผ ๋…๋ฆฝ). ์„ ํ›„ = A โ†’ B โ†’ (B ์™„๋ฃŒ ์‹œ) E ์žฌ์˜คํ”ˆ. ์„ค๊ณ„ยท์ฐฉ์ง€์ ยท๊ฒŒ์ดํŠธ ์ƒ์„ธ = `UNSHADOW.typed-repr.md`. -- [ ] ๐ŸŸข unboxed-primitive array (`[i64]`/`[f64]` โ†’ native `int64_t[]`/`double[]`, boxed HexaVal[] ์•„๋‹˜) โ€” ยงparity-attest(7.9ร—~1263ร—) ์˜ array ์ถ• ยท ยงhexaval-unbox(scalar 0.98ร— closed) ์˜ array ํ™•์žฅ ยท ยงc-class ๊ฐ€ ๋‚จ๊ธด ๋ฏธ์ธก์ • lever(boxed-on-read + tag-guard ์ž”์กด, bench L577-579)์˜ ์ง๊ณ„. ์ฐฉ์ง€=element-kind ์ถ”๋ก (check) + ArrayLit/Index emit(codegen `:7594`/`:7661`) typed-arm + box/unbox ํ—ฌํผ(runtime.h public). ๋ฐœํ™”=์›์†Œํƒ€์ž… ์ •์  i64/f64 ์ฆ๋ช… ์‹œ๋งŒ, else ๊ธฐ์กด BOXED ๋ฌด๋ณ€๊ฒฝ. ๊ฒŒ์ดํŠธ=byte-diff IDENTICAL(typed ON/OFFยท๋™์ ๊ฒฝ๊ณ„ box ์ •ํ™•) + parity ฮ” ์ธก์ •(array hot-loop). RFC=domains/UNSHADOW.typed-repr.md +- [x] ๐ŸŸข unboxed-primitive array โ€” **correctness WIN ยท perf ๐Ÿ”ด CLOSED-NEGATIVE (์ถ• ruled-out: ๊ฐญ์€ tag-guard ์•„๋‹ˆ๋ผ STORAGE).** codegen ์— element-kind ์ถ”๋ก  ์ฐฉ์ง€(self/codegen.hexa): ๋ถˆ๋ณ€ `let xs=[int-litโ€ฆ]`(์ „๋ถ€ IntLit) โ†’ `_known_intarr_set` ๋“ฑ๋ก, in-range fact ๊ฐ€ ๋ฎ๋Š” `xs[i]` read ๊ฐ€ (a)receiver TAG_ARRAY (b)elem TAG_INT ๋‘˜ ๋‹ค ์ •์ ์ฆ๋ช… โ†’ ยงc-class array-tag guard(`HX_IS_ARRAY?`) ์‚ญ์ œ + `_is_known_int(xs[i])=true`(sum/dot ๊ฐ€ raw `.i` ์ถ”์ถœ). ๋ฐœํ™”=๋ถˆ๋ณ€-let+live in-range fact ๋‘˜ ๋‹ค์ผ ๋•Œ๋งŒ, ์žฌ๋Œ€์ž…/re-let ์ฆ‰์‹œ void, else ๊ธฐ์กด BOXED ๋ฌด๋ณ€๊ฒฝ. g5 byte-diff IDENTICAL 4-arm(refยทa_boxedยทb_unboxยทc_native, md5 `35470124`) + ๋™์ ๊ฒฝ๊ณ„ box ์ •ํ™•(typed arrayโ†’polymorphic site, md5 `9efbbf5d` ์–‘์ชฝ ๋™์ผ). **perf**: b_unbox 1.12s โ‰ˆ a_boxed 1.12s = ~0% ฮ” โ€” tag-guard ๋Š” loop-invariant ๋ผ clang -O2 ๊ฐ€ ์ด๋ฏธ hoist(์‚ญ์ œํ•ด๋„ wall ๋ฌด๋ณ€). ์ง„์งœ ๋ฒฝ = boxed ์ €์žฅ(sizeof HexaVal=16 โ†’ items[] 16B-stride, SIMD-gather ๋ถˆ๊ฐ€ 5 vec-op); native int64_t[](c_native, 8B contiguous, 23 vec-op) ๋งŒ ๊ฐญ 100% close(0.08sโ‰ˆref). **๋ˆ„๋ฝ ์ธํ”„๋ผ = native HexaArrI64/F64 ์ €์žฅ ํ‘œํ˜„(runtime ๋ณ€๊ฒฝ, B9 ๋ฒฝ ๋ฐ–ยทcodegen-only ๋ฒ”์œ„ ๋ฐ–)** = ๊ฐญ์ด ์‚ฌ๋Š” ๊ณณ. mini macOS arm64 best-of-9. verdict=`.verdicts/unshadow-unboxed-array/` ยท ์žฌํ˜„=`tool/unshadow_unboxed_array_bench.hexa`. RFC=domains/UNSHADOW.typed-repr.md - [ ] ๐Ÿ”ต typed monomorphic struct layout (flat C-struct typedef + offset field access, hash-map ์•„๋‹˜) โ€” E(AoSโ†”SoA) ์žฌ์˜คํ”ˆ ์„ ๊ฒฐ ยท ํ˜„ struct=`hexa_struct_pack_map` ํ•ด์‹œ๋งต(`:8043`)ยทfield=`hexa_map_get_ic` strcmp/IC(`:5394`)ยทvalstruct ๋Š” 12-์Šฌ๋กฏ carrier ์ „์šฉ(์ผ๋ฐ˜ํ™” ๋ถˆ๊ฐ€, `:7995`). monomorphic ์ฆ๋ช…(๋‹ซํžŒ ํ•„๋“œ์ง‘ํ•ฉยท์ •์  ์ ‘๊ทผ) ์‹œ per-type flat-struct emit + `obj.vs->f_k` offset ์ ‘๊ทผ. ์ฐฉ์ง€=struct shape ์ฆ๋ช…(check) + gen2_struct_decl(`:7982`) typedef arm + field access(`:5373`) offset arm. ๋‹คํ˜•/๋™์ -ํ‚ค struct ๋Š” hash-map ์œ ์ง€. ๊ฒŒ์ดํŠธ=byte-diff IDENTICAL + hash-lookupโ†’offset ฮ”(instr+wall). RFC=domains/UNSHADOW.typed-repr.md diff --git a/self/codegen.hexa b/self/codegen.hexa index fcc4ce215..858fad06b 100644 --- a/self/codegen.hexa +++ b/self/codegen.hexa @@ -2893,6 +2893,16 @@ fn _gen2_stmt_inner(node, depth) { if _is_float_init_expr(node.left) { _known_float_add(node.name) } + // UNSHADOW item A: register immutable let bound to a monomorphic + // i64 array literal (`[1,2,3,โ€ฆ]`) as a known-int-array, so an + // in-range read of it inside a proven loop can drop the array-tag + // guard and read the raw `.i` element (boxed-storage unbox). + if _is_int_literal_array(node.left) { + _known_intarr_add(node.name) + } else { + // A same-name re-let to a non-int-array RHS voids the proof. + _known_intarr_void(node.name) + } // PROBE r11 B3: also try comptime-fold for plain immutable `let`. // `let x = 2+3` now folds to literal 5 and registers in the // comptime-const table โ€” downstream references inline the literal @@ -2989,6 +2999,9 @@ fn _gen2_stmt_inner(node, depth) { // non-folded / string-folded names (mirrors the re-`let` D18 invalidate). if type_of(node.left) != "string" && node.left.kind == "Ident" { _invalidate_comptime_const(node.left.name) + // UNSHADOW item A: reassigning the binding may swap in a non-int + // array (or a non-array) โ€” void the known-int-array proof. + _known_intarr_void(node.left.name) } return pad + gen2_expr(node.left) + " = " + gen2_expr(node.right) + ";\n" } @@ -7658,6 +7671,29 @@ fn gen2_expr(node) { let _ir_ctr = _inrange_counter_for(node.left.name, idx.name) if _ir_ctr != "" { let _ir_av = gen2_expr(node.left) + // โ”€โ”€ UNSHADOW item A: unboxed-primitive array read โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // If `arr` is ALSO a proven known-int-array (immutable let = + // monomorphic i64 array literal), the receiver is statically + // TAG_ARRAY *and* the element is statically TAG_INT. Both the + // ยงc-class array-tag guard (`HX_IS_ARRAY(arr)?โ€ฆ`) and the + // per-element runtime work are now provably dead โ€” drop the + // ternary and emit a DIRECT element read. The element is + // produced as a HexaVal lvalue (`arr.arr_ptr->items[ctr]`, + // boxed-storage form), but `_is_known_int(arr[i])` now + // certifies it TAG_INT, so a surrounding sum/dot BinOp + // extracts the raw `.i` via HX_INT() with no tag dispatch โ€” + // the boxed-storage unbox (storage is still HexaVal* under + // the runtime.o wall; native int64_t[] storage is the modeled + // ceiling measured in the A/B proxy). DOUBLE-EVAL SAFE: _ir_av + // and _ir_ctr each appear once. FOLD-SHADOW SAFE: registration + // is immutable-let-only + voided on reassignment, and the + // read still requires a live in-range fact (no resize/realias + // in the loop). BOUNDARY-BOX: any use of `arr` NOT matching + // this proven read (passed to a fn / unproven index / untyped + // flow) falls through to the unchanged BOXED HexaArr path. + if _is_known_intarr_name(node.left.name) { + return "(" + _ir_av + ".arr_ptr->items[" + _ir_ctr + "])" + } return "(HX_IS_ARRAY(" + _ir_av + ") ? (" + _ir_av + ").arr_ptr->items[" + _ir_ctr + "] : hexa_index_get(" + _ir_av + ", hexa_int(" + _ir_ctr + ")))" } } @@ -9575,6 +9611,78 @@ let mut _known_float_set = [] // 64 buckets; each bucket is [name, ...] // CODEGEN proof, which is the axis unwall separated out). let mut _inrange_facts = [] // stack of [idx_var, arr_name, c_counter] +// โ”€โ”€ UNSHADOW item A: unboxed-primitive array โ€” element-kind tracker โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// Names of immutable `let`s whose initializer is a STATICALLY-PROVEN +// monomorphic-i64 array literal (`[1,2,3,โ€ฆ]`, every element an IntLit). For +// such a binding the codegen KNOWS each element is TAG_INT, so a read +// `arr[i]` covered by a live in-range fact (ยงc-class) is provably (a) on a +// TAG_ARRAY receiver and (b) a TAG_INT element. That lets us: +// โ€ข DROP the ยงc-class array-tag guard (`HX_IS_ARRAY(arr)?โ€ฆ`) โ€” array-ness is +// now statically proven for this binding, not just bounds. +// โ€ข Mark the read known-int so a surrounding sum/dot BinOp extracts the raw +// `.i` (`arr.arr_ptr->items[i].i`) with NO per-element box / HX_INT tag +// work โ€” the boxed-storage form of the unbox. +// This is a flat list (known-int-arrays are rare & local). Registration is +// gated on immutable LetStmt only; any reassignment of the name (AssignStmt +// LHS Ident) VOIDS it (mirrors _invalidate_comptime_const), and the read +// fast-path STILL also requires a live in-range fact (which independently +// proves no resize/reassign in the loop body). Box at the dynamic boundary is +// automatic: ANY use outside the proven `arr[i]` read (passing `arr` to a fn, +// `arr` flowing to an untyped site, an unproven index) falls through to the +// unchanged BOXED HexaArr path โ€” the elem stays a boxed HexaVal there. +let mut _known_intarr_set = [] + +fn _known_intarr_add(name) { + // de-dup + let mut i = 0 + while i < len(_known_intarr_set) { + if _known_intarr_set[i] == name { return } + i = i + 1 + } + _known_intarr_set = _known_intarr_set.push(name) +} + +fn _known_intarr_void(name) { + // Remove `name` if present (reassignment / re-let invalidates the proof). + let n = len(_known_intarr_set) + if n <= 0 { return } + let mut _rebuilt = [] + let mut i = 0 + while i < n { + if _known_intarr_set[i] != name { _rebuilt = _rebuilt.push(_known_intarr_set[i]) } + i = i + 1 + } + _known_intarr_set = _rebuilt +} + +fn _is_known_intarr_name(name) { + let mut i = 0 + while i < len(_known_intarr_set) { + if _known_intarr_set[i] == name { return true } + i = i + 1 + } + return false +} + +// Is `node` an array literal whose every element is an IntLit? (Exact, narrow +// monomorphic-i64 proof โ€” no Idents, no calls, no float, no nesting. The +// narrowest provable case the RFC names.) +// decreases node โ€” structural (single level; elements must be leaf IntLits) +fn _is_int_literal_array(node) { + if type_of(node) == "string" { return false } + if node.kind != "Array" { return false } + if type_of(node.items) != "array" { return false } + if len(node.items) == 0 { return false } + let mut i = 0 + while i < len(node.items) { + let el = node.items[i] + if type_of(el) == "string" { return false } + if el.kind != "IntLit" { return false } + i = i + 1 + } + return true +} + // โ”€โ”€ enum-variant access fix (inbox: enum-variant-access-miscodegen) โ”€โ”€ // `.` (e.g. RegionShape.K_BY_K) is an enum-variant // constant, NOT a runtime field access โ€” the gen2_enum_decl #define @@ -9982,6 +10090,22 @@ fn _is_known_int(node) { if type_of(node) == "string" { return false } if node.kind == "IntLit" { return true } if node.kind == "Ident" { return _is_known_int_name(node.name) } + // UNSHADOW item A: a read `arr[i]` of a proven known-int-array, where the + // index is covered by a live in-range fact, is statically TAG_INT โ€” its + // element kind is monomorphic i64 by the array-literal proof. Certifying it + // here lets a surrounding sum/dot BinOp extract the raw `.i` (HX_INT) with + // no per-element tag dispatch. Narrow: requires BOTH the known-int-array + // registration AND a live in-range fact (same guards as the Index emit + // fast-path), so it never fires on an unproven / boxed access. + if node.kind == "Index" { + let _l = node.left + let _r = node.right + if type_of(_l) != "string" && _l.kind == "Ident" && _is_known_intarr_name(_l.name) { + if type_of(_r) != "string" && _r.kind == "Ident" { + if _inrange_counter_for(_l.name, _r.name) != "" { return true } + } + } + } return false } diff --git a/tool/unshadow_unboxed_array_bench.hexa b/tool/unshadow_unboxed_array_bench.hexa new file mode 100644 index 000000000..adc502f31 --- /dev/null +++ b/tool/unshadow_unboxed_array_bench.hexa @@ -0,0 +1,241 @@ +// tool/unshadow_unboxed_array_bench.hexa +// UNSHADOW milestone "๐ŸŸข unboxed-primitive array" (axis A of the typed-repr +// RFC, domains/UNSHADOW.typed-repr.md). Extends the merged ยงc-class win: +// c-class elided the bounds check on `arr[i]` inside a proven +// `for i in 0..len(arr)` loop but the read STILL yielded a BOXED HexaVal and +// kept a single array-tag guard (bench L577-579 names dropping that guard as +// the unmeasured lever). This milestone closes it for a STATICALLY-PROVEN +// monomorphic-i64 array. +// +// CODEGEN CHANGE UNDER TEST (self/codegen.hexa): +// An immutable `let xs = [1,2,3,โ€ฆ]` (every element an IntLit) registers `xs` +// as a known-int-array. A read `xs[i]` covered by a live in-range fact then +// knows the receiver is TAG_ARRAY *and* the element is TAG_INT, so: +// โ€ข the ยงc-class array-tag guard (`HX_IS_ARRAY(xs)?โ€ฆ:checked`) is DROPPED +// (array-ness statically proven) โ†’ direct `xs.arr_ptr->items[i]`, +// โ€ข `_is_known_int(xs[i])` returns true so a surrounding sum/dot BinOp +// extracts the raw `.i` via HX_INT with NO per-element tag dispatch. +// Storage is still the runtime's HexaVal* (HexaArr) under the runtime.o wall +// (true native int64_t[] storage = HexaArrI64 is the modeled CEILING below); +// the win measured as LANDED is the boxed-storage unbox (guard + tag work +// dropped). +// +// ARMS (all link the SAME real runtime.o; only the element read differs): +// ref_c idiomatic plain `int64_t buf[]` sum (parity baseline, no HexaVal) +// a_boxed BEFORE = ยงc-class read: (HX_IS_ARRAY(a)?a.arr_ptr->items[i] +// :checked), then HX_INT() +// b_unbox AFTER = new codegen read for a known-int-array: +// a.arr_ptr->items[i] (no tag guard), then HX_INT() +// c_native CEILING = true native int64_t[] storage (what HexaArrI64 gives): +// int64_t* data; โ€ฆ data[i] (no HexaVal at all) +// +// INTEGRITY GATE (the hard one โ€” the dynamic boundary): a typed i64 array +// passed to a POLYMORPHIC site MUST still box correctly and produce identical +// output. We emit a boundary corpus: the SAME `[i64]` literal is (1) summed +// via the unboxed fast path AND (2) handed to a polymorphic `hexa_len` / +// element-print site that consumes boxed HexaVal. Both arms (boxed vs unbox) +// MUST byte-match the reference โ€” the unbox NEVER changes a value, only how +// the proven in-range read is emitted. A wrong unbox = tag/repr confusion = +// silent miscompile, so this gate is non-negotiable. +// +// MEASURE per arm: built? ยท program-output md5 (g5 byte-diff) ยท best-of-N wall ยท +// inner-loop `bl _hexa_index_get` count (the boxed-call drop) + asm shape. +// +// Argv: +// --rt dir holding runtime.h + runtime.c + runtime.o +// (default ~/.hx/packages/hexa/self) +// --work scratch dir (default ~/unshadow-uba) +// --runs timed runs per arm, best-of-N (default 5) + +fn parse_int_safe(s) { + let t = s.trim() + if len(t) == 0 { return 5 } + return to_int(t) +} + +fn arg_val(argv, key, dflt) { + let mut i = 0 + while i < argv.len() { + if argv[i] == key && i + 1 < argv.len() { return argv[i + 1] } + i = i + 1 + } + return dflt +} + +fn write_file(path, body) { + exec("cat > '" + path + "' <<'__UNSHADOW_EOF__'\n" + body + "\n__UNSHADOW_EOF__") +} + +fn build_arm(label, cc_cmd, out_bin) { + let r = exec(cc_cmd + " 2>&1") + let built = exec("[ -f '" + out_bin + "' ] && printf yes || printf no").trim() + let nerr = exec("printf '%s' \"" + r + "\" | grep -ciE 'error|undefined' 2>/dev/null").trim() + println(" [" + label + "] built=" + built + " err_lines=" + nerr) + return built +} + +fn md5_of_output(bin) { + return exec("'" + bin + "' 2>/dev/null | md5 2>/dev/null || '" + bin + "' 2>/dev/null | md5sum 2>/dev/null | cut -d' ' -f1").trim() +} + +fn best_wall(bin, runs) { + let mut best = "999" + let mut r = 0 + while r < runs { + let t = exec("{ /usr/bin/time -p '" + bin + "' >/dev/null ; } 2>&1 | awk '/^real/{print $2}'").trim() + if len(t) > 0 { + let cmp = exec("awk 'BEGIN{print (" + t + "<" + best + ")?1:0}'").trim() + if cmp == "1" { best = t } + } + r = r + 1 + } + return best +} + +fn bl_count(asm_path, sym) { + return exec("grep -c 'bl[[:space:]]*_" + sym + "' '" + asm_path + "' 2>/dev/null | head -1 || printf 0").trim() +} + +fn main() { + let argv = args() + let home = env("HOME") + let rt = arg_val(argv, "--rt", home + "/.hx/packages/hexa/self") + let work = arg_val(argv, "--work", home + "/unshadow-uba") + let runs = parse_int_safe(arg_val(argv, "--runs", "5")) + + println("=== UNSHADOW unboxed-primitive array bench (axis A) ===") + println("rt=" + rt + " work=" + work + " runs=" + to_string(runs)) + exec("rm -rf '" + work + "' && mkdir -p '" + work + "'") + + let inc = "-I '" + rt + "'" + let o2 = "clang -O2 " + inc + " " + + // โ”€โ”€ shared HexaVal-backed hot-loop body, parameterized by GET() โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + // Builds a 256-elem int array (the runtime HexaArr of boxed HexaVal*), then + // sums GET(arr,i) over N outer iters. This is exactly the C shape codegen + // emits for `let xs = [..]` + `for i in 0..len(xs) { acc += xs[i] }`. + write_file(work + "/body.h", + "static long run_corpus(void){\n" + + " HexaVal arr=hexa_array_new();\n" + + " for(int k=0;k<256;k++) arr=hexa_array_push(arr,hexa_int(k));\n" + + " long acc=0; long N=4000000L;\n" + + " for(long r=0;r\n#include \n" + + "static long run_ref(void){\n" + + " int64_t buf[256]; for(int k=0;k<256;k++) buf[k]=k;\n" + + " long acc=0; long N=4000000L;\n" + + " for(long r=0;r\n" + + "#define GET(a,i) (HX_IS_ARRAY(a) ? (a).arr_ptr->items[(i)] : hexa_index_get((a), hexa_int((i))))\n" + + "#include \"body.h\"\n") + + // b_unbox (AFTER = new codegen for a known-int-array) โ€” tag guard DROPPED, + // direct boxed-storage element read. The surrounding HX_INT() extracts `.i`. + write_file(work + "/b_unbox.c", + "#include \"runtime.h\"\n#include \n" + + "#define GET(a,i) ((a).arr_ptr->items[(i)])\n" + + "#include \"body.h\"\n") + + // c_native (CEILING) โ€” true native int64_t[] storage. Models the HexaArrI64 + // representation the RFC targets: `let xs:[i64]` lowers to a native buffer, + // the read is `data[i]` with no HexaVal. Same program output. + write_file(work + "/c_native.c", + "#include \"runtime.h\"\n#include \n" + + "static long run_native(void){\n" + + " HexaVal arr=hexa_array_new();\n" + + " for(int k=0;k<256;k++) arr=hexa_array_push(arr,hexa_int(k));\n" + + " int len=hexa_len(arr);\n" + + " int64_t* data=(int64_t*)malloc(sizeof(int64_t)*len);\n" + + " for(int k=0;kitems[k].i; /* unbox-on-build */\n" + + " long acc=0; long N=4000000L;\n" + + " for(long r=0;r/dev/null") + exec(o2 + "-S '" + work + "/b_unbox.c' -o '" + work + "/b.s' 2>/dev/null") + exec(o2 + "-S '" + work + "/c_native.c' -o '" + work + "/c.s' 2>/dev/null") + println("\n asm bl _hexa_index_get (total): a_boxed=" + bl_count(work + "/a.s", "hexa_index_get") + + " b_unbox=" + bl_count(work + "/b.s", "hexa_index_get") + + " c_native=" + bl_count(work + "/c.s", "hexa_index_get")) + + // โ”€โ”€ INTEGRITY GATE: the dynamic boundary โ€” typed array used polymorphically + // The SAME [i64] literal is BOTH summed via the unboxed fast path AND handed + // to a polymorphic consumer (hexa_len + a boxed element fetch via the + // CHECKED hexa_index_get, the path codegen emits when the access is NOT the + // proven loop read). Both arms MUST box correctly there and byte-match. + println("\n--- INTEGRITY: typed i64 array at a polymorphic boundary ---") + write_file(work + "/bnd_body.h", + "static long run_bnd(void){\n" + + " HexaVal arr=hexa_array_new();\n" + + " for(int k=0;k<16;k++) arr=hexa_array_push(arr,hexa_int(k*k));\n" + + " /* (1) unboxed fast-path sum (the proven loop read) */\n" + + " long s=0; for(int64_t i=0;i<(int64_t)hexa_len(arr);i++){ s+=HX_INT(GET(arr,i)); }\n" + + " /* (2) DYNAMIC BOUNDARY: hand the same array to a polymorphic site โ€” the\n" + + " length + a checked (boxed) element fetch, exactly the BOXED path\n" + + " codegen emits for a non-proven access. MUST box correctly. */\n" + + " long plen=(long)hexa_len(arr);\n" + + " long pelem=HX_INT(hexa_index_get(arr, hexa_int(7)));\n" + + " return s + plen*1000 + pelem; }\n" + + "int main(void){ printf(\"%ld\\n\", run_bnd()); return 0; }\n") + // boundary arm A (boxed read on the fast path) + write_file(work + "/a_bnd.c", + "#include \"runtime.h\"\n#include \n" + + "#define GET(a,i) (HX_IS_ARRAY(a) ? (a).arr_ptr->items[(i)] : hexa_index_get((a), hexa_int((i))))\n" + + "#include \"bnd_body.h\"\n") + // boundary arm B (unboxed read on the fast path; boundary use stays checked) + write_file(work + "/b_bnd.c", + "#include \"runtime.h\"\n#include \n" + + "#define GET(a,i) ((a).arr_ptr->items[(i)])\n" + + "#include \"bnd_body.h\"\n") + build_arm("a_bnd", o2 + "'" + work + "/a_bnd.c' '" + rt + "/runtime.o' -o '" + work + "/abnd_bin' -lpthread", work + "/abnd_bin") + build_arm("b_bnd", o2 + "'" + work + "/b_bnd.c' '" + rt + "/runtime.o' -o '" + work + "/bbnd_bin' -lpthread", work + "/bbnd_bin") + println(" boundary g5 (MUST be identical โ€” unbox never changes a value):") + println(" a_bnd (boxed) : " + md5_of_output(work + "/abnd_bin")) + println(" b_bnd (unbox) : " + md5_of_output(work + "/bbnd_bin")) + println(" โ†’ both MUST match: the typed array boxes correctly when it reaches") + println(" the polymorphic boundary (hexa_len + checked element fetch).") + + println("\n=== done ===") +}