fix(ir): keep concrete scalar foreach values through the loop local (#405)#550
Conversation
…llegalstudio#405) A by-value foreach over an array with a concrete scalar element type degraded the loop variable to a boxed Mixed local, so appending it widened the array under construction to array<mixed> at runtime while the checker-side function signature kept the concrete element type. Callers then read the returned array through the concrete layout: box addresses for int elements, and a garbage (ptr,len) reinterpretation for string elements that made the next read exhaust the heap. foreach_value_type() now preserves Int/Float/Str/Bool elements (the codegen already loads unboxed concrete iterator values on both arches), and IterCurrentValue/IterCurrentKey results are modeled as owning temporaries only when they really bind a fresh owned copy: a concrete Str current value is a borrowed pointer into the source array's payload (the same rule the ownership model applies to ArrayGet string results), so the retaining store must not release it — that release freed the source array's string block, the next __rt_str_persist reused it, and the source's deep-free at foreach.exit zeroed it under its new owner.
|
Note on the single red shard (
A rerun of the failed shard should go green. Happy to adjust anything if you'd rather bump that fixture's timeout instead. |
…hod_return_type_values The test intentionally exercises five complete compile-link-run eval programs in one test (~38s on a local ARM64 debug build, identical on pristine main), which clears the global 60s slow-timeout only barely and timed out on a loaded shared macos-14 CI runner. Same category and same 180s override as the eval regression tests already listed here.
|
Pushed |
Fixes #405.
Problem
Appending the by-value
foreachloop variable into another array and returning that array from a function produced a corrupted result:$r[0]printed nothing (or a garbage integer), and iterating the result died withFatal error: heap memory exhausted. On currentmainthe minimal repro is fatal already atecho $r[0].Root cause (two layers)
1. Signature/layout mismatch across the return boundary.
foreach_value_type()degraded the loop value local to a boxedMixedfor every element type exceptCallable/Object. Appending that box widened the array under construction toarray<mixed>at runtime, while the checker-side signature kept the concrete element type (array<string>). The caller then read the returned array through the concrete layout: forintelements it printed the box address; forstringelements it read the box pointer as a(ptr,len)pair and the garbage length turned the next read into a huge copy → heap exhaustion. Inline code was unaffected because the widened slot type is used consistently inside one function — only the call boundary miscompiled.2. Ownership asymmetry for borrowed
Striterator values. With concrete elements preserved,iter_current_valueresults were still modeled as owning temporaries. That is correct for the boxed-Mixed path (the box is a fresh owned cell) and for objects/callables (the codegen retains them), but a concreteStrcurrent value is a borrowed pointer into the source array's payload — exactly likeArrayGetstring results, whichvalue_is_owned_index_read_tempalready documents as borrowed. The retaining store's source release freed the source array's string block while the array still referenced it; the very next__rt_str_persistreused the freed block, and the source array's deep-free atforeach.exitzeroed it out from under its new owner. Verified with an lldb watchpoint: the element byte is overwritten by_rt_heap_free's free-list link write, called from_rt_array_free_deepin theforeach.exitblock.Fix
foreach_value_type()(src/ir_lower/stmt/mod.rs): preserve concrete scalar element types (Int,Float,Str,Bool) for the by-value loop variable. The codegen already supports unboxed concrete elements on both arches (load_current_array_value_{aarch64,x86_64}handleInt/Bool/Float/Str); only the EIR result type was missing.value_is_owning_temporary()(src/ir_lower/context.rs):IterCurrentValue/IterCurrentKeyresults stay owning except when the result is a concreteStr, which is borrowed from the source container payload (same rule the ownership model already applies toArrayGetstring results).QA (macOS ARM64, debug build, all with
--heap-debug)a/abc, heap clean (was: empty +Fatal error: heap memory exhausted).123(was: box addresses like4304419072).explodesource, local/temporary source, subscript-based control cases) all correct and heap-clean.tests/codegen/arrays/foreach_value_append.rs.arrays::,foreach*,spl::,optimizer::,control_flow::,iterator*,strings::,generators::,zval::plus the fullir_backend_smoke_testtarget: 1708 passed, 0 failed (the full suite compiles a native fixture per test and runs >1h locally; deferring the rest to CI).x86_64: the concrete-element iterator loads and
__rt_array_push_strpaths are pre-existing and symmetric on both arches; verified by inspection here (local Mach-O toolchain can't assemble the ELF runtime), CI shards cover it.