1use crate::riscv::target::RiscVTarget;
3use std::io::{Error, Write};
4
5pub struct RiscVFrame;
6
7const SAVED_PAIR_BYTES: i32 = 16;
9
10const IMM12_MIN: i64 = -2048;
12const IMM12_MAX: i64 = 2047;
13
14impl RiscVFrame {
15 fn locals_bytes(
20 stack_size: usize,
21 callee_saved_registers: &[&str],
22 target: RiscVTarget,
23 ) -> usize {
24 let callee_saved_bytes = callee_saved_registers.len() * target.word_bytes() as usize;
25 (stack_size + callee_saved_bytes).div_ceil(16) * 16
26 }
27
28 fn callee_saved_offset(stack_size: usize, index: usize, target: RiscVTarget) -> i32 {
29 -(SAVED_PAIR_BYTES + stack_size as i32 + (index as i32 + 1) * target.word_bytes())
30 }
31
32 pub fn generate_prologue<W: Write>(
34 writer: &mut W,
35 stack_size: usize,
36 callee_saved_registers: &[&str],
37 target: RiscVTarget,
38 ) -> Result<(), Error> {
39 let w = target.word_bytes();
41 let sw = target.store_word();
42 writeln!(writer, " addi sp, sp, -{SAVED_PAIR_BYTES}")?;
43 writeln!(writer, " {sw} ra, {}(sp)", SAVED_PAIR_BYTES - w)?;
44 writeln!(writer, " {sw} fp, {}(sp)", SAVED_PAIR_BYTES - 2 * w)?;
45 writeln!(writer, " addi fp, sp, {SAVED_PAIR_BYTES}")?;
46
47 let locals = Self::locals_bytes(stack_size, callee_saved_registers, target);
49 if locals > 0 {
50 Self::adjust_sp(writer, -(locals as i64))?;
51 }
52 for (index, register) in callee_saved_registers.iter().enumerate() {
53 let offset = Self::callee_saved_offset(stack_size, index, target);
54 Self::fp_store(writer, sw, register, offset, "t0")?;
55 }
56 Ok(())
57 }
58
59 pub fn generate_epilogue<W: Write>(
61 writer: &mut W,
62 stack_size: usize,
63 callee_saved_registers: &[&str],
64 target: RiscVTarget,
65 ) -> Result<(), Error> {
66 let lw = target.load_word();
67 for (index, register) in callee_saved_registers.iter().enumerate().rev() {
68 let offset = Self::callee_saved_offset(stack_size, index, target);
69 Self::fp_load(writer, lw, register, offset, register)?;
70 }
71
72 let locals = Self::locals_bytes(stack_size, callee_saved_registers, target);
74 if locals > 0 {
75 Self::adjust_sp(writer, locals as i64)?;
76 }
77
78 let w = target.word_bytes();
80 writeln!(writer, " {lw} ra, {}(fp)", -w)?;
81 writeln!(writer, " {lw} fp, {}(fp)", -2 * w)?;
82 writeln!(writer, " addi sp, sp, {SAVED_PAIR_BYTES}")?;
83 writeln!(writer, " ret")?;
84 Ok(())
85 }
86
87 pub fn generate_tail_epilogue<W: Write>(
89 writer: &mut W,
90 stack_size: usize,
91 callee_saved_registers: &[&str],
92 target_sym: &str,
93 target: RiscVTarget,
94 ) -> Result<(), Error> {
95 let lw = target.load_word();
96 for (index, register) in callee_saved_registers.iter().enumerate().rev() {
97 let offset = Self::callee_saved_offset(stack_size, index, target);
98 Self::fp_load(writer, lw, register, offset, register)?;
99 }
100 let locals = Self::locals_bytes(stack_size, callee_saved_registers, target);
101 if locals > 0 {
102 Self::adjust_sp(writer, locals as i64)?;
103 }
104 let w = target.word_bytes();
105 writeln!(writer, " {lw} ra, {}(fp)", -w)?;
106 writeln!(writer, " {lw} fp, {}(fp)", -2 * w)?;
107 writeln!(writer, " addi sp, sp, {SAVED_PAIR_BYTES}")?;
108 writeln!(writer, " j {target_sym}")?;
109 Ok(())
110 }
111
112 pub fn adjust_sp<W: Write>(writer: &mut W, delta: i64) -> Result<(), Error> {
115 if (IMM12_MIN..=IMM12_MAX).contains(&delta) {
116 writeln!(writer, " addi sp, sp, {delta}")
117 } else {
118 Self::load_imm(writer, "t0", delta)?;
119 writeln!(writer, " add sp, sp, t0")
120 }
121 }
122
123 pub fn fp_load<W: Write>(
127 writer: &mut W,
128 mnemonic: &str,
129 reg: &str,
130 offset: i32,
131 scratch: &str,
132 ) -> Result<(), Error> {
133 if (IMM12_MIN..=IMM12_MAX).contains(&i64::from(offset)) {
134 writeln!(writer, " {mnemonic} {reg}, {offset}(fp)")
135 } else {
136 Self::load_imm(writer, scratch, i64::from(offset))?;
137 writeln!(writer, " add {scratch}, fp, {scratch}")?;
138 writeln!(writer, " {mnemonic} {reg}, 0({scratch})")
139 }
140 }
141
142 pub fn fp_store<W: Write>(
145 writer: &mut W,
146 mnemonic: &str,
147 reg: &str,
148 offset: i32,
149 scratch: &str,
150 ) -> Result<(), Error> {
151 if (IMM12_MIN..=IMM12_MAX).contains(&i64::from(offset)) {
152 writeln!(writer, " {mnemonic} {reg}, {offset}(fp)")
153 } else if reg != scratch {
154 Self::fp_load(writer, mnemonic, reg, offset, scratch)
155 } else {
156 let walk = i64::from(offset) + 2048;
158 Self::walk_fp(writer, walk)?;
159 writeln!(writer, " {mnemonic} {reg}, -2048(fp)")?;
160 Self::walk_fp(writer, -walk)
161 }
162 }
163
164 pub fn fp_address<W: Write>(writer: &mut W, dst: &str, offset: i32) -> Result<(), Error> {
166 if (IMM12_MIN..=IMM12_MAX).contains(&i64::from(offset)) {
167 writeln!(writer, " addi {dst}, fp, {offset}")
168 } else {
169 Self::load_imm(writer, dst, i64::from(offset))?;
170 writeln!(writer, " add {dst}, fp, {dst}")
171 }
172 }
173
174 pub fn load_imm<W: Write>(writer: &mut W, reg: &str, value: i64) -> Result<(), Error> {
177 if (IMM12_MIN..=IMM12_MAX).contains(&value) {
178 return writeln!(writer, " li {reg}, {value}");
179 }
180 let low = ((value & 0xfff) ^ 0x800) - 0x800;
181 Self::load_imm(writer, reg, value.wrapping_sub(low) >> 12)?;
182 writeln!(writer, " slli {reg}, {reg}, 12")?;
183 if low != 0 {
184 writeln!(writer, " addi {reg}, {reg}, {low}")?;
185 }
186 Ok(())
187 }
188
189 fn walk_fp<W: Write>(writer: &mut W, mut delta: i64) -> Result<(), Error> {
190 while delta != 0 {
191 let step = delta.clamp(IMM12_MIN, IMM12_MAX);
192 writeln!(writer, " addi fp, fp, {step}")?;
193 delta -= step;
194 }
195 Ok(())
196 }
197
198 pub fn calculate_stack_offset(slot_index: usize, target: RiscVTarget) -> i32 {
205 -(SAVED_PAIR_BYTES + (slot_index as i32 + 1) * target.word_bytes())
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use crate::riscv::target::Xlen;
213 use std::error::Error;
214
215 #[test]
216 fn locals_never_land_on_the_saved_pair() {
217 let rv64 = RiscVTarget::general(Xlen::Rv64);
218 for slot in 0..8 {
219 let off = RiscVFrame::calculate_stack_offset(slot, rv64);
220 assert!(off <= -24, "slot {slot} at fp{off} overlaps the saved pair");
221 assert_eq!(off % 8, 0, "slot {slot} at fp{off} is not 8-byte aligned");
222 }
223 assert_eq!(RiscVFrame::calculate_stack_offset(0, rv64), -24);
224 assert_eq!(RiscVFrame::calculate_stack_offset(1, rv64), -32);
225 }
226
227 #[test]
228 fn locals_area_keeps_sp_16_byte_aligned() {
229 let rv64 = RiscVTarget::general(Xlen::Rv64);
230 assert_eq!(RiscVFrame::locals_bytes(0, &[], rv64), 0);
231 assert_eq!(RiscVFrame::locals_bytes(8, &[], rv64), 16);
232 assert_eq!(RiscVFrame::locals_bytes(16, &[], rv64), 16);
233 assert_eq!(RiscVFrame::locals_bytes(24, &[], rv64), 32);
234 assert_eq!(RiscVFrame::locals_bytes(16, &["s1"], rv64), 32);
235 }
236
237 #[test]
238 fn prologue_and_epilogue_move_sp_by_the_same_amount() {
239 let mut pro = Vec::new();
240 let mut epi = Vec::new();
241 let rv64 = RiscVTarget::general(Xlen::Rv64);
242 RiscVFrame::generate_prologue(&mut pro, 24, &[], rv64).expect("prologue");
243 RiscVFrame::generate_epilogue(&mut epi, 24, &[], rv64).expect("epilogue");
244 let pro = String::from_utf8(pro).expect("utf8");
245 let epi = String::from_utf8(epi).expect("utf8");
246 assert!(pro.contains("addi sp, sp, -32"), "prologue was:\n{pro}");
247 assert!(epi.contains("addi sp, sp, 32"), "epilogue was:\n{epi}");
248 }
249
250 #[test]
251 fn frame_beyond_the_immediate_range_is_adjusted_through_t0() -> Result<(), Box<dyn Error>> {
252 let rv64 = RiscVTarget::general(Xlen::Rv64);
253 let mut prologue = Vec::new();
254 RiscVFrame::generate_prologue(&mut prologue, 40000, &[], rv64)?;
255 let prologue = String::from_utf8(prologue)?;
256 assert!(prologue.contains(" add sp, sp, t0\n"), "{prologue}");
257 assert_eq!(eval_li_sequence(&prologue, "t0"), -40000, "{prologue}");
258 assert!(!prologue.contains("addi sp, sp, -40000"), "{prologue}");
259 Ok(())
260 }
261
262 fn eval_li_sequence(asm: &str, reg: &str) -> i64 {
264 let mut value = 0i64;
265 for line in asm.lines().map(str::trim) {
266 let (op, args) = line.split_once(' ').unwrap_or((line, ""));
267 let mut parts = args.split(", ");
268 if parts.next() != Some(reg) {
269 continue;
270 }
271 match op {
272 "li" => value = parts.next().and_then(|v| v.parse().ok()).unwrap_or(0),
273 "slli" => {
274 let shift: u32 = parts.nth(1).and_then(|v| v.parse().ok()).unwrap_or(0);
275 value = value.wrapping_shl(shift);
276 }
277 "addi" => {
278 let imm: i64 = parts.nth(1).and_then(|v| v.parse().ok()).unwrap_or(0);
279 value = value.wrapping_add(imm);
280 }
281 _ => {}
282 }
283 }
284 value
285 }
286
287 #[test]
288 fn load_imm_builds_every_tested_value() -> Result<(), Box<dyn Error>> {
289 for value in [
290 0,
291 1,
292 -1,
293 2047,
294 -2048,
295 2048,
296 -2049,
297 40016,
298 -40016,
299 1 << 31,
300 i32::MIN as i64,
301 i64::MAX,
302 i64::MIN,
303 ] {
304 let mut out = Vec::new();
305 RiscVFrame::load_imm(&mut out, "t0", value)?;
306 let asm = String::from_utf8(out)?;
307 assert_eq!(eval_li_sequence(&asm, "t0"), value, "{asm}");
308 }
309 Ok(())
310 }
311
312 #[test]
313 fn out_of_range_fp_access_builds_the_address_in_scratch() -> Result<(), Box<dyn Error>> {
314 let mut out = Vec::new();
315 RiscVFrame::fp_store(&mut out, "sd", "a0", -40016, "t0")?;
316 RiscVFrame::fp_load(&mut out, "ld", "a1", -40016, "a1")?;
317 let out = String::from_utf8(out)?;
318 assert_eq!(
319 out,
320 concat!(
321 " li t0, -10\n slli t0, t0, 12\n addi t0, t0, 944\n",
322 " add t0, fp, t0\n sd a0, 0(t0)\n",
323 " li a1, -10\n slli a1, a1, 12\n addi a1, a1, 944\n",
324 " add a1, fp, a1\n ld a1, 0(a1)\n",
325 )
326 );
327 Ok(())
328 }
329
330 #[test]
331 fn out_of_range_store_of_the_scratch_register_walks_fp_and_back() -> Result<(), Box<dyn Error>>
332 {
333 let mut out = Vec::new();
334 RiscVFrame::fp_store(&mut out, "sd", "t0", -40016, "t0")?;
335 let out = String::from_utf8(out)?;
336 let walked: i64 = out
337 .lines()
338 .filter_map(|line| line.trim().strip_prefix("addi fp, fp, "))
339 .map(|v| v.parse::<i64>())
340 .collect::<Result<Vec<_>, _>>()?
341 .iter()
342 .sum();
343 assert_eq!(walked, 0, "fp must return to its value: {out}");
344 assert!(out.contains("sd t0, -2048(fp)"), "{out}");
345 assert!(
346 out.lines()
347 .filter_map(|line| line.trim().strip_prefix("addi fp, fp, "))
348 .all(|v| (-2048..=2047).contains(&v.parse::<i64>().unwrap_or(i64::MAX))),
349 "{out}"
350 );
351 Ok(())
352 }
353
354 #[test]
355 fn callee_saved_registers_have_frame_slots_and_are_restored() -> Result<(), Box<dyn Error>> {
356 let mut prologue = Vec::new();
357 let mut epilogue = Vec::new();
358 let rv64 = RiscVTarget::general(Xlen::Rv64);
359 RiscVFrame::generate_prologue(&mut prologue, 16, &["s1"], rv64)?;
360 RiscVFrame::generate_epilogue(&mut epilogue, 16, &["s1"], rv64)?;
361 let prologue = String::from_utf8(prologue)?;
362 let epilogue = String::from_utf8(epilogue)?;
363 assert!(prologue.contains("addi sp, sp, -32"));
364 assert!(prologue.contains("sd s1, -40(fp)"));
365 assert!(epilogue.contains("ld s1, -40(fp)"));
366 assert!(epilogue.contains("addi sp, sp, 32"));
367 Ok(())
368 }
369}