splitting functions out of main to check if this still works #52
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/052-splitting-functions-out-of-main-to-check-if-th"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
splitting functions out of main to check if this still works
Along the way:
Touches 61 files:
current-state.md,docs/impl/memory-model/arenas.md,docs/syntax.md,lowering.md,plan.md,src/rust/crates/ck_codegen/src/call.rsand 55 more.Happy with this. I ran the suite against the branch and it is green,
src/rust/crates/ck_driver/tests/fib_runs_run.rsincluded.@ -0,0 +90,4 @@/// `ckc --container <shared> --work <name> run <app> --stdlib <repo>/tests/corpus/stdlib`,/// returning the process exit code.fn run_ck(name: &str, app: &Path) -> i32 {run_ckreads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?Split. The decision in
run_ckis a pure function now, with its own tests insrc/rust/crates/ck_driver/tests/fib_runs_run.rs.The loop-lowering half of plan.md row 14a item (1), first checkpoint: `while<N>` desugars in the pass (ck_lower::lower::loop_lower) to LC2 Floor-core blocks with a back-edge — the byte-copy-loop (LC4) shape generalized: entry `Jump head(0)` materializing the counter param, the condition re-evaluated per iteration in the head (its own lowering may split blocks), the guard `i < N`, the body in its own LC10 scope (defers run per iteration) ending in the back-edge `Jump head(i+1)`. ZERO new codegen — the mechanical CFG walker was untouched. THE BOUND IS ENFORCED AT RUNTIME (the bounded-everything family's uniform rule: rec<N> → StackOverflow, arena → OutOfMemory::Exhausted, while<N> → IterationBound::Exceeded): beginning iteration N+1 while the condition holds takes the guard's exceeded path — an injected ordinary Call on the NEW live-stdlib unit Bounds.ck (`data IterationBound composes Error { Exceeded }` + `fn exceeded()` = a REAL Panic::raise, the Arena.ck OOM shape; nothing invented in Rust) followed by the sanctioned post-call raise check (the arena_alloc_local precedent) and a Trap-terminated dead continuation. Driver: bounds_wiring locates Bounds::exceeded by name (the arena_wiring shape), LoopFacts (the 12th positional fact table) arms it on all entry paths; discovery rides the existing cross-module fixpoint (the injected Ref is an ordinary callee). UNARMED (no live Bounds.ck), a loop errors honestly — an unenforced bound would let `while<8> true {}` spin, the exact CPU burn decisions-scheduler-model.md bans. Arena sizing, same slice (plan §4's rule, owner 2026-07-03): the pass records the product of the enclosing loops' declared bounds on every arena-allocation node (floor_ir::Body's new alloc-factor side-table; recorded at the ONE chokepoint arena_alloc_local), and frame_alloc_footprint prices the site × the factor — body sites ×N, CONDITION sites ×(N+1) (it runs once more than the body), nested loops multiply, FrameAlloc stays ×1 by construction, an unbounded component stays honestly unbounded (×N of unbounded is unbounded). Exact-byte unit tests in arena/tests.rs (the FS5.1 style: List<Int,4> in while<8> = 40×8 = 320 B; nested ×12; factor-1 unrecorded = byte-identical pre-MA1.2 walk). Corpus stones + gates: tests/corpus/while_bound — memory-held state (no `let mut`; Memory::store/load over an os_acquire'd word) steps 0 → 42 in six iterations inside the bound of 8, exit 42, freestanding (9 448 B observed, empty NEEDED, nm -u empty, no libc/ck_rt) — gated by while_bound_run.rs; tests/corpus/while_bound_exceeded — the arena_oom twin: while<4> never falsifies → exactly 4 iterations → the raise → stderr names IterationBound::Exceeded + the hint, exit 101, no stdout, NO SPIN — gated by while_bound_exceeded_run.rs. Both in RUNTIME_TESTED. Pass-level goldens: lowering_loops.rs (armed CFG shape: back-edge to an earlier block, the i<3 guard, the injected call, the Trap, the tail 42; unarmed = honest LowerError::LoopUnsupported). Docs, same commit: lowering.md LC9 row annotated (loop half pulled forward, while landed MA1.2a); plan.md row 14a + the §4 loop-sizing bullet (multiplier LANDED; slot-reuse escape analysis stays deferred post-MA4); current-state.md §3 (while<N> RUNS); docs/syntax.md §Loops (the runtime enforcement sentence); arenas.md (the landed ×N pricing with exact numbers). LowerError::LoopUnsupported narrowed to `for`/range + unarmed-while; R3/R4 held by splitting (guard/back-edge helpers, alloc_factor accessor beside its builder, doc trims — floor_ir.rs 550/550 exactly at cap). Gates (from src/rust): cargo fmt --check clean; cargo clippy --all-targets -- -D warnings 0 warnings; cargo test --workspace exit 0 (architecture R1–R4, conformance, all container e2e incl. the two new gates); cargo build --workspace clean. 0 warnings, 0 errors. helloWorld unchanged.The second loop-lowering checkpoint: `for pattern in iterable { body }` desugars in the pass (loop_lower) to the same LC2 back-edged CFG as while<N>, minus the runtime guard — a `for` iterable is statically bounded BY CONSTRUCTION, so no exceeded path and no Trap block exist (pinned by golden). Two iterable shapes lower: - a LITERAL-ENDPOINT integer range `a..b` / `a..=b` — the endpoints fold at compile time (read_int on the manifest literals), giving the static trip count max(0, b−a(+1)) that is also the body's arena-sizing factor; the head compares Lt (exclusive) or Le (inclusive); the binder binds the counter each iteration. - a LIST LITERAL of one-word scalar elements — the buffer builds ONCE before the loop (its allocation prices ×1), then each iteration loads the element at buffer + i × stride into the binder; the literal's length is the static count and sizing factor. THE HONEST SCOPE (the list-repr finding, plan §4's new residue bullet): a list VALUE is one buffer-pointer word with NO runtime length (LC4's repr; the pass keeps no per-local list table), so `for` over a list-typed local/param CANNOT lower without guessing a length — the new real spanned LowerError::ForIterable names it and its future slice (the list-length materialization slice). Same error covers runtime-endpoint ranges (no static trip count for bounded-everything to price), wide (String/record) elements, and non-binder/wildcard patterns. Never a guessed length. Corpus stone + gate: tests/corpus/for_loop — `for i in 0..5` sums 10 (exclusive end), `for x in [10, 11, 11]` adds 32 through strided loads; memory-held accumulation (no `let mut`), exit 42, freestanding — gated by for_loop_run.rs, registered in RUNTIME_TESTED. Pass goldens (lowering_loops.rs, now 4): the for-range CFG shape (back-edge, `i < 5` head, the binder Let each iteration, NO guard/Trap — and it lowers UNARMED, needing no Bounds fn) + the dynamic-iterable honest error. Structural R4 splits: the shared emit_loop_head (`for`'s two shapes) + emit_loop_back_edge (shared with while). Docs, same commit: plan.md row 14a item (1) marked COMPLETE + the new §4 dynamic-length-list residue bullet (named future slices); lowering.md LC9 row annotated (for landed; the higher-order list-ops half remains LC9's, behind LC8); current-state.md §3 (`for` RUNS, the honest iterable scope). Gates (from src/rust): cargo fmt --check clean; cargo clippy --all-targets -- -D warnings 0 warnings; cargo test --workspace exit 0 (architecture R1-R4 incl. the corpus-coverage check, conformance, all container e2e incl. for_loop_run); cargo build --workspace clean. 0 warnings, 0 errors. Live: for_loop exit 42; helloWorld unchanged.Plan row 14a item (2): "plausibly a gate, not a feature" — and so it was. FS5.2 built rec<N>'s parse / cycle-check (E0901/E0902) / stack-frame sizing; this checkpoint proves a recursive fn EXECUTES: the S1 user-fn discovery is cycle-safe on the self-call (a recursive callee is discovered once, never looping the fixpoint), and the compiled double self-call is an ordinary Cranelift call to the fn's own symbol. NOTHING in the compiler changed — the checkpoint is the stone + gate + doc reconciliation. New corpus stone tests/corpus/fib_runs (the lean freestanding `-> Int` shape, NOT the Log-effect fibonacci app): `fn rec<10> fib(n: Int) -> Int` — the classic two-branch match + double self-call — with main exiting fib(10) = 55. Observed: exit 55, 5 000-byte freestanding ELF, empty NEEDED, nm -u empty, no libc/ck_rt symbols. Gate fib_runs_run.rs; registered in RUNTIME_TESTED. Honest scope (declared, not overclaimed — the stone's comments say so): the rec<10> depth stays COMPILE-TIME machinery only (sizing + the E0901 family); runtime depth-overflow enforcement is FS9's deferred StackOverflow raise — the while<N>-enforces-vs-rec<N>-doesn't asymmetry declared in the FS5.2 row / arenas.md. Docs, same commit: plan.md row 14a item (2) ✅ LANDED; stdlib.md FS5.2 row annotated (execution now gated; the "still unbuilt" note retired); current-state.md §3 (recursion EXECUTES). Gates (from src/rust): cargo fmt --check clean; cargo clippy --all-targets -- -D warnings 0 warnings; cargo test --workspace exit 0 (architecture, conformance, container e2e incl. fib_runs_run); cargo build clean. 0 warnings, 0 errors.Plan row 14a item (3), the FS4 pattern: the turbofish→width map learns the byte. Spelled U8 (the U16 precedent — capitalized primitives are the language's spelling; the design docs' lowercase `u8` was shorthand): Primitive::U8 joins the enum's documented-provisional fixed-width set + "U8" in primitive_from_name + scalar_width 1 + render — NO new inference, no widening, plan §5 held (the closed-set extension U16 already modeled). The seam end to end: driver scalar_kind_of maps Ty U8 → the new ScalarKind::Byte (→ ValueRepr::Byte, the LC4 repr); codegen's machine-op arms grow the width-aware pair — load::<U8> emits `uload8` (reads 8 bits, ZERO-EXTENDS into the i64 word, so the byte composes with scalar arithmetic — never a smeared 8-byte read), store::<U8> emits `istore8` (truncates the word to its low byte, writes exactly one byte, neighbors untouched); size_of::<U8> = 1 and handle_at::<U8>'s raw byte-offset add fall out of the existing repr law. Two honest findings, both resolved within the locks: - a bare Int literal in U8 position is E0702 (no numeric widening — manifest typing working as designed); the universal pin is the spelling: `42::<U8>` (synthesis-only, the turbofish IS the pin). - the pass's literal reader (read_int) choked on a pinned literal — the HIR Lit span covers `42::<U8>` — fixed: the VALUE is everything before the `::` (the pin was consumed at check time). Corpus stone tests/corpus/u8_roundtrip — each width PROVEN, not assumed (exit codes truncate to 8 bits, so exit arithmetic alone cannot see a word-smear; every check is a full-width `==` in a match): pre-fill a word 511 (FF 01 …), istore8 42 over byte 0 — `word == 298` proves the ONE-BYTE store (a smeared store would read 42); `load::<U8>(0) == 42` proves the byte load (a smeared load would read 298); the neighbor byte survives (`== 1`); 40 + size_of::<U8>() twice = exit 42 freestanding. Gate u8_roundtrip_run.rs; registered in RUNTIME_TESTED. Docs, same commit: stdlib.md §2 machine-instruction-leaf bullet grew the width tier (Int/I64 word; U8 byte with the uload8/istore8 semantics); plan.md row 14a item (3) ✅ LANDED; current-state.md §3; Memory.ckh's prose spelling fixed `::<u8>` → `::<U8>`. R3 held by moving primitive_name into check_util beside the other render helpers. Gates (from src/rust): cargo fmt --check clean; cargo clippy --all-targets -- -D warnings 0 warnings; cargo test --workspace exit 0 (architecture, conformance, container e2e incl. u8_roundtrip_run); cargo build clean. 0 warnings, 0 errors. helloWorld unchanged.The first half of plan row 14a item (4): the byte-operations unit the row's primitives were built FOR, promoted into the live stdlib. Bytes::copy(src, dst, at, len) is 100% CkLang over the row's own work — a while<65536> (MA1.2's ENFORCED bound; the ceiling is one region's capacity, the bounded-everything choice — a longer copy RAISES IterationBound::Exceeded, never spins; documented in .ck and .ckh) over a memory-held cursor (no `let mut` exists; the counter lives in a scratch region acquired/released per call — the honest two-syscall bootstrap cost, also documented), one Memory::{load,store}::<U8> per pass (MA1.4's width; runtime-endpoint ranges don't lower — the MA1.2b scope — so while<N> is the loop). Plus the TWO String ABI-word selection leaves the Meta.ck rewrite needs — `of_str(s) -> Region` (word 0, the content base) and `str_len(s) -> Int` (word 1) — registered `= intrinsic` in the closed floor registry (the `size_of` tier: pure word selection, NO instruction at all — codegen's new str_abi_word arm just returns one of the String argument's two ABI words). Declared explicitly as floor-ISA tier members, never a hidden pass form. No speculative API: copy + the two leaves are exactly what MA1.5b consumes. Corpus stone tests/corpus/bytes_roundtrip → exit 42 freestanding: region- to-region copy with placement AND the stop-at-len proof (the third source byte must NOT arrive), then a runtime-length String-to-region copy through of_str/str_len ('c'=99/'k'=107 verified by full-width ==) — exactly the append_str rewrite shape. Gate bytes_roundtrip_run.rs; RUNTIME_TESTED. Housekeeping (the flagged R3 chronic): corpus.rs properly SPLIT — the RUNTIME_TESTED registry + its runtime_coverage_is_exhaustive gate moved to the new tests/runtime_registry.rs (534 + 324 lines; the registry grows with every stone and is a separate responsibility from fixture reconciliation); no more comment-whittling. R4 held by extracting codegen's str_abi_word. Docs: stdlib.md §8 gained the MA1.5 Bytes row. (The floor-ISA pinned table, the Meta.ck rewrite, and the row-14a completion land in MA1.5b.) Gates (from src/rust): cargo fmt --check clean; cargo clippy --all-targets -- -D warnings 0 warnings; cargo test --workspace exit 0 (architecture, conformance, container e2e incl. bytes_roundtrip_run + the relocated coverage gate); cargo build clean. 0 warnings, 0 errors.