mclangc/src/targets/x86_64/asmgen/linux/mod.rs
MCorange 081ff9a27a Add external functions (both import and export), make includes work
still need to fix literal arrays, it has the same problem as struct
literals had with moving the literal into memory and modifying the
memory with variables
2026-01-29 22:37:04 +02:00

701 lines
28 KiB
Rust

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::<Vec<String>>().join(" "));
cmd.output()?;
Ok(())
}
fn link(&mut self, from: Vec<std::path::PathBuf>, 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::<Vec<String>>().join(" "));
cmd.output()?;
Ok(())
}
}
// also used for args
#[derive(Debug, Clone)]
pub enum VarMapT {
Stack(usize, Type),
}
pub struct FunctionCtx {
vars: HashMap<Ident, VarMapT>,
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<u8> = 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(&param.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<IfBranchExpr>, 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(&lt.name, typ.size_of(program)?, typ.clone());
if let Some(value) = &lt.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(())
}
}