kit

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

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:
Msrc/opt/opt.c | 2++
Mtest/opt/opt_test.c | 246+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
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();