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,
Constraint {
inst: Inst,
},
ExternalConflict(String),
}
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",
),
EmitError::Constraint { inst } => write!(
f,
"{inst}: a table branch's temporary is its index, or a tail call's register target is restored by the epilogue"
),
EmitError::ExternalConflict(name) => {
write!(f, "`{name}` is declared external and also emitted")
}
}
}
}
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, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum RelocKind {
Call32,
Pc32,
Abs64,
}
impl RelocKind {
#[inline]
#[must_use]
pub const fn size(self) -> usize {
match self {
RelocKind::Call32 | RelocKind::Pc32 => 4,
RelocKind::Abs64 => 8,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Reloc {
pub offset: usize,
pub kind: RelocKind,
pub symbol: String,
pub addend: i64,
}
impl Reloc {
#[must_use]
pub fn new(offset: usize, kind: RelocKind, symbol: &str, addend: i64) -> Reloc {
Reloc {
offset,
kind,
symbol: String::from(symbol),
addend,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CallSiteRecord {
return_offset: usize,
gc_slots: Vec<i32>,
landing_pad: Option<usize>,
}
impl CallSiteRecord {
#[inline]
#[must_use]
pub fn return_offset(&self) -> usize {
self.return_offset
}
#[inline]
#[must_use]
pub fn gc_slots(&self) -> &[i32] {
&self.gc_slots
}
#[inline]
#[must_use]
pub fn landing_pad(&self) -> Option<usize> {
self.landing_pad
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EmittedFunction {
name: String,
offset: usize,
size: usize,
frame: FrameLayout,
call_sites: Vec<CallSiteRecord>,
}
impl EmittedFunction {
#[inline]
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
#[must_use]
pub fn offset(&self) -> usize {
self.offset
}
#[inline]
#[must_use]
pub fn size(&self) -> usize {
self.size
}
#[inline]
#[must_use]
pub fn frame(&self) -> &FrameLayout {
&self.frame
}
#[inline]
#[must_use]
pub fn call_sites(&self) -> &[CallSiteRecord] {
&self.call_sites
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Program {
bytes: Vec<u8>,
symbols: Vec<(String, usize)>,
functions: Vec<EmittedFunction>,
relocs: Vec<Reloc>,
}
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
.binary_search_by(|(n, _)| n.as_str().cmp(name))
.ok()
.and_then(|i| self.symbols.get(i))
.map(|&(_, off)| off)
}
pub fn symbols(&self) -> impl Iterator<Item = (&str, usize)> {
self.symbols.iter().map(|(n, o)| (n.as_str(), *o))
}
#[inline]
#[must_use]
pub fn functions(&self) -> &[EmittedFunction] {
&self.functions
}
#[inline]
#[must_use]
pub fn relocations(&self) -> &[Reloc] {
&self.relocs
}
}
#[derive(Clone, Copy, Debug)]
struct Sym {
label: Label,
defined: bool,
external: bool,
}
#[derive(Clone, Debug)]
struct PendingFunc {
name: String,
start: Label,
end: Label,
frame: FrameLayout,
sites: core::ops::Range<usize>,
}
#[derive(Clone, Debug)]
struct PendingSite {
ret: Label,
gc: core::ops::Range<usize>,
landing: Option<Label>,
}
#[derive(Clone, Debug)]
struct PendingReloc {
after: Label,
kind: RelocKind,
symbol: String,
}
#[derive(Clone, Debug)]
struct PendingTable {
at: Label,
targets: core::ops::Range<usize>,
}
#[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>,
funcs: Vec<PendingFunc>,
call_sites: Vec<PendingSite>,
gc_offsets: Vec<i32>,
relocs: Vec<PendingReloc>,
tables: Vec<PendingTable>,
table_targets: Vec<Label>,
}
struct Ctx<'a> {
f: &'a Function,
frame: &'a FrameLayout,
site: usize,
tables: 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 first_site = self.call_sites.len();
let first_table = self.tables.len();
let mut ctx = Ctx {
f,
frame: &frame,
site: 0,
tables: 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)?;
}
}
for t in first_table..self.tables.len() {
let (at, n) = match self.tables.get(t) {
Some(p) => (p.at, p.targets.len()),
None => continue,
};
self.asm.bind(at).map_err(EmitError::Asm)?;
let zeros = [0u8; 64];
let mut left = n.saturating_mul(8);
while left > 0 {
let k = left.min(zeros.len());
self.asm
.bytes(&zeros[..k])
.map_err(|e| asm_error(None, e))?;
left -= k;
}
}
let end = self.asm.label();
self.asm.bind(end).map_err(EmitError::Asm)?;
self.funcs.push(PendingFunc {
name: f.name().to_string(),
start: entry,
end,
frame: frame.clone(),
sites: first_site..self.call_sites.len(),
});
Ok(FunctionInfo { frame })
}
pub fn declare_external(&mut self, name: &str) -> Result<(), EmitError> {
let _ = self.label_for(name);
match self.symbols.get_mut(name) {
Some(s) if s.defined => Err(EmitError::ExternalConflict(name.to_string())),
Some(s) => {
s.external = true;
Ok(())
}
None => Ok(()),
}
}
pub fn finish(self) -> Result<Program, EmitError> {
if let Some((name, _)) = self.symbols.iter().find(|(_, s)| !s.defined && !s.external) {
return Err(EmitError::UndefinedSymbol(name.clone()));
}
let code = self.asm.finish().map_err(EmitError::Asm)?;
let at = |l: Label| {
code.label_offset(l)
.ok_or(EmitError::Asm(AsmError::Unbound(l)))
};
let symbols = self
.symbols
.iter()
.filter(|(_, s)| s.defined)
.filter_map(|(n, s)| code.label_offset(s.label).map(|o| (n.clone(), o)))
.collect();
let mut relocs = Vec::with_capacity(self.relocs.len());
for r in &self.relocs {
let end = at(r.after)?;
relocs.push(Reloc {
offset: end.saturating_sub(4),
kind: r.kind,
symbol: r.symbol.clone(),
addend: -4,
});
}
let mut patches: Vec<(usize, i64)> = Vec::new();
for t in &self.tables {
let base = at(t.at)?;
for (k, &target) in self
.table_targets
.get(t.targets.clone())
.unwrap_or(&[])
.iter()
.enumerate()
{
let to = at(target)?;
patches.push((base + 8 * k, to as i64 - base as i64));
}
}
let mut functions = Vec::with_capacity(self.funcs.len());
for pf in &self.funcs {
let offset = at(pf.start)?;
let size = at(pf.end)?.saturating_sub(offset);
let mut call_sites = Vec::with_capacity(pf.sites.len());
for ps in self.call_sites.get(pf.sites.clone()).unwrap_or(&[]) {
call_sites.push(CallSiteRecord {
return_offset: at(ps.ret)?,
gc_slots: self.gc_offsets.get(ps.gc.clone()).unwrap_or(&[]).to_vec(),
landing_pad: match ps.landing {
Some(l) => Some(at(l)?),
None => None,
},
});
}
functions.push(EmittedFunction {
name: pf.name.clone(),
offset,
size,
frame: pf.frame.clone(),
call_sites,
});
}
let mut bytes = code.into_bytes();
for (pos, value) in patches {
if let Some(field) = bytes.get_mut(pos..pos + 8) {
field.copy_from_slice(&value.to_le_bytes());
}
}
Ok(Program {
bytes,
symbols,
functions,
relocs,
})
}
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,
external: 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.external => Err(EmitError::ExternalConflict(name.to_string())),
Some(s) if s.defined => Err(EmitError::DuplicateSymbol(name.to_string())),
Some(s) => {
s.defined = true;
Ok(label)
}
None => Ok(label),
}
}
fn is_external(&self, name: &str) -> bool {
self.symbols.get(name).is_some_and(|s| s.external)
}
fn external_ref(
&mut self,
inst: Option<Inst>,
bytes: &[u8],
kind: RelocKind,
symbol: &str,
) -> Result<(), EmitError> {
self.asm.bytes(bytes).map_err(|e| asm_error(inst, e))?;
let after = self.asm.label();
self.asm.bind(after).map_err(EmitError::Asm)?;
self.relocs.push(PendingReloc {
after,
kind,
symbol: symbol.to_string(),
});
Ok(())
}
fn branch_to_symbol(&mut self, inst: Inst, name: &str, jump: bool) -> Result<(), EmitError> {
if self.is_external(name) {
let opcode = if jump { 0xE9 } else { 0xE8 };
return self.external_ref(Some(inst), &[opcode, 0, 0, 0, 0], RelocKind::Call32, name);
}
let label = self.label_for(name);
if jump {
self.asm.jmp(label).map_err(|e| asm_error(Some(inst), e))
} else {
self.asm.call(label).map_err(|e| asm_error(Some(inst), e))
}
}
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::TailCall => {
let target = match ops.first() {
Some(Operand::Reg(r)) => {
let p = r.as_phys().ok_or(EmitError::VirtualRegister {
block: Block::from_index(bi),
inst: Some(inst),
})?;
if ctx.frame.pushed().contains(&p)
|| (ctx.frame.frame_pointer() && p == super::RBP)
{
return Err(EmitError::Constraint { inst });
}
Some(ireg(Some(inst), p, Size::Qword)?)
}
_ => None,
};
for &step in ctx.frame.epilogue() {
self.frame_step(step, R11)?;
}
match (target, ops.first()) {
(Some(t), _) => self.emit(Some(inst), &IInst::new(M::Jmp, [t])),
(None, Some(Operand::Symbol(s))) => {
let name = f
.symbol_name(*s)
.ok_or(EmitError::Mir(MirError::UnknownSymbol(*s)))?;
self.branch_to_symbol(inst, name, true)
}
_ => Err(EmitError::Mir(MirError::OperandShape { inst, index: 0 })),
}
}
Enc::JumpTable => {
let reg = |index: usize| -> Result<IReg, EmitError> {
match ops.get(index) {
Some(Operand::Reg(r)) => qword(inst, *r),
_ => Err(EmitError::Mir(MirError::OperandShape { inst, index })),
}
};
let (t, i) = (reg(0)?, reg(1)?);
if t == i {
return Err(EmitError::Constraint { inst });
}
let table = self.asm.label();
let start = self.table_targets.len();
for (index, op) in ops.iter().enumerate().skip(2) {
let b = match op {
Operand::Block(call) => call.block(),
_ => return Err(EmitError::Mir(MirError::OperandShape { inst, index })),
};
let l = self
.labels
.get(b.index())
.copied()
.ok_or(EmitError::Mir(MirError::UnknownBlock(b)))?;
self.table_targets.push(l);
}
self.tables.push(PendingTable {
at: table,
targets: start..self.table_targets.len(),
});
ctx.tables += 1;
let lea = IInst::new(M::Lea, [IOp::Reg(t), IOp::Mem(IMem::rip(Size::Qword, 0))]);
self.asm
.emit_rip(&lea, table)
.map_err(|e| asm_error(Some(inst), e))?;
let entry = IMem::new(Size::Qword, t).with_index(i, IScale::X8);
self.emit(
Some(inst),
&IInst::new(M::Add, [IOp::Reg(t), IOp::Mem(entry)]),
)?;
self.emit(Some(inst), &IInst::new(M::Jmp, [IOp::Reg(t)]))
}
Enc::Call => {
match ops.first() {
Some(Operand::Symbol(s)) => {
let name = f
.symbol_name(*s)
.ok_or(EmitError::Mir(MirError::UnknownSymbol(*s)))?;
self.branch_to_symbol(inst, name, false)?;
}
Some(op) => {
let target = self.operand(ctx, inst, op, Size::Qword)?;
self.emit(Some(inst), &IInst::new(M::Call, [target]))?;
}
None => return Err(EmitError::Mir(MirError::OperandShape { inst, index: 0 })),
}
if let Some(site) = f.call_site(inst) {
let ret = self.asm.label();
self.asm.bind(ret).map_err(EmitError::Asm)?;
let gc_start = self.gc_offsets.len();
for &slot in site.gc_slots() {
let off = ctx
.frame
.slot_offset(slot)
.ok_or(EmitError::Mir(MirError::UnknownSlot(slot)))?;
self.gc_offsets.push(off);
}
let landing = match site.landing_pad() {
Some(b) => Some(
self.labels
.get(b.index())
.copied()
.ok_or(EmitError::Mir(MirError::UnknownBlock(b)))?,
),
None => None,
};
self.call_sites.push(PendingSite {
ret,
gc: gc_start..self.gc_offsets.len(),
landing,
});
}
Ok(())
}
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)))?;
if self.is_external(name) {
let encoded = isa_lang::x86_64::encode(&i).map_err(|error| {
EmitError::Encode {
inst: Some(inst),
error,
}
})?;
return self.external_ref(
Some(inst),
encoded.as_bytes(),
RelocKind::Pc32,
name,
);
}
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(())
}