commit 5925ba0f1130826e2d1ea1f8ca31454d3b911588
parent cedc6e279cc8d9847ee4fa43c9d9010b7ec1b6bb
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 22:41:39 -0700
arm32: indirect call with stack args — stage callee clear of arg staging
A non-tail indirect call staged the callee into IP, but outgoing stack-arg
staging also uses IP, so the callee was clobbered and 'blx ip' jumped to garbage
(crash). Stage a non-tail callee into LR instead (BLX lr is valid; non-tail stack
args stage via IP), and only move when the callee is in an arg register or the
arg-staging reg. Fixes 112_many_function_type_params at -O1 (the -O0 large-frame
case where the address resolver also needs LR remains).
Diffstat:
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/src/arch/arm32/native.c b/src/arch/arm32/native.c
@@ -1614,23 +1614,29 @@ static void arm_plan_call(NativeTarget* t, const NativeCallDesc* desc,
if (!tail && plan->stack_arg_size > a->frame.max_outgoing)
a->frame.max_outgoing = plan->stack_arg_size;
- /* Stage an indirect callee into IP so it survives both the arg-register setup
- * and (for a tail call) the epilogue's POP of r0..r11. A normal call only needs
- * to dodge the arg registers (r0..r3); a tail call must dodge every restored
- * register, so any REG callee is staged. IP is never popped/restored. */
+ /* Outgoing stack args stage through IP normally; a tail call holds the callee
+ * in IP, so it stages through LR instead (LR is restored by the epilogue
+ * afterward; the in-window store address needs no scratch). */
+ u32 arg_stage = tail ? ARM_TMP : ARM_SCRATCH;
+
+ /* Stage an indirect callee into a register that survives the arg-register
+ * setup, the outgoing stack-arg staging, AND (for a tail call) the epilogue's
+ * POP of r0..r11. A tail call holds it in IP (never popped) and stages args via
+ * LR; a normal call stages args via IP, so an IP- or arg-register-resident
+ * callee moves to LR (BLX lr is valid). A callee already in a preserved
+ * register (r4..r11, or the chosen staging reg) needs no move. */
if (plan->callee.kind == NATIVE_LOC_REG &&
- (NativeAllocClass)plan->callee.cls == NATIVE_REG_INT &&
- (plan->callee.v.reg <= 3u || tail)) {
- NativeLoc scratch =
- native_loc_reg(plan->callee.type, NATIVE_REG_INT, ARM_SCRATCH);
- arm_move(t, scratch, plan->callee);
- plan->callee = scratch;
+ (NativeAllocClass)plan->callee.cls == NATIVE_REG_INT) {
+ u32 cr = plan->callee.v.reg & 0xfu;
+ u32 dst = tail ? ARM_SCRATCH : ARM_TMP;
+ int needs = tail ? 1 : (cr <= 3u || cr == arg_stage);
+ if (needs && cr != dst) {
+ NativeLoc scratch = native_loc_reg(plan->callee.type, NATIVE_REG_INT, dst);
+ arm_move(t, scratch, plan->callee);
+ plan->callee = scratch;
+ }
}
{
- /* Outgoing stack args stage through IP normally; a tail call holds the callee
- * in IP, so it stages through LR instead (LR is restored by the epilogue
- * afterward, and the in-window store address needs no scratch). */
- u32 arg_stage = tail ? ARM_TMP : ARM_SCRATCH;
u32 next_int = (abi && abi->has_sret) ? 1u : 0u;
u32 stack = 0, nmoves = 0, i, p;
NativeArgMove moves[ARM_MAX_REG_ARG_MOVES];