Section 02: Codegen & Runtime Integration
Status: Complete
Goal: Wire up LLVM codegen to store elem_dec_fn and elem_count in the RC header at collection construction time. Ensure all buffer-freeing paths (COW slow paths, collect, slice materialization) propagate both fields to newly allocated buffers.
Depends on: Section 01 (RC header must be extended first).
Blocks: Section 01.N has 3 Valgrind failures blocked on this section (map {int: [int]}, {str: int}, {str: [int]} double-frees in cow_leak_scenarios.ori, cow_map_insert_remove.ori, cow_nested.ori). These are map-value/map-key double-frees where standalone RcDec AND map_buffer_cleanup both fire. The root cause is that map element cleanup functions are not yet stored/propagated correctly — resolving Section 02.3 (map double-free investigation) should address these. After resolution, re-run the 3 blocked Section 01.N Valgrind tests to confirm.
02.1 Store elem_dec_fn and elem_count at Collection Construction
File(s): compiler/ori_llvm/src/codegen/arc_emitter/construction.rs, compiler/ori_rt/src/rc/mod.rs
When a list literal [a, b, c] is constructed, the codegen’s emit_construct (via CtorKind::ListLiteral, line 83 of construction.rs) allocates a buffer via ori_list_alloc_data. After storing elements, it must also store elem_dec_fn and elem_count in the buffer’s RC header.
Runtime FFI Functions
- Extract element header helpers (
store_elem_dec_fn,load_elem_dec_fn,store_elem_dec_fn_once,store_elem_count,load_elem_count, lines 108-213, ~105 lines) fromcompiler/ori_rt/src/rc/mod.rsinto a newrc/elem_header.rssubmodule.rc/mod.rsreduced from 501 → 384 lines. (2026-03-20) - Add runtime function
ori_buffer_store_elem_dec(data: *mut u8, elem_dec_fn: Option<extern "C" fn(*mut u8)>)inrc/elem_header.rs— wrapper aroundstore_elem_dec_fn,#[no_mangle] extern "C"callable from LLVM IR (2026-03-20) - Add runtime function
ori_buffer_store_elem_count(data: *mut u8, count: i64)inrc/elem_header.rs— wrapper aroundstore_elem_count,#[no_mangle] extern "C"callable from LLVM IR (2026-03-20) - Add
load_elem_dec_fn_const(data: *const u8) -> Option<...>overload (andload_elem_count_const) inrc/elem_header.rs(2026-03-20)
LLVM IR Declarations
- Declare
ori_buffer_store_elem_decincompiler/ori_llvm/src/codegen/runtime_decl/runtime_functions.rs— signature:(ptr, ptr) -> void(2026-03-20) - Declare
ori_buffer_store_elem_countincompiler/ori_llvm/src/codegen/runtime_decl/runtime_functions.rs— signature:(ptr, i64) -> void(2026-03-20)
List Construction Codegen
- In the
Constructhandler for lists (construction.rs,CtorKind::ListLiteralarm): emitori_buffer_store_elem_dec(data_ptr, elem_dec_fn)after storing elements (2026-03-20) - In the same arm: emit
ori_buffer_store_elem_count(data_ptr, count)(2026-03-20) - For scalar elements:
elem_dec_fnis null — call is idempotent (writes null over zero-init).elem_countalways stored. (2026-03-20) - For
ori_list_alloc_data: no change needed —ori_rc_alloczero-initializes the header. (2026-03-20)
Set Construction Codegen
- Set construction via
CtorKind::SetLiteral: emit bothori_buffer_store_elem_decandori_buffer_store_elem_countafter element insertion (2026-03-20)
Collection Reuse Codegen
-
emit_collection_reuse(): emit both header-store calls afterori_list_reset_bufferreturns the new buffer (2026-03-20)
COW Fast-Path Reallocation
- Verify
ori_rc_reallocpreserves all 32 header bytes: COW mutations on the fast path (unique owner, needs more capacity) callori_rc_realloc, which preserves all 32 header bytes becausereallocpreservesmin(old, new)bytes and the header is at the front. Verified viatest_rc_realloc_preserves_header_fieldstest atcompiler/ori_llvm/src/tests/runtime_tests.rs:327— stores elem_dec_fn + elem_count, reallocs, confirms both preserved. (2026-03-20)
COW Slow Path Propagation
ALL runtime functions that allocate new list buffers via ori_rc_alloc must propagate elem_dec_fn and elem_count from old to new buffer. Two propagation strategies:
- Direct copy (preferred when old buffer is available): read
elem_dec_fn/elem_countfrom old header, write to new header (~3 lines per function). - Deferred store (fallback when no old buffer exists, e.g.,
ori_iter_collect): rely on the nextori_buffer_rc_deccall to store viastore_elem_dec_fn_once, and storeelem_countexplicitly after the allocation completes.
For COW slow paths, elem_count on the new buffer = number of elements actually copied (may differ from old elem_count for pop_cow/remove_cow which reduce element count by 1).
-
cow.rs:ori_list_push_cow(slow path),ori_list_pop_cow(slow path),ori_list_set_cow(slow path) —propagate_elem_header()helper + calls at all 3 sites (2026-03-20) -
cow_structural.rs:ori_list_insert_cow(slow path),ori_list_remove_cow(slow path) — direct copy viastore_elem_dec_fn/store_elem_countat both sites (2026-03-20) -
cow_sort.rs: 4 allocation sites —propagate_header()helper + calls atconcat_cow(2 sites),reverse_cow,sort_cow. File at 499 lines (under limit via condensed helper + tightened module docs) (2026-03-20) -
query.rs:ori_list_reverseandori_list_concat— usesload_elem_dec_fn_constfor*const u8source data (2026-03-20) -
slice.rs:ori_list_materialize_slice— reads from ORIGINAL buffer viaload_elem_dec_fn(original), not slice data pointer (2026-03-20) -
mod.rs:ori_list_ensure_capacity(line 61, new alloc at line 85, realloc at line 88) — theori_rc_reallocgrow path preserves the header automatically. Theori_rc_allocfirst-allocation path (empty sentinel to first buffer) creates a buffer with zero-initializedelem_dec_fnandelem_count. JIT/test-only: declared inruntime_functions.rs(line 1218) with JIT mapping (line 175), but NOT referenced from anyarc_emitter/codegen code. For JIT paths, the header is populated by the firstori_buffer_rc_deccall viastore_elem_dec_fn_once. No codegen changes needed. Stale “8-byte refcount header” comment at line 83 tracked in Cleanup section. (2026-03-20, analysis complete) -
mod.rs:ori_list_new(line 153,ori_rc_allocat line 162) — allocates a data buffer for a heap-allocatedOriList. JIT/test-only: zero references fromarc_emitter/codegen code. Header populated by firstori_buffer_rc_decviastore_elem_dec_fn_once. No codegen changes needed. Stale “Used by AOT code” doc comment tracked in Cleanup section. (2026-03-20, analysis complete) -
mod.rs:ori_list_push_new(line 304) — allocates a new buffer viaori_rc_alloc(line 319) with no old buffer to copy from. JIT/test-only: zero references fromcodegen/arc_emitter/. Header populated by firstori_buffer_rc_decviastore_elem_dec_fn_once. No codegen changes needed. Dead declaration cleanup tracked in Section 03.2.5. (2026-03-20, analysis complete) -
mod.rs:ori_list_push(line 228) — IS called from codegen (references atapply.rs:208andterminators.rs:411). The first-growth path (line 244-245) callsori_rc_allocwhen the list starts empty, creating a buffer with zero-initialized header. Sinceori_list_pushmutates an existingOriListvia pointer (it doesn’t return a new buffer), the codegen cannot emit header-store calls after the push.elem_dec_fnpopulated by firstori_buffer_rc_decviastore_elem_dec_fn_once.elem_countNOT set on first-alloc path. Risk constrained: both codegen call sites are for-yield loops (apply.rs:208,terminators.rs:411) — buffer always created byori_list_new, result always goes throughori_buffer_rc_decbefore any slice. Slice before RcDec is not reachable in the for-yield pattern. (2026-03-20, verification complete) -
mod.rs:write_array_to_list— addedelem_dec_fnparameter, stores bothelem_dec_fnandelem_countin header after copy. All callers updated:ori_str_charspassesNone(scalar[char]),ori_set_to_listempty path passesNone,ori_map_keys_to_list/ori_map_values_to_listempty paths passNone. (2026-03-20) -
reset/mod.rs:ori_list_reset_buffer(line 34) — creates new buffer when reuse fails. Does NOT need internal propagation; codegen handles it externally viaori_buffer_store_elem_dec+ori_buffer_store_elem_countcalls after the reset returns (verified:construction.rs:483-490emits both header-store calls after reset). (2026-03-20, verification complete) -
iterator/consumers.rs:ori_iter_collect— storeselem_count(data, len)after collection loop completes.elem_dec_fnstored by codegen after collect returns (LLVM-generated thunk). (2026-03-20) -
iterator/consumers.rs:ori_iter_collect_set(line 85) — no old buffer exists; use deferred store. Sets currently readelem_dec_fnfrom the parameter (not header), but storing in the header provides defense-in-depth. Codegen fix done:emit_iter_collect_setemitsori_buffer_store_elem_dec(result_data, elem_dec_fn)on the output set buffer (2026-03-20, see line 171).
ori_args_from_argv — Buffer-Creating Function
ori_args_from_argv (line 303 of lib.rs) allocates a [OriStr] list buffer via ori_rc_alloc (line 315). This creates a [str] list that contains heap-allocated strings requiring elem_dec_fn for cleanup. The function is called from generate_main_wrapper in entry_point.rs for @main(args: [str]) signatures.
-
ori_args_from_argvstoreselem_countin the RC header after populating elements. Option (b) chosen:elem_dec_fndeferred to firstori_buffer_rc_decviastore_elem_dec_fn_once— safe becauseargsis a local binding that always gets RcDec on scope exit, cannot be sliced before then. No ABI change needed. (2026-03-20)
Set COW Slow Path Propagation
Set COW functions allocate new hash table buffers via alloc_set_hash_buffer (which calls ori_rc_alloc internally) or rehash_set. Each new buffer must have elem_dec_fn stored in its header for defense-in-depth with ori_set_buffer_rc_dec.
Note on elem_count for sets: Sets use metadata scanning (not elem_count) for element cleanup — ori_set_buffer_rc_dec iterates META_OCCUPIED buckets, not a contiguous array. The elem_count header field is only meaningful for list-style contiguous buffers (used by slice_buffer_rc_dec). Sets cannot be sliced. Therefore, elem_count does NOT need to be stored for set hash table buffers — only elem_dec_fn is relevant.
-
set/cow/basic.rs:ori_set_insert_cow— readselem_dec_fnfrom old buffer header, passes torehash_set(fast path) andalloc_set_hash_buffer(slow path). (2026-03-20) -
set/cow/basic.rs:ori_set_remove_cow— readselem_dec_fnfrom old buffer header, passes toalloc_set_hash_buffer. (2026-03-20) -
set/cow/algebra.rs:ori_set_union_cow—rehash_and_merge_set2readselem_dec_fnfromd1header and passes torehash_set.set1 emptypath reads fromd2header. (2026-03-20) -
set/cow/algebra.rs:ori_set_intersection_cow— readselem_dec_fnfromd1header, passes toalloc_set_hash_buffer. (2026-03-20) -
set/cow/algebra.rs:ori_set_difference_cow— readselem_dec_fnfromd1header, passes toalloc_set_hash_buffer. (2026-03-20) -
set/mod.rs:ori_set_to_list(line 55,ori_rc_allocat line 75) — creates a LIST buffer from set contents. Addedelem_dec_fnparameter, stores bothelem_dec_fnandelem_countin RC header (2026-03-20, see line 157-160). -
set/mod.rs:alloc_set_hash_buffer— centralized: addedelem_dec_fnparameter, stores in header internally. All callers passelem_dec_fn(from old header orNonefor fresh allocs). Coversori_set_literal_alloc,ori_iter_collect_set, all set COW functions. (2026-03-20) -
map/hash_table.rs:rehash_set— centralized: addedelem_dec_fnparameter, stores in header internally. All callers passelem_dec_fn(from old header orNone). Coversori_set_insert_cow,ori_set_union_cow,ori_iter_collect_set. (2026-03-20)
Map COW Slow Path Propagation
Map COW functions allocate new hash table buffers. Maps use the codegen-based approach (option c) for key/value dec functions, not the header. However, rehash_map and OriMap::alloc_hash_buffer in map/hash_table.rs call ori_rc_alloc. Since maps do NOT use the elem_dec_fn header slot (they need TWO functions), no header propagation is needed for map hash table buffers. map/cow.rs:144 (ori_rc_alloc in map COW slow path) is also a map hash table buffer — explicitly excluded, no action needed.
But map functions that create LIST buffers DO need propagation:
-
map/mod.rs:ori_map_keys_to_list(line 97,ori_rc_allocat line 118) — creates a[K]list. Addedkey_dec_fnparameter, stores bothelem_dec_fnandelem_count(2026-03-20, see lines 140-144). -
map/mod.rs:ori_map_values_to_list(line 141,ori_rc_allocat line 164) — creates a[V]list. Addedval_dec_fnparameter, stores both (2026-03-20, see lines 141-144).
Buffer-Creating Runtime Functions That Need Header Stores
Several runtime functions allocate new list buffers but lack access to elem_dec_fn. Each needs a different approach:
write_array_to_list (line 394 of list/mod.rs) — only used by ori_str_chars (which produces [char]/[i32], scalar elements, NULL elem_dec_fn). Since the only real caller uses scalar elements, adding elem_dec_fn is not strictly needed. However, for correctness and future safety, extend the signature:
- Add
elem_dec_fnparameter towrite_array_to_listfunction signature (2026-03-20) - Store
elem_dec_fnandelem_count(new_data, n)insidewrite_array_to_listafter the copy (2026-03-20) - Update
ori_str_charsto passNone(scalar elements) (2026-03-20)
write_array_to_list_from_data (line 313 of map/mod.rs) — used by ori_map_keys_to_list and ori_map_values_to_list. This takes ownership of an already-allocated buffer but does NOT store elem_dec_fn/elem_count in it. The buffer was allocated by the caller (not by write_array_to_list), so header stores must happen at the allocation site:
- In
ori_map_keys_to_list: addedkey_dec_fnparameter, stores bothelem_dec_fnandelem_countin RC header afterori_rc_alloc. (2026-03-20) - In
ori_map_values_to_list: addedval_dec_fnparameter, stores bothelem_dec_fnandelem_countin RC header afterori_rc_alloc. (2026-03-20) - LLVM IR declarations updated:
ori_map_keys_to_listaddsTy::Ptrforkey_dec_fn,ori_map_values_to_listaddsTy::Ptrforval_dec_fn. (2026-03-20) - Codegen call sites updated in
map_builtins.rs:emit_map_keyspassesself.get_or_generate_elem_dec_fn(key_ty),emit_map_valuespassesself.get_or_generate_elem_dec_fn(val_ty). (2026-03-20) - ABI sync point: all 4 changes (runtime + LLVM decl + codegen) committed together. (2026-03-20)
ori_str_split (line 45 of string/ops.rs) — allocates its own buffer directly via ori_rc_alloc at line 107 (NOT through write_array_to_list). The result is [str] (24-byte OriStr elements) which need elem_dec_fn for cleanup.
NOTE: The element dec function for str is an LLVM-generated thunk (created by get_or_generate_elem_dec_fn in element_fn_gen.rs), NOT a named Rust runtime function. There is no ori_str_rc_dec symbol in ori_rt. Approach (b) (internal store) is therefore not feasible — the function pointer only exists in LLVM IR space. Use approach (a).
- Added
elem_dec_fnparameter toori_str_splitfunction signature. (2026-03-20) - Stores
elem_dec_fnandelem_countin RC header after element population. (2026-03-20) - LLVM IR declaration updated: adds
Ty::Ptrforelem_dec_fn. (2026-03-20) - Codegen call site updated:
emit_str_splitnow acceptsstr_ty: Idx, passesself.get_or_generate_elem_dec_fn(str_ty). Call site passesIdx::STR. (2026-03-20) - ABI sync point: all 4 changes committed together. (2026-03-20)
ori_set_to_list (line 55 of set/mod.rs) — allocates a list buffer directly via ori_rc_alloc at line 75. The function itself doesn’t call write_array_to_list for the main path (only for the empty case at line 66).
- Added
elem_dec_fnparameter toori_set_to_list, stores bothelem_dec_fnandelem_countin RC header. (2026-03-20) - LLVM IR declaration updated: adds
Ty::Ptrforelem_dec_fn. (2026-03-20) - Codegen call site updated:
emit_set_to_listpassesself.get_or_generate_elem_dec_fn(elem_ty). (2026-03-20) - ABI sync point: all 4 changes committed together. (2026-03-20)
ori_iter_collect Design Decision
ori_iter_collect creates a new list buffer via ori_rc_alloc but has no access to elem_dec_fn (it receives only iter, elem_size, out_ptr). The plan says “use deferred store” but this has a gap:
Risk: If the collected buffer is sliced before any ori_buffer_rc_dec call fires, slice_buffer_rc_dec will find elem_count == 0 (zero-initialized) and skip cleanup. The elem_dec_fn header slot would also be NULL.
Mitigation: ori_iter_collect is always followed by codegen-emitted ori_buffer_rc_dec calls when the list goes out of scope. The buffer cannot be sliced before being returned to the codegen level, at which point the codegen can emit ori_buffer_store_elem_dec + ori_buffer_store_elem_count immediately after the collect call returns.
- Codegen fix (list collect):
emit_iter_collectnow emitsori_buffer_store_elem_dec(result_data, elem_dec_fn)andori_buffer_store_elem_count(result_data, result_len)after the runtime collect call. Extractsdataviaextract_value(result, 2),lenviaextract_value(result, 0). (2026-03-20) - Codegen fix (set collect):
emit_iter_collect_setnow emitsori_buffer_store_elem_dec(result_data, elem_dec_fn)inside the function (covers bothiterator_consumers.rsandapply_protocols.rscall paths).elem_countnot needed for sets. (2026-03-20)
ori_list_push_new Design Decision
ori_list_push_new (line 304 of list/mod.rs) allocates a new buffer via ori_rc_alloc but has no old buffer header to copy from (the original list is borrowed, not consumed) and no elem_dec_fn parameter.
RESOLVED: ori_list_push_new is declared in runtime_functions.rs (line 248) and has a JIT symbol mapping in runtime_mappings.rs (line 102), but is NOT referenced from any arc_emitter codegen code (grep for "ori_list_push_new" in codegen/arc_emitter/ returns zero results). It is JIT/test-only. The header will be populated by the first ori_buffer_rc_dec call via store_elem_dec_fn_once. No codegen changes needed.
- Determine whether
ori_list_push_newis called from LLVM codegen or only from test/JIT paths — JIT/test only (not called fromarc_emitter/) (2026-03-20, plan review) - [WASTE]
ori_list_push_new— removed declaration fromruntime_functions.rsand JIT symbol mapping fromruntime_mappings.rs. Runtime implementation inori_rtretained for future JIT use. (2026-03-21)
Invariant Assertion
- Add
debug_assert!inori_buffer_rc_dec(list_rc.rs): afterstore_elem_dec_fn_onceand before the RC decrement, assertelem_dec_fn.is_none() || load_elem_dec_fn(data).is_some(). Catches non-NULL caller with NULL header post-store. Added 2026-03-21.
SSO String Correctness
- Verify that
generate_elem_dec_fn_bodyforstrelements correctly handles mixed SSO/heap strings: code reviewed on 2026-03-21 —dec_value_rchitsTag::Stratrc_value_traversal.rs:183, extracts data pointer (field 2),emit_sso_check(MSB flag + null check), only callsori_rc_decon heap strings. SSO strings skipped. AOT testtest_str_list_mixed_sso_heapconfirms zero leaks with mixed “hi” (SSO) + long heap strings.
AOT Tests
All tests use ORI_CHECK_LEAKS=1 to verify zero leaks unless otherwise noted.
-
[str]list goes out of scope without iteration — zero leaks (AOT testtest_str_list_scope_drop, 2026-03-21) -
[[int]]nested list goes out of scope — zero leaks (AOT testtest_nested_int_list_scope_drop, 2026-03-21) -
[str]COW push on shared list (push creates new buffer) — zero leaks (AOT testtest_str_list_cow_push_shared, 2026-03-21) -
[str]with mixed SSO and heap strings — zero leaks (AOT testtest_str_list_mixed_sso_heap, 2026-03-21) -
ori_iter_collecton[str]viafor w in words yield w— zero leaks (AOT testtest_str_list_iter_collect, 2026-03-21) -
map.keys()on{str: int}— fixed viakey_inc_fnparameter (2026-03-21). AOT testtest_map_keys_str_scope_droppasses in debug + release. See 02.3 map double-free fix. -
str.split(sep:)returning[str]— zero leaks (AOT testtest_str_split_scope_drop, 2026-03-21)
Cleanup
- [WASTE]
construction.rslines 89, 136, 189, 424 — fallback_ => ori_types::Idx::INTsilently returns INT as element type on TypeInfo mismatch. Addtracing::warn!ordebug_assert!at each site so misclassification is visible. Affected arms:ListLiteral(89),MapLiteral(136),SetLiteral(189),emit_collection_reuse(424). Done:debug_assert!(false, ...)at all 4 sites (2026-03-21) - [DRIFT]
list_rc.rs:27— V4 → V5,header_data - 16→header_data - 24(2026-03-21) - [DRIFT]
list/mod.rs:83— “8-byte refcount header” → “32-byte V5 header” (2026-03-21) - [DRIFT]
list/mod.rs:103— “RC header atptr - 8” → “RC header atptr - 32; strong_count atptr - 8” (2026-03-21) - [DRIFT]
list/mod.rs:131— “8-byte refcount header” → “32-byte V5 header” (2026-03-21) - [DRIFT]
list/mod.rs:199— “RC-managed with 8-byte header” → “32-byte V5 RC header, alignment 8” (2026-03-21) - [DRIFT]
cow.rs:38— “§02.7” → “elem_dec_fnin the V5 RC header handles cleanup” (2026-03-21) - [DRIFT]
list/reset/mod.rs:71— V4 → V5 label (2026-03-21) - [DRIFT]
list/mod.rs:151— “Used by AOT code” → “Used by JIT/test code. Not called from arc_emitter/ codegen.” (2026-03-21) - [STYLE]
list/mod.rslines 96, 294 — 2 decorative banners replaced with plain section comments (2026-03-21) - [STYLE]
iterator/consumers.rslines 11, 76, 159, 181, 214, 247, 303, 330 — 8 decorative banners replaced with plain section comments (2026-03-21) - [WASTE]
cow_sort.rs:256—ori_list_reverse_cowfast path: replacedvec![0u8; es]with[0u8; 24]stack buffer + heap fallback for larger elements. (2026-03-21) - [WASTE]
cow_sort.rs:458—apply_permutation_in_place: same stack array optimization as line 256. (2026-03-21) - [LATENT]
query.rs—ori_list_reverseandori_list_concatnow acceptelem_align: i64parameter and use it inori_rc_alloc. LLVM declarations updated. No codegen callers (JIT-only). (2026-03-21) - [WASTE]
map/mod.rs:21-25— Removed#[allow(unused_imports)].META_EMPTYIS used withinmod.rsitself (line 50), so the re-export is correct. The plan claim thatMETA_EMPTYwas unused was incorrect. (2026-03-21) - [WASTE]
map/mod.rs— Removed deadelem_sizeparameter fromwrite_array_to_list_from_dataand updated 2 callers (ori_map_keys_to_list,ori_map_values_to_list). (2026-03-21) - [WASTE]
set/cow/basic.rs+set/cow/algebra.rs— Removed 5 dead_eacomputations. Renamedelem_alignparameter to_elem_alignin function signatures (parameter retained for ABI compatibility). (2026-03-21) - [STYLE]
set/mod.rs:98— decorative banner replaced with plain section comment (2026-03-21) - [STYLE]
map/mod.rs:228— decorative banner replaced with plain section comment (2026-03-21) - [BLOAT]
construction.rswas 513 lines — extractedemit_variant_via_allocaandemit_variant_via_insertvalueintovariant_construction.rs(169 lines).construction.rsnow 358 lines. (2026-03-21)
02.2 Iterator Creation and Drop
File(s): compiler/ori_llvm/src/codegen/arc_emitter/builtins/collections/list_builtins.rs, compiler/ori_rt/src/iterator/state.rs, compiler/ori_rt/src/iterator/sources.rs
STATUS UPDATE: The iter-rc-contract plan (2026-03-18) already fixed emit_list_iter (fn at line 126 of list_builtins.rs, fix at line 144) to pass self.get_or_generate_elem_dec_fn(elem_ty) instead of NULL. IterState::List Drop (line 170 of state.rs) passes this stored elem_dec_fn to ori_buffer_rc_dec. The iterator already carries the real function. This section’s remaining work is verifying the header provides a second safety net and deciding on parameter cleanup (Section 03).
With elem_dec_fn ALSO stored in the header at construction time (Section 02.1), there are now TWO mechanisms for element cleanup: (1) the parameter passed by the caller to ori_buffer_rc_dec, and (2) the header-stored function read by ori_buffer_rc_dec. The store_elem_dec_fn_once write-once pattern ensures they agree. This provides defense-in-depth: even if one path passes NULL, the header provides the function.
Parameter Retention Decision
- Decide: Keep or remove the
elem_dec_fnparameter inori_iter_from_list?- Decision: Option A — Keep parameter for defense-in-depth. Both the parameter and header provide
elem_dec_fn. Section 03 removes the parameter once the header-based approach is proven stable by the Section 04 test matrix. (2026-03-21)
- Decision: Option A — Keep parameter for defense-in-depth. Both the parameter and header provide
Integration Verification
- Verify: when iterator’s
ori_buffer_rc_deccall reaches zero, it readselem_dec_fnfrom the header and performs element cleanup — confirmed:drop_elements_and_freeatlist_rc.rs:39callsload_elem_dec_fn(header_data), not the caller parameter. Iterator Drop →ori_buffer_rc_dec→drop_elements_and_free→ reads from header. (2026-03-21) - Verify: when explicit RcDec’s
ori_buffer_rc_deccall reaches zero, same behavior — reads from header — confirmed: same code path. Both iterator and explicit decs go throughori_buffer_rc_decwhich always delegates todrop_elements_and_freereading from header. (2026-03-21) - Verify:
store_elem_dec_fn_onceCAS handles the case where both iterator Drop and explicit RcDec store to the same header — first non-NULL wins, second is a no-op. Confirmed:elem_header.rs:90-100usescompare_exchange(null → func)— CAS fails if already non-NULL. Unit testelem_dec_fn_store_once_first_non_null_winsexists. End-to-end exercised bytest_str_list_explicit_last_owner(both iterator dec and explicit dec hitstore_elem_dec_fn_onceon same buffer). (2026-03-21)
AOT Tests
-
[str]iteration where iterator dec reaches zero first —test_str_list_iter_last_ownerinelem_dec_scope.rs, zero leaks in debug + release (2026-03-21) -
[str]iteration where explicit dec reaches zero first —test_str_list_explicit_last_ownerinelem_dec_scope.rs, zero leaks in debug + release (2026-03-21) - Function parameter
[str]— callee iterates, caller uses after —test_str_list_fn_param_iterinelem_dec_scope.rs, no double-free, no leak in debug + release (2026-03-21) -
[str]iteration + slice —test_str_list_slice_then_iterinelem_dec_scope.rs, uses.take(count:)seamless slice, iterates original, zero leaks in debug + release (2026-03-21)
Cleanup
- [NOTE]
list_builtins.rs:115-125— Doc comment onemit_list_iterupdated to mention V5 header as second safety net forelem_dec_fn. (2026-03-21) - [DRIFT]
iterator/state.rs:49— Doc comment onIterState::Listupdated to explain V5 header defense-in-depth:elem_dec_fnstored in header viastore_elem_dec_fn_once, cleanup reads from header not parameter. (2026-03-21)
02.3 Map and Set Integration
File(s): compiler/ori_llvm/src/codegen/arc_emitter/rc_buffer_ops.rs, compiler/ori_rt/src/map/mod.rs, compiler/ori_rt/src/set/cow/basic.rs, compiler/ori_rt/src/set/cow/algebra.rs, compiler/ori_rt/src/set/mod.rs
STATUS UPDATE: The iter-rc-contract plan (2026-03-18) already fixed emit_map_iter to pass real key_dec_fn/val_dec_fn instead of NULL. Maps use the codegen-based approach (option c from Section 01.3). The __for_coll_N phantom only matches List | Set (not Map), but maps use ownership transfer (@iter(%map [own])) where the iterator’s Drop is the sole cleanup path.
Maps (Codegen-Based, Already Implemented)
- Verify
emit_map_iter(line 345 ofmap_builtins.rs) passes realkey_dec_fn/val_dec_fntoori_iter_from_map— confirmed viaget_or_generate_elem_dec_fnat lines 362-363.IterState::MapDrop at line 194 correctly passes*key_dec_fnand*val_dec_fntoori_map_buffer_rc_dec. Maps use codegen-based approach (two dec functions). (2026-03-21)
Map Double-Free Investigation
- Map double-free root cause identified and fixed (2026-03-21). Root cause:
ori_map_keys_to_listandori_map_values_to_listcopied element structs viacopy_nonoverlappingwithout incrementing RC children. Map and output list shared RC-tracked data (e.g., string data pointers) with only one reference count. Fix: addedkey_inc_fn/val_inc_fnparameters (generated byget_or_generate_elem_inc_fn); each copied element gets RcInc. Same fix applied toori_set_to_listwithelem_inc_fn. ABI sync: runtime + LLVM declarations + codegen call sites updated atomically.test_map_keys_str_scope_dropun-ignored — passes in debug + release. - [BUG] 3 Valgrind failures (
cow_leak_scenarios.ori,cow_map_insert_remove.ori,cow_nested.ori) — map insert double-free. Root cause:ori_map_insert_cowcopies key/value into hash buffer viacopy_nonoverlappingwithout callingkey_inc/val_inc. The caller’s borrowed reference gets RcDec’d (freeing the data), then the map’s drop also decs the buffer copy. Fix: addedkey_inc/val_inccalls after every new key/value insertion — 3 sites incow_insert_new(fast direct, fast rehash, slow) + 1 site inslow_copy_overwrite_value(inc the new value at overwrite bucket). Also inc’d new value in fast-path overwrite. AOT tests:test_map_insert_heap_str_key,test_map_insert_heap_str_value,test_map_cow_insert_shared_heap_key. All 13,500 tests pass. (2026-03-21) - [BUG][TPR-02-012]
cow_insert_existingfast path (unique) leaks the old value when overwriting — fixed by addingval_decparameter toori_map_insert_cow/cow_insert_existing/ori_map_update_cow. Fast path now callsval_dec(old_val_ptr)before overwriting. ABI sync: runtime + LLVM declaration (14 params) + codegen (emit_map_insertpassesget_or_generate_elem_dec_fn(val_ty)). AOT tests:test_map_insert_overwrite_heap_str_value(single),test_map_insert_overwrite_shared_heap_str_value(shared/slow path),test_map_insert_overwrite_multiple_heap_str_value(repeated). All pass in debug + release withORI_CHECK_LEAKS=1. (2026-03-21)
Branch-Local RcDec in Merge Blocks (TPR-02-007 + TPR-02-008)
The ARC pipeline places RcDec for branch-local variables in the post-merge block instead of in their respective branch blocks. When one branch is taken, the other branch’s variables are undefined at the merge point, and the LLVM emitter silently skips the dec (skipping RcDec on undefined variable). This causes double-frees: the taken branch’s variable gets its own cleanup PLUS the merge-block cleanup, while the untaken branch’s cleanup is silently dropped.
Root cause: propagate_project_source_demand (from TPR-02-006 fix) adds demand for ALL sources of a multi-predecessor block param at the merge block’s entry. This causes branch-local parent aggregates to bleed into the opposite branch’s state, producing RcDec for undefined variables in merge blocks and on branch edges. The backward analysis demand propagation is correct (conservative: keeps parent aggregates alive per-path), but the emission layer was treating merged demand as block-level RcDec.
Fix (2026-03-21): three-layer emission filter:
- Phase A (
emit_dead_at_entry_decs): at merge blocks, detect branch-local variables (not defined in ALL predecessors) and route to per-predecessor edge cleanup instead of block-level RcDec. - Edge cleanup (
collect_branch_edge_decs+collect_invoke_edge_decs): filter exit-state variables againstdefined_at_or_beforeset — skip variables whose defining block is downstream of the branching/invoke block. - Merge-edge routing (
emit_rc_unified): Phase A returns merge-edge decs; caller routes them toblock_deferredfor the specific predecessor that defines the variable, producing per-predecessor trampolines via edge cleanup.
Files changed: aims/emit_rc/dead_cleanup.rs, aims/emit_rc/edge_cleanup.rs, aims/realize/emit_unified.rs, aims/intraprocedural/project_aliases.rs.
- [BUG] Identify the ARC lowering pass that inserts
RcDecfor branch-local variables in the merge block — root cause:propagate_project_source_demandat merge block entry injects branch-local parent demand into shared entry state. (2026-03-21) - [BUG] Fix the ARC pipeline to emit branch-local cleanup in the correct block — three-layer emission filter (Phase A merge-block routing + edge cleanup defined_at_or_before filter + per-predecessor trampoline routing). Verified with
ORI_DUMP_AFTER_ARC=1: no RcDec in merge/unwind blocks references branch-local variables. (2026-03-21) - [BUG] Replace
test_rc_project_merge_two_distinct_parentswith heap-allocated strings (>23 bytes SSO threshold). Verified zero leaks and zero double-frees viaORI_CHECK_LEAKS=1in debug + release. (2026-03-21) - [BUG] Add
debug_assert!in edge cleanup that verifies merge-edge decs target valid successors: added assertions in both non-Invoke (edge_cleanup.rs:118-124) and Invoke (edge_cleanup.rs:93-101) paths. Catches misrouted merge-edge decs that target a block not in the predecessor’s successor list. (2026-03-21) - [BUG][TPR-02-010] Fix merge-edge cleanup to preserve successor identity: extended
block_deferredfrom(ArcVarId, RcStrategy)to(Option<usize>, ArcVarId, RcStrategy)—None= all edges (Phase B deferred parents),Some(succ)= only target edge (merge-edge decs). Updatedemit_unified.rsrouting andedge_cleanup.rsemission to filter by target. Addedtest_rc_project_merge_edge_scoped_cleanup(heap strings, function calls) and enhancedtest_rc_project_merge_two_distinct_parents(exercises both branches). All 13,500 tests pass (debug + release). (2026-03-21)
Sets (Header-Based, Same as Lists)
Set construction codegen is complete (see 02.1 “Set Construction Codegen” — elem_dec_fn and elem_count stored at literal construction time). Remaining work:
- Verify set iteration codegen:
emit_list_iteris also called for sets (dispatched atbuiltins/collections/mod.rsline 463 as("Set", "iter") => emit_list_iter). Sets share theori_buffer_rc_deccleanup path with lists for iterator Drop.ori_set_buffer_rc_decinrc/set_rc.rscorrectly scans META_OCCUPIED buckets and callselem_dec_fnper element. Headerelem_dec_fnprovides defense-in-depth. (2026-03-21) - Set COW slow path propagation: all set COW functions propagate
elem_dec_fnvia centralizedalloc_set_hash_bufferandrehash_set. Completed in 02.1. (2026-03-20) -
ori_set_to_list(set/mod.rsline 55): stores bothelem_dec_fnandelem_countin list buffer. Completed in 02.1. (2026-03-20)
Collect RcInc Bug (TPR-02-009 + Discovery)
Both ori_iter_collect (list) and ori_iter_collect_set (set) shallow-copy elements via copy_nonoverlapping without incrementing child RCs. When the iterator is dropped, ori_buffer_rc_dec fires elem_dec_fn on source elements, freeing child data. The collected target then has dangling pointers → double-free. Same bug pattern as pre-fix ori_map_keys_to_list/ori_set_to_list.
List collect (ori_iter_collect in iterator/consumers.rs:20):
- [BUG] Add
elem_inc_fnparameter toori_iter_collectruntime function signature (2026-03-21) - [BUG] Call
elem_inc_fnafter eachcopy_nonoverlappinginto the list buffer (consumers.rs:54) (2026-03-21) - [BUG] Update
emit_iter_collectcodegen to passelem_inc_fnthunk viaget_or_generate_elem_inc_fn(2026-03-21) - [BUG] Update runtime function declaration in
runtime_functions.rs(add ptr param for elem_inc_fn) (2026-03-21) - [BUG] JIT symbol mapping in
runtime_mappings.rsuses function pointer — no change needed (auto-resolved by link-time binding) (2026-03-21) - AOT test:
[str].iter().collect()with heap strings (>23 bytes) —test_str_list_method_collectpasses withORI_CHECK_LEAKS=1in debug + release. Note:for x in items yield xuses explicit loop (notori_iter_collect), so the method.collect()path is the correct test target. (2026-03-21)
Set collect (ori_iter_collect_set in iterator/consumers.rs:85):
- [BUG] Add
elem_inc_fnparameter toori_iter_collect_setruntime function signature (2026-03-21) - [BUG] Call
elem_inc_fnafter eachcopy_nonoverlappinginto the set slot (consumers.rs:152) (2026-03-21) - [BUG] Update
emit_iter_collect_setcodegen to passelem_inc_fnthunk viaget_or_generate_elem_inc_fn(2026-03-21) - [BUG] Update runtime function declaration in
runtime_functions.rs(add ptr param for elem_inc_fn) (2026-03-21) - [BUG] JIT symbol mapping in
runtime_mappings.rsuses function pointer — no change needed (auto-resolved by link-time binding) (2026-03-21) - AOT test:
Set<str>collect with heap strings (>23 bytes) — zero leaks viaORI_CHECK_LEAKS=1in debug + release. Unblocked after trampoline ABI fix.test_set_str_iter_collectadded toelem_dec_scope.rs. (2026-03-21)
AOT Tests
-
{str: int}map iteration — zero leaks viaORI_CHECK_LEAKS=1(10x stability check passed). AOT testtest_map_str_iterationadded toelem_dec_scope.rs. (2026-03-21) -
Set<str>iteration — zero leaks. AOT testtest_set_str_iterationpasses in debug + release. (2026-03-21) -
{str: int}map passed to function, iterated inside — zero leaks. AOT testtest_map_str_passed_to_fnadded toelem_dec_scope.rs. (2026-03-21) -
Set<str>passed to function, iterated inside — zero leaks. AOT testtest_set_str_passed_to_fnpasses in debug + release. (2026-03-21) -
Set<str>COW insert on shared set — zero leaks. AOT testtest_set_str_cow_insert_sharedpasses in debug + release. Required fix: addedinc_fncall after new element insertion in all 3 paths ofori_set_insert_cow. (2026-03-21) -
Set<str>union — zero leaks. AOT testtest_set_str_unionpasses in debug + release. Required fix: addedinc_fncall in union fast path for elements from set2. (2026-03-21) -
Set<str>intersection/difference — [TPR-02-014] Fixed: fast paths now callelem_dec_fnbefore tombstoning. AOT teststest_set_str_intersection_uniqueandtest_set_str_difference_uniquepass in debug + release withORI_CHECK_LEAKS=1. (2026-03-21) -
map.keys()on{str: int}— zero leaks.test_map_keys_str_scope_dropAOT test passes in debug + release. Exercisesori_map_keys_to_listwithkey_inc_fn. (2026-03-21)
Set Remove Fat-Pointer Leak (TPR-02-013)
ori_set_remove_cow leaks fat-pointer elements on all 3 paths: last-element ori_rc_free without element dec, tombstoning without dec, and slow-path skip without dec. Fix: add elem_dec_fn parameter and call it before tombstoning/freeing/skipping.
- [BUG] Add
elem_dec_fn: Option<extern "C" fn(*mut u8)>parameter toori_set_remove_cow(set/cow/basic.rs) (2026-03-21) - [BUG] Fast path (unique, tombstone): call
elem_dec_fnon removed element beforeset_meta(META_TOMBSTONE)(line ~206) (2026-03-21) - [BUG] Fast path (unique, last element): call
elem_dec_fnon removed element beforeori_rc_free(line ~195) (2026-03-21) - [BUG] Slow path (shared): removed element gets dec’d by
ori_rc_dec(data, None)— old buffer’sset_buffer_cleanuphandles it since the element staysMETA_OCCUPIEDin the old buffer. No explicit dec needed on the skip. (2026-03-21) - [BUG] Update LLVM IR declaration in
runtime_functions.rs— addTy::Ptrforelem_dec_fn(2026-03-21) - [BUG] Update codegen call site in
set_builtins.rs— passget_or_generate_elem_dec_fn(elem_ty)(2026-03-21) - [BUG] ABI sync: all 3 changes (runtime + LLVM decl + codegen) committed together (2026-03-21)
- AOT test:
Set<str>.remove()with remaining elements — zero leaks (test_set_str_remove_remaining, debug + release) (2026-03-21) - AOT test:
Set<str>.remove()removing last element — zero leaks (test_set_str_remove_last_element, debug + release) (2026-03-21)
Set Intersection/Difference Fat-Pointer Leak (TPR-02-014)
ori_set_intersection_cow and ori_set_difference_cow unique fast paths tombstone elements without calling elem_dec_fn. set_buffer_cleanup only iterates META_OCCUPIED buckets, so tombstoned elements are never cleaned.
- [BUG]
ori_set_intersection_cowfast path (unique): callelem_dec_fnon each element before tombstoning (line ~236 ofalgebra.rs) (2026-03-21) - [BUG]
ori_set_difference_cowfast path (unique): callelem_dec_fnon each element before tombstoning (line ~354 ofalgebra.rs) (2026-03-21) - [BUG]
elem_dec_fnavailable viaload_elem_dec_fn(d1)from header — no parameter change needed (2026-03-21) - [BUG] Also fixed: intersection empty edge case (n2==0, d1 unique with elements) — dec each element before
ori_rc_free(2026-03-21) - AOT test:
Set<str>.intersection()on unique set — zero leaks (test_set_str_intersection_unique, debug + release) (2026-03-21) - AOT test:
Set<str>.difference()on unique set — zero leaks (test_set_str_difference_unique, debug + release) (2026-03-21)
Map Remove Fat-Pointer Leak (Discovered During TPR-02-013 Investigation)
ori_map_remove_cow (map/cow.rs) has the same bug pattern as ori_set_remove_cow: fast path tombstones key/value without calling key_dec_fn/val_dec_fn, and slow path skips without dec. Same fix pattern: plumb dec functions and call before tombstoning/freeing/skipping.
- [BUG] Add
key_dec_fnandval_dec_fnparameters toori_map_remove_cow(map/cow.rs) (2026-03-21) - [BUG] Fast path (unique, tombstone): call
key_dec_fn+val_dec_fnon removed entry before tombstoning (2026-03-21) - [BUG] Fast path (unique, last element): call
key_dec_fn+val_dec_fnon removed entry beforeori_rc_free(2026-03-21) - [BUG] Slow path (shared): removed entry’s cleanup handled by
ori_rc_dec(data, None)— when last owner drops,map_buffer_cleanupfires on all META_OCCUPIED entries including the removed one. No explicit dec needed on the skip. (2026-03-21) - [BUG] Update LLVM IR declaration in
runtime_functions.rs— addTy::Ptrfor both (2026-03-21) - [BUG] Update codegen call site in
map_builtins.rs— pass both dec fns viaget_or_generate_elem_dec_fn(2026-03-21) - [BUG] ABI sync: all 3 changes (runtime + LLVM decl + codegen) committed together (2026-03-21)
- AOT test:
{str: int}map remove — zero leaks (test_map_remove_str_key, debug + release) (2026-03-21) - AOT test:
{str: int}map remove last element — zero leaks (test_map_remove_str_key_last, debug + release) (2026-03-21)
Cleanup
- [NOTE]
map_builtins.rs:320-330— Doc comment onemit_map_itercorrectly documents that real dec functions are passed. No update needed. (2026-03-21, verified) - [DRIFT]
set/cow/basic.rsandset/cow/algebra.rs— doc comments updated forelem_dec_fnpropagation in earlier session. (2026-03-21, verified — already marked in 02.N cleanup)
02.R Third Party Review Findings
-
[TPR-02-001][medium]plans/rc-header-elem-dec/section-02-integration.md:1— Section 02 currently advertises conflicting progress states across its own metadata and the plan index. Evidence: This file’s frontmatter already saysstatus: in-progressand02.Nisin-progress, but the section body still says**Status:** Not Started, andplans/rc-header-elem-dec/index.md:35still lists Section 02 asNot Started. Impact: Readers cannot tell whether Section 02 has merely been replanned or has actually begun, which makes dependency tracking and downstream plan updates unreliable. Required plan update: Pick a single state for Section 02 and sync the frontmatter, body,02.N, and index entry in one pass. Resolved: Fixed on 2026-03-20. Body status synced to “In Progress” matching frontmatter; index.md Section 02 status updated to “In Progress”. -
[TPR-02-002][high]compiler/ori_llvm/src/codegen/runtime_decl/runtime_functions.rs:141— The new header-store runtime helpers are marked AOT-only even though the shared ARC emitter now calls them on the JIT path as well. Resolved: Fixed on 2026-03-20. Setjit_allowed: truefor bothori_buffer_store_elem_decandori_buffer_store_elem_countinruntime_functions.rs. Added symbol mappings inruntime_mappings.rs.jit_symbol_mappings_match_jit_allowedtest confirms sync. All 1810 AOT tests pass. -
[TPR-02-003][low]compiler/ori_llvm/src/tests/evaluator_tests.rs:1— The regression fix for JIT symbol availability is not covered by a functional JIT test that actually compiles a collection literal or reuse path through MCJIT. Evidence:cargo test -p ori_llvm evaluator_tests -- --listshows only 8 evaluator unit tests, all structural;compiler/ori_llvm/src/tests/evaluator_tests.rscoversLLVMValue, error types, evaluator construction, and empty-module compilation, but no test exercises list/set literal codegen orori_list_reset_bufferon the JIT path. Impact: The exact failure mode fixed by TPR-02-002 can regress without being caught by the existing evaluator suite; the current guard rails only prove declaration/mapping consistency (jit_symbol_mappings_match_jit_allowed), not end-to-end execution through MCJIT. Required follow-up: Add a focused evaluator integration test that JIT-compiles a function using at least one list or set literal, and ideally a collection-reuse path, then executes it successfully. Resolved: Accepted on 2026-03-20. Finding is factually correct — no functional JIT test exercises collection literal construction. Spec tests exercise JIT indirectly but provide no explicit regression guard. Integrated as a task in 02.N Cleanup. -
[TPR-02-004][low]compiler/ori_rt/src/list/mod.rs:83— Section 02 touched files still violate mandatory hygiene rules with stale RC-header comments and decorative section banners. Resolved: Validated on 2026-03-21. Accepted — all 3 stale “8-byte refcount header” refs confirmed at list/mod.rs:83,131,199 and 4 decorative banners confirmed (list/mod.rs:96, map/mod.rs:242, iterator/consumers.rs:11, fat_ptr_iter.rs:15). Already integrated as cleanup tasks in 02.N (lines 425-436). -
[TPR-02-005][high]compiler/ori_llvm/src/codegen/function_compiler/entry_point.rs:85— The production@main(args: [str])AOT path is still broken, so Section 02’sori_args_from_argvwork has no end-to-end verification and the current safety note overstates reality. Evidence: Fresh verification on 2026-03-21 withtimeout 150 cargo run -p oric --bin ori -- build /tmp/main_args_len.orifor@main (args: [str]) -> int = args.len();fails LLVM verification:Call parameter type does not match function signature! ... call i64 @_ori_main({ i64, i64, ptr } %args). The wrapper inentry_point.rsmaterializesori_args_from_argvviacall_with_sret(...)and forwards the loaded list struct directly to_ori_main, but there is no coverage for an args-bearing main signature incompiler/ori_llvm/src/codegen/function_compiler/tests.rsand no AOT/spec test exercises@main(args: [str]). Impact: Any AOT program using the supported@main(args: [str])signature is currently uncompilable, and the Section 02 claim atplans/rc-header-elem-dec/section-02-integration.md:104that deferredelem_dec_fnhandling is “safe” cannot be validated on the only production caller forori_args_from_argv. Required plan update: Fix the main-wrapper ABI/signature mismatch for args-bearing mains, add an end-to-end AOT regression test for@main(args: [str]), then re-evaluate whether deferringelem_dec_fninori_args_from_argvis still justified once slice/take/drop paths are executable. Resolved: Validated and accepted on 2026-03-21. Bug confirmed —generate_main_wrapperloads sret result as{i64, i64, ptr}value but_ori_mainexpectsptr(Indirect ABI for 24-byte struct). Root cause: wrapper doesn’t consult callee’s param ABI. Integrated as blocking task in 02.N. -
[TPR-02-006][high]compiler/ori_arc/src/aims/intraprocedural/project_aliases.rs:38— The new block-param alias closure is still unsound at CFG merges because it records only oneProjectsource per block parameter, even though a merge param may receive projected values from multiple predecessor aggregates. Resolved: Fixed on 2026-03-21. ChangedFxHashMap<ArcVarId, ArcVarId>→FxHashMap<ArcVarId, SmallVec<[ArcVarId; 1]>>(type aliasProjectSources).merge_sources()helper performs set-union at merge points.propagate_project_source_demand()now iterates all sources. Tests:compute_project_alias_sources_multi_predecessor_merge(unit),project_block_param_multi_predecessor_merge_propagates_all_source_demand(semantic pin),test_rc_project_merge_two_distinct_parents(AOT, debug + release). All 13,494 tests pass. -
[TPR-02-007][medium]compiler/ori_llvm/tests/aot/arc.rs:994— The new AOT regression for TPR-02-006 does not distinguish the fixed multi-predecessor behavior from the old single-predecessor bug, so Section 02’s end-to-end closure claim is overstated. Evidence: The test condition atarc.rs:1004is deterministically true (p1.first.len() > 0), so the runtime always takes thethenpredecessor.lower_if()creates and lowers thethenblock before theelseblock (compiler/ori_arc/src/lower/control_flow/mod.rs:149-180), and the pre-fixcompute_project_alias_sources()only preserved the first predecessor source viaEntry::Vacantinsertion. Fresh verification withORI_DUMP_AFTER_ARC=1 target/debug/ori buildon this exact test program still lowers to a merge blockbb5(%17: str)reached first from the takenthenpath (bb3 -> Jump bb5(%14)), so the old unsound implementation could still pass this AOT case while dropping the else-parent source. Impact: The ARC unit semantic pin proves the lattice fix locally, but the current AOT guard can pass without proving that both predecessor aggregates survive end-to-end. A regression that re-drops the else predecessor would remain undetected by the claimed AOT coverage. Required plan update: Replace or augment the AOT case with a branch that exercises both predecessors across runs, or otherwise forces execution through the predecessor that the old single-source map dropped, before re-closing TPR-02-006’s AOT verification claim. Resolved: Accepted on 2026-03-21. Confirmed — condition is always true, strings are SSO (under 23-byte threshold, no RC ops), and when modified to use heap strings with runtime-variable condition, the program double-frees. Integrated as blocking tasks in 02.3 (ARC pipeline fix) and 02.N (test replacement). -
[TPR-02-008][medium]compiler/ori_llvm/tests/aot/arc.rs:994— The new TPR-02-006 AOT program does not currently compile through a clean ARC/LLVM path: the compiler logs undefined-variable emitter errors and silently drops twoRcDecs while building it. Evidence: Fresh verification withORI_DUMP_AFTER_ARC=1 target/debug/ori buildon the exact source fromarc.rs:994emits branch-local cleanupRcDec %13/RcDec %15before those vars are defined in the taken branches, then logsArcIrEmitter: variable not yet definedfromcompiler/ori_llvm/src/codegen/arc_emitter/emitter_utils.rs:170andskipping RcDec on undefined variablefromcompiler/ori_llvm/src/codegen/arc_emitter/rc_ops.rs:94. The build still succeeds only because the emitter treats the invalid RC ops as skippable. Impact: This AOT regression currently passes on top of a masked RC-emission error, so it does not provide trustworthy end-to-end evidence for the fix. Silent dropping of RC ops on undefined vars can hide leaks or ownership regressions instead of surfacing them as compiler failures. Required plan update: Identify the pass that inserts the pre-definitionRcDecs on this branch/merge shape, add a verifier or debug assertion that RC emission never relies onemit_rc_dec()’s undefined-var skip path, and add an IR-quality regression for this exact source before using it as closure evidence. Resolved: Accepted on 2026-03-21. Confirmed —RcDec %13/%15in merge block are for branch-local vars not defined in both predecessors. Root cause: ARC pipeline places branch-local cleanup in the merge block instead of in the branch blocks. Integrated as blocking task in 02.3 (ARC pipeline fix). -
[TPR-02-009][high]compiler/ori_rt/src/iterator/consumers.rs:85—__collect_setstill shallow-copies RC-tracked elements into the new set buffer without incrementing child RCs, soSet<str>AOT programs double-free immediately. Evidence: Fresh verification on 2026-03-21 withtarget/debug/ori build /tmp/set_str_len.ori -o /tmp/set_str_len_bin && ORI_CHECK_LEAKS=1 /tmp/set_str_len_binaborts withori_rc_dec called on already-freed allocation, and the same reproducer aborts in release. The runtime copy inori_iter_collect_setonly doescopy_nonoverlappinginto the hash-table slot (iterator/consumers.rs:150-153); unlikeori_map_keys_to_list/ori_set_to_list, there is noelem_inc_fncall after the copy, andemit_iter_collect_setdoes not pass an increment thunk either (compiler/ori_llvm/src/codegen/arc_emitter/builtins/iterator_consumers.rs:72-125). Impact: Section 02’s set integration is not correct for fat-pointer elements. Any AOT path that materializesSet<str>viaiter().collect()aliases string children between the source iterator input and the destination set with only one reference count, leading to double-free before downstream set operations like.to_list()are even reached. Required plan update: Addelem_inc_fnplumbing toori_iter_collect_setandemit_iter_collect_set, then add permanentORI_CHECK_LEAKS=1AOT coverage forSet<str>construction andSet<str>.to_list(). Resolved: Validated and accepted on 2026-03-21. Confirmed —ori_iter_collect_setcopies elements without child RC increment, causing double-free for fat-pointer elements. Same bug pattern as pre-fixori_map_keys_to_list/ori_set_to_list. Also discovered identical bug inori_iter_collect(list collect) — same shallow copy withoutelem_inc_fn. Both integrated as fix tasks in 02.3. -
[TPR-02-010][high]compiler/ori_arc/src/aims/realize/emit_unified.rs:143— The new merge-edge cleanup routing loses successor identity, so branch-localRcDecs selected for one merge edge are replayed on every outgoing edge of the defining predecessor. Evidence:emit_dead_at_entry_decs()now returnsmerge_edge_decsfor a specific merge block (dead_cleanup.rs:80-100).emit_unified()routes them by appending(var, strategy)intoblock_deferred[pred_idx]with no successor key (emit_unified.rs:143-155).emit_edge_cleanup()then treats every deferred entry as predecessor-wide and emits it on all successor edges of that predecessor, or on both normal and unwind edges forInvoke(edge_cleanup.rs:80-99). Impact: The current fix is only correct when each defining predecessor has exactly one outgoing edge. In a wider CFG, cleanup chosen for one merge edge can fire on unrelated edges, causing premature drops, double-frees, or unwind-path cleanup for values that are not live there. Required plan update: Represent merge-edge decs with successor scope (for example(pred, succ, var, strategy)), route them only to the specific merge successor, and add a regression where the defining predecessor has multiple outgoing edges so this cannot silently regress again. Resolved: Validated and accepted on 2026-03-21. Bug confirmed —block_deferredstores only(var, strategy)with no successor index,edge_cleanup.rs:95-99emits on ALL successors. Latent for single-successor predecessors, manifests for multi-successor (Branch/Switch/Invoke). Integrated as blocking task in 02.3. -
[TPR-02-011][low]compiler/ori_arc/src/aims/emit_rc/edge_cleanup.rs:1— The unstaged merge-edge follow-up pushesedge_cleanup.rspast the 500-line hygiene limit without extracting a helper/module. Evidence:wc -l compiler/ori_arc/src/aims/emit_rc/edge_cleanup.rsreports 529 lines in the current tree, and this file was modified as part of the same fix. Impact: The review surface for RC edge behavior is getting harder to audit precisely where correctness is most sensitive, which raises regression risk for future ARC cleanup work. Required plan update: Split the new merge-edge filtering helpers into a focused sibling module before adding more RC edge logic here. Resolved: Validated and accepted on 2026-03-21. Confirmed 529 lines. Integrated as cleanup task in 02.N — split merge-edge filtering helpers into sibling module before further ARC edge work. -
[TPR-02-012][high]compiler/ori_rt/src/map/cow.rs:104—ori_map_insert_cowstill has an unsound overwrite path for existing keys with RC-tracked values, and the current plan understates it as a low-priority leak. Resolved: Validated and accepted on 2026-03-21. Bug confirmed — fast-pathcow_insert_existingoverwrites old value withoutval_dec_fn, leaking RC-tracked children. User-visible AOT crash, not a leak. Existing item in 02.3 updated from “Low priority” to blocking. Fix: plumbval_dec_fnthroughori_map_insert_cow/cow_insert_existing/ LLVM declarations / codegen. -
[TPR-02-013][high]compiler/ori_rt/src/set/cow/basic.rs:190—ori_set_remove_cowstill leaks fat-pointer elements on unique-owner removals. Resolved: Validated and accepted on 2026-03-21. Bug confirmed across all 3 paths: (1) last-element free viaori_rc_freewithout element dec, (2) tombstoning without element dec, (3) slow path skips removed element without dec. Fix: addelem_dec_fnparameter, call before tombstoning/freeing/skipping. Also discovered:ori_map_remove_cowhas identical bug pattern. Integrated as blocking tasks in 02.3. -
[TPR-02-014][high]compiler/ori_rt/src/set/cow/algebra.rs:224— The unique fast paths forSet.intersection()andSet.difference()still leak filtered-out fat-pointer elements. Resolved: Validated and accepted on 2026-03-21. Bug confirmed —set_buffer_cleanuponly iteratesMETA_OCCUPIEDbuckets, tombstoned elements are never cleaned. Fix: callelem_dec_fnon each element before tombstoning in intersection (line 236) and difference (line 354). Integrated as blocking tasks in 02.3. -
[TPR-02-015][high]compiler/ori_rt/src/set/cow/basic.rs:193—ori_set_remove_cowstill double-decrements fat-pointer elements when removing the last element from a shared set. Resolved: Validated and fixed on 2026-03-21. Movedelem_dec_fncall inside theis_uniquebranch in thenew_len == 0path. Shared sets now onlyori_rc_dec(data, None)— surviving aliases handle element cleanup when buffer refcount reaches zero. Two permanent AOT regression tests added:test_set_str_remove_last_sharedandtest_set_str_remove_last_shared_only_alias_survives. Both pass debug + release withORI_CHECK_LEAKS=1. -
[TPR-02-016][high]compiler/ori_llvm/src/codegen/function_compiler/entry_point.rs:132— The new@main(args: [str])cleanup only runs on the normal-return path, so args-backed heap strings still leak whenever_ori_mainunwinds. Evidence: Current wrapper code emitscall @_ori_main(...)and only thencall @ori_args_cleanup(...)at entry_point.rs and entry_point.rs. Fresh IR verification on 2026-03-21 withORI_DEBUG_LLVM=1 target/debug/ori build /tmp/argspanic.ori -o /tmp/argspanic_binfor@main(args: [str]) -> void = panic(msg: "boom")showsdefine i32 @main(i32, ptr)containingcall void @_ori_main(ptr %args.indirect)followed bycall void @ori_args_cleanup(ptr %args.data, i64 %args.len), with noinvokeor landingpad inmain, while_ori_mainitself carries a personality function and can unwind viainvoke void @ori_panic. Impact: The newly added success-path tests in cli.rs prove only the non-panicking path. Any supported AOT program using@main(args: [str])that panics will skip bothori_args_cleanupand the wrapper leak check, leaving the argv-derived[str]buffer and any heap string children unfreed. Required plan update: Move args cleanup onto an unwind-safe path (for example, aninvoke/landingpad in the wrapper or a callee-owned cleanup strategy), then add panic-path coverage for@main(args: [str])so the leak-free guarantee is exercised on both normal return and unwind. Resolved: Validated and accepted on 2026-03-21. Bug confirmed — wrapper uses plaincallfor_ori_main, cleanup at L154 only runs on normal return. When_ori_maincan unwind (not in nounwind set), args leak on panic. Fix: useinvoke+landingpadfor_ori_maincall when not nounwind, cleanup in both normal and unwind paths. Integrated as task in 02.N. -
[TPR-02-017][medium]compiler/ori_llvm/tests/aot/cli.rs:927— The new args-wrapper IR semantic pins are not release-safe: they rely oncompile_and_capture_ir()even though releaseoribinaries intentionally emit no LLVM IR, so the claimed debug+release verification is false and the release AOT suite now fails when these tests run. Resolved: Validated and fixed on 2026-03-21. Addedif !ir.contains("define ") { return; }guard to all 3 IR semantic pin tests, matching the pattern used by all other IR quality tests. Tests now pass in both debug (assertions checked) and release (gracefully skipped). -
[TPR-02-018][medium]tests/valgrind/iter_rc/map_keys_values_fat.ori:3— The newmap.keys()/map.values()Valgrind pin still exercises only fat-pointer keys, not fat-pointer values. Resolved: Validated and fixed on 2026-03-21. Added AOT teststest_map_values_heap_str_values({int: str}) andtest_map_values_str_str({str: str}) inelem_dec_scope.rs, plus Valgrind testmap_values_fat_values.ori. All pass debug + release withORI_CHECK_LEAKS=1and Valgrind clean. -
[TPR-02-019][medium]compiler/ori_rt/src/map/cow.rs:373— The newval_deccleanup branches inori_map_remove_coware still unproven by the added tests. Resolved: Validated and fixed on 2026-03-21. Added AOT teststest_map_remove_heap_str_value(unique fast path, cow.rs:391) andtest_map_remove_heap_str_value_last(empty sentinel path, cow.rs:373) inelem_dec_scope.rs, plus Valgrind testmap_remove_fat_values.ori. All pass debug + release withORI_CHECK_LEAKS=1and Valgrind clean. -
[TPR-02-020][medium]compiler/ori_llvm/tests/aot/cli.rs:876— The new panic-path@main(args: [str])tests do not actually prove “no segfault” or leak-free cleanup on unwind. Resolved: Validated and fixed on 2026-03-21. THREE bugs found and fixed: (1)exit_code_from_status()now detects Unix signal termination (SIGSEGV → -139, SIGABRT → -134) instead of mapping all signals to-1—assert_no_signal_crash()catches post-panic crashes. (2) Critical: main wrapper used cleanup landingpad (not catch-all) — Phase 1 search found no handler →_URC_END_OF_STACK→ args never cleaned up. Fixed to uselandingpad catch ptr null+ori_catch_cleanup+ori_args_cleanup+ret 1. (3) Addedtest_main_args_panic_valgrind_clean— Valgrind now verifies zero memory errors on panic-path args cleanup. IR semantic pin updated to assertcatch ptr null(not justlandingpad). -
[TPR-02-021][low]compiler/ori_llvm/src/codegen/runtime_decl/tests.rs:333— The claimed JIT regression guard for RC-header helpers is still not a functional MCJIT test. Resolved: Validated and fixed on 2026-03-21. Addedtest_jit_str_list_constructionincli.rs— runsori test --backend=llvmon a[str]list literal, forcing MCJIT to resolveori_buffer_store_elem_dec+ori_buffer_store_elem_count. Supplements (does not replace) the symbol-table assertion. Both symbol registration and functional execution are now covered. -
[TPR-02-022][high]compiler/ori_llvm/src/codegen/function_compiler/entry_point.rs:266— The new SEH unwind cleanup path for@main(args: [str])emits a plain call from inside acleanuppad, which violates the repo’s own SEH builder contract and leaves the Windows/MSVC path unsound. Resolved: Validated and fixed on 2026-03-21. Changedself.builder.call(cleanup_fn, ...)toself.builder.call_with_funclet(cleanup_fn, ..., pad, "")in the SEH branch atentry_point.rs:268. All calls inside SEH funclet pads now usecall_with_funcletwith the operand bundle, matching the contract inseh.rs:173. All 13,540 tests pass debug + release. -
[TPR-02-023][low]compiler/ori_llvm/src/codegen/function_compiler/entry_point.rs:1—entry_point.rsis back over the 500-line hygiene limit after the new main-wrapper unwind work landed. Resolved: Validated and fixed on 2026-03-21. Extractedgenerate_panic_trampoline()(~183 lines) topanic_trampoline.rssibling submodule.entry_point.rsis now 334 lines,panic_trampoline.rsis 201 lines — both well under the 500-line limit. Module doc andmod.rsupdated. -
[TPR-02-024][medium]compiler/ori_llvm/src/codegen/arc_emitter/builtins/trampolines.rs:246— The fat-pointer trampoline fix is still missing a semantic pin for thefor_eachbranch, even though the current patch claims all four trampoline kinds are covered. Resolved: Validated and fixed on 2026-03-22. Addedtest_trampoline_for_each_strinelem_dec_scope.rs— exercises[str].iter().for_each(action: s -> { let $n = s.len(); n })with heap-allocated strings (>23 bytes, fat-pointer ABI). Passes debug + release. All four trampoline kinds (Map, Predicate, Fold, ForEach) now have fat-pointer semantic pins. -
[TPR-02-025][low]compiler/ori_rt/src/list/cow_sort.rs:1— The touched runtime sort helper still violates the repository’s 500-line hygiene limit. Resolved: Validated and fixed on 2026-03-22. Convertedcow_sort.rs(521 lines) to directory modulecow_sort/. Extracted sort + permutation logic tocow_sort/sort.rs(215 lines). Parentcow_sort/mod.rsretains concat, reverse, and shared helpers (324 lines). Both files well under 500-line limit. -
[TPR-02-026][high]compiler/ori_llvm/src/codegen/function_compiler/entry_point.rs:180— The current@main(args: [str])wrapper still unconditionally runsori_args_cleanup, even when_ori_mainreceives the argv list via owned indirect ABI and already frees it itself. Resolved: Validated and fixed on 2026-03-22. AddedMainArgsCleanupstruct withwrapper_owns_on_normalflag — settrueforParamPassing::Reference(callee borrows, wrapper cleans up) andfalseforIndirect/Direct(callee owns, wrapper skips normal-path cleanup but still cleans up on unwind because callee’s ARC dec hasn’t run). Added 5 tests:test_main_args_owned_path_no_double_free(SSO strings),test_main_args_owned_path_heap_strings(fat pointers),test_main_args_owned_path_int_return(int return variant),test_main_args_owned_path_valgrind_clean(Valgrind verification),test_main_args_owned_wrapper_ir_no_normal_cleanup(IR semantic pin: cleanup appears once on catch path, not on normal path). All 13,546 tests pass debug + release. -
[TPR-02-027][high]compiler/ori_llvm/src/codegen/function_compiler/entry_point.rs:309— The MSVC@main(args: [str])unwind path still rethrows viacleanupretinstead of translating an Ori panic into the documentedmainexit-code contract. Resolved: Validated and fixed on 2026-03-21. Root cause:cleanuppad/cleanupret(SEH cleanup semantics) does NOT catch Ori’s customRaiseException-based panics — onlyori_try_call’s C-level__try/__exceptdoes. Additionally,ORI_FATAL_EXIT_CODEwas defined only in the Itanium#elsesection ofeh_personality.c, causing MSVC compilation to fail. Fix: (1) Replaced the SEH branch inemit_main_call_with_invokewith a newemit_main_call_with_seh_trymethod that uses theori_try_callthunk pattern (same mechanism ascatch(expr:)). (2) Extracted SEH main thunk toseh_main_thunk.rs(186 lines). (3) MovedORI_FATAL_EXIT_CODE#definebefore the#ifdef _MSC_VERguard. (4) Updated IR semantic pin tests (test_main_args_wrapper_uses_invoke_ir,test_main_args_owned_wrapper_ir_no_normal_cleanup) to validate both Itaniuminvokeand SEHori_try_callpatterns. Verified on Windows/MSVC: panicking@main(args:)returns exit code 1, successful@main(args:)returns exit code 0,@main(args:) -> intreturns the correct value. Both debug and release builds pass.
02.N Completion Checklist
Runtime & Codegen Wiring
-
ori_buffer_store_elem_decruntime function exists and is callable from LLVM IR (2026-03-20) -
ori_buffer_store_elem_countruntime function exists and is callable from LLVM IR (2026-03-20) - Both declared in
runtime_functions.rs:store_elem_decas(ptr, ptr) -> void,store_elem_countas(ptr, i64) -> void(2026-03-20) -
load_elem_dec_fn_const/load_elem_count_constoverloads exist for*const u8callers (2026-03-20) -
rc/mod.rssplit complete: element header helpers extracted torc/elem_header.rs(mod.rs: 384 lines) (2026-03-20) - List construction stores
elem_dec_fnandelem_countin RC header after element storage (2026-03-20) - Set construction stores
elem_dec_fnandelem_countin RC header after buffer population (2026-03-20) - Collection reuse (
emit_collection_reuse) stores bothelem_dec_fnandelem_countafterori_list_reset_bufferreturns (2026-03-20) - Map iteration passes real
key_dec_fn/val_dec_fn(not NULL) toori_iter_from_map(implemented by iter-rc-contract plan, 2026-03-18) -
emit_list_iterpasses realelem_dec_fntoori_iter_from_list(implemented by iter-rc-contract plan, 2026-03-18)
COW & Buffer Propagation
- ALL list COW slow path functions propagate both
elem_dec_fnandelem_countfrom old to new buffer:push_cow,pop_cow,set_cow(cow.rs),insert_cow,remove_cow(cow_structural.rs),concat_cow,reverse_cow,sort_cow,sort_stable_cow(cow_sort.rs) (2026-03-20) - ALL set COW slow path functions propagate
elem_dec_fnto new buffer via centralizedalloc_set_hash_bufferandrehash_set:insert_cow,remove_cow(set/cow/basic.rs),union_cow,intersection_cow,difference_cow(set/cow/algebra.rs),ori_iter_collect_set(iterator/consumers.rs) (2026-03-20) -
query.rsfunctions (ori_list_reverse,ori_list_concat) propagate bothelem_dec_fnandelem_countvia direct copy (2026-03-20) -
write_array_to_listextended withelem_dec_fnparameter and stores bothelem_dec_fn+elem_countinternally.ori_str_charspassesNone. (2026-03-20) -
ori_map_keys_to_liststoreselem_dec_fn+elem_counton list buffer afterori_rc_alloc—key_dec_fnparameter added, LLVM decl + codegen updated (2026-03-20) -
ori_map_values_to_liststoreselem_dec_fn+elem_counton list buffer (2026-03-20) -
ori_str_splitstoreselem_dec_fn+elem_counton list buffer viaelem_dec_fnparameter + internal store (2026-03-20) -
ori_set_to_liststoreselem_dec_fn+elem_counton list buffer viaelem_dec_fnparameter (2026-03-20) - LLVM IR declarations updated for all 4 signature changes:
ori_map_keys_to_list,ori_map_values_to_list,ori_set_to_list,ori_str_split(2026-03-20) - Codegen call sites updated:
map_builtins.rs,set_builtins.rs,string_builtins.rs(2026-03-20) -
ori_iter_collectoutput buffer getselem_dec_fn+elem_countvia codegen-emitted header-store calls (2026-03-20) -
ori_iter_collect_setoutput buffer getselem_dec_fnvia codegen-emitted header-store call insideemit_iter_collect_set(2026-03-20) -
ori_args_from_argvstoreselem_countin header;elem_dec_fndeferred to firstori_buffer_rc_dec(2026-03-20) -
alloc_set_hash_buffercentralized withelem_dec_fnparameter; stores in header internally (2026-03-20) -
rehash_setcentralized withelem_dec_fnparameter; stores in header on new buffer internally (2026-03-20) -
ori_rc_reallocpreserves bothelem_dec_fnandelem_count— verified bytest_rc_realloc_preserves_header_fieldsatcompiler/ori_llvm/src/tests/runtime_tests.rs:327(2026-03-20)
Invariant & Safety
-
debug_assert!inori_buffer_rc_deccatches NULL header with non-NULL callerelem_dec_fn(placed inori_buffer_rc_dec, NOT indrop_elements_and_free) — added 2026-03-21 -
test_rc_header_is_32_bytestest existence verified — exists atcompiler/ori_llvm/src/tests/runtime_tests.rs:289(2026-03-20) - Map double-free root cause identified (2026-03-21):
ori_map_keys_to_list/ori_map_values_to_list/ori_set_to_listmissing RcInc on copied elements. Fixed withelem_inc_fnparameter.test_map_keys_str_scope_dropun-ignored. Section 01.N blocked Valgrind failures pending re-run (may need additional COW-path fixes).
AOT Tests & Verification
-
[str]list scope drop — zero leaks (test_str_list_scope_drop, 2026-03-21) -
[[int]]nested list scope drop — zero leaks (test_nested_int_list_scope_drop, 2026-03-21) -
[str]COW push on shared list — zero leaks (test_str_list_cow_push_shared, 2026-03-21) - SSO/heap mixed
[str]— zero leaks (test_str_list_mixed_sso_heap, 2026-03-21) -
ori_iter_collecton[str]— output buffer has correctelem_dec_fn, zero leaks (test_str_list_iter_collect, 2026-03-21) -
map.keys()on{str: int}— zero leaks (test_map_keys_str_scope_drop, fixed 2026-03-21 viakey_inc_fnparameter). Passes in debug + release. -
str.split(sep:)returning[str]— zero leaks (test_str_split_scope_drop, 2026-03-21) -
[str]iteration where iterator dec reaches zero first —test_str_list_iter_last_owner, zero leaks in debug + release (2026-03-21) -
[str]iteration where explicit dec reaches zero first —test_str_list_explicit_last_owner, zero leaks in debug + release (2026-03-21) - Function parameter
[str]— callee iterates, caller uses after —test_str_list_fn_param_iter, no double-free, no leak in debug + release (2026-03-21) - Iterator + slice cross-feature test —
test_str_list_slice_then_iter, uses.take(count:)seamless slice, zero leaks in debug + release (2026-03-21) -
{str: int}map iteration — zero leaks (10x stability,test_map_str_iterationAOT test) (2026-03-21) -
Set<str>iteration — zero leaks (test_set_str_iteration, debug + release) (2026-03-21) -
{str: int}map passed to function, iterated inside — zero leaks (test_map_str_passed_to_fnAOT test) (2026-03-21) -
Set<str>passed to function, iterated inside — zero leaks (test_set_str_passed_to_fn, debug + release) (2026-03-21) -
Set<str>COW insert on shared set — zero leaks (test_set_str_cow_insert_shared, debug + release). Fix:ori_set_insert_cowmissinginc_fnon new element. (2026-03-21) -
Set<str>union — zero leaks (test_set_str_union, debug + release). Fix: union fast path missinginc_fnon set2 elements. (2026-03-21) -
Set<str>intersection/difference — fixed and tested:test_set_str_intersection_unique+test_set_str_difference_unique(debug + release) (2026-03-21) -
set.to_list()onSet<str>— zero leaks (test_set_str_to_list, debug + release) (2026-03-21) -
ori_iter_collect_setonSet<str>— tested viawords.iter().collect()withSet<str>annotation (for-yield always produces list, not set).test_set_str_iter_collectAOT test passes debug + release withORI_CHECK_LEAKS=1. (2026-03-21) - [TPR-02-005] Fix
generate_main_wrapperinentry_point.rs— wrapper loads sret result as{i64, i64, ptr}struct value but_ori_mainexpectsptr(Indirect ABI for 24-byte param). Fixed: checkabi.params[0].passingforIndirect/Referenceand pass via alloca pointer. Also addedori_args_cleanupruntime function to free args list after_ori_mainreturns. (2026-03-21) -
@main(args: [str])with arguments — exercisesori_args_from_argvcreating[str]buffer, zero leaks. 4 AOT tests: no args, SSO strings, heap strings, void return. All pass withORI_CHECK_LEAKS=1. (2026-03-21) -
test_str_list_passed_to_two_functionspasses reliably (not ignored) — verified 81/81 fat_ptr_iter tests pass (2026-03-21) -
test_nested_list_iterationpasses reliably (not ignored) — verified in fat_ptr_iter.rs, passes (2026-03-21)
Valgrind
- No valgrind errors on
[str]and[[int]]iteration patterns —nested_int_list_iter.ori+ existingstr_for_yield.orietc. (2026-03-21) - No valgrind errors on
{str: int}map iteration patterns —map_str_iteration.ori+ existingmap_str_for_do.ori(2026-03-21) - No valgrind errors on
Set<str>iteration patterns —set_str_iteration.ori(2026-03-21) - No valgrind errors on
Set<str>COW mutation patterns (insert, remove, union) —set_str_cow_mutations.ori. BUG FIXED: set algebrarelease_set_bufferhelper now decs occupied elements via V5 headerelem_dec_fnbefore freeing unique buffers. Previouslyori_rc_free(d1, ...)freed without element cleanup, leaking fat-pointer elements. (2026-03-21) - No valgrind errors on
map.keys()/map.values()with fat-pointer keys/values —map_keys_values_fat.ori(2026-03-21) - No valgrind errors on
set.to_list()withSet<str>—set_str_to_list.ori(2026-03-21) - No valgrind errors on
str.split(sep:)returning[str]—str_split_result.ori(2026-03-21) - No valgrind errors on
ori_iter_collect_setwithSet<str>elements —collect_set_str.ori(2026-03-21)
ABI Sync Points — All Must Be Single-Commit Changes
All runtime function signature changes below require updating THREE locations atomically (same commit):
- Runtime function signature in
ori_rt(Rust) - LLVM IR declaration in
runtime_functions.rs(parameter list) - Codegen call site(s) in
arc_emitter/builtins/(argument passing)
| Function | Runtime File | LLVM Decl Line | Codegen File | New Param |
|---|---|---|---|---|
ori_map_keys_to_list | map/mod.rs:97 | runtime_functions.rs:512 | map_builtins.rs:74 | key_dec_fn: ptr |
ori_map_values_to_list | map/mod.rs:141 | runtime_functions.rs:520 | map_builtins.rs:112 | val_dec_fn: ptr |
ori_str_split | string/ops.rs:45 | runtime_functions.rs:778 | string_builtins.rs:158 | elem_dec_fn: ptr |
ori_set_to_list | set/mod.rs:55 | runtime_functions.rs:728 | set_builtins.rs:270 | elem_dec_fn: ptr |
ori_map_insert_cow | map/cow.rs:37 | runtime_functions.rs:563 | map_builtins.rs:250 | val_dec: ptr |
Additionally, if alloc_set_hash_buffer and rehash_set gain elem_dec_fn parameters, their callers within ori_rt must be updated (these are internal-only, no LLVM IR declaration needed).
If ori_args_from_argv option (a) is chosen: lib.rs:303 + runtime_functions.rs + entry_point.rs must also sync.
- All ABI sync points committed atomically (no partial updates) — collect RcInc fix: runtime + declarations + codegen synced in one change (2026-03-21)
Build Verification
- All existing AOT tests pass (
timeout 150 cargo test -p ori_llvm --test aot) — 1849 passed (2026-03-21) - All tests pass in release build (
cargo b --release && timeout 150 cargo test -p ori_llvm --test aot) — 1855 passed, 0 failures. Two pre-existing IR capture failures in arc.rs and generics.rs fixed with release-safe guards. (2026-03-21)
Cleanup
- Stale “V4: at
header_data - 16” comment inlist_rc.rs:27updated to V5 (2026-03-21) - Stale “8-byte refcount header” comments in
list/mod.rsupdated to “32-byte V5 header” (lines 83, 103, 131, 199) (2026-03-21) - Stale “§02.7” reference in
cow.rs:38updated to V5 header-based cleanup model (2026-03-21) - Stale “V4” label in
list/reset/mod.rs:71updated to V5 (2026-03-21) - Decorative banners removed from
list/mod.rs(2),iterator/consumers.rs(8),set/mod.rs(1),map/mod.rs(1) — all replaced with plain section comments (2026-03-21) -
construction.rsfallback_ => Idx::INTpatterns now havedebug_assert!(false, ...)at all 4 sites (lines 89, 136, 189, 424) (2026-03-21) -
iterator/state.rsdoc comment updated to mention V5 header defense-in-depth forelem_dec_fncleanup (2026-03-21) -
list_builtins.rsdoc comment updated to mention V5 header as second safety net (2026-03-21) -
set/cow/basic.rsandset/cow/algebra.rsdoc comments updated forelem_dec_fnpropagation (2026-03-21) -
ori_list_push_newcodegen usage determined — JIT/test only (not called fromarc_emitter/); no codegen changes needed (2026-03-20) - [TPR-02-003] JIT evaluator integration test — added
jit_symbols_include_elem_header_helperstest inruntime_decl/tests.rsthat verifiesori_buffer_store_elem_decandori_buffer_store_elem_countare in the JIT symbol mapping table. Existingjit_symbol_mappings_match_jit_allowedtest already ensures all JIT-allowed RT_FUNCTIONS have working mappings. (2026-03-21) - Decorative banners removed from
set/mod.rs(1 banner) andmap/mod.rs(1 banner) — included in batch above (2026-03-21) -
map/mod.rs#[allow(unused_imports)]removed.META_EMPTYIS used inmod.rs(line 50); plan claim was incorrect. (2026-03-21) -
map/mod.rsdeadelem_sizeparam removed fromwrite_array_to_list_from_data+ 2 callers updated. (2026-03-21) -
set/cow/basic.rs+set/cow/algebra.rsdead_eacomputations removed (5 sites), params renamed_elem_align. (2026-03-21) -
cow_sort.rsbothvec![0u8; es]allocations converted to[0u8; 24]stack array with heap fallback. (2026-03-21) - [BUG]
.iter().map(transform:).collect()on[str]crashes with misaligned pointer dereference instring/ops.rs:319during AOT compilation. Root cause: Trampoline ABI mismatch — for fat-pointer types (>16 bytes), the trampoline loaded elements by-value and used direct return, but the closure uses sret return + indirect param ABI. Fix: trampolines.rs now checksabi_sizeand usescall_indirect_voidwith pointer passing for types >16 bytes. Addedcall_indirect_voidto IrBuilder. All 4 trampoline kinds (Map/Predicate/ForEach/Fold) fixed. AOT regression tests:test_trampoline_map_str_identity,test_trampoline_filter_str,test_trampoline_fold_str. Pass debug + release. (2026-03-21) - [TPR-02-011] Split
edge_cleanup.rs(539→419 lines) — extractedinsert_trampoline,compute_defined_at_or_before, andretarget_terminatorinto siblingtrampoline.rs(142 lines). Both under 500-line limit. All 1008 ori_arc tests pass. (2026-03-21) - [TPR-02-016]
@main(args: [str])unwind-path args cleanup — Fixed inentry_point.rs: wrapper usesinvoke+landingpadfor_ori_mainwhen it can unwind AND has args. Cleanup landingpad callsori_args_cleanupbeforeresume. Supports both Itanium (landingpad/resume) and SEH (cleanuppad/cleanupret). Nounwind_ori_maincorrectly uses plaincall. Runtime AOT tests pass in debug + release. IR semantic pins are release-safe (skip gracefully via TPR-02-017 fix). (2026-03-21) - [BUG] Set intersection/difference fast paths (unique) — Fixed in 02.3 via
load_elem_dec_fn(d1)+ dec before tombstoning. AOT tests pass debug + release. (2026-03-21) - [BUG] Map remove fat-pointer leak — Fixed in 02.3 via
key_dec+val_decparameters. AOT tests pass debug + release. (2026-03-21) - [TPR-02-020] Main wrapper catch-all + signal detection + Valgrind — Fixed main wrapper to use
landingpad catch ptr null(not cleanup+resume). Addedexit_code_from_status()for signal detection,assert_no_signal_crash()for crash detection,compile_and_run_valgrind_with_args()for leak verification. Valgrind test confirms zero memory errors on panic-path args cleanup. (2026-03-21) - [TPR-02-021] JIT functional test for collection construction — Added
test_jit_str_list_constructionrunningori test --backend=llvmon[str]list literal. Exercises MCJIT symbol resolution ofori_buffer_store_elem_dec/ori_buffer_store_elem_count. (2026-03-21)
Excluded Allocation Sites (No Action Needed)
The following ori_rc_alloc call sites do NOT need elem_dec_fn propagation and are explicitly excluded:
string/methods/mod.rs(lines 301, 388): String COW operations. These allocate string DATA buffers (raw bytes), not list element buffers.elem_dec_fnis for element-level cleanup of collections, not for string internals.string/mod.rs(lines 196, 240, 263):OriStr::from_bytes,with_capacity,from_raw. Same — string data, not collection elements.string/ops.rs:221:ori_str_concat_cow— allocates a new string data buffer on the slow path. Not a collection element buffer.map/hash_table.rs(lines 232, 274):rehash_map,OriMap::alloc_hash_buffer. Map hash table buffers. Maps use TWO cleanup functions (key + value) that cannot fit in a single header slot. The codegen-based approach (option c) handles map cleanup. No header propagation needed.map/cow.rs:144: Map COW slow path. Same as above — map hash table buffer, not list/set buffer.iterator/sources.rs:93:ori_iter_from_str— allocates a heap copy of string bytes for the string iterator. Not a collection element buffer.list/mod.rs:108:ori_list_new— allocates theOriListSTRUCT on the heap (not the data buffer). Theori_rc_allochere is for the list metadata struct, not for the data buffer. The data buffer allocation at line 162 is covered separately.