iter raw-buf.4 rawbuf-payload-termnew-desugar (DONE, drop-call deferred to .5): RawBuf works, prints 60 (refs #7)
Ships RawBuf end-to-end as a consumer of the raw-buf.3 scope-qualified
intrinsic mechanism, plus the general Term::New construction sugar.
Working subset committed; the drop-CALL ratification (no-leak) is
re-carved to raw-buf.5 (see b49f57d) — the flat drop FUNCTION ships here,
its call-insertion needs a codegen resolution mechanism.
What ships (all green):
- raw_buf kernel-tier submodule (crates/ailang-kernel/src/raw_buf/):
RawBuf TypeDef (param-in {Int,Float,Bool}, ctor B a) + four
(intrinsic) ops new/get/set/size. parse_raw_buf + workspace injection
(kernel-tier auto-imported); workspace count 4 -> 5.
- 12 scope-qualified INTERCEPTS entries RawBuf_{new,get,set,size}__{Int,
Float,Bool} + emit fns: new allocs an @ailang_rc_alloc slab
(8-byte i64 size header + n*width element bytes), get/set
getelementptr+load/store at offset 8 + i*width, size loads the header.
Mechanical on the raw-buf.3 naming + bijection machinery; bijection
green (4 markers -> 12 entries).
- Term::New desugar (crates/ailang-core/src/desugar.rs): (new T <types>
<values>) -> (app T.new <values>), runs before check so the
type-scoped callee flows through the raw-buf.3 scope threading to
RawBuf_new__T; drops the NewArg::Type (element type inferred from
use). Both codegen Term::New deferral arms removed (replaced with
unreachable!). Ratified by new_stubt_builds_and_runs.
- Flat intrinsic-storage drop FUNCTION @drop_raw_buf_RawBuf (single
@ailang_rc_dec on the slab), emitted for any TypeDef whose new op is
(intrinsic)-bodied — distinguishes RawBuf (intrinsic new) from StubT
(real-body new -> generic ADT drop).
- E2E: raw_buf_int (-> 60), raw_buf_float (-> 4.0), raw_buf_bool
(-> 42), raw_buf_param_in_reject (param-not-in-restricted-set).
In-scope additions beyond the literal plan (both sound, ratified):
- qualify_workspace_term now normalises a monomorphic cross-module
type-scoped callee (StubT.new) to <home>.f; without it
new_stubt_builds_and_runs cannot build (StubT.new is monomorphic, so
the raw-buf.3 poly-mono path mints no symbol). Same-module + polymorphic
callees carved out.
- diagnostic-behaviour: (new T ..) missing-new-op now surfaces
type-scoped-member-not-found (desugar runs before check, bypassing
synth's Term::New arm); new-arg-kind-mismatch obsoleted. Two unit
tests updated to the new behaviour. param-in reject unaffected.
The 5 .ll snapshots gained @drop_raw_buf_RawBuf (+ partial) — injecting
raw_buf emits its drop fn into every program; benign, regenerated to the
final IR. (The plan's "snapshots stay green" assumption was wrong.)
Verification (orchestrator, this session): cargo test --workspace 676
passed / 0 failed / 2 ignored. raw_buf_int_e2e prints 60; bijection,
round-trip, new_stubt, float/bool/reject all green. The raw_buf_no_leak
test is NOT in this commit — it moves to raw-buf.5 with the drop-call
resolution that makes it pass (the slab currently leaks at scope close;
tracked, fixed next iter; milestone not released until close).
This commit is contained in:
@@ -694,6 +694,29 @@ fn llvm_scalar(t: &Type) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
/// raw-buf.4: does `f`'s declared return type construct the TypeDef
|
||||
/// named `td_name`? Used by the drop-dispatch loop to recognise an
|
||||
/// intrinsic-storage `new` op (its return is `(own (con T …))`).
|
||||
/// Peels `Forall` → `Fn.ret`, then matches the result `Type::Con`
|
||||
/// name against `td_name` (the `own`/`borrow` mode lives in
|
||||
/// `ret_mode`, not in the type, so it needs no peeling). Accepts both
|
||||
/// the bare same-module name and a `<module>.<T>` qualified spelling.
|
||||
fn fn_returns_type(f: &FnDef, td_name: &str) -> bool {
|
||||
let mut ty = &f.ty;
|
||||
if let Type::Forall { body, .. } = ty {
|
||||
ty = body;
|
||||
}
|
||||
let Type::Fn { ret, .. } = ty else {
|
||||
return false;
|
||||
};
|
||||
match ret.as_ref() {
|
||||
Type::Con { name, .. } => {
|
||||
name == td_name || name.rsplit('.').next() == Some(td_name)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn main_is_void(t: &Type) -> bool {
|
||||
match t {
|
||||
Type::Fn { params, ret, .. } => {
|
||||
@@ -1070,6 +1093,32 @@ impl<'a> Emitter<'a> {
|
||||
if matches!(self.alloc, AllocStrategy::Rc) {
|
||||
for def in &self.module.defs {
|
||||
if let Def::Type(td) = def {
|
||||
// raw-buf.4: a TypeDef whose construction is
|
||||
// compiler-supplied (its `new` op is an (intrinsic),
|
||||
// not a real term-ctor body) has a flat slab layout
|
||||
// [size:i64][elements], NOT a tagged-ADT layout. The
|
||||
// generic drop loads a tag at offset 0 and switches
|
||||
// on it — but offset 0 here is the size, so the
|
||||
// generic tag-switch drop is wrong. Emit a flat
|
||||
// drop instead: a single rc-dec on the slab pointer
|
||||
// (primitive elements carry no recursive drops).
|
||||
// This distinguishes RawBuf (intrinsic `new`) from
|
||||
// StubT (real-body `new` → generic ADT drop), where
|
||||
// a param_in heuristic would misclassify StubT
|
||||
// (also param_in). A matching flat partial-drop is
|
||||
// emitted for symbol-resolution parity with the
|
||||
// generic path (no carve-out site can actually
|
||||
// target it — the sole field is a primitive).
|
||||
if self.module.defs.iter().any(|d| {
|
||||
matches!(d, Def::Fn(f)
|
||||
if f.name == "new"
|
||||
&& matches!(f.body, Term::Intrinsic)
|
||||
&& fn_returns_type(f, &td.name))
|
||||
}) {
|
||||
self.emit_flat_intrinsic_drop_fn(td);
|
||||
self.emit_flat_intrinsic_partial_drop_fn(td);
|
||||
continue;
|
||||
}
|
||||
if td.drop_iterative {
|
||||
// opt-in iterative-drop body. The
|
||||
// recursive cascade overflows the C stack on
|
||||
@@ -2083,18 +2132,13 @@ impl<'a> Emitter<'a> {
|
||||
self.block_terminated = true;
|
||||
Ok(("0".into(), "i8".into()))
|
||||
}
|
||||
// prep.2 (kernel-extension-mechanics): Term::New has no
|
||||
// direct codegen path in this milestone — typecheck
|
||||
// accepts it via synth's elaboration, but lowering to LLVM
|
||||
// IR requires a desugar pass that resolves the `new` def
|
||||
// and rewrites the site as a Term::App. That desugar lands
|
||||
// in the raw-buf milestone. Until then, codegen of
|
||||
// Term::New is an internal error: a workspace that types-
|
||||
// checks should not reach codegen with a surviving Term::New.
|
||||
Term::New { .. } => Err(CodegenError::Internal(
|
||||
"Term::New requires the type's `new` def to be desugared first; \
|
||||
codegen does not yet support Term::New directly — milestone raw-buf".into(),
|
||||
)),
|
||||
// raw-buf.4: the Term::New desugar (desugar.rs) rewrites
|
||||
// every `(new T …)` to `(app T.new …)` before check and
|
||||
// codegen, so no Term::New survives to lowering. The arm is
|
||||
// kept only for match exhaustiveness.
|
||||
Term::New { .. } => {
|
||||
unreachable!("Term::New is desugared to (app T.new …) before codegen — raw-buf.4")
|
||||
}
|
||||
Term::Intrinsic => Err(CodegenError::Internal(
|
||||
"Term::Intrinsic must be consumed by the intercept route in fn-body \
|
||||
emission, not lowered as an expression; reaching lower_term means an \
|
||||
@@ -3287,16 +3331,12 @@ impl<'a> Emitter<'a> {
|
||||
// value at its own position).
|
||||
Term::Loop { body, .. } => self.synth_with_extras(body, extras),
|
||||
Term::Recur { .. } => Ok(Type::unit()),
|
||||
// prep.2 (kernel-extension-mechanics): Term::New has no
|
||||
// codegen path in this milestone (see lower_term's arm).
|
||||
// synth_with_extras would only fire if codegen reaches a
|
||||
// surviving Term::New as a callee or sub-expression — same
|
||||
// BUG class as a surviving LetRec — so an internal error
|
||||
// here mirrors lower_term's stance.
|
||||
Term::New { .. } => Err(CodegenError::Internal(
|
||||
"Term::New cannot be synthed at codegen — must be desugared first; \
|
||||
codegen support lands in the raw-buf milestone".into(),
|
||||
)),
|
||||
// raw-buf.4: Term::New is desugared to (app T.new …) before
|
||||
// codegen (see lower_term's arm), so it never reaches the
|
||||
// synth path. The arm is kept only for match exhaustiveness.
|
||||
Term::New { .. } => {
|
||||
unreachable!("Term::New is desugared to (app T.new …) before codegen — raw-buf.4")
|
||||
}
|
||||
Term::Intrinsic => Err(CodegenError::Internal(
|
||||
"Term::Intrinsic cannot be synthed at codegen — an intrinsic body is \
|
||||
signature-only and routed through the intercept registry, never an expression"
|
||||
|
||||
Reference in New Issue
Block a user