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}