use alloc::vec;
use alloc::vec::Vec;
use core::fmt;
use super::encode::{encode, encode_form};
use super::error::EncodeError;
use super::inst::{Inst, Mnemonic, Op};
use super::nop::recommended_nop;
use super::operand::{Cond, Operand};
use super::table;
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Label(u32);
impl Label {
#[inline]
#[must_use]
pub const fn index(self) -> usize {
self.0 as usize
}
}
impl fmt::Debug for Label {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Label({})", self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AsmError {
Encode(EncodeError),
UnknownLabel(Label),
Rebound(Label),
Unbound(Label),
NoRipOperand,
TooLarge,
}
impl From<EncodeError> for AsmError {
fn from(e: EncodeError) -> AsmError {
AsmError::Encode(e)
}
}
impl fmt::Display for AsmError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AsmError::Encode(e) => fmt::Display::fmt(e, f),
AsmError::UnknownLabel(l) => write!(f, "label {} was not made by this assembler", l.0),
AsmError::Rebound(l) => write!(f, "label {} is already bound", l.0),
AsmError::Unbound(l) => write!(f, "label {} is used but never bound", l.0),
AsmError::NoRipOperand => {
f.write_str("the instruction has no rip-relative memory operand to resolve")
}
AsmError::TooLarge => write!(f, "code exceeds {} bytes", Assembler::MAX_CODE),
}
}
}
impl core::error::Error for AsmError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
AsmError::Encode(e) => Some(e),
_ => None,
}
}
}
#[derive(Clone, Copy)]
struct Anchor {
offset: u32,
branches: u32,
bound: bool,
}
#[derive(Clone, Copy)]
struct Branch {
at: u32,
label: u32,
cond: Option<Cond>,
}
#[derive(Clone, Copy)]
struct Patch {
field: u32,
end: u32,
branches: u32,
label: u32,
addend: i32,
}
const SHORT: u64 = 2;
const fn growth(cond: Option<Cond>) -> u64 {
if cond.is_some() { 4 } else { 3 }
}
const WINDOW: i64 = 256;
const JMP_REL8: u16 = table::find_form(Op::Jmp, 1, 0);
const JMP_REL32: u16 = table::find_form(Op::Jmp, 4, 0);
const JCC_REL8: u16 = table::find_form(Op::Jcc, 1, 0);
const JCC_REL32: u16 = table::find_form(Op::Jcc, 4, 1);
#[derive(Clone, Default)]
pub struct Assembler {
code: Vec<u8>,
branches: Vec<Branch>,
patches: Vec<Patch>,
labels: Vec<Anchor>,
}
impl fmt::Debug for Assembler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Assembler")
.field("fixed_bytes", &self.code.len())
.field("branches", &self.branches.len())
.field("patches", &self.patches.len())
.field("labels", &self.labels.len())
.finish()
}
}
impl Assembler {
pub const MAX_CODE: usize = i32::MAX as usize;
#[must_use]
pub fn new() -> Assembler {
Assembler::default()
}
#[must_use]
pub fn with_capacity(bytes: usize) -> Assembler {
Assembler {
code: Vec::with_capacity(bytes),
..Assembler::default()
}
}
pub fn label(&mut self) -> Label {
let id = u32::try_from(self.labels.len()).unwrap_or(u32::MAX);
if id != u32::MAX {
self.labels.push(Anchor {
offset: 0,
branches: 0,
bound: false,
});
}
Label(id)
}
pub fn bind(&mut self, label: Label) -> Result<(), AsmError> {
let offset = self.code.len() as u32;
let branches = self.branches.len() as u32;
let anchor = self
.labels
.get_mut(label.index())
.ok_or(AsmError::UnknownLabel(label))?;
if anchor.bound {
return Err(AsmError::Rebound(label));
}
*anchor = Anchor {
offset,
branches,
bound: true,
};
Ok(())
}
pub fn emit(&mut self, inst: &Inst) -> Result<(), AsmError> {
let encoded = encode(inst)?;
self.append(&encoded)
}
pub fn bytes(&mut self, data: &[u8]) -> Result<(), AsmError> {
self.append(data)
}
pub fn nops(&mut self, len: usize) -> Result<(), AsmError> {
self.reserve(len)?;
let mut left = len;
while left > 0 {
let step = left.min(9);
self.code
.extend_from_slice(recommended_nop(step).unwrap_or(&[]));
left -= step;
}
Ok(())
}
pub fn jmp(&mut self, label: Label) -> Result<(), AsmError> {
self.branch(label, None)
}
pub fn jcc(&mut self, cond: Cond, label: Label) -> Result<(), AsmError> {
self.branch(label, Some(cond))
}
pub fn call(&mut self, label: Label) -> Result<(), AsmError> {
self.check_label(label)?;
let encoded = encode(&Inst::new(Mnemonic::Call, [Operand::Rel(5)]))?;
let field = encoded.imm_at().unwrap_or(1);
self.patched(&encoded, field, label, 0)
}
pub fn emit_rip(&mut self, inst: &Inst, label: Label) -> Result<(), AsmError> {
self.check_label(label)?;
let addend = inst
.operands()
.iter()
.find_map(|op| match op {
Operand::Mem(m) if m.is_rip_relative() => Some(m.disp()),
_ => None,
})
.ok_or(AsmError::NoRipOperand)?;
let encoded = encode(inst)?;
let field = encoded.rip_disp_at().ok_or(AsmError::NoRipOperand)?;
self.patched(&encoded, field, label, addend)
}
pub fn finish(self) -> Result<Code, AsmError> {
let layout = Layout::relax(&self)?;
let mut out = Vec::with_capacity(layout.total);
let mut prev = 0usize;
for (i, b) in self.branches.iter().enumerate() {
let at = b.at as usize;
out.extend_from_slice(self.code.get(prev..at).unwrap_or(&[]));
let start = layout.branch_start(i, b.at);
let target = layout.label_pos(self.anchor(b.label)?);
let mnemonic = match b.cond {
Some(c) => Mnemonic::Jcc(c),
None => Mnemonic::Jmp,
};
let form = match (b.cond.is_some(), layout.long[i]) {
(false, false) => JMP_REL8,
(false, true) => JMP_REL32,
(true, false) => JCC_REL8,
(true, true) => JCC_REL32,
};
let inst = Inst::new(mnemonic, [Operand::Rel(target - start)]);
let form = table::form(form).ok_or(AsmError::TooLarge)?;
out.extend_from_slice(&encode_form(form, &inst)?);
prev = at;
}
out.extend_from_slice(self.code.get(prev..).unwrap_or(&[]));
for p in &self.patches {
let shift = layout.shift(p.branches);
let field = (i64::from(p.field) + shift) as usize;
let end = i64::from(p.end) + shift;
let disp = layout.label_pos(self.anchor(p.label)?) + i64::from(p.addend) - end;
let disp = i32::try_from(disp).map_err(|_| AsmError::TooLarge)?;
if let Some(slot) = out.get_mut(field..field + 4) {
slot.copy_from_slice(&disp.to_le_bytes());
}
}
let labels = self
.labels
.iter()
.map(|&a| {
if a.bound {
layout.label_pos(a) as u32
} else {
u32::MAX
}
})
.collect();
Ok(Code { bytes: out, labels })
}
fn anchor(&self, label: u32) -> Result<Anchor, AsmError> {
match self.labels.get(label as usize) {
Some(&a) if a.bound => Ok(a),
_ => Err(AsmError::Unbound(Label(label))),
}
}
fn check_label(&self, label: Label) -> Result<(), AsmError> {
if label.index() < self.labels.len() {
Ok(())
} else {
Err(AsmError::UnknownLabel(label))
}
}
fn reserve(&self, extra: usize) -> Result<(), AsmError> {
let worst = self
.code
.len()
.checked_add(extra)
.and_then(|n| n.checked_add(self.branches.len().checked_mul(6)?));
match worst {
Some(n) if n <= Assembler::MAX_CODE => Ok(()),
_ => Err(AsmError::TooLarge),
}
}
fn append(&mut self, bytes: &[u8]) -> Result<(), AsmError> {
self.reserve(bytes.len())?;
self.code.extend_from_slice(bytes);
Ok(())
}
fn branch(&mut self, label: Label, cond: Option<Cond>) -> Result<(), AsmError> {
self.check_label(label)?;
self.reserve(6)?;
self.branches.push(Branch {
at: self.code.len() as u32,
label: label.0,
cond,
});
Ok(())
}
fn patched(
&mut self,
bytes: &[u8],
field: usize,
label: Label,
addend: i32,
) -> Result<(), AsmError> {
let start = self.code.len();
self.append(bytes)?;
self.patches.push(Patch {
field: (start + field) as u32,
end: (start + bytes.len()) as u32,
branches: self.branches.len() as u32,
label: label.0,
addend,
});
Ok(())
}
}
struct Layout {
long: Vec<bool>,
growth: Fenwick,
total: usize,
}
impl Layout {
fn relax(asm: &Assembler) -> Result<Layout, AsmError> {
let unbound = |label: u32| match asm.labels.get(label as usize) {
Some(a) if a.bound => None,
_ => Some(AsmError::Unbound(Label(label))),
};
for b in &asm.branches {
if let Some(e) = unbound(b.label) {
return Err(e);
}
}
for p in &asm.patches {
if let Some(e) = unbound(p.label) {
return Err(e);
}
}
let n = asm.branches.len();
let mut layout = Layout {
long: vec![false; n],
growth: Fenwick::new(n),
total: 0,
};
let mut work = Worklist {
queued: vec![false; n],
stack: Vec::new(),
};
for (i, b) in asm.branches.iter().enumerate() {
if !layout.reaches(asm, i, b) {
work.push(i);
}
}
while let Some(i) = work.stack.pop() {
let Some(&b) = asm.branches.get(i) else {
continue;
};
layout.long[i] = true;
layout.growth.add(i, growth(b.cond));
let here = layout.branch_start(i, b.at);
let before = asm
.branches
.get(..i)
.unwrap_or(&[])
.iter()
.enumerate()
.rev();
for (j, bj) in before {
if layout.branch_start(j, bj.at) < here - WINDOW {
break;
}
if !work.is_queued(j) && !layout.reaches(asm, j, bj) {
work.push(j);
}
}
let after = asm.branches.iter().enumerate().skip(i + 1);
for (j, bj) in after {
if layout.branch_start(j, bj.at) > here + WINDOW {
break;
}
if !work.is_queued(j) && !layout.reaches(asm, j, bj) {
work.push(j);
}
}
}
let grown = layout.growth.prefix(n);
layout.total = asm.code.len() + (SHORT as usize) * n + grown as usize;
Ok(layout)
}
fn shift(&self, branches: u32) -> i64 {
(SHORT * u64::from(branches) + self.growth.prefix(branches as usize)) as i64
}
fn label_pos(&self, a: Anchor) -> i64 {
i64::from(a.offset) + self.shift(a.branches)
}
fn branch_start(&self, i: usize, at: u32) -> i64 {
i64::from(at) + self.shift(i as u32)
}
fn reaches(&self, asm: &Assembler, i: usize, b: &Branch) -> bool {
let Some(&anchor) = asm.labels.get(b.label as usize) else {
return false;
};
let end = self.branch_start(i, b.at) + SHORT as i64;
let disp = self.label_pos(anchor) - end;
(-128..=127).contains(&disp)
}
}
struct Worklist {
queued: Vec<bool>,
stack: Vec<usize>,
}
impl Worklist {
fn is_queued(&self, i: usize) -> bool {
self.queued.get(i).copied().unwrap_or(true)
}
fn push(&mut self, i: usize) {
if let Some(q) = self.queued.get_mut(i) {
*q = true;
self.stack.push(i);
}
}
}
struct Fenwick {
tree: Vec<u64>,
}
impl Fenwick {
fn new(n: usize) -> Fenwick {
Fenwick { tree: vec![0; n] }
}
fn add(&mut self, index: usize, value: u64) {
let mut i = index + 1;
while let Some(slot) = self.tree.get_mut(i - 1) {
*slot += value;
i += i & i.wrapping_neg();
}
}
fn prefix(&self, end: usize) -> u64 {
let mut i = end.min(self.tree.len());
let mut sum = 0;
while i > 0 {
sum += self.tree.get(i - 1).copied().unwrap_or(0);
i &= i - 1;
}
sum
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct Code {
bytes: Vec<u8>,
labels: Vec<u32>,
}
impl Code {
#[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 label_offset(&self, label: Label) -> Option<usize> {
match self.labels.get(label.index()) {
Some(&off) if off != u32::MAX => Some(off as usize),
_ => None,
}
}
}
impl fmt::Debug for Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Code")
.field("len", &self.bytes.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::x86_64::Reg;
#[test]
fn test_fenwick_prefix_sums() {
let mut f = Fenwick::new(10);
for i in 0..10 {
f.add(i, i as u64 + 1);
}
for end in 0..=10 {
let expect: u64 = (1..=end as u64).sum();
assert_eq!(f.prefix(end), expect);
}
assert_eq!(f.prefix(100), 55);
let empty = Fenwick::new(0);
assert_eq!(empty.prefix(5), 0);
}
#[test]
fn test_branch_forms_are_the_table_rows() {
let syntax = |id| table::form(id).map(|f| f.syntax());
assert_eq!(syntax(JMP_REL8), Some("JMP rel8"));
assert_eq!(syntax(JMP_REL32), Some("JMP rel32"));
assert_eq!(syntax(JCC_REL8), Some("Jcc rel8"));
assert_eq!(syntax(JCC_REL32), Some("Jcc rel32"));
}
#[test]
fn test_unknown_label_from_another_assembler() {
let mut other = Assembler::new();
let _ = other.label();
let foreign = other.label();
let mut asm = Assembler::new();
assert_eq!(asm.jmp(foreign), Err(AsmError::UnknownLabel(foreign)));
assert_eq!(asm.bind(foreign), Err(AsmError::UnknownLabel(foreign)));
assert_eq!(asm.call(foreign), Err(AsmError::UnknownLabel(foreign)));
}
#[test]
fn test_emit_rip_requires_rip_operand() {
let mut asm = Assembler::new();
let l = asm.label();
let inst = Inst::new(Mnemonic::Mov, [Reg::RAX.into(), Reg::RBX.into()]);
assert_eq!(asm.emit_rip(&inst, l), Err(AsmError::NoRipOperand));
}
#[test]
fn test_boundary_127_and_128() {
for (gap, expect_len) in [(127usize, 2usize), (128, 5)] {
let mut asm = Assembler::new();
let l = asm.label();
assert!(asm.jmp(l).is_ok());
assert!(asm.bytes(&vec![0x90; gap]).is_ok());
assert!(asm.bind(l).is_ok());
let code = asm.finish();
assert_eq!(code.map(|c| c.bytes().len() - gap), Ok(expect_len));
}
}
}