kit

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

commit 38d3c95b7c26a5a8fdb6b0ffa33487ce5f2d5b07
parent ee0b2db079b32741e80fc9468ac6e02859749b8f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 15 Jun 2026 15:43:34 -0700

Fix AArch64 O1 indexed address lowering

Diffstat:
Msrc/arch/aa64/native.c | 63++++++++++++++++++++++++++++++++++++++++++---------------------
Msrc/opt/pass_combine.c | 9+++++++++
Atest/parse/cases/opt_03_addr_index_alias_store.c | 38++++++++++++++++++++++++++++++++++++++
Atest/parse/cases/opt_03_addr_index_alias_store.expected | 1+
4 files changed, 90 insertions(+), 21 deletions(-)

diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -483,8 +483,9 @@ static void aa_emit_add_imm(AANativeTarget* a, u32 rd, u32 rn, i32 off) { aa_emit32(mc, aa64_sub_imm(1, rd, rn, imm12, sh)); return; } - aa_emit_load_imm(mc, 1, rd, off); - aa_emit32(mc, aa64_add(1, rd, rn, rd)); + u32 tmp = rd == rn ? (rd == AA_TMP0 ? AA_TMP1 : AA_TMP0) : rd; + aa_emit_load_imm(mc, 1, tmp, off); + aa_emit32(mc, aa64_add(1, rd, rn, tmp)); } static void aa_emit_add_i64(AANativeTarget* a, u32 rd, u32 rn, i64 off) { @@ -498,8 +499,9 @@ static void aa_emit_add_i64(AANativeTarget* a, u32 rd, u32 rn, i64 off) { aa_emit32(mc, aa64_sub_imm(1, rd, rn, imm12, sh)); return; } - aa_emit_load_imm(mc, 1, rd, off); - aa_emit32(mc, aa64_add(1, rd, rn, rd)); + u32 tmp = rd == rn ? (rd == AA_TMP0 ? AA_TMP1 : AA_TMP0) : rd; + aa_emit_load_imm(mc, 1, tmp, off); + aa_emit32(mc, aa64_add(1, rd, rn, tmp)); } /* Unscaled load with an explicit load opcode (AA64_LDST_OPC_LDR for a plain @@ -1095,12 +1097,30 @@ static int aa_imm_legal(NativeTarget* t, NativeImmUse use, u32 op, return 0; } -static void aa_apply_index(AANativeTarget* a, u32 rd, const NativeAddr* addr) { - if (addr->index_kind == NATIVE_ADDR_INDEX_NONE) return; +static int aa_addr_index_is_reg(const NativeAddr* addr, u32 reg) { + return addr->index_kind == NATIVE_ADDR_INDEX_REG && addr->index.reg == reg; +} + +static u32 aa_tmp_avoiding(u32 reg) { + return reg == AA_TMP0 ? AA_TMP1 : AA_TMP0; +} + +static void aa_load_addr_from_base(AANativeTarget* a, u32 rd, u32 base, + i64 off, const NativeAddr* addr) { + if (addr->index_kind == NATIVE_ADDR_INDEX_NONE) { + if (rd != base || off) aa_emit_add_i64(a, rd, base, off); + return; + } if (addr->index_kind != NATIVE_ADDR_INDEX_REG) aa_panic(a, "unsupported address index"); if (addr->log2_scale > 4u) aa_panic(a, "unsupported address scale"); - aa_emit32(a->base.mc, aa_add_lsl(rd, rd, addr->index.reg, addr->log2_scale)); + + /* Read the index before writing rd. O1 can legally select the same physical + * register for an address result and its dead-after-use index; materializing + * the base into rd first would turn `base + index` into `base + base`. */ + aa_emit32(a->base.mc, + aa_add_lsl(rd, base, addr->index.reg, addr->log2_scale)); + if (off) aa_emit_add_i64(a, rd, rd, off); } static void aa_materialize_frame_index(AANativeTarget* a, NativeAddr* addr, @@ -2215,13 +2235,14 @@ static void aa_load_addr(NativeTarget* t, NativeLoc dst, NativeAddr addr) { switch ((NativeAddrBaseKind)addr.base_kind) { case NATIVE_ADDR_BASE_FRAME: { AANativeSlot* s = aa_slot(a, addr.base.frame); - aa_emit_add_imm(a, rd, AA_FP, aa_fp_off_slot(a, s->off) + addr.offset); - aa_apply_index(a, rd, &addr); + aa_load_addr_from_base(a, rd, AA_FP, + aa_fp_off_slot(a, s->off) + addr.offset, &addr); return; } case NATIVE_ADDR_BASE_FRAME_VALUE: { NativeAddr load; MemAccess mem; + u32 base_reg = aa_addr_index_is_reg(&addr, rd) ? aa_tmp_avoiding(rd) : rd; memset(&load, 0, sizeof load); load.base_kind = NATIVE_ADDR_BASE_FRAME; load.base.frame = addr.base.frame; @@ -2231,42 +2252,42 @@ static void aa_load_addr(NativeTarget* t, NativeLoc dst, NativeAddr addr) { mem.type = load.base_type; mem.size = 8; mem.align = 8; - aa_emit_mem(a, 1, dst, load, mem); - if (addr.offset) aa_emit_add_imm(a, rd, rd, addr.offset); - aa_apply_index(a, rd, &addr); + aa_emit_mem(a, 1, native_loc_reg(dst.type, NATIVE_REG_INT, base_reg), load, + mem); + aa_load_addr_from_base(a, rd, base_reg, addr.offset, &addr); return; } case NATIVE_ADDR_BASE_REG: - aa_emit_add_imm(a, rd, addr.base.reg, addr.offset); - aa_apply_index(a, rd, &addr); + aa_load_addr_from_base(a, rd, addr.base.reg, addr.offset, &addr); return; case NATIVE_ADDR_BASE_GLOBAL: { i64 addend = addr.base.global.addend + (i64)addr.offset; + u32 base_reg = aa_addr_index_is_reg(&addr, rd) ? aa_tmp_avoiding(rd) : rd; u32 pos = mc_pos(t->mc); if (aa_use_got_for_sym(t, addr.base.global.sym)) { - aa_emit32(t->mc, aa64_adrp(rd, 0, 0)); + aa_emit32(t->mc, aa64_adrp(base_reg, 0, 0)); mc_emit_reloc_at(t->mc, t->mc->section_id, pos, R_AARCH64_ADR_GOT_PAGE, addr.base.global.sym, 0, 0, 0); pos = mc_pos(t->mc); - aa_emit32(t->mc, aa_ldr_uimm(3, rd, rd, 0)); + aa_emit32(t->mc, aa_ldr_uimm(3, base_reg, base_reg, 0)); mc_emit_reloc_at(t->mc, t->mc->section_id, pos, R_AARCH64_LD64_GOT_LO12_NC, addr.base.global.sym, 0, 0, 0); - if (addend) aa_emit_add_i64(a, rd, rd, addend); - aa_apply_index(a, rd, &addr); + if (addend) aa_emit_add_i64(a, base_reg, base_reg, addend); + aa_load_addr_from_base(a, rd, base_reg, 0, &addr); return; } - aa_emit32(t->mc, aa64_adrp(rd, 0, 0)); + aa_emit32(t->mc, aa64_adrp(base_reg, 0, 0)); mc_emit_reloc_at(t->mc, t->mc->section_id, pos, R_AARCH64_ADR_PREL_PG_HI21, addr.base.global.sym, addend, 0, 0); pos = mc_pos(t->mc); - aa_emit32(t->mc, aa64_add_imm(1, rd, rd, 0, 0)); + aa_emit32(t->mc, aa64_add_imm(1, base_reg, base_reg, 0, 0)); mc_emit_reloc_at(t->mc, t->mc->section_id, pos, R_AARCH64_ADD_ABS_LO12_NC, addr.base.global.sym, addend, 0, 0); - aa_apply_index(a, rd, &addr); + aa_load_addr_from_base(a, rd, base_reg, 0, &addr); return; } default: diff --git a/src/opt/pass_combine.c b/src/opt/pass_combine.c @@ -28,6 +28,10 @@ static int same_reg_operand(const Operand* a, const Operand* b) { a->v.reg == b->v.reg; } +static int producer_def_aliases_source(const Operand* def, const Operand* src) { + return def && src && same_reg_operand(def, src); +} + static int frame_slot_is_spill(Func* f, FrameSlot fs) { if (fs == FRAME_SLOT_NONE || fs > f->nframe_slots) return 0; return f->frame_slots[fs - 1u].kind == FS_SPILL; @@ -991,6 +995,8 @@ static int try_addr_synth_one_op(CombineCtx* ctx, Inst* in, i32 i, * index slot — cannot stack two indices. */ if (has_no_index && lhs.kind == OPK_REG && rhs.kind == OPK_REG && lhs.cls == RC_INT && rhs.cls == RC_INT && + !producer_def_aliases_source(&prod_def, &lhs) && + !producer_def_aliases_source(&prod_def, &rhs) && !ctx_def_changed_since(ctx, RC_INT, lhs.v.reg, prod_idx) && !ctx_def_changed_since(ctx, RC_INT, rhs.v.reg, prod_idx)) { op->v.ind.base = lhs.v.reg; @@ -1002,6 +1008,7 @@ static int try_addr_synth_one_op(CombineCtx* ctx, Inst* in, i32 i, * an existing index — only the offset is mutated. */ else if (lhs.kind == OPK_REG && rhs.kind == OPK_IMM && lhs.cls == RC_INT && + !producer_def_aliases_source(&prod_def, &lhs) && !ctx_def_changed_since(ctx, RC_INT, lhs.v.reg, prod_idx)) { i64 sum = (i64)op->v.ind.ofs + rhs.v.imm; if (sum >= INT32_MIN && sum <= INT32_MAX) { @@ -1013,6 +1020,7 @@ static int try_addr_synth_one_op(CombineCtx* ctx, Inst* in, i32 i, /* imm + reg: base = rhs, fold imm into ofs (commutative IADD). */ else if (lhs.kind == OPK_IMM && rhs.kind == OPK_REG && rhs.cls == RC_INT && + !producer_def_aliases_source(&prod_def, &rhs) && !ctx_def_changed_since(ctx, RC_INT, rhs.v.reg, prod_idx)) { i64 sum = (i64)op->v.ind.ofs + lhs.v.imm; if (sum >= INT32_MIN && sum <= INT32_MAX) { @@ -1050,6 +1058,7 @@ static int try_addr_synth_one_op(CombineCtx* ctx, Inst* in, i32 i, if (uses_after >= 1 && uses_after <= 2 && (killed || !opt_block_live_out_has_phys_reg( ctx->f, ctx->hard_live, ctx->bl->id, &prod_def)) && + !producer_def_aliases_source(&prod_def, &prod->opnds[1]) && !ctx_def_changed_since(ctx, RC_INT, prod->opnds[1].v.reg, prod_idx)) { op->v.ind.index = prod->opnds[1].v.reg; diff --git a/test/parse/cases/opt_03_addr_index_alias_store.c b/test/parse/cases/opt_03_addr_index_alias_store.c @@ -0,0 +1,38 @@ +/* Regression: AArch64 -O1 address materialization must handle the address + * result register aliasing a dead-after-use scaled index. Lua's stack_init hit + * this shape in `stack[i].tag = 0`: the broken sequence computed base+base and + * stored through an unmapped address. */ +typedef struct Slot { + unsigned long long payload; + unsigned char tag; + unsigned char pad[7]; +} Slot; + +typedef struct State { + Slot* stack; +} State; + +static void clear_tags(State* s, int n) { + for (int i = 0; i < n; ++i) { + Slot* slot = s->stack + i; + slot->tag = 0; + } +} + +int test_main(void) { + Slot slots[45]; + State state; + unsigned total = 0; + + for (int i = 0; i < 45; ++i) { + slots[i].payload = (unsigned long long)(1000 + i); + slots[i].tag = (unsigned char)(i + 1); + } + + state.stack = slots; + clear_tags(&state, 45); + + for (int i = 0; i < 45; ++i) total += slots[i].tag; + + return total == 0 && slots[44].payload == 1044u ? 42 : 1; +} diff --git a/test/parse/cases/opt_03_addr_index_alias_store.expected b/test/parse/cases/opt_03_addr_index_alias_store.expected @@ -0,0 +1 @@ +42