use alloc::vec::Vec;
use core::fmt;
use super::abi::Abi;
use crate::mir::{SlotKind, StackSlot};
use crate::regs::{PReg, RegSet};
const WORD: u64 = 8;
#[derive(Clone, Copy, Debug)]
pub struct FrameInput<'a> {
pub slots: &'a [SlotKind],
pub written: RegSet,
pub has_calls: bool,
pub outgoing: u32,
pub frame_pointer: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FrameError {
TooLarge,
Alignment {
slot: StackSlot,
align: u32,
},
OverlappingIncoming {
slot: StackSlot,
},
}
impl fmt::Display for FrameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FrameError::TooLarge => {
f.write_str("the stack frame exceeds the 2 GiB a 32-bit displacement reaches")
}
FrameError::Alignment { slot, align } => write!(
f,
"stack slot ss{} asks for alignment {align}; slots take a power of two up to the stack alignment",
slot.index()
),
FrameError::OverlappingIncoming { slot } => write!(
f,
"incoming stack slot ss{} overlaps another incoming slot",
slot.index()
),
}
}
}
impl core::error::Error for FrameError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FrameStep {
Push(PReg),
SetFramePointer,
Alloc {
bytes: u32,
probe: u32,
},
SaveVec {
reg: PReg,
offset: i32,
},
RestoreVec {
reg: PReg,
offset: i32,
},
Free(u32),
Pop(PReg),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FrameLayout {
offsets: Vec<i32>,
pushed: Vec<PReg>,
vec_saves: Vec<(PReg, i32)>,
prologue: Vec<FrameStep>,
epilogue: Vec<FrameStep>,
alloc: u32,
cfa_offset: u32,
frame_pointer: bool,
red_zone: bool,
}
impl FrameLayout {
#[inline]
#[must_use]
pub fn slot_offset(&self, slot: StackSlot) -> Option<i32> {
self.offsets.get(slot.index()).copied()
}
#[inline]
#[must_use]
pub fn pushed(&self) -> &[PReg] {
&self.pushed
}
#[inline]
#[must_use]
pub fn vec_saves(&self) -> &[(PReg, i32)] {
&self.vec_saves
}
#[inline]
#[must_use]
pub fn prologue(&self) -> &[FrameStep] {
&self.prologue
}
#[inline]
#[must_use]
pub fn epilogue(&self) -> &[FrameStep] {
&self.epilogue
}
#[inline]
#[must_use]
pub const fn alloc(&self) -> u32 {
self.alloc
}
#[inline]
#[must_use]
pub const fn cfa_offset(&self) -> u32 {
self.cfa_offset
}
#[inline]
#[must_use]
pub const fn frame_pointer(&self) -> bool {
self.frame_pointer
}
#[inline]
#[must_use]
pub const fn uses_red_zone(&self) -> bool {
self.red_zone
}
}
fn round_to_residue(at_least: u64, residue: u64, align: u64) -> u64 {
let rem = at_least % align;
let add = (residue + align - rem) % align;
at_least + add
}
fn fits_i32(v: u64) -> Result<i32, FrameError> {
i32::try_from(v).map_err(|_| FrameError::TooLarge)
}
impl Abi {
pub fn layout_frame(&self, input: &FrameInput<'_>) -> Result<FrameLayout, FrameError> {
let target = self.target();
let align = u64::from(self.stack().align()).max(1);
let sp = target.stack_pointer();
let fp = target.frame_pointer();
let mut saved = input.written.intersect(self.callee_saved()).without(sp);
if input.frame_pointer {
saved = saved.without(fp);
}
let push_class = target.addr_class();
let mut pushed = Vec::new();
let mut vec_regs = Vec::new();
for reg in saved.iter() {
if target.class_of(reg) == Some(push_class) {
pushed.push(reg);
} else {
vec_regs.push(reg);
}
}
let pushes = pushed.len() as u64 + u64::from(input.frame_pointer);
let above = WORD + WORD * pushes;
let mut locals: Vec<(u32, u64, usize)> = Vec::new(); for (i, kind) in input.slots.iter().enumerate() {
if let SlotKind::Local { size, align: a } = *kind {
if a == 0 || !a.is_power_of_two() || u64::from(a) > align {
return Err(FrameError::Alignment {
slot: StackSlot::from_index(i),
align: a,
});
}
locals.push((a, u64::from(size), i));
}
}
for (k, ®) in vec_regs.iter().enumerate() {
let class = target.class(target.class_of(reg).unwrap_or(push_class));
let a = class.spill_align().clamp(1, 16);
locals.push((a, u64::from(class.spill_size()), input.slots.len() + k));
}
locals.sort_unstable_by(|x, y| y.0.cmp(&x.0).then(x.2.cmp(&y.2)));
let mut local_off = Vec::with_capacity(locals.len());
let mut cursor = 0u64;
for &(a, size, id) in &locals {
let a = u64::from(a);
cursor = cursor.div_ceil(a) * a;
local_off.push((id, cursor));
cursor += size;
}
let locals_size = cursor;
let mut outgoing = u64::from(input.outgoing);
if input.has_calls {
outgoing = outgoing.max(u64::from(self.stack().shadow_space()));
}
let outgoing = outgoing.div_ceil(align) * align;
let residue = (align - above % align) % align;
let red_zone_bytes = round_to_residue(locals_size, residue, align);
let (alloc, base, red_zone) = if !input.has_calls
&& outgoing == 0
&& locals_size > 0
&& red_zone_bytes <= u64::from(self.stack().red_zone())
{
(0u64, -(red_zone_bytes as i64), true)
} else if !input.has_calls && outgoing == 0 && locals_size == 0 {
(0, 0, false)
} else {
(
round_to_residue(outgoing + locals_size, residue, align),
outgoing as i64,
false,
)
};
if alloc > i32::MAX as u64 {
return Err(FrameError::TooLarge);
}
let cfa = alloc + above;
let cfa_i = fits_i32(cfa)?;
let mut offsets = alloc::vec![0i32; input.slots.len()];
let mut vec_saves = Vec::with_capacity(vec_regs.len());
for &(id, off) in &local_off {
let at = base + off as i64;
let at = i32::try_from(at).map_err(|_| FrameError::TooLarge)?;
match offsets.get_mut(id) {
Some(o) => *o = at,
None => {
if let Some(®) = vec_regs.get(id - input.slots.len()) {
vec_saves.push((reg, at));
}
}
}
}
let mut incoming: Vec<(u64, u64, usize)> = Vec::new();
for (i, kind) in input.slots.iter().enumerate() {
if let SlotKind::Incoming { offset, size } = *kind {
let at = cfa + u64::from(offset);
offsets[i] = fits_i32(at)?;
let _end = fits_i32(at + u64::from(size))?;
incoming.push((u64::from(offset), u64::from(size.max(1)), i));
}
}
incoming.sort_unstable();
for pair in incoming.windows(2) {
let (a, b) = (pair[0], pair[1]);
if a.0 + a.1 > b.0 {
return Err(FrameError::OverlappingIncoming {
slot: StackSlot::from_index(b.2),
});
}
}
let alloc = alloc as u32;
let probe = self.stack().probe_interval();
let probe = if probe > 0 && alloc >= probe {
probe
} else {
0
};
let mut prologue = Vec::with_capacity(pushed.len() + vec_saves.len() + 3);
let mut epilogue = Vec::with_capacity(pushed.len() + vec_saves.len() + 2);
if input.frame_pointer {
prologue.push(FrameStep::Push(fp));
prologue.push(FrameStep::SetFramePointer);
}
prologue.extend(pushed.iter().map(|&r| FrameStep::Push(r)));
if alloc > 0 {
prologue.push(FrameStep::Alloc {
bytes: alloc,
probe,
});
}
prologue.extend(
vec_saves
.iter()
.map(|&(reg, offset)| FrameStep::SaveVec { reg, offset }),
);
epilogue.extend(
vec_saves
.iter()
.map(|&(reg, offset)| FrameStep::RestoreVec { reg, offset }),
);
if alloc > 0 {
epilogue.push(FrameStep::Free(alloc));
}
epilogue.extend(pushed.iter().rev().map(|&r| FrameStep::Pop(r)));
if input.frame_pointer {
epilogue.push(FrameStep::Pop(fp));
}
Ok(FrameLayout {
offsets,
pushed,
vec_saves,
prologue,
epilogue,
alloc,
cfa_offset: cfa_i as u32,
frame_pointer: input.frame_pointer,
red_zone,
})
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use alloc::vec;
use super::*;
use crate::x86_64::*;
fn input(slots: &[SlotKind], written: RegSet, calls: bool, fp: bool) -> FrameInput<'_> {
FrameInput {
slots,
written,
has_calls: calls,
outgoing: 0,
frame_pointer: fp,
}
}
fn check(abi: &Abi, frame: &FrameLayout, slots: &[SlotKind], calls: bool) {
let cfa: i64 = 0x10000;
let mut sp = cfa - 8; for step in frame.prologue() {
match *step {
FrameStep::Push(_) => sp -= 8,
FrameStep::Alloc { bytes, .. } => sp -= i64::from(bytes),
_ => {}
}
}
assert_eq!(cfa - sp, i64::from(frame.cfa_offset()));
if calls || frame.alloc() > 0 {
assert_eq!(sp % 16, 0, "stack pointer aligned after the prologue");
}
let mut spans = vec![];
for (i, s) in slots.iter().enumerate() {
let off = i64::from(
frame
.slot_offset(StackSlot::from_index(i))
.unwrap_or(i32::MIN),
);
let addr = sp + off;
match *s {
SlotKind::Local { size, align } => {
assert_eq!(addr % i64::from(align), 0, "slot {i} aligned");
let lo = if frame.uses_red_zone() {
sp - i64::from(abi.stack().red_zone())
} else {
sp
};
assert!(addr >= lo);
let pushes_bottom = cfa
- 8
- 8 * (frame.pushed().len() as i64 + i64::from(frame.frame_pointer()));
assert!(addr + i64::from(size) <= pushes_bottom);
spans.push((addr, addr + i64::from(size)));
}
SlotKind::Incoming { offset, .. } => assert_eq!(addr, cfa + i64::from(offset)),
}
}
spans.sort_unstable();
for w in spans.windows(2) {
assert!(w[0].1 <= w[1].0, "locals overlap");
}
let mut back = sp;
for step in frame.epilogue() {
match *step {
FrameStep::Pop(_) => back += 8,
FrameStep::Free(b) => back += i64::from(b),
_ => {}
}
}
assert_eq!(back, cfa - 8);
}
#[test]
fn test_layout_invariants_over_mixed_shapes() {
let shapes: &[&[SlotKind]] = &[
&[],
&[SlotKind::Local { size: 1, align: 1 }],
&[
SlotKind::Local { size: 3, align: 1 },
SlotKind::Local {
size: 16,
align: 16,
},
SlotKind::Local { size: 8, align: 8 },
SlotKind::Incoming { offset: 0, size: 8 },
SlotKind::Local { size: 2, align: 2 },
],
&[SlotKind::Local {
size: 200,
align: 8,
}],
&[SlotKind::Local {
size: 9000,
align: 16,
}],
];
let writes = [
RegSet::EMPTY,
RegSet::of(&[RBX]),
RegSet::of(&[RBX, R12, R15]),
RegSet::of(&[RBX, RSI, RDI, XMM6, XMM9]),
];
for abi in [&SYSV, &WIN64] {
for slots in shapes {
for &written in &writes {
for calls in [false, true] {
for fp in [false, true] {
let frame = abi.layout_frame(&input(slots, written, calls, fp));
let frame = frame.unwrap_or_else(|e| panic!("{e}"));
check(abi, &frame, slots, calls);
}
}
}
}
}
}
#[test]
fn test_red_zone_only_for_sysv_leaves_that_fit() {
let small = [SlotKind::Local {
size: 120,
align: 8,
}];
let f = SYSV
.layout_frame(&input(&small, RegSet::EMPTY, false, true))
.unwrap_or_else(|e| panic!("{e}"));
assert!(f.uses_red_zone());
assert_eq!(f.alloc(), 0);
let f = SYSV
.layout_frame(&input(&small, RegSet::EMPTY, true, true))
.unwrap_or_else(|e| panic!("{e}"));
assert!(!f.uses_red_zone());
let f = WIN64
.layout_frame(&input(&small, RegSet::EMPTY, false, true))
.unwrap_or_else(|e| panic!("{e}"));
assert!(!f.uses_red_zone());
let big = [SlotKind::Local {
size: 129,
align: 8,
}];
let f = SYSV
.layout_frame(&input(&big, RegSet::EMPTY, false, true))
.unwrap_or_else(|e| panic!("{e}"));
assert!(!f.uses_red_zone());
}
#[test]
fn test_win64_shadow_space_and_vector_saves() {
let f = WIN64
.layout_frame(&input(&[], RegSet::of(&[XMM6, RSI]), true, false))
.unwrap_or_else(|e| panic!("{e}"));
assert_eq!(f.pushed(), [RSI]);
assert_eq!(f.vec_saves(), [(XMM6, 32)]);
assert!(f.alloc() >= 48);
assert!(f.prologue().contains(&FrameStep::SaveVec {
reg: XMM6,
offset: 32
}));
let f = SYSV
.layout_frame(&input(&[], RegSet::of(&[XMM6]), true, false))
.unwrap_or_else(|e| panic!("{e}"));
assert!(f.vec_saves().is_empty());
}
#[test]
fn test_probe_threshold() {
let page = [SlotKind::Local {
size: 4096,
align: 8,
}];
for abi in [&SYSV, &WIN64] {
let f = abi
.layout_frame(&input(&page, RegSet::EMPTY, true, true))
.unwrap_or_else(|e| panic!("{e}"));
assert!(matches!(
f.prologue().last(),
Some(FrameStep::Alloc { probe: 4096, .. })
));
}
let small = [SlotKind::Local {
size: 4000,
align: 8,
}];
let f = WIN64
.layout_frame(&input(&small, RegSet::EMPTY, true, true))
.unwrap_or_else(|e| panic!("{e}"));
assert!(matches!(
f.prologue().last(),
Some(FrameStep::Alloc { probe: 0, .. })
));
}
#[test]
fn test_errors() {
let bad = [SlotKind::Local { size: 8, align: 3 }];
assert_eq!(
SYSV.layout_frame(&input(&bad, RegSet::EMPTY, false, true)),
Err(FrameError::Alignment {
slot: StackSlot::from_index(0),
align: 3
})
);
let zero = [SlotKind::Local { size: 8, align: 0 }];
assert!(
SYSV.layout_frame(&input(&zero, RegSet::EMPTY, false, true))
.is_err()
);
let huge = [SlotKind::Local {
size: u32::MAX,
align: 8,
}];
assert_eq!(
SYSV.layout_frame(&input(&huge, RegSet::EMPTY, false, true)),
Err(FrameError::TooLarge)
);
let many = vec![
SlotKind::Local {
size: 1 << 30,
align: 8
};
3
];
assert_eq!(
SYSV.layout_frame(&input(&many, RegSet::EMPTY, false, true)),
Err(FrameError::TooLarge)
);
let far = [SlotKind::Incoming {
offset: u32::MAX - 4,
size: 8,
}];
assert_eq!(
SYSV.layout_frame(&input(&far, RegSet::EMPTY, false, true)),
Err(FrameError::TooLarge)
);
let overlap = [
SlotKind::Incoming { offset: 0, size: 8 },
SlotKind::Incoming { offset: 4, size: 8 },
];
assert_eq!(
SYSV.layout_frame(&input(&overlap, RegSet::EMPTY, false, true)),
Err(FrameError::OverlappingIncoming {
slot: StackSlot::from_index(1)
})
);
assert!(FrameError::TooLarge.to_string().contains("2 GiB"));
}
}