use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
use isa_lang::x86_64::{
AsmError, Assembler, Cond as ICond, EncodeError, Inst as IInst, Label, Mem as IMem,
Mnemonic as M, Operand as IOp, Reg as IReg, Scale as IScale, Size,
};
use super::table::{DESCS, ENCODINGS, Enc, Fam, Src};
use super::{GPR, R10, R11, REGS};
use crate::mir::{
Block, BlockCall, Function, ImplicitKind, Inst, Mem, MirError, Operand, Scale, SlotKind,
StackSlot,
};
use crate::moves::{Loc, Move, MoveEnv, MoveError, MoveResolver, Source, Step};
use crate::regs::{PReg, Reg, RegClass};
use crate::target::frame::{FrameError, FrameInput, FrameLayout, FrameStep};
use crate::target::{InstKind, OperandRule};
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum EmitError {
Mir(MirError),
Frame(FrameError),
Moves(MoveError),
UnsupportedTarget,
VirtualRegister {
block: Block,
inst: Option<Inst>,
},
TiedMismatch {
inst: Inst,
},
ReservedWrite {
block: Block,
inst: Option<Inst>,
},
Unencodable {
inst: Option<Inst>,
},
Encode {
inst: Option<Inst>,
error: EncodeError,
},
Asm(AsmError),
UndefinedSymbol(String),
DuplicateSymbol(String),
ProbeScratch,
}
impl fmt::Display for EmitError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
EmitError::Mir(e) => fmt::Display::fmt(e, f),
EmitError::Frame(e) => fmt::Display::fmt(e, f),
EmitError::Moves(e) => fmt::Display::fmt(e, f),
EmitError::UnsupportedTarget => {
f.write_str("the function's target is not the x86-64 description this emitter encodes")
}
EmitError::VirtualRegister { block, inst } => match inst {
Some(i) => write!(f, "{i} in {block} still names a virtual register; allocate registers first"),
None => write!(f, "a parameter of {block} is a virtual register; allocate registers first"),
},
EmitError::TiedMismatch { inst } => write!(
f,
"{inst}: a tied use must be the same register as its definition"
),
EmitError::ReservedWrite { block, inst } => match inst {
Some(i) => write!(f, "{i} in {block} writes the stack or frame pointer"),
None => write!(f, "a parameter of {block} is the stack or frame pointer"),
},
EmitError::Unencodable { inst } => match inst {
Some(i) => write!(f, "{i}: isa-lang 0.2 has no encoding for this vector-register operation"),
None => f.write_str("a frame or edge move needs a vector-register instruction isa-lang 0.2 does not encode"),
},
EmitError::Encode { inst, error } => match inst {
Some(i) => write!(f, "{i}: {error}"),
None => write!(f, "frame or edge move: {error}"),
},
EmitError::Asm(e) => fmt::Display::fmt(e, f),
EmitError::UndefinedSymbol(name) => {
write!(f, "`{name}` is called but no function of that name was emitted")
}
EmitError::DuplicateSymbol(name) => {
write!(f, "two functions named `{name}` were emitted")
}
EmitError::ProbeScratch => f.write_str(
"the stack probe loop needs r10 or r11, and the entry block receives values in both",
),
}
}
}
impl core::error::Error for EmitError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
EmitError::Mir(e) => Some(e),
EmitError::Frame(e) => Some(e),
EmitError::Moves(e) => Some(e),
EmitError::Encode { error, .. } => Some(error),
EmitError::Asm(e) => Some(e),
_ => None,
}
}
}
impl From<MirError> for EmitError {
fn from(e: MirError) -> EmitError {
EmitError::Mir(e)
}
}
impl From<FrameError> for EmitError {
fn from(e: FrameError) -> EmitError {
EmitError::Frame(e)
}
}
impl From<MoveError> for EmitError {
fn from(e: MoveError) -> EmitError {
EmitError::Moves(e)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FunctionInfo {
frame: FrameLayout,
}
impl FunctionInfo {
#[inline]
#[must_use]
pub fn frame(&self) -> &FrameLayout {
&self.frame
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Program {
bytes: Vec<u8>,
symbols: Vec<(String, usize)>,
}
impl Program {
#[inline]
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
#[must_use]
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
#[must_use]
pub fn symbol(&self, name: &str) -> Option<usize> {
self.symbols
.iter()
.find(|(n, _)| n == name)
.map(|&(_, off)| off)
}
pub fn symbols(&self) -> impl Iterator<Item = (&str, usize)> {
self.symbols.iter().map(|(n, o)| (n.as_str(), *o))
}
}
#[derive(Clone, Copy, Debug)]
struct Sym {
label: Label,
defined: bool,
}
#[derive(Debug, Default)]
pub struct Emitter {
asm: Assembler,
symbols: BTreeMap<String, Sym>,
resolver: MoveResolver,
moves: Vec<Move>,
steps: Vec<Step>,
sites: Vec<(u32, u32)>,
labels: Vec<Label>,
}
struct Ctx<'a> {
f: &'a Function,
frame: &'a FrameLayout,
site: usize,
}
impl Emitter {
#[must_use]
pub fn new() -> Emitter {
Emitter::default()
}
#[must_use]
pub fn with_capacity(bytes: usize) -> Emitter {
Emitter {
asm: Assembler::with_capacity(bytes),
..Emitter::default()
}
}
pub fn function(&mut self, f: &Function) -> Result<FunctionInfo, EmitError> {
let target = f.target();
if !core::ptr::eq(target.insts(), &DESCS[..]) || !core::ptr::eq(target.regs(), ®S[..]) {
return Err(EmitError::UnsupportedTarget);
}
f.verify()?;
check_physical(f)?;
let base = f.slots().len();
let scratch = StackSlot::from_index(base);
let victim = StackSlot::from_index(base + 1);
let env = MoveEnv::new()
.with_scratch_slot(scratch)
.with_victim_slot(victim);
let (mut need_scratch, mut need_victim) = (false, false);
self.steps.clear();
self.sites.clear();
for block in f.blocks() {
for &inst in f.insts(block) {
let Some(opcode) = f.opcode(inst) else {
continue;
};
let ops = f.operands(inst);
let mut site =
|em: &mut Emitter, pairs: &mut dyn Iterator<Item = (Operand, Operand)>| {
em.moves.clear();
for (d, s) in pairs {
em.moves.push(to_move(f, d, s)?);
}
let start = em.steps.len();
let used = em
.resolver
.resolve(target, &em.moves, &env, &mut em.steps)?;
need_scratch |= used.scratch_slot;
need_victim |= used.victim_slot;
em.sites.push((start as u32, em.steps.len() as u32));
Ok::<(), EmitError>(())
};
match ENCODINGS.get(opcode.index()) {
Some(Enc::Copy) => {
site(
self,
&mut ops.first().copied().zip(ops.get(1).copied()).into_iter(),
)?;
}
Some(Enc::Pcopy) => {
let (d, s) = ops.split_at(ops.len() / 2);
site(self, &mut d.iter().copied().zip(s.iter().copied()))?;
}
Some(Enc::Jmp | Enc::Jcc) => {
for op in ops {
if let Operand::Block(call) = op {
let params = f.params(call.block()).iter().copied();
site(self, &mut params.zip(f.args(*call).iter().copied()))?;
}
}
}
_ => {}
}
}
}
let mut slots: Vec<SlotKind> = Vec::with_capacity(base + 2);
slots.extend_from_slice(f.slots());
for used in [need_scratch, need_victim] {
slots.push(if used {
SlotKind::Local {
size: 16,
align: 16,
}
} else {
SlotKind::Local { size: 0, align: 1 }
});
}
let frame = f.abi().layout_frame(&FrameInput {
slots: &slots,
written: f.written_regs(),
has_calls: f.has_calls(),
outgoing: f.outgoing(),
frame_pointer: f.frame_pointer(),
})?;
let entry = self.define(f.name())?;
self.asm.bind(entry).map_err(EmitError::Asm)?;
let probe_reg = probe_scratch(f)?;
for &step in frame.prologue() {
self.frame_step(step, probe_reg)?;
}
self.labels.clear();
for _ in 0..f.block_count() {
let l = self.asm.label();
self.labels.push(l);
}
let mut ctx = Ctx {
f,
frame: &frame,
site: 0,
};
for (bi, block) in f.blocks().enumerate() {
let label = self.labels[bi];
self.asm.bind(label).map_err(EmitError::Asm)?;
for &inst in f.insts(block) {
self.inst(&mut ctx, bi, inst)?;
}
}
Ok(FunctionInfo { frame })
}
pub fn finish(self) -> Result<Program, EmitError> {
if let Some((name, _)) = self.symbols.iter().find(|(_, s)| !s.defined) {
return Err(EmitError::UndefinedSymbol(name.clone()));
}
let code = self.asm.finish().map_err(EmitError::Asm)?;
let symbols = self
.symbols
.iter()
.filter_map(|(n, s)| code.label_offset(s.label).map(|o| (n.clone(), o)))
.collect();
Ok(Program {
bytes: code.into_bytes(),
symbols,
})
}
fn label_for(&mut self, name: &str) -> Label {
if let Some(s) = self.symbols.get(name) {
return s.label;
}
let label = self.asm.label();
let _ = self.symbols.insert(
name.to_string(),
Sym {
label,
defined: false,
},
);
label
}
fn define(&mut self, name: &str) -> Result<Label, EmitError> {
let label = self.label_for(name);
match self.symbols.get_mut(name) {
Some(s) if s.defined => Err(EmitError::DuplicateSymbol(name.to_string())),
Some(s) => {
s.defined = true;
Ok(label)
}
None => Ok(label),
}
}
fn emit(&mut self, inst: Option<Inst>, i: &IInst) -> Result<(), EmitError> {
self.asm.emit(i).map_err(|e| asm_error(inst, e))
}
fn frame_step(&mut self, step: FrameStep, probe_reg: PReg) -> Result<(), EmitError> {
let rsp = IOp::Reg(IReg::RSP);
match step {
FrameStep::Push(r) => {
self.emit(None, &IInst::new(M::Push, [ireg(None, r, Size::Qword)?]))
}
FrameStep::Pop(r) => {
self.emit(None, &IInst::new(M::Pop, [ireg(None, r, Size::Qword)?]))
}
FrameStep::SetFramePointer => {
self.emit(None, &IInst::new(M::Mov, [IOp::Reg(IReg::RBP), rsp]))
}
FrameStep::Alloc { bytes, probe } if probe == 0 || bytes < probe => {
self.emit(None, &IInst::new(M::Sub, [rsp, IOp::Imm(i64::from(bytes))]))
}
FrameStep::Alloc { bytes, probe } => self.probe(bytes, probe, probe_reg),
FrameStep::Free(bytes) => {
self.emit(None, &IInst::new(M::Add, [rsp, IOp::Imm(i64::from(bytes))]))
}
_ => Err(EmitError::Unencodable { inst: None }),
}
}
fn probe(&mut self, bytes: u32, page: u32, counter: PReg) -> Result<(), EmitError> {
let rsp = IOp::Reg(IReg::RSP);
let touch = IInst::new(M::Test, [IMem::new(Size::Qword, IReg::RSP).into(), rsp]);
let step = IInst::new(M::Sub, [rsp, IOp::Imm(i64::from(page))]);
let pages = bytes / page;
let rest = bytes % page;
if pages <= 4 {
for _ in 0..pages {
self.emit(None, &step)?;
self.emit(None, &touch)?;
}
} else {
let c = ireg(None, counter, Size::Dword)?;
self.emit(None, &IInst::new(M::Mov, [c, IOp::Imm(i64::from(pages))]))?;
let top = self.asm.label();
self.asm.bind(top).map_err(EmitError::Asm)?;
self.emit(None, &step)?;
self.emit(None, &touch)?;
self.emit(None, &IInst::new(M::Dec, [c]))?;
self.asm.jcc(ICond::Ne, top).map_err(EmitError::Asm)?;
}
if rest > 0 {
self.emit(None, &IInst::new(M::Sub, [rsp, IOp::Imm(i64::from(rest))]))?;
}
Ok(())
}
fn site(&mut self, ctx: &mut Ctx<'_>) -> Result<bool, EmitError> {
let (start, end) = self.sites.get(ctx.site).copied().unwrap_or((0, 0));
ctx.site += 1;
for i in start as usize..end as usize {
let step = self.steps[i];
self.step(ctx.frame, step)?;
}
Ok(end > start)
}
fn next_site_empty(&self, ctx: &Ctx<'_>, ahead: usize) -> bool {
self.sites
.get(ctx.site + ahead)
.is_none_or(|&(s, e)| s == e)
}
fn step(&mut self, frame: &FrameLayout, step: Step) -> Result<(), EmitError> {
let gpr = |class: RegClass| {
if class == GPR {
Ok(())
} else {
Err(EmitError::Unencodable { inst: None })
}
};
match step {
Step::Swap { a, b, class } => {
gpr(class)?;
let i = IInst::new(
M::Xchg,
[ireg(None, a, Size::Qword)?, ireg(None, b, Size::Qword)?],
);
self.emit(None, &i)
}
Step::Move { dst, src, class } => {
gpr(class)?;
let i = match (dst, src) {
(Loc::Reg(d), Source::Imm(v)) => return self.mov_imm(None, d, v),
(Loc::Reg(d), Source::Loc(Loc::Reg(s))) => IInst::new(
M::Mov,
[ireg(None, d, Size::Qword)?, ireg(None, s, Size::Qword)?],
),
(Loc::Reg(d), Source::Loc(Loc::Slot(s))) => IInst::new(
M::Mov,
[
ireg(None, d, Size::Qword)?,
slot_mem(frame, s, 0, Size::Qword)?.into(),
],
),
(Loc::Slot(d), Source::Loc(Loc::Reg(s))) => IInst::new(
M::Mov,
[
slot_mem(frame, d, 0, Size::Qword)?.into(),
ireg(None, s, Size::Qword)?,
],
),
(Loc::Slot(d), Source::Imm(v)) => IInst::new(
M::Mov,
[slot_mem(frame, d, 0, Size::Qword)?.into(), IOp::Imm(v)],
),
(Loc::Slot(_), Source::Loc(Loc::Slot(_))) => {
return Err(EmitError::Unencodable { inst: None });
}
};
self.emit(None, &i)
}
}
}
fn mov_imm(&mut self, inst: Option<Inst>, reg: PReg, value: i64) -> Result<(), EmitError> {
let i = if (0..=i64::from(u32::MAX)).contains(&value) {
IInst::new(
M::Mov,
[
ireg(inst, reg, Size::Dword)?,
IOp::Imm(i64::from(value as u32 as i32)),
],
)
} else {
IInst::new(M::Mov, [ireg(inst, reg, Size::Qword)?, IOp::Imm(value)])
};
self.emit(inst, &i)
}
fn inst(&mut self, ctx: &mut Ctx<'_>, bi: usize, inst: Inst) -> Result<(), EmitError> {
let f = ctx.f;
let Some(opcode) = f.opcode(inst) else {
return Ok(());
};
let ops = f.operands(inst);
let enc = ENCODINGS
.get(opcode.index())
.copied()
.ok_or(EmitError::UnsupportedTarget)?;
let next = Block::from_index(bi + 1);
match enc {
Enc::Copy | Enc::Pcopy => {
let _ = self.site(ctx)?;
Ok(())
}
Enc::Jmp => {
let call = block_operand(ops, 0, inst)?;
let _ = self.site(ctx)?;
if call.block() != next {
self.jmp(call.block())?;
}
Ok(())
}
Enc::Jcc => {
let cond = cond_operand(ops, 0, inst)?;
let then = block_operand(ops, 1, inst)?;
let other = block_operand(ops, 2, inst)?;
let then_moves = !self.next_site_empty(ctx, 0);
let else_moves = !self.next_site_empty(ctx, 1);
if !then_moves {
self.jcc(cond, then.block())?;
let _ = self.site(ctx)?;
let _ = self.site(ctx)?;
} else if !else_moves {
self.jcc(cond.negate(), other.block())?;
let _ = self.site(ctx)?;
if then.block() != next {
self.jmp(then.block())?;
}
let _ = self.site(ctx)?;
return Ok(());
} else {
let stub = self.asm.label();
self.asm.jcc(cond.negate(), stub).map_err(EmitError::Asm)?;
let _ = self.site(ctx)?;
self.jmp(then.block())?;
self.asm.bind(stub).map_err(EmitError::Asm)?;
let _ = self.site(ctx)?;
}
if other.block() != next {
self.jmp(other.block())?;
}
Ok(())
}
Enc::Ret => {
for &step in ctx.frame.epilogue() {
self.frame_step(step, R11)?;
}
self.emit(Some(inst), &IInst::new(M::Ret, []))
}
Enc::Call => match ops.first() {
Some(Operand::Symbol(s)) => {
let name = f
.symbol_name(*s)
.ok_or(EmitError::Mir(MirError::UnknownSymbol(*s)))?;
let label = self.label_for(name);
self.asm.call(label).map_err(|e| asm_error(Some(inst), e))
}
Some(op) => {
let target = self.operand(ctx, inst, op, Size::Qword)?;
self.emit(Some(inst), &IInst::new(M::Call, [target]))
}
None => Err(EmitError::Mir(MirError::OperandShape { inst, index: 0 })),
},
Enc::MovImm64 => match (
ops.first().and_then(Operand::reg).and_then(Reg::as_phys),
ops.get(1),
) {
(Some(d), Some(Operand::Imm(v))) => self.mov_imm(Some(inst), d, *v),
_ => Err(EmitError::Mir(MirError::OperandShape { inst, index: 0 })),
},
Enc::Raw(bytes) => self.asm.bytes(bytes).map_err(|e| asm_error(Some(inst), e)),
Enc::Isa {
fam,
ops: srcs,
cond,
} => {
let mnemonic = match fam {
Fam::Plain(m) => m,
Fam::Setcc => M::Setcc(cond_operand(ops, usize::from(cond), inst)?),
Fam::Cmovcc => M::Cmovcc(cond_operand(ops, usize::from(cond), inst)?),
};
let mut out = [IOp::Imm(0); 3];
let mut rip = None;
for (k, src) in srcs.iter().enumerate().take(3) {
out[k] = match *src {
Src::Fixed(r) => IOp::Reg(r),
Src::Op(n, size) => match ops.get(usize::from(n)) {
Some(Operand::Symbol(s)) => {
rip = Some(*s);
IOp::Mem(IMem::rip(size, 0))
}
Some(op) => self.operand(ctx, inst, op, size)?,
None => {
return Err(EmitError::Mir(MirError::OperandShape {
inst,
index: usize::from(n),
}));
}
},
};
}
let i = IInst::from_slice(mnemonic, &out[..srcs.len().min(3)])
.ok_or(EmitError::Unencodable { inst: Some(inst) })?;
match rip {
Some(s) => {
let name = f
.symbol_name(s)
.ok_or(EmitError::Mir(MirError::UnknownSymbol(s)))?;
let label = self.label_for(name);
self.asm
.emit_rip(&i, label)
.map_err(|e| asm_error(Some(inst), e))
}
None => self.emit(Some(inst), &i),
}
}
}
}
fn jmp(&mut self, block: Block) -> Result<(), EmitError> {
let label = self
.labels
.get(block.index())
.copied()
.ok_or(EmitError::Mir(MirError::UnknownBlock(block)))?;
self.asm.jmp(label).map_err(EmitError::Asm)
}
fn jcc(&mut self, cond: ICond, block: Block) -> Result<(), EmitError> {
let label = self
.labels
.get(block.index())
.copied()
.ok_or(EmitError::Mir(MirError::UnknownBlock(block)))?;
self.asm.jcc(cond, label).map_err(EmitError::Asm)
}
fn operand(
&self,
ctx: &Ctx<'_>,
inst: Inst,
op: &Operand,
size: Size,
) -> Result<IOp, EmitError> {
Ok(match *op {
Operand::Reg(r) => {
let p = r.as_phys().ok_or(EmitError::VirtualRegister {
block: ctx.f.inst_block(inst).unwrap_or(Block::new(0)),
inst: Some(inst),
})?;
ireg(Some(inst), p, size)?
}
Operand::Imm(v) => IOp::Imm(v),
Operand::Mem(m) => IOp::Mem(mem(inst, &m, size)?),
Operand::Slot { slot, offset } => IOp::Mem(slot_mem(ctx.frame, slot, offset, size)?),
_ => return Err(EmitError::Unencodable { inst: Some(inst) }),
})
}
}
fn asm_error(inst: Option<Inst>, e: AsmError) -> EmitError {
match e {
AsmError::Encode(error) => EmitError::Encode { inst, error },
other => EmitError::Asm(other),
}
}
fn ireg(inst: Option<Inst>, p: PReg, size: Size) -> Result<IOp, EmitError> {
let info = REGS.get(p.index()).ok_or(EmitError::Unencodable { inst })?;
if info.class() != GPR {
return Err(EmitError::Unencodable { inst });
}
IReg::new(size, info.hw())
.map(IOp::Reg)
.ok_or(EmitError::Unencodable { inst })
}
fn qword(inst: Inst, r: Reg) -> Result<IReg, EmitError> {
let p = r
.as_phys()
.ok_or(EmitError::Unencodable { inst: Some(inst) })?;
match ireg(Some(inst), p, Size::Qword)? {
IOp::Reg(r) => Ok(r),
_ => Err(EmitError::Unencodable { inst: Some(inst) }),
}
}
fn mem(inst: Inst, m: &Mem, size: Size) -> Result<IMem, EmitError> {
let mut out = match m.base_reg() {
Some(b) => IMem::new(size, qword(inst, b)?),
None => IMem::absolute(size, 0),
};
if let Some((i, s)) = m.index_reg() {
let scale = match s {
Scale::X1 => IScale::X1,
Scale::X2 => IScale::X2,
Scale::X4 => IScale::X4,
Scale::X8 => IScale::X8,
};
out = out.with_index(qword(inst, i)?, scale);
}
Ok(out.with_disp(m.disp()))
}
fn slot_mem(
frame: &FrameLayout,
slot: StackSlot,
offset: i32,
size: Size,
) -> Result<IMem, EmitError> {
let base = frame
.slot_offset(slot)
.ok_or(EmitError::Mir(MirError::UnknownSlot(slot)))?;
let disp = base
.checked_add(offset)
.ok_or(EmitError::Frame(FrameError::TooLarge))?;
Ok(IMem::new(size, IReg::RSP).with_disp(disp))
}
fn block_operand(ops: &[Operand], index: usize, inst: Inst) -> Result<BlockCall, EmitError> {
match ops.get(index) {
Some(Operand::Block(c)) => Ok(*c),
_ => Err(EmitError::Mir(MirError::OperandShape { inst, index })),
}
}
fn cond_operand(ops: &[Operand], index: usize, inst: Inst) -> Result<ICond, EmitError> {
match ops.get(index) {
Some(Operand::Cond(c)) => {
ICond::from_code(c.code()).ok_or(EmitError::Mir(MirError::OperandShape { inst, index }))
}
_ => Err(EmitError::Mir(MirError::OperandShape { inst, index })),
}
}
fn to_move(f: &Function, dst: Operand, src: Operand) -> Result<Move, EmitError> {
let class_of = |op: &Operand| op.reg().and_then(|r| f.reg_class(r));
let class = class_of(&dst)
.or_else(|| class_of(&src))
.unwrap_or(f.target().addr_class());
let loc = |op: Operand| -> Option<Loc> {
match op {
Operand::Reg(r) => r.as_phys().map(Loc::Reg),
Operand::Slot { slot, offset: 0 } => Some(Loc::Slot(slot)),
_ => None,
}
};
let bad = EmitError::Unencodable { inst: None };
let d = loc(dst).ok_or(bad.clone())?;
let s = match src {
Operand::Imm(v) => Source::Imm(v),
other => Source::Loc(loc(other).ok_or(bad)?),
};
Ok(Move {
dst: d,
src: s,
class,
})
}
fn probe_scratch(f: &Function) -> Result<PReg, EmitError> {
let params = f.entry().map_or(&[][..], |e| f.params(e));
let taken = |r: PReg| params.iter().any(|p| p.reg() == Some(r.into()));
[R11, R10]
.into_iter()
.find(|&r| !taken(r))
.ok_or(EmitError::ProbeScratch)
}
fn check_physical(f: &Function) -> Result<(), EmitError> {
let target = f.target();
let reserved = f.reserved();
for block in f.blocks() {
for p in f.params(block) {
if let Some(r) = p.reg() {
match r.as_phys() {
None => return Err(EmitError::VirtualRegister { block, inst: None }),
Some(p) if reserved.contains(p) => {
return Err(EmitError::ReservedWrite { block, inst: None });
}
Some(_) => {}
}
}
}
for &inst in f.insts(block) {
let virt = EmitError::VirtualRegister {
block,
inst: Some(inst),
};
let ops = f.operands(inst);
for op in ops {
let any_virtual = match op {
Operand::Reg(r) => r.is_virtual(),
Operand::Mem(m) => m.regs().any(Reg::is_virtual),
Operand::Block(call) => f
.args(*call)
.iter()
.any(|a| a.reg().is_some_and(Reg::is_virtual)),
_ => false,
};
if any_virtual {
return Err(virt);
}
}
let Some(desc) = f.opcode(inst).and_then(|o| target.inst(o)) else {
continue;
};
let defs = match desc.kind() {
InstKind::Copy => 1,
InstKind::ParallelCopy => ops.len() / 2,
_ => desc.defs(),
};
let reserved_write = ops
.iter()
.take(defs)
.filter_map(|o| o.reg().and_then(Reg::as_phys))
.chain(
f.implicit(inst)
.iter()
.filter(|i| i.kind == ImplicitKind::Def)
.map(|i| i.reg),
)
.chain(desc.implicit_defs().iter().copied())
.any(|p| reserved.contains(p))
|| !f.clobbers(inst).intersect(reserved).is_empty();
if reserved_write {
return Err(EmitError::ReservedWrite {
block,
inst: Some(inst),
});
}
for (i, rule) in desc.operands().iter().enumerate() {
if let OperandRule::Tied(d) = rule {
if ops.get(i).and_then(Operand::reg)
!= ops.get(usize::from(*d)).and_then(Operand::reg)
{
return Err(EmitError::TiedMismatch { inst });
}
}
}
}
}
Ok(())
}