kit

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

commit 34b5294b427e90ed1a00dda03759deab3f203ddf
parent 390b95747b59e7a73202cd9a9b15a89bd985c270
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 17 Jun 2026 13:56:25 -0700

arm32: stage frame-resident "r" inline-asm inputs at -O1

arm_asm_block_native panicked "optimizer asm input not in a register" when a
register-constrained ("r") input's value is not in a register but in a frame
slot — the optimizer cannot keep an address-stable / frame-resident local (e.g.
a value that is both `+r` inout and returned) live in a register across an asm
block, so it forwards the frame home, not a register. aa64 already handles this
(native.c) by loading the value into a reserved scratch before binding.

Mirror that on arm32: a plain "r" input arriving as a frame/stack/addr loc (or a
folded immediate) is staged into a reserved scratch (IP, then LR) and bound as
that register. The "m" memory path now shares the same staging counter via the
new arm_asm_in_loc_addr / arm_asm_stage_reg helpers, so two staged operands no
longer alias IP. The restricted "l" class and hard-register pins still require
an already-allocated register (IP/LR are high regs), so they keep the
diagnostic.

Diffstat:
Msrc/arch/arm32/native.c | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
1 file changed, 64 insertions(+), 24 deletions(-)

diff --git a/src/arch/arm32/native.c b/src/arch/arm32/native.c @@ -3468,6 +3468,44 @@ static void arm_direct_asm_block(NativeDirectTarget* d, const char* tmpl, &hooks); } +/* Convert an inline-asm input location — a computed address, or a frame/stack + * home — to a NativeAddr. Shared by the "m" (bind the address) and the staged + * register (load the value) input paths. */ +static NativeAddr arm_asm_in_loc_addr(NativeTarget* t, NativeLoc loc, + KitCgTypeId type) { + NativeAddr ma; + memset(&ma, 0, sizeof ma); + if (loc.kind == NATIVE_LOC_ADDR) { + ma = loc.v.addr; + } else if (loc.kind == NATIVE_LOC_FRAME || loc.kind == NATIVE_LOC_STACK) { + ma.base_kind = NATIVE_ADDR_BASE_FRAME; + ma.base.frame = + loc.kind == NATIVE_LOC_FRAME ? loc.v.frame : loc.v.stack.slot; + ma.offset = loc.kind == NATIVE_LOC_STACK ? loc.v.stack.offset : 0; + ma.base_type = type; + } else { + arm_asm_panic_at(t->c, arm_of(t)->loc, "unsupported memory asm input"); + } + return ma; +} + +/* Reserve the next scratch register to stage an inline-asm operand into: IP (the + * operand scratch) first, then LR. The native-emit asm path runs with both free + * (the optimizer allocates asm operands to r0..r11 and never to IP/LR), so two + * staged operands are available — enough for the operand shapes the corpus + * exercises. A third staged operand is a clean diagnostic rather than a clobber. */ +static Reg arm_asm_stage_reg(NativeTarget* t, u32* nstage) { + Reg r; + if (*nstage == 0u) + r = (Reg)ARM_SCRATCH; + else if (*nstage == 1u) + r = (Reg)ARM_TMP; + else + arm_asm_panic_at(t->c, arm_of(t)->loc, "too many staged inline-asm operands"); + (*nstage)++; + return r; +} + /* NativeTarget (optimizer / -O1) inline-asm path: bind operands to registers * (no self-allocation — the optimizer pre-allocated and forwarded clobbers via * plan_frame), then run the template. The minimal runner needs only the bound @@ -3481,6 +3519,7 @@ static void arm_asm_block_native(NativeTarget* t, const char* tmpl, Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL; Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL; u32 i; + u32 nstage = 0u; /* reserved scratch (IP, then LR) consumed for staged inputs */ (void)clobbers; (void)nclob; for (i = 0; i < nout; ++i) { @@ -3514,30 +3553,31 @@ static void arm_asm_block_native(NativeTarget* t, const char* tmpl, } else if (body[0] == 'm') { /* Memory ("m") constraint: bind a base register holding the operand's * address. The optimizer hands a computed address (NATIVE_LOC_ADDR) or a - * frame/stack home; either way materialize the address into the operand - * scratch (ip) and bind it as the memory base. */ - NativeAddr ma; - memset(&ma, 0, sizeof ma); - if (in_locs[i].kind == NATIVE_LOC_ADDR) { - ma = in_locs[i].v.addr; - } else if (in_locs[i].kind == NATIVE_LOC_FRAME || - in_locs[i].kind == NATIVE_LOC_STACK) { - ma.base_kind = NATIVE_ADDR_BASE_FRAME; - ma.base.frame = in_locs[i].kind == NATIVE_LOC_FRAME - ? in_locs[i].v.frame - : in_locs[i].v.stack.slot; - ma.offset = in_locs[i].kind == NATIVE_LOC_STACK - ? in_locs[i].v.stack.offset - : 0; - ma.base_type = type; - } else { - arm_asm_panic_at(c, arm_of(t)->loc, "unsupported memory asm input"); - } - arm_load_addr(t, - native_loc_reg(builtin_id(KIT_CG_BUILTIN_I32), - NATIVE_REG_INT, ARM_SCRATCH), - ma); - arm_asm_bound_mem(&bound_ins[i], type, (Reg)ARM_SCRATCH); + * frame/stack home; either way materialize the address into a reserved + * staging scratch and bind it as the memory base. */ + Reg base = arm_asm_stage_reg(t, &nstage); + NativeAddr ma = arm_asm_in_loc_addr(t, in_locs[i], type); + arm_load_addr( + t, native_loc_reg(builtin_id(KIT_CG_BUILTIN_I32), NATIVE_REG_INT, base), + ma); + arm_asm_bound_mem(&bound_ins[i], type, base); + } else if (body[0] == 'r' && body[1] == '\0' && !ins[i].reg) { + /* Plain register ("r") input whose value is NOT already in a register: an + * address-stable / frame-resident local (the optimizer cannot keep an + * address-taken local live in a register across the asm block), or a + * folded immediate. Stage the VALUE into a reserved scratch and bind that + * register, mirroring the aa64 native asm path. Only the unrestricted "r" + * class can use IP/LR; the low-reg "l" class (and hard-register pins) must + * already arrive in an allowed register, so they fall through to the + * diagnostic below. */ + Reg sr = arm_asm_stage_reg(t, &nstage); + NativeLoc sd = native_loc_reg(type, NATIVE_REG_INT, sr); + if (in_locs[i].kind == NATIVE_LOC_IMM) + arm_load_imm(t, sd, in_locs[i].v.imm); + else + arm_load(t, sd, arm_asm_in_loc_addr(t, in_locs[i], type), + native_mem_for_type(t, type, native_type_size(t, type))); + arm_asm_bound_reg(&bound_ins[i], type, NATIVE_REG_INT, sr); } else { arm_asm_panic_at(c, arm_of(t)->loc, "optimizer asm input not in a register");