use crate::branch::{BranchInsts, Fusion, Move};
use crate::frame::{ClassMoves, FrameInsts, Thunks};
use crate::machine::MachineInsts;
use crate::operand::OperandDesc;
use crate::regs::{
CallRegs, Chkstk, ClassInfo, Conventions, Guard, PhysReg, RegClass, RegFile, Segment, Trace,
};
use crate::x86_64::{Form, Kind, Mode, encoding_in, form, written};
use rucc_abi::Convention;
pub const GPR: RegClass = RegClass::new(0);
pub const XMM: RegClass = RegClass::new(1);
pub const X87: RegClass = RegClass::new(2);
pub const EAX: PhysReg = PhysReg::new(0);
pub const ECX: PhysReg = PhysReg::new(1);
pub const EDX: PhysReg = PhysReg::new(2);
pub const EBX: PhysReg = PhysReg::new(3);
pub const ESP: PhysReg = PhysReg::new(4);
pub const EBP: PhysReg = PhysReg::new(5);
pub const ESI: PhysReg = PhysReg::new(6);
pub const EDI: PhysReg = PhysReg::new(7);
pub const GOT_BASE: PhysReg = EBX;
#[must_use]
pub const fn xmm(number: u8) -> PhysReg {
assert!(number < 8, "i386 has eight vector registers");
PhysReg::new(number)
}
#[must_use]
pub const fn st(number: u8) -> PhysReg {
assert!(number < 8, "the x87 stack is eight deep");
PhysReg::new(number)
}
static GPR_NAMES: [&str; 8] = ["eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi"];
static XMM_NAMES: [&str; 8] = ["xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7"];
static X87_NAMES: [&str; 8] = ["st0", "st1", "st2", "st3", "st4", "st5", "st6", "st7"];
static CLASSES: [ClassInfo; 3] = [
ClassInfo { name: "gpr", bits: 32, regs: &GPR_NAMES, allocatable: true },
ClassInfo { name: "xmm", bits: 128, regs: &XMM_NAMES, allocatable: true },
ClassInfo { name: "x87", bits: 80, regs: &X87_NAMES, allocatable: false },
];
pub static REGS: RegFile = RegFile::new(&CLASSES);
static GPR_DWARF: [u16; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
static XMM_DWARF: [u16; 8] = [21, 22, 23, 24, 25, 26, 27, 28];
static X86_DWARF: [&[u16]; 2] = [&GPR_DWARF, &XMM_DWARF];
pub const DWARF_RETURN_ADDRESS: u16 = 8;
static SYSV_INT_ARGS: [PhysReg; 0] = [];
static SYSV_SSE_ARGS: [PhysReg; 0] = [];
static SYSV_INT_RETURNS: [PhysReg; 2] = [EAX, EDX];
static SYSV_SSE_RETURNS: [PhysReg; 0] = [];
static SYSV_X87_RETURNS: [PhysReg; 1] = [st(0)];
static SYSV_INT_SAVED: [PhysReg; 4] = [EBX, ESI, EDI, EBP];
static SYSV_SSE_SAVED: [PhysReg; 0] = [];
static SYSV_INT_ORDER: [PhysReg; 6] = [EAX, ECX, EDX, ESI, EDI, EBX];
static SYSV_PIC_INT_ORDER: [PhysReg; 5] = [EAX, ECX, EDX, ESI, EDI];
static SSE_ORDER: [PhysReg; 8] = [xmm(0), xmm(1), xmm(2), xmm(3), xmm(4), xmm(5), xmm(6), xmm(7)];
pub static SYSV: CallRegs = CallRegs {
abi: &rucc_abi::abis::I386_SYSV,
int_class: GPR,
sse_class: XMM,
int_args: &SYSV_INT_ARGS,
sse_args: &SYSV_SSE_ARGS,
shared_positions: false,
int_returns: &SYSV_INT_RETURNS,
sse_returns: &SYSV_SSE_RETURNS,
x87_returns: &SYSV_X87_RETURNS,
int_saved: &SYSV_INT_SAVED,
sse_saved: &SYSV_SSE_SAVED,
sse_kept: None,
int_order: &SYSV_INT_ORDER,
sse_order: &SSE_ORDER,
stack_pointer: ESP,
frame_pointer: EBP,
late_frame_pointer: false,
unwind_codes: false,
vector_count: None,
red_zone: 0,
shadow: 0,
home: 0,
stack_align: 16,
trusted_align: 16,
return_address: 4,
word: 4,
total_store_order: true,
unaligned: true,
byte_swaps: &[16, 32],
push: 4,
link: None,
sret: None,
chain: None,
list: crate::VaList::CharPointer,
dwarf: &X86_DWARF,
dwarf_return_address: DWARF_RETURN_ADDRESS,
guard: Some(Guard::in_segment(Segment::Gs, 20)),
trace: Some(Trace { early: "__fentry__", late: "mcount", fentry: false }),
chkstk: None,
conventions: Conventions(&OWN_0),
};
static REGPARM_1_ARGS: [PhysReg; 1] = [EAX];
static REGPARM_2_ARGS: [PhysReg; 2] = [EAX, EDX];
static REGPARM_3_ARGS: [PhysReg; 3] = [EAX, EDX, ECX];
macro_rules! regparm {
($base:ident, 0, $conventions:ident) => {
CallRegs { conventions: Conventions(&$conventions), ..$base }
};
($base:ident, $abis:ident, $n:literal, $args:ident, $conventions:ident) => {
CallRegs {
abi: &rucc_abi::abis::$abis[$n - 1],
int_args: &$args,
conventions: Conventions(&$conventions),
..$base
}
};
}
macro_rules! family {
($regs:ident, $own:ident, $foreign:ident, $default:expr, $stdcall:ident, $fastcall:ident) => {
static $own: [(Convention, &CallRegs); 6] = [
(Convention::Regparm(0), &$regs[0]),
(Convention::Regparm(1), &$regs[1]),
(Convention::Regparm(2), &$regs[2]),
(Convention::Regparm(3), &$regs[3]),
(Convention::Stdcall, &$stdcall),
(Convention::Fastcall, &$fastcall),
];
static $foreign: [(Convention, &CallRegs); 7] = [
(Convention::Target, $default),
(Convention::Regparm(0), &$regs[0]),
(Convention::Regparm(1), &$regs[1]),
(Convention::Regparm(2), &$regs[2]),
(Convention::Regparm(3), &$regs[3]),
(Convention::Stdcall, &$stdcall),
(Convention::Fastcall, &$fastcall),
];
};
}
macro_rules! pops {
($base:ident, $abi:expr, $args:ident, $conventions:ident) => {
CallRegs { abi: &$abi, int_args: &$args, conventions: Conventions(&$conventions), ..$base }
};
}
static REGPARM_0: [&CallRegs; 4] = [&SYSV, ®PARM_0_1, ®PARM_0_2, ®PARM_0_3];
static REGPARM_0_1: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 1, REGPARM_1_ARGS, FOREIGN_0);
static REGPARM_0_2: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 2, REGPARM_2_ARGS, FOREIGN_0);
static REGPARM_0_3: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 3, REGPARM_3_ARGS, FOREIGN_0);
static REGPARM_0_STDCALL: CallRegs =
pops!(SYSV, rucc_abi::abis::I386_SYSV_STDCALL, SYSV_INT_ARGS, FOREIGN_0);
static REGPARM_0_FASTCALL: CallRegs =
pops!(SYSV, rucc_abi::abis::I386_SYSV_FASTCALL, FASTCALL_INT_ARGS, FOREIGN_0);
family!(REGPARM_0, OWN_0, FOREIGN_0, &SYSV, REGPARM_0_STDCALL, REGPARM_0_FASTCALL);
static REGPARM_1: [&CallRegs; 4] = [®PARM_1_0, ®PARM_1_1, ®PARM_1_2, ®PARM_1_3];
static REGPARM_1_0: CallRegs = regparm!(SYSV, 0, FOREIGN_1);
static REGPARM_1_1: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 1, REGPARM_1_ARGS, OWN_1);
static REGPARM_1_2: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 2, REGPARM_2_ARGS, FOREIGN_1);
static REGPARM_1_3: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 3, REGPARM_3_ARGS, FOREIGN_1);
static REGPARM_1_STDCALL: CallRegs =
pops!(SYSV, rucc_abi::abis::I386_SYSV_REGPARM_STDCALL[0], REGPARM_1_ARGS, FOREIGN_1);
static REGPARM_1_FASTCALL: CallRegs =
pops!(SYSV, rucc_abi::abis::I386_SYSV_FASTCALL, FASTCALL_INT_ARGS, FOREIGN_1);
family!(REGPARM_1, OWN_1, FOREIGN_1, ®PARM_1_1, REGPARM_1_STDCALL, REGPARM_1_FASTCALL);
static REGPARM_2: [&CallRegs; 4] = [®PARM_2_0, ®PARM_2_1, ®PARM_2_2, ®PARM_2_3];
static REGPARM_2_0: CallRegs = regparm!(SYSV, 0, FOREIGN_2);
static REGPARM_2_1: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 1, REGPARM_1_ARGS, FOREIGN_2);
static REGPARM_2_2: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 2, REGPARM_2_ARGS, OWN_2);
static REGPARM_2_3: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 3, REGPARM_3_ARGS, FOREIGN_2);
static REGPARM_2_STDCALL: CallRegs =
pops!(SYSV, rucc_abi::abis::I386_SYSV_REGPARM_STDCALL[1], REGPARM_2_ARGS, FOREIGN_2);
static REGPARM_2_FASTCALL: CallRegs =
pops!(SYSV, rucc_abi::abis::I386_SYSV_FASTCALL, FASTCALL_INT_ARGS, FOREIGN_2);
family!(REGPARM_2, OWN_2, FOREIGN_2, ®PARM_2_2, REGPARM_2_STDCALL, REGPARM_2_FASTCALL);
static REGPARM_3: [&CallRegs; 4] = [®PARM_3_0, ®PARM_3_1, ®PARM_3_2, ®PARM_3_3];
static REGPARM_3_0: CallRegs = regparm!(SYSV, 0, FOREIGN_3);
static REGPARM_3_1: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 1, REGPARM_1_ARGS, FOREIGN_3);
static REGPARM_3_2: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 2, REGPARM_2_ARGS, FOREIGN_3);
static REGPARM_3_3: CallRegs = regparm!(SYSV, I386_SYSV_REGPARM, 3, REGPARM_3_ARGS, OWN_3);
static REGPARM_3_STDCALL: CallRegs =
pops!(SYSV, rucc_abi::abis::I386_SYSV_REGPARM_STDCALL[2], REGPARM_3_ARGS, FOREIGN_3);
static REGPARM_3_FASTCALL: CallRegs =
pops!(SYSV, rucc_abi::abis::I386_SYSV_FASTCALL, FASTCALL_INT_ARGS, FOREIGN_3);
family!(REGPARM_3, OWN_3, FOREIGN_3, ®PARM_3_3, REGPARM_3_STDCALL, REGPARM_3_FASTCALL);
pub static SYSV_REG: CallRegs = CallRegs {
abi: &rucc_abi::abis::I386_SYSV_REG_STRUCT,
conventions: Conventions(®_OWN_0),
..SYSV
};
static REG_STRUCT_0: [&CallRegs; 4] =
[&SYSV_REG, ®_STRUCT_0_1, ®_STRUCT_0_2, ®_STRUCT_0_3];
static REG_STRUCT_0_1: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 1, REGPARM_1_ARGS, REG_FOREIGN_0);
static REG_STRUCT_0_2: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 2, REGPARM_2_ARGS, REG_FOREIGN_0);
static REG_STRUCT_0_3: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 3, REGPARM_3_ARGS, REG_FOREIGN_0);
static REG_STRUCT_0_STDCALL: CallRegs =
pops!(SYSV_REG, rucc_abi::abis::I386_SYSV_REG_STRUCT_STDCALL, SYSV_INT_ARGS, REG_FOREIGN_0);
static REG_STRUCT_0_FASTCALL: CallRegs = pops!(
SYSV_REG,
rucc_abi::abis::I386_SYSV_REG_STRUCT_FASTCALL,
FASTCALL_INT_ARGS,
REG_FOREIGN_0
);
family!(
REG_STRUCT_0,
REG_OWN_0,
REG_FOREIGN_0,
&SYSV_REG,
REG_STRUCT_0_STDCALL,
REG_STRUCT_0_FASTCALL
);
static REG_STRUCT_1: [&CallRegs; 4] =
[®_STRUCT_1_0, ®_STRUCT_1_1, ®_STRUCT_1_2, ®_STRUCT_1_3];
static REG_STRUCT_1_0: CallRegs = regparm!(SYSV_REG, 0, REG_FOREIGN_1);
static REG_STRUCT_1_1: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 1, REGPARM_1_ARGS, REG_OWN_1);
static REG_STRUCT_1_2: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 2, REGPARM_2_ARGS, REG_FOREIGN_1);
static REG_STRUCT_1_3: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 3, REGPARM_3_ARGS, REG_FOREIGN_1);
static REG_STRUCT_1_STDCALL: CallRegs = pops!(
SYSV_REG,
rucc_abi::abis::I386_SYSV_REGPARM_REG_STRUCT_STDCALL[0],
REGPARM_1_ARGS,
REG_FOREIGN_1
);
static REG_STRUCT_1_FASTCALL: CallRegs = pops!(
SYSV_REG,
rucc_abi::abis::I386_SYSV_REG_STRUCT_FASTCALL,
FASTCALL_INT_ARGS,
REG_FOREIGN_1
);
family!(
REG_STRUCT_1,
REG_OWN_1,
REG_FOREIGN_1,
®_STRUCT_1_1,
REG_STRUCT_1_STDCALL,
REG_STRUCT_1_FASTCALL
);
static REG_STRUCT_2: [&CallRegs; 4] =
[®_STRUCT_2_0, ®_STRUCT_2_1, ®_STRUCT_2_2, ®_STRUCT_2_3];
static REG_STRUCT_2_0: CallRegs = regparm!(SYSV_REG, 0, REG_FOREIGN_2);
static REG_STRUCT_2_1: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 1, REGPARM_1_ARGS, REG_FOREIGN_2);
static REG_STRUCT_2_2: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 2, REGPARM_2_ARGS, REG_OWN_2);
static REG_STRUCT_2_3: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 3, REGPARM_3_ARGS, REG_FOREIGN_2);
static REG_STRUCT_2_STDCALL: CallRegs = pops!(
SYSV_REG,
rucc_abi::abis::I386_SYSV_REGPARM_REG_STRUCT_STDCALL[1],
REGPARM_2_ARGS,
REG_FOREIGN_2
);
static REG_STRUCT_2_FASTCALL: CallRegs = pops!(
SYSV_REG,
rucc_abi::abis::I386_SYSV_REG_STRUCT_FASTCALL,
FASTCALL_INT_ARGS,
REG_FOREIGN_2
);
family!(
REG_STRUCT_2,
REG_OWN_2,
REG_FOREIGN_2,
®_STRUCT_2_2,
REG_STRUCT_2_STDCALL,
REG_STRUCT_2_FASTCALL
);
static REG_STRUCT_3: [&CallRegs; 4] =
[®_STRUCT_3_0, ®_STRUCT_3_1, ®_STRUCT_3_2, ®_STRUCT_3_3];
static REG_STRUCT_3_0: CallRegs = regparm!(SYSV_REG, 0, REG_FOREIGN_3);
static REG_STRUCT_3_1: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 1, REGPARM_1_ARGS, REG_FOREIGN_3);
static REG_STRUCT_3_2: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 2, REGPARM_2_ARGS, REG_FOREIGN_3);
static REG_STRUCT_3_3: CallRegs =
regparm!(SYSV_REG, I386_SYSV_REGPARM_REG_STRUCT, 3, REGPARM_3_ARGS, REG_OWN_3);
static REG_STRUCT_3_STDCALL: CallRegs = pops!(
SYSV_REG,
rucc_abi::abis::I386_SYSV_REGPARM_REG_STRUCT_STDCALL[2],
REGPARM_3_ARGS,
REG_FOREIGN_3
);
static REG_STRUCT_3_FASTCALL: CallRegs = pops!(
SYSV_REG,
rucc_abi::abis::I386_SYSV_REG_STRUCT_FASTCALL,
FASTCALL_INT_ARGS,
REG_FOREIGN_3
);
family!(
REG_STRUCT_3,
REG_OWN_3,
REG_FOREIGN_3,
®_STRUCT_3_3,
REG_STRUCT_3_STDCALL,
REG_STRUCT_3_FASTCALL
);
#[must_use]
pub fn regparm(registers: u8, struct_in_registers: bool) -> Option<&'static CallRegs> {
let family = match (registers, struct_in_registers) {
(0, false) => ®PARM_0,
(1, false) => ®PARM_1,
(2, false) => ®PARM_2,
(3, false) => ®PARM_3,
(0, true) => ®_STRUCT_0,
(1, true) => ®_STRUCT_1,
(2, true) => ®_STRUCT_2,
(3, true) => ®_STRUCT_3,
_ => return None,
};
Some(family[usize::from(registers)])
}
pub static SYSV_PIC: CallRegs = CallRegs { int_order: &SYSV_PIC_INT_ORDER, ..SYSV };
pub static MINGW32: CallRegs = CallRegs {
abi: &rucc_abi::abis::I386_MINGW,
guard: None,
trace: None,
chkstk: Some(Chkstk { name: "__chkstk_ms", size: EAX, shift: 0, moves: false }),
conventions: Conventions(&MINGW32_CONVENTIONS),
trusted_align: 8,
..SYSV
};
static FASTCALL_INT_ARGS: [PhysReg; 2] = [ECX, EDX];
pub static MINGW32_STDCALL: CallRegs =
CallRegs { abi: &rucc_abi::abis::I386_MINGW_STDCALL, ..MINGW32 };
pub static MINGW32_FASTCALL: CallRegs =
CallRegs { abi: &rucc_abi::abis::I386_MINGW_FASTCALL, int_args: &FASTCALL_INT_ARGS, ..MINGW32 };
static MINGW32_CONVENTIONS: [(Convention, &CallRegs); 3] = [
(Convention::Target, &MINGW32),
(Convention::Stdcall, &MINGW32_STDCALL),
(Convention::Fastcall, &MINGW32_FASTCALL),
];
pub static MSVC32: CallRegs = CallRegs {
abi: &rucc_abi::abis::I386_MSVC,
chkstk: Some(Chkstk { name: "_chkstk", size: EAX, shift: 0, moves: true }),
conventions: Conventions(&MSVC32_CONVENTIONS),
..MINGW32
};
pub static MSVC32_STDCALL: CallRegs =
CallRegs { abi: &rucc_abi::abis::I386_MSVC_STDCALL, ..MSVC32 };
pub static MSVC32_FASTCALL: CallRegs =
CallRegs { abi: &rucc_abi::abis::I386_MSVC_FASTCALL, int_args: &FASTCALL_INT_ARGS, ..MSVC32 };
static MSVC32_CONVENTIONS: [(Convention, &CallRegs); 3] = [
(Convention::Target, &MSVC32),
(Convention::Stdcall, &MSVC32_STDCALL),
(Convention::Fastcall, &MSVC32_FASTCALL),
];
static X86_MOVES: [ClassMoves; 2] = [
ClassMoves { mov: "mov_rr_32", load: "mov_rm_32", store: "mov_mr_32", narrow: &[] },
ClassMoves { mov: "movaps_rr", load: "movaps_rm", store: "movaps_mr", narrow: &[] },
];
pub static FRAME: FrameInsts = FrameInsts {
prefix: "x64.",
classes: &X86_MOVES,
push: "push_32",
pop: "pop_32",
pair: None,
kept: None,
add: "add_ri_32",
sub: "sub_ri_32",
grow: "sub_rr_32",
scaled: None,
insert: None,
align: "and_ri_32",
imm: "mov_ri_32",
lea: "lea_32",
sum: "add_rr_32",
ret: "ret",
ret_pop: Some("ret_pop"),
iret: None,
clear_direction: None,
differ: "cmp_set_ne_32",
above: "cmp_set_a_32",
away: Some("jmp_away"),
call: "call",
probe: Some(crate::x86_64::PROBE),
landing: Some("endbr32"),
pad: Some("nop"),
step_bits: None,
reaches: None,
thunks: Some(THUNKS),
};
pub static THUNKS: Thunks = Thunks { regs: &GPR_NAMES, padded_from: 8, ..crate::x86_64::THUNKS };
pub static MACHINE: MachineInsts = MachineInsts {
prefix: "x64.",
operands: machine_operands,
takes_imm: machine_takes_imm,
takes_mem: machine_takes_mem,
touches_mem: machine_touches_mem,
calls: machine_calls,
commutes: machine_commutes,
scales: &[1, 2, 4, 8],
index_and_disp: true,
};
#[must_use]
pub fn form_here(name: &str) -> Option<Form> {
let found = form(name)?;
let insts = written(name)?;
insts
.iter()
.all(|inst| {
let args: Vec<Kind> = inst.args.iter().map(|&arg| Kind::of(arg)).collect();
encoding_in(Mode::Bits32, inst.mnemonic, &args, 0).is_some_and(|row| !row.size.wide())
})
.then_some(found)
}
#[must_use]
fn machine_operands(name: &str) -> Option<&'static [OperandDesc]> {
form_here(name).map(Form::operands)
}
#[must_use]
fn machine_takes_imm(name: &str) -> bool {
form_here(name).is_some_and(Form::takes_imm)
}
#[must_use]
fn machine_takes_mem(name: &str) -> bool {
form_here(name).is_some_and(Form::takes_mem)
}
#[must_use]
fn machine_touches_mem(name: &str) -> bool {
form_here(name).is_some_and(Form::touches_mem)
}
#[must_use]
fn machine_calls(name: &str) -> bool {
form_here(name) == Some(Form::Call)
}
#[must_use]
fn machine_commutes(name: &str) -> bool {
form_here(name).is_some() && (crate::x86_64::MACHINE.commutes)(name)
}
pub static BRANCH: BranchInsts =
BranchInsts { fused: &FUSED, moves: &MOVES, ..crate::x86_64::BRANCH };
const fn is_wide(name: &str) -> bool {
let bytes = name.as_bytes();
let n = bytes.len();
n >= 3 && bytes[n - 3] == b'_' && bytes[n - 2] == b'6' && bytes[n - 1] == b'4'
}
const fn narrow_fused<const N: usize>(all: &[Fusion]) -> [Fusion; N] {
let mut out = [all[0]; N];
let (mut at, mut kept) = (0, 0);
while at < all.len() {
if !is_wide(all[at].set) {
out[kept] = all[at];
kept += 1;
}
at += 1;
}
assert!(kept == N, "the count of narrow comparisons");
out
}
const fn narrow_moves<const N: usize>(all: &[Move]) -> [Move; N] {
let mut out = [all[0]; N];
let (mut at, mut kept) = (0, 0);
while at < all.len() {
if !is_wide(all[at].select) {
out[kept] = all[at];
kept += 1;
}
at += 1;
}
assert!(kept == N, "the count of narrow selects");
out
}
static FUSED: [Fusion; 120] = narrow_fused(&crate::x86_64::FUSED);
static MOVES: [Move; 30] = narrow_moves(&crate::x86_64::MOVES);
#[cfg(test)]
mod tests {
use super::*;
fn covers(order: &[PhysReg], count: usize) -> bool {
let mut seen: Vec<u8> = order.iter().map(|reg| reg.number()).collect();
seen.sort_unstable();
seen.dedup();
seen.len() == order.len() && order.len() == count
}
#[test]
fn the_file_numbers_registers_the_way_the_encoding_does() {
assert_eq!(REGS.name(GPR, EAX), Some("eax"));
assert_eq!(REGS.name(GPR, ESP), Some("esp"));
assert_eq!(REGS.name(GPR, EDI), Some("edi"));
assert_eq!(REGS.reg_named("ebx"), Some((GPR, EBX)));
assert_eq!(REGS.reg_named("xmm7"), Some((XMM, xmm(7))));
assert_eq!(REGS.reg_named("st0"), Some((X87, st(0))));
assert_eq!(REGS.reg_named("rax"), None, "there is no sixty four bit register here");
assert_eq!(REGS.reg_named("xmm8"), None);
}
#[test]
fn the_file_gives_no_name_to_two_registers() {
assert_eq!(REGS.duplicate(), None);
assert_eq!(REGS.len(GPR), 8);
assert_eq!(REGS.len(XMM), 8);
assert_eq!(REGS.len(X87), 8);
assert!(REGS.allocatable(GPR) && REGS.allocatable(XMM) && !REGS.allocatable(X87));
}
#[test]
fn dwarf_numbers_the_registers_the_way_the_i386_psabi_does() {
for (reg, number) in [(EAX, 0), (ECX, 1), (EDX, 2), (EBX, 3)] {
assert_eq!(SYSV.dwarf(GPR, reg), Some(number));
}
for (reg, number) in [(ESP, 4), (EBP, 5), (ESI, 6), (EDI, 7)] {
assert_eq!(SYSV.dwarf(GPR, reg), Some(number));
}
assert_eq!(SYSV.dwarf_return_address, 8);
assert_eq!(SYSV.dwarf(XMM, xmm(0)), Some(21));
assert_eq!(SYSV.dwarf(XMM, xmm(7)), Some(28));
assert_eq!(SYSV.dwarf(X87, st(0)), None);
}
#[test]
fn a_dwarf_number_leads_back_to_the_register_it_was_given_to() {
for reg in [EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI] {
let number = SYSV.dwarf(GPR, reg).expect("a general purpose register has a column");
assert_eq!(SYSV.machine(GPR, number), Some(reg));
}
assert_eq!(SYSV.machine(XMM, 21), Some(xmm(0)));
assert_eq!(SYSV.machine(GPR, 8), None, "the return address is not a register here");
}
#[test]
fn cdecl_passes_nothing_in_registers_and_returns_in_three_places() {
assert!(SYSV.int_args.is_empty() && SYSV.sse_args.is_empty());
assert_eq!(SYSV.int_returns, &[EAX, EDX]);
assert!(SYSV.sse_returns.is_empty(), "a double comes back on the x87 stack");
assert_eq!(SYSV.x87_returns, &[st(0)]);
assert_eq!((SYSV.word, SYSV.push, SYSV.return_address), (4, 4, 4));
assert_eq!((SYSV.red_zone, SYSV.shadow, SYSV.vector_count), (0, 0, None));
assert!(std::ptr::eq(SYSV.abi, &rucc_abi::abis::I386_SYSV));
}
#[test]
fn only_an_abi_whose_callee_pops_the_return_address_slot_says_so() {
assert_eq!(SYSV.return_pointer_popped(), 4);
let callers_pop = CallRegs { abi: &rucc_abi::abis::WIN64, ..SYSV };
assert_eq!(callers_pop.return_pointer_popped(), 0, "a plain ret where the caller pops");
}
#[test]
fn windows_is_cdecl_over_the_same_registers_with_its_own_abi_and_probe() {
for (regs, abi) in
[(&MINGW32, &rucc_abi::abis::I386_MINGW), (&MSVC32, &rucc_abi::abis::I386_MSVC)]
{
assert!(std::ptr::eq(regs.abi, abi), "{}", abi.name);
assert_eq!(regs.return_pointer_popped(), 0, "{}: the caller pops it", abi.name);
assert!(regs.int_args.is_empty() && regs.sse_args.is_empty(), "{}", abi.name);
assert_eq!(regs.int_returns, SYSV.int_returns, "{}", abi.name);
assert_eq!(regs.x87_returns, SYSV.x87_returns, "{}", abi.name);
assert_eq!(regs.int_saved, SYSV.int_saved, "{}", abi.name);
assert_eq!(regs.stack_align, 16, "{}", abi.name);
assert_eq!(regs.trusted_align, 8, "{}: Windows promises four", abi.name);
assert!(regs.guard.is_none() && regs.trace.is_none(), "{}", abi.name);
}
let mingw = MINGW32.chkstk.expect("mingw's routine");
assert_eq!((mingw.name, mingw.size, mingw.moves), ("__chkstk_ms", EAX, false));
let msvc = MSVC32.chkstk.expect("Microsoft's routine");
assert_eq!((msvc.name, msvc.size, msvc.moves), ("_chkstk", EAX, true));
}
#[test]
fn a_stack_boundary_moves_what_a_function_counts_on_only_where_it_matched() {
assert_eq!(SYSV.aligned_to(32).trusted_align, 32, "a caller is held to the new boundary");
assert_eq!(SYSV.aligned_to(4).trusted_align, 4);
for regs in [&MINGW32, &MINGW32_STDCALL, &MINGW32_FASTCALL, &MSVC32, &MSVC32_STDCALL] {
assert_eq!(regs.trusted_align, 8, "{}", regs.abi.name);
assert_eq!(
regs.aligned_to(32).trusted_align,
8,
"{}: Windows still promises four",
regs.abi.name
);
assert_eq!(regs.aligned_to(4).trusted_align, 4, "{}", regs.abi.name);
}
}
#[test]
fn windows_has_stdcall_and_fastcall_and_only_fastcall_has_registers() {
for (regs, stdcall, fastcall) in [
(&MINGW32, &MINGW32_STDCALL, &MINGW32_FASTCALL),
(&MSVC32, &MSVC32_STDCALL, &MSVC32_FASTCALL),
] {
assert!(std::ptr::eq(regs.under(Convention::Target).expect("cdecl"), regs));
let under_stdcall = regs.under(Convention::Stdcall).expect("stdcall");
let under_fastcall = regs.under(Convention::Fastcall).expect("fastcall");
assert!(std::ptr::eq(under_stdcall, stdcall));
assert!(std::ptr::eq(under_fastcall, fastcall));
assert!(std::ptr::eq(stdcall.under(Convention::Target).expect("cdecl"), regs));
assert!(stdcall.int_args.is_empty());
assert_eq!(fastcall.int_args, [ECX, EDX]);
assert_eq!(stdcall.abi.cleanup, rucc_abi::Cleanup::Callee);
assert_eq!(fastcall.abi.cleanup, rucc_abi::Cleanup::Callee);
assert_eq!(regs.abi.cleanup, rucc_abi::Cleanup::Caller);
assert_eq!(fastcall.chkstk, regs.chkstk);
}
}
#[test]
fn linux_has_stdcall_and_fastcall_too_and_stdcall_keeps_the_units_registers() {
for reg_struct in [false, true] {
for own in 0..=3u8 {
let unit = regparm(own, reg_struct).expect("0 to 3 are counts");
let stdcall = unit.under(Convention::Stdcall).expect("stdcall");
let fastcall = unit.under(Convention::Fastcall).expect("fastcall");
assert_eq!(stdcall.int_args, unit.int_args, "regparm {own}");
assert_eq!(fastcall.int_args, [ECX, EDX]);
assert_eq!(stdcall.abi.cleanup, rucc_abi::Cleanup::Callee);
assert_eq!(fastcall.abi.cleanup, rucc_abi::Cleanup::Callee);
assert_eq!(unit.abi.cleanup, rucc_abi::Cleanup::Caller);
assert_eq!(stdcall.abi.returns, unit.abi.returns);
assert_eq!(fastcall.abi.returns, unit.abi.returns);
for from in [stdcall, fastcall] {
assert!(std::ptr::eq(from.under(Convention::Target).expect("own"), unit));
assert!(std::ptr::eq(from.under(Convention::Stdcall).expect("again"), stdcall));
}
}
}
}
#[test]
fn each_regparm_count_finds_every_other_from_where_it_is() {
for registers in [false, true] {
for own in 0..=3u8 {
let unit = regparm(own, registers).expect("0 to 3 are counts");
assert_eq!(unit.int_args.len(), usize::from(own));
assert!(std::ptr::eq(unit.under(Convention::Target).expect("its own"), unit));
for other in (0..=3u8).filter(|&other| other != own) {
let there = unit.under(Convention::Regparm(other)).expect("every count");
assert_eq!(there.int_args, &[EAX, EDX, ECX][..usize::from(other)]);
assert_eq!(there.abi.returns.len(), unit.abi.returns.len());
assert!(std::ptr::eq(there.under(Convention::Target).expect("own"), unit));
}
}
}
assert!(std::ptr::eq(regparm(0, false).expect("none"), &SYSV));
assert!(std::ptr::eq(regparm(0, true).expect("none"), &SYSV_REG));
assert!(regparm(4, false).is_none());
}
#[test]
fn the_psabi_says_which_registers_a_call_leaves_alone() {
for reg in [EBX, ESI, EDI, EBP] {
assert!(SYSV.preserves_int(reg));
}
for reg in [EAX, ECX, EDX] {
assert!(!SYSV.preserves_int(reg));
}
assert!((0..8).all(|number| !SYSV.preserves_sse(xmm(number))));
}
#[test]
fn the_allocator_is_offered_every_register_but_the_ones_reserved() {
assert!(covers(SYSV.int_order, 6));
assert!(covers(SYSV_PIC.int_order, 5));
for convention in [&SYSV, &SYSV_PIC] {
assert!(covers(convention.sse_order, 8));
assert!(!convention.int_order.contains(&ESP));
assert!(!convention.int_order.contains(&EBP));
}
assert!(SYSV.int_order.contains(&EBX));
assert!(!SYSV_PIC.int_order.contains(&GOT_BASE));
assert!(SYSV_PIC.preserves_int(GOT_BASE), "the table's address survives a call");
}
#[test]
fn a_register_a_call_destroys_is_offered_before_one_it_preserves() {
for convention in [&SYSV, &SYSV_PIC] {
let first_saved = convention
.int_order
.iter()
.position(|®| convention.preserves_int(reg))
.expect("some register in the order is preserved");
assert!(
convention.int_order[first_saved..]
.iter()
.all(|®| convention.preserves_int(reg)),
"the preserved registers are not one run at the end"
);
}
}
#[test]
fn every_opcode_the_frame_and_the_branches_name_is_one_this_machine_has() {
let mut names = vec![
FRAME.push,
FRAME.pop,
FRAME.add,
FRAME.sub,
FRAME.grow,
FRAME.align,
FRAME.imm,
FRAME.lea,
FRAME.sum,
FRAME.ret,
FRAME.differ,
FRAME.above,
FRAME.call,
];
names.extend(FRAME.away);
names.extend(FRAME.landing);
names.extend(FRAME.pad);
names.extend(FRAME.probe.map(|probe| probe.inst));
for class in FRAME.classes {
names.extend([class.mov, class.load, class.store]);
}
let thunks = FRAME.thunks.expect("the frame has thunks");
names.extend([
thunks.call_through,
thunks.jump_through,
thunks.call,
thunks.jump,
thunks.trap,
]);
names.extend([BRANCH.cond, BRANCH.test, BRANCH.if_true, BRANCH.if_false]);
names.extend([BRANCH.jump, BRANCH.indirect]);
names.extend(BRANCH.conditional);
for fusion in BRANCH.fused {
names.extend([fusion.set, fusion.cmp, fusion.if_true, fusion.if_false]);
}
for entry in BRANCH.moves {
names.extend([entry.select, entry.when, entry.cmov]);
}
for name in names {
assert!(form_here(name).is_some(), "{name} is not an i386 instruction");
}
}
#[test]
fn the_machine_has_the_thirty_two_bit_forms_and_not_the_sixty_four_bit_ones() {
for name in ["add_rr_64", "mov_rr_64", "mov_ri_64", "lea_64", "push_64", "pop_64"] {
assert_eq!(form_here(name), None, "{name}");
assert_eq!((MACHINE.operands)(name), None, "{name}");
}
for name in ["add_rr_32", "mov_rr_32", "mov_ri_32", "lea_32", "push_32", "pop_32"] {
assert!(form_here(name).is_some(), "{name}");
}
assert!((MACHINE.commutes)("add_rr_32"));
assert!(!(MACHINE.commutes)("add_rr_64"));
assert!(!(MACHINE.commutes)("sub_rr_32"));
assert!((MACHINE.calls)("call"));
}
#[test]
fn the_branch_tables_are_x86_64_s_at_the_widths_this_machine_has() {
let here = |shapes: &[Form]| -> Vec<&str> {
crate::x86_64::INSTS
.iter()
.filter(|&&(name, shape)| shapes.contains(&shape) && form_here(name).is_some())
.map(|&(name, _)| name)
.collect()
};
let sets = here(&[Form::CmpSet, Form::CmpSetRi, Form::CmpSetRm, Form::CmpSetMi]);
let entries: Vec<&str> = BRANCH.fused.iter().map(|fusion| fusion.set).collect();
assert_eq!(entries, sets);
let selects = here(&[Form::TestCmov]);
assert_eq!(BRANCH.moves.len(), selects.len() * 10);
for entry in BRANCH.moves {
assert!(selects.contains(&entry.select), "{}", entry.select);
}
}
}