Skip to content

Commit

Permalink
[x64] implementing more of instruction compilation
Browse files Browse the repository at this point in the history
  • Loading branch information
Cr0a3 committed Aug 2, 2024
1 parent 74fc27c commit 92916b5
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 45 deletions.
2 changes: 1 addition & 1 deletion src/IR/ir.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{any::Any, fmt::Debug, hash::Hash};
use super::{FunctionType, IRBuilder, Type, TypeMetadata, Var, VerifyError};
use crate::Target::{Instr, TargetBackendDescr};
use crate::Target::{instr::Instr, TargetBackendDescr};

macro_rules! IrTypeWith3 {
($name:tt, $param1:tt, $param2:tt, $param3:tt) => {
Expand Down
7 changes: 5 additions & 2 deletions src/Target/reg.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt::{Display, Debug};
use std::{any::Any, fmt::{Debug, Display}};

/// A register
pub trait Reg: Display + ToString + Debug {
pub trait Reg: Display + ToString + Debug + Any {
/// sub64 variant (e.g: eax -> rax (x64) or x0 -> x0 (aarch64))
fn sub64(&self) -> String;
/// sub32 variant (e.g: rax -> eax (x64) or x0 -> x0 (aarch64))
Expand Down Expand Up @@ -29,6 +29,9 @@ pub trait Reg: Display + ToString + Debug {

/// parses the string variant
fn from(&self, string: String) -> Box<dyn Reg>;

#[doc(hidden)]
fn as_any(&self) -> &dyn Any;
}

impl PartialEq for Box<dyn Reg> {
Expand Down
2 changes: 1 addition & 1 deletion src/Target/registry.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{collections::{HashMap, VecDeque}, error::Error, fmt::Display};

use crate::{prelude::{Block, Function}, Obj::Linkage, Target::Instr};
use crate::{prelude::{Block, Function}, Obj::Linkage, Target::instr::Instr};

use super::{Arch, CallConv, TargetBackendDescr, Triple};

Expand Down
2 changes: 1 addition & 1 deletion src/Target/target_descr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use core::fmt::Debug;

use crate::prelude::{ir::*, Block, Function, Type, TypeMetadata, Var};

use super::{x64Reg, CallConv, Compiler, Instr, Lexer, MemOp, Reg};
use super::{x64Reg, CallConv, Compiler, instr::Instr, Lexer, instr::MemOp, Reg};

#[derive(Debug)]
pub(crate) struct BackendInfos {
Expand Down
189 changes: 168 additions & 21 deletions src/Target/x64/asm/instr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::{fmt::Display, ops::{Add, Sub}};
use std::{fmt::Display, ops::{Add, Sub}, str::FromStr};

use crate::Target::{x64Reg, Reg};
use crate::Target::{isa::{buildOpcode, MandatoryPrefix, RexPrefix}, x64Reg, Reg};

use super::isa::ModRm;

/// The target instruction
#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -44,10 +46,107 @@ impl Instr {
/// Encodes the instruction (some will say compiles)
pub fn encode(&self) -> Result<Vec<u8>, InstrEncodingError> {
self.verify()?;
//let mut out = vec![];
todo!("TODO: implement instruction encoding");

Ok(match self.mnemonic {
Mnemonic::Add | Mnemonic::Adc | Mnemonic::And | Mnemonic::Or | Mnemonic::Sub | Mnemonic::Xor => {

let mandatory = if let Some(Operand::Reg(reg)) = &self.op1 {
if reg.is_gr16() { Some(MandatoryPrefix::t16BitOps)}
else { None }
} else { None };

let rex: Option<RexPrefix> = if let Some(Operand::Reg(reg)) = &self.op1 {
if reg.is_gr64() { Some(RexPrefix { w: true, r: false, x: false, b: false })}
else { None }
} else { None };

let (mut r, mut m, _) = match self.mnemonic {
Mnemonic::Add => (0x01, 0x03, 0),
Mnemonic::Adc => (0x11, 0x03, 2),
Mnemonic::Sub => (0x29, 0x2B, 5),
Mnemonic::And => (0x21, 0x23, 4),
Mnemonic::Or => (0x09, 0x0B, 1),
Mnemonic::Xor => (0x31, 0x33, 6),
_ => unreachable!(),
};

if let Some(Operand::Reg(reg)) = &self.op1 {
if reg.is_gr8() { r -= 1; m -= 1; }
}

//Ok(out)
match self.op2.as_ref().expect("verifycation failed") {
Operand::Reg(reg) => {
let reg = reg.as_any().downcast_ref::<x64Reg>().expect("expected x64 registers and not the ones from other archs");

let mut rex = if reg.extended() {
if let Some(mut rex) = rex {
rex.r = true;
Some(rex)
} else {
Some(RexPrefix { w: false, r: true, x: false, b: false })
}
} else { rex };
let mut op = vec![];

if let Some(Operand::Reg(op0)) = &self.op1 {
let op0 = op0.as_any().downcast_ref::<x64Reg>().expect("expected x64 registers and not the ones from other archs");

rex = if op0.extended() {
if let Some(mut rex) = rex {
rex.b = true;
Some(rex)
} else {
Some(RexPrefix { w: false, r: false, x: false, b: true })
}
} else { rex };

op.push(m);
op.extend_from_slice(&ModRm::reg2(
*reg,
*op0
));

} else if let Some(Operand::Mem(mem)) = &self.op1 {
op.push(m);
op.extend_from_slice(&ModRm::regM(
*reg.as_any().downcast_ref::<x64Reg>().expect("expected x64 registers and not the ones from other archs"),
mem.clone(),
))
} else { todo!() }

buildOpcode(mandatory, rex, op)
},
Operand::Mem(mem) => {
let mut op = vec![];
let mut rex = None;

if let Some(Operand::Reg(op0)) = &self.op1 {
let op0 = op0.as_any().downcast_ref::<x64Reg>().expect("expected x64 registers and not the ones from other archs");

if op0.extended() {
rex = Some(RexPrefix { w: false, r: false, x: false, b: true });
}

op.push(r);
op.extend_from_slice(&ModRm::memR(
mem.clone(),
*op0
));

} else { todo!() }

buildOpcode(mandatory, rex, op)
},

_ => todo!(),
}
},
Mnemonic::Lea => todo!(),
Mnemonic::Mov => todo!(),
Mnemonic::Push => todo!(),
Mnemonic::Pop => todo!(),
Mnemonic::Ret => vec![0xC3],
})
}

/// Verifys the instruction (like checking the right opcodes etc.)
Expand All @@ -60,7 +159,7 @@ impl Instr {
if let Some(Operand::Reg(_)) = self.op1 {} else {
Err(InstrEncodingError::InvalidVariant(self.clone(), "leas first operand needs to be an register".into()))?
}
if let Some(Operand::Mem(_)) = self.op1 {} else {
if let Some(Operand::Mem(_)) = self.op2 {} else {
Err(InstrEncodingError::InvalidVariant(self.clone(), "leas secound operand needs to be an memop".into()))?
}
},
Expand All @@ -71,21 +170,25 @@ impl Instr {
if let Some(Operand::Imm(_)) = self.op1 {
Err(InstrEncodingError::InvalidVariant(self.clone(), "the mov instructions requires that the first operand is either a reg or a memop".into()))?
}
},
Mnemonic::Add => {
if self.op2 == None || self.op1 == None {
Err(InstrEncodingError::InvalidVariant(self.clone(), "add needs to have two operand".into()))?
}
if let Some(Operand::Imm(_)) = self.op1 {} else {
Err(InstrEncodingError::InvalidVariant(self.clone(), "the add instructions requires that the first operand is either a reg or a memop".into()))?

if let Some(Operand::Mem(_)) = self.op1 {
if let Some(Operand::Mem(_)) = self.op2 {
Err(InstrEncodingError::InvalidVariant(self.clone(), "add/sub/or/xor can't have two mem operands".into()))?
}
}
},
Mnemonic::Sub => {
Mnemonic::Add | Mnemonic::Adc | Mnemonic::Sub | Mnemonic::And | Mnemonic::Or | Mnemonic::Xor => {
if self.op2 == None || self.op1 == None {
Err(InstrEncodingError::InvalidVariant(self.clone(), "sub needs to have two operand".into()))?
Err(InstrEncodingError::InvalidVariant(self.clone(), "add/sub/and/or/xor needs to have two operand".into()))?
}
if let Some(Operand::Imm(_)) = self.op1 {
Err(InstrEncodingError::InvalidVariant(self.clone(), "the add/sub/and/or/xor instructions requires that the first operand is either a reg or a memop".into()))?
}
if let Some(Operand::Imm(_)) = self.op1 {} else {
Err(InstrEncodingError::InvalidVariant(self.clone(), "the sub instructions requires that the first operand is either a reg or a memop".into()))?

if let Some(Operand::Mem(_)) = self.op1 {
if let Some(Operand::Mem(_)) = self.op2 {
Err(InstrEncodingError::InvalidVariant(self.clone(), "add/sub/or/xor can't have two mem operands".into()))?
}
}
},
Mnemonic::Push => {
Expand Down Expand Up @@ -165,21 +268,51 @@ impl std::error::Error for InstrEncodingError {}
#[allow(missing_docs)]
pub enum Mnemonic {
Add,
Adc,
And,
Or,
Xor,
Sub,

Lea,
Mov,
Push,
Pop,
Ret,
Sub,
}

impl FromStr for Mnemonic {
type Err = ();

fn from_str(s: &str) -> Result<Mnemonic, Self::Err> {
match s {
"add" => Ok(Mnemonic::Add),
"adc" => Ok(Mnemonic::Adc),
"and" => Ok(Mnemonic::And),
"or" => Ok(Mnemonic::Or),
"xor" => Ok(Mnemonic::Xor),
"sub" => Ok(Mnemonic::Sub),
"lea" => Ok(Mnemonic::Lea),
"mov" => Ok(Mnemonic::Mov),
"push" => Ok(Mnemonic::Push),
"pop" => Ok(Mnemonic::Pop),
"ret" => Ok(Mnemonic::Ret),
_ => Err(()),
}
}
}

impl Display for Mnemonic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", match self {
Mnemonic::Lea => "lea",
Mnemonic::Mov => "mov",
write!(f, "{}", match self {
Mnemonic::Add => "add",
Mnemonic::Adc => "adc",
Mnemonic::And => "and",
Mnemonic::Or => "or",
Mnemonic::Xor => "xor",
Mnemonic::Sub => "sub",
Mnemonic::Lea => "lea",
Mnemonic::Mov => "mov",
Mnemonic::Push => "push",
Mnemonic::Pop => "pop",
Mnemonic::Ret => "ret",
Expand Down Expand Up @@ -234,6 +367,20 @@ pub struct MemOp {
pub add: bool,
}

impl MemOp {
#[doc(hidden)]
pub fn encode(&self) -> Vec<u8> {
todo!()
}

#[doc(hidden)]
pub fn _mod(&self) -> u8 {
if self.displ == 0 { 0 }
else if self.displ <= u8::MAX as isize { 0b01 }
else { 0b10 }
}
}

impl PartialEq for MemOp {
fn eq(&self, other: &Self) -> bool {
self.base == other.base.clone() && self.index == other.index && self.scale == other.scale && self.displ == other.displ && self.add == other.add
Expand Down
65 changes: 65 additions & 0 deletions src/Target/x64/asm/isa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::Target::{x64Reg, Reg};

use super::instr::MemOp;

#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RexPrefix {
pub w: bool,
pub r: bool,
pub x: bool,
pub b: bool,
}

#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MandatoryPrefix {
t16BitOps,
}

pub(crate) fn buildOpcode(mandatory: Option<MandatoryPrefix>, rex: Option<RexPrefix>, op: Vec<u8>) -> Vec<u8> {
let mut out = vec![];

if let Some(man) = mandatory {
out.extend_from_slice(&match man {
MandatoryPrefix::t16BitOps => vec![0x66],
})
}

if let Some(rex) = rex {
out.push({
let mut enc = 1 << 6;
if rex.w { enc |= 1 << 3}
if rex.r { enc |= 1 << 2}
if rex.x { enc |= 1 << 1}
if rex.b { enc |= 1 << 0}
enc
})
}

out.extend_from_slice(&op);

out
}

#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ModRm {}

impl ModRm {
pub fn reg2(reg1: x64Reg, reg2: x64Reg) -> Vec<u8> {
vec![0b11 << 6 | reg2.enc() << 3 | reg1.enc()]
}

pub fn regM(reg: x64Reg, mem: MemOp) -> Vec<u8> {
let mut out = vec![mem._mod() << 6 | reg.enc() << 3 | 0b100];
out.extend_from_slice(&mem.encode());
out
}

pub fn memR(mem: MemOp, reg: x64Reg) -> Vec<u8> {
let mut out = vec![0 << 6 | reg.enc() << 3 | 0b100];
out.extend_from_slice(&mem.encode());
out
}
}
8 changes: 5 additions & 3 deletions src/Target/x64/asm/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
mod lexer;
mod parser;
mod instr;
/// x64 instruction encoding (compilation) and verifycation
pub mod instr;
/// x64 instruction set architecture specific stuff like rex prefix
pub mod isa;

pub use lexer::*;
pub use parser::*;
pub use instr::*;
pub use parser::*;
Loading

0 comments on commit 92916b5

Please sign in to comment.