floats iter 5: JOURNAL — iter-5 close + Floats milestone close

This commit is contained in:
2026-05-10 16:40:37 +02:00
parent 965e628c52
commit 41dd2ddc1b
+193
View File
@@ -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 `<digits>.<digits>(e[+-]?<digits>)?`
and `<digits>e[+-]?<digits>` 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<Float>` 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.