diff --git a/Cargo.lock b/Cargo.lock index 224c22b..a81fc24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,30 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "anyhow" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "base64" version = "0.21.7" @@ -32,6 +50,12 @@ version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" @@ -95,6 +119,114 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cranelift-bforest" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1277fbfa94bc82c8ec4af2ded3e639d49ca5f7f3c7eeab2c66accd135ece4e70" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6e8c31ad3b2270e9aeec38723888fe1b0ace3bea2b06b3f749ccf46661d3220" +dependencies = [ + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.13.2", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ac5ac30d62b2d66f12651f6b606dbdfd9c2cfd0908de6b387560a277c5c9da" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd82b8b376247834b59ed9bdc0ddeb50f517452827d4a11bccf5937b213748b8" + +[[package]] +name = "cranelift-entity" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40099d38061b37e505e63f89bab52199037a72b931ad4868d9089ff7268660b0" + +[[package]] +name = "cranelift-frontend" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a25d9d0a0ae3079c463c34115ec59507b4707175454f0eee0891e83e30e82d" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80de6a7d0486e4acbd5f9f87ec49912bf4c8fb6aea00087b989685460d4469ba" + +[[package]] +name = "cranelift-jit" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca96b05988aa057eda09a817a6e31915fabd7f476b513123aff08053cd193dd" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-module", + "cranelift-native", + "libc", + "log", + "region", + "target-lexicon", + "wasmtime-jit-icache-coherence", + "windows-sys 0.45.0", +] + +[[package]] +name = "cranelift-module" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5112c0be9cc5da064e0620570d67852f11ce44f2e572a58ecf7f11df73978b8" +dependencies = [ + "anyhow", + "cranelift-codegen", +] + +[[package]] +name = "cranelift-native" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6b03e0e03801c4b3fd8ce0758a94750c07a44e7944cc0ffbf0d3f2e7c79b00" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -158,6 +290,12 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "2.3.0" @@ -265,6 +403,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -289,6 +436,17 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +dependencies = [ + "fallible-iterator", + "indexmap 1.9.3", + "stable_deref_trait", +] + [[package]] name = "h2" version = "0.3.27" @@ -301,13 +459,28 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -517,6 +690,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -611,6 +794,10 @@ dependencies = [ name = "lux" version = "0.1.0" dependencies = [ + "cranelift-codegen", + "cranelift-frontend", + "cranelift-jit", + "cranelift-module", "lsp-server", "lsp-types", "rand", @@ -618,10 +805,20 @@ dependencies = [ "rustyline", "serde", "serde_json", + "target-lexicon", "tempfile", "thiserror", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.8.0" @@ -849,6 +1046,30 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "regalloc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80535183cae11b149d618fbd3c37e38d7cda589d82d7769e196ca9a9042d7621" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags 1.3.2", + "libc", + "mach", + "winapi", +] + [[package]] name = "reqwest" version = "0.11.27" @@ -1061,6 +1282,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slice-group-by" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" + [[package]] name = "smallvec" version = "1.15.1" @@ -1142,6 +1369,12 @@ dependencies = [ "libc", ] +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.25.0" @@ -1308,6 +1541,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.1" @@ -1417,7 +1656,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder", "wasmparser", ] @@ -1430,10 +1669,21 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.10.0", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "semver", ] +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecae978b13f7f67efb23bd827373ace4578f2137ec110bbf6a4a7cde4121bbd" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "web-sys" version = "0.3.85" @@ -1444,12 +1694,43 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[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.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1495,6 +1776,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -1543,6 +1839,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -1561,6 +1863,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -1579,6 +1887,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -1609,6 +1923,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -1627,6 +1947,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -1645,6 +1971,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -1663,6 +1995,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -1719,7 +2057,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.13.0", "prettyplease", "syn", "wasm-metadata", @@ -1750,7 +2088,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.10.0", - "indexmap", + "indexmap 2.13.0", "log", "serde", "serde_derive", @@ -1769,7 +2107,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", diff --git a/Cargo.toml b/Cargo.toml index 4c62ab3..853f2b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,13 @@ serde_json = "1" rand = "0.8" reqwest = { version = "0.11", features = ["blocking", "json"] } +# Cranelift for native compilation +cranelift-codegen = "0.95" +cranelift-frontend = "0.95" +cranelift-module = "0.95" +cranelift-jit = "0.95" +target-lexicon = "0.12" + [dev-dependencies] tempfile = "3" diff --git a/flake.nix b/flake.nix index 22e4245..170a910 100644 --- a/flake.nix +++ b/flake.nix @@ -22,6 +22,8 @@ rustToolchain cargo-watch cargo-edit + pkg-config + openssl ]; RUST_BACKTRACE = "1"; diff --git a/src/compiler.rs b/src/compiler.rs new file mode 100644 index 0000000..b8e0687 --- /dev/null +++ b/src/compiler.rs @@ -0,0 +1,536 @@ +//! Cranelift-based native compiler for Lux +//! +//! This module compiles Lux programs to native machine code using Cranelift. +//! Currently supports a subset of the language for performance-critical code. + +use crate::ast::{Expr, Program, Declaration, FunctionDecl, BinaryOp, UnaryOp, LiteralKind, Statement}; +use cranelift_codegen::ir::{AbiParam, InstBuilder, Value, types}; +use cranelift_codegen::ir::condcodes::IntCC; +use cranelift_codegen::Context; +use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable}; +use cranelift_jit::{JITBuilder, JITModule}; +use cranelift_module::{Module, Linkage, FuncId}; +use std::collections::HashMap; + +/// Errors that can occur during compilation +#[derive(Debug)] +pub struct CompileError { + pub message: String, +} + +impl std::fmt::Display for CompileError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Compile error: {}", self.message) + } +} + +impl std::error::Error for CompileError {} + +/// The JIT compiler for Lux +pub struct JitCompiler { + /// The Cranelift JIT module + module: JITModule, + /// Builder context (reusable) + builder_context: FunctionBuilderContext, + /// Cranelift context (reusable) + ctx: Context, + /// Compiled function pointers + functions: HashMap, + /// Function IDs for linking + func_ids: HashMap, +} + +impl JitCompiler { + /// Create a new JIT compiler + pub fn new() -> Result { + let builder = JITBuilder::new(cranelift_module::default_libcall_names()) + .map_err(|e| CompileError { message: e.to_string() })?; + + let module = JITModule::new(builder); + + Ok(Self { + module, + builder_context: FunctionBuilderContext::new(), + ctx: Context::new(), + functions: HashMap::new(), + func_ids: HashMap::new(), + }) + } + + /// Compile a Lux function to native code + pub fn compile_function(&mut self, func: &FunctionDecl) -> Result<*const u8, CompileError> { + // Check if already compiled + if let Some(ptr) = self.functions.get(&func.name.name) { + return Ok(*ptr); + } + + // Create function signature + let mut sig = self.module.make_signature(); + for _ in &func.params { + sig.params.push(AbiParam::new(types::I64)); + } + sig.returns.push(AbiParam::new(types::I64)); + + // Declare the function + let func_id = self.module + .declare_function(&func.name.name, Linkage::Local, &sig) + .map_err(|e| CompileError { message: e.to_string() })?; + + self.func_ids.insert(func.name.name.clone(), func_id); + + // Clear context for reuse + self.ctx.clear(); + self.ctx.func.signature = sig; + + // Clone func_ids for use in closure + let func_ids = self.func_ids.clone(); + + // Build the function body + { + let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.builder_context); + + // Create entry block + 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); + + // Map parameter names to values + let mut variables: HashMap = HashMap::new(); + let params = builder.block_params(entry_block).to_vec(); + + for (i, param) in func.params.iter().enumerate() { + let var = Variable::from_u32(i as u32); + builder.declare_var(var, types::I64); + builder.def_var(var, params[i]); + variables.insert(param.name.name.clone(), var); + } + + // Compile the function body + let var_count = variables.len(); + let result = compile_expr(&mut builder, &func.body, &mut variables, var_count, &func_ids, &mut self.module)?; + + // Return the result + builder.ins().return_(&[result]); + + builder.finalize(); + } + + // Compile to machine code + self.module + .define_function(func_id, &mut self.ctx) + .map_err(|e| CompileError { message: e.to_string() })?; + + self.module.clear_context(&mut self.ctx); + + // Finalize and get the function pointer + self.module.finalize_definitions() + .map_err(|e| CompileError { message: e.to_string() })?; + + let ptr = self.module.get_finalized_function(func_id); + self.functions.insert(func.name.name.clone(), ptr); + + Ok(ptr) + } + + /// Compile and run a simple function that takes no args and returns an i64 + pub fn compile_and_run(&mut self, func: &FunctionDecl) -> Result { + let ptr = self.compile_function(func)?; + + // Cast to function pointer and call + let func_ptr: fn() -> i64 = unsafe { std::mem::transmute(ptr) }; + Ok(func_ptr()) + } + + /// Compile a program and return pointers to all compiled functions + pub fn compile_program(&mut self, program: &Program) -> Result<(), CompileError> { + // First pass: declare all functions + for decl in &program.declarations { + if let Declaration::Function(func) = decl { + let mut sig = self.module.make_signature(); + for _ in &func.params { + sig.params.push(AbiParam::new(types::I64)); + } + sig.returns.push(AbiParam::new(types::I64)); + + let func_id = self.module + .declare_function(&func.name.name, Linkage::Local, &sig) + .map_err(|e| CompileError { message: e.to_string() })?; + + self.func_ids.insert(func.name.name.clone(), func_id); + } + } + + // Second pass: compile all functions + for decl in &program.declarations { + if let Declaration::Function(func) = decl { + self.compile_function_body(func)?; + } + } + + // Finalize + self.module.finalize_definitions() + .map_err(|e| CompileError { message: e.to_string() })?; + + // Store function pointers + for (name, func_id) in &self.func_ids { + let ptr = self.module.get_finalized_function(*func_id); + self.functions.insert(name.clone(), ptr); + } + + Ok(()) + } + + /// Compile a function body (assumes function is already declared) + fn compile_function_body(&mut self, func: &FunctionDecl) -> Result<(), CompileError> { + let func_id = *self.func_ids.get(&func.name.name).ok_or_else(|| CompileError { + message: format!("Function not declared: {}", func.name.name), + })?; + + // Create signature + let mut sig = self.module.make_signature(); + for _ in &func.params { + sig.params.push(AbiParam::new(types::I64)); + } + sig.returns.push(AbiParam::new(types::I64)); + + // Clear and set up context + self.ctx.clear(); + self.ctx.func.signature = sig; + + // Clone func_ids for use in closure + let func_ids = self.func_ids.clone(); + + // Build function + { + 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 variables: HashMap = HashMap::new(); + let params = builder.block_params(entry_block).to_vec(); + + for (i, param) in func.params.iter().enumerate() { + let var = Variable::from_u32(i as u32); + builder.declare_var(var, types::I64); + builder.def_var(var, params[i]); + variables.insert(param.name.name.clone(), var); + } + + let var_count = variables.len(); + let result = compile_expr(&mut builder, &func.body, &mut variables, var_count, &func_ids, &mut self.module)?; + builder.ins().return_(&[result]); + + builder.finalize(); + } + + // Define the function + self.module + .define_function(func_id, &mut self.ctx) + .map_err(|e| CompileError { message: e.to_string() })?; + + self.module.clear_context(&mut self.ctx); + + Ok(()) + } + + /// Get a compiled function pointer by name + pub fn get_function(&self, name: &str) -> Option<*const u8> { + self.functions.get(name).copied() + } + + /// Call a compiled function with given i64 arguments + pub unsafe fn call_function(&self, name: &str, args: &[i64]) -> Result { + let ptr = self.get_function(name).ok_or_else(|| CompileError { + message: format!("Function not found: {}", name), + })?; + + match args.len() { + 0 => { + let f: fn() -> i64 = std::mem::transmute(ptr); + Ok(f()) + } + 1 => { + let f: fn(i64) -> i64 = std::mem::transmute(ptr); + Ok(f(args[0])) + } + 2 => { + let f: fn(i64, i64) -> i64 = std::mem::transmute(ptr); + Ok(f(args[0], args[1])) + } + 3 => { + let f: fn(i64, i64, i64) -> i64 = std::mem::transmute(ptr); + Ok(f(args[0], args[1], args[2])) + } + _ => Err(CompileError { + message: format!("Too many arguments: {}", args.len()), + }), + } + } +} + +impl Default for JitCompiler { + fn default() -> Self { + Self::new().expect("Failed to create JIT compiler") + } +} + +/// Compile an expression to Cranelift IR (free function to avoid borrow issues) +fn compile_expr( + builder: &mut FunctionBuilder, + expr: &Expr, + variables: &mut HashMap, + next_var: usize, + func_ids: &HashMap, + module: &mut JITModule, +) -> Result { + match expr { + Expr::Literal(lit) => { + match &lit.kind { + LiteralKind::Int(n) => { + Ok(builder.ins().iconst(types::I64, *n)) + } + LiteralKind::Bool(b) => { + Ok(builder.ins().iconst(types::I64, if *b { 1 } else { 0 })) + } + _ => Err(CompileError { + message: "Unsupported literal type".to_string() + }), + } + } + + Expr::Var(ident) => { + let var = variables.get(&ident.name).ok_or_else(|| CompileError { + message: format!("Undefined variable: {}", ident.name), + })?; + Ok(builder.use_var(*var)) + } + + Expr::BinaryOp { op, left, right, .. } => { + let lhs = compile_expr(builder, left, variables, next_var, func_ids, module)?; + let rhs = compile_expr(builder, right, variables, next_var, func_ids, module)?; + + let result = match op { + BinaryOp::Add => builder.ins().iadd(lhs, rhs), + BinaryOp::Sub => builder.ins().isub(lhs, rhs), + BinaryOp::Mul => builder.ins().imul(lhs, rhs), + BinaryOp::Div => builder.ins().sdiv(lhs, rhs), + BinaryOp::Mod => builder.ins().srem(lhs, rhs), + BinaryOp::Eq => { + let cmp = builder.ins().icmp(IntCC::Equal, lhs, rhs); + builder.ins().uextend(types::I64, cmp) + } + BinaryOp::Ne => { + let cmp = builder.ins().icmp(IntCC::NotEqual, lhs, rhs); + builder.ins().uextend(types::I64, cmp) + } + BinaryOp::Lt => { + let cmp = builder.ins().icmp(IntCC::SignedLessThan, lhs, rhs); + builder.ins().uextend(types::I64, cmp) + } + BinaryOp::Le => { + let cmp = builder.ins().icmp(IntCC::SignedLessThanOrEqual, lhs, rhs); + builder.ins().uextend(types::I64, cmp) + } + BinaryOp::Gt => { + let cmp = builder.ins().icmp(IntCC::SignedGreaterThan, lhs, rhs); + builder.ins().uextend(types::I64, cmp) + } + BinaryOp::Ge => { + let cmp = builder.ins().icmp(IntCC::SignedGreaterThanOrEqual, lhs, rhs); + builder.ins().uextend(types::I64, cmp) + } + BinaryOp::And => builder.ins().band(lhs, rhs), + BinaryOp::Or => builder.ins().bor(lhs, rhs), + _ => return Err(CompileError { + message: format!("Unsupported binary operator: {:?}", op), + }), + }; + Ok(result) + } + + Expr::UnaryOp { op, operand, .. } => { + let val = compile_expr(builder, operand, variables, next_var, func_ids, module)?; + let result = match op { + UnaryOp::Neg => builder.ins().ineg(val), + UnaryOp::Not => { + let one = builder.ins().iconst(types::I64, 1); + builder.ins().bxor(val, one) + } + }; + Ok(result) + } + + Expr::If { condition, then_branch, else_branch, .. } => { + let cond_val = compile_expr(builder, condition, variables, next_var, func_ids, module)?; + + // Create blocks + let then_block = builder.create_block(); + let else_block = builder.create_block(); + let merge_block = builder.create_block(); + + // Add block parameter for the result + builder.append_block_param(merge_block, types::I64); + + // Branch based on condition + let zero = builder.ins().iconst(types::I64, 0); + let cmp = builder.ins().icmp(IntCC::NotEqual, cond_val, zero); + builder.ins().brif(cmp, then_block, &[], else_block, &[]); + + // Then block + builder.switch_to_block(then_block); + builder.seal_block(then_block); + let then_val = compile_expr(builder, then_branch, variables, next_var, func_ids, module)?; + builder.ins().jump(merge_block, &[then_val]); + + // Else block + builder.switch_to_block(else_block); + builder.seal_block(else_block); + let else_val = compile_expr(builder, else_branch, variables, next_var, func_ids, module)?; + builder.ins().jump(merge_block, &[else_val]); + + // Merge block + builder.switch_to_block(merge_block); + builder.seal_block(merge_block); + + Ok(builder.block_params(merge_block)[0]) + } + + Expr::Let { name, value, body, .. } => { + // Compile the value + let val = compile_expr(builder, value, variables, next_var, func_ids, module)?; + + // Create a new variable + let var = Variable::from_u32(next_var as u32); + builder.declare_var(var, types::I64); + builder.def_var(var, val); + variables.insert(name.name.clone(), var); + + // Compile the body with the new variable in scope + compile_expr(builder, body, variables, next_var + 1, func_ids, module) + } + + Expr::Call { func, args, .. } => { + // Check if it's a direct function call + if let Expr::Var(name) = func.as_ref() { + // Look up the function + let func_id = *func_ids.get(&name.name).ok_or_else(|| CompileError { + message: format!("Unknown function: {}", name.name), + })?; + + // Compile arguments + let mut arg_values = Vec::new(); + for arg in args { + arg_values.push(compile_expr(builder, arg, variables, next_var, func_ids, module)?); + } + + // Get function reference + let func_ref = module.declare_func_in_func(func_id, builder.func); + + // Make the call + let call = builder.ins().call(func_ref, &arg_values); + Ok(builder.inst_results(call)[0]) + } else { + Err(CompileError { + message: "Only direct function calls supported".to_string(), + }) + } + } + + Expr::Block { statements, result: block_result, .. } => { + let mut current_var = next_var; + // Compile all statements + for stmt in statements { + match stmt { + Statement::Let { name, value, .. } => { + let val = compile_expr(builder, value, variables, current_var, func_ids, module)?; + let var = Variable::from_u32(current_var as u32); + builder.declare_var(var, types::I64); + builder.def_var(var, val); + variables.insert(name.name.clone(), var); + current_var += 1; + } + Statement::Expr(expr) => { + compile_expr(builder, expr, variables, current_var, func_ids, module)?; + } + } + } + // Compile and return the result expression + compile_expr(builder, block_result, variables, current_var, func_ids, module) + } + + _ => Err(CompileError { + message: "Unsupported expression type".to_string(), + }), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::Parser; + + fn parse_function(src: &str) -> FunctionDecl { + let program = Parser::parse_source(src).expect("Parse error"); + match &program.declarations[0] { + Declaration::Function(f) => f.clone(), + _ => panic!("Expected function"), + } + } + + #[test] + fn test_simple_arithmetic() { + let func = parse_function("fn test(): Int = 1 + 2 * 3"); + let mut jit = JitCompiler::new().unwrap(); + let result = jit.compile_and_run(&func).unwrap(); + assert_eq!(result, 7); + } + + #[test] + fn test_conditionals() { + let func = parse_function("fn test(): Int = if 1 > 0 then 42 else 0"); + let mut jit = JitCompiler::new().unwrap(); + let result = jit.compile_and_run(&func).unwrap(); + assert_eq!(result, 42); + } + + #[test] + fn test_let_binding() { + let func = parse_function("fn test(): Int = { let x = 10; let y = 20; x + y }"); + let mut jit = JitCompiler::new().unwrap(); + let result = jit.compile_and_run(&func).unwrap(); + assert_eq!(result, 30); + } + + #[test] + fn test_recursive_fibonacci() { + use std::time::Instant; + + // Compile a program with recursive fibonacci + let src = r#" + fn fib(n: Int): Int = if n <= 1 then n else fib(n - 1) + fib(n - 2) + "#; + let program = Parser::parse_source(src).expect("Parse error"); + + let mut jit = JitCompiler::new().unwrap(); + + let compile_start = Instant::now(); + jit.compile_program(&program).unwrap(); + let compile_time = compile_start.elapsed(); + + // Call fib(30) + let exec_start = Instant::now(); + let result = unsafe { jit.call_function("fib", &[30]).unwrap() }; + let exec_time = exec_start.elapsed(); + + println!("\n=== JIT Benchmark ==="); + println!("Compile time: {:?}", compile_time); + println!("Execute fib(30) = {} in {:?}", result, exec_time); + println!("Total: {:?}", compile_time + exec_time); + + assert_eq!(result, 832040); + } +} diff --git a/src/main.rs b/src/main.rs index c20e476..d630c56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ //! Lux - A functional programming language with first-class effects mod ast; +mod compiler; mod debugger; mod diagnostics; mod exhaustiveness;