From 35255a924acd6691ac540ecaa2f13bd1c58292e6 Mon Sep 17 00:00:00 2001 From: Jooris Hadeler Date: Mon, 20 Apr 2026 23:27:16 +0200 Subject: [PATCH] feat: add a backed using cranelift ir and codegen crates --- .gitignore | 2 + Cargo.lock | 556 +++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 + src/backend/cranelift.rs | 225 ++++++++++++++++ src/backend/mod.rs | 1 + src/main.rs | 52 +++- 6 files changed, 834 insertions(+), 8 deletions(-) create mode 100644 src/backend/cranelift.rs create mode 100644 src/backend/mod.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..560ca41 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +/**/*.o +a.out \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2245ad6..e62bf18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,562 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "compiler" version = "0.1.0" +dependencies = [ + "clap", + "cranelift-codegen", + "cranelift-frontend", + "cranelift-module", + "cranelift-native", + "cranelift-object", +] + +[[package]] +name = "cranelift-assembler-x64" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb5bdd1af46714e3224a017fabbbd57f70df4e840eb5ad6a7429dc456119d6" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a819599186e1b1a1f88d464e06045696afc7aa3e0cc018aa0b2999cb63d1d088" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e2c152d488e03c87b913bc2ed3414416eb1e0d66d61b49af60bf456a9665c7" +dependencies = [ + "cranelift-entity", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-bitset" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6559d4fbc253d1396e1f6beeae57fa88a244f02aaf0cde2a735afd3492d9b2e" +dependencies = [ + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d9315d98d6e0a64454d4c83be2ee0e8055c3f80c3b2d7bcad7079f281a06ff" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.16.1", + "libm", + "log", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89c00a88081c55e3087c45bebc77e0cc973de2d7b44ef6a943c7122647b89f5" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f77c497a1eb6273482aa1ac3b23cb8563ff04edb39ed5dfcfd28c8deff8f5" + +[[package]] +name = "cranelift-control" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498dc1f17a6910c88316d49c7176d8fa97cf10c30859c32a266040449317f963" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2acba797f6a46042ce82aaf7680d0c3567fe2001e238db9df649fd104a2727f" +dependencies = [ + "cranelift-bitset", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-frontend" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dca3df1d107d98d88f159ad1d5eaa2d5cdb678b3d5bcfadc6fc83d8ebb448ea" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f62dd18116d88bed649871feceda79dad7b59cc685ea8998c2b3e64d0e689602" + +[[package]] +name = "cranelift-module" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5ca0d214ecee44405ea9f0c65a5318b41ac469e8258fd9fe944e564c1c1b0b" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", +] + +[[package]] +name = "cranelift-native" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f843b80360d7fdf61a6124642af7597f6d55724cf521210c34af8a1c66daca6e" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-object" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d212d15015c374333b11b833111b7c7e686bfaec02385af53611050bce7e9d" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-control", + "cranelift-module", + "log", + "object", + "target-lexicon", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.131.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090ee5de58c6f17eb5e3a5ae8cf1695c7efea04ec4dd0ecba6a5b996c9bad7dc" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "gimli" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +dependencies = [ + "fnv", + "hashbrown 0.16.1", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "object" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63944c133d03f44e75866bbd160b95af0ec3f6a13d936d69d31c81078cbc5baf" +dependencies = [ + "crc32fast", + "hashbrown 0.16.1", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regalloc2" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de2c52737737f8609e94f975dee22854a2d5c125772d4b1cf292120f4d45c186" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.17.0", + "log", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasmtime-internal-core" +version = "44.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816a61a75275c6be435131fc625a4f5956daf24d9f9f59443e81cbef228929b3" +dependencies = [ + "hashbrown 0.16.1", + "libm", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index 1170539..7e6fc4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,9 @@ version = "0.1.0" edition = "2024" [dependencies] +clap = { version = "4.5", features = ["derive"] } +cranelift-codegen = "0.131.0" +cranelift-frontend = "0.131.0" +cranelift-module = "0.131.0" +cranelift-object = "0.131.0" +cranelift-native = "0.131.0" diff --git a/src/backend/cranelift.rs b/src/backend/cranelift.rs new file mode 100644 index 0000000..94476d0 --- /dev/null +++ b/src/backend/cranelift.rs @@ -0,0 +1,225 @@ +use std::collections::HashMap; + +use cranelift_codegen::{ + Context, + control::ControlPlane, + ir::{self, AbiParam, InstBuilder, types}, + settings::{self, Configurable}, +}; +use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable}; +use cranelift_module::{Linkage, Module, default_libcall_names}; +use cranelift_object::{ObjectBuilder, ObjectModule}; + +use crate::frontend::{ + ast::{BinaryOp, UnaryOp}, + sema::Ty, + typed_ast::*, +}; + +/// The backend responsible for lowering a `TypedModule` into Cranelift IR and +/// generating native machine code object files. +pub struct CraneliftBackend { + ctx: Context, + builder_context: FunctionBuilderContext, + module: ObjectModule, +} + +impl CraneliftBackend { + /// Initializes the Cranelift backend with the host's native instruction set architecture (ISA), + /// enabling speed optimizations and position-independent code (PIC) generation. + pub fn new() -> Self { + let mut flag_builder = settings::builder(); + flag_builder.set("is_pic", "true").unwrap(); + flag_builder.set("opt_level", "speed").unwrap(); + + let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| { + panic!("host machine is not supported: {}", msg); + }); + + let isa = isa_builder + .finish(settings::Flags::new(flag_builder)) + .unwrap(); + + let builder = ObjectBuilder::new(isa, "module", default_libcall_names()).unwrap(); + + Self { + ctx: Context::new(), + builder_context: FunctionBuilderContext::new(), + module: ObjectModule::new(builder), + } + } + + /// Compiles a fully typed AST module into native object code. + /// + /// Returns a tuple containing the generated Cranelift IR (as a human-readable string) and the assembled object file bytes. + pub fn compile_module(mut self, module: &TypedModule) -> (String, Vec) { + let mut ir_output = String::new(); + + for decl in &module.decls { + match decl { + TypedDecl::Function { + name, + params, + return_type, + body, + } => { + self.compile_function(params, return_type, body); + + // Run Cranelift's optimization passes before emitting the text IR + let mut ctrl_plane = ControlPlane::default(); + self.ctx + .optimize(self.module.isa(), &mut ctrl_plane) + .unwrap(); + + ir_output.push_str(&format!( + "; Function: {}\n{}", + name, + self.ctx.func.to_string() + )); + ir_output.push('\n'); + + let func_id = self + .module + .declare_function(name, Linkage::Export, &self.ctx.func.signature) + .unwrap(); + + self.module.define_function(func_id, &mut self.ctx).unwrap(); + self.module.clear_context(&mut self.ctx); + } + } + } + + let obj_bytes = self.module.finish().emit().unwrap(); + (ir_output, obj_bytes) + } + + /// Lowers a single function declaration into Cranelift IR. + /// + /// This sets up the function signature, ABI parameters, entry block, and declares the parameters as local variables. + fn compile_function(&mut self, params: &[(String, Ty)], return_type: &Ty, body: &TypedStmt) { + let mut sig = self.module.make_signature(); + + for (_, ty) in params { + sig.params.push(AbiParam::new(Self::lower_type(ty))); + } + + if return_type != &Ty::Unit { + sig.returns + .push(AbiParam::new(Self::lower_type(return_type))); + } + + self.ctx.func.signature = sig; + self.ctx.func.name = ir::UserFuncName::user(0, 0); + + let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context); + + let entry_block = builder.create_block(); + builder.append_block_params_for_function_params(entry_block); + builder.switch_to_block(entry_block); + builder.seal_block(entry_block); + + let mut vars = HashMap::new(); + + for (i, (param_name, ty)) in params.iter().enumerate() { + let var = builder.declare_var(Self::lower_type(ty)); + let val = builder.block_params(entry_block)[i]; + builder.def_var(var, val); + vars.insert(param_name.clone(), var); + } + + let mut trans = FunctionTranslator { builder, vars }; + + trans.translate_stmt(body); + trans.builder.finalize(); + } + + /// Maps our semantic types (`Ty`) to Cranelift's internal types (`ir::Type`). + fn lower_type(ty: &Ty) -> ir::Type { + match ty { + Ty::I8 | Ty::U8 => types::I8, + Ty::I16 | Ty::U16 => types::I16, + Ty::I32 | Ty::U32 => types::I32, + Ty::I64 | Ty::U64 => types::I64, + Ty::Bool => types::I8, // Booleans are represented as 8-bit integers + _ => unimplemented!("Unsupported type for Cranelift lowering: {:?}", ty), + } + } +} + +/// A visitor that traverses typed statements and expressions, emitting Cranelift IR instructions into the current function builder. +struct FunctionTranslator<'a> { + builder: FunctionBuilder<'a>, + vars: HashMap, +} + +impl<'a> FunctionTranslator<'a> { + /// Translates a statement, recursively compiling its inner components. + fn translate_stmt(&mut self, stmt: &TypedStmt) { + match stmt { + TypedStmt::Compound { inner } => { + for s in inner { + self.translate_stmt(s); + } + } + TypedStmt::Return { value } => { + if let Some(expr) = value { + let val = self.translate_expr(expr); + self.builder.ins().return_(&[val]); + } else { + self.builder.ins().return_(&[]); + } + } + } + } + + /// Translates an expression into a Cranelift IR value. + /// Emits appropriate computation instructions based on operators and operand types. + fn translate_expr(&mut self, expr: &TypedExpr) -> ir::Value { + match &expr.kind { + TypedExprKind::Identifier { name } => { + let var = self.vars.get(name).expect("Undeclared variable"); + self.builder.use_var(*var) + } + TypedExprKind::Integer { value } => { + let ty = CraneliftBackend::lower_type(&expr.ty); + self.builder.ins().iconst(ty, *value as i64) + } + TypedExprKind::Boolean { value } => { + let ty = CraneliftBackend::lower_type(&expr.ty); + self.builder.ins().iconst(ty, if *value { 1 } else { 0 }) + } + TypedExprKind::Unary { op, expr: inner } => { + let inner_val = self.translate_expr(inner); + match op { + UnaryOp::Neg => self.builder.ins().ineg(inner_val), + } + } + TypedExprKind::Binary { op, lhs, rhs } => { + let lhs_val = self.translate_expr(lhs); + let rhs_val = self.translate_expr(rhs); + + let is_signed = matches!(lhs.ty, Ty::I8 | Ty::I16 | Ty::I32 | Ty::I64); + + match op { + BinaryOp::Add => self.builder.ins().iadd(lhs_val, rhs_val), + BinaryOp::Sub => self.builder.ins().isub(lhs_val, rhs_val), + BinaryOp::Mul => self.builder.ins().imul(lhs_val, rhs_val), + BinaryOp::Div => { + if is_signed { + self.builder.ins().sdiv(lhs_val, rhs_val) + } else { + self.builder.ins().udiv(lhs_val, rhs_val) + } + } + BinaryOp::Rem => { + if is_signed { + self.builder.ins().srem(lhs_val, rhs_val) + } else { + self.builder.ins().urem(lhs_val, rhs_val) + } + } + } + } + } + } +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs new file mode 100644 index 0000000..6d12854 --- /dev/null +++ b/src/backend/mod.rs @@ -0,0 +1 @@ +pub mod cranelift; diff --git a/src/main.rs b/src/main.rs index 26f9f95..3475d9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,34 @@ -use std::{env::args, fs::read_to_string, process::exit}; +use std::{fs::read_to_string, path::PathBuf, process::exit}; + +use clap::Parser as ClapParser; use crate::frontend::parser::Parser; use crate::frontend::sema::Sema; +pub mod backend; pub mod frontend; -fn main() { - let Some(path) = args().nth(1) else { - eprintln!("usage: compiler "); - exit(1); - }; +use crate::backend::cranelift::CraneliftBackend; - let content = read_to_string(&path).unwrap_or_else(|err| { +#[derive(ClapParser)] +#[command(version, about, long_about = None)] +struct Cli { + /// The input source file to compile + input: String, + + /// The output file path + #[arg(short, long)] + output: Option, + + /// Emit Cranelift IR instead of an object file + #[arg(long)] + emit_ir: bool, +} + +fn main() { + let cli = Cli::parse(); + + let content = read_to_string(&cli.input).unwrap_or_else(|err| { eprintln!("error: failed to read source file ({:?})", err); exit(1); }); @@ -38,5 +55,24 @@ fn main() { exit(1); } - println!("{:#?}", typed_module); + let backend = CraneliftBackend::new(); + let (ir, obj_bytes) = backend.compile_module(&typed_module); + + if cli.emit_ir { + println!("{}", ir); + } else { + let output_path = cli.output.unwrap_or_else(|| { + PathBuf::from(&cli.input) + .with_extension("o") + .to_string_lossy() + .into_owned() + }); + + std::fs::write(&output_path, obj_bytes).unwrap_or_else(|err| { + eprintln!("error: failed to write object file: {:?}", err); + exit(1); + }); + + println!("Generated object file: {}", output_path); + } }