code wiki / (root) / callee_save_test.nx

callee_save_test.nx source

↩ module page · 216 lines · 8740 B

1// callee_save_test.nx -- exercise emit_function's save_mask > 0 path. 2// 3// Hand-crafts a mask that claims s0 + s3 were used by regalloc, then 4// calls emit_function and verifies the asm output contains: 5// * prologue saves for s0 and s3 6// * an epilogue label .L<fn>_epi: 7// * epilogue restores + ld ra + addi + ret 8// * the RETURN instruction emits `j .L<fn>_epi` instead of inline 9// 10// Confirms mask == 0 path stays inline-return (no epilogue label 11// present in that output -- absence check). 12 13import "syscalls.nx" 14import "types.nx" 15import "ir.nx" 16import "outbuf.nx" 17import "riscv.nx" 18 19func buf_contains(buf: *u8, n: i64, needle: *u8) -> i64 { 20 var needle_len: i64 = 0 21 while needle[needle_len] != 0 { needle_len = needle_len + 1 } 22 if needle_len == 0 { return 1 } 23 var i: i64 = 0 24 while i + needle_len <= n { 25 var j: i64 = 0 26 var hit: i64 = 1 27 while j < needle_len { 28 if buf[i + j] != needle[j] { hit = 0; j = needle_len } 29 j = j + 1 30 } 31 if hit == 1 { return 1 } 32 i = i + 1 33 } 34 return 0 35} 36 37func main() -> i64 { 38 // --- 1: popcount ------------------------------------------- 39 if popcount(0) != 0 { return __syscall(93, 10, 0, 0, 0, 0, 0) } 40 if popcount(1) != 1 { return __syscall(93, 11, 0, 0, 0, 0, 0) } 41 if popcount(0xF) != 4 { return __syscall(93, 12, 0, 0, 0, 0, 0) } 42 if popcount(0x110) != 2 { return __syscall(93, 13, 0, 0, 0, 0, 0) } 43 44 // --- 2: mask == 0 path is byte-identical to old behaviour --- 45 // 46 // Build `main() -> i64 { return 42 }` with no saves. Asm must 47 // contain inline `ret` and NO epilogue label. 48 let m0_raw: *u8 = sys_mmap(256) 49 let m0: *Module = m0_raw as *Module 50 m0.name = "t0" as *u8 51 m0.functions = 0 as *Function 52 m0.n_functions = 0 53 54 let f0: *Function = ir_function_new(m0, "main" as *u8, 4, ir_type_i64()) 55 let b0: *BasicBlock = ir_block_new(f0) 56 let c42: i64 = ir_const_i64(f0, 42) 57 emit_return_instr(b0, c42) 58 59 let locs0_raw: *u8 = sys_mmap(256) 60 let locs0: *ValueLoc = locs0_raw as *ValueLoc 61 let o0: *OutBuf = out_new(4096) 62 emit_function(f0, locs0, o0, "main", 16, 8, 0, 0) // save_mask = 0 63 // Unified epilogue pattern: every function gets an _epi label, 64 // every RETURN jumps there, even when save_mask == 0. 65 if buf_contains(o0.buf, o0.pos, ".Lmain_epi:" as *u8) != 1 { 66 return __syscall(93, 20, 0, 0, 0, 0, 0) 67 } 68 if buf_contains(o0.buf, o0.pos, "j .Lmain_epi" as *u8) != 1 { 69 return __syscall(93, 21, 0, 0, 0, 0, 0) 70 } 71 if buf_contains(o0.buf, o0.pos, "ret" as *u8) != 1 { 72 return __syscall(93, 22, 0, 0, 0, 0, 0) 73 } 74 // No s-reg saves when mask == 0. 75 if buf_contains(o0.buf, o0.pos, "sd s0" as *u8) == 1 { 76 return __syscall(93, 23, 0, 0, 0, 0, 0) 77 } 78 79 // --- 3: mask > 0 path emits saves + epilogue label ---------- 80 // 81 // Use mask with bits 4 + 7 set (regalloc index 4 = s0, 82 // index 7 = s3). Frame grows by 16 bytes (2 saves). Expect: 83 // * prologue 'sd s0, 16(sp)' and 'sd s3, 24(sp)' 84 // (save_base = ra_slot + 8 = 16; s0 at 16, s3 at 24) 85 // * body RETURN becomes 'j .Lmain_epi' 86 // * epilogue label '.Lmain_epi:' + 'ld s0' + 'ld s3' + 87 // 'ld ra' + 'addi sp, sp, 32' + 'ret' 88 let m1_raw: *u8 = sys_mmap(256) 89 let m1: *Module = m1_raw as *Module 90 m1.name = "t1" as *u8 91 m1.functions = 0 as *Function 92 m1.n_functions = 0 93 94 let f1: *Function = ir_function_new(m1, "main" as *u8, 4, ir_type_i64()) 95 let b1: *BasicBlock = ir_block_new(f1) 96 let c42b: i64 = ir_const_i64(f1, 42) 97 emit_return_instr(b1, c42b) 98 99 let locs1_raw: *u8 = sys_mmap(256) 100 let locs1: *ValueLoc = locs1_raw as *ValueLoc 101 let o1: *OutBuf = out_new(4096) 102 let mask: i64 = (1 << 4) | (1 << 7) // s0 + s3 103 emit_function(f1, locs1, o1, "main", 16, 8, mask, 0) 104 105 // Epilogue label present. 106 if buf_contains(o1.buf, o1.pos, ".Lmain_epi:" as *u8) != 1 { 107 return __syscall(93, 30, 0, 0, 0, 0, 0) 108 } 109 // Prologue saves (save_base = ra_slot + 8 = 16). 110 if buf_contains(o1.buf, o1.pos, "sd s0, 16(sp)" as *u8) != 1 { 111 return __syscall(93, 31, 0, 0, 0, 0, 0) 112 } 113 if buf_contains(o1.buf, o1.pos, "sd s3, 24(sp)" as *u8) != 1 { 114 return __syscall(93, 32, 0, 0, 0, 0, 0) 115 } 116 // Body RETURN jumps to epilogue. 117 if buf_contains(o1.buf, o1.pos, "j .Lmain_epi" as *u8) != 1 { 118 return __syscall(93, 33, 0, 0, 0, 0, 0) 119 } 120 // Epilogue restores. 121 if buf_contains(o1.buf, o1.pos, "ld s0, 16(sp)" as *u8) != 1 { 122 return __syscall(93, 34, 0, 0, 0, 0, 0) 123 } 124 if buf_contains(o1.buf, o1.pos, "ld s3, 24(sp)" as *u8) != 1 { 125 return __syscall(93, 35, 0, 0, 0, 0, 0) 126 } 127 // Frame grew from 16 to 32 (two saves * 8 bytes). 128 if buf_contains(o1.buf, o1.pos, "addi sp, sp, -32" as *u8) != 1 { 129 return __syscall(93, 36, 0, 0, 0, 0, 0) 130 } 131 if buf_contains(o1.buf, o1.pos, "addi sp, sp, 32" as *u8) != 1 { 132 return __syscall(93, 37, 0, 0, 0, 0, 0) 133 } 134 // CFI unwinding directives: 135 // .cfi_startproc + .cfi_def_cfa_offset 32 + .cfi_offset ra, -24 + 136 // .cfi_offset s0, -16 + .cfi_offset s3, -8 + .cfi_endproc 137 if buf_contains(o1.buf, o1.pos, ".cfi_startproc" as *u8) != 1 { 138 return __syscall(93, 38, 0, 0, 0, 0, 0) 139 } 140 if buf_contains(o1.buf, o1.pos, ".cfi_def_cfa_offset 32" as *u8) != 1 { 141 return __syscall(93, 39, 0, 0, 0, 0, 0) 142 } 143 if buf_contains(o1.buf, o1.pos, ".cfi_offset ra, -24" as *u8) != 1 { 144 return __syscall(93, 42, 0, 0, 0, 0, 0) 145 } 146 if buf_contains(o1.buf, o1.pos, ".cfi_offset s0, -16" as *u8) != 1 { 147 return __syscall(93, 43, 0, 0, 0, 0, 0) 148 } 149 if buf_contains(o1.buf, o1.pos, ".cfi_offset s3, -8" as *u8) != 1 { 150 return __syscall(93, 44, 0, 0, 0, 0, 0) 151 } 152 if buf_contains(o1.buf, o1.pos, ".cfi_endproc" as *u8) != 1 { 153 return __syscall(93, 45, 0, 0, 0, 0, 0) 154 } 155 156 // --- 4: two-digit s-reg name (s10, s11) ---------------------- 157 // 158 // Mask with bit 14 (s10) set. The emitter has separate 159 // 1-digit / 2-digit branches -- confirm 's10' lands correctly. 160 let mask2: i64 = (1 << 14) // s10 161 let o2: *OutBuf = out_new(4096) 162 emit_function(f1, locs1, o2, "main", 16, 8, mask2, 0) 163 if buf_contains(o2.buf, o2.pos, "sd s10, 16(sp)" as *u8) != 1 { 164 return __syscall(93, 40, 0, 0, 0, 0, 0) 165 } 166 if buf_contains(o2.buf, o2.pos, "ld s10, 16(sp)" as *u8) != 1 { 167 return __syscall(93, 41, 0, 0, 0, 0, 0) 168 } 169 170 // --- 5: fs-reg saves via save_mask_fpr ----------------------- 171 // 172 // Pass save_mask_fpr with fs1 + fs4 set (bits 1 + 4). Expect: 173 // * prologue 'fsd fs1, 16(sp)' and 'fsd fs4, 24(sp)' 174 // (save_base_fpr = ra_slot+8+gpr_count*8 = 8+8+0 = 16) 175 // * epilogue 'fld fs1' and 'fld fs4' 176 let fpr_mask: i64 = (1 << 1) | (1 << 4) // fs1 + fs4 177 let o3: *OutBuf = out_new(4096) 178 emit_function(f1, locs1, o3, "main", 16, 8, 0, fpr_mask) 179 if buf_contains(o3.buf, o3.pos, "fsd fs1, 16(sp)" as *u8) != 1 { 180 return __syscall(93, 50, 0, 0, 0, 0, 0) 181 } 182 if buf_contains(o3.buf, o3.pos, "fsd fs4, 24(sp)" as *u8) != 1 { 183 return __syscall(93, 51, 0, 0, 0, 0, 0) 184 } 185 if buf_contains(o3.buf, o3.pos, "fld fs1, 16(sp)" as *u8) != 1 { 186 return __syscall(93, 52, 0, 0, 0, 0, 0) 187 } 188 if buf_contains(o3.buf, o3.pos, "fld fs4, 24(sp)" as *u8) != 1 { 189 return __syscall(93, 53, 0, 0, 0, 0, 0) 190 } 191 // Frame grew by 2 fs-reg saves = 16 bytes; total frame = 32. 192 if buf_contains(o3.buf, o3.pos, "addi sp, sp, -32" as *u8) != 1 { 193 return __syscall(93, 54, 0, 0, 0, 0, 0) 194 } 195 196 // --- 6: combined GPR + FPR saves ----------------------------- 197 // 198 // Mix s0 (GPR bit 4) + fs2 (FPR bit 2). Layout: 199 // ra at 8 200 // s0 at 16 (save_base_gpr = 16) 201 // fs2 at 24 (save_base_fpr = 16 + 1*8 = 24) 202 // frame 32 203 let o4: *OutBuf = out_new(4096) 204 emit_function(f1, locs1, o4, "main", 16, 8, (1 << 4), (1 << 2)) 205 if buf_contains(o4.buf, o4.pos, "sd s0, 16(sp)" as *u8) != 1 { 206 return __syscall(93, 60, 0, 0, 0, 0, 0) 207 } 208 if buf_contains(o4.buf, o4.pos, "fsd fs2, 24(sp)" as *u8) != 1 { 209 return __syscall(93, 61, 0, 0, 0, 0, 0) 210 } 211 if buf_contains(o4.buf, o4.pos, "addi sp, sp, -32" as *u8) != 1 { 212 return __syscall(93, 62, 0, 0, 0, 0, 0) 213 } 214 215 return 0 216}