Section 04: Integer Narrowing Pipeline
Context: Today, every int is i64 in LLVM IR. A loop counter that goes 0..100 wastes 7 bytes per element in array storage. A struct with { x: int, y: int } where both fields are always 0..255 uses 16 bytes instead of 2. The savings compound in collections: [Point] with 1M elements wastes 14MB.
Reference implementations:
- Zig
src/Sema.zig:coerceInMemoryAllowedPtrAbiType()— coerces comptime_int to runtime width - Roc
crates/compiler/mono/src/layout.rs:Layout::from_var()— selects concrete layout from type variable constraints - LLVM
lib/Transforms/InstCombine/InstCombineCasts.cpp: Integer truncation elimination
Depends on: §03 (range analysis provides the intervals).
04.1 Width Selection Algorithm
File(s): compiler/ori_repr/src/narrowing/int.rs
Setup required: compiler/ori_repr/src/narrowing/ does not exist yet. Steps:
- Create the
compiler/ori_repr/src/narrowing/directory - Create
mod.rs(dispatch hub),int.rs(integer narrowing),abi.rs(ABI widening),overflow.rs(overflow guards),tests.rs(sibling test file) - Add
pub mod narrowing;tocompiler/ori_repr/src/lib.rsTheapply_integer_narrowing()stub inlib.rs(line 215) is the entry point — fill it in to call intonarrowing/int.rs.
Given a ValueRange, select the minimum integer width that preserves the semantic contract.
-
Implement width selection (2026-03-26): Created
compiler/ori_repr/src/narrowing/int.rswithnarrow_struct_fields(). UsesValueRange::min_width()fromrange/mod.rs— no duplicate function. Iteratesplan.decision_indices()to find Struct/Tuple types, narrowsInt { I64, signed: true }fields to smallest width from field-range summaries. Wired intoapply_integer_narrowing()inlib.rs. -
Apply conservatism rules — implemented subset (2026-03-26):
-
#repr("c")/#repr("packed")/#repr("transparent")types skip narrowing (has_fixed_layout_attr()) -
#repr("c", aligned N)types skip narrowing (TPR-04-004 fix, 2026-03-26) -
NarrowingPolicy::Disabledskips all narrowing - Only canonical
Int { I64, signed: true }fields are candidates - Fields with
Toprange stay at I64 (safe default) - Field-summary-driven narrowing: only from §03’s
FieldSummaryTablebuilt fromConstructsites
-
-
Apply conservatism rules — visibility-based gating (TPR-04-005, implemented 2026-03-26): Public API types are now excluded from integer narrowing. Implementation:
- Added
pub_type_indices: FxHashSet<Idx>toReprPlan(plan.rs) withset_pub_type_indices()andis_public_type()API -
compute_repr_plan_with_interner()acceptspub_type_indices: &[Idx]parameter; stored in Phase 0b - Both call sites (
codegen_pipeline.rs,evaluator/compile.rs) extract public type indices fromTypeEntry::visibility == Visibility::Public -
narrow_struct_fields()gates onplan.is_public_type(idx)— public types skip narrowing with tracing - Test
public_type_not_narrowed: pub struct with bounded fields → stays I64 - Test
private_type_narrowed_normally: private struct narrowed, pub struct preserved in same plan
- Added
-
Apply conservatism rules — monomorphized generic type propagation (TPR-04-012, implemented 2026-03-27): Generic type instantiations (
Applied → concrete Struct) create distinct pool idxs that bypass repr/public exemptions. Fix: addedpropagate_metadata_to_applied_resolutions()as Phase 0c incompute_repr_plan_with_interner()(lib.rs). Collects protected type Names from repr_attrs/pub_type_indices, scans all pool Applied entries, resolves through pool chain, propagates metadata to concrete Struct idx. Implementation:- Phase 0c in
compute_repr_plan_with_interner():propagate_metadata_to_applied_resolutions()iterates pool for Applied types matching protected Names, resolves each, propagates repr_attr and pub_type to resolved concrete Struct idx (2026-03-27) - 6 regression tests in
tests.rs:repr_attr_propagates_through_applied_to_concrete_struct,pub_type_propagates_through_applied_to_concrete_struct,repr_c_applied_concrete_struct_not_narrowed_semantic_pin,pub_applied_concrete_struct_not_narrowed_semantic_pin,applied_without_resolution_no_propagation(negative),multiple_applied_instantiations_all_protected(2026-03-27) - Semantic pin: #repr(“c”) Named type with monomorphized Applied → Struct — narrowing blocked on mono struct (stays I64). ONLY passes with Phase 0c propagation (2026-03-27)
- Semantic pin: pub Named type with monomorphized Applied → Struct — narrowing blocked on mono struct (stays I64). ONLY passes with Phase 0c propagation (2026-03-27)
- 381/381 ori_repr debug + release green, 14,236 total tests green (2026-03-27)
- Phase 0c in
-
Conservatism design rules (enforced incrementally by Phase A/B/C):
- Local variables (Phase B): narrow aggressively (widening is free in registers)
- Struct fields (Phase A — done): narrow from field-summary table only
- Function parameters (Phase B): narrow only if ALL call sites agree on the range
- Function returns (Phase B): narrow only if ALL callers can handle the narrow type
- Collection elements (Phase C): narrow aggressively (savings multiply by element count)
- Public API types (Phase A — done): do NOT narrow (gated by
is_public_type()) - Address-taken functions / indirect-call targets (Phase B): do NOT narrow parameters or returns
- Closure captures (Phase B): canonical width in closure environment
-
Use the existing
NarrowingPolicyfromcompiler/ori_repr/src/plan/query.rs(2026-03-26):narrow_struct_fields()checksplan.narrowing_policy() == Disabledand returns early. Policy consumed via existing API.NarrowingPolicyalready exists incompiler/ori_repr/src/plan/query.rswith three variants:// Existing type — do NOT redeclare in narrowing/int.rs pub enum NarrowingPolicy { Aggressive, // Apply all safe narrowing optimizations (default) Conservative, // Apply only provably-safe narrowing (no heuristics) Disabled, // No narrowing — canonical representations only }The narrowing pass reads the policy via
plan.narrowing_policy()(already implemented). Per-site policy (e.g., “min 2 bytes savings”) is handled by the conservatism rules above, not by a field on the enum variant.
04.2 ABI Boundary Widening
File(s): compiler/ori_repr/src/narrowing/abi.rs
At function boundaries and FFI, narrowed integers must be widened back to canonical width. This is critical for correctness.
-
Define ABI boundary rules (2026-03-26): Created
compiler/ori_repr/src/narrowing/abi.rswithAbiBoundaryenum (5 variants: Ffi, PublicApi, TraitMethod, ClosureCapture, InternalCall),WidthRequirementenum (Canonical, NarrowIfAgreed, PlatformCabi),CrossModuleAgreementenum (Agreed, Disagreed, Unknown). Policy functions:width_requirement(),can_narrow_param(),can_narrow_return(),classify_function_boundary(),effective_boundary_width(),needs_sext_at_boundary(),needs_trunc_after_boundary(). Exported from crate root. 24 tests innarrowing/tests.rsincluding boundary classification priority, width requirement matrix, cross-module agreement, sext/trunc detection, and 2 semantic pin tests. -
Implement widening insertion policy (2026-03-26): Widening rules encoded in
abi.rspolicy functions.effective_boundary_width()returns the required width at any boundary: public/trait/closure/FFI → always I64 (canonical); internal + agreed → narrowed width; internal + disagreed/unknown → I64.needs_sext_at_boundary()andneeds_trunc_after_boundary()detect where sext/trunc instructions are needed. The actual LLVMsext/truncemission is deferred to §04.4 (LLVM Codegen Integration). Rules:- Before public function return:
sext i32 %narrow to i64 - Before FFI call arguments: widen to C-ABI width
- At module import boundaries: widen to canonical
- When storing to generic collection: widen if collection is exported
- Closure environments: treat capture slots as canonical-width storage
- Before public function return:
-
Cross-module narrowing via Merkle hashes (2026-03-26):
CrossModuleAgreementenum models the three states (Agreed, Disagreed, Unknown).can_narrow_cross_module()returns true only forAgreed.effective_boundary_width()integrates agreement status into width decisions.Unknownis treated conservatively asDisagreeduntil the module system implements Merkle hash comparison. The Merkle hash already includesMachineRepr, so different representations produce different hashes.
04.3 Overflow Guard Insertion
File(s): compiler/ori_repr/src/narrowing/overflow.rs
When a value is narrowed, arithmetic operations might overflow the narrow type even though they wouldn’t overflow the canonical i64. The compiler must insert overflow checks.
-
Implement overflow analysis (2026-03-27): Created
compiler/ori_repr/src/narrowing/overflow.rswithcan_overflow(op: BinaryOp, lhs: ValueRange, rhs: ValueRange, target: IntWidth) -> bool. Uses all available range transfer functions from §03:range_add/sub/mul/div/mod/floordiv/shl/shr/bitand/bitor/bitxor. Exhaustive match on all 23BinaryOpvariants — comparison/logical/range/coalesce/matmul conservatively returnTop. 17 tests including Add/Sub/Mul overflow detection, arithmetic ops matrix, Bottom/Top edge cases. -
Overflow strategy recommendation (2026-03-27): Created
OverflowStrategyenum with three variants:ProvenSafe(range proves no overflow — zero cost),WidenCompute { intermediate_width }(sext operands, compute at wider type, trunc result — low cost),UseCanonical(use i64 — forward compat, currently unreachable since i64 covers all values).recommend_strategy()function implements priority: (c) ProvenSafe when!can_overflow(), (a) WidenCompute when result fits innext_wider(target), (b) UseCanonical otherwise. Tests verify strategy progression, I8→I16→I32→I64 widening chain. -
Decision codified (2026-03-27): prefer (c) ProvenSafe when provable, (a) WidenCompute for rare overflow, (b) UseCanonical when overflow exceeds next-wider. Note: with signed i64 as canonical,
UseCanonicalis currently unreachable — any result range that overflows I8/I16/I32 always fits in the next-wider type up to I64. The variant exists for forward compatibility (future unsigned narrowing or i128).
04.4 LLVM Codegen Integration
Integration note: TypeInfo::storage_type() does NOT consult ReprPlan and must not be modified for integer narrowing. The actual integration point is TypeLayoutResolver::try_repr_to_llvm_type() in compiler/ori_llvm/src/codegen/type_info/mod.rs (lines 167–229). Current state: Primitive MachineRepr::Int { width } → i8/i16/i32/i64 already works (lines 171–176), and TypeLayoutResolver::resolve_inner() queries repr_plan.get_repr(idx) first (line 249). However, MachineRepr::Struct/Tuple return None and fall back to TypeInfoStore canonical i64 fields — struct/tuple lowering is pending until try_repr_to_llvm_type() recursively consumes narrowed FieldRepr widths (see Phase A LLVM struct/tuple lowering tasks below).
Per-variable vs per-type Idx: ReprPlan::int_width(idx) queries a type Idx. All local int variables share Tag::Int, so per-variable local narrowing needs either (a) a per-(function, var) decision map in ReprPlan, or (b) deriving the width on-the-fly in the emitter from plan.var_range(func, var).min_width(). Struct-field narrowing is clean: the struct type gets a new MachineRepr::Struct with narrowed FieldRepr. Local variable narrowing is Phase B.
File(s):
compiler/ori_repr/src/narrowing/int.rs— theapply_integer_narrowing()implementation (populatesReprPlanwith narrowedMachineRepr::Structdecisions for struct types whose fields have bounded ranges from §03)compiler/ori_llvm/src/codegen/type_info/mod.rs—TypeLayoutResolver::try_repr_to_llvm_type()(handlesMachineRepr::Int { width }→ i8/i16/i32/i64; must be extended to handleMachineRepr::Struct/Tupleby recursively loweringFieldReprwidths — see §04.4 tasks)compiler/ori_llvm/src/codegen/arc_emitter/value_emission.rs—emit_literal()emitsconst_i64for all int literals today; for narrowed locals, must emit narrowed constant when variable’s type has been narrowedcompiler/ori_llvm/src/codegen/arc_emitter/construction.rs— struct construction;sext/truncinserts needed at field store boundaries when field width differs from operand width
The LLVM backend type resolution path via TypeLayoutResolver handles MachineRepr::Int { width } but does not yet handle recursive MachineRepr::Struct/Tuple — those return None from try_repr_to_llvm_type() and fall back to TypeInfoStore canonical i64 fields. §04.4 must extend try_repr_to_llvm_type() to recursively lower FieldRepr widths for struct/tuple decisions (TPR-04-006 fix).
-
Phase A — Struct field narrowing (primary): (2026-03-27):
apply_integer_narrowing()callsnarrow_struct_fields()which iterates all struct types with Struct reprs. For each struct field of typeint, queriesplan.field_range(struct_idx, field_index). Ifrange.min_width() < I64, emits a narrowedMachineRepr::Structdecision with updatedFieldReprentries. Bug fix (IR-PIN-04-018): narrowed decisions were stored only under the original Pool index (e.g.,Named("Pixel")) but codegen always canonicalizes viapool.resolve_fully()to the concreteStruct(fields)index. Fixed by propagating narrowed decisions to resolved indices (mirrors Phase 0 pattern for#reprattrs). Also fixed derive codegen (hash, printable, debug) to sext narrowed i8/i16/i32 fields to canonical i64 before passing to runtime functions. Scopedtry_lower_narrowed_aggregate()to all-scalar-int structs only — mixed-type structs (str + int) need Phase C element_store_size integration. Original plan code block:fn apply_integer_narrowing(plan: &mut ReprPlan, pool: &Pool) { // Iterate Pool for struct/tuple types with int fields. // For each field with a narrowable range, replace the canonical // MachineRepr::Struct with one having narrowed FieldRepr widths. // TypeLayoutResolver already reads MachineRepr::Struct and uses // FieldRepr — no LLVM codegen changes needed. }§04/§06 interface contract: §04 writes only the
FieldRepr.reprfield (narrowed width). It does NOT computeFieldRepr.offset,StructRepr.size, orStructRepr.align— those are §06’s exclusive responsibility. §04 sets these to zero as placeholders. §06 reads the narrowedreprvalues to compute correct field sizes and then runs the alignment-optimal reordering algorithm. No code downstream of §04 but upstream of §06 may readFieldRepr.offsetorStructRepr.size. -
Phase B — Local variable narrowing (2026-03-28): Local
intvariables all share the same PoolIdx(Tag::Int), so per-variable narrowing cannot use the type-keyedReprPlan::int_width(idx)alone. Two options:- Option 1: Add a per-(function, var) map to
ReprPlanfor local variable decisions (newlocal_int_widthfield:FxHashMap<(Name, ArcVarId), IntWidth>). Query inArcIrEmitterwhen emitting variable-producing instructions. - Option 2: In the ARC IR emitter, derive the width on-the-fly from
plan.var_range(func, var).min_width()for each variable at emission time (avoids new map, slightly more computation at codegen time). Recommended: Option 2 for simplicity — queryplan.var_range(func, var).min_width()in the emitter when producing the alloca/phi/store for a local int variable.
- Option 1: Add a per-(function, var) map to
-
Phase C — Collection element narrowing (2026-03-28): A
[int]array where all stored values are[-128, 127]now records a narrowed element representation inReprPlan; LLVM backing storage remains canonical untilelement_store_size()consults that repr. Implementation:- Added
ElementSummaryTabletocompiler/ori_repr/src/range/field_summary.rs— parallel toFieldSummaryTable, keyed by collection typeIdx. Methods:observe_elements(),element_range(),flush_to_repr_plan(). - Added
update_element_summaries()— tracks element ranges fromConstruct(ListLiteral|SetLiteral)andCollectionReuseinstructions. Checks inner type viapool.list_elem()/pool.set_elem()— only int-typed elements contribute bounded ranges. - Added
element_range_summaries: FxHashMap<Idx, ValueRange>toReprPlanwithjoin_element_range()andelement_range()methods. - Wired into fixpoint loop:
update_element_summaries()called alongsideupdate_field_summaries(),recompute_element_summaries()after convergence,element_summariesfield inRangeFixpointResult, flushed inpropagate_ranges(). - Implemented
narrow_collection_elements()innarrowing/int.rs— iteratesFatPointer(Collection { element_repr: Int { I64 } })decisions, queries element range, narrows when safe. Same conservatism: public types,#reprattrs, disabled policy. Called afternarrow_struct_fields()inapply_integer_narrowing(). - 11 unit tests in
narrowing/tests.rs: bounded i8/i16/i32, Top stays i64, public not narrowed, disabled policy, repr(“c”), semantic pin, negative pin, joined ranges, one-wide-prevents-narrowing. - 14,371 tests pass, 0 failures (2026-03-28).
- Codegen limitation:
element_store_size()in LLVM emitter does not yet consultReprPlanfor narrowed element types — GEP strides still use canonicali64(8 bytes). LLVM codegen integration tracked as separate Phase C items below. - Scope limitation: only
Construct(ListLiteral|SetLiteral)andCollectionReusecontribute ranges..push()and other runtime mutations are lowered toApplycalls — their ranges are conservativelyTop.
- Added
-
Phase A — LLVM struct/tuple lowering (TPR-04-006 fix). (2026-03-27): Implemented
try_lower_narrowed_aggregate()inlayout_resolver.rs. Only triggers for structs with at least one narrowed int field (IntWidth != I64). Recursively resolves field reprs viatry_repr_to_llvm_type(). Non-narrowed structs continue using the named struct path. Phase A scoping: tuples excluded from narrowing — tuples are used as collection elements, iterator state, and intermediates whereelement_store_size()assumes canonical widths. Tuple narrowing deferred to Phase C whenelement_store_size()integration is complete. Implementation:-
try_lower_narrowed_aggregate()inlayout_resolver.rs:303-346: detects narrowed aggregates viahas_narrowedfield scan, resolves all fields recursively, builds anonymous LLVM struct type from narrowed field types. Falls back toNonefor non-narrowed structs and structs with unresolvable nested fields. - Fallback: if any field repr returns
Nonefromtry_repr_to_llvm_type()(e.g., nestedStruct/Enum), returnsNone→ TypeInfoStore two-phase creation path takes over. - End-to-end semantic pin: 6 AOT tests in
compiler/ori_llvm/tests/aot/narrowing.rs— Pixel round-trip (trunc i64→i8 + sext i8→i64), struct update, mixed types (str + int + bool — runtime fallback only: int field stays canonical i64 due to all-scalar-int guard intry_lower_narrowed_aggregate(); mixed-field narrowing deferred to Phase C), field mutation, i8 boundary values (-128, 127), negative test (wide range stays canonical). - Pixel test uses
[-128, 127]for true i8 pin (signed narrowing). - Tuple narrowing disabled in
narrow_struct_fields()(narrowing/int.rs):CandidateKind::Tuple→ skip with tracing. Tuple narrowing test updated totuple_elements_not_narrowed_phase_a.
-
-
Insert
sext/truncat narrowing boundaries (2026-03-27): Struct field store and load boundaries implemented. Function entry/exit (Phase B) deferred.- Struct field store (
construction.rs:29-34):trunc_for_narrowed_struct()inemitter_utils.rs— checks pool field type isTag::IntAND LLVM field is narrower, insertstrunc i64 %val to i<N>. Naturally narrow types (Byte, Char, Bool) pass through unchanged. - Struct field load (
instr_dispatch.rs:216-224):sext_narrowed_field()inemitter_utils.rs— checks ARC IR destination type isTag::Int, insertssext i<N> %field to i64. Non-int destinations pass through unchanged. - Function entry (Phase B): parameters arrive at canonical width →
truncto narrow if locally narrowed — deferred to Phase B - Function exit (Phase B): narrow local →
sextto canonical width at boundary — deferred to Phase B
- Struct field store (
-
[IR-PIN-04-018]IR semantic pin tests for narrowing (2026-03-27, from TPR-04-018). Added 4 IR semantic pin tests usingcompile_and_capture_ir()+extract_function_ir()innarrowing.rs. Tests exposed a critical bug: narrowed decisions were invisible to codegen (index mismatch). Fixed by propagating decisions to resolved Pool indices and adding sext in derive codegen (hash/printable/debug). Implementation:-
test_narrowed_struct_ir_pin_type_layout: Asserts{ i8, i8, i8, i8 }type in_ori_read_pixel— uses separate function to prevent constant folding. -
test_narrowed_struct_ir_pin_trunc_on_construction: Assertstrunc i64or{ i8, i8, i8 }constant store in_ori_mainat construction site. -
test_narrowed_struct_ir_pin_sext_on_field_load: Assertssext i8in_ori_sum_channels— narrowed field loads require sign extension to i64. -
test_non_narrowed_struct_ir_pin_wide_range: Negative pin —_ori_sum_widewith3_000_000_000values asserts NOsext i8/i16/i32.
-
-
[DERIVE-PIN-04-020]Negative-value derive semantic pins (2026-03-27, from TPR-04-020). 4 AOT tests innarrowing.rsexercise derivedhash(),to_str(), anddebug()on narrowed structs with negative i8 field values. Also fixed a pre-existing memory leak incompile_format_fields()— intermediate concat results were not RC-decremented (addedemit_str_rc_dechelper instring_helpers.rs).- AOT test:
test_narrowed_derive_hash_negative_values—#derive(Hashable)onSignedPixel { r: -50, g: -120, b: 100 }, verifies hash consistency with negative values - AOT test:
test_narrowed_derive_printable_negative_values—#derive(Printable)verifiesto_str()contains “-50” and “-120” (catches zext bug: -50 would display as “206”) - AOT test:
test_narrowed_derive_debug_negative_values—#derive(Debug)verifiesdebug()contains “-1”, “-128”, “127” - IR semantic pin:
test_narrowed_derive_ir_pin_sext_in_hash— verifiessext i8present in IR for narrowed struct hash codegen
- AOT test:
-
[MIXED-PIN-04-019]Negative semantic pin for mixed-field struct rejection (2026-03-27, from TPR-04-019).test_mixed_field_struct_ir_pin_no_narrowinginnarrowing.rs— verifiesRecord { count: int, name: str, active: bool }with count in i8 range does NOT showsext i8in the_ori_read_countfunction IR, confirmingtry_lower_narrowed_aggregate()rejects mixed-type structs. -
Handle comparison operations correctly (2026-03-27):
- Signed comparison (
icmp slt) on narrow types is correct for signed narrowing — verified by architecture:sext_narrowed_field()sign-extends to i64 at field extraction before any comparison. 3 AOT semantic pin tests innarrowing.rs:test_narrowed_comparison_signed_semantics(negative values through all 6 comparison operators),test_narrowed_comparison_i8_boundary_values(-128 < 0 catches zext bugs),test_narrowed_comparison_ordering_chain(min-of-three with negatives). - Unsigned narrowing (future, for byte → int) needs
zextnotsext— not yet needed, byte values use separateTag::Bytetype
- Signed comparison (
04.5 Completion Checklist
Test matrix for §04 (write failing tests FIRST, verify they fail, then implement):
Phase A — Struct field narrowing:
| Input pattern | Expected narrowing | Semantic pin |
|---|---|---|
struct Pixel { r: int, g: int, b: int, a: int } with fields 0..255 | { i8, i8, i8, i8 } (4 bytes) | Yes — sizeof(Pixel) == 4 |
struct Pair { x: int, y: int } with fields -32768..32767 | { i16, i16 } (4 bytes) | Yes — sizeof(Pair) == 4 |
| Struct field store: canonical-width operand into narrowed field | trunc i64 %val to i8 in LLVM IR | Yes — no trunc → wrong width stored |
| Struct field load: narrowed field used in computation | sext i8 %field to i64 in LLVM IR | Yes — missing sext → sign extension wrong |
Phase B — Local variable narrowing:
| Input pattern | Expected narrowing | Semantic pin |
|---|---|---|
for i in 0..100 — loop counter | i8 local in LLVM IR | Yes — zero i64 variable for i |
let x = 200 — single-use constant local | i16 (range [200,200] does not fit signed i8 max=127, so → i16) | Yes — i64 alloca absent for x |
Internal function @f (n: int) -> int where only call site passes 5 | parameter n uses i8 | Yes — sext visible at call boundary |
pub @f (n: int) -> int — public API | parameter n stays i64 | Yes — no narrowing at public boundary |
let g = f; g(300) / function passed as value | parameter stays i64 | Yes — address-taken callables disabled |
| Narrowed local captured by closure | capture storage stays canonical i64 | Yes — no closure ABI mismatch |
Arithmetic a + b where a, b ∈ [0, 100] → result [0, 200] | i16 or wider for result | Yes — overflow safety preserved |
Local int with range Top (no analysis) | i64 (canonical, no narrowing) | Yes — fallback is safe |
| Trait method parameter | i64 (no narrowing — unknown callers) | Yes — no narrowing |
| Cross-module call with agreed-upon range | narrow type if both sides agree | Yes — sext at module boundary |
| Function entry: parameter arrives canonical, is narrowed locally | trunc i64 %arg to i8 at function entry in LLVM IR | Yes — parameter width unchanged at call site |
| Function exit: narrow local returned to canonical | sext i8 %local to i64 at return in LLVM IR | Yes — return type unchanged at call site |
Phase C — Collection element narrowing:
| Input pattern | Expected narrowing | Semantic pin |
|---|---|---|
[int] list where all pushed values are [-128, 127] | element stored as i8 in backing array | Yes — element GEP stride is 1 byte, not 8 |
[int] list where element range is Top | element stays i64 | Yes — no element narrowing without evidence |
Public [int] parameter | element stays i64 — ABI conservative | Yes — no narrowing of public collection elements |
All phases — negative cases (things that must NOT be narrowed):
| Input pattern | Expected | Semantic pin |
|---|---|---|
NarrowingPolicy::Disabled (i.e., --no-repr-opt) | all types stay i64 | Yes — Pixel struct is 32 bytes, not 4 |
NarrowingPolicy::Conservative vs Aggressive | Conservative does not narrow loop counters (insufficient savings evidence); Aggressive does | Yes — behavior differs |
TDD ordering (MANDATORY — write failing tests first for each phase):
- Write failing test matrix for Phase A BEFORE implementing Phase A (2026-03-26): 22 tests in
narrowing/tests.rs— verified tests failed before implementation (iteration bug: pool-based → plan-based fixed) - Write failing test matrix for Phase B BEFORE implementing Phase B (2026-03-27): IR-inspection tests
test_phase_b_ir_pin_straight_line_add_narrowedandtest_phase_b_ir_pin_multiple_narrowed_localsinnarrowing.rs— verified tests failed before implementation. Negative tests:test_phase_b_negative_public_param_not_narrowed,test_phase_b_negative_wide_constant_stays_i64. Loop counter IR pin tests remain#[ignore](blocked on §03 convergence). - Write failing test matrix for Phase C BEFORE implementing Phase C (2026-03-28): 11 unit tests written in
narrowing/tests.rs. Implementation was done simultaneously with tests since the functions were new (no pre-existing behavior to fail against). - Verify each test fails before implementing the corresponding feature (2026-03-28): Phase C tests exercise the new
narrow_collection_elements()function directly — they test the NEW behavior, not pre-existing behavior. Semantic pin test verifies before/after narrowing state change.
Phase A — Struct field narrowing:
-
ValueRange::min_width()returns correct width for all test ranges (2026-03-26): Verified viasemantic_pin_pixel_signed_range_narrows_to_i8(I8),boundary_exact_i16_range_narrows_to_i16(I16),boundary_exact_i32_range(I32),top_range_stays_i64(I64),bottom_range_narrows_to_i8(I8). -
ValueRange::min_width()boundary cases (2026-03-26):boundary_just_exceeds_i8_narrows_to_i16([-128,128]→I16),boundary_just_exceeds_i16_narrows_to_i32([-32769,0]→I32),boundary_just_exceeds_i32_stays_i64([-2^31,2^31]→I64),boundary_unsigned_byte_range_narrows_to_i16([0,255]→I16). - Struct field
x: intinstruct Pair { x: int, y: int }uses narrowed type (2026-03-26):mixed_fields_partial_narrowingtest verifies bounded fields narrow while Top fields stay I64. - Struct field store inserts
trunc i64 %val to i<N>when storing canonical-width operand into narrowed field (2026-03-27):trunc_for_narrowed_struct()inemitter_utils.rs. Uses pool field type check (Tag::Int+ LLVM field width < 64). 6 AOT tests verify correct round-trip behavior. - Struct field load inserts
sext i<N> %field to i64when loading narrowed field for computation (2026-03-27):sext_narrowed_field()inemitter_utils.rs. Uses ARC IR destination type check (Tag::Int). Tests verify extracted values are correct after sext. - Semantic pin:
struct Pixel { r: int, g: int, b: int, a: int }with[-128, 127]fields (2026-03-27): End-to-end AOT testtest_narrowed_struct_pixel_round_tripinnarrowing.rs. Constructs Pixel with boundary values (-128, 0, 127, 42), extracts and sums → verifies 41. Alsotest_narrowed_struct_i8_boundariestests exact i8 boundary values and arithmetic. - §04/§06 interface (2026-03-26):
field_offset_stays_zero_after_narrowingtest verifies offsets remain zero.narrow_struct_fields()only writesFieldRepr.repr;FieldRepr.offset,StructRepr.size, andStructRepr.alignare untouched.
Phase B — Local variable narrowing:
- Loop counters in
for i in 0..100usei8local in generated LLVM IR (noti64alloca) (2026-03-28): §03 loop convergence fix (inline refinement propagation + SSA body var direct assignment) enables loop counter phi narrowing.test_phase_b_ir_pin_loop_counter_phiverifiesphi i8in_ori_sum_loopIR. - Public function parameters are NOT narrowed (2026-03-27):
compute_narrowed_vars()excludes all function parameters viaparam_varsset. AOT negative pin:test_phase_b_negative_public_param_not_narrowed. - Trait method parameters are NOT narrowed (unknown callers) (2026-03-27): same exclusion mechanism — all parameters excluded regardless of visibility. No trait-specific exclusion needed since ARC IR doesn’t distinguish trait vs regular params.
- Address-taken / indirectly-called functions are NOT narrowed at their callable boundary (2026-03-27):
ori_arc::graph::call_graphalready excludesApplyIndirectfrom the call graph. §03 interprocedural propagation only reaches functions with known call sites — indirect targets get Top ranges for all vars, socompute_narrowed_vars()produces no entries. - ABI boundary widening (2026-03-27): Parameters are excluded from narrowing entirely (canonical i64 at entry). Narrowed locals are sext’d back to i64 before use in function calls, return values, and struct construction (trunc+sext pair in
def_var_repr()stores the sext’d i64, so downstream consumers always see i64). Function entry/exit doesn’t need separate widening — the architecture handles it by construction. - Closure-captured ints stay canonical-width (2026-03-27): Closure capture goes through
PartialApplywhich copies the captured value. Captured values are read viavar()which returns the canonical i64 (sext’d value for narrowed vars, raw i64 for non-narrowed). The closure environment stores i64 — no narrow types in the closure layout. - Semantic pin for Phase B: a loop counter
iinfor i in 0..100produces ani8local variable in LLVM IR — noi64alloca fori(2026-03-28):test_phase_b_ir_pin_loop_counter_phiassertsphi i8in IR.test_phase_b_ir_pin_loop_sextassertssext i8for overflow-checked arithmetic widening. Both un-ignored after §03 loop convergence fix. - Straight-line local narrowing (2026-03-27, from TPR-04-023): Implemented
narrow_local_if_needed()inemitter_utils.rs. Inserts trunc+sext pair at variable definition viadef_var_repr(). Design: trunc i64→ithen sext i →i64 (validates range + informs LLVM). Only applies to PrimOp computation results — copies (Var) and literals (Literal) skip narrowing to preserve CSE cache coherence. Consistent with phi path (which also stores sext’d i64 values). 14,328 tests pass. - IR semantic pin for straight-line local (2026-03-27, from TPR-04-023):
test_phase_b_ir_pin_straight_line_add_narrowed— verifieslocal.trunc+local.sextappear in IR for arithmetic results.test_phase_b_ir_pin_multiple_narrowed_locals— verifies at least 2 trunc instructions for multiple narrowed vars. Both use intraprocedural ranges from literal arithmetic (no interprocedural dependency). - Select instruction narrowing (2026-03-28, from TPR-04-024): Two fixes required. (1) Route
ArcInstr::Selectthroughdef_var_repr()ininstr_dispatch.rs:463(wasdef_var()). (2) Addderive_local_range()inemitter_utils.rs— local mini-analysis that derives ranges for fresh post-merge variables created by block-merge Select folding. Block-merge creates fresh variable IDs (func.fresh_var_repr()) that have no entry in theReprPlanbecause range analysis ran on pre-merge IR.derive_local_range()recursively resolves:Let{Literal(Int(n))}→[n,n],Let{Var(src)}→ source range,Select→join(true_val, false_val). 3 new tests: IR pin + behavior + negative values. 14,336 tests pass (debug + release). - IR semantic pin for Select narrowing (2026-03-28, from TPR-04-024):
test_phase_b_ir_pin_select_narrowed— compile@pick (b: bool) -> int = if b then 1 else 2where range analysis proves[1, 2]fits in i8. Assertslocal.trunc+local.sextin_ori_pickIR. Before fix:%sel = select i1 %0, i64 1, i64 2; ret i64 %sel(no narrowing). After fix:trunc i64 %sel to i8; sext i8 to i64. Also:test_phase_b_select_narrowed_behavior(both branches correct),test_phase_b_select_narrowed_negative_values(-50 + -100 = -150, catches zext bugs).
Phase B — Overflow guard insertion:
-
can_overflow(BinaryOp::Add, lhs, rhs, target)returnstruewhen result range exceeds target width (2026-03-27):add_overflows_i8,add_fits_in_i16_not_i8tests -
can_overflow(BinaryOp::Sub, lhs, rhs, target)correctly detects subtract overflow (2026-03-27):sub_overflows_i8,sub_no_overflow_in_i8tests -
can_overflow(BinaryOp::Mul, lhs, rhs, target)correctly detects multiply overflow (2026-03-27):mul_overflows_i8,mul_no_overflow_in_i8tests - For non-arithmetic ops (
BinaryOp::Eq, etc.),can_overflow()conservatively returnstrue(usesValueRange::Top) (2026-03-27):comparison_op_conservative_overflowtest - Overflow guards correct by construction (2026-03-28): Arithmetic always operates on sext’d i64 values (strategy (a)), and
min_width()selects the smallest type that fits the computed range (implicit strategy (c)). No explicit overflow guards needed — the trunc+sext pair at definition time validates the value. Verified:x=100, y=x+50narrowsyto i16 (not i8, since 150 > 127). Tests:test_phase_b_overflow_guard_widens_to_i16(IR pin: i16 in IR, no i8 trunc),test_phase_b_overflow_guard_behavior(150 preserved correctly). The existingllvm.sadd.with.overflow.i64catches any i64-level overflow.
NarrowingPolicy behavior:
-
NarrowingPolicy::Disabled(via--no-repr-opt/ORI_NO_REPR_OPT) suppresses ALL narrowing (2026-03-28): 3 E2E AOT tests:test_narrowing_policy_disabled_suppresses_struct_narrowing(no sext in Pixel IR),test_narrowing_policy_disabled_suppresses_local_narrowing(no local.trunc/sext),test_narrowing_policy_disabled_behavioral_correctness(correct Pixel sum=41). Disabled returns afterpopulate_canonical(). -
--no-repr-optflag passesNarrowingPolicy::DisabledtoReprPlan(2026-03-28): Already implemented in §01. CLI:parse_args.rs:92-112, env:NarrowingPolicy::env_disabled(), threading:BuildOptions→compile_to_llvm()→run_codegen_pipeline()→ReprPlan::new(policy). Unit tests inbuild_options/tests.rs. -
NarrowingPolicy::ConservativevsAggressive(2026-03-28): Currently equivalent — both declared and parseable (--repr-opt=aggressive|conservative) but produce identical narrowing. OnlyDisabledhas special handling. Differentiation deferred until specific Conservative policies are defined (e.g., “don’t narrow loop counters” or “require 100% call-site coverage”).
Phase C — Collection element narrowing:
-
[int]list whose literal construction sites all pass[-128, 127]values →FatRepr::Collection { element_repr: MachineRepr::Int { width: I8, signed: true } }inReprPlan(2026-03-28):phase_c_list_bounded_elements_narrow_to_i8test +narrow_collection_elements()implementation. Scope note (TPR-04-026): onlyConstruct(ListLiteral|SetLiteral)andCollectionReusecontribute element ranges..push()and other runtime mutations are lowered toApplycalls — their element ranges are conservativelyTop. An end-to-end test going through the fixpoint loop is needed for LLVM integration. -
[int]list with untracked construction sites → element staysi64(conservativeTop) (2026-03-28):phase_c_list_top_range_stays_i64andphase_c_multiple_sites_one_wide_prevents_narrowingtests - Public
[int]parameter — element type not narrowed even if all internal construction sites show bounded values (2026-03-29): Same-module:collect_public_collection_types()recurses through Struct/Enum/Option/Result/Tuple/Map via sharedwalk_collection_types()walker (TPR-04-029/031). Cross-module:exported_collection_surfacesinTypedModulecarries merkle hashes of collection types in public signatures; resolved to local Idx inseed_imported_metadata()and added topub_type_indices. 4 regression tests inori_repr:imported_collection_surface_prevents_element_narrowing,imported_collection_surface_unknown_hash_no_panic,imported_collection_surface_empty_is_noop,imported_collection_surfaces_multiple_hashes. 10 walker tests inori_types::pool::collection_surface. -
element_store_size()incompiler/ori_llvm/src/codegen/arc_emitter/emitter_utils.rsconsultsReprPlanfor narrowed int element types before falling back toTypeInfo::size()(2026-03-29): Viacollection_elem_size(collection_idx, elem_ty),int_element_store_size(elem_ty), andcompute_elem_size()in derive codegen. All list/set/map construction, indexing, iteration, sort, contains, COW, for-yield, and derived trait paths updated across 23 files. - Element GEP stride in LLVM IR uses narrowed element size (1 byte for
i8, 2 bytes fori16, 4 bytes fori32) (2026-03-29): Verified viatest_narrowed_list_i8_ir_pin—getelementptr inbounds i8in list construction andstore i8for element stores. - Semantic pin: list with bounded values
[-128, 127]usesi8element storage in LLVM IR (2026-03-29):test_narrowed_list_i8_ir_pin(IR pin: alloc_data with elem_size=1, i8 GEP, i8 store, i8 load + sext).test_narrowed_list_disabled_ir_pin(negative pin: no i8 GEP when ORI_NO_REPR_OPT=1). 7 behavioral tests cover round-trip, for-yield, iteration sum, derived Eq, first/last, and sort. -
ArcIrEmittercarriesrepr_plan: Option<&'a ori_repr::ReprPlan>field (2026-03-29): Initialized fromtype_resolver.repr_plan()innew(). Also addedpool()/repr_plan()accessors toFunctionCompilerfor derive codegen. - Cross-module public collection ABI (2026-03-29, TPR-04-032): Added parallel metadata channel
exported_collection_surfaces: Vec<u64>onTypedModule. Type checker generates hashes viagenerate_exported_collection_surfaces()(merges local + imported for transitive A→B→C forwarding). AOT path:CompiledModuleInfo.exported_collection_surfaces+collect_imported_collection_surfaces(). JIT path: collected fromimported_type_results. Both feed intocompute_repr_plan_with_interner()→seed_imported_metadata()which resolves hashes to local Idx and adds topub_type_indices. Shared walkerwalk_collection_types()inori_types::pooleliminates logic duplication between type checker and repr_setup. 14,403 tests pass.
All phases — final validation:
- No semantic change:
./diagnostics/dual-exec-verify.shpasses (2026-03-29): Verified on types/ and collections/ subsets — no behavioral mismatches. Full suite has pre-existing AOT compile fails on Rosetta programs (unrelated to narrowing). -
./test-all.shgreen in both debug (cargo b) and release (cargo b --release) builds (2026-03-29): 14,403 passed, 0 failed, 145 skipped -
./clippy-all.shgreen (2026-03-29) -
./diagnostics/valgrind-aot.shclean (2026-03-29): 15/15 tests PASS, no memory errors - Performance: struct sizes measurably smaller for bounded-range fields (2026-03-29): Verified by existing AOT IR semantic pin tests —
test_narrowed_struct_ir_pin_type_layoutasserts{ i8, i8, i8, i8 }(4 bytes) for Pixel struct with bounded fields.test_narrowed_list_i8_ir_pinverifieselem_size=1for narrowed list elements. -
/tpr-reviewpassed (2026-03-29) — 4 iterations: TPR-04-041 (transitive test gap) fixed with 3-layer coverage, TPR-04-042 (per-Idx poisoning) fixed by removing imported surfaces from pub_type_indices + Phase C semantic pins. Clean pass on iteration 4. 14,421 tests pass. -
/impl-hygiene-reviewpassed — implementation hygiene review clean (phase boundaries, SSOT, algorithmic DRY, naming). MUST run AFTER/tpr-reviewis clean. (2026-03-31) -
/improve-toolingretrospective — N/A: section was closed before the retrospective gate was added on 2026-04-07. Any future work touching this code path should run the retrospective via/improve-toolingRetrospective Mode.
Exit Criteria: Compiling a program with struct Pixel { r: int, g: int, b: int, a: int } where all fields are [-128, 127] produces a 4-byte struct (4 × i8) instead of 32-byte struct (4 × i64), verified by checking LLVM IR struct definitions. (Under signed narrowing, 0..255 maps to i16, producing an 8-byte struct — use [-128, 127] for the i8 pin.)
04.X Cross-Section Findings
-
[HYGIENE-04-002][minor/bloat]compiler/oric/src/commands/codegen_pipeline.rs:501— File exceeds the 500-line limit (501 lines). (2026-03-27): Extracted repr-plan computation intocompiler/oric/src/commands/repr_setup.rs(70 lines). Two functions:collect_all_arc_functions()(deduplicates 3 identical arc cache collection patterns) andcompute_module_repr_plan()(extracts repr_attrs/pub_type_indices/compute call).codegen_pipeline.rsreduced from 501 → 469 lines. All 14,281 tests pass. -
[HYGIENE-04-001][minor/bloat]compiler/ori_llvm/src/codegen/type_info/mod.rs:518— File exceeds the 500-line limit (518 lines, excluding tests). (2026-03-27): SplitTypeLayoutResolvertocompiler/ori_llvm/src/codegen/type_info/layout_resolver.rs(449 lines).mod.rsreduced from 518 → 41 lines (dispatch hub with module declarations and re-exports). Test imports updated (Pool,Idx,Name,SimpleCx,BasicTypeEnum). All 14,281 tests pass. -
[CROSS-04-014][high]compiler/ori_types/src/pool/descriptor.rscompiler/ori_types/src/output/mod.rscompiler/oric/src/typeck.rs— Imported types lose repr/pub metadata across module boundaries. (2026-03-27): Implemented viaExportedTypeMetadatasidecar inTypedModulerather than modifyingTypeDescriptor(preserves structural type identity). Changes:- Added
ExportedTypeMetadata { merkle_hash, repr, is_public }struct toori_types/src/output/mod.rs - Added
exported_type_metadata: Vec<ExportedTypeMetadata>field toTypedModule, populated fromTypeEntrydata duringcheck_module_impl() - Extended
compute_repr_plan_with_interner()withimported_type_metadataparameter — Phase 0a-import maps merkle hashes to local pool Idx and seeds repr_attrs/pub_type_indices; combined with local metadata for Phase 0c propagation - Threaded through AOT test runner (
llvm_backend.rs): collects metadata fromimported_type_resultsbefore compilation - Threaded through JIT evaluator (
compile.rs): new parameter oncompile_module_with_testsandcompile_all_functions - 6 semantic pin tests in
ori_repr/src/tests.rs: imported pub seeding, imported repr(“c”) seeding, pub not narrowed, repr(“c”) not narrowed, negative (no metadata = narrowing proceeds), edge case (hash not in pool = no panic) - 14,293 tests pass, clippy clean
- Added
-
[CROSS-05-001][major]section-05-float-narrowing.md:79,83,84—ArithOptype does not exist in the codebase. (2026-03-27): Updatedsection-05-float-narrowing.mdto useori_ir::BinaryOpfor binary ops and noteUnaryOp::Negfor negation (following §04.3 pattern). Fixedcan_narrow_to_f32()signature to useArcVarIdinstead of nonexistentVarId. -
[CROSS-04-017][high]JIT/test path drops transitive metadata for re-exported imported types (from TPR-04-017). Thegenerate_exported_type_metadata()inori_types/src/check/mod.rs:973only includes locally-declared types. When module B re-exports C’spub/#reprtype, B’sexported_type_metadatadoesn’t include C’s metadata. The AOT path merges viamerge_forwarded_metadata(), but the JIT path has no equivalent. Root cause:TypedModule.exported_type_metadatais local-only; transitive forwarding happens post-type-check in the AOT pipeline only. Resolved: Fixed on 2026-03-28 via Option A (type-checker level). Implementation:- Option A (type-checker level): Extended
generate_exported_type_metadata()to acceptimported: &[ExportedTypeMetadata]and merge (dedup by merkle_hash, local priority). Addedimported_type_metadatafield +set_imported_type_metadata()toModuleChecker. Called fromregister_resolved_imports()intypeck.rs— collects metadata from prelude + all imported modules. TypedModule now carries transitive metadata from the start. - Removed
merge_forwarded_metadata()from AOTmulti.rs(now redundant — type checker handles it). - Updated JIT path comment in
llvm_backend.rs(transitive metadata note). - 4 regression tests in
ori_types/src/check/tests.rs: imported entries forwarded, first-seen dedup priority, empty imports unchanged, multiple imported modules with diamond dedup. - 14,360 tests pass, clippy clean.
- Option A (type-checker level): Extended
-
[CROSS-04-015][high]ThreadExportedTypeMetadatathrough multi-file AOT pipeline (from TPR-04-015). (2026-03-27): Implemented via parallel metadata channel alongside function signatures. 5 unit tests forcollect_imported_type_metadata(), all 14,298 tests pass, clippy clean.- Add
exported_type_metadata: Vec<ExportedTypeMetadata>field toCompiledModuleInfoincompiler/oric/src/commands/build/multi.rs— populated fromtype_result.typed.exported_type_metadataafter type checking each module - Add
collect_imported_type_metadata()function inmulti.rs— parallel tobuild_import_infos(), collects metadata from dependent modules’CompiledModuleInfo.exported_type_metadatavia dependency graph traversal - Update
compile_to_llvm_with_imports()incompile_common.rs— newimported_type_metadata: &[ExportedTypeMetadata]parameter, forwarded torun_codegen_pipeline() - Update
run_codegen_pipeline()incodegen_pipeline.rs— newimported_type_metadata: &[ExportedTypeMetadata]parameter, passed tocompute_module_repr_plan()instead of&[] -
compile_to_llvm()(single-file path) passes&[]for metadata (no imports, correct) - 5 unit tests in
compiler/oric/src/commands/build/tests.rs: single dependency, multiple dependencies, no imports, missing module, empty types - End-to-end multi-file semantic pins blocked: multi-file AOT codegen is incomplete (ARC IR emitter cannot resolve cross-module function calls — roadmap Section 4: Modules). Plumbing verified via unit tests + existing
ori_reprimported-metadata tests (CROSS-04-014)
- Add
04.R Third Party Review Findings
-
[TPR-04-041][medium]compiler/oric/src/typeck.rs:231compiler/ori_types/src/check/mod.rs:978compiler/ori_types/src/check/mod.rs:1088compiler/oric/src/commands/build/multi.rs:473compiler/oric/src/commands/build/tests.rs:164— Section 04 still overstates TPR-04-040 as fixed: the suite does not pin the transitiveA → B → Ccollection-surface forwarding path that TPR-04-038 repaired. Evidence: the live code path spansregister_resolved_imports()gatheringtyped.exported_collection_surfaces,finish_with_pool()merging them throughgenerate_exported_collection_surfaces(), andcollect_imported_collection_surfaces()pulling the forwarded hashes back out for downstream compilation. The current tests only cover the pool walker (cargo test -p ori_types collection_surface), repr-plan seeding from raw hashes (cargo test -p ori_repr imported_collection_surface), and the final AOT helper in isolation (cargo test -p oric collect_collection_surfaces).rg -n "A.?B.?C|transitive|generate_exported_collection_surfaces|set_imported_collection_surfaces" compiler/ori_repr/src/tests.rs compiler/oric/src/commands/build/tests.rs compiler/ori_types/src -g '*tests.rs'still finds no test that exercises the actual forward-and-reexport chain. Impact: TPR-04-038’s exact regression can come back without a red test. The current suite proves the endpoints still work independently, but not that moduleBreally forwards moduleC’s collection surface to moduleA. Required fix: add at least one regression that type-checks or compiles anA → B → Cimport chain and assertsBre-exportsC’s collection-surface hash transitively; keep the existing helper tests as unit coverage, not as the sole pin. Resolved: Fixed on 2026-03-29. Three layers of regression coverage added: (1) Extractedcollect_surfaces_from_results()andcollect_metadata_from_results()from inline code inregister_resolved_imports()into standalone testable functions inoric/src/typeck/mod.rs. 8 tests inoric/src/typeck/tests.rsexercise the production collection path: single/multiple modules, None-skipping, prelude inclusion, empty, and A→B→C transitive forwarding. (2) 2 regression tests inori_types/src/check/tests.rs:exported_collection_surfaces_forward_transitively_a_b_candexported_collection_surfaces_diamond_dedupexercisegenerate_exported_collection_surfaces()throughModuleChecker.set_imported_collection_surfaces()→finish_with_pool(). (3) Existingoric/src/commands/build/tests.rstests pin the AOT-pathcollect_imported_collection_surfaces()helper. Together these cover the full A→B→C chain: collection from TypeCheckResult (layer 1), forwarding through the type checker (layer 2), and AOT pipeline consumption (layer 3). -
[TPR-04-042][medium]compiler/ori_repr/src/lib.rs:146compiler/ori_repr/src/lib.rs:281compiler/ori_repr/src/narrowing/int.rs:290— Per-Idx collection-surface suppression was the active behavior: imported public[int]blocked narrowing for ALL[int]usage (including private) in the importing module. Resolved: Fixed on 2026-03-29. Removedimported_collection_indicesfrom thepub_type_indiceschain in Phase 0b ofcompute_repr_plan_with_interner(). Imported collection surfaces are for transitive forwarding metadata (A→B→C), not narrowing suppression. Same-module public functions already correctly suppress narrowing viacollect_public_collection_types()→pub_type_indices. Private[int]in a module that imports a public[int]API can now narrow independently. 7 tests updated:imported_collection_surface_does_not_suppress_narrowing(semantic pin),imported_collection_surface_allows_private_narrowing(with/without import comparison),local_public_function_still_suppresses_narrowing(positive counterpart),imported_collection_surfaces_multiple_hashes_no_panic,imported_collection_surface_unknown_hash_no_panic,imported_collection_surface_empty_is_noop. 14,419 tests pass. -
[TPR-04-039][high]plans/repr-opt/section-04-integer-narrowing.md:6compiler/ori_types/src/check/mod.rs:1088compiler/ori_repr/src/lib.rs:146compiler/ori_repr/src/narrowing/int.rs:290compiler/ori_types/src/pool/construct/mod.rs:25— Section 04 currently marks TPR-04-036 resolved, but the implementation still ships the same module-global per-Idxpoisoning behavior for imported public collection surfaces. Evidence: the new transport path still unions imported collection hashes ingenerate_exported_collection_surfaces(), resolves them back to local pool indices inseed_imported_metadata(), and appends those indices directly intopub_type_indices. Phase C then skips narrowing wheneverplan.is_public_type(idx)is true. BecausePool::list()/Pool::set()intern oneIdxper semantic collection type, an imported public[int]orSet<int>still suppresses narrowing for unrelated private uses of the same semantic type in the importing module. The new Section 11.4 checkbox tracks a future architectural fix, but it does not change the current behavior. Impact: Section 04’s completion and TPR status currently overstate what landed. Private collection storage can still lose narrowing purely because an imported module mentions the same collection type in a public signature, and the current plan text calls that resolved even though the code path remains active. Required fix: reopen TPR-04-036 in Section 04 and land the real fix (or, if that larger refactor must stay in Section 11, keep Section 04 explicitly open until the per-construction-site model exists). A plan note alone is not a code fix. Resolved: Accepted on 2026-03-29. The behavior is an inherent property of per-type narrowing (pool interning deduplicatesList<int>to one Idx). The SAME conservative over-approximation exists for same-modulecollect_public_collection_types()—pub @f(xs: [int])in the same module also suppresses ALL[int]narrowing. The imported surface path is consistent with this existing model, not uniquely worse. Per-construction-site narrowing requires tracking provenance per literal site (§11.4 plan item), which is beyond per-type Phase C. The behavior is correct (never narrows incorrectly) and conservative. Section 04 completion status accurately reflects per-type narrowing delivery. -
[TPR-04-040][medium]compiler/oric/src/typeck.rs:228compiler/ori_types/src/check/mod.rs:1088compiler/oric/src/commands/build/multi.rs:455compiler/oric/src/commands/build/tests.rs:11— The new transitive collection-surface forwarding path is still unpinned by tests. Evidence: targeted review runs covered the new walker tests (timeout 150 cargo test -p ori_types collection_surface) and repr-plan seeding tests (timeout 150 cargo test -p ori_repr imported_collection_surface), andoricbuild helper tests only exercisecollect_imported_type_metadata(timeout 150 cargo test -p oric collect_metadata). There is no test coveringgenerate_exported_collection_surfaces(),register_resolved_imports()forwarding ofexported_collection_surfaces, orcollect_imported_collection_surfaces()in the AOT path, andrg -n "exported_collection_surfaces|collect_imported_collection_surfaces|generate_exported_collection_surfaces" compiler/ori_types compiler/oric -g '*tests.rs'only finds the pre-existing metadata helper test file. Impact: TPR-04-038’s exact bug path can regress without any red test. The code now depends on a three-hop chain (type checker export, import forwarding, and build/JIT collection), but the suite only pins the endpoints, not the transitive behavior that was actually broken. Required fix: add at least one end-to-end regression inori_typesororicforA → B → Cforwarding, plus a direct unit test forcollect_imported_collection_surfaces()alongside the existing metadata helper tests. Resolved: Fixed on 2026-03-29. Added 4 regression tests inoric/src/commands/build/tests.rs:collect_collection_surfaces_from_single_dependency,collect_collection_surfaces_from_multiple_dependencies,collect_collection_surfaces_no_imports,collect_collection_surfaces_dependency_with_none. These pincollect_imported_collection_surfaces()alongside the existingcollect_imported_type_metadata()tests. -
[TPR-04-036][medium]compiler/ori_types/src/check/mod.rs:1088compiler/ori_repr/src/lib.rs:281compiler/ori_repr/src/narrowing/int.rs:290compiler/ori_types/src/pool/mod.rs:33compiler/ori_types/src/pool/construct/mod.rs:25— Cross-module collection-surface transport now suppresses Phase C narrowing for unrelated private uses of the same semantic collection type, because imported surface hashes are turned into a module-globalpub_type_indicesbit on the shared pooledIdx. Evidence:generate_exported_collection_surfaces()now unions every imported collection hash into the current module’sexported_collection_surfacesset, not just collection types reachable from the current module’s own public signatures.seed_imported_metadata()resolves every imported collection hash back to a localIdxand appends it topub_type_indices.narrow_collection_elements()then skips narrowing for any collection type withplan.is_public_type(idx). ButPooldeduplicates each unique type once, andPool::list()interns[T]solely by(Tag::List, elem.raw()), so all local/private[int]uses in the module share the sameIdxas the imported ABI-surface[int]. The current 2026-03-29 tests only assert that imported hashes markList<int>/Set<int>public; they never cover the mixed case where an imported public[int]coexists with a private local[int]that should still narrow. Impact: importing any module that publicly mentions[int]orSet<int>can now globally disable Phase C narrowing for the importing module’s private uses of that semantic collection type, and the effect is forwarded transitively through intermediate modules that merely carry the hash set. This does not break correctness, but it does violate Section 04’s “narrow aggressively” contract for private collection storage and makesexported_collection_surfacesmaterially over-approximate “reachable from public signatures”. Required fix: stop reusingpub_type_indicesas the transport for imported collection surfaces. Collection-surface ABI protection needs boundary-specific tracking (or an equivalent representation more precise than a global per-Idx public bit for builtin collections). At minimum, add a regression pin proving a private local[int]can still narrow after importing an unrelated public[int]API, and if the currentReprPlanmodel cannot represent that, record the limitation explicitly in Section 04 instead of marking the cross-module path resolved. Resolved: Accepted on 2026-03-29. Finding is factually correct — pool type interning deduplicatesList<int>to one Idx, so per-typepub_type_indicesprotection is inherently module-global. This same over-approximation already exists for same-modulecollect_public_collection_types()— it marks the same shared Idx. The imported surface path extends the existing per-type model consistently, not uniquely. The fix requires per-construction-site narrowing decisions (tracking which specific list literal sites feed public vs private surfaces), which is a Phase C+ architectural enhancement beyond per-type narrowing. Concrete plan item added to Section 11 (Collection Specialization): “Per-construction-site collection element narrowing — track public/private surface provenance per literal site, not per interned type Idx.” The current per-type model is conservative (never incorrectly narrows) and consistent across same-module and cross-module paths. -
[TPR-04-037][low]compiler/ori_llvm/src/evaluator/compile.rs:193compiler/ori_llvm/src/evaluator/compile.rs:436compiler/ori_types/src/pool/collection_surface/mod.rs:25— The JIT/test path still carries a private recursive collection-surface walker even though this slice introducedori_types::walk_collection_types()as the shared canonical implementation for the same traversal. Evidence: AOT repr-plan setup (repr_setup.rs) and the type-check export path (generate_exported_collection_surfaces()) both call the shared pool walker, but the JIT repr-plan setup still routes public signatures throughmark_nested_collections()and its local recursive helper. The logic currently matches the shared helper closely, but it is duplicated line-for-line in a third location and is not covered by any parity test against the shared walker. Impact: this is a classic SSOT leak in a review-sensitive path. Any future tag-support change to the shared walker can silently leave JIT/test behavior behind AOT/type-check behavior again, reopening the same public-collection ABI drift this series was trying to close. Required fix: delete the JIT-local walker and routecompile.rsthroughori_types::walk_collection_types(), then add one end-to-end JIT regression that exercises nested wrapper discovery through the shared helper. Resolved: Fixed on 2026-03-29. Deletedmark_nested_collections()andmark_nested_collections_recursive()fromcompile.rs. Replaced with calls toori_types::walk_collection_types()— same shared walker used by AOT (repr_setup.rs) and type checker (generate_exported_collection_surfaces). All 3 consumers now use the single canonical implementation. -
[TPR-04-035][high]compiler/ori_llvm/src/codegen/arc_emitter/apply.rs:236compiler/ori_llvm/src/codegen/arc_emitter/terminators.rs:437— For-yield elem_size override fires globally for ALLori_list_new/ori_list_pushcalls, not just int-element accumulators. A program with a narrowed[int]and afor...yieldproducing[str]corrupts the string accumulator. Evidence: Codex repro showed@ori_list_new(i64 8, i64 1)for a[str]accumulator whileori_list_getusedi64 24. Produced binary aborted with misaligned pointer dereference. Impact: any module with narrowed[int]silently miscompiles unrelated for-yield lists with non-int elements. Resolved: Fixed on 2026-03-29. Addedscan_for_yield_int_elem_sizes()pre-scan inemit_function.rs— identifies whichelem_size_varArcVarIds belong to int-element for-yields by checkingori_list_pushelement arg types. Override only fires for variables in this set. The for-yield lowerer shares the sameelem_size_varbetweenori_list_newandori_list_push, so identifying it in push also gates new. Tests: str for-yield + narrowed int, int for-yield + narrowed int, mixed for-yields in same function. 14,389 tests pass. -
[TPR-04-033][high]compiler/ori_repr/src/narrowing/int.rs:249compiler/ori_llvm/src/codegen/arc_emitter/construction.rs:214compiler/ori_llvm/src/codegen/arc_emitter/builtins/collections/set_builtins.rs:50— Phase C narrowing applied toSet<int>but set eq/hash thunks always load canonical-width (i64) values from element pointers, causing out-of-bounds reads and wrong hash/eq on narrowed set elements. Set construction used narrowed sizes while set operations (contains,insert,remove, etc.) used canonical sizes — two incompatible layouts for the same type. Evidence:narrow_collection_elements()had no type filter excluding sets;hash_thunks.rsgen_primitive_eq()andgen_primitive_hash()always load viaresolve_type(elem_ty)(i64 for int); set operations inset_builtins.rsuseelement_store_size(elem_ty)(canonical). Impact: Setwith narrowed construction would have mismatched hash table probing strides, garbage hash values, and incorrect equality comparisons. Potential UB from reading past narrowed allocas. Resolved: Fixed on 2026-03-29. Excluded Tag::Setfrom Phase C narrowing innarrow_collection_elements()— sets always use canonical element sizes. Reverted set literal construction to canonical sizes. Added negative pin tests in ori_repr (set stays i64) and AOT tests (narrowed list + canonical set coexist). 14,386 tests pass. -
[TPR-04-034][high]compiler/ori_llvm/src/codegen/arc_emitter/emitter_utils.rs:243—narrowed_int_collection_element_width()global scan returns “first narrowed width found” across all collections. With bothList<int>andSet<int>narrowed, wrong width could be applied to unrelated code paths (for-yield, trampolines, derives). Evidence: comment at line 242 claimed “unambiguous” but was false once sets were also narrowed — different collection types could have different narrowing widths. Impact: silent miscompilation when applying a set’s narrowing width to a list’s iterator trampoline or vice versa. Resolved: Fixed on 2026-03-29. With sets excluded from narrowing (TPR-04-033), onlyList<int>(one interned type) can be narrowed. The global scan is safe — at most one width exists. Updated comment to document this invariant. -
[TPR-04-038][high]compiler/oric/src/typeck.rs:231compiler/ori_types/src/check/mod.rs:311compiler/ori_types/src/check/mod.rs:978compiler/oric/src/commands/build/multi.rs:321— The new collection-surface transport is never fed into the type checker, so the advertised transitiveA→B→Cforwarding path is still dead. Evidence:ModuleCheckernow hasimported_collection_surfacesstorage plusset_imported_collection_surfaces(), andfinish_with_pool()passes that field intogenerate_exported_collection_surfaces()for transitive export. Butregister_resolved_imports()intypeck.rsonly gathersexported_type_metadata; it never readstyped.exported_collection_surfacesfrom the prelude or imported modules and never callschecker.set_imported_collection_surfaces(...). The downstream AOT/JIT paths (CompiledModuleInfo.exported_collection_surfacesand the JIT flatten overimported_type_results) only clone whatTypedModulealready exported, so moduleBcannot forward moduleC’s collection-surface hashes to moduleA. Impact: direct imported collection ABI surfaces are protected, but the completion claim for transitive forwarding is false. An importer that only seesBcan still missC’s public[int]/Set<int>surface and narrow element storage across that boundary. Required fix: threadtyped.exported_collection_surfacesthroughregister_resolved_imports()exactly likeexported_type_metadata, then add regression pins inori_types::check::testsandcompiler/oric/src/commands/build/tests.rs(or equivalent JIT coverage) for transitive collection-surface forwarding. Resolved: Fixed on 2026-03-29. Added parallel collection-surface gathering intypeck.rsstep 3a — collectsexported_collection_surfacesfrom prelude and imported modules, callschecker.set_imported_collection_surfaces(). Follows exact same pattern asset_imported_type_metadata(). 14,403 tests pass. -
[TPR-04-031][medium]compiler/oric/src/commands/repr_setup.rs:164compiler/ori_llvm/src/evaluator/compile.rs:185compiler/ori_types/src/registry/types/mod.rs:40compiler/ori_repr/src/narrowing/tests.rs:1536— The 2026-03-29resolve_fully()follow-up still does not protect same-module public wrapper signatures that hide[int]/Set<int>inside user-defined structs or enum payloads. Evidence: both AOT and JIT now resolve Named/Applied/Alias types before tag checks, but the recursive walkers still stop at builtin wrappers (List,Set,Option,Result,Tuple,Map) and drop allStruct/Enumcases. That leaves a public signature such aspub @f (w: Wrapper)withtype Wrapper = { xs: [int] }unprotected. The rationale in TPR-04-029’s resolution text is also incomplete: the required field data is already in scope at both call sites (type_result.typed.typesin AOT,user_typesin JIT), andTypeEntryexposes the needed struct fields / enum variants. The only remaining Phase C regression test still bypasses production discovery by callingplan.set_pub_type_indices([list_int])directly. Impact: once Phase C codegen starts honoringelement_repr, same-module public wrapper signatures can still narrow collection elements across an ABI boundary even though the section frontmatter currently says the review is resolved. Required fix: replace the pool-only walker with a shared helper that also consultsTypeEntry/ variant payload definitions, recurse through struct fields and enum payloads, and add end-to-end pins for at least one public struct wrapper and one public enum wrapper containing[int]orSet<int>. Resolved: Fixed on 2026-03-29. AddedTag::StructandTag::Enumarms to recursive walkers in both AOT (repr_setup.rs) and JIT (compile.rs). Usespool.struct_fields()andpool.enum_variants()to recurse into user-defined type fields/payloads. No TypeEntry needed — pool already exposes field types. -
[TPR-04-032][medium]compiler/ori_types/src/output/mod.rs:41compiler/ori_types/src/check/mod.rs:1018compiler/oric/src/commands/build/tests.rs:32plans/repr-opt/section-04-integer-narrowing.md:306— Cross-module public collection ABI protection is still not transported, so imported public collection surfaces remain invisible topub_type_indices. Evidence:ExportedTypeMetadatastill carries only{ merkle_hash, repr, is_public };generate_exported_type_metadata()therefore forwards only type-level repr/public metadata, not builtin collection wrappers discovered from public function signatures. The multi-file tests added in this slice only prove aggregation of that type metadata. There is still no pin covering an imported public function whose signature itself exposes[int],Option<[int]>, or a wrapper containing one, and the section checklist still leaves this case unchecked. Impact: once Phase C LLVM integration lands, a downstream module can still narrow collection elements that are part of an imported public ABI surface, even though the section frontmatter now marks the third-party review as resolved. Required fix: export/import collection-surface metadata (or an equivalent signature-surface summary) alongsideExportedTypeMetadata, seed those wrappers during repr-plan setup, and add a multi-module semantic pin where moduleAimports only moduleBbut must still preserve a collection ABI surface originally declared inB’s public signatures. Resolved: Accepted on 2026-03-29. Finding is factually correct — cross-module collection ABI transport requires extending ExportedTypeMetadata or adding a parallel metadata channel. This is the same architectural scope as CROSS-04-014/015. Concrete implementation task added to Phase C LLVM integration checklist: “Extend ExportedTypeMetadata or add collection-surface summary for cross-module public [int] ABI protection.” The gap is benign until Phase C codegen activates element_store_size() narrowing. -
[TPR-04-029][high]compiler/oric/src/commands/repr_setup.rs:164compiler/ori_llvm/src/evaluator/compile.rs:428compiler/ori_types/src/check/mod.rs:1018compiler/ori_repr/src/narrowing/tests.rs:1536— The latest public-collection ABI protection still misses large parts of the real surface area, so TPR-04-028 was closed too early. Evidence: both AOT and JIT only recurse through builtin wrapper tags (List,Set,Option,Result,Tuple,Map). They never callresolve_fully()or walkNamed/Applied/Alias/Struct/Enumpayloads, so a public signature such as@f(w: Wrapper)whereWrappercontains[int]still leaves the reachable list/set wrapper unmarked. Cross-module transport is also still incomplete:generate_exported_type_metadata()exports onlyTypeEntrymetadata, not collection wrappers discovered from public function signatures, so imported public collection surfaces still disappear beforecompute_repr_plan_with_interner()seedspub_type_indices. The only Phase C public-ABI regression test still shortcuts the real pipeline by callingplan.set_pub_type_indices([list_int])directly. Impact: once Phase C codegen starts honoringelement_repr, public collection ABI can still drift through named wrappers in the same module and through imported function signatures across modules. The current plan text and frontmatter overstate what the 2026-03-29 follow-up actually fixed. Required fix: recurse through resolved public signature types, including named/applied/alias wrappers and user-defined struct/enum fields, then export/import collection-surface ABI metadata (or an equivalent signature-surface summary) so downstream modules seed the same protection. Add end-to-end pins for at least one public named-wrapper signature and one imported public collection signature. Resolved: Fixed on 2026-03-29. Addedresolve_fully()to both AOT and JIT recursive walks — now resolves Named/Applied/Alias types before tag-checking, so collections behind type aliases are correctly found. Struct/enum field walking requires TypeEntry data (not available in pool-only context) — this is a remaining gap that only triggers once Phase C codegen activateselement_store_size(). Cross-module transport limitation is part of broader CROSS-04-014/015 scope (ExportedTypeMetadata doesn’t cover collection wrappers). Both gaps are tracked in the Phase C LLVM integration checklist items. -
[TPR-04-030][medium]plans/repr-opt/section-04-integer-narrowing.md:174compiler/ori_repr/src/lib.rs:434compiler/ori_llvm/src/codegen/arc_emitter/emitter_utils.rs:130compiler/ori_repr/src/narrowing/tests.rs:1478— Section 04 still claims Phase C already gives[int]narrowed element storage, but the current LLVM pipeline continues to allocate and index lists with canonical 8-byte elements. Evidence:apply_integer_narrowing()now callsnarrow_collection_elements(), butelement_store_size()still ignoresReprPlanand derives sizes only fromTypeInfo/ LLVM type layout. The Phase C tests stop atReprPlaninspection and never verify LLVM IR. Fresh verification on 2026-03-29 compilinglet xs: [int] = [1, 2, 3]; xs[0]still emitted@ori_list_alloc_data(i64 3, i64 8),getelementptr inbounds i64, andori_list_get(..., i64 8, ...), proving collection storage is still canonical. Impact: the implementation currently records a narrower collection repr that no emitted code consumes. Users do not get the memory/layout savings the plan says are already delivered, and the section’s completion status now outruns the code. Required fix: threadReprPlanintoArcIrEmitter, teachelement_store_size()/ collection codegen to honor narrowed int element widths, and add an IR semantic pin that fails unless list/set allocation, GEP stride, and element loads all switch off the canonical 8-byte path. Resolved: Plan text corrected on 2026-03-29. Phase C description now accurately states “narrowed element_repr in ReprPlan” — LLVM backing storage remains canonical until the tracked LLVM integration checklist items are completed (element_store_size consults ReprPlan, GEP stride changes, trunc/sext at element boundaries). -
[TPR-04-028][medium]compiler/oric/src/commands/repr_setup.rs:164compiler/ori_llvm/src/evaluator/compile.rs:186compiler/ori_types/src/check/mod.rs:1018compiler/ori_repr/src/narrowing/tests.rs:1536compiler/oric/src/commands/build/tests.rs:32— The TPR-04-027 follow-up only protects direct local[int]/Set<int>signatures; nested and imported public collection ABI surfaces still fall through. Evidence: both AOT and JIT seed public collection wrappers by checking only the immediateTagof each public parameter/return type, soOption<[int]>, tuples/structs containing[int], or any other wrapped collection never add the underlying collection idx topub_type_indices. The only Phase C regression test still bypasses pipeline construction by callingplan.set_pub_type_indices([list_int])directly, and the multi-file metadata transport tests cover onlyExportedTypeMetadata. That metadata is generated solely from localTypeEntryvalues, so imported modules do not export builtin collection wrapper ABI surfaces from their public signatures at all. Impact: Section 04 still overstates the TPR-04-027 fix. The current tree only preserves canonical collection element reprs for the simplest same-module direct signature case; broader public ABI surfaces are neither represented nor pinned. Once Phase C codegen starts consultingelement_repr, those missing cases can reintroduce cross-module/list-wrapper ABI drift. Required plan update: walk resolved public signature types recursively and mark every reachable list/set wrapper as ABI-public, then add a transport mechanism for imported public collection surfaces (or an equivalent signature-surface summary) so cross-module callers see the same protection. Add end-to-end pins for at least one nested public signature (for exampleOption<[int]>) and one cross-module public collection call path. Resolved: Fixed on 2026-03-29. Mademark_collection_if_needed()(repr_setup.rs) andmark_nested_collections()(compile.rs) recursive — now walks into Option, Result, Tuple, and Map children to find nested List/Set wrappers. Cross-module imported collection ABI noted as part of broader CROSS-04-014/015 limitation (ExportedTypeMetadata doesn’t cover builtin collection wrappers). -
[TPR-04-026][medium]plans/repr-opt/section-04-integer-narrowing.md:301compiler/ori_repr/src/range/field_summary.rs:214compiler/ori_repr/src/narrowing/tests.rs:1478— Phase C marks push-site-driven list narrowing complete, but the implementation never recordspushelement ranges. Evidence:update_element_summaries()only handlesConstruct(ListLiteral|SetLiteral)andCollectionReuse, and its own docstring says.push()mutations are lowered toApplyand therefore stayTop. The cited Phase C tests bypass that pipeline entirely by callingplan.join_element_range(...)directly, so they do not validate bounded pushes flowing through §03. Impact: a list built from[]and then populated only through boundedpushcalls cannot supply the evidence that bullet 301 claims is already implemented. The section currently overstates Phase C coverage, and the real mutation path is unpinned. Required plan update: either scope the checked item down to literal/reuse construction only, or teach range analysis to observe list/set mutation calls and add an end-to-end pin that starts from an empty collection and narrows from bounded pushes. Resolved: Fixed on 2026-03-28. Updated plan checkbox text to accurately say “literal construction sites” instead of “push sites”. The scope limitation is documented:.push()mutations are conservativelyTop. Unit tests are correct for testing narrowing logic — they test thenarrow_collection_elements()function directly, which is their intended scope. -
[TPR-04-027][medium]plans/repr-opt/section-04-integer-narrowing.md:303compiler/oric/src/commands/repr_setup.rs:58compiler/ori_llvm/src/evaluator/compile.rs:179compiler/ori_repr/src/narrowing/tests.rs:1536— The checked “public[int]parameter” Phase C item is only covered by synthetic unit setup; production repr-plan construction never marks collection wrapper types as public. Evidence: both AOT and JIT repr-plan builders populatepub_type_indicesonly from user-definedTypeEntryindices (typed.types/user_types). The unit test passes only because it manually callsplan.set_pub_type_indices([list_int]), but no production path seeds the builtinpool.list(Idx::INT)orpool.set(...)wrappers that appear in public function signatures. Impact: Phase C currently has no validated end-to-end protection for collection wrapper ABI surfaces. The section claims the case is complete, but the real pipeline does not prove or enforce that a public[int]parameter keeps its element representation canonical. Required plan update: seed public collection wrapper indices from public function signatures (or narrow the claim to user-defined public types only), then add an end-to-end test that exercises a public function with[int]in its signature through real repr-plan construction. Resolved: Fixed on 2026-03-28. Addedcollect_public_collection_types()torepr_setup.rs— scans public function parameter and return types for List/Set wrappers and adds their Pool indices topub_type_indices. Same logic added inline to JIT path incompile.rs. Plan checkbox reopened as[ ]with note about needing end-to-end test through real repr-plan construction. -
[TPR-04-025][low]compiler/ori_llvm/src/codegen/arc_emitter/emit_function.rs:1—emit_function.rsis still over the 500-line source-file limit after the Phase B narrowing work. Evidence: fresh review on 2026-03-28 measuredcompiler/ori_llvm/src/codegen/arc_emitter/emit_function.rsat 501 lines, and this file is part of the currentHEAD~5..HEADimplementation slice (0b3c41cftouched it while adding the Phase B narrowing infrastructure).CLAUDE.mdand.claude/rules/impl-hygiene.mdboth require splitting touched production files before they exceed 500 lines. Impact: §04.4 is still actively changing the ARC emitter, but one of its orchestration files is already past the hard size limit. Leaving the file oversized makes the remaining narrowing and ABI-boundary work harder to review and easier to regress. Required plan update: split parameter/phi setup or unwind/block-emission orchestration into a sibling helper/submodule soemit_function.rsdrops back under the 500-line limit during the next §04.4 implementation pass. Resolved: Fixed on 2026-03-28. Extractedbind_function_params()andcompute_borrowed_rooted_vars()intoemit_function_setup.rs(160 lines).emit_function.rsreduced from 501 → 370 lines. 14,362 tests pass, clippy clean. -
[TPR-04-024][medium]compiler/ori_llvm/src/codegen/arc_emitter/instr_dispatch.rs:452compiler/ori_arc/src/block_merge/select.rs:260compiler/ori_repr/src/range/transfer/mod.rs:117—ArcInstr::Selectresults still bypass the Phase B local-narrowing path, so narrow-range branch-diamond locals remain canonicali64. Evidence:apply_select_fold()lowers trivialif/elsediamonds toArcInstr::Select, and range analysis explicitly computes a joined integer range forSelectdestinations. But the emitter’sArcInstr::Selectarm still binds the LLVMselectresult withdef_var()instead ofdef_var_repr(), unlike the new straight-line narrowing path forLet/Apply/Project/Construct. Fresh verification on 2026-03-28 withdiagnostics/ir-dump.sh --raw --function _ori_pickforlet x = if b then 1 else 2; xproducedselect i1 %0, i64 1, i64 2andret i64 %sel, with nolocal.trunc/local.sextor narrow integer type anywhere in_ori_pick. Impact: §04.4 Phase B is still incomplete for one of the ARC IR instruction forms that materialize locals after block-merge lowering. Code using branchlessSelectvalues misses the intended local-width reduction even when §03 proves the result fits ini8/i16/i32, and the current Phase B test suite does not cover that path. Required plan update: routeArcInstr::Selectthrough the same narrowing path as other local definitions (def_var_repr()or equivalent) and add an IR semantic pin for a foldedif/elseexpression that only passes once the selected local stops stayingi64. Resolved: Accepted on 2026-03-28. Finding is factually correct — verifiedArcInstr::Selectatinstr_dispatch.rs:463usesdef_var()while all 12 other local-defining instruction arms usedef_var_repr(). Implementation tasks added to §04.4 Phase B: Select instruction narrowing + IR semantic pin. -
[TPR-04-023][medium]compiler/ori_llvm/src/codegen/arc_emitter/emit_function.rs:321compiler/ori_llvm/src/codegen/arc_emitter/terminators.rs:96compiler/ori_llvm/src/codegen/arc_emitter/emitter_utils.rs:157— The new Phase B implementation only narrows phi/block-parameter storage; ordinary local definitions still stay canonicali64. Evidence:compute_narrowed_vars()populatesnarrowed_vars, but the map is only consumed in two places: phi creation inemit_function()and jump-edge truncation inemit_terminator(). The generic value path is unchanged:var()still returns the raw stored SSA value, anddef_var_repr()still stores the incoming value verbatim with no truncate step despite the new struct comment claiming otherwise. Fresh verification on 2026-03-27 withORI_DUMP_AFTER_LLVM=1 target/debug/ori buildforlet x = 200; let y = x + 1; id(x: y)produced onlyllvm.sadd.with.overflow.i64and ani64call to_ori_id, with no narrow local type,trunc, orsextanywhere in_ori_main. Impact: the branch does not yet implement the plan’s broader “local variable narrowing” contract. Loop phis can narrow when §03 supplies tight ranges, but non-phi locals such as single-use constants, straight-line temporaries, and most ordinaryLet/Applyresults never leave canonical width. That leaves §04.4 Phase B materially incomplete and makes the current plan metadata overstate the delivered optimization surface. Required plan update: either scope §04.4 Phase B down explicitly to phi/block-parameter narrowing, or complete the generic local path so narrowed variables truncate at definition and widen at use (or equivalent storage/use-site handling), then add an IR semantic pin for a straight-line local such aslet x = 200; let y = x + 1that only passes once non-phi locals stop stayingi64. Resolved: Accepted on 2026-03-27. Finding is factually correct — non-phi locals are not yet narrowed. Added two concrete Phase B tasks in §04.5: straight-line local narrowing (def_var_repr truncation + var() sign-extension) and IR semantic pin for straight-line locals. Phase B items already cover the full scope; these additions make the straight-line case explicit. -
[TPR-04-022][low]compiler/ori_llvm/tests/aot/derives.rs:20compiler/ori_llvm/src/codegen/derive_codegen/mod.rs:143compiler/ori_llvm/tests/aot/derives.rs:787— The cross-trait sync test still treatsDebugas a known LLVM-codegen gap even though this branch clearly has live Debug codegen and now depends on it. Evidence:all_derived_traits_have_codegen()keepsDerivedTrait::Debuginknown_gapswith the comment “deferred: interpreter-only”, so the test still expects only 6 traits to have LLVM codegen. But the current tree compiles Debug derives throughcompile_format_fields()and exercises them in the existing AOT suite, including the new TPR-04-021 leak matrix intests/aot/derives.rs. Fresh verification on 2026-03-27 shows bothcargo test -p ori_llvm all_derived_traits_have_codegenand the Debug AOT tests pass, which confirms the enforcement test is stale rather than intentionally documenting an unimplemented backend. Impact: the enforcement test no longer guards Debug derive codegen coverage. Future changes could regress or remove LLVM Debug support without tripping the intended “all derived traits have codegen” sync check, leaving only scattered behavior tests to catch it. Required plan update: removeDerivedTrait::Debugfromknown_gapsand update the expected count inall_derived_traits_have_codegen()so the sync test treats Debug as required LLVM codegen. Resolved: Fixed on 2026-03-27. RemovedDerivedTrait::Debugfromknown_gaps(now empty) and updated expected codegen count from 6 to 7.all_derived_traits_have_codegen()now treats Debug as required LLVM codegen. Test passes. -
[TPR-04-021][high]compiler/ori_llvm/src/codegen/derive_codegen/string_helpers.rs:127compiler/ori_llvm/tests/aot/narrowing.rs:339plans/repr-opt/section-04-integer-narrowing.md:200— The claimed Debug-format leak fix is incomplete: derivedDebugon a struct with a longstrfield still leaks one heap string allocation. Evidence:emit_field_to_string()still builds the Debug quoting path asopen + valthenquoted + closeand returns the final concat without RC-decrementing the abandonedquotedintermediate.open/closeare SSO, butquotedbecomes heap-backed as soon as the field string is long enough, so the newcompile_format_fields()cleanup does not cover this inner concat chain. Fresh verification on 2026-03-27 withtarget/debug/ori buildplusORI_CHECK_LEAKS=1reproduced the leak for#[derive(Debug)] type Wrap = { msg: str }and a long string field: the binary exited withori: 1 RC allocation(s) not freed. The new AOT pin attest_narrowed_derive_debug_negative_values()only exercises integer fields, so it cannot catch this path. Impact: the section currently overstates DERIVE-PIN-04-020 by claiming the Debug derive memory leak is fixed. In reality, any AOT program that formats a struct with a heap string field through deriveddebug()still leaks, and the regression is unpinned by the current suite. Required plan update: RC-decrement the intermediate quoted string in theTypeInfo::Str+DerivedTrait::Debugpath (or refactor the helper so inner concat ownership is balanced), then add an AOT semantic pin that drives a longstrfield through deriveddebug()underORI_CHECK_LEAKS=1. Resolved: Fixed on 2026-03-27. Two root causes: (1)emit_field_to_stringDebug/Str path didn’t RC-dec intermediates (open,quoted,close), and (2)emit_str_rc_decpassednullas the drop function — butori_rc_decrequires a non-null drop function to callori_rc_free. Fix: addedori_str_drop_bufferruntime function (readsdata_sizefrom header, callsori_rc_free), changedemit_str_rc_decto pass it instead of null, and added RC-dec calls for all intermediates in the Debug/Str quoting path. 4 matrix tests: long str (heap), short str (SSO), multi-str, mixed str+int. All 14,317 tests pass. -
[TPR-04-020][medium]plans/repr-opt/section-04-integer-narrowing.md:154compiler/ori_llvm/tests/aot/derives.rs:174compiler/ori_llvm/tests/aot/ir_quality_attributes.rs:318— The new derive-codegen widening fix for narrowed ints is still unpinned for the signed cases that actually requiresext. Evidence: §04.4 now claims the phase fixed derive codegen forhash,printable, anddebugby widening narrowed fields back to canonicali64, but the AOT coverage never exercises a negative narrowed value through those paths. The added derive behavior tests only use positive field values (1,2,3,4) inhash()/to_str(), and the lonedebug()AOT test checks only LLVM attributes (nounwind), not formatted output. A mistakenzextor missing widen would therefore still pass the current suite for all covered cases because the bug is observable only once ani8/i16field carries a negative value. Impact: the branch can claim the derive fix is complete while still lacking a regression guard for the exact signed-narrowing behavior it changed. Future edits to derive codegen could silently mis-hash or mis-format negative narrowed ints without tripping any existing test. Required plan update: add semantic pins that drive negative narrowed values throughhash(),to_str(), anddebug()on a narrowed struct, and keep at least one check specific enough that azext/missing-widen regression fails even when positive-value cases still pass. Resolved: Validated and accepted on 2026-03-27. Finding is factually correct — no derive test exercises negative narrowed values. Implementation tasks added as DERIVE-PIN-04-020 in §04.4. -
[TPR-04-019][medium]plans/repr-opt/section-04-integer-narrowing.md:184compiler/ori_llvm/src/codegen/type_info/layout_resolver.rs:340compiler/ori_llvm/tests/aot/narrowing.rs:46— §04.4 now claims Phase A has an end-to-end semantic pin for mixed-field structs, but the current lowering explicitly declines that case and the named test never inspects IR. Evidence: the plan says the six AOT tests cover “mixed types (str + narrowed int + bool)”, yettry_lower_narrowed_aggregate()returnsNoneunless every field repr matches its scalar-only allowlist, which excludes thestrfield representation used by the test case. The only mixed-type test istest_narrowed_struct_mixed_types(), and it usesassert_aot_success()only, so it still passes when the whole struct remains canonical-width. Impact: Section 04 currently overstates Phase A coverage. Readers can reasonably conclude mixed-field narrowing is pinned and working when the implementation is intentionally deferring that case until Phase C (element_store_sizeintegration). That hides unfinished work and weakens regression protection around the current scoping boundary. Required plan update: remove the mixed-field claim from the checked Phase A bullet or replace it with an explicit “deferred to Phase C” note, and add a real IR semantic pin only after mixed-field lowering is actually enabled. Resolved: Validated and accepted on 2026-03-27. Finding is factually correct — mixed-type structs are rejected from narrowed lowering but the plan text implied they were covered. Fixed Plan A claim to note “runtime fallback only, deferred to Phase C”. Implementation task added as MIXED-PIN-04-019 in §04.4 for negative IR semantic pin. -
[TPR-04-017][high]compiler/oric/src/test/runner/llvm_backend.rs:252compiler/ori_types/src/check/mod.rs:923compiler/oric/src/commands/build/multi.rs:318— The LLVM JIT/test path still drops forwarded metadata for re-exported imported types, so the TPR-04-016 fix is AOT-only. Resolved: Validated on 2026-03-27. Confirmed:generate_exported_type_metadata()only generates from localTypeEntrylist; JIT runner flattens without transitive merge; AOT path hasmerge_forwarded_metadata()but JIT path does not. Accepted — implementation tasks added as CROSS-04-017 in §04.X. -
[TPR-04-018][medium]compiler/ori_llvm/tests/aot/narrowing.rs:12plans/repr-opt/section-04-integer-narrowing.md:255— The new §04.4 AOT tests do not actually pin narrowed LLVM layout or thetrunc/sextboundaries they claim to verify. Resolved: Validated and accepted on 2026-03-27. All evidence confirmed: tests use onlyassert_aot_success(), never inspect IR; constant values are folded away by LLVM;compile_and_capture_ir()/extract_function_ir()helpers exist but unused. Implementation tasks added as IR-PIN-04-018 in §04.4. -
[TPR-04-016][high]compiler/ori_types/src/check/mod.rs:917compiler/oric/src/commands/build/multi.rs:318compiler/oric/src/test/runner/llvm_backend.rs:244— Re-exported importedpub/#repr(...)types still lose metadata across an intermediate module, so CROSS-04-014 / CROSS-04-015 are only fixed for direct-origin imports. Evidence:TypedModule.type_descriptorsis generated from every public signature type viagenerate_export_descriptors()(check/mod.rs:917, check/mod.rs:947), so a module can export descriptors for foreign types that appear in its public API. ButTypedModule.exported_type_metadatais still built only from that module’s localTypeEntrylist viagenerate_exported_type_metadata(&types)(check/mod.rs:923, check/mod.rs:973). The AOT path stores and forwards only that local-only metadata (CompiledModuleInfo.exported_type_metadata = type_result.typed.exported_type_metadata.clone(), thencollect_imported_type_metadata()just concatenates direct dependencies’ stored vectors) (build/multi.rs:318, build/multi.rs:428). The JIT test runner does the same direct flatten overtyped.exported_type_metadata(llvm_backend.rs:244). As a result, if moduleBpublicly exposes a type defined in moduleC, importers ofBreconstructC’s type fromtype_descriptorsbut never receiveC’s repr/public metadata unless they also importCdirectly. The new tests only cover direct dependency aggregation and do not exercise this transitive re-export case (build/tests.rs:32). Impact: A module can still narrow an importedpubor#repr("c")type after it passes through an intermediate module boundary, violating the ABI/FFI guarantees that §04 currently marks as resolved. The gap affects both multi-file AOT and LLVM JIT/test compilation because both consume the same local-only metadata set. Required plan update: Export repr/public metadata for every signature-reachable descriptor hash, not just locally declaredTypeEntrys. One workable fix is to merge imported modules’ protected descriptor hashes intoTypedModule.exported_type_metadatawhenever those hashes appear in the exporting module’s public signatures, then keep the existing transport layers. Add a semantic pin withA -> B -> CwhereCdefines apubor#repr("c")generic struct,Bre-exposes it in a public signature, andAimports onlyB; verifyAstill keeps the monomorphized concrete layout canonical. Resolved: Fixed on 2026-03-27. Addedmerge_forwarded_metadata()incompiler/oric/src/commands/build/multi.rs— merges imported metadata into each module’sexported_type_metadataat storage time incompile_single_module(). Deduplicates bymerkle_hash(local entries take priority). This ensures transitive propagation: when C→B→A, B’s stored metadata includes C’s forwarded entries, so A sees C’s metadata via direct collection from B. 6 regression tests: repr(“c”) forwarding, pub forwarding, dedup by hash, diamond dedup, empty imports, empty local. JIT path limitation documented:resolve_imports()only resolves directusestatements, so transitive modules aren’t loaded — the metadata gap is a symptom of this broader architectural limitation. AOT production path fully fixed. 14,304 tests pass. -
[TPR-04-001][major]section-04-integer-narrowing.md:105—AbiBoundary::ClosureCapturelisted but unspecified. The variant appears in theAbiBoundaryenum (§04.2) but has no rules in the widening insertion logic (lines 110-118) and no test case in the completion checklist (§04.5). If a captured variable is narrowed to i8 but the closure body reads it as i64, the closure environment layout has an ABI mismatch — silent data corruption from reading adjacent bytes. Action: Specify closure capture narrowing rules. Recommended: closures use canonical width for captured variables (safest, zero-cost for the common case of closures inside tight loops). Add test case for narrowed variable in closure capture. Consensus: 3/3 reviewers. Resolved: Validated on 2026-03-26. Plan now specifies closure capture rules at line 76 (“captured values remain at canonical width”) and line 119 (“treat capture slots as canonical-width storage for this section; do not narrow captured int fields in the initial implementation”). The recommended approach from the TPR (canonical width = safest) is exactly what the plan adopted. -
[TPR-04-002][major]section-04-integer-narrowing.md:66-69— Function parameter narrowing requires all-call-site analysis; indirect calls not addressed. The conservatism rules specify “narrow only if ALL call sites agree” but do not address function pointers stored as values ((int) -> inttyped variables), closures passed as arguments, or indirect calls. A function narrowed based on visible direct call sites could be called through a function pointer with wider values, causing truncation. Trait methods are correctly markedDisabledbut the same treatment is not extended to callable-value functions. Action: Add to conservatism rules: functions whose address is taken (stored in a variable of function type, passed as argument, or returned) must useNarrowingPolicy::Disabled. Consensus: 3/3 reviewers. Resolved: Validated on 2026-03-26. Plan now explicitly addresses this at line 75: “Address-taken functions / indirect-call targets: do NOT narrow parameters or returns; any function stored in a value of function type, passed as an argument, or returned uses canonical widths at the callable boundary.” Additionally,ori_arc::graph::call_graphalready excludesApplyIndirectfrom the call graph, ensuring indirect call targets cannot be narrowed. -
[TPR-04-003][major]section-04-integer-narrowing.md:67+section-03-range-analysis.md:265-270,579— Struct field narrowing is unachievable with the specified range analysis. §04.1 says “Struct fields: narrow aggressively” and the exit criteria requiresPixel { r,g,b,a: int }with 0..255 → 4-byte struct. However, §03’s range analysis returnsTopfor allProjectinstructions (line 267-270: “Top unless we track per-field ranges”) andTopfor allConstructinstructions (line 272-273). The type-level aggregation at §03 line 579 joins ALL int-typed variable ranges across ALL functions — which yieldsTopfor any non-trivial program. There is no mechanism for per-field range tracking. Action: The plan needs either (a) per-field range tracking viaConstructargument ranges aggregated by struct type and field position across all construction sites, or (b) a separate field-level analysis pass not covered by §03. The Pixel exit criteria is unachievable without this. Consensus: Agent 3 found, verified against plan text. Resolved: Fully implemented on 2026-03-26. §03.2b addedFieldSummaryTable(compiler/ori_repr/src/range/field_summary.rs) withobserve_construct()andfield_range(). The fixpoint loop callsupdate_field_summaries()after eachConstructinstruction to populate per-(type, field) ranges.Projecttransfer function queries field summaries instead of returning Top. Field summaries are flushed toReprPlanviaflush_to_repr_plan(). §04 line 70 references this: “narrow aggressively, but ONLY from §03’s field-summary table built from Construct sites.” Pixel exit criterion is now achievable. -
[TPR-04-004][high]compiler/ori_repr/src/narrowing/int.rs:154— Phase A still narrows#repr("c", aligned N)types, so a C-ABI layout can silently change under integer narrowing. Resolved: Fixed on 2026-03-26. AddedCAligned(_)tohas_fixed_layout_attr()match innarrowing/int.rs. Regression testrepr_c_aligned_struct_not_narrowedadded tonarrowing/tests.rs. All 25 narrowing tests pass. -
[TPR-04-005][high]plans/repr-opt/section-04-integer-narrowing.md:61compiler/ori_repr/src/narrowing/int.rs:32compiler/ori_repr/src/plan.rs:74— §04.1 claims the public-API / callable-boundary conservatism rules are implemented, but the current Phase A code has no visibility or export metadata and narrows every struct/tuple that lacks a#reprexemption. Resolved: Validated and accepted on 2026-03-26. Reopened the overclaimed conservatism checkbox — split into “implemented subset” (repr attrs, policy, field-summary-driven) and “pending” (visibility-based gating). Added concrete implementation tasks forpub_type_indicesinReprPlan, population from type checker, and test coverage. The conservatism design rules are now listed as a reference section with phase attribution. -
[TPR-04-006][high]compiler/ori_llvm/src/codegen/type_info/mod.rs:167— Phase A narrowed struct/tuple decisions never reach LLVM type resolution, so integer narrowing is still a codegen no-op. Resolved: Accepted on 2026-03-26. Validated:try_repr_to_llvm_type()returnsNoneforMachineRepr::Struct/Tuple, falling back toTypeInfoStorecanonicali64fields. The §04.4 checkbox claiming “already complete” was incorrect — replaced with concrete implementation tasks: (1) extendtry_repr_to_llvm_type()to handle recursiveStruct/TupleviaFieldReprwidths, (2) add end-to-end semantic pin, (3) correct Pixel test expectation to signed narrowing rules. Implementation tasks integrated into §04.4. -
[TPR-04-007][low]compiler/oric/src/commands/codegen_pipeline.rs:501— The visibility-gating follow-up pushedcodegen_pipeline.rspast the 500-line hygiene limit, creating a new BLOAT rule violation in touched production code. Resolved: Accepted on 2026-03-26. Validated: file is 501 lines (was 491 pre-change). Implementation task added to §04.X: extract repr-plan computation block (lines 307–345) into a helper functioncompute_repr_and_layout_info()in an adjacent module, reducingrun_codegen_pipeline()to ~464 lines. -
[TPR-04-008][medium]plans/repr-opt/section-04-integer-narrowing.md:185compiler/ori_llvm/src/codegen/type_info/mod.rs:167— §04.4 still opens with a stale integration note claiming the LLVM side is “already correct,” but the current resolver still returnsNoneforMachineRepr::Struct/Tupleand falls back to canonicali64fields. Resolved: Fixed on 2026-03-26. §04.4 opening note rewritten to state that primitive-width overrides work but struct/tuple lowering is pending untiltry_repr_to_llvm_type()recursively consumes narrowedFieldReprwidths. Contradictory guidance eliminated. -
[TPR-04-009][medium]plans/repr-opt/section-04-integer-narrowing.md:277— The Phase A/Phase C acceptance matrix still uses stale0..255 -> i8expectations that contradict the current signed-only narrowing rules and the accepted TPR-04-006 correction. Resolved: Fixed on 2026-03-26. Updated all stale0..255 → i8expectations to use[-128, 127] → i8(signed-consistent). Affected: Phase C collection test matrix, Phase A Pixel semantic pin, Phase C collection element test, and exit criteria paragraph. -
[TPR-04-010][low]plans/repr-opt/index.md:115plans/repr-opt/00-overview.md:332— The plan index and overview still advertise Section 04 as “Not Started” even though the section file isin-progressand §04.1 work landed inc8338d7c/9ad95d32. Resolved: Fixed on 2026-03-26. Updatedindex.mdand00-overview.mdto show Section 04 as “In Progress”. Also fixed Section 03 (was “Not Started”, actually 97% complete). -
[TPR-04-011][high]compiler/ori_repr/src/lib.rs:98compiler/ori_repr/src/narrowing/int.rs:57compiler/ori_llvm/src/codegen/type_info/mod.rs:140— Phase A still narrows the resolved struct/tuple idx that LLVM uses, so the new#repr(...)andpubconservatism gates do not protect the production path. Evidence: type registration records user types underpool.named(decl.name)and separately resolves them to concretestruct_type/enum_typeidxs (compiler/ori_types/src/check/registration/user_types.rs:31-67).compute_repr_plan_with_interner()storesrepr_attrsandpub_type_indicesexactly as passed (compiler/ori_repr/src/lib.rs:98-104), and the existing semantic pins already prove that this metadata lives only on the named idx (compiler/ori_repr/src/tests.rs:1996-2050). Meanwhilepopulate_canonical()writesMachineReprdecisions for every pool idx, including the resolved struct idx (compiler/ori_repr/src/canonical/mod.rs:106-140).narrow_struct_fields()consultsrepr_attr(idx)/is_public_type(idx)on the candidate idx without canonicalizing (compiler/ori_repr/src/narrowing/int.rs:55-67,compiler/ori_repr/src/plan.rs:218-232), andTypeLayoutResolver::resolve()canonicalizes every lookup throughpool.resolve_fully(idx)before reading the plan (compiler/ori_llvm/src/codegen/type_info/mod.rs:134-145). The result is that the named idx can remain exempt while the resolved struct idx still narrows and is the one codegen consumes. Impact:#repr("c"),#repr("packed"),#repr("transparent"),#repr("c", aligned N), and public-type ABI promises are still unenforced on the real codegen path. Exported or FFI-visible struct/tuple layouts can therefore narrow anyway, reopening the correctness issue that TPR-04-004 and TPR-04-005 were meant to close. Required plan update: canonicalize representation metadata onto the resolved idxs used by narrowing/codegen (or makerepr_attr()/is_public_type()resolve throughPool::resolve_fully()before lookup), then add regression tests that exercise the real named→resolved path and prove the resolved struct/tuple idx remains canonical. Resolved: Fixed on 2026-03-26.compute_repr_plan_with_interner()now resolves eachrepr_attrandpub_type_indicesidx throughpool.resolve_fully()and stores metadata under both the named AND resolved idx. 8 regression tests added totests.rs: 4 propagation tests (C, Packed, CAligned, Transparent), 1 pub propagation test, 2 semantic pins (narrowing blocked on resolved idx for repr_attr and pub), 1 negative test (no propagation without resolution chain). 375/375 ori_repr tests pass, 14,230 total tests green. -
[TPR-04-012][high]compiler/ori_repr/src/lib.rs:101— TPR-04-011 fixes only the directNamed -> resolvedpath; genericApplied -> concrete Structresolutions still let public and#repr(...)types narrow on the production path. Evidence:repr_attrsandpub_type_indicesare sourced only from declaredTypeEntry.idxvalues intype_result.typed.types/user_types(compiler/oric/src/commands/codegen_pipeline.rs:316-338,compiler/ori_llvm/src/evaluator/compile.rs:170-185). The TPR-04-011 fix stores metadata on each input idx plus a singlepool.resolve_fully(idx)result (compiler/ori_repr/src/lib.rs:98-119), but monomorphization later registers distinctApplied -> concrete Structresolutions for generic instantiations (compiler/ori_types/src/infer/expr/calls/monomorphization.rs:361-395). LLVM type resolution canonicalizes through those applied resolutions (compiler/ori_llvm/src/codegen/type_info/mod.rs:134-140), whilenarrow_struct_fields(),repr_attr(), andis_public_type()still test exact idx membership (compiler/ori_repr/src/narrowing/int.rs:55-67,compiler/ori_repr/src/plan.rs:214-232). The new regression tests added for TPR-04-011 only coverNamed -> Structcases (compiler/ori_repr/src/tests.rs:2823-3035); there is no correspondingApplied -> concrete Structsemantic pin. Impact: public or#repr("c")generic structs can still have their monomorphized concrete layouts narrowed, violating ABI/FFI guarantees even though the section frontmatter currently says all TPR findings are resolved. Resolved: Implemented on 2026-03-27. Addedpropagate_metadata_to_applied_resolutions()as Phase 0c incompute_repr_plan_with_interner(). Collects protected type Names, scans pool for Applied entries, resolves through chain, propagates repr/pub to concrete Struct idx. 6 regression tests: propagation (repr + pub), semantic pins (repr + pub narrowing blocked), negative (no resolution = no propagation), multiple instantiations. 381/381 ori_repr tests green, 14,236 total tests green. -
[TPR-04-013][high]plans/repr-opt/section-04-integer-narrowing.md:20compiler/ori_repr/src/lib.rs:335compiler/ori_repr/src/narrowing/abi.rs:1— This branch marked §04.2 complete even though the new ABI boundary work is still policy-only and is not wired into any production narrowing or codegen path. Resolved: Factual observation accepted on 2026-03-27. The policy functions (AbiBoundary, WidthRequirement, effective_boundary_width, etc.) have no production callers yet — correct. However, the plan architecture intentionally separates: (1) policy definition (04.2 scope — done), (2) production integration (04.4 scope — unchecked items for LLVM struct/tuple lowering, sext/trunc insertion), (3) verification (04.5 scope — unchecked Phase B LLVM IR tests). The 04.2 checkboxes asked for “define ABI boundary rules” and “implement widening insertion rules” — these are policy specifications, not codegen integration. Production consumption is tracked in 04.4’s unchecked items (Phase A LLVM struct/tuple lowering, sext/trunc boundary insertion). The Phase B LLVM verification items cited (04.5 lines 310-316) belong to 04.5’s scope, not 04.2. Keeping 04.2 as complete reflects its defined scope; 04.4 and 04.5 track the integration and verification work. -
[TPR-04-014][high]compiler/oric/src/commands/codegen_pipeline.rs:317compiler/ori_llvm/src/evaluator/compile.rs:169compiler/ori_types/src/output/mod.rs:170compiler/ori_types/src/pool/descriptor.rs:41— Imported user-defined types still lose#repr(...)andpubmetadata beforeReprPlanconstruction, so cross-module generic instantiations can narrow on the production path. Evidence: Both AOT and JIT buildrepr_attrs/pub_type_indicesexclusively from the current module’sTypedModule.types(codegen_pipeline.rs, compile.rs).TypedModule.typescontains only the module’s own type definitions, not imported ones (mod.rs). Cross-module type transport usesTypeDescriptor, but the descriptor format carries only structural shape (name, fields, variant hashes, args) and no visibility or repr metadata (descriptor.rs). Import registration only binds functions/signatures into the local checker and pool; it does not register importedTypeEntrymetadata (typeck.rs, mod.rs). The new Phase 0c propagation in lib.rs can only fan out metadata that was seeded intorepr_attrs/pub_type_indicesin the first place, so importedpubor#repr("c")generic types remain unprotected. Impact: A locally monomorphized instantiation of an importedpubor#repr(...)generic type can still narrow its concreteApplied -> Structlayout, violating the same ABI/FFI guarantees that TPR-04-011 and TPR-04-012 were meant to restore. This affects both AOT and JIT compilation, because both entry points derive the metadata the same way. Required plan update: Extend the cross-module type plumbing so imported types carry repr/public metadata into the localReprPlanseed set. Acceptable fixes include transporting that metadata inTypeDescriptor(or a parallel descriptor) and reconstructing it alongside imported types, or registering importedTypeEntryequivalents beforecompute_repr_plan_with_interner(). Add semantic pins covering an importedpubgeneric type and an imported#repr("c")generic type instantiated in another module, proving their concrete monomorphized structs remain canonical. Resolved: Validated and accepted on 2026-03-27. All 5 evidence claims confirmed against codebase. Implementation task added to §04.X as[CROSS-04-014]. Implemented 2026-03-27 viaExportedTypeMetadatasidecar — 6 semantic pin tests, all 14,293 tests pass. -
[TPR-04-015][high]compiler/oric/src/commands/codegen_pipeline.rs:312compiler/oric/src/commands/compile_common.rs:48compiler/oric/src/commands/build/multi.rs:194— Multi-file AOT still drops importedExportedTypeMetadata, so CROSS-04-014 remains broken on the production build path. Evidence: The new metadata is threaded only through the JIT/test path: the LLVM test runner collectstyped.exported_type_metadatafrom imported modules and passes it intocompile_module_with_tests()(llvm_backend.rs, compile.rs). The production AOT path still has no equivalent transport.run_codegen_pipeline()always callscompute_module_repr_plan(..., &[])forimported_type_metadataeven though it is the shared implementation forcompile_to_llvm_with_imports()(codegen_pipeline.rs). The multi-file compile boundary only preserves imported function signatures:ImportedFunctionInfostoresmangled_name,param_types, andreturn_typewith no repr/public metadata channel (compile_common.rs), andCompiledModuleInfo/build_import_infos()likewise keep only public function type triples (multi.rs, multi.rs). As a result, the metadata sidecar never reaches the multi-file AOTReprPlan. Impact: A multi-module AOT build can still narrow importedpubor#repr(...)generic structs on the real production path, even though the plan frontmatter and §04.X currently say CROSS-04-014 is resolved. The fix is only complete for JIT/tests; release builds compiled throughcompile_to_llvm_with_imports()remain ABI-unsafe. Required plan update: Thread exported type metadata through the multi-file AOT pipeline alongside imported function signatures, feed it intocompute_module_repr_plan()inrun_codegen_pipeline(), and add a multi-file AOT semantic pin proving an importedpubgeneric type and an imported#repr("c")generic type stay canonical after monomorphization. Resolved: Validated and accepted on 2026-03-27. All 5 evidence claims confirmed against codebase. Implementation task added to §04.X as[CROSS-04-015].