floats iter 4: JOURNAL — iteration close (codegen + E2E)

This commit is contained in:
2026-05-10 16:28:10 +02:00
parent 9764b616ce
commit 73d65baa81
+136
View File
@@ -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.