Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 56 additions & 8 deletions HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ about the present state, the next decision, and operational facts. The former
per-PR journal is preserved in
[`docs/archive/HANDOFF-2026-07-25.md`](docs/archive/HANDOFF-2026-07-25.md).

_Last updated: 2026-08-02. `main` includes the shipped wave through #688 and the merged am-p
placement-validation PR #690 (`39f9c7d`). The current resumable branch is
`feat/am-n-nominal-link`, implementing the next am-n nominal/link slice.
_Last updated: 2026-08-02. `main` includes the shipped wave through #688 plus the merged am-p
placement-validation PR #690 (`39f9c7d`) and am-n nominal/link PR #691 (`755cb9c`). The current
resumable branch is `feat/am-h-declarations-headers`, implementing the next am-h
declarations/headers slice.
#667 adds the canonical recursive Drop plan and sound `Option<string>` fields;
#668 admits one direct recursively Move payload per tagged arm; #669 admits multiple Move payloads;
#670 completes nested tagged payload representation and the exact pkg.db L1b acceptance shape.
Expand Down Expand Up @@ -409,9 +410,10 @@ contract fixes, graph-valid negative fixtures, four lowering-entrypoint parity,
ledger are one atomic boundary; an intermediate split would either publish an unvalidated entrypoint
or leave the reviewed producer/validator matrix without its required owner evidence.

### am-n implementation checkpoint (2026-08-02)
### am-n shipped checkpoint (2026-08-02)

The am-n implementation is active on `feat/am-n-nominal-link`. MIR now validates struct/enum
The am-n implementation shipped in PR #691 (`755cb9c`), after am-p shipped in PR #690
(`39f9c7d`). MIR now validates struct/enum
identity text, combined internal-name collisions, ASCII member names, repeated source-name
structural equality, tuple interning uniqueness, alignment, enum field bases, and link-library
names before all four HIR-to-MIR entrypoints copy HIR. Source-shape comparison uses an explicit
Expand All @@ -437,8 +439,54 @@ required benchmark row is implemented, but this host cannot run the release harn
`llvm-config-22` is absent. The current diff is about 940 changed lines:
the validator, four-entrypoint owner matrix, shared-graph regression, and benchmark are one atomic
am-n boundary, so splitting it would leave an activated identity gate without its complete owner
evidence. The author self-review is complete; remaining work is the final pre-PR stamp, then one
bounded post-open review cycle for the am-n SHA.
evidence. The author self-review, pre-PR stamp, CI, post-open host/independent review records,
and final attestation all bind to the merged am-n SHA. The branch is now reset to the next am-h
slice.

### am-h implementation checkpoint (2026-08-02)

The next slice is am-h declarations and headers. It activates body-independent validation for
externs, imported functions, stored functions, `main`, locals, parameter modes, return-borrow and
return-region summaries, and structural drop-set records. Imported interface facts gain a
normalized `FnEffect`: a present compatibility-map entry is copied as-is, while an absent entry
becomes `Impure`. HIR retains that fact for validation and MIR strips it after validation so the
existing six-field imported declaration and its structural Debug bytes, interface bytes,
`impl_hash`, and cache identity remain unchanged. `FnOrigin` replaces the overloaded
`lifted_capture_count`/`exportable` pair and is derived before per-unit export decisions.

The public contract and owner evidence are recorded in the am-h ledger and implementation
closure matrix in `docs/impl/17-library-boundary-prerequisites.md`; the universal malformed-record
rules are in `docs/impl/19-hir-validation-ledger.md`. A fresh independent adversarial review of
the completed matrix returned `CLEAN`; its record is
`.git/align-am-h-matrix-review-2026-08-02.log`. The intended owner tests are
`malformed_hir_declaration_header_metadata_fails_closed`,
`main_header_abi_matrix_is_exhaustive`,
`valid_hir_declaration_header_preflight_is_mir_identity`, imported-effect normalization twins,
deep signature/summary twins, and the `mir-header-validation` benchmark row. The first fresh
independent am-h preflight found one valid P2 in the parameter-id mutation fixture; the baseline
now contains a valid non-parameter local so that mutation reaches the header validator. A second
fresh review found two P2s: capture-count rollback now truncates nested lifted functions, and the
matrix now covers duplicate declaration names, FnTy mode/summary fields, root order/range/
borrowability, drop-set ranges, and the full main/Error ABI matrix. The final fresh review is
required on this revised tree. A follow-up Galileo review was run against the revised worktree
but returned no verdict after repeated bounded waits and no findings were reported; it was stopped
as an incomplete review, not treated as CLEAN. The final review must start from the committed SHA
and record its last completed scope if its bound is reached.
The current am-h worktree delta is 1,445 changed hand-written lines under `crates/`, above the
750–950 estimate and the roughly 1,000-line throughput target. The count includes the common
validator, its all-entrypoint mutation matrix, the expanded exact header/ABI matrix, the
producer/effect migration and every consumer, the distinct MIR imported record, and the
codec/producer owners. It cannot be split safely: a producer-only slice would need a temporary
old/new HIR representation, a validator-only slice would leave the overloaded origin pair, and a
MIR-only slice would publish an unvalidated effect; the reviewed am-h matrix therefore keeps this
as one atomic vertical boundary.

The standalone benchmark now checks and runs with the repository LLVM 22 path. Its release
`provenance` run reported `mir-header-validation 0.730 ms/valid+malformed-lower` for 257
functions, alongside the unchanged global-type, nominal/link, continuation, and interface rows.
The revised header owner test compiled but its macOS test binary again paused in dyld startup
(CPU 0, no output after sampling) and was stopped; this host limitation is recorded with the
successful compile and benchmark evidence rather than treated as a test pass.

#660's final verification records 48/48 `align_driver` `par_map` tests,
including 65,537-element worker-range tests for both materializing chunks and
Expand Down Expand Up @@ -803,7 +851,7 @@ return-provenance slices, L2c cleanup-ABI record plus dynamic Move-return bit, L
then L2e
mutable borrow/out and all-peer
exclusivity, for twenty-three L2b and twenty-seven L2 implementation PRs. The counts are fixed by
#678; #690 now lands am-p, and am-n is the next implementation slice. The required milestone order
#678; #690 lands am-p, #691 lands am-n, and am-h is the next implementation slice. The required milestone order
is L2,
L3 package-defined/dependent
resources, L4 named region capability, L5 deterministic static inputs/Query/command artifacts, and
Expand Down
3 changes: 3 additions & 0 deletions bench/library_boundary/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ a 256-function, high-CFG fixture with three expression-valued branches per funct
- `mir-nominal-link-validation`: whole-program MIR-lowering milliseconds per iteration for nominal
structs/enums, repeated id-free source-shape twins, and one linked library, isolating the
nominal/source-identity, enum-base, alignment, and link-name preflight.
- `mir-header-validation`: paired valid and malformed whole-program MIR-lowering milliseconds per
iteration for a large function-header/signature fixture, isolating declaration/header validation
and its canonical-empty failure path.
- `mir-continuation-lowering`: L2b-a2-ac whole-program MIR-lowering milliseconds per iteration and
the fixture's total basic-block count, tracking the O(1) required-child continuation protocol.

Expand Down
33 changes: 33 additions & 0 deletions bench/library_boundary/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,39 @@ fn run_provenance() {
"mir-nominal-link-validation\t{milliseconds:.3}\tms/lower\t{nominal_definitions}\tdefinitions"
);

let header_source = provenance_fixture();
let mut source_map = align_span::SourceMap::new();
let checked = align_driver::check(
&mut source_map,
"mir-header.align",
&header_source,
);
assert!(
!checked.diags.has_errors(),
"MIR header fixture must check"
);
let valid = align_driver::lower_to_mir(&checked.hir);
assert!(!valid.fns.is_empty(), "header fixture must lower");
let mut malformed = checked.hir.clone();
malformed.fns[0].name.push('\0');
let invalid = align_driver::lower_to_mir(&malformed);
assert!(invalid.fns.is_empty(), "malformed header must fail closed");

let mut iterations = 0_u64;
let start = Instant::now();
while start.elapsed() < minimum {
let valid = align_driver::lower_to_mir(black_box(&checked.hir));
let invalid = align_driver::lower_to_mir(black_box(&malformed));
black_box((valid, invalid));
iterations += 1;
}
let elapsed = start.elapsed();
let milliseconds = elapsed.as_secs_f64() * 1_000.0 / iterations as f64;
println!(
"mir-header-validation\t{milliseconds:.3}\tms/valid+malformed-lower\t{}\tfunctions",
checked.hir.fns.len()
);

let continuation_source = mir_continuation_fixture();
let mut source_map = align_span::SourceMap::new();
let checked = align_driver::check(
Expand Down
2 changes: 1 addition & 1 deletion crates/align_codegen_llvm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5022,7 +5022,7 @@ fn declare_fn<'c>(
fn declare_imported_fn<'c>(
ctx: &'c Context,
module: &Module<'c>,
imp: &align_sema::hir::ImportedFn,
imp: &align_mir::ImportedFn,
struct_types: &[StructType<'c>],
enum_types: &[StructType<'c>],
tagged_types: &[StructType<'c>],
Expand Down
17 changes: 17 additions & 0 deletions crates/align_interface/src/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,3 +538,20 @@ pub fn deserialize(bytes: &[u8]) -> Result<InterfaceSummary, DecodeError> {
}
Ok(summary)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn invalid_effect_tag_is_rejected_before_sema() {
let mut reader = Reader::new(&[3]);
assert_eq!(
read_effect(&mut reader),
Err(DecodeError::BadTag {
what: "effect",
tag: 3,
})
);
}
}
39 changes: 34 additions & 5 deletions crates/align_mir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ pub type ValueId = u32;
pub type Slot = u32;
pub type BlockId = u32;

/// A bodyless cross-unit declaration after HIR header validation. The checked-HIR `FnEffect` seed
/// is intentionally absent: it is consumed by sema/MIR validation and is not part of the imported
/// Align ABI, structural MIR bytes, implementation hash, or object-cache input.
#[derive(Clone, Debug)]
pub struct ImportedFn {
pub name: String,
pub params: Vec<Ty>,
pub param_modes: Vec<align_ast::ParamMode>,
pub ret: Ty,
pub return_borrow: hir::ReturnBorrowSummary,
pub return_region: hir::ReturnRegionSummary,
}

#[derive(Clone, Debug)]
pub struct Program {
pub fns: Vec<Function>,
Expand All @@ -88,7 +101,7 @@ pub struct Program {
/// `declare` for each so a `Rvalue::Call` keyed by the mangled `module$name` resolves at link
/// time. **Empty in the whole-program path** (byte-identity), populated only by per-unit
/// lowering.
pub imported_fns: Vec<hir::ImportedFn>,
pub imported_fns: Vec<ImportedFn>,
/// External libraries to link (`-l<name>`), passed through from HIR; consumed by the driver.
pub link_libs: Vec<String>,
/// Struct layouts, indexed by the id in [`Ty::Struct`]; codegen builds LLVM struct
Expand Down Expand Up @@ -129,7 +142,7 @@ pub struct Function {
pub blocks: Vec<Block>,
pub entry: BlockId,
/// M15 S2: whether this function gets `external` linkage under separate compilation (a non-entry
/// `pub` user function; see [`hir::Fn::exportable`]). **Always `false` from whole-program
/// `pub` user function; see [`hir::FnOrigin::is_exportable`]). **Always `false` from whole-program
/// lowering** ([`lower_program`]) so the default object is byte-identical to today; set from HIR
/// only by per-unit lowering ([`lower_program_per_unit`]).
pub exportable: bool,
Expand Down Expand Up @@ -1523,7 +1536,7 @@ pub fn lower_program_located(program: &hir::Program, sm: &SourceMap) -> Program
}

/// M15 S2 per-unit lowering: like [`lower_program`], but honors the separate-compilation visibility
/// model — a non-entry `pub` user function ([`hir::Fn::exportable`]) gets `external` linkage in the
/// model — a non-entry `pub` user function ([`hir::FnOrigin::is_exportable`]) gets `external` linkage in the
/// object (so a dependent unit's object resolves the cross-unit call), and the unit's imported
/// `pub` declarations ([`hir::Program::imported_fns`]) are carried through as bodyless external
/// declares. The whole-program [`lower_program`] forces every function `internal` and drops any
Expand Down Expand Up @@ -1566,6 +1579,7 @@ fn hir_program_is_valid(program: &hir::Program) -> bool {
&& validate_hir::global_type_metadata_is_valid(program)
&& validate_hir::type_placement_metadata_is_valid(program)
&& validate_hir::nominal_link_metadata_is_valid(program)
&& validate_hir::declaration_header_metadata_is_valid(program)
}

fn empty_program() -> Program {
Expand Down Expand Up @@ -1604,7 +1618,7 @@ fn lower_program_unchecked(
);
// Separate-compilation visibility (per-unit lowering only); whole-program lowering keeps
// every function `internal` for byte-identity.
mf.exportable = per_unit && f.exportable;
mf.exportable = per_unit && f.origin.is_exportable();
simplify_known_drop_flags(&mut mf);
fuse_builder_writes(&mut mf);
mf
Expand All @@ -1624,7 +1638,22 @@ fn lower_program_unchecked(
externs: program.externs.clone(),
// Cross-unit `pub` callee declares are a per-unit-only concern; the whole-program path has
// every callee body in `fns`, so its declare list is empty (byte-identity).
imported_fns: if per_unit { program.imported_fns.clone() } else { Vec::new() },
imported_fns: if per_unit {
program
.imported_fns
.iter()
.map(|import| ImportedFn {
name: import.name.clone(),
params: import.params.clone(),
param_modes: import.param_modes.clone(),
ret: import.ret,
return_borrow: import.return_borrow.clone(),
return_region: import.return_region.clone(),
})
.collect()
} else {
Vec::new()
},
link_libs,
structs: program.structs.clone(),
enums: program.enums.clone(),
Expand Down
Loading
Loading