Remove all unused stackslot handling code

This commit is contained in:
bjorn3
2021-10-10 14:55:41 +02:00
parent ed31678161
commit fd59a3e045

View File

@@ -4,10 +4,7 @@
//! //!
use crate::entity::{Iter, IterMut, Keys, PrimaryMap}; use crate::entity::{Iter, IterMut, Keys, PrimaryMap};
use crate::ir::{StackSlot, Type}; use crate::ir::StackSlot;
use crate::packed_option::PackedOption;
use alloc::vec::Vec;
use core::cmp;
use core::fmt; use core::fmt;
use core::ops::{Index, IndexMut}; use core::ops::{Index, IndexMut};
use core::slice; use core::slice;
@@ -23,61 +20,13 @@ use serde::{Deserialize, Serialize};
/// platform. /// platform.
pub type StackSize = u32; pub type StackSize = u32;
/// A stack offset.
///
/// The location of a stack offset relative to a stack pointer or frame pointer.
pub type StackOffset = i32;
/// The minimum size of a spill slot in bytes.
///
/// ISA implementations are allowed to assume that small types like `b1` and `i8` get a full 4-byte
/// spill slot.
const MIN_SPILL_SLOT_SIZE: StackSize = 4;
/// Get the spill slot size to use for `ty`.
fn spill_size(ty: Type) -> StackSize {
cmp::max(MIN_SPILL_SLOT_SIZE, ty.bytes())
}
/// The kind of a stack slot. /// The kind of a stack slot.
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub enum StackSlotKind { pub enum StackSlotKind {
/// A spill slot. This is a stack slot created by the register allocator.
SpillSlot,
/// An explicit stack slot. This is a chunk of stack memory for use by the `stack_load` /// An explicit stack slot. This is a chunk of stack memory for use by the `stack_load`
/// and `stack_store` instructions. /// and `stack_store` instructions.
ExplicitSlot, ExplicitSlot,
/// An incoming function argument.
///
/// If the current function has more arguments than fits in registers, the remaining arguments
/// are passed on the stack by the caller. These incoming arguments are represented as SSA
/// values assigned to incoming stack slots.
IncomingArg,
/// An outgoing function argument.
///
/// When preparing to call a function whose arguments don't fit in registers, outgoing argument
/// stack slots are used to represent individual arguments in the outgoing call frame. These
/// stack slots are only valid while setting up a call.
OutgoingArg,
/// Space allocated in the caller's frame for the callee's return values
/// that are passed out via return pointer.
///
/// If there are more return values than registers available for the callee's calling
/// convention, or the return value is larger than the available registers' space, then we
/// allocate stack space in this frame and pass a pointer to the callee, which then writes its
/// return values into this space.
StructReturnSlot,
/// An emergency spill slot.
///
/// Emergency slots are allocated late when the register's constraint solver needs extra space
/// to shuffle registers around. They are only used briefly, and can be reused.
EmergencySlot,
} }
impl FromStr for StackSlotKind { impl FromStr for StackSlotKind {
@@ -87,11 +36,6 @@ impl FromStr for StackSlotKind {
use self::StackSlotKind::*; use self::StackSlotKind::*;
match s { match s {
"explicit_slot" => Ok(ExplicitSlot), "explicit_slot" => Ok(ExplicitSlot),
"spill_slot" => Ok(SpillSlot),
"incoming_arg" => Ok(IncomingArg),
"outgoing_arg" => Ok(OutgoingArg),
"sret_slot" => Ok(StructReturnSlot),
"emergency_slot" => Ok(EmergencySlot),
_ => Err(()), _ => Err(()),
} }
} }
@@ -102,11 +46,6 @@ impl fmt::Display for StackSlotKind {
use self::StackSlotKind::*; use self::StackSlotKind::*;
f.write_str(match *self { f.write_str(match *self {
ExplicitSlot => "explicit_slot", ExplicitSlot => "explicit_slot",
SpillSlot => "spill_slot",
IncomingArg => "incoming_arg",
OutgoingArg => "outgoing_arg",
StructReturnSlot => "sret_slot",
EmergencySlot => "emergency_slot",
}) })
} }
} }
@@ -120,25 +59,12 @@ pub struct StackSlotData {
/// Size of stack slot in bytes. /// Size of stack slot in bytes.
pub size: StackSize, pub size: StackSize,
/// Offset of stack slot relative to the stack pointer in the caller.
///
/// On x86, the base address is the stack pointer *before* the return address was pushed. On
/// RISC ISAs, the base address is the value of the stack pointer on entry to the function.
///
/// For `OutgoingArg` stack slots, the offset is relative to the current function's stack
/// pointer immediately before the call.
pub offset: Option<StackOffset>,
} }
impl StackSlotData { impl StackSlotData {
/// Create a stack slot with the specified byte size. /// Create a stack slot with the specified byte size.
pub fn new(kind: StackSlotKind, size: StackSize) -> Self { pub fn new(kind: StackSlotKind, size: StackSize) -> Self {
Self { Self { kind, size }
kind,
size,
offset: None,
}
} }
/// Get the alignment in bytes of this stack slot given the stack pointer alignment. /// Get the alignment in bytes of this stack slot given the stack pointer alignment.
@@ -154,11 +80,7 @@ impl StackSlotData {
impl fmt::Display for StackSlotData { impl fmt::Display for StackSlotData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.kind, self.size)?; write!(f, "{} {}", self.kind, self.size)
if let Some(offset) = self.offset {
write!(f, ", offset {}", offset)?;
}
Ok(())
} }
} }
@@ -170,12 +92,6 @@ impl fmt::Display for StackSlotData {
pub struct StackSlots { pub struct StackSlots {
/// All allocated stack slots. /// All allocated stack slots.
slots: PrimaryMap<StackSlot, StackSlotData>, slots: PrimaryMap<StackSlot, StackSlotData>,
/// All the outgoing stack slots, ordered by offset.
outgoing: Vec<StackSlot>,
/// All the emergency slots.
emergency: Vec<StackSlot>,
} }
/// Stack slot manager functions that behave mostly like an entity map. /// Stack slot manager functions that behave mostly like an entity map.
@@ -188,8 +104,6 @@ impl StackSlots {
/// Clear out everything. /// Clear out everything.
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.slots.clear(); self.slots.clear();
self.outgoing.clear();
self.emergency.clear();
} }
/// Allocate a new stack slot. /// Allocate a new stack slot.
@@ -252,90 +166,9 @@ impl IndexMut<StackSlot> for StackSlots {
} }
} }
/// Higher-level stack frame manipulation functions.
impl StackSlots {
/// Create a new spill slot for spilling values of type `ty`.
pub fn make_spill_slot(&mut self, ty: Type) -> StackSlot {
self.push(StackSlotData::new(StackSlotKind::SpillSlot, spill_size(ty)))
}
/// Create a stack slot representing an incoming function argument.
pub fn make_incoming_arg(&mut self, size: u32, offset: StackOffset) -> StackSlot {
let mut data = StackSlotData::new(StackSlotKind::IncomingArg, size);
debug_assert!(offset <= StackOffset::max_value() - data.size as StackOffset);
data.offset = Some(offset);
self.push(data)
}
/// Get a stack slot representing an outgoing argument.
///
/// This may create a new stack slot, or reuse an existing outgoing stack slot with the
/// requested offset and size.
///
/// The requested offset is relative to this function's stack pointer immediately before making
/// the call.
pub fn get_outgoing_arg(&mut self, size: u32, offset: StackOffset) -> StackSlot {
// Look for an existing outgoing stack slot with the same offset and size.
let inspos = match self.outgoing.binary_search_by_key(&(offset, size), |&ss| {
(self[ss].offset.unwrap(), self[ss].size)
}) {
Ok(idx) => return self.outgoing[idx],
Err(idx) => idx,
};
// No existing slot found. Make one and insert it into `outgoing`.
let mut data = StackSlotData::new(StackSlotKind::OutgoingArg, size);
debug_assert!(offset <= StackOffset::max_value() - size as StackOffset);
data.offset = Some(offset);
let ss = self.slots.push(data);
self.outgoing.insert(inspos, ss);
ss
}
/// Get an emergency spill slot that can be used to store a `ty` value.
///
/// This may allocate a new slot, or it may reuse an existing emergency spill slot, excluding
/// any slots in the `in_use` list.
pub fn get_emergency_slot(
&mut self,
ty: Type,
in_use: &[PackedOption<StackSlot>],
) -> StackSlot {
let size = spill_size(ty);
// Find the smallest existing slot that can fit the type.
if let Some(&ss) = self
.emergency
.iter()
.filter(|&&ss| self[ss].size >= size && !in_use.contains(&ss.into()))
.min_by_key(|&&ss| self[ss].size)
{
return ss;
}
// Alternatively, use the largest available slot and make it larger.
if let Some(&ss) = self
.emergency
.iter()
.filter(|&&ss| !in_use.contains(&ss.into()))
.max_by_key(|&&ss| self[ss].size)
{
self.slots[ss].size = size;
return ss;
}
// No existing slot found. Make one and insert it into `emergency`.
let data = StackSlotData::new(StackSlotKind::EmergencySlot, size);
let ss = self.slots.push(data);
self.emergency.push(ss);
ss
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::ir::types;
use crate::ir::Function; use crate::ir::Function;
use alloc::string::ToString; use alloc::string::ToString;
@@ -343,43 +176,21 @@ mod tests {
fn stack_slot() { fn stack_slot() {
let mut func = Function::new(); let mut func = Function::new();
let ss0 = func.create_stack_slot(StackSlotData::new(StackSlotKind::IncomingArg, 4)); let ss0 = func.create_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 4));
let ss1 = func.create_stack_slot(StackSlotData::new(StackSlotKind::SpillSlot, 8)); let ss1 = func.create_stack_slot(StackSlotData::new(StackSlotKind::ExplicitSlot, 8));
assert_eq!(ss0.to_string(), "ss0"); assert_eq!(ss0.to_string(), "ss0");
assert_eq!(ss1.to_string(), "ss1"); assert_eq!(ss1.to_string(), "ss1");
assert_eq!(func.stack_slots[ss0].size, 4); assert_eq!(func.stack_slots[ss0].size, 4);
assert_eq!(func.stack_slots[ss1].size, 8); assert_eq!(func.stack_slots[ss1].size, 8);
assert_eq!(func.stack_slots[ss0].to_string(), "incoming_arg 4"); assert_eq!(func.stack_slots[ss0].to_string(), "explicit_slot 4");
assert_eq!(func.stack_slots[ss1].to_string(), "spill_slot 8"); assert_eq!(func.stack_slots[ss1].to_string(), "explicit_slot 8");
}
#[test]
fn outgoing() {
let mut sss = StackSlots::new();
let ss0 = sss.get_outgoing_arg(4, 8);
let ss1 = sss.get_outgoing_arg(4, 4);
let ss2 = sss.get_outgoing_arg(8, 8);
assert_eq!(sss[ss0].offset, Some(8));
assert_eq!(sss[ss0].size, 4);
assert_eq!(sss[ss1].offset, Some(4));
assert_eq!(sss[ss1].size, 4);
assert_eq!(sss[ss2].offset, Some(8));
assert_eq!(sss[ss2].size, 8);
assert_eq!(sss.get_outgoing_arg(4, 8), ss0);
assert_eq!(sss.get_outgoing_arg(4, 4), ss1);
assert_eq!(sss.get_outgoing_arg(8, 8), ss2);
} }
#[test] #[test]
fn alignment() { fn alignment() {
let slot = StackSlotData::new(StackSlotKind::SpillSlot, 8); let slot = StackSlotData::new(StackSlotKind::ExplicitSlot, 8);
assert_eq!(slot.alignment(4), 4); assert_eq!(slot.alignment(4), 4);
assert_eq!(slot.alignment(8), 8); assert_eq!(slot.alignment(8), 8);
@@ -392,31 +203,4 @@ mod tests {
assert_eq!(slot2.alignment(16), 8); assert_eq!(slot2.alignment(16), 8);
assert_eq!(slot2.alignment(32), 8); assert_eq!(slot2.alignment(32), 8);
} }
#[test]
fn emergency() {
let mut sss = StackSlots::new();
let ss0 = sss.get_emergency_slot(types::I32, &[]);
assert_eq!(sss[ss0].size, 4);
// When a smaller size is requested, we should simply get the same slot back.
assert_eq!(sss.get_emergency_slot(types::I8, &[]), ss0);
assert_eq!(sss[ss0].size, 4);
assert_eq!(sss.get_emergency_slot(types::F32, &[]), ss0);
assert_eq!(sss[ss0].size, 4);
// Ask for a larger size and the slot should grow.
assert_eq!(sss.get_emergency_slot(types::F64, &[]), ss0);
assert_eq!(sss[ss0].size, 8);
// When one slot is in use, we should get a new one.
let ss1 = sss.get_emergency_slot(types::I32, &[None.into(), ss0.into()]);
assert_eq!(sss[ss0].size, 8);
assert_eq!(sss[ss1].size, 4);
// Now we should get the smallest fit of the two available slots.
assert_eq!(sss.get_emergency_slot(types::F32, &[]), ss1);
assert_eq!(sss.get_emergency_slot(types::F64, &[]), ss0);
}
} }