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:
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