use crate::{common::{Loc, loc::LocBox}, parser::ast::{Ast, Ident, Program, Punctuation, expr::{Expr, IfBranchExpr}, literal::Literal, statement::{ConstDataTyp, Function, Statement, get_constant_data_as_bytes}, typ::Type}, targets::Target}; use std::{collections::HashMap, fs::File, io::Write, process}; pub struct AsmGen; const RUNTIME_CODE: &'static str = include_str!("./runtime.s"); impl Target for AsmGen { fn new() -> Self { Self {} } fn get_target_triple() -> &'static str { "x86_64-asmgen-linux" } fn get_int_ext(&self) -> &'static str { "s" } fn write_code(&mut self, program: &crate::parser::ast::Program, f: &mut File) -> anyhow::Result<()> { writeln!(f, "bits 64")?; writeln!(f, "global _start")?; self.write_extern_func_decls(program, f)?; writeln!(f, "section .text")?; writeln!(f, "{}", RUNTIME_CODE)?; for item in &program.ast.0 { match item { Ast::Statement(stat) => { match stat.inner() { Statement::Fn(func) => self.write_func(program, f, func.clone(), stat.loc().clone())?, _ => () } } _ => () } } writeln!(f, "section .data")?; self.write_constants(program, f)?; self.write_literals(program, f)?; writeln!(f, "section .data")?; self.write_statics(program, f)?; Ok(()) } fn compile(&mut self, from: &std::path::Path, to: &std::path::Path) -> anyhow::Result<()> { let mut cmd = std::process::Command::new("nasm"); let cmd = cmd.args(&[ "-felf64", "-o", to.to_string_lossy().to_string().as_str(), from.to_string_lossy().to_string().as_str(), ]); info!("Running: {} {}", cmd.get_program().to_string_lossy(), cmd.get_args().map(|f| f.to_string_lossy().to_string()).collect::>().join(" ")); cmd.output()?; Ok(()) } fn link(&mut self, from: Vec, to: &std::path::Path) -> anyhow::Result<()> { let mut cmd = std::process::Command::new("ld"); cmd.args(&[ "-o", to.to_string_lossy().to_string().as_str(), ]); for item in &from { cmd.arg(item.to_string_lossy().to_string().as_str()); } info!("Running: {} {}", cmd.get_program().to_string_lossy(), cmd.get_args().map(|f| f.to_string_lossy().to_string()).collect::>().join(" ")); cmd.output()?; Ok(()) } } // also used for args #[derive(Debug, Clone)] pub enum VarMapT { Stack(usize, Type), } pub struct FunctionCtx { vars: HashMap, stack_offset: usize, used_registers: usize, loop_level: usize, // used for loops if_level: usize, // used for if stats cmp_level: usize, // used for logical comparisons pub emit_short_circuit_label: bool, pub is_last_item: bool, } impl FunctionCtx { pub fn new() -> Self { Self { vars: Default::default(), stack_offset: 0, used_registers: 0, loop_level: 0, if_level: 0, cmp_level: 0, emit_short_circuit_label: true, is_last_item: false } } pub fn insert_stack(&mut self, name: &Ident, size: usize, typ: Type) -> usize { let offset = self.stack_offset; self.vars.insert(name.clone(), VarMapT::Stack(self.stack_offset, typ)); self.stack_offset += size; offset } pub fn get(&self, name: &Ident) -> Option<&VarMapT> { self.vars.get(name) } pub fn get_stack_offset(&self) -> usize { self.stack_offset } pub fn register_id_to_str(&self, id: usize) -> &str { match id { 0 => "rdi", 1 => "rsi", 2 => "rdx", 3 => "rcx", 4 => "r8", 5 => "r9", _ => unreachable!() } } pub fn inc_loop_level(&mut self) -> usize { self.loop_level += 1; self.loop_level } pub fn dec_loop_level(&mut self) -> usize { self.loop_level -= 1; self.loop_level } pub fn loop_level(&self) -> usize { self.loop_level } pub fn inc_if_level(&mut self) -> usize { self.if_level += 1; self.if_level } pub fn dec_if_level(&mut self) -> usize { self.if_level -= 1; self.if_level } pub fn if_level(&self) -> usize { self.if_level } pub fn inc_cmp_level(&mut self) -> usize { self.if_level += 1; self.if_level } pub fn dec_cmp_level(&mut self) -> usize { self.if_level -= 1; self.if_level } pub fn cmp_level(&self) -> usize { self.cmp_level } } impl AsmGen { pub fn write_func(&self, program: &Program, f: &mut File, func: Function, loc: Loc) -> anyhow::Result<()> { if let Some(body) = &func.body { let mut fc = FunctionCtx::new(); let name = if let Some(struct_name) = &func.struct_name { format!("{}${}", struct_name, func.name) } else { func.name.to_string() }; writeln!(f, "{}: ; {} {}", name, loc, func.get_full_name_pretty())?; if body.0.is_empty() { writeln!(f, " ret")?; } else { let mut buf: Vec = Vec::new(); let mut last_item = None; for (i, param) in func.params.iter().enumerate() { let typ = param.1.clone(); let typ = typ.inner().clone(); let offset = fc.insert_stack(¶m.0.clone(), typ.size_of(program)?, typ.clone()); writeln!(&mut buf, " mov [rsp+{offset}], {} ; func arg", fc.register_id_to_str(i))?; } let body = func.body.expect("Safe as its checked already").0; for (i, item) in body.iter().enumerate() { if i == body.len() - 1 { fc.is_last_item = true; } self.write_ast(program, &mut buf, &mut fc, item)?; last_item = Some(item.clone()); } if fc.stack_offset > 0 { writeln!(f, " sub rsp, {}", fc.stack_offset)?; } f.write(&buf)?; if fc.stack_offset > 0 { writeln!(f, " add rsp, {}", fc.stack_offset)?; } match last_item { Some(Ast::Expr(expr)) => { match expr.inner() { Expr::Return(_) => (), _ => writeln!(f, " ret")?, } } _ => () } // while writing the return expr, it changes is_last_item to false if it didnt write ret // and it was the last item in a body if !fc.is_last_item { writeln!(f, " ret")?; } } } else { writeln!(f, " ret")?; } writeln!(f, "\n")?; Ok(()) } pub fn write_extern_func_decls(&self, program: &Program, f: &mut File) -> anyhow::Result<()> { for (name, func) in &program.functions { match func.inner().qual_extern { Some(_) if func.inner().body.is_some() => { writeln!(f, "global {name}")?; } Some(_) if func.inner().body.is_none() => { writeln!(f, "extern {name}")?; } _ => () } } Ok(()) } pub fn write_ast(&self, program: &Program, f: &mut impl Write, fc: &mut FunctionCtx, ast: &Ast) -> anyhow::Result<()> { match ast { Ast::Expr(expr) => self.write_expr(program, f, fc, expr.inner())?, Ast::Statement(stat) => self.write_stat(program, f, fc, stat.inner())?, }; Ok(()) } pub fn write_expr(&self, program: &Program, f: &mut impl Write, fc: &mut FunctionCtx, expr: &Expr) -> anyhow::Result<()> { match expr { Expr::Cast { .. } => (), Expr::Literal(id, val) => { match val { Literal::Ident(_) => unreachable!(), Literal::Array(_) | Literal::String(_) | Literal::ArrayRepeat { .. } => { writeln!(f, " lea rax, [rel mcl_lit_{id}]")?; } Literal::Bool(v) => { writeln!(f, " mov rax, {} ; {}", *v as u8, v)?; } Literal::Number(_) | Literal::Char(_) => { writeln!(f, " mov rax, [rel mcl_lit_{id}]")?; } } } Expr::Struct(id, strct) => { writeln!(f, " lea r10, [rel mcl_lit_{id}]")?; let strct_t = program.structs.get(&strct.path.0[0]).unwrap(); for (name, expr) in &strct.fields { self.write_expr(program, f, fc, expr.inner())?; let offset = strct_t.inner().get_offset_of(program, name)?; writeln!(f, " mov [r10+{offset}], rax")?; } } Expr::PtrFieldAccess { left, right, offset } => { self.write_expr(program, f, fc, left.clone().unwrap().inner())?; writeln!(f, " add rax, {offset} ; ->{:?}", right.inner())?; }, Expr::FieldAccess { left, right, offset } => { self.write_expr(program, f, fc, left.clone().unwrap().inner())?; writeln!(f, " mov rax, [rel rax] ; .{:?}", right.inner())?; writeln!(f, " add rax, {offset} ; .{:?}", right.inner())?; }, Expr::InfLoop { body } => { let sl = fc.inc_loop_level(); writeln!(f, ".L{sl}_test: ; inf loop (named test for tehnical reason)")?; for item in &body.0 { self.write_ast(program, f, fc, item)?; } writeln!(f, ".L{sl}_end: ; inf loop")?; } Expr::ForLoop { init, test, on_loop, body } => { let sl = fc.inc_loop_level(); writeln!(f, ".L{sl}_init: ; for loop")?; self.write_ast(program, f, fc, &init)?; writeln!(f, ".L{sl}_test: ; for loop")?; writeln!(f, " xor rax, rax")?; self.write_expr(program, f, fc, &test.inner())?; writeln!(f, " test rax, rax")?; writeln!(f, " jz .L{sl}_end")?; writeln!(f, ".L{sl}_on_loop: ; for loop")?; self.write_expr(program, f, fc, &on_loop.inner())?; for item in &body.0 { self.write_ast(program, f, fc, item)?; } writeln!(f, ".L{sl}_end: ; for loop")?; } Expr::WhileLoop { test, body } => { let sl = fc.inc_loop_level(); writeln!(f, ".L{sl}_test: ; while loop")?; writeln!(f, " xor rax, rax")?; self.write_expr(program, f, fc, &test.inner())?; writeln!(f, " test rax, rax")?; writeln!(f, " jz .L{sl}_end")?; for item in &body.0 { self.write_ast(program, f, fc, item)?; } writeln!(f, ".L{sl}_end: ; while loop")?; } Expr::Continue => { let sl = fc.loop_level(); writeln!(f, " jmp .L{sl}_test ; continue")?; } Expr::Break => { let sl = fc.loop_level(); writeln!(f, " jmp .L{sl}_end ; break")?; } Expr::If(ifs) => { let cl = fc.inc_if_level(); writeln!(f, " xor rax, rax")?; self.write_expr(program, f, fc, ifs.test.inner())?; writeln!(f, " test rax, rax")?; if ifs.else_if.is_some() { writeln!(f, " jz .C{cl}_branch0")?; } else { writeln!(f, " jz .C{cl}_end")?; } for item in &ifs.body.0 { self.write_ast(program, f, fc, item)?; } self.write_expr(program, f, fc, ifs.test.inner())?; if ifs.else_if.is_some() { writeln!(f, " jmp .C{cl}_end")?; } fn x(slf: &AsmGen, program: &Program, f: &mut impl Write, fc: &mut FunctionCtx, els: &Option, depth: usize) -> anyhow::Result<()> { let cl = fc.if_level(); if let Some(els) = els { match els { IfBranchExpr::Else(els) => { for item in &els.0 { writeln!(f, ".C{cl}_branch{depth}: ; if")?; slf.write_ast(program, f, fc, item)?; } } IfBranchExpr::ElseIf(elsif) => { slf.write_expr(program, f, fc, elsif.test.inner())?; writeln!(f, " test rax, rax")?; if elsif.else_if.is_some() { writeln!(f, " jz .C{cl}_branch{}", depth + 1)?; } else { writeln!(f, " jz .C{cl}_end")?; } for item in &elsif.body.0 { slf.write_ast(program, f, fc, item)?; } writeln!(f, " jmp .C{cl}_end")?; x(slf, program, f, fc, &elsif.else_if, depth+1)?; } } } Ok(()) } x(self, program, f, fc, &ifs.else_if, 0)?; writeln!(f, ".C{cl}_end: ; if")?; } Expr::Group(grp) => { self.write_expr(program, f, fc, grp.inner())?; } Expr::Return(ret) => { if let Some(ret) = &**ret { self.write_expr(program, f, fc, ret.inner())?; } if !fc.is_last_item { writeln!(f, " ret")?; } else { fc.is_last_item = false; } } Expr::Call { path, params } => { for (i, param) in params.0.iter().enumerate() { self.write_expr(program, f, fc, param.inner())?; if i <= 5 { let reg = fc.register_id_to_str(i); writeln!(f, " mov {reg}, rax")?; } else { writeln!(f, " push rax")?; } } writeln!(f, " call {}", path.inner().unwrap_path().display_asm_compat())?; } Expr::UnOp { typ, right } => { self.write_expr(program, f, fc, right.inner())?; match typ { Punctuation::Not => { writeln!(f, " test rax, rax ; logical not")?; writeln!(f, " sete al")?; writeln!(f, " movzx rax, al")?; }, Punctuation::Plus => { writeln!(f, " mov rdx, rax ; +x")?; writeln!(f, " sar rdx, 63")?; writeln!(f, " xor rax, rdx")?; writeln!(f, " sub rax, rdx")?; }, Punctuation::Minus => { writeln!(f, " neg rax ; -x")?; }, Punctuation::Ampersand => { writeln!(f, " ; noop?")?; }, Punctuation::Star => { writeln!(f, " ; noop deref")?; }, _ => unreachable!() } } Expr::BinOp { typ, left, right, signed } => { self.write_expr(program, f, fc, left.inner())?; writeln!(f, " mov r10, rax")?; self.write_expr(program, f, fc, right.inner())?; match typ { Punctuation::Plus => { writeln!(f, " add rax, r10")?; }, Punctuation::Minus => { writeln!(f, " sub rax, r10")?; }, Punctuation::Div => { writeln!(f, " mov rdi, rax")?; writeln!(f, " mov rax, r10 ")?; if *signed { writeln!(f, " cqo")?; writeln!(f, " idiv rdi")?; } else { writeln!(f, " xor rdx, rdx")?; writeln!(f, " div rdi")?; } }, Punctuation::Star => { writeln!(f, " mov rdi, rax")?; writeln!(f, " mov rax, r10 ")?; if *signed { writeln!(f, " imul r10 ")?; } else { writeln!(f, " mul r10 ")?; } }, Punctuation::Mod => { writeln!(f, " mov rdi, rax")?; writeln!(f, " mov rax, r10 ")?; if *signed { writeln!(f, " cqo")?; writeln!(f, " idiv rdi")?; } else { writeln!(f, " xor rdx, rdx")?; writeln!(f, " div rdi")?; } writeln!(f, " mov rax, rdx")?; }, Punctuation::Shl => { writeln!(f, " shl r10, rax")?; }, Punctuation::Shr => { writeln!(f, " shr r10, rax")?; }, Punctuation::AndAnd => { let l = fc.cmp_level(); let should_emit = fc.emit_short_circuit_label; self.write_expr(program, f, fc, left.inner())?; writeln!(f, " test rax, rax")?; writeln!(f, " jz .M{l}_false")?; fc.emit_short_circuit_label = false; self.write_expr(program, f, fc, right.inner())?; fc.emit_short_circuit_label = should_emit; if !should_emit { writeln!(f, " setnz al")?; writeln!(f, " movzx rax, al")?; writeln!(f, " jmp .M{l}_sc")?; writeln!(f, ".M{l}_true:")?; writeln!(f, " mov rax, 1")?; writeln!(f, ".M{l}_false:")?; writeln!(f, " xor rax, rax")?; writeln!(f, ".M{l}_sc:")?; fc.inc_cmp_level(); } }, Punctuation::OrOr => { let l = fc.cmp_level(); let should_emit = fc.emit_short_circuit_label; self.write_expr(program, f, fc, left.inner())?; writeln!(f, " cmp rax, rax")?; writeln!(f, " jnz .M{l}_true")?; fc.emit_short_circuit_label = false; self.write_expr(program, f, fc, right.inner())?; fc.emit_short_circuit_label = should_emit; if !should_emit { writeln!(f, " setnz al")?; writeln!(f, " movzx rax, al")?; writeln!(f, " jmp .M{l}_sc")?; writeln!(f, ".M{l}_true:")?; writeln!(f, " mov rax, 1")?; writeln!(f, ".M{l}_false:")?; writeln!(f, " xor rax, rax")?; writeln!(f, ".M{l}_sc:")?; fc.inc_cmp_level(); } }, Punctuation::Ampersand => {}, Punctuation::Or => {}, Punctuation::Xor => {}, Punctuation::AddEq => {}, Punctuation::SubEq => {}, Punctuation::DivEq => {}, Punctuation::MulEq => {}, Punctuation::ModEq => {}, Punctuation::ShlEq => {}, Punctuation::ShrEq => {}, Punctuation::AndEq => {}, Punctuation::OrEq => {}, Punctuation::XorEq => {}, Punctuation::Eq => { self.write_expr(program, f, fc, right.inner())?; let VarMapT::Stack(offset, _) = fc.get(&left.inner().unwrap_path().0.clone()[0]).unwrap(); writeln!(f, "mov [rbx+{offset}], rax")?; }, Punctuation::EqEq => {}, Punctuation::Lt => {}, Punctuation::Gt => {}, Punctuation::Le => {}, Punctuation::Ge => {}, _ => unreachable!() } } Expr::Path(path) => { let ident = path.0.last().unwrap().clone(); if let Some(var) = fc.get(&ident) { match var { VarMapT::Stack(offset, typ) => { match typ { Type::Builtin { .. } => writeln!(f, " lea rax, [rsp + {offset}]")?, _ => writeln!(f, " mov rax, [rsp + {offset}]")?, } } } } else if let Some(_) = program.get_const_var(&ident) { writeln!(f, " lea rax, [rel {ident}]")?; } else { panic!() } } v => unreachable!("{v:?}") } Ok(()) } pub fn write_stat(&self, program: &Program, f: &mut impl Write, fc: &mut FunctionCtx, stat: &Statement) -> anyhow::Result<()> { match stat { Statement::Let(lt) => { let typ = lt.typ.clone().unwrap(); let loc = typ.loc().clone(); let typ = typ.inner().clone(); let offset = fc.insert_stack(<.name, typ.size_of(program)?, typ.clone()); if let Some(value) = <.val { self.write_expr(program, f, fc, value.inner())?; writeln!(f, " mov [rsp+{offset}], rax ; {loc} let {};", lt.name)?; } } _ => unreachable!() } Ok(()) } pub fn write_constants(&self, program: &Program, f: &mut File) -> anyhow::Result<()> { for (name, constant) in &program.const_vars { writeln!(f, "{name}: ; const")?; let bytes = get_constant_data_as_bytes(program, &HashMap::new(), constant.val.clone(), false, false)?; fn x(bytes: &ConstDataTyp, f: &mut File) -> anyhow::Result<()> { match bytes { ConstDataTyp::Array(arr) => { for v in arr { x(v, f)?; } } ConstDataTyp::Bytes(bytes) => { write!(f, " db ")?; for (i, b) in bytes.into_iter().enumerate() { if i != 0 { write!(f, ", ")?; } write!(f, "0x{:02x}", b)?; } writeln!(f, "")?; } ConstDataTyp::AddrOfFunc(name) | ConstDataTyp::AddrOfConst(name) | ConstDataTyp::AddrOfStatic(name) => { writeln!(f, " dq {name}")?; }, ConstDataTyp::Variable(..) => unreachable!(), } /* */ Ok(()) } x(&bytes, f)?; } Ok(()) } pub fn write_statics(&self, program: &Program, f: &mut File) -> anyhow::Result<()> { for (name, statc) in &program.static_vars { writeln!(f, "{name}: ; static")?; let bytes = get_constant_data_as_bytes(program, &HashMap::new(), statc.val.clone(), false, false)?; fn x(bytes: &ConstDataTyp, f: &mut File) -> anyhow::Result<()> { match bytes { ConstDataTyp::Array(arr) => { for v in arr { x(v, f)?; } } ConstDataTyp::Bytes(bytes) => { write!(f, " db ")?; for (i, b) in bytes.into_iter().enumerate() { if i != 0 { write!(f, ", ")?; } write!(f, "0x{:02x}", b)?; } writeln!(f, "")?; } ConstDataTyp::AddrOfFunc(name) | ConstDataTyp::AddrOfConst(name) | ConstDataTyp::AddrOfStatic(name) => { writeln!(f, " dq {name}")?; }, ConstDataTyp::Variable(..) => unreachable!(), } /* */ Ok(()) } x(&bytes, f)?; } Ok(()) } pub fn write_literals(&self, program: &Program, f: &mut File) -> anyhow::Result<()> { fn w(bytes: ConstDataTyp, f: &mut File) -> anyhow::Result<()> { match bytes { ConstDataTyp::Array(arr) => { for v in arr { w(v, f)?; } } ConstDataTyp::Bytes(bytes) => { write!(f, " db ")?; for (i, b) in bytes.into_iter().enumerate() { if i != 0 { write!(f, ", ")?; } write!(f, "0x{:02x}", b)?; } writeln!(f, "")?; } ConstDataTyp::AddrOfFunc(name) | ConstDataTyp::AddrOfConst(name) | ConstDataTyp::AddrOfStatic(name) => { writeln!(f, " dq {name}")?; }, ConstDataTyp::Variable(name, size) => { writeln!(f, " resb {size} ; {name}")?; } } Ok(()) } for lit in &program.literals { writeln!(f, "mcl_lit_{}:", lit.0)?; w(get_constant_data_as_bytes(program, &HashMap::new(), LocBox::new(&Loc::default(), Expr::Literal(lit.0.clone(), lit.1.clone())), false, false)?, f)?; } for lit in &program.struct_literals { writeln!(f, "mcl_lit_{}:", lit.0)?; w(get_constant_data_as_bytes(program, &HashMap::new(), LocBox::new(&Loc::default(), Expr::Struct(lit.0.clone(), lit.1.clone())), false, false)?, f)?; } Ok(()) } }