commit 5b2748e99c18922f08cc6fcb285526a119f441f9
parent 177f4e8b9580ec1b9e1ce75700ad2305f3c47336
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 21 May 2026 12:19:36 -0700
test opt scalar gvn red cases
Diffstat:
2 files changed, 243 insertions(+), 5 deletions(-)
diff --git a/src/opt/opt.c b/src/opt/opt.c
@@ -1427,6 +1427,8 @@ static void opt_run_o2_pipeline(OptImpl* o) {
opt_verify(o->f, "o2-addr-xform-dce");
opt_copy_cleanup(o->f);
opt_verify(o->f, "o2-addr-xform-copy-cleanup");
+ opt_gvn(o->f);
+ opt_verify(o->f, "o2-gvn-ssa");
opt_ssa_dce(o->f);
opt_verify(o->f, "o2-copy-dce");
opt_make_conventional_ssa(o->f);
diff --git a/test/opt/opt_test.c b/test/opt/opt_test.c
@@ -234,6 +234,18 @@ static Inst* emit_load_imm(Func* f, u32 b, Val dst, CfreeCgTypeId ty, i64 imm) {
return in;
}
+static Inst* emit_scalar_input(Func* f, u32 b, Val dst, CfreeCgTypeId ty) {
+ Inst* in = ir_emit(f, b, IR_PARAM_DECL);
+ in->opnds = arena_array(f->arena, Operand, 1);
+ in->opnds[0] = op_reg_(dst, ty);
+ in->nopnds = 1;
+ in->def = dst;
+ in->type = ty;
+ f->val_def_block[dst] = b;
+ f->val_def_inst[dst] = f->blocks[b].ninsts - 1u;
+ return in;
+}
+
static Inst* emit_copy(Func* f, u32 b, Val dst, Val src, CfreeCgTypeId ty) {
Inst* in = ir_emit(f, b, IR_COPY);
in->opnds = arena_array(f->arena, Operand, 2);
@@ -263,6 +275,37 @@ static Inst* emit_binop(Func* f, u32 b, Val dst, Val a, Val c,
return in;
}
+static Inst* emit_unop(Func* f, u32 b, Val dst, Val src, CfreeCgTypeId ty,
+ UnOp op) {
+ Inst* in = ir_emit(f, b, IR_UNOP);
+ in->opnds = arena_array(f->arena, Operand, 2);
+ in->opnds[0] = op_reg_(dst, ty);
+ in->opnds[1] = op_reg_(src, ty);
+ in->nopnds = 2;
+ in->def = dst;
+ in->type = ty;
+ in->extra.imm = op;
+ f->val_def_block[dst] = b;
+ f->val_def_inst[dst] = f->blocks[b].ninsts - 1u;
+ return in;
+}
+
+static Inst* emit_cmp(Func* f, u32 b, Val dst, Val a, Val c, CfreeCgTypeId ty,
+ CmpOp op) {
+ Inst* in = ir_emit(f, b, IR_CMP);
+ in->opnds = arena_array(f->arena, Operand, 3);
+ in->opnds[0] = op_reg_(dst, ty);
+ in->opnds[1] = op_reg_(a, ty);
+ in->opnds[2] = op_reg_(c, ty);
+ in->nopnds = 3;
+ in->def = dst;
+ in->type = ty;
+ in->extra.imm = op;
+ f->val_def_block[dst] = b;
+ f->val_def_inst[dst] = f->blocks[b].ninsts - 1u;
+ return in;
+}
+
static Inst* emit_phys_copy(Func* f, u32 b, Reg dst, Reg src,
CfreeCgTypeId ty) {
Inst* in = ir_emit(f, b, IR_COPY);
@@ -285,15 +328,16 @@ static Inst* emit_phys_binop(Func* f, u32 b, Reg dst, Reg a, Reg c,
return in;
}
-static Inst* emit_convert(Func* f, u32 b, Val dst, Val src, CfreeCgTypeId ty,
- ConvKind k) {
+static Inst* emit_convert_typed(Func* f, u32 b, Val dst, Val src,
+ CfreeCgTypeId dst_ty, CfreeCgTypeId src_ty,
+ ConvKind k) {
Inst* in = ir_emit(f, b, IR_CONVERT);
in->opnds = arena_array(f->arena, Operand, 2);
- in->opnds[0] = op_reg_(dst, ty);
- in->opnds[1] = op_reg_(src, ty);
+ in->opnds[0] = op_reg_(dst, dst_ty);
+ in->opnds[1] = op_reg_(src, src_ty);
in->nopnds = 2;
in->def = dst;
- in->type = ty;
+ in->type = dst_ty;
in->extra.imm = k;
if (dst < f->nvals) {
f->val_def_block[dst] = b;
@@ -302,6 +346,11 @@ static Inst* emit_convert(Func* f, u32 b, Val dst, Val src, CfreeCgTypeId ty,
return in;
}
+static Inst* emit_convert(Func* f, u32 b, Val dst, Val src, CfreeCgTypeId ty,
+ ConvKind k) {
+ return emit_convert_typed(f, b, dst, src, ty, ty, k);
+}
+
static Inst* emit_load_local(Func* f, u32 b, Val dst, FrameSlot fs,
CfreeCgTypeId ty, u16 flags) {
Inst* in = ir_emit(f, b, IR_LOAD);
@@ -469,6 +518,29 @@ static int count_op(Func* f, IROp op) {
return n;
}
+static Inst* def_inst(Func* f, Val v) {
+ if (!f || v == VAL_NONE || v >= f->nvals) return NULL;
+ u32 b = f->val_def_block[v];
+ u32 i = f->val_def_inst[v];
+ if (b >= f->nblocks || i >= f->blocks[b].ninsts) return NULL;
+ return &f->blocks[b].insts[i];
+}
+
+static Val ret_val(Func* f, u32 b) {
+ if (!f || b >= f->nblocks || !f->blocks[b].ninsts) return VAL_NONE;
+ Inst* in = &f->blocks[b].insts[f->blocks[b].ninsts - 1u];
+ if ((IROp)in->op != IR_RET) return VAL_NONE;
+ IRRetAux* aux = (IRRetAux*)in->extra.aux;
+ if (!aux || !aux->present || aux->val.storage.kind != OPK_REG)
+ return VAL_NONE;
+ return (Val)aux->val.storage.v.reg;
+}
+
+static int val_is_load_imm(Func* f, Val v, i64 imm) {
+ Inst* in = def_inst(f, v);
+ return in && (IROp)in->op == IR_LOAD_IMM && in->extra.imm == imm;
+}
+
static u32 count_uses_of(Func* f, Val v) {
opt_rebuild_def_use(f);
u32 n = 0;
@@ -2372,6 +2444,165 @@ static void opt_addr_xform_preserves_volatile_and_globals(void) {
tc_fini(&tc);
}
+static void opt_gvn_rewrites_same_block_scalar_duplicate(void) {
+ TestCtx tc;
+ tc_init(&tc);
+ Func* f = new_func(&tc);
+ Val a = add_val(f, tc.i32);
+ Val b = add_val(f, tc.i32);
+ Val first = add_val(f, tc.i32);
+ Val second = add_val(f, tc.i32);
+ emit_scalar_input(f, f->entry, a, tc.i32);
+ emit_scalar_input(f, f->entry, b, tc.i32);
+ emit_binop(f, f->entry, first, a, b, tc.i32);
+ emit_binop(f, f->entry, second, a, b, tc.i32);
+ emit_ret_val(f, f->entry, second, tc.i32);
+
+ opt_build_cfg(f);
+ opt_gvn(f);
+ opt_verify(f, "test-gvn-ssa-same-block");
+ EXPECT(ret_val(f, f->entry) == first,
+ "GVN should rewrite duplicate same-block scalar users");
+ EXPECT(count_uses_of(f, second) == 0,
+ "GVN should leave duplicate scalar def unused after replacement");
+ tc_fini(&tc);
+}
+
+static void opt_gvn_rewrites_dominated_scalar_duplicate(void) {
+ TestCtx tc;
+ tc_init(&tc);
+ Func* f = new_func(&tc);
+ u32 child = ir_block_new(f);
+ ir_note_emit(f, child);
+ Val a = add_val(f, tc.i32);
+ Val b = add_val(f, tc.i32);
+ Val first = add_val(f, tc.i32);
+ Val second = add_val(f, tc.i32);
+ emit_scalar_input(f, f->entry, a, tc.i32);
+ emit_scalar_input(f, f->entry, b, tc.i32);
+ emit_binop(f, f->entry, first, a, b, tc.i32);
+ emit_br_to(f, f->entry, child);
+ emit_binop(f, child, second, a, b, tc.i32);
+ emit_ret_val(f, child, second, tc.i32);
+
+ opt_build_cfg(f);
+ opt_gvn(f);
+ opt_verify(f, "test-gvn-ssa-dominated");
+ EXPECT(ret_val(f, child) == first,
+ "GVN should rewrite scalar duplicate dominated by first def");
+ tc_fini(&tc);
+}
+
+static void opt_gvn_preserves_nondominated_scalar_duplicates(void) {
+ TestCtx tc;
+ tc_init(&tc);
+ Func* f = new_func(&tc);
+ u32 then_b = ir_block_new(f);
+ u32 else_b = ir_block_new(f);
+ ir_note_emit(f, then_b);
+ ir_note_emit(f, else_b);
+ Val a = add_val(f, tc.i32);
+ Val b = add_val(f, tc.i32);
+ Val then_v = add_val(f, tc.i32);
+ Val else_v = add_val(f, tc.i32);
+ emit_scalar_input(f, f->entry, a, tc.i32);
+ emit_scalar_input(f, f->entry, b, tc.i32);
+ emit_test_branch(f, f->entry, then_b, else_b, tc.i32);
+ emit_binop(f, then_b, then_v, a, b, tc.i32);
+ emit_ret_val(f, then_b, then_v, tc.i32);
+ emit_binop(f, else_b, else_v, a, b, tc.i32);
+ emit_ret_val(f, else_b, else_v, tc.i32);
+
+ opt_build_cfg(f);
+ opt_gvn(f);
+ opt_verify(f, "test-gvn-ssa-nondominated");
+ EXPECT(ret_val(f, then_b) == then_v && ret_val(f, else_b) == else_v,
+ "GVN should not rewrite through a non-dominating sibling def");
+ tc_fini(&tc);
+}
+
+static void opt_gvn_canonicalizes_commutative_scalar_operands(void) {
+ TestCtx tc;
+ tc_init(&tc);
+ Func* f = new_func(&tc);
+ Val a = add_val(f, tc.i32);
+ Val b = add_val(f, tc.i32);
+ Val first = add_val(f, tc.i32);
+ Val second = add_val(f, tc.i32);
+ emit_scalar_input(f, f->entry, a, tc.i32);
+ emit_scalar_input(f, f->entry, b, tc.i32);
+ emit_binop(f, f->entry, first, a, b, tc.i32);
+ emit_binop(f, f->entry, second, b, a, tc.i32);
+ emit_ret_val(f, f->entry, second, tc.i32);
+
+ opt_build_cfg(f);
+ opt_gvn(f);
+ opt_verify(f, "test-gvn-ssa-commutative");
+ EXPECT(ret_val(f, f->entry) == first,
+ "GVN should canonicalize commutative scalar operands");
+ tc_fini(&tc);
+}
+
+static void opt_gvn_folds_safe_scalar_constants(void) {
+ TestCtx tc;
+ tc_init(&tc);
+
+ Func* add = new_func(&tc);
+ Val two = add_val(add, tc.i32);
+ Val three = add_val(add, tc.i32);
+ Val sum = add_val(add, tc.i32);
+ emit_load_imm(add, add->entry, two, tc.i32, 2);
+ emit_load_imm(add, add->entry, three, tc.i32, 3);
+ emit_binop(add, add->entry, sum, two, three, tc.i32);
+ emit_ret_val(add, add->entry, sum, tc.i32);
+ opt_build_cfg(add);
+ opt_gvn(add);
+ opt_verify(add, "test-gvn-ssa-fold-add");
+ EXPECT(val_is_load_imm(add, ret_val(add, add->entry), 5),
+ "GVN should fold safe integer binop constants");
+
+ Func* logical = new_func(&tc);
+ Val zero = add_val(logical, tc.i32);
+ Val not_zero = add_val(logical, tc.i32);
+ emit_load_imm(logical, logical->entry, zero, tc.i32, 0);
+ emit_unop(logical, logical->entry, not_zero, zero, tc.i32, UO_NOT);
+ emit_ret_val(logical, logical->entry, not_zero, tc.i32);
+ opt_build_cfg(logical);
+ opt_gvn(logical);
+ opt_verify(logical, "test-gvn-ssa-fold-unop");
+ EXPECT(val_is_load_imm(logical, ret_val(logical, logical->entry), 1),
+ "GVN should fold safe integer unop constants");
+
+ Func* cmp = new_func(&tc);
+ Val lhs = add_val(cmp, tc.i32);
+ Val rhs = add_val(cmp, tc.i32);
+ Val lt = add_val(cmp, tc.i32);
+ emit_load_imm(cmp, cmp->entry, lhs, tc.i32, 2);
+ emit_load_imm(cmp, cmp->entry, rhs, tc.i32, 3);
+ emit_cmp(cmp, cmp->entry, lt, lhs, rhs, tc.i32, CMP_LT_S);
+ emit_ret_val(cmp, cmp->entry, lt, tc.i32);
+ opt_build_cfg(cmp);
+ opt_gvn(cmp);
+ opt_verify(cmp, "test-gvn-ssa-fold-cmp");
+ EXPECT(val_is_load_imm(cmp, ret_val(cmp, cmp->entry), 1),
+ "GVN should fold safe integer cmp constants");
+
+ Func* trunc = new_func(&tc);
+ Val wide = add_val(trunc, tc.i64);
+ Val narrow = add_val(trunc, tc.i32);
+ emit_load_imm(trunc, trunc->entry, wide, tc.i64, 5);
+ emit_convert_typed(trunc, trunc->entry, narrow, wide, tc.i32, tc.i64,
+ CV_TRUNC);
+ emit_ret_val(trunc, trunc->entry, narrow, tc.i32);
+ opt_build_cfg(trunc);
+ opt_gvn(trunc);
+ opt_verify(trunc, "test-gvn-ssa-fold-convert");
+ EXPECT(val_is_load_imm(trunc, ret_val(trunc, trunc->entry), 5),
+ "GVN should fold safe integer conversion constants");
+
+ tc_fini(&tc);
+}
+
static void opt_jump_cleanup_forwards_branch_targets(void) {
TestCtx tc;
tc_init(&tc);
@@ -4458,6 +4689,11 @@ int main(void) {
opt_block_cloning_skips_loop_backedges();
opt_addr_xform_folds_local_addr_into_memory_operand();
opt_addr_xform_preserves_volatile_and_globals();
+ opt_gvn_rewrites_same_block_scalar_duplicate();
+ opt_gvn_rewrites_dominated_scalar_duplicate();
+ opt_gvn_preserves_nondominated_scalar_duplicates();
+ opt_gvn_canonicalizes_commutative_scalar_operands();
+ opt_gvn_folds_safe_scalar_constants();
opt_jump_cleanup_forwards_branch_targets();
opt_jump_cleanup_inverts_to_remove_jump_block();
opt_jump_cleanup_keeps_conditional_fallthrough_block();