From 41dd2ddc1b69ba25a42761ad42eba0bad0f356a6 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 10 May 2026 16:40:37 +0200 Subject: [PATCH] =?UTF-8?q?floats=20iter=205:=20JOURNAL=20=E2=80=94=20iter?= =?UTF-8?q?-5=20close=20+=20Floats=20milestone=20close?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/JOURNAL.md | 193 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index c91418a..4eef155 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -13486,3 +13486,196 @@ Known debt deliberately deferred BEYOND iter 5 (this milestone): - Float-typed container slot layout (mono `List_Fl` etc.) — not exercised by any current fixture; revisit if a future use case surfaces. + +## 2026-05-10 — Iteration Floats.5: prose + DESIGN.md + milestone close + +Replaced the iter-1 prose `unimplemented!("Floats milestone iter 5: +prose")` arm with `write_float_lit` — finite values render as +shortest round-trippable decimal with `.0` suffix (parallel to +surface print); non-finite values render as `NaN` / `+Inf` / +`-Inf` (Mainstream-language spellings) because prose is one-way +render and CAN handle Form-A NaN / ±Inf bits that surface lex +cannot produce. + +DESIGN.md gained a new top-level §"Float semantics" subsection per +spec section A5: IEEE-754 binary64 commitment, per-op bit stability +on fixed (target, LLVM), NaN / ±Inf propagation, `-0`/`+0` hash-vs- +equality asymmetry, FMA-contraction / reassociation / subnormal +flushing UNSPECIFIED, conversions semantics, Form-A serialisation +shape, Pattern::Lit::Float rejection rationale, and the +`float_to_str` deferred-codegen note. The "What is supported" +bullet at line 2034 was extended to mention `Float` as a primitive +type; the builtins list was refreshed to include the widened ops +(`forall a. (a, a) -> a`), the 5 new fn builtins, the 3 Float +constants, and the 4th IO effect op (`io/print_float`). + +Roadmap flipped Floats from `[~]` to `[x]`; Post-22 Prelude is +unblocked (the `depends on: Floats` line dropped — the partial- +Eq/Ord-for-Float story is now settled and documented in +§"Float semantics", so the Prelude milestone can decide what's +instanced without bouncing back to Float design). + +Per-task commits: + +- `ea8988b` floats iter 5.1: prose renders Float literals (finite + NaN/Inf) +- `2d2646a` floats iter 5.2: DESIGN.md — Float in primitive types + refreshed builtins list +- `47b32bf` floats iter 5.3: DESIGN.md — new §Float semantics subsection (A5 determinism contract) +- `965e628` floats iter 5.4: roadmap — mark Floats [x] + unblock Post-22 Prelude + +## 2026-05-10 — Milestone close: Floats + +The Floats milestone is closed. `Float` is a fully supported +primitive type in AILang, end-to-end. `examples/floats.ail.json` +builds via `ail build`, runs, and produces predictable stdout — +the milestone-acceptance gate landed first try in iter 4.6. + +**Five-iteration arc:** + +- **Iter 1 (schema).** `Literal::Float { bits: u64 }` AST variant + with private `hex_u64` serde helper (16-char lowercase hex string + in canonical JSON; bypasses `serde_json::Number` for bit + stability + NaN/Inf representability). `Float` registered as + primitive name. 8 downstream `match Literal` sites wired with + permanent semantic arms or named-iteration `unimplemented!` + placeholders. Drift-test anchors added to `spec_drift.rs` / + `design_schema_drift.rs` / `form_a.md` / DESIGN.md JSON-schema + block. Bit-stability tests pin A1 / A5 properties. +- **Iter 2 (surface).** Lex `.(e[+-]?)?` + and `e[+-]?` per spec A2; reject bare leading / + trailing dots, missing fraction-after-dot, missing / sign-only + exponents. Parser accepts `Tok::Float` in atom + pat-lit + positions (typecheck rejects pat-lit Float in iter 3). Surface + print emits shortest-round-trippable decimal with `.0` suffix + fallback; non-finite bits panic per spec (cannot reach printer + via well-formed surface input). Round-trip property + `lex(print(L)) == L` pinned for 6 representative bit patterns. +- **Iter 3 (typecheck + builtins).** Widened `+`/`-`/`*`/`/` and + `!=`/`<`/`<=`/`>`/`>=` from monomorphic `(Int, Int) -> {Int, Bool}` + to polymorphic `forall a. (a, a) -> {a, Bool}` (same shape `==` + already had). `%` stays Int-only. 5 new builtins installed: + `neg` (poly), `int_to_float`, `float_to_int_truncate`, + `float_to_str`, `is_nan`. 3 bit-pattern constants installed as + bare-value globals: `nan`, `inf`, `neg_inf`. 1 new effect op: + `io/print_float`. `Pattern::Lit::Float` typecheck-rejected via + new `CheckError::FloatPatternNotAllowed`. +- **Iter 4 (codegen + E2E).** Float literals lower as LLVM hex- + float `double` constants. `synth::builtin_binop` replaced by + 3-tuple-returning `builtin_binop_typed(name, &Type)` — + `fadd/fsub/fmul/fdiv double` and `fcmp olt/ole/ogt/oge/oeq/UNE + double` arms (note `UNE`, not `one`). `lower_eq` Float arm. + 5 new fn-builtin lowering arms (`neg` poly with `fneg double`; + `int_to_float` `sitofp`; `float_to_int_truncate` + `@llvm.fptosi.sat.i64.f64`; `is_nan` `fcmp uno`; `float_to_str` + deferred via structured `CodegenError`). 3 Float constants + intercepted at `Term::Var` arm. `io/print_float` via inline + `printf("%g\n", v)`. `examples/floats.ail.json` E2E fixture + + `crates/ail/tests/floats_e2e.rs` E2E test — milestone gate. +- **Iter 5 (prose + DESIGN.md).** Prose renderer for Float + literals (finite + NaN/Inf). DESIGN.md §"Float semantics" + line- + 2034 + builtins-list refresh. Roadmap mark + Prelude unblock. + +**Key design decisions (rationale):** + +- **One float type only — `f64`, named `Float`** (no `f32`). LLM + authors don't reach for `f32` unprompted; shipping both would + surface a per-op type-pun question that adds friction without + measurable correctness gain. +- **IEEE-conformant equality** — `==` returns `false` for + `nan == nan`, no `Eq` instance for `Float` in the eventual + prelude. The partial-equality reality is real and surfaces + through builtins (`is_nan`, ordering ops returning `false` for + NaN-involved comparisons) rather than through a lying total + typeclass instance. +- **Polymorphic operators over `{Int, Float}` via codegen-dispatch** + — the same mechanism `==` already used. Mainstream alignment + beats the explicit-naming purity of the earlier `(fadd 1.5 2.5)` + draft. The hardcoded `{Int, Float}` filter is transitional; + cleanly replaceable by a `Num a` constraint when typeclasses + ship in 22c. +- **Form-A bit-pattern hex string** — bypasses `serde_json::Number` + (not bit-stable across versions for floats; cannot represent + NaN / ±Inf). Routing through the string path preserves bit-exact + determinism + lets non-finite Floats round-trip through + canonical JSON. +- **Pattern::Lit::Float hard-reject at typecheck** — IEEE semantics + make Float patterns semantically dubious (NaN never matches; + bit-exact equality is rarely the LLM-author's intent). Surface + lex / parser accept the syntax to keep the diagnostic at the + correct layer (typecheck, not parser). +- **`fcmp UNE` for `!=`, NOT `fcmp one`** — `one` is "ordered and + not equal" and returns false for `nan != nan`, violating IEEE- + `!=` and the user-fixed constraint #2. Caught during the + pre-implementation LLVM IR audit; documented in iter-4 codegen + arm + DESIGN.md §"Float semantics". +- **`fneg double` for `neg`, NOT `fsub double 0.0, x`** — IEEE + `0.0 - 0.0 = +0.0`, so the `fsub`-from-zero desugar would + wrongly produce `+0.0` for `neg(+0.0)` instead of `-0.0`. LLVM 8+ + intrinsic; clang 22 always available. +- **`@llvm.fptosi.sat.i64.f64` for `float_to_int_truncate`** — + saturating fp-to-int intrinsic exactly matches Rust `as i64` + semantics (NaN → 0, ±Inf → i64::{MIN,MAX}, saturating). Total, + no Maybe wrapper. + +**Process notes (orchestration lessons):** + +- **Drift tests are part of the schema layer.** The original 5-iter + plan put DESIGN.md anchor edits in iter 5; iter 1's drift-test + break revealed they belong in iter 1 (where they get added + together with the AST variant they document). The exhaustive- + match drift tests are *the enforcement mechanism* of the schema + invariant — adding a new `Literal` variant without their anchors + IS a schema-layer break. +- **Plan inconsistencies caught by the reviewer chain.** Iter 4.2 + shipped a plan that would have stranded comparison ops (Step 4 + said `builtin_binop_typed` returns None for them; Step 5 + narrowed the dispatch matches!). Implementer caught it; pulled + forward Task 3 Steps 3-4 as minimal repair; orchestrator + endorsed; Task 3's residual scope was narrowed accordingly. The + two-stage review (spec + quality) caught two more issues: + the dual-meaning `(instr, ll_ty)` 2-tuple → fixed via 3-tuple + return; the `unimplemented!()` panic for `float_to_str` → + fixed via structured `CodegenError::Internal`. +- **`is_static_callee` companion-edit.** Any new lowering arm in + `lower_app` MUST also be recognised by `is_static_callee` + (otherwise App dispatch falls through to indirect-call → + UnknownVar). This bit twice (iter 4.2 for `==`, iter 4.4 for + the 5 new fn-builtins). Both pulled forward as minimal repairs. +- **The `synth_term` test helper does not exist.** Iter 3 plan + named it; the actual fn is `crate::synth(...)` with 8 args. The + implementer flagged this; future plans should specify the + adapter pattern explicitly. + +**Workspace at milestone close:** 405 tests passing, 0 failed. +Iter-1-baseline rustdoc warnings preserved (1 warning on `desugar` +private link, pre-existing). `def_hash` regression hashes +(`db33f57cb329935e` for `sum.sum`, `b082192bd0c99202` for +`IntList`) preserved bit-identical — adding a Literal variant +does not perturb pre-existing canonical bytes. + +**Known debt deferred beyond this milestone:** + +- `float_to_str` codegen lowering — requires runtime-allocated Str + (the current Str path uses only static `@.str_*` globals; + no malloc-backed dynamic-Str infrastructure). Symbol is + type-installed and surface-callable, but lowering errors with a + structured `CodegenError::Internal`. Future milestone wires the + runtime Str-allocator path; revisit then. +- Float-typed container slot layout — mono pass produces + `List_Fl` etc. naturally per the iter-1 `type_descriptor` `Fl` + arm, but no current fixture exercises a Float-typed container. + Verify when a future use case surfaces. +- Pattern-matching on Float ranges (e.g. `(0.0..1.0)`) — out of + scope; would need a different Pattern variant entirely. +- Float-aware ADT layout for Float-only ADT fields — same + monomorphisation guarantee as `List` would extend; not + exercised by current corpus. + +**Roadmap effects:** Floats `[x]`. Post-22 Prelude unblocked (was +`depends on: Floats`); the Prelude milestone can now ship `Show` +for `Float` and decide-against `Eq` / `Ord` for Float (both +reflecting the partial-equality reality settled in this milestone). + +Next dispatch (orchestrator): `audit` skill — milestone-close +drift review + bench-regression diagnostics + rustdoc audit. +After audit closes clean: `fieldtest` skill — 2-4 `.ailx` +real-world examples exercising the new Float surface.