floats iter 5: JOURNAL — iter-5 close + Floats milestone close
This commit is contained in:
+193
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user