iter embedding-abi-m3.1 (DONE 6-7): freeze the value layout — DESIGN.md frozen-layout SSOT + lockstep pointers + enforceable byte-pin

Tasks 6-7 of the Boss-repaired split dispatch (Tasks 1-5 committed d5c565d).
Task 6: DESIGN.md §"Embedding ABI" gains the `### Frozen value layout
(M3 — one-way commitment)` SSOT subsection; both "provisional until M3"
sentences rewritten to one-way-freeze wording (docs_honesty_pin-safe — the
pinned "written **bare**" sentence stayed byte-verbatim, :2297->:2299
content-asserted); // FROZEN ABI lockstep pointers at runtime/rc.c +
match_lower.rs lower_ctor + drop.rs; 3 plan-named stale rustdocs fixed +
the Boss-caught stale // gate-comment block (check/lib.rs:1917-1922,
M1/scalar-only -> M1/M2/M3/C-ABI-permitted-incl-record); byte-pin
enforceability demonstrated (RED on a local offset perturbation, GREEN on
git-checkout restore — the only legitimate git op, working-tree only).
Task 7: milestone-close verification gate.

Boss-verified independently (agent report = intent, not outcome): workspace
639 passed / 0 failed; embed_record_e2e 2/2 (own+borrow, global
leak-freedom model); byte-pin 1/1; gate 10/10; forwarder 3/3;
docs_honesty_pin 5/5 pin-safe; design_schema_drift + embed_export_hash_stable
+ embed_e2e + M2 swarm regression-green-unmodified.

INDEX.md line added (deferred from the PARTIAL — one line per iter, final
title). Milestone-close audit next (architect Invariant 1 + bench trio,
spec Testing items 8/9 — audit-owned, not the implement run's job).
This commit is contained in:
2026-05-18 21:29:50 +02:00
parent d5c565d48d
commit 4ea8bc5faf
10 changed files with 223 additions and 33 deletions
+14 -9
View File
@@ -445,9 +445,12 @@ pub enum CheckError {
ConstHasEffects(String, Vec<String>),
/// An `(export …)` fn has a parameter or return type that is not
/// a C-scalar (`Int`/`Float`). M1's embedding ABI is scalar-only;
/// `Bool`/`Str`/every ADT is rejected at typecheck (the
/// feature-acceptance clause-3 discriminator, in code).
/// a permitted embedding-ABI type. The embedding ABI is frozen as
/// of M3 (DESIGN.md §"Embedding ABI" > "Frozen value layout"):
/// `Int`/`Float` or a single-constructor record of those; every
/// other shape (multi-ctor sums, `Str`/`List`/nested-record
/// fields) is rejected at typecheck (the feature-acceptance
/// clause-3 discriminator, in code).
/// Code: `export-non-scalar-signature`.
#[error("export `{0}`: embedding ABI accepts `Int`/`Float` or a single-constructor record of those — {1} type `{2}` is not permitted (multi-constructor sums, and `Str`/`List`/nested-record fields, are a future M4 layer)")]
ExportNonScalarSignature(String, &'static str, String),
@@ -1911,12 +1914,14 @@ fn check_fn(f: &FnDef, env: &Env, out_warnings: &mut Vec<Diagnostic>) -> Result<
}
check_type_well_formed(&ret_ty, &env)?;
// Embedding-ABI M1: an `(export …)` fn is the C call boundary.
// Its signature must be scalar-only (`Int`/`Float`) and pure.
// Gate order: params → ret → effects, first violation wins
// (so a combined Str+IO export deterministically fails on the
// signature). Unconditional at check-time — independent of the
// codegen emit mode — this IS the clause-3 discriminator.
// Embedding-ABI gate (M1 scalars / M2 ctx / M3 record): an
// `(export …)` fn is the C call boundary. Its signature must be
// C-ABI-permitted — `Int`/`Float`, or a single-constructor
// record of those (M3) — and pure. Gate order: params → ret →
// effects, first violation wins (so a combined Str+IO export
// deterministically fails on the signature). Unconditional at
// check-time — independent of the codegen emit mode — this IS
// the clause-3 discriminator.
if f.export.is_some() {
fn is_c_scalar(t: &Type) -> bool {
matches!(t, Type::Con { name, args, .. }
+2
View File
@@ -85,6 +85,7 @@ impl<'a> Emitter<'a> {
out.push_str(" %is_null = icmp eq ptr %p, null\n");
out.push_str(" br i1 %is_null, label %ret, label %live\n");
out.push_str("live:\n");
// FROZEN ABI (embedding boundary) — see DESIGN.md §"Embedding ABI".
out.push_str(" %tag = load i64, ptr %p, align 8\n");
let n_ctors = td.ctors.len();
@@ -110,6 +111,7 @@ impl<'a> Emitter<'a> {
if lty != "ptr" {
continue;
}
// FROZEN ABI (embedding boundary) — see DESIGN.md §"Embedding ABI".
let off = 8 + (j as i64) * 8;
let addr_id = local;
local += 1;
+3 -2
View File
@@ -174,8 +174,9 @@ pub enum Target {
/// to the internal `@ail_<module>_<def>`. M2: the entrypoint takes
/// a mandatory leading `ptr %ctx` (per-thread embedding context)
/// published via `@__ail_tls_ctx` around the unchanged internal
/// call. The ctx-threaded C signature is the M2 shape; only the
/// value/record layout remains provisional until M3.
/// call. The ctx-threaded C signature is the M2 shape; the
/// value/record layout is frozen as of M3 (DESIGN.md §"Embedding
/// ABI" > "Frozen value layout").
StaticLib,
}
+2
View File
@@ -104,6 +104,8 @@ impl<'a> Emitter<'a> {
}
compiled.push((v, vty));
}
// FROZEN ABI (embedding boundary) — DESIGN.md §"Embedding ABI"
// > "Frozen value layout". size = 8 + n*8, tag@0, fields@8+i*8.
let size_bytes = 8 + (compiled.len() * 8) as i64;
let p = self.fresh_ssa();
// Iter 17a: pick allocator based on escape analysis. `alloca`
+2 -1
View File
@@ -208,7 +208,8 @@ pub struct FnDef {
/// Iter embedding-abi-m1: when `Some(sym)`, this fn is the
/// embedding boundary. Codegen's `Target::StaticLib` mode
/// additionally emits an externally-visible C entrypoint
/// `@<sym>` (scalar signature, provisional until M3) forwarding
/// `@<sym>` (signature frozen as of M3 — DESIGN.md §"Embedding
/// ABI" > "Frozen value layout") forwarding
/// to `@ail_<module>_<fn>`. The symbol is author-chosen and
/// decoupled from the `ail_<module>_<def>` mangling so the
/// M3-frozen ABI survives module/fn refactors.