From 1381f4e8cc1db3b7d4bcfff15d84f59a3de16fba Mon Sep 17 00:00:00 2001 From: TheCPP Date: Sat, 13 Jul 2024 13:14:55 +0200 Subject: [PATCH] [X64] Starting x64 backend --- Cargo.lock | 1 + Cargo.toml | 1 + examples/simple.rs | 18 ++++-- src/IR/builder.rs | 5 ++ src/IR/ir.rs | 2 +- src/IR/typ.rs | 2 - src/Support/mod.rs | 2 +- src/Target/mod.rs | 2 + src/Target/registry.rs | 8 ++- src/Target/x64/call.rs | 35 ++++++++++++ src/Target/x64/ir.rs | 125 +++++++++++++++++++++++++++++++++++++++++ src/Target/x64/mod.rs | 17 ++++++ src/lib.rs | 4 +- 13 files changed, 211 insertions(+), 11 deletions(-) create mode 100644 src/Target/x64/call.rs create mode 100644 src/Target/x64/ir.rs create mode 100644 src/Target/x64/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 4d79b924..ef6260ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "Ygen" version = "0.1.0" dependencies = [ "object", + "once_cell", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ae4b9ce2..b6bd6ed6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ edition = "2021" [dependencies] object = { version = "0.36.1", features = ["write"] } +once_cell = "1.19.0" diff --git a/examples/simple.rs b/examples/simple.rs index ecc3522e..dba74c36 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -1,8 +1,12 @@ use std::error::Error; -use Ygen::{prelude::*, PassManager::Passes::PreComputeValue}; +use Ygen::{prelude::*, PassManager::Passes::PreComputeValue, Target::{initializeX64Target, CallConv}}; pub fn main() -> Result<(), Box> { + + initializeX64Target(); + let mut module = Module(); + let mut builder = IRBuilder(); let ty = FnTy(vec![TypeMetadata::i32, TypeMetadata::i32], TypeMetadata::i32); @@ -14,10 +18,12 @@ pub fn main() -> Result<(), Box> { builder.positionAtEnd(entry); let val = builder.BuildAdd(ty.arg(0), ty.arg(1)); - let add2 = builder.BuildAdd(Type::i32(5), Type::i32(5)); - let ret = builder.BuildAdd(val, add2); + //let add2 = builder.BuildAdd(Type::i32(5), Type::i32(5)); + //let ret = builder.BuildAdd(val, add2); - builder.BuildRet( ret ); + //builder.BuildRet( ret ); + let block = builder.getLastBlock().clone().unwrap().clone(); + let func = func.clone().to_owned().clone(); module.verify().print(); @@ -26,9 +32,11 @@ pub fn main() -> Result<(), Box> { module.runPassMngr(passes); - println!("{}", + eprintln!("{}", module.dumpColored() ); + eprintln!("{:#?}", block.buildAsmX86(&func, &CallConv::WindowsFastCall)); + Ok(()) } \ No newline at end of file diff --git a/src/IR/builder.rs b/src/IR/builder.rs index ea2ba6e9..14d773e5 100644 --- a/src/IR/builder.rs +++ b/src/IR/builder.rs @@ -29,6 +29,11 @@ impl<'a> IRBuilder<'a> { self.blocks.push_front(block); self.curr = 0; // Can cause an intenger underflow but shouldn't } + + /// Returns the last block of the builder + pub fn getLastBlock(&mut self) -> Option<&Block> { + Some(self.blocks.back()?.to_owned().to_owned()) + } } /// Creates an new IRBuilder diff --git a/src/IR/ir.rs b/src/IR/ir.rs index 44497049..4f15d595 100644 --- a/src/IR/ir.rs +++ b/src/IR/ir.rs @@ -333,7 +333,7 @@ impl BuildAdd for IRBuilder<'_> { let op0Ty: TypeMetadata = op0.ty.into(); - let ty = op0Ty; // now both types need to be the same + let ty = op0Ty; let var = Var::new(block, ty); block.push_ir(Add::new(op0, op1, var.clone())); diff --git a/src/IR/typ.rs b/src/IR/typ.rs index 8e6c13a0..199d08d4 100644 --- a/src/IR/typ.rs +++ b/src/IR/typ.rs @@ -4,7 +4,6 @@ use std::fmt::Display; /// /// If you want an empty Type consider using `TypeMetadata` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[allow(non_camel_case_types)] pub enum Type { /// Just an u16 with a value u16(u16), @@ -26,7 +25,6 @@ pub enum Type { /// Stores type metadata (just the type without data) #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[allow(non_camel_case_types)] pub enum TypeMetadata { /// u16 u16, diff --git a/src/Support/mod.rs b/src/Support/mod.rs index 890171f0..55ec955d 100644 --- a/src/Support/mod.rs +++ b/src/Support/mod.rs @@ -27,7 +27,7 @@ impl PrintErrorAndExit for Result<(), VerifyError> { match self { Ok(_) => {}, Err(e) => { - println!("{}", e); + eprintln!("{}", e); exit(-1) } } diff --git a/src/Target/mod.rs b/src/Target/mod.rs index 9e22f84c..7ec742e9 100644 --- a/src/Target/mod.rs +++ b/src/Target/mod.rs @@ -1,5 +1,7 @@ mod triple; mod registry; +mod x64; +pub use x64::initializeX64Target; pub use triple::Triple; pub use registry::TargetRegistry; diff --git a/src/Target/registry.rs b/src/Target/registry.rs index 235621e0..6dce45bf 100644 --- a/src/Target/registry.rs +++ b/src/Target/registry.rs @@ -1,11 +1,17 @@ +use std::sync::Mutex; + +use once_cell::sync::OnceCell; + use super::Arch; /// The Target Registry: stores if a target was already initialized #[derive(Debug, Clone, PartialEq, Eq)] pub struct TargetRegistry { - inited_targets: Vec, + pub(crate) inited_targets: Vec, } +pub(crate) static TARGETS: OnceCell> = OnceCell::new(); + impl TargetRegistry { /// Creates a new instance pub fn new() -> Self { diff --git a/src/Target/x64/call.rs b/src/Target/x64/call.rs new file mode 100644 index 00000000..8c01a862 --- /dev/null +++ b/src/Target/x64/call.rs @@ -0,0 +1,35 @@ +use crate::Target::CallConv; + +impl CallConv { + /// Returns the number of register arguments in the calling convention + pub fn regArgs(&self) -> usize { + match self { + CallConv::SystemV => 6, + CallConv::WindowsFastCall => 4, + } + } + + /// Returns the 16Bit intenger argument registers as a vec + pub fn args16(&self) -> Vec { + match self { + CallConv::SystemV => vec!["si".into(), "di".into(), "dx".into(), "cx".into(), "r8w".into(), "r9w".into()], + CallConv::WindowsFastCall => vec!["dx".into(), "cx".into(), "r8w".into(), "r9w".into()], + } + } + + /// Returns the 32Bit intenger argument registers as a vec + pub fn args32(&self) -> Vec { + match self { + CallConv::SystemV => vec!["esi".into(), "edi".into(), "edx".into(), "ecx".into(), "r8d".into(), "r9d".into()], + CallConv::WindowsFastCall => vec!["edx".into(), "ecx".into(), "r8d".into(), "r9d".into()], + } + } + + /// Returns the 16Bit intenger argument registers as a vec + pub fn args64(&self) -> Vec { + match self { + CallConv::SystemV => vec!["rsi".into(), "rdi".into(), "rdx".into(), "rcx".into(), "r8".into(), "r9".into()], + CallConv::WindowsFastCall => vec!["rdx".into(), "rcx".into(), "r8".into(), "r9".into()], + } + } +} \ No newline at end of file diff --git a/src/Target/x64/ir.rs b/src/Target/x64/ir.rs new file mode 100644 index 00000000..c7c3fbde --- /dev/null +++ b/src/Target/x64/ir.rs @@ -0,0 +1,125 @@ +use std::{any::Any, collections::HashMap}; + +use crate::{prelude::{Block, Function, TypeMetadata, Var}, IR::ir::*}; + +use crate::Target::CallConv; + +/// Stores compilation infos for ir node compilation +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) struct x64CompilationInfos { + pub(crate) varsStorage: HashMap, +} + +impl x64CompilationInfos { + pub(crate) fn insertVar(&mut self, var: Var, store: VarStorage) { + self.varsStorage.insert(var, store); + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum VarStorage { + Register(String), + Memory(String), +} + +/// A trait which is used to implement compilability for ir nodes +pub(crate) trait Compile: Ir { + /// Compiles the node into an asm string + fn compile(&self, infos: &mut x64CompilationInfos) -> Vec; +} + +impl Compile for Add { + fn compile(&self, infos: &mut x64CompilationInfos) -> Vec { + let loc1 = if let Some(loc1) = infos.varsStorage.get(&self.inner1) { + loc1.clone() + } else { + panic!("unknown variable: {:?}", self.inner1) + }; + + let loc2 = if let Some(loc2) = infos.varsStorage.get(&self.inner2) { + loc2.clone() + + } else { + panic!("unknown variable: {:?}", self.inner1) + }; + + let op0 = if let VarStorage::Register(ref reg) = loc1 { + reg.to_string() + } else if let VarStorage::Memory(ref mem) = loc1 { + mem.to_string() + } else { panic!() }; + + let op1 = if let VarStorage::Register(ref reg) = loc2 { + reg.to_string() + } else if let VarStorage::Memory(ref mem) = loc2 { + mem.to_string() + } else { panic!() }; + + let ret: String = "rax".into(); + + infos.insertVar( + self.inner3.clone(), + VarStorage::Register(ret.clone()) + ); + + if let VarStorage::Register(_) = loc1 { + if let VarStorage::Register(_) = loc2 { + return vec![format!("lea {}, [{} + {}", ret, op0, op1)]; + } + } + + if let VarStorage::Memory(_) = loc1 { + if let VarStorage::Memory(_) = loc2 { + return vec![ + format!("mov rax, {}", op0), + format!("mov rbx, {}", op1), + format!("add rax, rbx"), + format!("mov rax, {}", ret), + ]; + } + } + + vec![] + } +} + +impl Block { + /// Builds the block to x86 assembly intel syntax + pub fn buildAsmX86(&self, func: &Function, call: &CallConv) -> Vec { + let mut info = x64CompilationInfos { varsStorage: HashMap::new() }; + + let mut reg_vars = 0; + let mut stack_off = 0; + + for (_, meta) in &func.ty.args { + info.insertVar(Var(&mut self.clone(), *meta), { + if reg_vars >= call.regArgs() { + let addend = match meta { + TypeMetadata::u16 | TypeMetadata::i16=> 2, + TypeMetadata::u32 | TypeMetadata::i32=> 4, + TypeMetadata::u64 | TypeMetadata::i64=> 8, + TypeMetadata::Void => continue, + }; + + stack_off += addend; + VarStorage::Memory(format!("[rbp - {}]", stack_off - addend)) + } else { + reg_vars += 1; + VarStorage::Register(format!("{}", match meta { + TypeMetadata::u16 | TypeMetadata::i16 => call.args16()[reg_vars - 1].clone(), + TypeMetadata::u32 | TypeMetadata::i32 => call.args32()[reg_vars - 1].clone(), + TypeMetadata::u64 | TypeMetadata::i64 => call.args64()[reg_vars - 1].clone(), + TypeMetadata::Void => continue, + })) + } + }); + } + + for node in &self.nodes { + let ty = (node.as_any()).downcast_ref::>().unwrap(); + ty.compile(&mut info); + } + + vec![] + } +} \ No newline at end of file diff --git a/src/Target/x64/mod.rs b/src/Target/x64/mod.rs new file mode 100644 index 00000000..7271bbf6 --- /dev/null +++ b/src/Target/x64/mod.rs @@ -0,0 +1,17 @@ +//! The x64 Target: used for compiling ir and inline asm into x64 machine code + +use std::sync::Mutex; + +use super::{registry::TARGETS, Arch, TargetRegistry}; + +pub(crate) mod ir; +pub(crate) mod call; + +/// Initializes the x86-64 target +pub fn initializeX64Target() { + TARGETS.get_or_init( || { + Mutex::new(TargetRegistry::new()) + }); + + TARGETS.get().unwrap().lock().unwrap().set_inited(Arch::X86_64); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 648a4681..7e221678 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,8 @@ #![warn(unreachable_pub)] #![allow(clippy::redundant_field_names)] #![allow(rustdoc::invalid_html_tags)] +#![allow(rustdoc::invalid_codeblock_attributes)] +#![allow(non_camel_case_types)] //! # Ygen - Yet another Code Generator //! ### Description @@ -22,7 +24,7 @@ //! //! ###### Here is a quick introduction to the YGEN-IR: //! A function is defined like this: -//! ``` +//! ```no-run //! define i32 @add( i32 %0, i32 %1 ) { //! entry: //! %2 = add i32 %0, %1