diff --git a/src/arch/x86_64/encode.rs b/src/arch/x86_64/encode.rs index 6be3558..b877ee0 100644 --- a/src/arch/x86_64/encode.rs +++ b/src/arch/x86_64/encode.rs @@ -28,7 +28,7 @@ impl Code { if src.requires_rex() || dst.requires_rex() { self.bytes.push(rex(width, src, 0, dst)); } - self.bytes.push(0x88 | width.gt8() as u8); + self.bytes.push(0x88 | width.not8()); self.bytes.push(modrm_regs(src, dst)); } RegImmMem::Imm(src) => { @@ -49,15 +49,42 @@ impl Code { if dst.requires_rex() { self.bytes.push(rex(dst.width(), 0, 0, dst)); } - let opcode = 0xb0 | ((dst.width().gt8() as u8) << 3); - self.bytes.push(opcode | dst.base()); + self.bytes.push(0xb0 | (dst.not8() << 3) | dst.base()); self.imm(src, dst.width()); } } - RegImmMem::Mem(src) => todo!(), + RegImmMem::Mem(src) => { + if src.width != dst.width() { + return Err("register & memory sizes don't match".into()); + } + if dst.high() && src.reg.gt8() { + return Err("registers incompatible (REX)".into()); + } + self.prefix32(&src)?; + self.prefix16(dst); + if dst.requires_rex() | src.reg.gt8() { + self.bytes.push(rex(dst.width(), dst, 0, src.reg)); + } + self.bytes.push(0x8a | dst.not8()); + self.modrm_regdisp(dst, src); + } }, RegMem::Mem(dst) => match src { - RegImmMem::Reg(src) => todo!(), + RegImmMem::Reg(src) => { + if src.width() != dst.width { + return Err("register & memory sizes don't match".into()); + } + if src.high() && dst.reg.gt8() { + return Err("registers incompatible (REX)".into()); + } + self.prefix32(&dst)?; + self.prefix16(src); + if src.requires_rex() | dst.reg.gt8() { + self.bytes.push(rex(src.width(), src, 0, dst.reg)); + } + self.bytes.push(0x88 | src.not8()); + self.modrm_regdisp(src, dst); + } RegImmMem::Imm(src) => { let encode_width = dst.width.min(Width::B32); let src_width = if dst.width == Width::B64 { @@ -71,17 +98,13 @@ impl Code { if src_width > dst.width { return Err("source cannot fit in destination".into()); } - match dst.reg.width() { - Width::B8 | Width::B16 => return Err("invalid register width".into()), - Width::B32 => self.bytes.push(0x67), - Width::B64 => (), - } + self.prefix32(&dst)?; self.prefix16(encode_width); if dst.reg.requires_mem_rex() || dst.width == Width::B64 { self.bytes.push(rex(dst.width, 0, 0, dst.reg)); } - self.bytes.push(0xc6 | (encode_width != Width::B8) as u8); - self.modrm_regdisp(dst.reg, dst.disp); + self.bytes.push(0xc6 | encode_width.not8()); + self.modrm_regdisp(None, dst); self.imm(src, encode_width); } RegImmMem::Mem(_) => return Err("cannot move memory to memory".into()), @@ -170,23 +193,33 @@ impl Code { } } - fn modrm_regdisp(&mut self, reg: Reg, disp: i32) { + fn prefix32(&mut self, mem: &Mem) -> Result<(), CompilerMsg> { + match mem.reg.width() { + Width::B8 | Width::B16 => return Err("invalid register width".into()), + Width::B32 => self.bytes.push(0x67), + Width::B64 => (), + } + Ok(()) + } + + fn modrm_regdisp(&mut self, reg: impl Into>, mem: Mem) { const I8_MIN: i32 = i8::MIN as i32; const I8_MAX: i32 = i8::MAX as i32; - let mod_ = match disp { + let mod_ = match mem.disp { 0 => 0b00, I8_MIN..=I8_MAX => 0b01, _ => 0b10, }; - self.bytes.push(modrm(mod_, 0, reg.base())); - if reg.base() == rsp.base() { + let r = reg.into().map(|r| Reg::base(&r)).unwrap_or(0); + self.bytes.push(modrm(mod_, r, mem.reg.base())); + if mem.reg.base() == rsp.base() { // SIB self.bytes.push(0x24); } match mod_ { 0b00 => (), - 0b01 => self.bytes.push(disp as u8), - 0b10 => self.bytes.extend(disp.to_le_bytes()), + 0b01 => self.bytes.push(mem.disp as u8), + 0b10 => self.bytes.extend(mem.disp.to_le_bytes()), _ => unreachable!(), } } diff --git a/src/arch/x86_64/reg.rs b/src/arch/x86_64/reg.rs index 3217857..2284fa5 100644 --- a/src/arch/x86_64/reg.rs +++ b/src/arch/x86_64/reg.rs @@ -51,6 +51,14 @@ impl Reg { self.width } + pub fn not8(&self) -> u8 { + self.width.not8() + } + + pub fn high(&self) -> bool { + self.high + } + /// if self has 64 bit width, changes width to 32 bit pub fn lower64(&self) -> Self { let mut new = *self; @@ -101,8 +109,8 @@ impl Width { } /// greater than 8 bits - pub const fn gt8(&self) -> bool { - !matches!(self, Self::B8) + pub const fn not8(&self) -> u8 { + !matches!(self, Self::B8) as u8 } } diff --git a/src/arch/x86_64/test/nasm.rs b/src/arch/x86_64/test/nasm.rs index 92840b7..2b533c8 100644 --- a/src/arch/x86_64/test/nasm.rs +++ b/src/arch/x86_64/test/nasm.rs @@ -34,26 +34,53 @@ const WIDTHS: &[Width] = &[Width::B8, Width::B16, Width::B32, Width::B64]; #[test] fn mov() { - let c = &mut TestCtx::new(); + let c = &mut TestCtx::new("mov"); - for &r1 in Reg::IMPORTANT { - for &r2 in Reg::IMPORTANT { - eq(c, format!("mov {r1}, {r2}"), |c| c.mov(r1, r2)); + for dst in regs() { + for src in regs() { + eq(c, format!("mov {dst}, {src}"), |c| c.mov(dst, src)); } } - for &r1 in Reg::IMPORTANT { - for &imm in IMMS { - eq(c, format!("mov {r1}, {imm}"), |c| c.mov(r1, imm)); + for dst in regs() { + for src in mems() { + eq(c, format!("mov {dst}, {src}"), |c| c.mov(dst, src)); } } - for ® in Reg::IMPORTANT { - for &disp in DISPS { - for &imm in IMMS { + for dst in regs() { + for src in imms() { + eq(c, format!("mov {dst}, {src}"), |c| c.mov(dst, src)); + } + } + + for dst in mems() { + for src in regs() { + eq(c, format!("mov {dst}, {src}"), |c| c.mov(dst, src)); + } + } + + for dst in mems() { + for src in imms() { + eq(c, format!("mov {dst}, {src}"), |c| c.mov(dst, src)); + } + } +} + +fn imms() -> impl Iterator { + IMMS.iter().cloned() +} + +fn regs() -> impl Iterator { + Reg::IMPORTANT.iter().cloned() +} + +fn mems() -> impl Iterator { + gen move { + for ® in Reg::IMPORTANT { + for &disp in DISPS { for &width in WIDTHS { - let mem = mem(reg, disp, width); - eq(c, format!("mov {mem}, {imm}"), |c| c.mov(mem, imm)); + yield mem(reg, disp, width); } } } @@ -61,6 +88,7 @@ fn mov() { } struct TestCtx { + path: String, code: Code, cache: HashMap, String>>, changed: bool, @@ -145,15 +173,17 @@ fn write(path: &str, binary: &[u8]) { file.sync_all().expect("Failed to sync file"); } -const CACHE_PATH: &str = "test/nasm_test_cache"; +const CACHE_PATH: &str = "test/nasm_cache"; impl TestCtx { - fn new() -> Self { - let cache = match std::fs::read(CACHE_PATH) { + fn new(name: &str) -> Self { + let path = CACHE_PATH.to_string() + "/" + name; + let cache = match std::fs::read(&path) { Ok(bytes) => bitcode::decode(&bytes).unwrap_or_default(), Err(_) => Default::default(), }; Self { + path, code: Default::default(), cache, changed: Default::default(), @@ -164,7 +194,7 @@ impl TestCtx { impl Drop for TestCtx { fn drop(&mut self) { if self.changed { - write(CACHE_PATH, &bitcode::encode(&self.cache)); + write(&self.path, &bitcode::encode(&self.cache)); } } } diff --git a/src/main.rs b/src/main.rs index b1fbde5..8f3d00d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![feature(gen_blocks)] + use crate::{io::CompilerOutput, parser_ir::parse_program}; mod arch; diff --git a/test/nasm_cache/mov b/test/nasm_cache/mov new file mode 100644 index 0000000..e53395c Binary files /dev/null and b/test/nasm_cache/mov differ diff --git a/test/nasm_test_cache b/test/nasm_test_cache deleted file mode 100644 index 48944ce..0000000 Binary files a/test/nasm_test_cache and /dev/null differ