feat(plan_values A4): float width conversion via value turbofish #58
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/058-feat-plan-values-a4-float-width-conversion-via"
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?
feat(plan_values A4): float width conversion via value turbofish
Along the way:
Touches 49 files:
docs/current-state.md,plan_values.md,src/rust/crates/ck_codegen/src/binding.rs,src/rust/crates/ck_codegen/src/strings.rs,src/rust/crates/ck_codegen/src/user_fn.rs,src/rust/crates/ck_codegen/src/value.rsand 43 more.It said what I assumed. Reworded in
src/rust/crates/ck_driver/tests/float_narrow_run.rsso it names the input that broke it, which is what the bug report will need.Split. The decision in
find_near_exeis a pure function now, with its own tests insrc/rust/crates/ck_driver/tests/float_fn_cross_run.rs.Read it too. No objection to
src/rust/crates/ck_driver/tests/float_narrow_run.rs.Approving. I would have split
src/rust/crates/ck_driver/tests/float_narrow_run.rsdifferently, but not enough to hold this up.@ -0,0 +204,4 @@}/// Search the test executable's ancestor directories for a sibling file `name`.fn find_near_exe(name: &str) -> Option<PathBuf> {find_near_exereads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?@ -0,0 +95,4 @@}// (5) low KB.let size = std::fs::metadata(&binary).expect("binary metadata").len();The expect message on
sizeis the only thing a user sees if this fires. Does it say what they should do, or only what we assumed?`expr::<T>` on a float value whose width differs from T is a width conversion (`let n = a::<f32>` narrows f64 -> f32; `n::<f64>` widens). Rides the EXISTING value-turbofish surface — no `as` cast, no invented Convert trait. Zero new floor leaves; ISA row-set stays 23. - ck_types: `synth_local_ref` — a float local pinned to a float width synthesizes as the TARGET width (the conversion result), so `n == 12.0::<f32>` ties same-width. Type-directed on `Primitive::is_float`, never a `match type_name`. - ck_lower: new scalar-tier `ExprKind::FloatConvert { from, to, value }`; the pass wraps the ref (source width = local's checked width, target = the reference's checked width); a same-width pin folds to identity. Decomposed into `lower/convert_lower.rs`. - ck_codegen: `apply_float_convert` emits a single Cranelift `fdemote` (narrow) / `fpromote` (widen) at the primitive-op tier — no leaf. Float scalar concern extracted to `value::emit_float_scalar` to hold the fn cap (no cap raise). Gate: `tests/corpus/float_convert` narrows 12.0->f32, widens back (bit-exact f64->f32->f64 round-trip), narrows 0.5->f32, each proven against a pinned exactly-representable literal -> exit 42 freestanding. Verified to discriminate (12.0 -> 12.5 yields exit 1). Honest deferrals (recorded, not faked): f8 has no IEEE-8 machine type; f16's x86 conversion needs soft-float libcalls (__truncsfhf2/__extendhfsf2) a freestanding binary cannot link — both are an honest backend error. f32/f64 are the native SSE widths the gate proves. Annotated `let x: T = e` is a separate pre-existing codegen gap (fails for Int too) — the corpus uses manifest inference. Gates: fmt clean, clippy 0, cargo test --workspace exit 0, container exits 42 (discriminates), ISA 23, caps unchanged.Two real defects, found when A4b's agent read the CLIF and saw A4's conversions folding to identity: 1. Primitive::from_name was uppercase-only ("F32"), so the canonical LOWERCASE turbofish `::<f32>` (docs/types.md; the owner's `i::<f32>` surface) failed to resolve and silently fell back to F64. `from_name`/`name` now use the canonical lowercase fixed-width spellings (f8/f16/f32/f64, i8..u64); Int/Bool/ Char stay capitalized; the round-trip test is updated. 2. An unresolvable value-turbofish pin silently defaulted to F64. It is now a hard error (E0739). Generic ::<T>, nominal ::<Region>, and non-float scalars still resolve — only an unresolvable scalar pin errors. Consequence fixed: A4's float_convert gate had been passing TRIVIALLY (lowercase ::<f32> folded to identity, everything stayed f64, 12.0==12.0). It is rewritten as a precision-loss gate: a 0.1 f64->f32->f64 round-trip must break `== 0.1` (a real fdemote/fpromote loses precision), plus an exact 0.5 direction that must hold — discriminating in both directions, not trivially satisfiable. Migrated every uppercase SCALAR turbofish pin to lowercase (u8_roundtrip, float_narrow, primes*, bytes_roundtrip, stdlib Bytes/Memory/Meta, the Size=u16 fixtures, numeric_literal_turbofish); nominal ::<Region>/::<Int> and generic ::<T> untouched. Proof lowercase now really converts: `let a=0.1; let n=a::<f32>; let b=n::<f64>; b==0.1` -> exit 42 (equality broken by the real round-trip; was 99 under the defect). Stale ::<F32> and bogus ::<f99> both -> E0739. Gates: fmt clean, clippy 0, cargo test --workspace exit 0, float_convert/ float_narrow/u8_roundtrip all exit 42, ISA 23, caps unchanged. No codegen leaf touched — pure resolution + gate rewrite.A float now passes as a user-fn param and comes back as a return, byte-exact — the single-word crossing (a float is one word: f64=8B, f8/f16/f32 share the lowered f32 4B word per A4b), distinct from the multi-word POD case (A6). Two one-arm edits, no new leaf, ISA stays 23, no `match type_name`. - ck_driver/user_fns.rs: `is_scalar_return` — the restriction behind the error "a called user function returns a value outside this slice's frame (Unit / Int / floor handle / union only…)" — gains a guarded `Ty::Primitive(p) if p.is_float()` arm, so a float return joins the in-frame set. - ck_codegen/user_fn.rs: `abi_types` gains `Ty::Primitive(p) if p.is_float()` -> `repr_types(ValueRepr::Float(FloatWidth::from_primitive(p)))`, reusing the one repr law. user_fn_signature / bind_params / ret_types / emit_user_call_value all route through abi_types, so param passing, block-param binding, and the single-word return fall out uniformly. Type-directed on float width. Param arithmetic (`x + x` on an f64 param) folds to native fadd with no extra wiring — the fold gates on the operand's declared type, which a param carries. Gate: tests/corpus/float_fn_cross proves (a) id(x: f64) round-trips a float, (b) dbl { x + x } returns 2*x, (c) an f32 param/return crosses (lowercase 1.25::<f32>) -> exit 42 freestanding. Verified to discriminate: input a=3.0 breaks id(a)==2.5 -> exit 1; dropping the + in dbl -> exit 2. Gates: fmt clean, clippy 0, cargo test --workspace exit 0, container exits 42 (discriminates), ISA 23, caps unchanged.