commit 2b9dd35027292377b9741f0e6cbd9d154699024f
parent d1a5d01b64822a3d2c2657e7755eb8bf53887a85
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 29 May 2026 12:32:52 -0700
opt: parameters mutually interfere at function entry
The ABI delivers every incoming parameter simultaneously at entry, but each
param_decl marker was sequenced, so a param's live range started at its marker
rather than at the entry point. A dead parameter (e.g. an unused arg) then had a
range disjoint from a later live parameter and the allocator coalesced the two
into one physical register — yielding two entry binds targeting the same
register, which the entry-bind parallel copy cannot resolve (one clobbers the
other). Anchor every param_decl def at the entry block's base point so all
parameters interfere, forcing distinct registers. Fixes funcptr_field_first_arg
and the VLA-param cases at -O1.
Diffstat:
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/opt/pass_live.c b/src/opt/pass_live.c
@@ -800,7 +800,15 @@ void opt_live_ranges_build(Func* f, const OptLiveInfo* live,
range_add_live_across_call(live_pregs.regs[k], &call_ctx);
}
- RangeCloseCtx close_ctx = {&build, raw_start + i, b};
+ /* Incoming parameters are all delivered simultaneously by the ABI at
+ * function entry, so their values are mutually live there even though the
+ * param_decl markers are sequenced. Anchor every param_decl def at the
+ * entry block's base point so the params interfere with one another; this
+ * stops the allocator from coalescing a dead param (e.g. an unused arg)
+ * into a live param's incoming register, which the entry-bind parallel
+ * copy could not then resolve (two binds targeting one register). */
+ u32 def_pos = ((IROp)in->op == IR_PARAM_DECL) ? raw_start : raw_start + i;
+ RangeCloseCtx close_ctx = {&build, def_pos, b};
for (u32 k = 0; k < refs.ndefs; ++k)
range_close_def(refs.defs[k], &close_ctx);
RangeUseCtx use_ctx = {&build, raw_start + i + 1u, b};