use alloc::vec;
use alloc::vec::Vec;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct R16(pub u8);
pub const AX: R16 = R16(0);
pub const CX: R16 = R16(1);
pub const DX: R16 = R16(2);
pub const BX: R16 = R16(3);
pub const SP: R16 = R16(4);
pub const BP: R16 = R16(5);
pub const SI: R16 = R16(6);
pub const DI: R16 = R16(7);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct R8(pub u8);
pub const AL: R8 = R8(0);
pub const CL: R8 = R8(1);
pub const DL: R8 = R8(2);
pub const BL: R8 = R8(3);
pub const AH: R8 = R8(4);
pub const CH: R8 = R8(5);
pub const DH: R8 = R8(6);
pub const BH: R8 = R8(7);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Sreg(pub u8);
pub const ES: Sreg = Sreg(0);
pub const CS: Sreg = Sreg(1);
pub const SS: Sreg = Sreg(2);
pub const DS: Sreg = Sreg(3);
const RM_ABS: u8 = 0xff;
#[derive(Clone, Copy, Debug)]
pub struct Mem {
rm: u8,
disp: i32,
force_disp: bool,
seg: Option<Sreg>,
}
impl Mem {
#[must_use]
pub fn abs(disp: u16) -> Mem {
Mem {
rm: RM_ABS,
disp: i32::from(disp),
force_disp: false,
seg: None,
}
}
#[must_use]
pub fn bx(disp: i32) -> Mem {
Mem::based(0b111, disp, false)
}
#[must_use]
pub fn si(disp: i32) -> Mem {
Mem::based(0b100, disp, false)
}
#[must_use]
pub fn di(disp: i32) -> Mem {
Mem::based(0b101, disp, false)
}
#[must_use]
pub fn bp(disp: i32) -> Mem {
Mem::based(0b110, disp, true)
}
#[must_use]
pub fn bx_si(disp: i32) -> Mem {
Mem::based(0b000, disp, false)
}
#[must_use]
pub fn bx_di(disp: i32) -> Mem {
Mem::based(0b001, disp, false)
}
#[must_use]
pub fn seg(mut self, seg: Sreg) -> Mem {
self.seg = Some(seg);
self
}
fn based(rm: u8, disp: i32, force_disp: bool) -> Mem {
Mem {
rm,
disp,
force_disp,
seg: None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum Rm {
Reg(u8),
Mem(Mem),
}
impl From<R16> for Rm {
fn from(r: R16) -> Rm {
Rm::Reg(r.0)
}
}
impl From<R8> for Rm {
fn from(r: R8) -> Rm {
Rm::Reg(r.0)
}
}
impl From<Mem> for Rm {
fn from(m: Mem) -> Rm {
Rm::Mem(m)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Alu(pub u8);
impl Alu {
pub const ADD: Alu = Alu(0);
pub const OR: Alu = Alu(1);
pub const ADC: Alu = Alu(2);
pub const SBB: Alu = Alu(3);
pub const AND: Alu = Alu(4);
pub const SUB: Alu = Alu(5);
pub const XOR: Alu = Alu(6);
pub const CMP: Alu = Alu(7);
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Cc(pub u8);
impl Cc {
pub const O: Cc = Cc(0x0);
pub const B: Cc = Cc(0x2);
pub const AE: Cc = Cc(0x3);
pub const E: Cc = Cc(0x4);
pub const NE: Cc = Cc(0x5);
pub const BE: Cc = Cc(0x6);
pub const A: Cc = Cc(0x7);
pub const S: Cc = Cc(0x8);
pub const NS: Cc = Cc(0x9);
pub const L: Cc = Cc(0xc);
pub const GE: Cc = Cc(0xd);
pub const G: Cc = Cc(0xf);
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Shift(pub u8);
impl Shift {
pub const ROL: Shift = Shift(0);
pub const ROR: Shift = Shift(1);
pub const SHL: Shift = Shift(4);
pub const SHR: Shift = Shift(5);
pub const SAR: Shift = Shift(7);
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Label(usize);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum Fix {
Rel16,
Abs16,
}
#[derive(Clone, Copy, Debug)]
struct Fixup {
at: usize,
label: Label,
kind: Fix,
}
#[derive(Debug)]
pub struct Asm {
image: Vec<u8>,
at: usize,
labels: Vec<Option<u16>>,
fixups: Vec<Fixup>,
overflow: bool,
}
impl Asm {
#[must_use]
pub fn new(size: usize, fill: u8) -> Asm {
Asm {
image: vec![fill; size],
at: 0,
labels: Vec::new(),
fixups: Vec::new(),
overflow: false,
}
}
pub fn label(&mut self) -> Label {
self.labels.push(None);
Label(self.labels.len() - 1)
}
pub fn bind(&mut self, label: Label) {
assert!(
self.labels[label.0].is_none(),
"label {} bound twice",
label.0
);
self.labels[label.0] = Some(self.here());
}
pub fn here_label(&mut self) -> Label {
let l = self.label();
self.bind(l);
l
}
#[must_use]
pub fn here(&self) -> u16 {
self.at as u16
}
pub fn seek(&mut self, offset: u16) {
self.at = offset as usize;
}
#[must_use]
pub fn offset_of(&self, label: Label) -> Option<u16> {
self.labels[label.0]
}
#[must_use]
pub fn finish(mut self) -> Vec<u8> {
assert!(!self.overflow, "the image overflowed its socket");
for f in core::mem::take(&mut self.fixups) {
let target = self.labels[f.label.0]
.unwrap_or_else(|| panic!("label {} referenced but never bound", f.label.0));
let value = match f.kind {
Fix::Rel16 => target.wrapping_sub((f.at as u16).wrapping_add(2)),
Fix::Abs16 => target,
};
self.image[f.at] = value as u8;
self.image[f.at + 1] = (value >> 8) as u8;
}
self.image
}
pub fn db(&mut self, bytes: &[u8]) {
for &b in bytes {
if self.at < self.image.len() {
self.image[self.at] = b;
self.at += 1;
} else {
self.overflow = true;
self.at += 1;
}
}
}
pub fn dw(&mut self, word: u16) {
self.db(&word.to_le_bytes());
}
pub fn dw_label(&mut self, label: Label) {
self.fixups.push(Fixup {
at: self.at,
label,
kind: Fix::Abs16,
});
self.dw(0);
}
pub fn fill(&mut self, byte: u8, count: usize) {
for _ in 0..count {
self.db(&[byte]);
}
}
fn encode(&mut self, opcode: &[u8], reg: u8, rm: impl Into<Rm>) {
let rm = rm.into();
if let Rm::Mem(m) = rm
&& let Some(s) = m.seg
{
self.db(&[0x26 | (s.0 << 3)]);
}
self.db(opcode);
match rm {
Rm::Reg(r) => self.db(&[0xc0 | (reg << 3) | r]),
Rm::Mem(m) if m.rm == RM_ABS => {
self.db(&[(reg << 3) | 0b110]);
self.dw(m.disp as u16);
}
Rm::Mem(m) => {
let (mode, width) = if m.disp == 0 && !m.force_disp {
(0u8, 0)
} else if (-128..=127).contains(&m.disp) {
(1, 1)
} else {
(2, 2)
};
self.db(&[(mode << 6) | (reg << 3) | m.rm]);
match width {
1 => self.db(&[m.disp as u8]),
2 => self.dw(m.disp as u16),
_ => {}
}
}
}
}
fn encode32(&mut self, opcode: &[u8], reg: u8, rm: impl Into<Rm>) {
self.db(&[0x66]);
self.encode(opcode, reg, rm);
}
pub fn movi(&mut self, dst: R16, imm: u16) {
self.db(&[0xb8 | dst.0]);
self.dw(imm);
}
pub fn movi_label(&mut self, dst: R16, label: Label) {
self.db(&[0xb8 | dst.0]);
self.dw_label(label);
}
pub fn movi32(&mut self, dst: R16, imm: u32) {
self.db(&[0x66, 0xb8 | dst.0]);
self.db(&imm.to_le_bytes());
}
pub fn movi8(&mut self, dst: R8, imm: u8) {
self.db(&[0xb0 | dst.0, imm]);
}
pub fn mov(&mut self, dst: R16, src: impl Into<Rm>) {
self.encode(&[0x8b], dst.0, src);
}
pub fn movto(&mut self, dst: impl Into<Rm>, src: R16) {
self.encode(&[0x89], src.0, dst);
}
pub fn mov32(&mut self, dst: R16, src: impl Into<Rm>) {
self.encode32(&[0x8b], dst.0, src);
}
pub fn movto32(&mut self, dst: impl Into<Rm>, src: R16) {
self.encode32(&[0x89], src.0, dst);
}
pub fn mov8(&mut self, dst: R8, src: impl Into<Rm>) {
self.encode(&[0x8a], dst.0, src);
}
pub fn movto8(&mut self, dst: impl Into<Rm>, src: R8) {
self.encode(&[0x88], src.0, dst);
}
pub fn movmi(&mut self, dst: impl Into<Rm>, imm: u16) {
self.encode(&[0xc7], 0, dst);
self.dw(imm);
}
pub fn movmi32(&mut self, dst: impl Into<Rm>, imm: u32) {
self.encode32(&[0xc7], 0, dst);
self.db(&imm.to_le_bytes());
}
pub fn movmi_label(&mut self, dst: impl Into<Rm>, label: Label) {
self.encode(&[0xc7], 0, dst);
self.dw_label(label);
}
pub fn movmi8(&mut self, dst: impl Into<Rm>, imm: u8) {
self.encode(&[0xc6], 0, dst);
self.db(&[imm]);
}
pub fn movsr(&mut self, dst: Sreg, src: impl Into<Rm>) {
self.encode(&[0x8e], dst.0, src);
}
pub fn movrs(&mut self, dst: impl Into<Rm>, src: Sreg) {
self.encode(&[0x8c], src.0, dst);
}
pub fn lea(&mut self, dst: R16, src: Mem) {
self.encode(&[0x8d], dst.0, src);
}
pub fn xchg_ax(&mut self, other: R16) {
self.db(&[0x90 | other.0]);
}
pub fn alu(&mut self, op: Alu, dst: R16, src: impl Into<Rm>) {
self.encode(&[0x03 | (op.0 << 3)], dst.0, src);
}
pub fn aluto(&mut self, op: Alu, dst: impl Into<Rm>, src: R16) {
self.encode(&[0x01 | (op.0 << 3)], src.0, dst);
}
pub fn alu32(&mut self, op: Alu, dst: R16, src: impl Into<Rm>) {
self.encode32(&[0x03 | (op.0 << 3)], dst.0, src);
}
pub fn alu8(&mut self, op: Alu, dst: R8, src: impl Into<Rm>) {
self.encode(&[0x02 | (op.0 << 3)], dst.0, src);
}
pub fn aluto8(&mut self, op: Alu, dst: impl Into<Rm>, src: R8) {
self.encode(&[op.0 << 3], src.0, dst);
}
pub fn alui(&mut self, op: Alu, dst: impl Into<Rm>, imm: u16) {
self.encode(&[0x81], op.0, dst);
self.dw(imm);
}
pub fn alui32(&mut self, op: Alu, dst: impl Into<Rm>, imm: u32) {
self.encode32(&[0x81], op.0, dst);
self.db(&imm.to_le_bytes());
}
pub fn alui8(&mut self, op: Alu, dst: impl Into<Rm>, imm: u8) {
self.encode(&[0x80], op.0, dst);
self.db(&[imm]);
}
pub fn test8(&mut self, a: impl Into<Rm>, b: R8) {
self.encode(&[0x84], b.0, a);
}
pub fn testi8(&mut self, a: impl Into<Rm>, imm: u8) {
self.encode(&[0xf6], 0, a);
self.db(&[imm]);
}
pub fn testi(&mut self, a: impl Into<Rm>, imm: u16) {
self.encode(&[0xf7], 0, a);
self.dw(imm);
}
pub fn test(&mut self, a: impl Into<Rm>, b: R16) {
self.encode(&[0x85], b.0, a);
}
pub fn inc(&mut self, r: R16) {
self.db(&[0x40 | r.0]);
}
pub fn dec(&mut self, r: R16) {
self.db(&[0x48 | r.0]);
}
pub fn incm8(&mut self, dst: impl Into<Rm>) {
self.encode(&[0xfe], 0, dst);
}
pub fn decm8(&mut self, dst: impl Into<Rm>) {
self.encode(&[0xfe], 1, dst);
}
pub fn incm(&mut self, dst: impl Into<Rm>) {
self.encode(&[0xff], 0, dst);
}
pub fn decm(&mut self, dst: impl Into<Rm>) {
self.encode(&[0xff], 1, dst);
}
pub fn incm32(&mut self, dst: impl Into<Rm>) {
self.encode32(&[0xff], 0, dst);
}
pub fn neg(&mut self, dst: impl Into<Rm>) {
self.encode(&[0xf7], 3, dst);
}
pub fn not8(&mut self, dst: impl Into<Rm>) {
self.encode(&[0xf6], 2, dst);
}
pub fn not(&mut self, dst: impl Into<Rm>) {
self.encode(&[0xf7], 2, dst);
}
pub fn mul(&mut self, src: impl Into<Rm>) {
self.encode(&[0xf7], 4, src);
}
pub fn mul32(&mut self, src: impl Into<Rm>) {
self.encode32(&[0xf7], 4, src);
}
pub fn mul8(&mut self, src: impl Into<Rm>) {
self.encode(&[0xf6], 4, src);
}
pub fn div(&mut self, src: impl Into<Rm>) {
self.encode(&[0xf7], 6, src);
}
pub fn div8(&mut self, src: impl Into<Rm>) {
self.encode(&[0xf6], 6, src);
}
pub fn cbw(&mut self) {
self.db(&[0x98]);
}
pub fn cwd(&mut self) {
self.db(&[0x99]);
}
pub fn shift(&mut self, op: Shift, dst: impl Into<Rm>, count: u8) {
self.encode(&[0xc1], op.0, dst);
self.db(&[count]);
}
pub fn shift8(&mut self, op: Shift, dst: impl Into<Rm>, count: u8) {
self.encode(&[0xc0], op.0, dst);
self.db(&[count]);
}
pub fn shift32(&mut self, op: Shift, dst: impl Into<Rm>, count: u8) {
self.encode32(&[0xc1], op.0, dst);
self.db(&[count]);
}
pub fn push(&mut self, r: R16) {
self.db(&[0x50 | r.0]);
}
pub fn pop(&mut self, r: R16) {
self.db(&[0x58 | r.0]);
}
pub fn pushs(&mut self, s: Sreg) {
self.db(&[0x06 | (s.0 << 3)]);
}
pub fn pops(&mut self, s: Sreg) {
assert!(s != CS, "there is no POP CS");
self.db(&[0x07 | (s.0 << 3)]);
}
pub fn pusha(&mut self) {
self.db(&[0x60]);
}
pub fn popa(&mut self) {
self.db(&[0x61]);
}
pub fn pushf(&mut self) {
self.db(&[0x9c]);
}
pub fn popf(&mut self) {
self.db(&[0x9d]);
}
pub fn pushad(&mut self) {
self.db(&[0x66, 0x60]);
}
pub fn popad(&mut self) {
self.db(&[0x66, 0x61]);
}
pub fn pushi(&mut self, imm: u16) {
self.db(&[0x68]);
self.dw(imm);
}
pub fn pushi_label(&mut self, label: Label) {
self.db(&[0x68]);
self.dw_label(label);
}
pub fn jmp(&mut self, label: Label) {
self.db(&[0xe9]);
self.rel16(label);
}
pub fn jcc(&mut self, cc: Cc, label: Label) {
self.db(&[0x0f, 0x80 | cc.0]);
self.rel16(label);
}
pub fn call(&mut self, label: Label) {
self.db(&[0xe8]);
self.rel16(label);
}
pub fn call_rm(&mut self, target: impl Into<Rm>) {
self.encode(&[0xff], 2, target);
}
pub fn callf_m(&mut self, target: Mem) {
self.encode(&[0xff], 3, target);
}
pub fn jmpf(&mut self, segment: u16, offset: u16) {
self.db(&[0xea]);
self.dw(offset);
self.dw(segment);
}
pub fn jmpf_label(&mut self, segment: u16, label: Label) {
self.db(&[0xea]);
self.dw_label(label);
self.dw(segment);
}
pub fn jmpf_m(&mut self, target: Mem) {
self.encode(&[0xff], 5, target);
}
pub fn ret(&mut self) {
self.db(&[0xc3]);
}
pub fn retf(&mut self) {
self.db(&[0xcb]);
}
pub fn iret(&mut self) {
self.db(&[0xcf]);
}
pub fn int(&mut self, vector: u8) {
self.db(&[0xcd, vector]);
}
pub fn loop_(&mut self, label: Label) {
self.db(&[0xe2]);
self.rel8(label);
}
pub fn hlt(&mut self) {
self.db(&[0xf4]);
}
pub fn nop(&mut self) {
self.db(&[0x90]);
}
pub fn cli(&mut self) {
self.db(&[0xfa]);
}
pub fn sti(&mut self) {
self.db(&[0xfb]);
}
pub fn cld(&mut self) {
self.db(&[0xfc]);
}
pub fn stc(&mut self) {
self.db(&[0xf9]);
}
pub fn clc(&mut self) {
self.db(&[0xf8]);
}
pub fn rep(&mut self) {
self.db(&[0xf3]);
}
pub fn movsb(&mut self) {
self.db(&[0xa4]);
}
pub fn movsw(&mut self) {
self.db(&[0xa5]);
}
pub fn stosb(&mut self) {
self.db(&[0xaa]);
}
pub fn stosw(&mut self) {
self.db(&[0xab]);
}
pub fn lodsb(&mut self) {
self.db(&[0xac]);
}
pub fn lodsw(&mut self) {
self.db(&[0xad]);
}
pub fn insw(&mut self) {
self.db(&[0x6d]);
}
pub fn outsw(&mut self) {
self.db(&[0x6f]);
}
pub fn in_al(&mut self, port: u8) {
self.db(&[0xe4, port]);
}
pub fn in_al_dx(&mut self) {
self.db(&[0xec]);
}
pub fn in_ax_dx(&mut self) {
self.db(&[0xed]);
}
pub fn out_al(&mut self, port: u8) {
self.db(&[0xe6, port]);
}
pub fn out_dx_al(&mut self) {
self.db(&[0xee]);
}
pub fn out_dx_ax(&mut self) {
self.db(&[0xef]);
}
pub fn lgdt(&mut self, table: Mem) {
self.encode(&[0x0f, 0x01], 2, table);
}
pub fn read_cr0(&mut self, dst: R16) {
self.encode(&[0x0f, 0x20], 0, dst);
}
pub fn write_cr0(&mut self, src: R16) {
self.encode(&[0x0f, 0x22], 0, src);
}
fn rel16(&mut self, label: Label) {
self.fixups.push(Fixup {
at: self.at,
label,
kind: Fix::Rel16,
});
self.dw(0);
}
fn rel8(&mut self, label: Label) {
let target = self.labels[label.0].expect("a short branch must be backward");
let from = (self.at as u16).wrapping_add(1);
let delta = target.wrapping_sub(from) as i16;
let delta = i32::from(delta);
assert!(
(-128..=127).contains(&delta),
"short branch out of range: {delta}"
);
self.db(&[delta as u8]);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn one(f: impl FnOnce(&mut Asm)) -> Vec<u8> {
let mut a = Asm::new(64, 0x00);
f(&mut a);
let n = a.here() as usize;
a.finish()[..n].to_vec()
}
#[test]
fn the_encodings_match_the_opcode_map() {
assert_eq!(one(|a| a.movi(AX, 0x1234)), [0xb8, 0x34, 0x12]);
assert_eq!(one(|a| a.movi8(AH, 0x0e)), [0xb4, 0x0e]);
assert_eq!(one(|a| a.mov(BX, AX)), [0x8b, 0xd8]);
assert_eq!(
one(|a| a.movto(Mem::abs(0x0410), AX)),
[0x89, 0x06, 0x10, 0x04]
);
assert_eq!(one(|a| a.alui(Alu::CMP, AX, 3)), [0x81, 0xf8, 0x03, 0x00]);
assert_eq!(one(|a| a.int(0x13)), [0xcd, 0x13]);
assert_eq!(
one(|a| a.jmpf(0xf000, 0xe05b)),
[0xea, 0x5b, 0xe0, 0x00, 0xf0]
);
assert_eq!(
one(|a| a.movi32(CX, 20)),
[0x66, 0xb9, 0x14, 0x00, 0x00, 0x00]
);
assert_eq!(
one(|a| a.lgdt(Mem::abs(0x0078))),
[0x0f, 0x01, 0x16, 0x78, 0x00]
);
assert_eq!(one(|a| a.read_cr0(AX)), [0x0f, 0x20, 0xc0]);
assert_eq!(one(|a| a.write_cr0(AX)), [0x0f, 0x22, 0xc0]);
}
#[test]
fn a_memory_operand_picks_the_narrowest_displacement() {
assert_eq!(one(|a| a.mov(AX, Mem::bx(0))), [0x8b, 0x07]);
assert_eq!(one(|a| a.mov(AX, Mem::bx(4))), [0x8b, 0x47, 0x04]);
assert_eq!(one(|a| a.mov(AX, Mem::bx(0x200))), [0x8b, 0x87, 0x00, 0x02]);
assert_eq!(one(|a| a.mov(AX, Mem::bp(0))), [0x8b, 0x46, 0x00]);
assert_eq!(
one(|a| a.mov(AX, Mem::abs(0x413))),
[0x8b, 0x06, 0x13, 0x04]
);
assert_eq!(one(|a| a.mov(AX, Mem::bx(0).seg(ES))), [0x26, 0x8b, 0x07]);
}
#[test]
fn a_backward_branch_and_a_forward_one_both_resolve() {
let mut a = Asm::new(64, 0x90);
let top = a.here_label();
let out = a.label();
a.jcc(Cc::E, out); a.jmp(top); a.bind(out); a.hlt();
let bytes = a.finish();
assert_eq!(
&bytes[..8],
&[0x0f, 0x84, 0x03, 0x00, 0xe9, 0xf9, 0xff, 0xf4]
);
}
#[test]
#[should_panic(expected = "referenced but never bound")]
fn an_unbound_label_is_a_build_failure_rather_than_a_broken_rom() {
let mut a = Asm::new(64, 0);
let l = a.label();
a.jmp(l);
let _ = a.finish();
}
}