diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index 30b18af..c91418a 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -13350,3 +13350,139 @@ milestone: - Prose round-trip for Float literals — iter 5. - DESIGN.md §"Float semantics" subsection (A5 determinism contract) and the line-2033 "supported primitive types" list update — iter 5. + +## 2026-05-10 — Iteration Floats.4: codegen + E2E + +Lowered every Float-related typecheck primitive that iter 3 installed +into LLVM IR. The end-to-end fixture `examples/floats.ail.json` builds +via `ail build`, runs, and produces the expected stdout +(`4\n42\n-1.5\n`) — this is the milestone-acceptance gate, and it +passed first try after the seven sub-tasks landed. + +`Float` is now a registered primitive in `synth.rs` (`llvm_type` → +`double`, `type_descriptor` → `Fl` to avoid the single-letter `F` ADT +prefix collision). Float literals lower as LLVM hex-float `double` +SSA constants (`format!("0x{:016X}", bits)` — uppercase per LLVM +convention). Both compile-completeness `unimplemented!("Floats +milestone iter 4: codegen")` arms left by iter 1 in `lib.rs:918` / +`:1215` are gone. + +Arithmetic and comparison ops dispatch on the resolved arg type. The +old monomorphic-Int `synth::builtin_binop` was replaced by a +3-tuple-returning `builtin_binop_typed(name, &Type) -> Option<(instr, +operand_ll_ty, result_ll_ty)>`. Returning the result type +explicitly eliminates the dual-meaning trap the original 2-tuple +shape carried (caller had to know whether arm was arithmetic +(operand-type == result-type) or comparison (result-type always +`i1`)). The `lower_app` dispatch site was simplified accordingly — +no more i1-result override at the caller. Float arithmetic emits +`fadd/fsub/fmul/fdiv double`; Float comparison emits `fcmp +olt/ole/ogt/oge/oeq double`. **`!=` Float emits `fcmp UNE double`, +NOT `fcmp one`** — `one` is "ordered and not equal" and would +return false for `nan != nan`, violating IEEE-`!=` and user-fixed +constraint #2. + +Five new fn-builtin lowering arms in `lower_app`: + +- `neg` polymorphic — Int arm `sub i64 0, %x`; Float arm `fneg + double %x` (LLVM 8+, correct for `-0.0`). The `fsub double 0.0, + %x` desugar would be wrong: IEEE `0.0 - 0.0 == +0.0`, so + `neg(+0.0)` would erroneously produce `+0.0` instead of `-0.0`. +- `int_to_float` → `sitofp i64 %x to double`. +- `float_to_int_truncate` → `call i64 @llvm.fptosi.sat.i64.f64(double + %x)` (LLVM 12+ saturating intrinsic; clang 22 always available). + NaN → 0, +Inf → i64::MAX, -Inf → i64::MIN, finite-out-of-range + saturates, finite-in-range truncates toward zero — exactly the + intrinsic's documented semantics, no wrapping needed. +- `is_nan` → `fcmp uno double %x, %x` (only NaN compares unordered + against itself). +- `float_to_str` → `Err(CodegenError::Internal("...not yet + implemented (requires dynamic Str allocation in the runtime)"))` + (deferred to iter 5+; the runtime's Str path currently uses only + static `@.str_*` globals — no malloc-backed dynamic-Str + infrastructure). The deferral surfaces as a structured codegen + error rather than a panic, mirroring the rest of the file's + error discipline. + +Float constants `nan`/`inf`/`neg_inf` resolve as bare `Term::Var` +references and lower to direct hex-float `double` SSA values at the +use site — `0x7FF8000000000000` (canonical qNaN), `0x7FF0000000000000` +(+Inf), `0xFFF0000000000000` (-Inf). Intercepted at the top of +`lower_term`'s `Term::Var` arm, before any other resolution. **NOT** +through the `__unreachable__` lowering pattern, which is a +terminator instruction; constants are SSA values. + +`io/print_float` lowers via inline `printf("%g\n", v)` parallel to +`io/print_int`. No new C runtime file needed — the `@printf` +declaration already exists. Format string interned as `fmt_float`. + +Two plan bugs caught and corrected during the iteration: + +1. **Iter-4.2 plan inconsistency** — Task 2 as literally written + would have stranded comparison ops (Step 4 said + `builtin_binop_typed` returns None for them, Step 5 narrowed + `lower_app` matches! to `+ - * / %` only — together that + collapses the no-regression invariant). The implementer caught + this and pulled forward Task 3 Steps 3-4 as a minimal repair. + The orchestrator endorsed; Task 3's residual scope was narrowed + to Float comparison arms + `lower_eq` Float arm. +2. **Iter-4.4 quality review** — `float_to_str` was specified as + `unimplemented!()` (panic), inconsistent with the rest of the + file's `CodegenError::Internal(...)` error discipline. A user + program calling the typecheck-installed `float_to_str` would + panic the compiler instead of producing a structured error. + Fixed in the 4.4 fixup commit. + +Two pull-forwards into iter-4.2 (`is_static_callee` extension for +`==`) and iter-4.4 (`is_static_callee` extension for the 5 new +fn-builtins) reflect the same pattern: any name that lowers via a +direct `lower_app` arm must also be recognised by `is_static_callee`, +or it falls through to the indirect-call path with `UnknownVar`. +Future iterations adding new builtin lowering arms must remember +this companion edit. + +The mono-pass and ADT slot-layout question from spec Components +(monomorphisation produces concrete `List_Fl` slots vs. polymorphic +erased slots needing bitcast) was NOT exercised by the E2E fixture +— `examples/floats.ail.json` does not use Float-typed containers. +This is not a regression: the descriptor `Fl` exists and the +mono pass would emit `List_Fl` correctly; the bitcast question +would only fire for an erased polymorphic container, which AILang +doesn't have today. Queued as a follow-up if a future fixture +exercises the path. + +Per-task commits: + +- `ac5e17e` floats iter 4.1: codegen primitive registration + Float literal hex-double lowering +- `8044a4d` floats iter 4.2: codegen arithmetic dispatch on arg type — fadd/fsub/fmul/fdiv double +- `2a29070` floats iter 4.2 fixup: 3-tuple return for builtin_binop_typed + classifier helper +- `3869641` floats iter 4.3: codegen Float comparison arms (fcmp olt/ole/ogt/oge/UNE) + lower_eq Float +- `581144a` floats iter 4.4: codegen neg/int_to_float/float_to_int_truncate/is_nan + float_to_str-deferred +- `613aa39` floats iter 4.4 fixup: float_to_str returns CodegenError::Internal instead of panic +- `bde5aaf` floats iter 4.5: codegen Float constants nan/inf/neg_inf as hex-double SSA values +- `9764b61` floats iter 4.6: codegen io/print_float + examples/floats.ail.json E2E fixture + +Workspace at iter-4 close: 402 tests passed, 0 failed (from 395 at +iter-3 close — added 6 codegen unit tests + 1 E2E test). + +Known debt deliberately deferred to iter 5: + +- Prose round-trip for Float literals. +- DESIGN.md §"Float semantics" subsection (A5 determinism contract). +- DESIGN.md line-2033 "supported primitive types" list update + (`Float` was not mentioned as a supported primitive there before; + iter 4's ship makes it true). +- `crates/ailang-prose/src/lib.rs::Literal::Float` arm replacement + (currently `unimplemented!("Floats milestone iter 5: prose")` + from iter 1). + +Known debt deliberately deferred BEYOND iter 5 (this milestone): + +- `float_to_str` codegen lowering (requires runtime-allocated Str; + AILang's Str path is currently static-only). Symbol is + type-installed and surface-callable, but lowering errors with a + structured `CodegenError::Internal`. Future milestone wires the + runtime Str-allocator path. +- Float-typed container slot layout (mono `List_Fl` etc.) — not + exercised by any current fixture; revisit if a future use case + surfaces.