Files
AILang/examples/fieldtest/rbx_1_float_window_stats.ail
T
Brummel 76b21c00eb feat(lang): eliminate the Implicit ownership default — totality + the drop-soundness it demasks (#55)
Deletes `ParamMode::Implicit`. `ParamMode` is now `{Own, Borrow}`:
every fn-type slot on every signature carries an explicit `own` or
`borrow`, no defaulted position survives anywhere (model 0008 §2,
spec 0062). The parser rejects a bare fn-type slot; `borrow-return`
and `borrow-over-value` reject at the signature; the corpus is
migrated to minimal-ownership modes (consumed ⇒ own, read-only-heap
⇒ borrow, value ⇒ trivial-own). The documented `Implicit`-ret-mode
leak is fixed: an owned heap return now drops exactly once (live=0,
acceptance criterion 5).

This was the easy half. Removing the default ACTIVATED a family of
drop paths that `Implicit` had silently skipped — the pre-cutover
language was leaking (and in places mis-dropping) here rather than
crashing, because an Implicit scrutinee turned the drop off. Making
the modes explicit (Own) turned those paths on and exposed two
latent-bug clusters, all fixed RED-first as part of this cutover:

Drop-soundness family (four legs):
  A. lit-sub-pattern double-free — the desugar re-matched the same
     owned scrutinee in the lit fall-through; fixed by grouping
     consecutive same-ctor arms into one match (bind fields once),
     in ailang-core desugar.
  B. Cons-husk leak on non-tail arm bodies — the lit-sub-pattern
     desugar rebound the owned scrutinee via `Let $mp = xs`, which
     bumped consume_count and suppressed the existing fn-return
     partial_drop. Fixed by not rebinding a bare-Var scrutinee
     (one husk-freeing mechanism, not two).
  C. polymorphic `drop_<T>` rc_dec'd monomorphised value fields —
     the per-ADT drop fn was emitted once from the polymorphic
     TypeDef, defaulting type-var fields to ptr and rc_dec'ing
     inline Ints (segfault). Fixed with per-monomorph drop
     functions (new ailang-codegen::dropmono): the drop set is
     collected from the lowered MIR, value-type fields are skipped,
     heap fields still freed once; monomorphic-concrete ADTs keep
     their byte-identical un-suffixed drop symbol.
  D. static Str literal passed to an `(own Str)` param — the
     literal lowers to a header-less rodata constant; the callee's
     now-active rc_dec read its length field as a refcount and
     freed a static address (segfault). Fixed with the missing
     fourth StrRep::Static→Heap promotion in lower_to_mir's App arm,
     gated on Own mode (borrow args stay static, no regression).

over-strict-mode lint over-fired: it suggested `(borrow V)` for
value-typed params (which `borrow-over-value` rejects — own is the
only legal mode there) and fired on `(intrinsic)` bodies (whose
consumption the linearity walk cannot observe). Tightened to skip
both; contract 0008 updated to the narrowed firing scope.

Irreversible step — canonical-form hash reset (model 0008 §6,
acceptance criterion 6). Every signature now carries explicit modes,
so the hashable canonical JSON changed for every module. RATIFY:
the corpus-wide hash-pin reset (hash_pin, prelude_module_hash_pin,
mono_hash_stability, eq_ord_e2e, embed_export_hash_stable, the
ct4/iter*/loop_recur schema-extension pins) and the list ir_snapshot
golden were regenerated once, deliberately, as the intended one-time
consequence of removing the mode elision from the canonical form —
not a regression. Each regenerated hash verified deterministic across
two runs.

Also fixes a pre-existing latent failure surfaced by the verification
gate, unrelated to this cutover: the `every_contract_names_a_resolvable_
ratifying_test` resolver (design_index_pin) could not resolve the
" + " dual-link ratifying-test form (`uniqueness.rs + linearity.rs`)
that the #57 audit-close (dfdc65f) introduced — it shipped red on that
commit. Resolver taught the dual-link form, mirroring its sibling.

Verification: cargo test --workspace = 731 passed, 0 failed (twice,
stable); e2e 102 passed, no binary exits non-zero (corpus crash-free);
grep-clean for Implicit/fn_implicit/mode_eq across crates; every drop
fix confirmed via emitted IR + AILANG_RC_STATS balance on the head==K,
head!=K, and Nil paths. Three BLOCKEDs en route (the unsound first
husk-dec attempt, the over-strict derivation premise, the leg-B fix
direction) were each treated as a real design/spec gap and rediagnosed,
not patched over.

Supersedes #54 (return-position-only leak patch). Precondition #57
(linearity hardening) was already met. Spec docs/specs/0062, plan
docs/plans/0121.

closes #55
2026-06-02 00:03:46 +02:00

93 lines
3.9 KiB
Plaintext

; Fieldtest raw-buf comprehensive .1 (Axes 1+2+3+4 combined — the
; "Series substrate" shape with a Float element width).
;
; Task an LLM author is naturally given: "model a fixed-size window of
; Float samples with a running total, fill it from a loop, then report
; the window mean and the window maximum through read-only helpers."
;
; The natural decomposition wraps a RawBuf<Float> together with a
; bookkeeping `count` field in a user ADT (Window), exactly the Series
; substrate the ledger (design/models/0007 §Series) describes — an ADT
; holding `(own (RawBuf a))` plus Int bookkeeping. The buffer is filled
; via a (loop ...)/recur (runtime-N, not literal indices), and the
; read-back is factored into two `borrow (Window)` helpers (`mean`,
; `wmax`) that each pattern-match the ADT and drive a read loop off the
; bookkeeping count, reading the buffer through `RawBuf.get` on a borrow.
;
; This stresses, in one program:
; - Float element width through a fill loop (not just literal sets).
; - A RawBuf stored in a user ADT field (the #50 substrate shape).
; - TWO borrow-receiver helpers composing over the same wrapped buffer
; (the #46 borrow-receiver-read fix, under composition + behind a
; match on the ADT).
; - The drop cascade: the Window ADT owns the buffer; dropping the
; Window frees the slab. Leak-clean expected under AILANG_RC_STATS.
;
; Window of 4 samples: 2.0, 4.0, 6.0, 8.0.
; mean = 20.0 / 4 = 5.0
; wmax = 8.0
; Expected stdout: two lines, "5.0" then "8.0".
(module rbx_1_float_window_stats
; NOTE (field test): the ledger's §Series substrate form writes the
; storage field as `(own (RawBuf a))`. That does NOT parse here — `own`
; in a ctor field position is rejected (surface-parse-error). And the
; bare `(con RawBuf (con Float))` field gives a type-mismatch
; (expected RawBuf<Float>, got raw_buf.RawBuf<Float>). The only form
; that checks is the fully-qualified `raw_buf.RawBuf`. See findings.
(data Window
(ctor W (con raw_buf.RawBuf (con Float)) (con Int)))
(fn build_window
(doc "Allocate a 4-slot Float buffer, fill slot i with (2*(i+1)), wrap in a Window with count=4.")
(type (fn-type (params (own (con Int))) (ret (own (con Window)))))
(params n)
(body
(let buf (new RawBuf (con Float) 4)
(let filled
(loop (b (con RawBuf (con Float)) buf) (i (con Int) 0)
(if (app ge i n)
b
(recur (app RawBuf.set b i (app int_to_float (app * 2 (app + i 1))))
(app + i 1))))
(term-ctor Window W filled n)))))
(fn mean
(doc "Average of the first count slots, read through a borrow of the Window.")
(type (fn-type (params (borrow (con Window))) (ret (own (con Float)))))
(params w)
(body
(match w
(case (pat-ctor W buf count)
(app /
(loop (acc (con Float) 0.0) (i (con Int) 0)
(if (app ge i count)
acc
(recur (app + acc (app RawBuf.get buf i))
(app + i 1))))
(app int_to_float count))))))
(fn wmax
(doc "Maximum of the first count slots, read through a borrow of the Window.")
(type (fn-type (params (borrow (con Window))) (ret (own (con Float)))))
(params w)
(body
(match w
(case (pat-ctor W buf count)
(loop (mx (con Float) 0.0) (i (con Int) 0)
(if (app ge i count)
mx
(recur (if (app float_gt (app RawBuf.get buf i) mx)
(app RawBuf.get buf i)
mx)
(app + i 1))))))))
(fn main
(type (fn-type (params) (ret (own (con Unit))) (effects IO)))
(params)
(body
(let w (app build_window 4)
(seq (seq (app print (app mean w)) (do io/print_str "\n"))
(seq (app print (app wmax w)) (do io/print_str "\n")))))))