cg_ir_lower_test.c (6251B)
1 #include <kit/core.h> 2 #include <stdarg.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 7 #include "cg/ir.h" 8 #include "lib/kit_unit.h" 9 #include "opt/opt.h" 10 11 #undef Operand 12 #undef CGFuncDesc 13 #undef CGParamDesc 14 #undef CGCallDesc 15 #undef CGLocalStorage 16 17 /* Shared test context replaces the per-file heap/diag/counter globals; 18 * EXPECT aliases CU_EXPECT so the call sites are unchanged. ctx.now = -1 is 19 * preserved by setting it once after kit_unit_init in main(). */ 20 static KitUnit g_u; 21 #define EXPECT(cond, ...) CU_EXPECT(&g_u, cond, __VA_ARGS__) 22 23 typedef struct TestCtx { 24 Compiler* c; 25 KitCgTypeId i32; 26 } TestCtx; 27 28 static void tc_init(TestCtx* tc) { 29 KitTargetSpec target; 30 KitCgBuiltinTypes b; 31 memset(tc, 0, sizeof *tc); 32 target = kit_unit_target(KIT_ARCH_ARM_64, KIT_OS_MACOS, KIT_OBJ_MACHO); 33 if (kit_unit_compiler_new(&g_u, target, (KitCompiler**)&tc->c) != KIT_OK || 34 !tc->c) { 35 fprintf(stderr, "fatal: compiler allocation failed\n"); 36 abort(); 37 } 38 b = kit_cg_builtin_types(tc->c); 39 tc->i32 = b.id[KIT_CG_BUILTIN_I32]; 40 } 41 42 static void tc_fini(TestCtx* tc) { 43 kit_compiler_free(tc->c); 44 tc->c = NULL; 45 } 46 47 static Operand local_op(CGLocal local, KitCgTypeId type) { 48 Operand o; 49 memset(&o, 0, sizeof o); 50 o.kind = OPK_LOCAL; 51 o.type = type; 52 o.v.local = local; 53 return o; 54 } 55 56 static Operand imm_op(i64 value, KitCgTypeId type) { 57 Operand o; 58 memset(&o, 0, sizeof o); 59 o.kind = OPK_IMM; 60 o.type = type; 61 o.v.imm = value; 62 return o; 63 } 64 65 static CGLocal add_local(CgIrFunc* f, KitCgTypeId type, const char* name) { 66 CGLocalDesc d; 67 (void)name; 68 memset(&d, 0, sizeof d); 69 d.type = type; 70 d.size = 4; 71 d.align = 4; 72 return cg_ir_func_add_local(f, &d, 0, 0); 73 } 74 75 static CgIrInst* emit_ops(CgIrFunc* f, CgIrOp op, const Operand* ops, u32 n) { 76 CgIrInst* in = cg_ir_emit(f, op, (SrcLoc){0, 0, 0}); 77 in->opnds = cg_ir_dup_operands(f->arena, ops, n); 78 in->nopnds = n; 79 return in; 80 } 81 82 static void converter_builds_cfg_and_pregs(void) { 83 TestCtx tc; 84 tc_init(&tc); 85 86 CGFuncDesc fd; 87 memset(&fd, 0, sizeof fd); 88 fd.fn_type = tc.i32; 89 fd.result_type = tc.i32; 90 CgIrFunc* cg = cg_ir_func_new(tc.c, &fd); 91 CGLocal a = add_local(cg, tc.i32, "a"); 92 CGLocal b = add_local(cg, tc.i32, "b"); 93 Label done = cg_ir_func_add_label(cg); 94 95 Operand one[] = {local_op(a, tc.i32)}; 96 CgIrInst* li = emit_ops(cg, CG_IR_LOAD_IMM, one, 1); 97 li->extra.imm = 1; 98 99 Operand cmp[] = {local_op(a, tc.i32), imm_op(0, tc.i32)}; 100 CgIrInst* br = emit_ops(cg, CG_IR_CMP_BRANCH, cmp, 2); 101 CgIrCmpBranchAux* br_aux = arena_znew(cg->arena, CgIrCmpBranchAux); 102 br_aux->op = CMP_NE; 103 br_aux->target = done; 104 br->extra.aux = br_aux; 105 106 Operand two[] = {local_op(b, tc.i32)}; 107 CgIrInst* li2 = emit_ops(cg, CG_IR_LOAD_IMM, two, 1); 108 li2->extra.imm = 2; 109 110 CgIrInst* label = cg_ir_emit(cg, CG_IR_LABEL, (SrcLoc){0, 0, 0}); 111 label->extra.imm = (i64)done; 112 cg_ir_func_note_label_place(cg, done, (SrcLoc){0, 0, 0}); 113 114 CgIrInst* li3 = emit_ops(cg, CG_IR_LOAD_IMM, two, 1); 115 li3->extra.imm = 3; 116 117 CgIrRetAux* ret_aux = arena_znew(cg->arena, CgIrRetAux); 118 ret_aux->value = b; 119 ret_aux->present = 1; 120 CgIrInst* ret = cg_ir_emit(cg, CG_IR_RET, (SrcLoc){0, 0, 0}); 121 ret->extra.aux = ret_aux; 122 123 Func* f = opt_func_from_cg_ir(tc.c, cg); 124 EXPECT(f != NULL, "converter returned NULL"); 125 EXPECT(f->nlocals == 2, "expected 2 locals, got %u", f->nlocals); 126 EXPECT(f->npregs == 3, "expected two PRegs plus sentinel, got %u", f->npregs); 127 EXPECT(f->nblocks >= 3, "expected at least 3 blocks, got %u", f->nblocks); 128 EXPECT(f->blocks[f->entry].nsucc == 2, "entry should branch two ways"); 129 EXPECT(f->blocks[f->entry].ninsts == 2, "entry should contain load+branch"); 130 EXPECT(f->blocks[f->entry].insts[0].op == IR_LOAD_IMM, 131 "first inst should be IR_LOAD_IMM"); 132 EXPECT(f->blocks[f->entry].insts[0].opnds[0].kind == OPK_REG, 133 "local value should lower to PReg operand"); 134 135 tc_fini(&tc); 136 } 137 138 static void jump_cleanup_threads_empty_fallthrough_target(void) { 139 TestCtx tc; 140 tc_init(&tc); 141 142 Func f; 143 memset(&f, 0, sizeof f); 144 f.c = tc.c; 145 f.arena = tc.c->tu; 146 f.entry = ir_block_new(&f); 147 u32 then_block = ir_block_new(&f); 148 u32 scope_block = ir_block_new(&f); 149 u32 empty_block = ir_block_new(&f); 150 u32 merge_block = ir_block_new(&f); 151 ir_note_emit(&f, f.entry); 152 ir_note_emit(&f, then_block); 153 ir_note_emit(&f, scope_block); 154 ir_note_emit(&f, empty_block); 155 ir_note_emit(&f, merge_block); 156 157 Inst* br = ir_emit(&f, f.entry, IR_CMP_BRANCH); 158 br->extra.imm = CMP_EQ; 159 f.blocks[f.entry].succ[0] = scope_block; 160 f.blocks[f.entry].succ[1] = then_block; 161 f.blocks[f.entry].nsucc = 2; 162 163 Inst* then_body = ir_emit(&f, then_block, IR_LOAD_IMM); 164 (void)then_body; 165 Inst* then_br = ir_emit(&f, then_block, IR_BR); 166 (void)then_br; 167 f.blocks[then_block].succ[0] = merge_block; 168 f.blocks[then_block].nsucc = 1; 169 170 Inst* scope_end = ir_emit(&f, scope_block, IR_SCOPE_END); 171 (void)scope_end; 172 f.blocks[scope_block].succ[0] = empty_block; 173 f.blocks[scope_block].nsucc = 1; 174 175 f.blocks[empty_block].succ[0] = merge_block; 176 f.blocks[empty_block].nsucc = 1; 177 178 Inst* ret = ir_emit(&f, merge_block, IR_RET); 179 (void)ret; 180 181 opt_build_cfg(&f); 182 EXPECT(f.blocks[f.entry].succ[0] == scope_block, 183 "precondition: branch should target scope block"); 184 EXPECT(f.blocks[scope_block].npreds == 1, 185 "precondition: scope block should have one predecessor"); 186 187 opt_jump_cleanup(&f, OPT_JUMP_CLEANUP_CFG); 188 opt_build_cfg(&f); 189 190 EXPECT(f.blocks[f.entry].succ[0] == merge_block, 191 "branch target should forward through empty fallthrough block"); 192 EXPECT(f.blocks[scope_block].npreds == 0 && f.blocks[scope_block].nsucc == 0, 193 "scope block should become unreachable after forwarding"); 194 EXPECT(f.blocks[empty_block].npreds == 0 && f.blocks[empty_block].nsucc == 0, 195 "empty block should become unreachable after forwarding"); 196 197 tc_fini(&tc); 198 } 199 200 int main(void) { 201 kit_unit_init(&g_u); 202 g_u.ctx.now = -1; 203 converter_builds_cfg_and_pregs(); 204 jump_cleanup_threads_empty_fallthrough_target(); 205 if (g_u.fails) { 206 fprintf(stderr, "cg-ir-lower: %d/%d failed\n", g_u.fails, g_u.checks); 207 return 1; 208 } 209 printf("cg-ir-lower: %d checks, 0 failures\n", g_u.checks); 210 return 0; 211 }