kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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:
Msrc/arch/arm32/native.c | 34++++++++++++++++++++--------------
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];