1use std::io::{self, Write};
13
14pub const LINKAGE_SIZE: usize = 32;
17
18pub const PARAM_SAVE_SIZE: usize = 64;
22
23pub const LOCALS_BASE: usize = LINKAGE_SIZE + PARAM_SAVE_SIZE;
25
26const IMM16_MIN: i64 = -32768;
28const IMM16_MAX: i64 = 32767;
29
30pub struct Ppc64Frame;
31
32impl Ppc64Frame {
33 pub fn aligned_frame_size(local_bytes: usize) -> usize {
35 ((LOCALS_BASE + local_bytes) + 15) & !15
36 }
37
38 pub fn calculate_stack_offset(slot: usize) -> i32 {
45 (LOCALS_BASE + slot * 8) as i32
46 }
47
48 pub fn generate_prologue<W: Write>(writer: &mut W, local_bytes: usize) -> io::Result<()> {
54 let frame_size = Self::aligned_frame_size(local_bytes) as i64;
55 writeln!(writer, " mflr 0")?;
56 writeln!(writer, " std 0, 16(1)")?; if -frame_size >= IMM16_MIN {
58 writeln!(writer, " stdu 1, -{frame_size}(1)")?; } else {
60 load_imm(writer, "12", -frame_size)?;
62 writeln!(writer, " stdux 1, 1, 12")?;
63 }
64 Ok(())
65 }
66
67 pub fn generate_epilogue<W: Write>(writer: &mut W, local_bytes: usize) -> io::Result<()> {
69 let frame_size = Self::aligned_frame_size(local_bytes) as i64;
70 adjust_sp(writer, frame_size)?; writeln!(writer, " ld 0, 16(1)")?; writeln!(writer, " mtlr 0")?;
73 writeln!(writer, " blr")?;
74 Ok(())
75 }
76
77 pub fn generate_tail_epilogue<W: Write>(writer: &mut W, local_bytes: usize) -> io::Result<()> {
80 let frame_size = Self::aligned_frame_size(local_bytes) as i64;
81 adjust_sp(writer, frame_size)?;
82 writeln!(writer, " ld 0, 16(1)")?;
83 writeln!(writer, " mtlr 0")?;
84 Ok(())
85 }
86}
87
88pub fn load_imm<W: Write>(writer: &mut W, reg: &str, value: i64) -> io::Result<()> {
92 let out_of_range = || {
93 io::Error::new(
94 io::ErrorKind::InvalidInput,
95 format!("PowerPC64 immediate {value} does not fit a 32-bit load"),
96 )
97 };
98 if (IMM16_MIN..=IMM16_MAX).contains(&value) {
99 return writeln!(writer, " li {reg}, {value}");
100 }
101 if reg == "0" {
102 if !(0..(1 << 31)).contains(&value) {
103 return Err(out_of_range());
104 }
105 writeln!(writer, " lis 0, {}", value >> 16)?;
106 return writeln!(writer, " ori 0, 0, {}", value & 0xffff);
107 }
108 let low = i64::from((value & 0xffff) as u16 as i16);
109 let high = (value - low) >> 16;
110 if i32::try_from(value).is_err() || !(IMM16_MIN..=IMM16_MAX).contains(&high) {
111 return Err(out_of_range());
112 }
113 writeln!(writer, " li {reg}, {low}")?;
114 writeln!(writer, " addis {reg}, {reg}, {high}")
115}
116
117pub fn r1_mem<W: Write>(writer: &mut W, mnemonic: &str, reg: &str, offset: i32) -> io::Result<()> {
120 if (IMM16_MIN..=IMM16_MAX).contains(&i64::from(offset)) {
121 return writeln!(writer, " {mnemonic} {reg}, {offset}(1)");
122 }
123 let scratch = if reg == "0" { "12" } else { "0" };
124 load_imm(writer, scratch, i64::from(offset))?;
125 writeln!(writer, " {mnemonic}x {reg}, 1, {scratch}")
126}
127
128pub fn r1_address<W: Write>(writer: &mut W, dst: &str, offset: i32) -> io::Result<()> {
130 if (IMM16_MIN..=IMM16_MAX).contains(&i64::from(offset)) {
131 return writeln!(writer, " addi {dst}, 1, {offset}");
132 }
133 load_imm(writer, dst, i64::from(offset))?;
134 writeln!(writer, " add {dst}, 1, {dst}")
135}
136
137fn adjust_sp<W: Write>(writer: &mut W, delta: i64) -> io::Result<()> {
139 if (IMM16_MIN..=IMM16_MAX).contains(&delta) {
140 writeln!(writer, " addi 1, 1, {delta}")
141 } else {
142 load_imm(writer, "12", delta)?;
143 writeln!(writer, " add 1, 1, 12")
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_stack_offset_slot_0() {
153 assert_eq!(Ppc64Frame::calculate_stack_offset(0), LOCALS_BASE as i32);
154 }
155
156 #[test]
157 fn test_stack_offset_slot_1() {
158 assert_eq!(
159 Ppc64Frame::calculate_stack_offset(1),
160 (LOCALS_BASE + 8) as i32
161 );
162 }
163
164 #[test]
165 fn test_prologue_emits_stdu() {
166 let mut buf = Vec::new();
167 Ppc64Frame::generate_prologue(&mut buf, 0).unwrap();
168 let s = String::from_utf8(buf).unwrap();
169 assert!(s.contains("stdu"), "Expected stdu in prologue: {s}");
170 assert!(s.contains("mflr"), "Expected mflr in prologue: {s}");
171 }
172
173 #[test]
174 fn test_large_frame_is_allocated_with_stdux() {
175 let mut buf = Vec::new();
176 Ppc64Frame::generate_prologue(&mut buf, 40000).unwrap();
177 let s = String::from_utf8(buf).unwrap();
178 assert!(!s.contains("stdu 1"), "{s}");
179 assert!(s.contains("stdux 1, 1, 12"), "{s}");
180 assert_eq!(ppc_value_of(&s, "12"), -40096, "{s}");
181 }
182
183 #[test]
184 fn test_large_frame_is_released_through_r12() {
185 let mut buf = Vec::new();
186 Ppc64Frame::generate_epilogue(&mut buf, 40000).unwrap();
187 let s = String::from_utf8(buf).unwrap();
188 assert!(s.contains("add 1, 1, 12"), "{s}");
189 assert_eq!(ppc_value_of(&s, "12"), 40096, "{s}");
190 }
191
192 fn ppc_value_of(asm: &str, reg: &str) -> i64 {
194 let mut value = 0i64;
195 for line in asm.lines().map(str::trim) {
196 let (op, args) = line.split_once(' ').unwrap_or((line, ""));
197 let parts: Vec<&str> = args.split(", ").collect();
198 match (op, parts.as_slice()) {
199 ("li", [r, v]) if *r == reg => value = v.parse().unwrap(),
200 ("addis", [r, _, v]) if *r == reg => value += v.parse::<i64>().unwrap() << 16,
201 _ => {}
202 }
203 }
204 value
205 }
206
207 #[test]
208 fn test_load_imm_covers_the_32_bit_range() {
209 for value in [
210 0i64,
211 1,
212 -1,
213 32767,
214 -32768,
215 32768,
216 40096,
217 65536,
218 0x7FFF_0000,
219 -40096,
220 ] {
221 let mut buf = Vec::new();
222 load_imm(&mut buf, "12", value).unwrap();
223 let s = String::from_utf8(buf).unwrap();
224 assert_eq!(ppc_value_of(&s, "12"), value, "{s}");
225 }
226 }
227
228 #[test]
229 fn test_epilogue_emits_blr() {
230 let mut buf = Vec::new();
231 Ppc64Frame::generate_epilogue(&mut buf, 0).unwrap();
232 let s = String::from_utf8(buf).unwrap();
233 assert!(s.contains("blr"), "Expected blr in epilogue: {s}");
234 assert!(s.contains("mtlr"), "Expected mtlr in epilogue: {s}");
235 }
236
237 #[test]
238 fn test_tail_epilogue_has_no_blr() {
239 let mut buf = Vec::new();
240 Ppc64Frame::generate_tail_epilogue(&mut buf, 0).unwrap();
241 let s = String::from_utf8(buf).unwrap();
242 assert!(!s.contains("blr"), "tail epilogue must not return: {s}");
243 assert!(s.contains("mtlr"), "Expected mtlr: {s}");
244 }
245}