use alloc::string::String;
use alloc::vec::Vec;
use super::entity::{Block, Inst, StackSlot, Symbol};
use super::operand::{BlockCall, Implicit, ImplicitKind, Operand};
use super::verify::MirError;
use crate::regs::{PReg, Reg, RegClass, RegSet, VReg};
use crate::target::abi::Abi;
use crate::target::frame::{FrameError, FrameInput, FrameLayout};
use crate::target::{InstKind, Opcode, OperandRule, Target};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SlotKind {
Local {
size: u32,
align: u32,
},
Incoming {
offset: u32,
size: u32,
},
}
impl SlotKind {
#[inline]
#[must_use]
pub const fn size(self) -> u32 {
match self {
SlotKind::Local { size, .. } | SlotKind::Incoming { size, .. } => size,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Effects<'a> {
uses: &'a [PReg],
defs: &'a [PReg],
clobbers: RegSet,
}
impl<'a> Effects<'a> {
#[must_use]
pub const fn new() -> Effects<'a> {
Effects {
uses: &[],
defs: &[],
clobbers: RegSet::EMPTY,
}
}
#[must_use]
pub const fn uses(self, regs: &'a [PReg]) -> Effects<'a> {
Effects { uses: regs, ..self }
}
#[must_use]
pub const fn defs(self, regs: &'a [PReg]) -> Effects<'a> {
Effects { defs: regs, ..self }
}
#[must_use]
pub const fn clobbers(self, regs: RegSet) -> Effects<'a> {
Effects {
clobbers: regs,
..self
}
}
#[must_use]
pub const fn uses_regs(&self) -> &'a [PReg] {
self.uses
}
#[must_use]
pub const fn defs_regs(&self) -> &'a [PReg] {
self.defs
}
#[must_use]
pub const fn clobber_set(&self) -> RegSet {
self.clobbers
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct InstData {
pub(crate) opcode: Opcode,
pub(crate) block: Block,
pub(crate) ops: u32,
pub(crate) nops: u32,
pub(crate) imp: u32,
pub(crate) nimp: u32,
pub(crate) clobber: u32,
}
pub(crate) const NO_CLOBBERS: u32 = u32::MAX;
#[derive(Clone, Copy, Debug)]
pub(crate) struct SiteData {
pub(crate) inst: Inst,
pub(crate) gc: u32,
pub(crate) ngc: u32,
pub(crate) landing: Option<Block>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CallSite<'a> {
inst: Inst,
gc_slots: &'a [StackSlot],
landing_pad: Option<Block>,
}
impl<'a> CallSite<'a> {
#[inline]
#[must_use]
pub const fn inst(&self) -> Inst {
self.inst
}
#[inline]
#[must_use]
pub const fn gc_slots(&self) -> &'a [StackSlot] {
self.gc_slots
}
#[inline]
#[must_use]
pub const fn landing_pad(&self) -> Option<Block> {
self.landing_pad
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct BlockData {
pub(crate) params: Vec<Operand>,
pub(crate) insts: Vec<Inst>,
}
pub(crate) const MAX_ENTRIES: usize = u32::MAX as usize - 1;
#[derive(Clone, Debug)]
pub struct Function {
name: String,
abi: &'static Abi,
pub(crate) vregs: Vec<RegClass>,
pub(crate) blocks: Vec<BlockData>,
pub(crate) insts: Vec<InstData>,
pub(crate) operands: Vec<Operand>,
pub(crate) args: Vec<Operand>,
pub(crate) implicits: Vec<Implicit>,
pub(crate) clobbers: Vec<RegSet>,
pub(crate) slots: Vec<SlotKind>,
pub(crate) symbols: Vec<String>,
pub(crate) sites: Vec<SiteData>,
pub(crate) site_slots: Vec<StackSlot>,
outgoing: u32,
frame_pointer: bool,
}
fn room(len: usize) -> Result<(), MirError> {
next_id(len).map(|_| ())
}
fn next_id(len: usize) -> Result<u32, MirError> {
if len < MAX_ENTRIES {
Ok(len as u32)
} else {
Err(MirError::Limit)
}
}
impl Function {
#[must_use]
pub fn new(name: &str, abi: &'static Abi) -> Function {
Function {
name: String::from(name),
abi,
vregs: Vec::new(),
blocks: Vec::new(),
insts: Vec::new(),
operands: Vec::new(),
args: Vec::new(),
implicits: Vec::new(),
clobbers: Vec::new(),
slots: Vec::new(),
symbols: Vec::new(),
sites: Vec::new(),
site_slots: Vec::new(),
outgoing: 0,
frame_pointer: true,
}
}
#[must_use]
pub fn with_capacity(name: &str, abi: &'static Abi, insts: usize, operands: usize) -> Function {
let mut f = Function::new(name, abi);
f.insts.reserve(insts);
f.operands.reserve(operands);
f
}
#[inline]
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
#[must_use]
pub fn abi(&self) -> &'static Abi {
self.abi
}
#[inline]
#[must_use]
pub fn target(&self) -> &'static Target {
self.abi.target()
}
pub fn vreg(&mut self, class: RegClass) -> Result<Reg, MirError> {
if !self.target().has_class(class) {
return Err(MirError::UnknownClass(class));
}
if self.vregs.len() >= VReg::MAX {
return Err(MirError::Limit);
}
let v = VReg::from_index(self.vregs.len() as u32);
self.vregs.push(class);
Ok(v.into())
}
#[inline]
#[must_use]
pub fn vreg_class(&self, vreg: VReg) -> Option<RegClass> {
self.vregs.get(vreg.index()).copied()
}
#[inline]
#[must_use]
pub fn vreg_count(&self) -> usize {
self.vregs.len()
}
#[must_use]
pub fn reg_class(&self, reg: Reg) -> Option<RegClass> {
match (reg.as_phys(), reg.as_virt()) {
(Some(p), _) => self.target().class_of(p),
(_, Some(v)) => self.vreg_class(v),
_ => None,
}
}
pub fn block(&mut self) -> Result<Block, MirError> {
let id = next_id(self.blocks.len())?;
self.blocks.push(BlockData::default());
Ok(Block::new(id))
}
#[inline]
#[must_use]
pub fn entry(&self) -> Option<Block> {
if self.blocks.is_empty() {
None
} else {
Some(Block::new(0))
}
}
pub fn blocks(&self) -> impl ExactSizeIterator<Item = Block> + '_ {
(0..self.blocks.len()).map(Block::from_index)
}
#[inline]
#[must_use]
pub fn block_count(&self) -> usize {
self.blocks.len()
}
pub fn add_param(&mut self, block: Block, param: Operand) -> Result<(), MirError> {
match param {
Operand::Reg(r) => self.check_reg(r)?,
Operand::Slot { slot, offset: 0 } => self.check_slot(slot)?,
_ => return Err(MirError::BadParam { block }),
}
let data = self
.blocks
.get_mut(block.index())
.ok_or(MirError::UnknownBlock(block))?;
room(data.params.len())?;
data.params.push(param);
Ok(())
}
#[inline]
#[must_use]
pub fn params(&self, block: Block) -> &[Operand] {
self.blocks
.get(block.index())
.map_or(&[], |b| b.params.as_slice())
}
pub fn stack_slot(&mut self, kind: SlotKind) -> Result<StackSlot, MirError> {
let id = next_id(self.slots.len())?;
self.slots.push(kind);
Ok(StackSlot::new(id))
}
pub fn spill_slot(&mut self, class: RegClass) -> Result<StackSlot, MirError> {
if !self.target().has_class(class) {
return Err(MirError::UnknownClass(class));
}
let info = self.target().class(class);
self.stack_slot(SlotKind::Local {
size: info.spill_size(),
align: info.spill_align(),
})
}
#[inline]
#[must_use]
pub fn slot(&self, slot: StackSlot) -> Option<SlotKind> {
self.slots.get(slot.index()).copied()
}
#[inline]
#[must_use]
pub fn slots(&self) -> &[SlotKind] {
&self.slots
}
pub fn symbol(&mut self, name: &str) -> Result<Symbol, MirError> {
let id = next_id(self.symbols.len())?;
self.symbols.push(String::from(name));
Ok(Symbol::new(id))
}
#[inline]
#[must_use]
pub fn symbol_name(&self, symbol: Symbol) -> Option<&str> {
self.symbols.get(symbol.index()).map(String::as_str)
}
pub fn block_call(&mut self, block: Block, args: &[Operand]) -> Result<BlockCall, MirError> {
if block.index() >= self.blocks.len() {
return Err(MirError::UnknownBlock(block));
}
for &a in args {
match a {
Operand::Reg(r) => self.check_reg(r)?,
Operand::Slot { slot, offset: 0 } => self.check_slot(slot)?,
Operand::Imm(_) => {}
_ => return Err(MirError::BadArg { block }),
}
}
let start = next_id(self.args.len())?;
let len = u32::try_from(args.len()).map_err(|_| MirError::Limit)?;
room(self.args.len().saturating_add(args.len()))?;
self.args.extend_from_slice(args);
Ok(BlockCall::new(block, start, len))
}
#[inline]
#[must_use]
pub fn args(&self, call: BlockCall) -> &[Operand] {
self.args.get(call.range()).unwrap_or(&[])
}
pub fn push(
&mut self,
block: Block,
opcode: Opcode,
operands: &[Operand],
) -> Result<Inst, MirError> {
self.push_with(block, opcode, operands, Effects::new())
}
pub fn push_with(
&mut self,
block: Block,
opcode: Opcode,
operands: &[Operand],
effects: Effects<'_>,
) -> Result<Inst, MirError> {
let inst = self.create_inst(block, opcode, operands, effects)?;
if let Some(b) = self.blocks.get_mut(block.index()) {
b.insts.push(inst);
}
Ok(inst)
}
pub(crate) fn create_inst(
&mut self,
block: Block,
opcode: Opcode,
operands: &[Operand],
effects: Effects<'_>,
) -> Result<Inst, MirError> {
if block.index() >= self.blocks.len() {
return Err(MirError::UnknownBlock(block));
}
let inst = Inst::new(next_id(self.insts.len())?);
self.check_inst(inst, opcode, operands)?;
for &r in effects.uses.iter().chain(effects.defs) {
if !self.target().has_reg(r) {
return Err(MirError::UnknownPReg(r));
}
}
let ops = next_id(self.operands.len())?;
room(self.operands.len().saturating_add(operands.len()))?;
let imp = next_id(self.implicits.len())?;
let nimp = effects.uses.len() + effects.defs.len();
room(self.implicits.len().saturating_add(nimp))?;
let clobber = if effects.clobbers.is_empty() {
NO_CLOBBERS
} else {
let id = next_id(self.clobbers.len())?;
self.clobbers.push(effects.clobbers);
id
};
self.operands.extend_from_slice(operands);
self.implicits
.extend(effects.uses.iter().map(|®| Implicit {
reg,
kind: ImplicitKind::Use,
}));
self.implicits
.extend(effects.defs.iter().map(|®| Implicit {
reg,
kind: ImplicitKind::Def,
}));
self.insts.push(InstData {
opcode,
block,
ops,
nops: operands.len() as u32,
imp,
nimp: nimp as u32,
clobber,
});
Ok(inst)
}
pub(crate) fn set_block_insts(&mut self, block: Block, insts: Vec<Inst>) {
if let Some(b) = self.blocks.get_mut(block.index()) {
b.insts = insts;
}
}
pub(crate) fn take_block_insts(&mut self, block: Block) -> Vec<Inst> {
self.blocks
.get_mut(block.index())
.map(|b| core::mem::take(&mut b.insts))
.unwrap_or_default()
}
pub(crate) fn set_params(&mut self, block: Block, params: Vec<Operand>) {
if let Some(b) = self.blocks.get_mut(block.index()) {
b.params = params;
}
}
pub(crate) fn operand_base(&self, inst: Inst) -> usize {
self.insts.get(inst.index()).map_or(0, |d| d.ops as usize)
}
#[inline]
#[must_use]
pub fn insts(&self, block: Block) -> &[Inst] {
self.blocks
.get(block.index())
.map_or(&[], |b| b.insts.as_slice())
}
#[inline]
#[must_use]
pub fn inst_count(&self) -> usize {
self.insts.len()
}
#[inline]
#[must_use]
pub fn opcode(&self, inst: Inst) -> Option<Opcode> {
self.insts.get(inst.index()).map(|d| d.opcode)
}
#[inline]
#[must_use]
pub fn inst_block(&self, inst: Inst) -> Option<Block> {
self.insts.get(inst.index()).map(|d| d.block)
}
#[inline]
#[must_use]
pub fn operands(&self, inst: Inst) -> &[Operand] {
self.insts
.get(inst.index())
.and_then(|d| {
self.operands
.get(d.ops as usize..d.ops as usize + d.nops as usize)
})
.unwrap_or(&[])
}
#[inline]
#[must_use]
pub fn implicit(&self, inst: Inst) -> &[Implicit] {
self.insts
.get(inst.index())
.and_then(|d| {
self.implicits
.get(d.imp as usize..d.imp as usize + d.nimp as usize)
})
.unwrap_or(&[])
}
#[inline]
#[must_use]
pub fn clobbers(&self, inst: Inst) -> RegSet {
self.insts
.get(inst.index())
.and_then(|d| self.clobbers.get(d.clobber as usize))
.copied()
.unwrap_or(RegSet::EMPTY)
}
pub fn set_call_site(
&mut self,
inst: Inst,
gc_slots: &[StackSlot],
landing_pad: Option<Block>,
) -> Result<(), MirError> {
let is_call = self
.opcode(inst)
.and_then(|o| self.target().inst(o))
.is_some_and(|d| d.kind() == InstKind::Call);
if !is_call {
return Err(MirError::NotACall { inst });
}
for &s in gc_slots {
self.check_slot(s)?;
}
if let Some(b) = landing_pad {
if b.index() >= self.blocks.len() {
return Err(MirError::UnknownBlock(b));
}
}
let gc = next_id(self.site_slots.len())?;
room(self.site_slots.len().saturating_add(gc_slots.len()))?;
let ngc = u32::try_from(gc_slots.len()).map_err(|_| MirError::Limit)?;
self.site_slots.extend_from_slice(gc_slots);
let data = SiteData {
inst,
gc,
ngc,
landing: landing_pad,
};
match self.sites.binary_search_by_key(&inst, |s| s.inst) {
Ok(i) => self.sites[i] = data,
Err(i) => {
room(self.sites.len())?;
self.sites.insert(i, data);
}
}
Ok(())
}
#[must_use]
pub fn call_site(&self, inst: Inst) -> Option<CallSite<'_>> {
let i = self.sites.binary_search_by_key(&inst, |s| s.inst).ok()?;
self.sites.get(i).map(|d| self.site_ref(d))
}
pub fn call_sites(&self) -> impl ExactSizeIterator<Item = CallSite<'_>> + '_ {
self.sites.iter().map(|d| self.site_ref(d))
}
fn site_ref(&self, d: &SiteData) -> CallSite<'_> {
let start = d.gc as usize;
CallSite {
inst: d.inst,
gc_slots: self
.site_slots
.get(start..start + d.ngc as usize)
.unwrap_or(&[]),
landing_pad: d.landing,
}
}
pub fn reserve_outgoing(&mut self, bytes: u32) {
self.outgoing = self.outgoing.max(bytes);
}
#[inline]
#[must_use]
pub fn outgoing(&self) -> u32 {
self.outgoing
}
pub fn omit_frame_pointer(&mut self) {
self.frame_pointer = false;
}
#[inline]
#[must_use]
pub fn frame_pointer(&self) -> bool {
self.frame_pointer
}
#[must_use]
pub fn reserved(&self) -> RegSet {
let t = self.target();
let set = RegSet::of(&[t.stack_pointer()]);
if self.frame_pointer {
set.with(t.frame_pointer())
} else {
set
}
}
#[must_use]
pub fn has_calls(&self) -> bool {
let t = self.target();
self.insts.iter().any(|d| {
t.inst(d.opcode)
.is_some_and(|desc| desc.kind() == InstKind::Call)
})
}
#[must_use]
pub fn written_regs(&self) -> RegSet {
let t = self.target();
let mut set = RegSet::EMPTY;
for (i, d) in self.insts.iter().enumerate() {
let inst = Inst::from_index(i);
let Some(desc) = t.inst(d.opcode) else {
continue;
};
for p in desc.implicit_defs() {
set = set.with(*p);
}
set = set.union(desc.clobbers()).union(self.clobbers(inst));
for imp in self.implicit(inst) {
if imp.kind == ImplicitKind::Def {
set = set.with(imp.reg);
}
}
let ops = self.operands(inst);
let defs = match desc.kind() {
InstKind::Copy => ops.len().min(1),
InstKind::ParallelCopy => ops.len() / 2,
_ => desc
.operands()
.iter()
.take_while(|r| matches!(r, OperandRule::Def(_)))
.count(),
};
for op in ops.iter().take(defs) {
if let Some(p) = op.reg().and_then(Reg::as_phys) {
set = set.with(p);
}
}
}
for b in &self.blocks {
for p in &b.params {
if let Some(r) = p.reg().and_then(Reg::as_phys) {
set = set.with(r);
}
}
}
set
}
pub fn frame_layout(&self) -> Result<FrameLayout, FrameError> {
self.abi.layout_frame(&FrameInput {
slots: &self.slots,
written: self.written_regs(),
has_calls: self.has_calls(),
outgoing: self.outgoing,
frame_pointer: self.frame_pointer,
})
}
pub(crate) fn check_reg(&self, reg: Reg) -> Result<(), MirError> {
match (reg.as_phys(), reg.as_virt()) {
(Some(p), _) if !self.target().has_reg(p) => Err(MirError::UnknownPReg(p)),
(_, Some(v)) if v.index() >= self.vregs.len() => Err(MirError::UnknownVReg(v)),
_ => Ok(()),
}
}
pub(crate) fn check_slot(&self, slot: StackSlot) -> Result<(), MirError> {
if slot.index() < self.slots.len() {
Ok(())
} else {
Err(MirError::UnknownSlot(slot))
}
}
}