use std::io::{self, Write};
pub const LINKAGE_SIZE: usize = 32;
pub const PARAM_SAVE_SIZE: usize = 64;
pub const LOCALS_BASE: usize = LINKAGE_SIZE + PARAM_SAVE_SIZE;
const IMM16_MIN: i64 = -32768;
const IMM16_MAX: i64 = 32767;
pub struct Ppc64Frame;
impl Ppc64Frame {
pub fn aligned_frame_size(local_bytes: usize) -> usize {
((LOCALS_BASE + local_bytes) + 15) & !15
}
pub fn calculate_stack_offset(slot: usize) -> i32 {
(LOCALS_BASE + slot * 8) as i32
}
pub fn generate_prologue<W: Write>(writer: &mut W, local_bytes: usize) -> io::Result<()> {
let frame_size = Self::aligned_frame_size(local_bytes) as i64;
writeln!(writer, " mflr 0")?;
writeln!(writer, " std 0, 16(1)")?; if -frame_size >= IMM16_MIN {
writeln!(writer, " stdu 1, -{frame_size}(1)")?; } else {
load_imm(writer, "12", -frame_size)?;
writeln!(writer, " stdux 1, 1, 12")?;
}
Ok(())
}
pub fn generate_epilogue<W: Write>(writer: &mut W, local_bytes: usize) -> io::Result<()> {
let frame_size = Self::aligned_frame_size(local_bytes) as i64;
adjust_sp(writer, frame_size)?; writeln!(writer, " ld 0, 16(1)")?; writeln!(writer, " mtlr 0")?;
writeln!(writer, " blr")?;
Ok(())
}
pub fn generate_tail_epilogue<W: Write>(writer: &mut W, local_bytes: usize) -> io::Result<()> {
let frame_size = Self::aligned_frame_size(local_bytes) as i64;
adjust_sp(writer, frame_size)?;
writeln!(writer, " ld 0, 16(1)")?;
writeln!(writer, " mtlr 0")?;
Ok(())
}
}
pub fn load_imm<W: Write>(writer: &mut W, reg: &str, value: i64) -> io::Result<()> {
let out_of_range = || {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("PowerPC64 immediate {value} does not fit a 32-bit load"),
)
};
if (IMM16_MIN..=IMM16_MAX).contains(&value) {
return writeln!(writer, " li {reg}, {value}");
}
if reg == "0" {
if !(0..(1 << 31)).contains(&value) {
return Err(out_of_range());
}
writeln!(writer, " lis 0, {}", value >> 16)?;
return writeln!(writer, " ori 0, 0, {}", value & 0xffff);
}
let low = i64::from((value & 0xffff) as u16 as i16);
let high = (value - low) >> 16;
if i32::try_from(value).is_err() || !(IMM16_MIN..=IMM16_MAX).contains(&high) {
return Err(out_of_range());
}
writeln!(writer, " li {reg}, {low}")?;
writeln!(writer, " addis {reg}, {reg}, {high}")
}
pub fn r1_mem<W: Write>(writer: &mut W, mnemonic: &str, reg: &str, offset: i32) -> io::Result<()> {
if (IMM16_MIN..=IMM16_MAX).contains(&i64::from(offset)) {
return writeln!(writer, " {mnemonic} {reg}, {offset}(1)");
}
let scratch = if reg == "0" { "12" } else { "0" };
load_imm(writer, scratch, i64::from(offset))?;
writeln!(writer, " {mnemonic}x {reg}, 1, {scratch}")
}
pub fn r1_address<W: Write>(writer: &mut W, dst: &str, offset: i32) -> io::Result<()> {
if (IMM16_MIN..=IMM16_MAX).contains(&i64::from(offset)) {
return writeln!(writer, " addi {dst}, 1, {offset}");
}
load_imm(writer, dst, i64::from(offset))?;
writeln!(writer, " add {dst}, 1, {dst}")
}
fn adjust_sp<W: Write>(writer: &mut W, delta: i64) -> io::Result<()> {
if (IMM16_MIN..=IMM16_MAX).contains(&delta) {
writeln!(writer, " addi 1, 1, {delta}")
} else {
load_imm(writer, "12", delta)?;
writeln!(writer, " add 1, 1, 12")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stack_offset_slot_0() {
assert_eq!(Ppc64Frame::calculate_stack_offset(0), LOCALS_BASE as i32);
}
#[test]
fn test_stack_offset_slot_1() {
assert_eq!(
Ppc64Frame::calculate_stack_offset(1),
(LOCALS_BASE + 8) as i32
);
}
#[test]
fn test_prologue_emits_stdu() {
let mut buf = Vec::new();
Ppc64Frame::generate_prologue(&mut buf, 0).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("stdu"), "Expected stdu in prologue: {s}");
assert!(s.contains("mflr"), "Expected mflr in prologue: {s}");
}
#[test]
fn test_large_frame_is_allocated_with_stdux() {
let mut buf = Vec::new();
Ppc64Frame::generate_prologue(&mut buf, 40000).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(!s.contains("stdu 1"), "{s}");
assert!(s.contains("stdux 1, 1, 12"), "{s}");
assert_eq!(ppc_value_of(&s, "12"), -40096, "{s}");
}
#[test]
fn test_large_frame_is_released_through_r12() {
let mut buf = Vec::new();
Ppc64Frame::generate_epilogue(&mut buf, 40000).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("add 1, 1, 12"), "{s}");
assert_eq!(ppc_value_of(&s, "12"), 40096, "{s}");
}
fn ppc_value_of(asm: &str, reg: &str) -> i64 {
let mut value = 0i64;
for line in asm.lines().map(str::trim) {
let (op, args) = line.split_once(' ').unwrap_or((line, ""));
let parts: Vec<&str> = args.split(", ").collect();
match (op, parts.as_slice()) {
("li", [r, v]) if *r == reg => value = v.parse().unwrap(),
("addis", [r, _, v]) if *r == reg => value += v.parse::<i64>().unwrap() << 16,
_ => {}
}
}
value
}
#[test]
fn test_load_imm_covers_the_32_bit_range() {
for value in [
0i64,
1,
-1,
32767,
-32768,
32768,
40096,
65536,
0x7FFF_0000,
-40096,
] {
let mut buf = Vec::new();
load_imm(&mut buf, "12", value).unwrap();
let s = String::from_utf8(buf).unwrap();
assert_eq!(ppc_value_of(&s, "12"), value, "{s}");
}
}
#[test]
fn test_epilogue_emits_blr() {
let mut buf = Vec::new();
Ppc64Frame::generate_epilogue(&mut buf, 0).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(s.contains("blr"), "Expected blr in epilogue: {s}");
assert!(s.contains("mtlr"), "Expected mtlr in epilogue: {s}");
}
#[test]
fn test_tail_epilogue_has_no_blr() {
let mut buf = Vec::new();
Ppc64Frame::generate_tail_epilogue(&mut buf, 0).unwrap();
let s = String::from_utf8(buf).unwrap();
assert!(!s.contains("blr"), "tail epilogue must not return: {s}");
assert!(s.contains("mtlr"), "Expected mtlr: {s}");
}
}