use std::fmt::Write as _;
use rucc_base::Interner;
use rucc_mir::{Amode, Block, Func, Inst, defs};
use rucc_target::aarch64::{self, Addr, Arg, Extend, Mode, Offset, Operands};
use rucc_target::template::template_filled;
use crate::Error;
pub(crate) const PREFIX: &str = "a64.";
pub(crate) struct Context<'a> {
pub names: &'a Interner,
pub symbol: &'a str,
pub spelling: aarch64::Spelling,
pub func_name: &'a str,
}
pub(crate) fn inst(
out: &mut String,
at: &Context<'_>,
func: &Func,
block: Block,
inst: Inst,
label: impl Fn(Block) -> String,
table: impl Fn(u32) -> String,
) -> Result<(), Error> {
let data = func[inst];
let spelled = at.names.resolve(data.opcode.name());
let opcode = spelled.strip_prefix(PREFIX).unwrap_or(spelled);
let refused = || Error::Opcode { func: at.func_name.to_owned(), opcode: spelled.to_owned() };
if opcode == aarch64::TEMPLATE {
return template(out, at, func, inst);
}
let Some(written) = aarch64::written(opcode) else {
return Err(refused());
};
if written.is_empty() {
return Ok(());
}
let operands = &func[data.operands];
let mut regs = Vec::with_capacity(operands.len());
for operand in operands {
let Some(phys) = operand.reg.phys() else {
return Err(Error::Virtual {
func: at.func_name.to_owned(),
opcode: spelled.to_owned(),
});
};
regs.push(phys.number());
}
let near = data.mem.map(|mem| &func[mem]).and_then(|amode| match amode {
Amode { base: None, index: None, disp: 0, block: Some(to), .. } => Some(label(*to)),
Amode { base: None, index: None, disp: 0, table: Some(at), .. } => Some(table(*at)),
_ => None,
});
let mem = match data.mem {
Some(_) if near.is_some() => None,
Some(mem) => Some(address(&func[mem], ®s).ok_or_else(refused)?),
None => None,
};
let with = Operands {
regs: ®s,
reads: defs(operands),
imm: data.imm.map_or(0, |imm| func[imm].0),
mem,
};
let branches = written.iter().any(|machine| machine.args.contains(&Arg::Label));
let symbol = if branches {
func[block].succs.first().map(|to| label(to.block))
} else if near.is_some() {
near
} else {
data.symbol.map(|symbol| format!("{}{}", at.symbol, at.names.resolve(symbol)))
};
for machine in written {
let mut values = Vec::with_capacity(machine.args.len());
for &arg in machine.args {
values.push(aarch64::fill(arg, &with).map_err(|_| refused())?);
}
if let Err(why) = aarch64::encode(machine.mnemonic, &values) {
return Err(Error::Encode {
func: at.func_name.to_owned(),
opcode: spelled.to_owned(),
why: why.to_string(),
});
}
let line = aarch64::write(machine.mnemonic, &values, symbol.as_deref(), at.spelling);
let _ = writeln!(out, "\t{line}");
}
Ok(())
}
fn template(out: &mut String, at: &Context<'_>, func: &Func, inst: Inst) -> Result<(), Error> {
let data = func[inst];
let spelled = at.names.resolve(data.opcode.name());
let Some(text) = data.symbol else {
return Err(Error::Opcode { func: at.func_name.to_owned(), opcode: spelled.to_owned() });
};
let operands = &func[data.operands];
if operands.iter().any(|operand| operand.reg.phys().is_none()) {
return Err(Error::Virtual { func: at.func_name.to_owned(), opcode: spelled.to_owned() });
}
let reg = |held: usize, width: char| match operands.get(held).and_then(|op| op.reg.phys()) {
Some(phys) => format!("{width}{}", phys.number()),
None => String::from("?"),
};
let filled =
template_filled(at.names.resolve(text), "", |name| format!("{}{name}", at.symbol), reg);
for line in filled.lines() {
let _ = writeln!(out, "\t{}", line.trim_start());
}
Ok(())
}
fn address(amode: &Amode, regs: &[u8]) -> Option<Addr> {
if amode.symbol.is_some()
|| amode.block.is_some()
|| amode.table.is_some()
|| amode.segment.is_some()
{
return None;
}
let base = *regs.get(usize::from(amode.base?))?;
let offset = match amode.index {
None => Offset::Imm(i64::from(amode.disp)),
Some(at) if amode.disp == 0 && amode.scale.is_power_of_two() => Offset::Reg {
reg: *regs.get(usize::from(at))?,
extend: Extend::Uxtx,
amount: (amode.scale > 1)
.then(|| u8::try_from(amode.scale.trailing_zeros()).unwrap_or(0)),
},
Some(_) => return None,
};
Some(Addr { base, offset, mode: Mode::Offset })
}