kit

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

commit 652fef0cc277f799a66c21f463aa6a56fd1e8d93
parent cbeab9e6eaea110d2dc9f1a3263f8db8587a46d2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 14 May 2026 16:55:38 -0700

Test O1 on x64 and rv64

Enable the parse harness to drive opt levels from CFREE_OPT_LEVEL, tighten O1 lowering and backend coordination for x64/rv64, and add regression coverage for rv64 32-bit comparison canonicalization including signed bitfields.

Diffstat:
Msrc/arch/rv64/alloc.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/rv64/ops.c | 21+++++++++++++++++++++
Msrc/arch/rv64/opt_coord.c | 10++++------
Msrc/arch/x64/ops.c | 12++++++++++++
Msrc/arch/x64/opt_coord.c | 24+++++++++++++++++-------
Msrc/opt/opt.c | 18++++++++++++++++--
Msrc/opt/pass_lower.c | 54+++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mtest/opt/opt_test.c | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Atest/parse/cases/6_5_16_05_i32_eq_minus_one.c | 4++++
Atest/parse/cases/6_5_16_05_i32_eq_minus_one.expected | 1+
Atest/parse/cases/6_5_16_06_uint_order_high_bit.c | 4++++
Atest/parse/cases/6_5_16_06_uint_order_high_bit.expected | 1+
Mtest/parse/harness/parse_runner.c | 12++++++++++++
13 files changed, 318 insertions(+), 22 deletions(-)

diff --git a/src/arch/rv64/alloc.c b/src/arch/rv64/alloc.c @@ -192,6 +192,50 @@ u32 rv64_force_reg_int(CGTarget* t, Operand op, u32 scratch) { "rv64: operand kind %d unsupported here", (int)op.kind); } +static int signed_order_cmp_op(CmpOp op) { + return op == CMP_LT_S || op == CMP_LE_S || + op == CMP_GT_S || op == CMP_GE_S; +} + +static int unsigned_order_cmp_op(CmpOp op) { + return op == CMP_LT_U || op == CMP_LE_U || + op == CMP_GT_U || op == CMP_GE_U; +} + +static u32 sign_extend_i32_for_cmp(MCEmitter* mc, u32 src, u32 other) { + u32 dst = (src == RV_T0 || src == RV_T1) ? src + : ((other == RV_T0) ? RV_T1 + : RV_T0); + rv64_emit32(mc, rv_addiw(dst, src, 0)); + return dst; +} + +static u32 zero_extend_i32_for_cmp(MCEmitter* mc, u32 src, u32 other) { + u32 dst = (src == RV_T0 || src == RV_T1) ? src + : ((other == RV_T0) ? RV_T1 + : RV_T0); + rv64_emit32(mc, rv_slli(dst, src, 32)); + rv64_emit32(mc, rv_srli(dst, dst, 32)); + return dst; +} + +static void canonicalize_i32_cmp_operands(MCEmitter* mc, CmpOp op, + CfreeCgTypeId type, + u32* ra, u32* rb) { + if (type_byte_size(type) != 4u) return; + + if (unsigned_order_cmp_op(op)) { + *ra = zero_extend_i32_for_cmp(mc, *ra, *rb); + *rb = zero_extend_i32_for_cmp(mc, *rb, *ra); + return; + } + + if (signed_order_cmp_op(op) || op == CMP_EQ || op == CMP_NE) { + *ra = sign_extend_i32_for_cmp(mc, *ra, *rb); + *rb = sign_extend_i32_for_cmp(mc, *rb, *ra); + } +} + /* Emit a conditional branch (a OP b) → label. Uses BEQ/BNE/BLT/BGE etc. */ void rv_cmp_branch(CGTarget* t, CmpOp op, Operand a_op, Operand b_op, Label l) { @@ -203,6 +247,7 @@ void rv_cmp_branch(CGTarget* t, CmpOp op, Operand a_op, Operand b_op, } u32 ra = rv64_force_reg_int(t, a_op, RV_T0); u32 rb = rv64_force_reg_int(t, b_op, (ra == RV_T0) ? RV_T1 : RV_T0); + canonicalize_i32_cmp_operands(mc, op, a_op.type, &ra, &rb); u32 word = 0; switch (op) { case CMP_EQ: word = rv_beq(ra, rb, 0); break; @@ -246,6 +291,7 @@ void rv_cmp(CGTarget* t, CmpOp op, Operand dst, Operand a_op, } u32 ra = rv64_force_reg_int(t, a_op, RV_T0); u32 rb = rv64_force_reg_int(t, b_op, (ra == RV_T0) ? RV_T1 : RV_T0); + canonicalize_i32_cmp_operands(mc, op, a_op.type, &ra, &rb); switch (op) { case CMP_EQ: diff --git a/src/arch/rv64/ops.c b/src/arch/rv64/ops.c @@ -869,6 +869,14 @@ static void emit_arg_value(CGTarget* t, const CGABIValue* av, u32* next_int, rv64_emit32(mc, rv_fsgnj(fmt, freg, r, r)); break; } + case OPK_LOCAL: { + RvSlot* s = rv64_slot_get(a, av->storage.v.frame_slot); + if (!s) compiler_panic(t->c, a->loc, "rv64 call: bad FP arg slot"); + i32 off = -(i32)s->off + (i32)pt->src_offset; + rv64_emit32(mc, (sz == 8) ? rv_fld(freg, RV_S0, off) + : rv_flw(freg, RV_S0, off)); + break; + } case OPK_INDIRECT: { u32 base = av->storage.v.ind.base & 0x1fu; i32 off = av->storage.v.ind.ofs + (i32)pt->src_offset; @@ -886,6 +894,19 @@ static void emit_arg_value(CGTarget* t, const CGABIValue* av, u32* next_int, if (sz == 8) rv64_emit32(mc, rv_fsd(reg_num(av->storage), RV_SP, (i32)*stack_off)); else rv64_emit32(mc, rv_fsw(reg_num(av->storage), RV_SP, (i32)*stack_off)); break; + case OPK_LOCAL: { + RvSlot* s = rv64_slot_get(a, av->storage.v.frame_slot); + if (!s) compiler_panic(t->c, a->loc, "rv64 call: bad FP arg slot"); + i32 off = -(i32)s->off + (i32)pt->src_offset; + if (sz == 8) { + rv64_emit32(mc, rv_fld(/*ft0=*/0u, RV_S0, off)); + rv64_emit32(mc, rv_fsd(/*ft0=*/0u, RV_SP, (i32)*stack_off)); + } else { + rv64_emit32(mc, rv_flw(/*ft0=*/0u, RV_S0, off)); + rv64_emit32(mc, rv_fsw(/*ft0=*/0u, RV_SP, (i32)*stack_off)); + } + break; + } case OPK_INDIRECT: { /* Route through ft0 — it is in {ft0..ft7}, caller-saved * scratch outside the cg fs2..fs11 pool. */ diff --git a/src/arch/rv64/opt_coord.c b/src/arch/rv64/opt_coord.c @@ -5,13 +5,11 @@ /* ============================================================ * Static register tables reported to caller-owned allocators. */ -static const Reg rv_int_allocable[] = {18, 19, 20, 21, 22, - 23, 24, 25, 26, 27}; -static const Reg rv_fp_allocable[] = {18, 19, 20, 21, 22, - 23, 24, 25, 26, 27}; +static const Reg rv_int_allocable[] = {20, 21, 22, 23, 24, 25, 26, 27}; +static const Reg rv_fp_allocable[] = {20, 21, 22, 23, 24, 25, 26, 27}; -static const Reg rv_int_scratch[] = {5, 6}; /* t0, t1 */ -static const Reg rv_fp_scratch[] = {0}; /* ft0 */ +static const Reg rv_int_scratch[] = {18, 19}; /* s2, s3; reserved by opt_emit */ +static const Reg rv_fp_scratch[] = {18, 19}; /* fs2, fs3; reserved by opt_emit */ /* ============================================================ * Vtable methods */ diff --git a/src/arch/x64/ops.c b/src/arch/x64/ops.c @@ -799,6 +799,11 @@ static void emit_arg_value(CGTarget* t, const CGABIValue* av, u32* next_int, if (av->storage.kind == OPK_REG) { u32 sx = av->storage.v.reg & 0xFu; if (sx != dst_x) emit_sse_rr(t->mc, prefix2, 0x10, dst_x, sx); + } else if (av->storage.kind == OPK_LOCAL) { + XSlot* s = x64_slot_get(a, av->storage.v.frame_slot); + if (!s) compiler_panic(t->c, a->loc, "x64 call: bad FP arg slot"); + emit_sse_load(t->mc, prefix2, 0x10, dst_x, X64_RBP, + -(i32)s->off + (i32)pt->src_offset); } else if (av->storage.kind == OPK_INDIRECT) { emit_sse_load(t->mc, prefix2, 0x10, dst_x, av->storage.v.ind.base & 0xFu, @@ -812,6 +817,13 @@ static void emit_arg_value(CGTarget* t, const CGABIValue* av, u32* next_int, if (av->storage.kind == OPK_REG) { emit_sse_store(t->mc, prefix2, 0x11, av->storage.v.reg & 0xFu, X64_RSP, (i32)*stack_off); + } else if (av->storage.kind == OPK_LOCAL) { + XSlot* s = x64_slot_get(a, av->storage.v.frame_slot); + if (!s) compiler_panic(t->c, a->loc, "x64 call: bad FP arg slot"); + emit_sse_load(t->mc, prefix2, 0x10, X64_XMM15, X64_RBP, + -(i32)s->off + (i32)pt->src_offset); + emit_sse_store(t->mc, prefix2, 0x11, X64_XMM15, X64_RSP, + (i32)*stack_off); } else if (av->storage.kind == OPK_INDIRECT) { /* Load through xmm15 (scratch — last in g_fp_order so cg won't * have it live mid-call) then store. */ diff --git a/src/arch/x64/opt_coord.c b/src/arch/x64/opt_coord.c @@ -3,10 +3,20 @@ #include "arch/x64/internal.h" /* ============================================================ - * Scratch tables used by backend internals and opt spill rewriting. */ + * Register tables used by opt. + * + * Spill scratch registers are passed back to normal backend emit calls as + * operands, so keep them out of the backend's internal scratch set (RAX/R11, + * XMM15 in a few lowering paths) and out of opt's allocable pool. */ -static const Reg x_int_scratch[] = {X64_R11}; /* RAX is reserved for backend */ -static const Reg x_fp_scratch[] = {X64_XMM0 + 15}; /* xmm15 */ +static const Reg x_int_allocable[] = {X64_R13, X64_R14, X64_R15, X64_R10}; +static const Reg x_fp_allocable[] = {X64_XMM6, X64_XMM7, X64_XMM8, + X64_XMM0 + 9, X64_XMM0 + 10, + X64_XMM0 + 11, X64_XMM0 + 12, + X64_XMM0 + 13}; + +static const Reg x_int_scratch[] = {X64_RBX, X64_R12}; +static const Reg x_fp_scratch[] = {X64_XMM0 + 14, X64_XMM15}; /* ============================================================ * Vtable methods */ @@ -16,12 +26,12 @@ static void x_get_allocable_regs(CGTarget* t, RegClass cls, (void)t; switch (cls) { case RC_INT: - *out = g_int_order; - *nregs = 6u; + *out = x_int_allocable; + *nregs = sizeof x_int_allocable / sizeof x_int_allocable[0]; break; case RC_FP: - *out = g_fp_order; - *nregs = 10u; + *out = x_fp_allocable; + *nregs = sizeof x_fp_allocable / sizeof x_fp_allocable[0]; break; default: *out = NULL; diff --git a/src/opt/opt.c b/src/opt/opt.c @@ -247,9 +247,10 @@ static void w_cmp_branch(CGTarget* t, CmpOp op, Operand a, Operand b, Label l) { in->opnds = dup_opnds(o->f, ops, 2); in->nopnds = 2; in->extra.imm = (i64)op; - Block* cb = &o->f->blocks[o->cur]; - cb->succ[0] = taken; + u32 cur = o->cur; u32 ft = ir_block_new(o->f); + Block* cb = &o->f->blocks[cur]; + cb->succ[0] = taken; cb->succ[1] = ft; cb->nsucc = 2; set_cur(o, ft); @@ -391,6 +392,11 @@ static void w_load_const(CGTarget* t, Operand dst, ConstBytes cb) { in->opnds = dup_opnds(o->f, ops, 1); in->nopnds = 1; in->extra.cbytes = cb; + if (cb.size) { + u8* bytes = arena_array(o->f->arena, u8, cb.size); + memcpy(bytes, cb.bytes, cb.size); + in->extra.cbytes.bytes = bytes; + } if (dst.kind == OPK_REG) set_def(o->f, in, o->cur, (Val)dst.v.reg, dst.type); } @@ -1296,6 +1302,14 @@ static void replay_func_to(Compiler* c, Func* f, CGTarget* w, int identity) { } if (!already) used[nused++] = hr; } + for (u32 i = 0; i < f->opt_scratch_reg_count[c]; ++i) { + Reg hr = f->opt_scratch_regs[c][i]; + int already = 0; + for (u32 j = 0; j < nused; ++j) { + if (used[j] == hr) { already = 1; break; } + } + if (!already && nused < OPT_MAX_HARD_REGS) used[nused++] = hr; + } if (nused) w->reserve_hard_regs(w, (RegClass)c, used, nused); } } else if (!r.identity_regs && w->reserve_hard_regs) { diff --git a/src/opt/pass_lower.c b/src/opt/pass_lower.c @@ -661,6 +661,12 @@ void opt_live_info(Func* f) { BitsCtx bc = {use, def}; walk_inst_operands(f, in, collect_bits, &bc); + for (Val d = 1; d < f->nvals; ++d) { + if (!bit_has(def, d)) continue; + for (Val u = 1; u < f->nvals; ++u) + if (bit_has(use, u)) add_conflict(f, d, u); + } + for (Val v = 1; v < f->nvals; ++v) { if (bit_has(def, v)) { add_conflicts_with_set(f, v, after, def); @@ -873,6 +879,41 @@ static void rewrite_one_operand(Func* f, Inst* owner, Operand* op, int is_def, } } +static void rewrite_call_arg_operand(Func* f, Operand* op) { + if (!op || op->kind != OPK_REG) return; + Val v = (Val)op->v.reg; + if (v == VAL_NONE || v >= f->nvals) return; + OptValInfo* vi = &f->val_info[v]; + if (vi->alloc_kind == OPT_ALLOC_HARD) { + op->v.reg = vi->hard_reg; + } else if (vi->alloc_kind == OPT_ALLOC_SPILL) { + *op = spill_addr(f, v); + } +} + +static void rewrite_call_arg_indirect_base(Func* f, Inst* owner, Operand* op, + RewriteCtx* ctx) { + if (!op || op->kind != OPK_INDIRECT) return; + Operand base = *op; + base.kind = OPK_REG; + base.cls = RC_INT; + base.v.reg = op->v.ind.base; + rewrite_one_operand(f, owner, &base, 0, ctx); + op->v.ind.base = base.v.reg; +} + +static void rewrite_call_arg_value(Func* f, Inst* owner, CGABIValue* v, + RewriteCtx* ctx) { + if (!v) return; + rewrite_call_arg_indirect_base(f, owner, &v->storage, ctx); + rewrite_call_arg_operand(f, &v->storage); + for (u32 i = 0; i < v->nparts; ++i) { + Operand* op = (Operand*)&v->parts[i].op; + rewrite_call_arg_indirect_base(f, owner, op, ctx); + rewrite_call_arg_operand(f, op); + } +} + static void rewrite_func(Func* f) { for (u32 b = 0; b < f->nblocks; ++b) { Block* bl = &f->blocks[b]; @@ -892,7 +933,18 @@ static void rewrite_func(Func* f) { memset(&ctx, 0, sizeof ctx); ctx.before = &before; ctx.after = &after; - walk_inst_operands(f, &in, rewrite_one_operand, &ctx); + if ((IROp)in.op == IR_CALL) { + IRCallAux* aux = (IRCallAux*)in.extra.aux; + if (aux) { + rewrite_one_operand(f, &in, &aux->desc.callee, 0, &ctx); + for (u32 k = 0; k < aux->desc.nargs; ++k) + rewrite_call_arg_value(f, &in, (CGABIValue*)&aux->desc.args[k], + &ctx); + walk_abivalue(f, &in, &aux->desc.ret, 1, rewrite_one_operand, &ctx); + } + } else { + walk_inst_operands(f, &in, rewrite_one_operand, &ctx); + } for (u32 k = 0; k < before.n; ++k) { Inst* dst = list_push(f, &out, (IROp)before.data[k].op); *dst = before.data[k]; diff --git a/test/opt/opt_test.c b/test/opt/opt_test.c @@ -64,6 +64,7 @@ typedef struct TestCtx { Compiler* c; CfreeCgTypeId i32; CfreeCgTypeId i64; + CfreeCgTypeId f64; } TestCtx; static void tc_init(TestCtx* tc) { @@ -89,6 +90,7 @@ static void tc_init(TestCtx* tc) { CfreeCgBuiltinTypes b = cfree_cg_builtin_types(tc->c); tc->i32 = b.id[CFREE_CG_BUILTIN_I32]; tc->i64 = b.id[CFREE_CG_BUILTIN_I64]; + tc->f64 = b.id[CFREE_CG_BUILTIN_F64]; } } @@ -104,6 +106,12 @@ static Operand op_reg_(Reg r, CfreeCgTypeId ty) { return o; } +static Operand op_reg_cls_(Reg r, CfreeCgTypeId ty, RegClass cls) { + Operand o = op_reg_(r, ty); + o.cls = cls; + return o; +} + static Operand op_imm_(i64 v, CfreeCgTypeId ty) { Operand o; memset(&o, 0, sizeof o); @@ -330,6 +338,10 @@ typedef struct MockCGTarget { int reserve_calls[OPT_REG_CLASSES]; int load_imm_calls; Reg last_load_imm_dst; + int load_const_calls; + u8 last_const_bytes[16]; + u32 last_const_size; + int cmp_branch_calls; } MockCGTarget; static void mock_func_begin(CGTarget* t, const CGFuncDesc* d) { @@ -391,6 +403,15 @@ static void mock_load_imm(CGTarget* t, Operand dst, i64 imm) { m->last_load_imm_dst = dst.v.reg; } +static void mock_load_const(CGTarget* t, Operand dst, ConstBytes cb) { + MockCGTarget* m = (MockCGTarget*)t; + (void)dst; + ++m->load_const_calls; + m->last_const_size = cb.size; + if (cb.size <= sizeof m->last_const_bytes && cb.bytes) + memcpy(m->last_const_bytes, cb.bytes, cb.size); +} + static void mock_ret(CGTarget* t, const CGABIValue* v) { (void)t; (void)v; @@ -410,6 +431,16 @@ static void mock_jump(CGTarget* t, Label l) { (void)l; } +static void mock_cmp_branch(CGTarget* t, CmpOp op, Operand a, Operand b, + Label l) { + MockCGTarget* m = (MockCGTarget*)t; + (void)op; + (void)a; + (void)b; + (void)l; + ++m->cmp_branch_calls; +} + static FrameSlot mock_frame_slot(CGTarget* t, const FrameSlotDesc* d) { (void)t; (void)d; @@ -431,7 +462,9 @@ static void mock_init(MockCGTarget* m, Compiler* c) { m->base.label_new = mock_label_new; m->base.label_place = mock_label_place; m->base.jump = mock_jump; + m->base.cmp_branch = mock_cmp_branch; m->base.load_imm = mock_load_imm; + m->base.load_const = mock_load_const; m->base.ret = mock_ret; m->base.set_loc = mock_set_loc; m->base.get_allocable_regs = mock_get_allocable_regs; @@ -606,10 +639,10 @@ static void opt_interference_def_live_out(void) { "a and b should conflict while both are live"); EXPECT(val_conflicts(f, bv, a), "conflicts should be symmetric"); - EXPECT(!val_conflicts(f, a, c), - "a should not conflict with c after c's def kills a"); - EXPECT(!val_conflicts(f, bv, c), - "b should not conflict with c after c's def kills b"); + EXPECT(val_conflicts(f, a, c), + "a should conflict with c for same-instruction def/use lowering"); + EXPECT(val_conflicts(f, bv, c), + "b should conflict with c for same-instruction def/use lowering"); tc_fini(&tc); } @@ -1758,8 +1791,8 @@ static void opt_emit_no_virtual_alloc(void) { opt->ret(opt, &retv); opt->func_end(opt); - EXPECT(mock.reserve_calls[RC_INT] == 1, - "opt_emit should reserve the 1 hard pool reg used, got %d", + EXPECT(mock.reserve_calls[RC_INT] == 3, + "opt_emit should reserve the hard pool reg and 2 scratch regs, got %d", mock.reserve_calls[RC_INT]); EXPECT(mock.load_imm_calls == 1, "expected one emitted load_imm"); EXPECT(mock.last_load_imm_dst == 19, @@ -1770,6 +1803,92 @@ static void opt_emit_no_virtual_alloc(void) { tc_fini(&tc); } +static void opt_records_const_bytes_by_value(void) { + TestCtx tc; + tc_init(&tc); + MockCGTarget mock; + mock_init(&mock, tc.c); + static const Reg fp_pool[] = {19}; + static const Reg fp_scratch[] = {9, 10}; + mock_set_pool(&mock, RC_FP, fp_pool, 1, fp_scratch, 2, 0x4007FFFFu); + + CGTarget* opt = opt_cgtarget_new(tc.c, &mock.base, 1); + CGFuncDesc fd; + CfreeCgFuncSig sig; + memset(&fd, 0, sizeof fd); + memset(&sig, 0, sizeof sig); + sig.ret = tc.f64; + sig.call_conv = CFREE_CG_CC_TARGET_C; + fd.fn_type = cfree_cg_type_func(tc.c, sig); + opt->func_begin(opt, &fd); + + u8 bytes[8] = {0, 0, 0, 0, 0, 0, 0x45, 0x40}; /* 42.0 */ + ConstBytes cb; + memset(&cb, 0, sizeof cb); + cb.type = tc.f64; + cb.bytes = bytes; + cb.size = sizeof bytes; + cb.align = 8; + Operand dst = op_reg_cls_(1, tc.f64, RC_FP); + opt->load_const(opt, dst, cb); + memset(bytes, 0xa5, sizeof bytes); + + CGABIValue retv = {0}; + retv.type = tc.f64; + retv.storage = dst; + opt->ret(opt, &retv); + opt->func_end(opt); + + EXPECT(mock.load_const_calls == 1, "expected one emitted load_const"); + EXPECT(mock.last_const_size == 8, "expected 8-byte FP constant"); + EXPECT(mock.last_const_bytes[0] == 0 && mock.last_const_bytes[6] == 0x45 && + mock.last_const_bytes[7] == 0x40, + "opt replay should preserve original const bytes"); + + opt->destroy(opt); + tc_fini(&tc); +} + +static void opt_cmp_branch_keeps_fallthrough_after_block_growth(void) { + TestCtx tc; + tc_init(&tc); + MockCGTarget mock; + mock_init(&mock, tc.c); + static const Reg pool[] = {19}; + static const Reg scratch[] = {9, 10}; + mock_set_pool(&mock, RC_INT, pool, 1, scratch, 2, 0x4007FFFFu); + + CGTarget* opt = opt_cgtarget_new(tc.c, &mock.base, 1); + CGFuncDesc fd; + CfreeCgFuncSig sig; + memset(&fd, 0, sizeof fd); + memset(&sig, 0, sizeof sig); + sig.ret = tc.i32; + sig.call_conv = CFREE_CG_CC_TARGET_C; + fd.fn_type = cfree_cg_type_func(tc.c, sig); + opt->func_begin(opt, &fd); + + Label labels[7]; + for (u32 i = 0; i < 7; ++i) labels[i] = opt->label_new(opt); + for (u32 i = 0; i < 7; ++i) opt->label_place(opt, labels[i]); + + opt->cmp_branch(opt, CMP_EQ, op_imm_(0, tc.i32), op_imm_(1, tc.i32), + labels[0]); + opt->load_imm(opt, op_reg_(1, tc.i32), 42); + CGABIValue retv = {0}; + retv.type = tc.i32; + retv.storage = op_reg_(1, tc.i32); + opt->ret(opt, &retv); + opt->func_end(opt); + + EXPECT(mock.cmp_branch_calls == 1, "expected one emitted cmp_branch"); + EXPECT(mock.load_imm_calls == 1, + "cmp_branch fallthrough block should remain reachable"); + + opt->destroy(opt); + tc_fini(&tc); +} + static void simple_regalloc_reports_exact_used_regs(void) { CGSimpleRegAlloc a; static const Reg regs[] = {3, 7, 11}; @@ -1825,6 +1944,8 @@ int main(void) { opt_dead_def_keeps_observable_loads(); opt_dead_def_elim_test(); opt_emit_no_virtual_alloc(); + opt_records_const_bytes_by_value(); + opt_cmp_branch_keeps_fallthrough_after_block_growth(); simple_regalloc_reports_exact_used_regs(); if (g_fails) { fprintf(stderr, "opt tests: %d failed (%d checks)\n", g_fails, g_checks); diff --git a/test/parse/cases/6_5_16_05_i32_eq_minus_one.c b/test/parse/cases/6_5_16_05_i32_eq_minus_one.c @@ -0,0 +1,4 @@ +int test_main(void) { + int x = -1; + return (x == -1) ? 42 : 0; +} diff --git a/test/parse/cases/6_5_16_05_i32_eq_minus_one.expected b/test/parse/cases/6_5_16_05_i32_eq_minus_one.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_5_16_06_uint_order_high_bit.c b/test/parse/cases/6_5_16_06_uint_order_high_bit.c @@ -0,0 +1,4 @@ +int test_main(void) { + unsigned int x = 0xffffffffu; + return (x > 0xfffffffeu) ? 42 : 0; +} diff --git a/test/parse/cases/6_5_16_06_uint_order_high_bit.expected b/test/parse/cases/6_5_16_06_uint_order_high_bit.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/harness/parse_runner.c b/test/parse/harness/parse_runner.c @@ -257,6 +257,16 @@ static void env_init(CfreeEnv* env) { env->now = -1; } +static int opt_level_from_env(void) { + const char* s = getenv("CFREE_OPT_LEVEL"); + if (!s || !*s) return 0; + if (!strcmp(s, "0")) return 0; + if (!strcmp(s, "1")) return 1; + if (!strcmp(s, "2")) return 2; + fprintf(stderr, "parse-runner: invalid CFREE_OPT_LEVEL=%s\n", s); + exit(2); +} + /* ---- modes ---- */ static int mode_emit(const char* src_path, const char* out_path) { @@ -293,6 +303,7 @@ static int mode_emit(const char* src_path, const char* out_path) { in.lang = CFREE_LANG_C; memset(&opts, 0, sizeof opts); + opts.opt_level = opt_level_from_env(); w = cfree_writer_mem(&g_heap); if (cfree_compile_obj_emit(c, &opts, &in, w) != 0) { @@ -369,6 +380,7 @@ static int mode_jit(const char* src_path) { in.len = src_len; in.lang = CFREE_LANG_C; memset(&opts, 0, sizeof opts); + opts.opt_level = opt_level_from_env(); if (cfree_compile_obj(c, &opts, &in, &ob) != 0 || !ob) { cfree_compiler_free(c);