diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb8f31e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +target/ +test.leaf +out.asm +a.out diff --git a/Cargo.lock b/Cargo.lock index 8d0ca76..c87227f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "arcstr" version = "1.2.0" @@ -10,9 +16,9 @@ checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "boxcar" @@ -22,9 +28,9 @@ checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -32,6 +38,16 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -55,18 +71,18 @@ checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", @@ -76,6 +92,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "fxhash" version = "0.2.1" @@ -96,6 +124,45 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "inkwell" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1def4112dfb2ce2993db7027f7acdb43c1f4ee1c70a082a2eef306ed5d0df365" +dependencies = [ + "inkwell_internals", + "libc", + "llvm-sys", + "once_cell", + "thiserror", +] + +[[package]] +name = "inkwell_internals" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63736175c9a30ea123f7018de9f26163e0b39cd6978990ae486b510c4f3bad69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leaf_allocators" version = "0.1.0" @@ -117,6 +184,18 @@ dependencies = [ "scc", ] +[[package]] +name = "leaf_backend_llvm" +version = "0.1.0" +dependencies = [ + "arcstr", + "derive_more", + "fxhash", + "inkwell", + "leaf_assembly", + "scc", +] + [[package]] name = "leaf_compiler" version = "0.1.0" @@ -124,24 +203,13 @@ dependencies = [ "arcstr", "derive_more", "fxhash", + "itertools", "leaf_allocators", "leaf_assembly", + "leaf_backend_llvm", "leaf_parser", ] -[[package]] -name = "leaf_interpreter" -version = "0.1.0" -dependencies = [ - "fxhash", - "leaf_allocators", - "leaf_assembly", - "leaf_compiler", - "leaf_parser", - "scc", - "smallvec", -] - [[package]] name = "leaf_parser" version = "0.1.0" @@ -151,6 +219,32 @@ dependencies = [ "peg", ] +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "llvm-sys" +version = "211.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "108b3ad2b2eaf2a561fc74196273b20e3436e4a688b8b44e250d83974dc1b2e2" +dependencies = [ + "anyhow", + "cc", + "lazy_static", + "libc", + "regex-lite", + "semver", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "peg" version = "0.8.5" @@ -180,22 +274,28 @@ checksum = "132dca9b868d927b35b5dd728167b2dee150eb1ad686008fc71ccb298b776fca" [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] +[[package]] +name = "regex-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943f41321c63ef1c92fd763bfe054d2668f7f225a5c29f0105903dc2fc04ba30" + [[package]] name = "rustc_version" version = "0.4.1" @@ -207,15 +307,15 @@ dependencies = [ [[package]] name = "saa" -version = "5.3.3" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dfac11c0cd0606aaf7eb9ef66f82c119438a96dc487715abb8b57fdf08ad4fe" +checksum = "16c7f49c9d5caa3bf4b3106900484b447b9253fe99670ceb81cb6cb5027855e1" [[package]] name = "scc" -version = "3.3.7" +version = "3.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3904515dbe9c10a126a54faa1d44fd40b893224247a1198d8c5dab6ef028bbdc" +checksum = "6012e652611b2fdcb557a7b4be8cee00d8be19397c70011906a68aa4dac2fe37" dependencies = [ "saa", "sdd", @@ -223,9 +323,9 @@ dependencies = [ [[package]] name = "sdd" -version = "4.3.1" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd556f4cb1d30590889c126f4951eb87f8a70d736ed5a2f62cc89d290721a1c2" +checksum = "4becc2f27bd39aafb78a8a1b4e1d6877ed0a6b5bf096722ed538dc028367a9b5" [[package]] name = "semver" @@ -234,16 +334,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] -name = "smallvec" -version = "1.15.1" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "syn" -version = "2.0.110" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -251,10 +351,30 @@ dependencies = [ ] [[package]] -name = "unicode-ident" -version = "1.0.22" +name = "thiserror" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" @@ -270,18 +390,18 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e180ace..cbc4fd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "3" -members = ["allocators","assembly", "compiler", "interpreter", "parser"] +members = ["allocators", "assembly", "compiler", "parser", "backends/llvm"] diff --git a/allocators/src/arena.rs b/allocators/src/arena.rs index 2da5ee2..a903eb9 100644 --- a/allocators/src/arena.rs +++ b/allocators/src/arena.rs @@ -5,69 +5,102 @@ use std::{alloc::Layout, sync::Mutex}; #[derive(Default)] pub struct ArenaAllocator { - bump: Bump, - allocations: Vec, + bump: Bump, + allocations: Vec, } impl Allocator for ArenaAllocator { - unsafe fn alloc_unsafe(&self, data: *const u8, layout: Layout, drop_fn: DropFn) -> *mut u8 { - unsafe { - let ptr = self.bump.alloc_layout(layout).as_ptr(); - std::ptr::copy_nonoverlapping(data, ptr, layout.size()); - self.allocations.push(AllocationEntry { - ptr, - layout, - drop_fn, - }); - ptr - } - } + unsafe fn alloc_unsafe(&self, data: *const u8, layout: Layout, drop_fn: DropFn) -> *mut u8 { + unsafe { + let ptr = self.bump.alloc_layout(layout).as_ptr(); + std::ptr::copy_nonoverlapping(data, ptr, layout.size()); + self.allocations.push(AllocationEntry { + ptr, + layout, + drop_fn, + }); + ptr + } + } + + unsafe fn alloc_uninit(&self, layout: Layout, drop_fn: DropFn) -> *mut u8 { + let ptr = self.bump.alloc_layout(layout).as_ptr(); + self.allocations.push(AllocationEntry { + ptr, + layout, + drop_fn, + }); + ptr + } } impl Drop for ArenaAllocator { - fn drop(&mut self) { - unsafe { - for AllocationEntry { ptr, drop_fn, .. } in std::mem::take(&mut self.allocations) { - if let Some(drop) = drop_fn { - drop(ptr); - } - } - } - } + fn drop(&mut self) { + unsafe { + for AllocationEntry { + ptr, + drop_fn, + layout, + } in std::mem::take(&mut self.allocations) + { + if let Some(drop) = drop_fn { + drop(ptr, layout); + } + } + } + } } #[derive(Default)] pub struct SyncArenaAllocator { - bump: Mutex, - allocations: Vec, + bump: Mutex, + allocations: Vec, } impl Allocator for SyncArenaAllocator { - unsafe fn alloc_unsafe(&self, data: *const u8, layout: Layout, drop_fn: DropFn) -> *mut u8 { - unsafe { - let ptr = { - let bump = self.bump.lock().unwrap(); - bump.alloc_layout(layout).as_ptr() - }; - std::ptr::copy_nonoverlapping(data, ptr, layout.size()); - self.allocations.push(AllocationEntry { - ptr, - layout, - drop_fn, - }); - ptr - } - } + unsafe fn alloc_unsafe(&self, data: *const u8, layout: Layout, drop_fn: DropFn) -> *mut u8 { + unsafe { + let ptr = { + let bump = self.bump.lock().unwrap(); + bump.alloc_layout(layout).as_ptr() + }; + std::ptr::copy_nonoverlapping(data, ptr, layout.size()); + self.allocations.push(AllocationEntry { + ptr, + layout, + drop_fn, + }); + ptr + } + } + + unsafe fn alloc_uninit(&self, layout: Layout, drop_fn: DropFn) -> *mut u8 { + let ptr = { + let bump = self.bump.lock().unwrap(); + bump.alloc_layout(layout).as_ptr() + }; + self.allocations.push(AllocationEntry { + ptr, + layout, + drop_fn, + }); + ptr + } } impl Drop for SyncArenaAllocator { - fn drop(&mut self) { - unsafe { - for AllocationEntry { ptr, drop_fn, .. } in std::mem::take(&mut self.allocations) { - if let Some(drop) = drop_fn { - drop(ptr); - } - } - } - } + fn drop(&mut self) { + unsafe { + for AllocationEntry { + ptr, + drop_fn, + layout, + } in std::mem::take(&mut self.allocations) + { + if let Some(drop) = drop_fn { + drop(ptr, layout); + } + } + } + } } diff --git a/allocators/src/global.rs b/allocators/src/global.rs index c2ee7c3..d93f0fc 100644 --- a/allocators/src/global.rs +++ b/allocators/src/global.rs @@ -4,38 +4,50 @@ use std::alloc::Layout; #[derive(Default)] pub struct GlobalAllocator { - allocations: Vec, + allocations: Vec, } impl Allocator for GlobalAllocator { - unsafe fn alloc_unsafe(&self, data: *const u8, layout: Layout, drop_fn: DropFn) -> *mut u8 { - unsafe { - let ptr = std::alloc::alloc(layout); - std::ptr::copy_nonoverlapping(data, ptr, layout.size()); - self.allocations.push(AllocationEntry { - ptr, - layout, - drop_fn, - }); - ptr - } - } + unsafe fn alloc_unsafe(&self, data: *const u8, layout: Layout, drop_fn: DropFn) -> *mut u8 { + unsafe { + let ptr = std::alloc::alloc(layout); + std::ptr::copy_nonoverlapping(data, ptr, layout.size()); + self.allocations.push(AllocationEntry { + ptr, + layout, + drop_fn, + }); + ptr + } + } + + unsafe fn alloc_uninit(&self, layout: Layout, drop_fn: DropFn) -> *mut u8 { + unsafe { + let ptr = std::alloc::alloc(layout); + self.allocations.push(AllocationEntry { + ptr, + layout, + drop_fn, + }); + ptr + } + } } impl Drop for GlobalAllocator { - fn drop(&mut self) { - unsafe { - for AllocationEntry { - ptr, - layout, - drop_fn, - } in std::mem::take(&mut self.allocations) - { - if let Some(drop) = drop_fn { - drop(ptr); - } - std::alloc::dealloc(ptr, layout); - } - } - } + fn drop(&mut self) { + unsafe { + for AllocationEntry { + ptr, + layout, + drop_fn, + } in std::mem::take(&mut self.allocations) + { + if let Some(drop) = drop_fn { + drop(ptr, layout); + } + std::alloc::dealloc(ptr, layout); + } + } + } } diff --git a/allocators/src/lib.rs b/allocators/src/lib.rs index 9abf99f..449e934 100644 --- a/allocators/src/lib.rs +++ b/allocators/src/lib.rs @@ -5,7 +5,7 @@ pub use arena::*; pub use global::*; use std::{alloc::Layout, mem::MaybeUninit}; -pub type DropFn = Option; +pub type DropFn = Option; pub struct AllocationEntry { ptr: *mut u8, @@ -17,6 +17,7 @@ unsafe impl Send for AllocationEntry {} unsafe impl Sync for AllocationEntry {} pub trait Allocator { + unsafe fn alloc_uninit(&self, layout: Layout, drop: DropFn) -> *mut u8; unsafe fn alloc_unsafe(&self, data: *const u8, layout: Layout, drop: DropFn) -> *mut u8; } @@ -32,7 +33,7 @@ impl<'l> dyn Allocator + 'l { let layout = Layout::new::(); let drop: DropFn = match std::mem::needs_drop::() { false => None, - true => Some(|ptr: *mut u8| std::ptr::drop_in_place(ptr as *mut T)), + true => Some(|ptr, _| std::ptr::drop_in_place(ptr as *mut T)), }; &mut *(self.alloc_unsafe(data, layout, drop) as *mut T) } @@ -47,6 +48,29 @@ impl<'l> dyn Allocator + 'l { std::str::from_utf8_unchecked(slice) } } + + pub fn alloc_slice(&'l self, iter: impl Iterator + ExactSizeIterator) -> &'l [T] { + unsafe { + let len = iter.len(); + let layout = Layout::array::(len).unwrap(); + let ptr = self.alloc_uninit( + layout, + match std::mem::needs_drop::() { + false => None, + true => Some(|ptr, layout| { + let len = layout.size() / size_of::(); + let slice = std::slice::from_raw_parts_mut(ptr as *mut T, len); + std::ptr::drop_in_place(slice); + }), + }, + ) as *mut MaybeUninit; + let slice = std::slice::from_raw_parts_mut(ptr, len); + for (dst, val) in slice.iter_mut().zip(iter) { + dst.write(val); + } + slice.assume_init_ref() + } + } } impl<'l> dyn SyncAllocator + 'l { @@ -57,4 +81,8 @@ impl<'l> dyn SyncAllocator + 'l { pub fn alloc_str(&'l self, value: &str) -> &'l str { ::alloc_str(self, value) } + + pub fn alloc_slice(&'l self, iter: impl Iterator + ExactSizeIterator) -> &'l [T] { + ::alloc_slice(self, iter) + } } diff --git a/assembly/src/assembly.rs b/assembly/src/assembly.rs index c7ce21c..68a091f 100644 --- a/assembly/src/assembly.rs +++ b/assembly/src/assembly.rs @@ -1,16 +1,18 @@ use crate::{ functions::Function, types::derivations::{FuncT, TypeDerivations}, - values::Const, }; -use derive_more::Debug; +use derive_more::{Debug, Display}; use fxhash::FxBuildHasher; use leaf_allocators::SyncAllocator; use scc::HashMap; -use std::{borrow::Cow, hash::Hash, sync::OnceLock}; +use std::{borrow::Cow, fmt::Display, hash::Hash, sync::OnceLock}; -#[derive(derive_more::Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive( + derive_more::Debug, Display, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, +)] #[debug("{major}.{minor}.{patch}.{build}")] +#[display("{major}.{minor}.{patch}.{build}")] pub struct Version { pub major: u16, pub minor: u16, @@ -18,7 +20,8 @@ pub struct Version { pub build: u16, } -#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Display, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[display("{name} v{version}")] pub struct AssemblyIdentifier { pub version: Version, pub name: Cow<'static, str>, @@ -57,6 +60,10 @@ impl<'l> Context<'l> { }) } + pub fn alloc(&self) -> &'l dyn SyncAllocator { + self.alloc + } + pub fn get_assembly(&'l self, ident: &AssemblyIdentifier) -> Option<&'l Assembly<'l>> { self.assemblies.get_sync(ident).map(|v| *v) } @@ -114,6 +121,10 @@ impl<'l> Assembly<'l> { self.ctx } + pub fn ident(&self) -> &AssemblyIdentifier { + &self.ident + } + pub fn create_function(&'l self, ty: &'l FuncT<'l>) -> &'l Function<'l> { let func = self.ctx.alloc.alloc(Function { ty, @@ -125,6 +136,10 @@ impl<'l> Assembly<'l> { func } + pub fn functions(&'l self) -> boxcar::vec::Iter<'l, &'l Function<'l>> { + self.functions.iter() + } + pub fn find_function( &'l self, filter: impl Fn(&'l Function<'l>) -> bool, diff --git a/assembly/src/functions/ir.rs b/assembly/src/functions/ir.rs index b514bf2..3fe30ca 100644 --- a/assembly/src/functions/ir.rs +++ b/assembly/src/functions/ir.rs @@ -1,7 +1,7 @@ use crate::{ assembly::Ctx, functions::{Function, FunctionBody}, - types::{Type, derivations::*}, + types::{IntT, Type, derivations::*}, values::{Value, ValueFlags}, }; use derive_more::{Debug, Display}; @@ -25,15 +25,15 @@ impl Eq for Instruction<'_> {} impl PartialEq for Instruction<'_> { #[inline] - fn eq(&self, _: &Self) -> bool { - false + fn eq(&self, other: &Self) -> bool { + std::ptr::eq(self, other) } } impl Hash for Instruction<'_> { #[inline] fn hash(&self, state: &mut H) { - (self as *const Self).hash(state); + std::ptr::hash(self, state); } } @@ -60,6 +60,10 @@ impl<'l> Instruction<'l> { pub fn value_flags(&self) -> ValueFlags { match self.variant { InstructionVariant::StackAlloc(_) => ValueFlags::LValue, + InstructionVariant::GetElementPtr(v, _) if v.flags().contains(ValueFlags::LValue) => { + ValueFlags::LValue + } + InstructionVariant::Reinterpret(_, _, f) => f, _ => ValueFlags::empty(), } } @@ -73,12 +77,35 @@ impl<'l> Instruction<'l> { Type::Ptr(PtrT { base, .. }) => *base, _ => unreachable!(), }, + InstructionVariant::GetElementPtr(v, _) => match v.ty() { + Type::Ptr(PtrT { + base: Type::Array(ArrayT { base, .. }), + mutable, + }) => base.make_ptr(*mutable).into(), + Type::Ref(RefT { + base: Type::Array(ArrayT { base, .. }), + mutable, + }) => base.make_ref(*mutable).into(), + _ => unreachable!(), + }, InstructionVariant::IAdd(a, _) => a.ty(), + InstructionVariant::ISub(a, _) => a.ty(), InstructionVariant::IMul(a, _) => a.ty(), + InstructionVariant::IDiv(a, _) => a.ty(), + InstructionVariant::IMod(a, _) => a.ty(), + InstructionVariant::SExt(_, t) => Type::Int(t), + InstructionVariant::ZExt(_, t) => Type::Int(t), + InstructionVariant::Trunc(_, t) => Type::Int(t), InstructionVariant::FAdd(a, _) => a.ty(), + InstructionVariant::FSub(a, _) => a.ty(), InstructionVariant::FMul(a, _) => a.ty(), + InstructionVariant::FDiv(a, _) => a.ty(), + InstructionVariant::FMod(a, _) => a.ty(), InstructionVariant::ICmp(_, _, _) => Type::Bool, InstructionVariant::Call(f, _) => f.ty.ret_t, + InstructionVariant::Jump(_) => Type::Void, + InstructionVariant::Branch { .. } => Type::Void, + InstructionVariant::Reinterpret(_, t, _) => t, _ => todo!("{self:?}"), } } @@ -87,6 +114,7 @@ impl<'l> Instruction<'l> { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Cmp { Eq, + Ne, Lt, Gt, Le, @@ -100,11 +128,21 @@ pub enum InstructionVariant<'l> { Load(Value<'l>), Store(Value<'l>, Value<'l>), + GetElementPtr(Value<'l>, Value<'l>), IAdd(Value<'l>, Value<'l>), + ISub(Value<'l>, Value<'l>), IMul(Value<'l>, Value<'l>), + IDiv(Value<'l>, Value<'l>), + IMod(Value<'l>, Value<'l>), + SExt(Value<'l>, IntT), + ZExt(Value<'l>, IntT), + Trunc(Value<'l>, IntT), FAdd(Value<'l>, Value<'l>), + FSub(Value<'l>, Value<'l>), FMul(Value<'l>, Value<'l>), + FDiv(Value<'l>, Value<'l>), + FMod(Value<'l>, Value<'l>), ICmp(Value<'l>, Value<'l>, Cmp), FCmp(Value<'l>, Value<'l>, Cmp), @@ -117,6 +155,8 @@ pub enum InstructionVariant<'l> { false_case: &'l Block<'l>, }, Return(Option>), + + Reinterpret(Value<'l>, Type<'l>, ValueFlags), } impl InstructionVariant<'_> { @@ -137,10 +177,20 @@ impl std::fmt::Debug for InstructionVariant<'_> { Self::GCAlloc(ty) => write!(f, "gcalloc {ty}"), Self::Load(v) => write!(f, "load {v}"), Self::Store(t, v) => write!(f, "store {t}, {v}"), + Self::GetElementPtr(t, v) => write!(f, "gep {t}, {v}"), Self::IAdd(a, b) => write!(f, "iadd {a}, {b}"), + Self::ISub(a, b) => write!(f, "isub {a}, {b}"), Self::IMul(a, b) => write!(f, "imul {a}, {b}"), + Self::IDiv(a, b) => write!(f, "idiv {a}, {b}"), + Self::IMod(a, b) => write!(f, "imod {a}, {b}"), + Self::SExt(a, b) => write!(f, "sext {a}, {b}"), + Self::ZExt(a, b) => write!(f, "zext {a}, {b}"), + Self::Trunc(a, b) => write!(f, "trunc {a}, {b}"), Self::FAdd(a, b) => write!(f, "fadd {a}, {b}"), + Self::FSub(a, b) => write!(f, "fsub {a}, {b}"), Self::FMul(a, b) => write!(f, "fmul {a}, {b}"), + Self::FDiv(a, b) => write!(f, "fdiv {a}, {b}"), + Self::FMod(a, b) => write!(f, "fmod {a}, {b}"), Self::ICmp(a, b, c) => write!(f, "icmp {c:?} {a}, {b}"), Self::FCmp(a, b, c) => write!(f, "fcmp {c:?} {a}, {b}"), Self::Call(func, args) => { @@ -164,6 +214,7 @@ impl std::fmt::Debug for InstructionVariant<'_> { Self::Jump(b) => write!(f, "jump #{}", b.id), Self::Return(None) => write!(f, "return"), Self::Return(Some(v)) => write!(f, "return {v}"), + Self::Reinterpret(v, t, fl) => write!(f, "reinterpret {v} as {t} {fl:?}"), } } } @@ -260,7 +311,23 @@ impl<'l> BlockBuilder<'l> { let inst = self.push_instruction(InstructionVariant::FAdd(a, b))?; Ok(inst.into()) } - _ => Err(format!("Cannot add values of type `{a_ty}` and `b_ty`.").into()), + _ => Err(format!("Cannot add values of type `{a_ty}` and `{b_ty}`.").into()), + } + } + + pub fn sub(&mut self, a: Value<'l>, b: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + let [a_ty, b_ty] = [a.ty(), b.ty()]; + + match (a_ty, b_ty) { + (Type::Int(a_ty), Type::Int(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::ISub(a, b))?; + Ok(inst.into()) + } + (Type::Float(a_ty), Type::Float(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::FSub(a, b))?; + Ok(inst.into()) + } + _ => Err(format!("Cannot subtract values of type `{a_ty}` and `{b_ty}`.").into()), } } @@ -276,7 +343,52 @@ impl<'l> BlockBuilder<'l> { let inst = self.push_instruction(InstructionVariant::FMul(a, b))?; Ok(inst.into()) } - _ => Err(format!("Cannot add values of type `{a_ty}` and `b_ty`.").into()), + _ => Err(format!("Cannot multiply values of type `{a_ty}` and `{b_ty}`.").into()), + } + } + + pub fn div(&mut self, a: Value<'l>, b: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + let [a_ty, b_ty] = [a.ty(), b.ty()]; + + match (a_ty, b_ty) { + (Type::Int(a_ty), Type::Int(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::IDiv(a, b))?; + Ok(inst.into()) + } + (Type::Float(a_ty), Type::Float(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::FDiv(a, b))?; + Ok(inst.into()) + } + _ => Err(format!("Cannot divide values of type `{a_ty}` and `{b_ty}`.").into()), + } + } + + pub fn modulo(&mut self, a: Value<'l>, b: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + let [a_ty, b_ty] = [a.ty(), b.ty()]; + + match (a_ty, b_ty) { + (Type::Int(a_ty), Type::Int(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::IMod(a, b))?; + Ok(inst.into()) + } + (Type::Float(a_ty), Type::Float(b_ty)) if a_ty == b_ty => { + let inst = self.push_instruction(InstructionVariant::FMod(a, b))?; + Ok(inst.into()) + } + _ => Err(format!("Cannot divide values of type `{a_ty}` and `{b_ty}`.").into()), + } + } + + pub fn trunc(&mut self, val: Value<'l>, target: IntT) -> BlockBuilderResult<'l, Value<'l>> { + let ty = val.ty(); + match ty { + Type::Int(a_ty) if a_ty.precision > target.precision => { + let inst = self.push_instruction(InstructionVariant::Trunc(val, target))?; + Ok(inst.into()) + } + _ => Err( + format!("Cannot truncate value of type `{ty}` to one of type `{target}`.").into(), + ), } } @@ -297,10 +409,34 @@ impl<'l> BlockBuilder<'l> { let inst = self.push_instruction(InstructionVariant::FCmp(a, b, cmp))?; Ok(inst.into()) } - _ => Err(format!("Cannot compare values of type `{a_ty}` and `b_ty`.").into()), + _ => Err(format!("Cannot compare values of type `{a_ty}` and `{b_ty}`.").into()), } } + pub fn gep(&mut self, value: Value<'l>, index: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + let v_ty = value.ty(); + let i_ty = index.ty(); + + let _ = match v_ty { + Type::Ptr(PtrT { + base: Type::Array(ArrayT { base, .. }), + .. + }) => *base, + Type::Ref(RefT { + base: Type::Array(ArrayT { base, .. }), + .. + }) => *base, + _ => return Err(format!("Cannot index a value of type `{}`.", v_ty).into()), + }; + + if i_ty != Type::USIZE { + return Err(format!("Expeted index type `usize`, found {}.", i_ty).into()); + } + + let inst = self.push_instruction(InstructionVariant::GetElementPtr(value, index))?; + Ok(inst.into()) + } + pub fn jump(&mut self, block: &'l Block<'l>) -> BlockBuilderResult<'l, Value<'l>> { if !std::ptr::eq(block.func, self.block.func) { return Err("Block does not belong to this function.".into()); @@ -376,6 +512,21 @@ impl<'l> BlockBuilder<'l> { Ok(inst.into()) } + /// WARNING: Incorrect usage of this function is very likely to cause catastrophic problems. Be sure to know what you're doing. + /// + /// # Safety + /// + /// The target reinterpretation must maintain the IR's invariants. + pub unsafe fn reinterpret( + &mut self, + value: Value<'l>, + ty: Type<'l>, + flags: ValueFlags, + ) -> BlockBuilderResult<'l, Value<'l>> { + let inst = self.push_instruction(InstructionVariant::Reinterpret(value, ty, flags))?; + Ok(inst.into()) + } + #[inline] pub fn has_termination(&self) -> bool { match self.instructions.as_slice() { @@ -494,10 +645,26 @@ impl<'l> FunctionBodyBuilder<'l> { self.current_builder().add(a, b) } + pub fn sub(&mut self, a: Value<'l>, b: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().sub(a, b) + } + pub fn mul(&mut self, a: Value<'l>, b: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { self.current_builder().mul(a, b) } + pub fn div(&mut self, a: Value<'l>, b: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().div(a, b) + } + + pub fn modulo(&mut self, a: Value<'l>, b: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().modulo(a, b) + } + + pub fn trunc(&mut self, val: Value<'l>, target: IntT) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().trunc(val, target) + } + pub fn cmp( &mut self, a: Value<'l>, @@ -507,6 +674,10 @@ impl<'l> FunctionBodyBuilder<'l> { self.current_builder().cmp(a, b, cmp) } + pub fn gep(&mut self, value: Value<'l>, index: Value<'l>) -> BlockBuilderResult<'l, Value<'l>> { + self.current_builder().gep(value, index) + } + pub fn jump(&mut self, block: &'l Block<'l>) -> BlockBuilderResult<'l, Value<'l>> { self.current_builder().jump(block) } @@ -532,6 +703,20 @@ impl<'l> FunctionBodyBuilder<'l> { self.current_builder().ret(value) } + /// WARNING: Incorrect usage of this function is very likely to cause catastrophic problems. Be sure to know what you're doing. + /// + /// # Safety + /// + /// The target reinterpretation must maintain the IR's invariants. + pub unsafe fn reinterpret( + &mut self, + value: Value<'l>, + ty: Type<'l>, + flags: ValueFlags, + ) -> BlockBuilderResult<'l, Value<'l>> { + unsafe { self.current_builder().reinterpret(value, ty, flags) } + } + fn current_builder(&mut self) -> &mut BlockBuilder<'l> { &mut self.blocks[self.current_block] } diff --git a/assembly/src/functions/mod.rs b/assembly/src/functions/mod.rs index f96bcc6..3c89981 100644 --- a/assembly/src/functions/mod.rs +++ b/assembly/src/functions/mod.rs @@ -24,6 +24,10 @@ impl<'l> Function<'l> { self.declaring_assembly.ctx() } + pub fn declaring_assembly(&self) -> &'l Assembly<'l> { + self.declaring_assembly + } + pub fn body(&self) -> Option<&FunctionBody<'l>> { self.body.get() } diff --git a/assembly/src/types/derivations.rs b/assembly/src/types/derivations.rs index 87a0aca..4b2013b 100644 --- a/assembly/src/types/derivations.rs +++ b/assembly/src/types/derivations.rs @@ -8,6 +8,7 @@ pub struct TypeDerivations<'l> { alloc: &'l dyn SyncAllocator, ptr_t: HashMap<(Type<'l>, bool), Type<'l>>, ref_t: HashMap<(Type<'l>, bool), Type<'l>>, + arr_t: HashMap<(Type<'l>, Option), Type<'l>>, fun_t: HashMap<(Type<'l>, Arc<[Type<'l>]>), Type<'l>>, } @@ -17,6 +18,7 @@ impl<'l> TypeDerivations<'l> { alloc, ptr_t: HashMap::new(), ref_t: HashMap::new(), + arr_t: HashMap::new(), fun_t: HashMap::new(), } } @@ -43,6 +45,17 @@ impl<'l> TypeDerivations<'l> { ty } + pub fn make_array(&self, base: Type<'l>, length: Option) -> &'l ArrayT<'l> { + let Type::Array(ty) = *self + .arr_t + .entry_sync((base, length)) + .or_insert_with(|| (&*self.alloc.alloc(ArrayT { base, length })).into()) + else { + unreachable!() + }; + ty + } + pub fn make_fn(&'l self, ret_t: Type<'l>, par_t: Arc<[Type<'l>]>) -> &'l FuncT<'l> { let Type::Func(ty) = *self .fun_t @@ -95,6 +108,23 @@ pub struct RefT<'l> { pub mutable: bool, } +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ArrayT<'l> { + #[debug("{base}")] + pub base: Type<'l>, + pub length: Option, +} + +impl Display for ArrayT<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.length { + None => write!(f, "[{}]", self.base), + Some(len) => write!(f, "[{}; {len}]", self.base), + } + } +} + #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FuncT<'l> { @@ -124,6 +154,10 @@ impl<'l> Type<'l> { self.ctx().derivations.make_ref(*self, mutable) } + pub fn make_array(&self, length: Option) -> &'l ArrayT<'l> { + self.ctx().derivations.make_array(*self, length) + } + pub fn make_fn(&self, par_t: impl IntoIterator>) -> &'l FuncT<'l> { self.ctx() .derivations diff --git a/assembly/src/types/mod.rs b/assembly/src/types/mod.rs index 335796a..cd96ca3 100644 --- a/assembly/src/types/mod.rs +++ b/assembly/src/types/mod.rs @@ -51,6 +51,10 @@ pub enum Type<'l> { #[display("{_0}")] Ref(&'l RefT<'l>), + #[debug("{_0:?}")] + #[display("{_0}")] + Array(&'l ArrayT<'l>), + #[debug("{_0:?}")] #[display("{_0}")] Func(&'l FuncT<'l>), @@ -82,8 +86,9 @@ impl<'l> Type<'l> { Type::ConstStr => None, Type::Int(_) => None, Type::Float(_) => None, - Type::Ptr(_) => None, - Type::Ref(_) => None, + Type::Ptr(PtrT { base, .. }) => base.non_default_ctx(), + Type::Ref(RefT { base, .. }) => base.non_default_ctx(), + Type::Array(ArrayT { base, .. }) => base.non_default_ctx(), Type::Func(f) => match f.ret_t.non_default_ctx() { Some(ctx) => Some(ctx), None => f.par_t.iter().find_map(|t| t.non_default_ctx()), diff --git a/assembly/src/values/constants.rs b/assembly/src/values/constants.rs index 83595bc..f356815 100644 --- a/assembly/src/values/constants.rs +++ b/assembly/src/values/constants.rs @@ -1,4 +1,8 @@ -use crate::{functions::Function, types::Type, values::ValueFlags}; +use crate::{ + functions::Function, + types::{Type, derivations::ArrayT}, + values::ValueFlags, +}; use derive_more::{Debug, *}; use half::f16; use std::hash::Hash; @@ -55,6 +59,18 @@ where } } +struct ListDisplay<'l>(&'l [AnyConst<'l>]); + +impl std::fmt::Display for ListDisplay<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut list = f.debug_list(); + for ele in self.0 { + list.entry(&format_args!("{ele}")); + } + list.finish() + } +} + #[derive(Debug, Display, Clone, Copy, From, TryInto, PartialEq, Eq, Hash)] #[from(forward)] pub enum AnyConst<'l> { @@ -79,6 +95,9 @@ pub enum AnyConst<'l> { #[debug("{:?}", _0)] #[display("{}", _0)] Function(&'l Function<'l>), + #[debug("{:?}", _0)] + #[display("{}", ListDisplay(_0))] + Array(&'l [AnyConst<'l>]), Type(Type<'l>), } @@ -104,6 +123,11 @@ impl<'l> AnyConst<'l> { Self::Float(Float::F16(_)) => Type::F16, Self::Float(Float::F32(_)) => Type::F32, Self::Float(Float::F64(_)) => Type::F64, + Self::Array([]) => Type::Array(&ArrayT { + base: Type::Void, + length: Some(0), + }), + Self::Array(a @ [v, ..]) => Type::Array(v.ty().make_array(Some(a.len() as u32))), _ => todo!("{self:?}"), } } diff --git a/assembly/src/values/mod.rs b/assembly/src/values/mod.rs index f72d1d1..42a8f95 100644 --- a/assembly/src/values/mod.rs +++ b/assembly/src/values/mod.rs @@ -53,7 +53,7 @@ impl<'l> Value<'l> { pub fn flags(&self) -> ValueFlags { match self { - Value::Instruction(instruction) => instruction.value_flags(), + Value::Instruction(v) => v.value_flags(), Value::Parameter(_, _) => ValueFlags::empty(), Value::Constant(c) => c.flags(), _ => todo!("{self:?}"), diff --git a/backends/llvm/Cargo.toml b/backends/llvm/Cargo.toml new file mode 100644 index 0000000..2c15625 --- /dev/null +++ b/backends/llvm/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "leaf_backend_llvm" +version = "0.1.0" +edition = "2024" + +[dependencies] +arcstr = "1.2.0" +fxhash = "0.2.1" +leaf_assembly = { path = "../../assembly" } +inkwell = { version = "0.8.0", default-features = false, features = ["llvm21-1", "target-aarch64", "target-x86"] } +derive_more = { version = "2.0.1", features = ["deref", "deref_mut", "debug", "display", "try_from", "from", "try_into", "into"] } +scc = "3.6.6" diff --git a/backends/llvm/src/lib.rs b/backends/llvm/src/lib.rs new file mode 100644 index 0000000..66b910b --- /dev/null +++ b/backends/llvm/src/lib.rs @@ -0,0 +1,395 @@ +use fxhash::{FxBuildHasher, FxHashMap}; +pub use inkwell; +use inkwell::{ + AddressSpace, IntPredicate, + module::Module, + targets::TargetMachine, + types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicTypeEnum, IntType}, + values::{AnyValue, BasicValue, BasicValueEnum}, +}; +use leaf_assembly::{ + assembly::Assembly, + functions::{ + Function, + ir::{Cmp, Instruction, InstructionVariant}, + }, + types::{ + Type, + derivations::{ArrayT, FuncT, PtrT, RefT}, + }, + values::{AnyConst, Int, Value}, +}; +use scc::HashMap; + +pub type LlvmContext = inkwell::context::Context; +pub type LlvmModule<'l> = inkwell::module::Module<'l>; +pub type LlvmFunction<'l> = inkwell::values::FunctionValue<'l>; +pub type LlvmType<'l> = inkwell::types::AnyTypeEnum<'l>; + +pub struct CompilationContext<'l> { + ctx: &'l LlvmContext, + native_int_ty: IntType<'l>, + types: HashMap, AnyTypeEnum<'l>, FxBuildHasher>, + modules: HashMap<&'l Assembly<'l>, Module<'l>, FxBuildHasher>, + functions: HashMap<(&'l Assembly<'l>, &'l Function<'l>), LlvmFunction<'l>, FxBuildHasher>, +} + +impl<'l> CompilationContext<'l> { + pub fn new(ctx: &'l LlvmContext, target: &TargetMachine) -> Self { + Self { + ctx, + types: HashMap::default(), + modules: HashMap::default(), + functions: HashMap::default(), + native_int_ty: match target.get_target_data().get_pointer_byte_size(None) { + 8 => ctx.i8_type().into(), + 16 => ctx.i16_type().into(), + 32 => ctx.i32_type().into(), + 64 => ctx.i64_type().into(), + 128 => ctx.i128_type().into(), + _ => unreachable!(), + }, + } + } + + pub fn compile(&self, assembly: &'l Assembly<'l>) -> LlvmModule<'l> { + let mut exists = true; + let module = self + .modules + .entry_sync(assembly) + .or_insert_with(|| { + exists = false; + self.ctx.create_module(&assembly.ident().to_string()) + }) + .clone(); + + if exists { + return module; + } + + for (i, func) in assembly.functions() { + let ty = self.get_type(Type::Func(func.ty)).into_function_type(); + let name = match func.name.get() { + Some(n) => *n, + None => &format!(""), + }; + + self.functions + .entry_sync((assembly, func)) + .or_insert_with(|| module.add_function(name, ty, None)); + } + + for (i, func) in assembly.functions() { + let llvm_func = self.functions.get_sync(&(assembly, *func)).unwrap().clone(); + + let Some(body) = func.body() else { + continue; + }; + + let mut values = FxHashMap::>::default(); + for (i, ty) in func.ty.par_t.iter().enumerate() { + let ty = self.get_type(*ty); + if BasicMetadataTypeEnum::try_from(ty).is_ok() { + values.insert( + Value::Parameter(i, func), + Some(llvm_func.get_nth_param(values.len() as u32).unwrap()), + ); + } + } + + let mut blocks = vec![]; + let builder = self.ctx.create_builder(); + for block in &body.blocks { + blocks.push( + self.ctx + .append_basic_block(llvm_func, &format!("block_{}", block.id)), + ); + } + for block in &body.blocks { + builder.position_at_end(blocks[block.id as usize]); + for inst @ Instruction { variant, .. } in block.instructions() { + let llvm_value = match variant { + InstructionVariant::StackAlloc(ty) => { + let ty = self.get_type(*ty); + BasicTypeEnum::try_from(ty) + .ok() + .map(|ty| builder.build_alloca(ty, "").unwrap().into()) + } + + InstructionVariant::Load(value) => 'val: { + let Type::Ptr(PtrT { base, .. }) = value.ty() else { + unreachable!() + }; + let Ok(ty): Result = self.get_type(*base).try_into() + else { + break 'val None; + }; + let Some(value) = self.get_value(&values, value) else { + break 'val None; + }; + let ptr = value.into_pointer_value(); + Some(builder.build_load(ty, ptr, "").unwrap().into()) + } + InstructionVariant::Store(target, value) => 'val: { + let Some(t_val) = self.get_value(&values, target) else { + break 'val None; + }; + let Some(v_val) = self.get_value(&values, value) else { + break 'val None; + }; + let t_val = t_val.into_pointer_value(); + builder.build_store(t_val, v_val).unwrap(); + None + } + InstructionVariant::GetElementPtr(ptr, idx) => 'val: { + let pointee_ty = self.get_type(match inst.value_ty() { + Type::Ptr(PtrT { base, .. }) => *base, + Type::Ref(RefT { base, .. }) => *base, + _ => unreachable!(), + }); + let Ok(pointee_ty): Result = pointee_ty.try_into() + else { + break 'val None; + }; + let ptr = self.get_value(&values, ptr).unwrap().into_pointer_value(); + let idx = self.get_value(&values, idx).unwrap().into_int_value(); + unsafe { + let ptr = builder.build_gep(pointee_ty, ptr, &[idx], "").unwrap(); + Some(ptr.into()) + } + } + + InstructionVariant::IAdd(lhs, rhs) => { + let lhs = self.get_value(&values, lhs).unwrap().into_int_value(); + let rhs = self.get_value(&values, rhs).unwrap().into_int_value(); + Some(builder.build_int_add(lhs, rhs, "").unwrap().into()) + } + InstructionVariant::ISub(lhs, rhs) => { + let lhs = self.get_value(&values, lhs).unwrap().into_int_value(); + let rhs = self.get_value(&values, rhs).unwrap().into_int_value(); + Some(builder.build_int_sub(lhs, rhs, "").unwrap().into()) + } + InstructionVariant::IMul(lhs, rhs) => { + let lhs = self.get_value(&values, lhs).unwrap().into_int_value(); + let rhs = self.get_value(&values, rhs).unwrap().into_int_value(); + Some(builder.build_int_mul(lhs, rhs, "").unwrap().into()) + } + InstructionVariant::IDiv(lhs, rhs) if is_signed(lhs.ty()) => { + let lhs = self.get_value(&values, lhs).unwrap().into_int_value(); + let rhs = self.get_value(&values, rhs).unwrap().into_int_value(); + Some(builder.build_int_signed_div(lhs, rhs, "").unwrap().into()) + } + InstructionVariant::IDiv(lhs, rhs) => { + let lhs = self.get_value(&values, lhs).unwrap().into_int_value(); + let rhs = self.get_value(&values, rhs).unwrap().into_int_value(); + Some(builder.build_int_unsigned_div(lhs, rhs, "").unwrap().into()) + } + InstructionVariant::IMod(lhs, rhs) if is_signed(lhs.ty()) => { + let lhs = self.get_value(&values, lhs).unwrap().into_int_value(); + let rhs = self.get_value(&values, rhs).unwrap().into_int_value(); + Some(builder.build_int_signed_rem(lhs, rhs, "").unwrap().into()) + } + InstructionVariant::IMod(lhs, rhs) => { + let lhs = self.get_value(&values, lhs).unwrap().into_int_value(); + let rhs = self.get_value(&values, rhs).unwrap().into_int_value(); + Some(builder.build_int_unsigned_rem(lhs, rhs, "").unwrap().into()) + } + InstructionVariant::Trunc(val, target) => { + let val = self.get_value(&values, val).unwrap().into_int_value(); + let target = self.get_type(Type::Int(*target)).into_int_type(); + Some(builder.build_int_truncate(val, target, "").unwrap().into()) + } + InstructionVariant::ICmp(lhs, rhs, cmp) => { + let u = !is_signed(lhs.ty()); + let cmp = match (cmp, u) { + (Cmp::Eq, _) => IntPredicate::EQ, + (Cmp::Ne, _) => IntPredicate::NE, + (Cmp::Lt, true) => IntPredicate::ULT, + (Cmp::Le, true) => IntPredicate::ULE, + (Cmp::Gt, true) => IntPredicate::UGT, + (Cmp::Ge, true) => IntPredicate::UGE, + (Cmp::Lt, false) => IntPredicate::SLT, + (Cmp::Le, false) => IntPredicate::SLE, + (Cmp::Gt, false) => IntPredicate::SGT, + (Cmp::Ge, false) => IntPredicate::SGE, + }; + let lhs = self.get_value(&values, lhs).unwrap().into_int_value(); + let rhs = self.get_value(&values, rhs).unwrap().into_int_value(); + Some(builder.build_int_compare(cmp, lhs, rhs, "").unwrap().into()) + } + + InstructionVariant::Call(func, args) => { + // TODO This will fail with external assemblies. Fix this. + let func = self.functions.get_sync(&(assembly, *func)).unwrap().clone(); + let args: Vec<_> = args + .iter() + .filter_map(|a| self.get_value(&values, a).map(|a| a.into())) + .collect(); + + let value = builder.build_direct_call(func, &args, "").unwrap(); + value.as_any_value_enum().try_into().ok() + } + InstructionVariant::Jump(block) => { + builder + .build_unconditional_branch(blocks[block.id as usize]) + .unwrap(); + None + } + InstructionVariant::Branch { + cond, + true_case, + false_case, + } => { + let cond = self.get_value(&values, cond).unwrap().into_int_value(); + builder + .build_conditional_branch( + cond, + blocks[true_case.id as usize], + blocks[false_case.id as usize], + ) + .unwrap(); + None + } + + InstructionVariant::Return(None) => { + builder.build_return(None).unwrap(); + None + } + + InstructionVariant::Return(Some(v)) => { + let v = self.get_value(&values, v); + builder + .build_return(v.as_ref().map(|v| v as &dyn BasicValue)) + .unwrap(); + None + } + + InstructionVariant::Reinterpret(v, t, _) => match (v.ty(), t) { + (Type::Ptr(_), Type::Ptr(_)) => { + Some(self.get_value(&values, v).unwrap()) + } + _ => todo!("{inst:?}"), + }, + + _ => { + module.print_to_stderr(); + todo!("{inst:?}"); + } + }; + values.insert((*inst).into(), llvm_value); + } + } + } + + module + } + + fn get_type(&self, ty: Type<'l>) -> AnyTypeEnum<'l> { + if let Some(ty) = self.types.get_sync(&ty) { + return *ty; + } + + let llvm_ty = match ty { + Type::Void => self.ctx.void_type().into(), + Type::I8 | Type::U8 => self.ctx.i8_type().into(), + Type::I16 | Type::U16 => self.ctx.i16_type().into(), + Type::I32 | Type::U32 => self.ctx.i32_type().into(), + Type::I64 | Type::U64 => self.ctx.i64_type().into(), + Type::I128 | Type::U128 => self.ctx.i128_type().into(), + Type::USIZE | Type::ISIZE => self.native_int_ty.into(), + + Type::Ptr(_) => self.ctx.ptr_type(AddressSpace::default()).into(), + + Type::Func(FuncT { ret_t, par_t, .. }) => { + let ret_t = self.get_type(*ret_t); + let par_t = par_t + .iter() + .map(|ty| self.get_type(*ty)) + .filter_map(|ty| BasicMetadataTypeEnum::try_from(ty).ok()) + .collect::>(); + + match ret_t { + AnyTypeEnum::VoidType(ty) => ty.fn_type(&par_t, false).into(), + AnyTypeEnum::IntType(ty) => ty.fn_type(&par_t, false).into(), + _ => todo!("{ret_t:?}"), + } + } + + Type::Array(ArrayT { + base, + length: Some(length), + .. + }) => { + let base = self.get_type(*base); + match base { + AnyTypeEnum::IntType(t) => t.array_type(*length).into(), + AnyTypeEnum::FloatType(t) => t.array_type(*length).into(), + AnyTypeEnum::StructType(t) => t.array_type(*length).into(), + _ => todo!("{ty:#?}"), + } + } + _ => todo!("{ty:#?}"), + }; + self.types.entry_sync(ty).or_insert(llvm_ty); + llvm_ty + } + + fn get_value( + &self, + map: &FxHashMap, Option>>, + val: &Value<'l>, + ) -> Option> { + if let Some(value) = map.get(val) { + return value.clone(); + } + match val { + Value::Constant(val) => match val { + AnyConst::Int(Int::U8(v)) => { + Some(self.ctx.i8_type().const_int(*v as u64, false).into()) + } + AnyConst::Int(Int::U16(v)) => { + Some(self.ctx.i16_type().const_int(*v as u64, false).into()) + } + AnyConst::Int(Int::U32(v)) => { + Some(self.ctx.i32_type().const_int(*v as u64, false).into()) + } + AnyConst::Int(Int::U64(v)) => { + Some(self.ctx.i64_type().const_int(*v as u64, false).into()) + } + AnyConst::Int(Int::USize(v)) => { + Some(self.native_int_ty.const_int(*v as u64, false).into()) + } + AnyConst::Array([]) => todo!("{val:?}"), + AnyConst::Array(array) => { + let ty = self.get_type(array[0].ty()); + match ty { + AnyTypeEnum::IntType(t) => { + let mut values = vec![]; + for v in *array { + let Some(BasicValueEnum::IntValue(v)) = + self.get_value(map, &Value::Constant(*v)) + else { + unreachable!(); + }; + values.push(v); + } + Some(t.const_array(&values).into()) + } + _ => todo!("{ty:?}"), + } + } + _ => todo!("{val:?}"), + }, + _ => unreachable!("{val:#?}"), + } + } +} + +#[inline] +fn is_signed(ty: Type) -> bool { + match ty { + Type::U8 | Type::U16 | Type::U32 | Type::U64 | Type::U128 | Type::USIZE => false, + Type::I8 | Type::I16 | Type::I32 | Type::I64 | Type::I128 | Type::ISIZE => true, + _ => unreachable!(), + } +} diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 707ef95..1bf311b 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -7,6 +7,8 @@ edition = "2024" leaf_parser = { path = "../parser" } leaf_assembly = { path = "../assembly" } leaf_allocators = { path = "../allocators" } +leaf_backend_llvm = { path = "../backends/llvm" } arcstr = "1.2.0" derive_more = { version = "2.0.1", features = ["deref", "deref_mut", "debug", "display", "try_from", "from", "try_into", "into"] } fxhash = "0.2.1" +itertools = "0.14.0" diff --git a/compiler/src/main.rs b/compiler/src/main.rs index 8b1a559..8ab3002 100644 --- a/compiler/src/main.rs +++ b/compiler/src/main.rs @@ -1,6 +1,13 @@ use arcstr::ArcStr; use leaf_allocators::SyncArenaAllocator; use leaf_assembly::assembly::{AssemblyIdentifier, Version}; +use leaf_backend_llvm::{ + LlvmContext, + inkwell::{ + OptimizationLevel, + targets::{CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine}, + }, +}; use leaf_compiler::CompilationContext; use leaf_parser::SourceCode; use std::{path::PathBuf, sync::Arc}; @@ -15,12 +22,42 @@ fn main() { if let Err(err) = context.extend( ident.clone(), &[Arc::new(SourceCode { - text: ArcStr::from(std::fs::read_to_string("../test.leaf").unwrap()), - file: PathBuf::from("../test.leaf"), + text: ArcStr::from(std::fs::read_to_string("test.leaf").unwrap()), + file: PathBuf::from("test.leaf"), })], ) { println!("{:#?}", err); - } else { - println!("{:#?}", context.get_assembly(&ident)); + return; } + + let assembly = context.get_assembly(&ident).unwrap(); + println!("{:#?}\n", assembly); + + Target::initialize_all(&InitializationConfig::default()); + let target_triple = TargetMachine::get_default_triple(); + let target = Target::from_name("aarch64").unwrap(); + + let target_machine = target + .create_target_machine( + &target_triple, + "generic", + "", // CPU features + OptimizationLevel::Default, + RelocMode::Default, + CodeModel::Default, + ) + .unwrap(); + + let ctx = LlvmContext::create(); + let ctx = leaf_backend_llvm::CompilationContext::new(&ctx, &target_machine); + let module = ctx.compile(assembly); + module.print_to_stderr(); + module.verify().unwrap(); + + let asm = target_machine + .write_to_memory_buffer(&module, FileType::Assembly) + .unwrap(); + let asm = std::str::from_utf8(asm.as_slice()).unwrap(); + eprintln!("{asm}"); + std::fs::write("out.asm", asm).unwrap(); } diff --git a/compiler/src/scope.rs b/compiler/src/scope.rs index 4cdf6c4..425938a 100644 --- a/compiler/src/scope.rs +++ b/compiler/src/scope.rs @@ -5,12 +5,12 @@ use leaf_assembly::{ Function, ir::{Cmp, FunctionBodyBuilder}, }, - types::{Type, derivations::PtrT}, + types::{IntT, Type, derivations::PtrT}, values::{AnyConst, Int, Value, ValueFlags}, }; use leaf_parser::{ SourceCode, - ast::{self, BinaryExpr, BinaryOp, ConstDecl, Expr, Ident, NamePattern, While}, + ast::{self, BinaryExpr, BinaryOp, ConstDecl, Expr, Ident, IndexingExpr, NamePattern, While}, }; use std::{collections::HashMap, sync::Arc}; @@ -177,12 +177,12 @@ impl<'l> Scope<'l> { } } - Expr::Binary(expr) => { + Expr::Binary(bin_expr) => { let BinaryExpr { lhs: lhs_expr, rhs: rhs_expr, op, - } = &**expr; + } = &**bin_expr; let mut lhs = self.compile_expression(lhs_expr, ctx)?; if lhs.flags().contains(ValueFlags::LValue) && !matches!(op, BinaryOp::Assign(_)) { @@ -196,28 +196,84 @@ impl<'l> Scope<'l> { let builder = ctx.builder.as_mut().unwrap(); match (lhs.ty(), rhs.ty(), op) { - (Type::U32, Type::U32, BinaryOp::Add(_)) => Ok(builder.add(lhs, rhs).unwrap()), - (Type::U32, Type::U32, BinaryOp::Mul(_)) => Ok(builder.mul(lhs, rhs).unwrap()), - (Type::U32, Type::U32, BinaryOp::Lt(_)) => { + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Add(_)) if a_ty == b_ty => { + Ok(builder.add(lhs, rhs).unwrap()) + } + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Sub(_)) if a_ty == b_ty => { + Ok(builder.sub(lhs, rhs).unwrap()) + } + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Mul(_)) if a_ty == b_ty => { + Ok(builder.mul(lhs, rhs).unwrap()) + } + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Div(_)) if a_ty == b_ty => { + Ok(builder.div(lhs, rhs).unwrap()) + } + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Mod(_)) if a_ty == b_ty => { + Ok(builder.modulo(lhs, rhs).unwrap()) + } + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Eq(_)) if a_ty == b_ty => { + Ok(builder.cmp(lhs, rhs, Cmp::Eq).unwrap()) + } + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Ne(_)) if a_ty == b_ty => { + Ok(builder.cmp(lhs, rhs, Cmp::Ne).unwrap()) + } + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Lt(_)) if a_ty == b_ty => { Ok(builder.cmp(lhs, rhs, Cmp::Lt).unwrap()) } - (Type::Ptr(PtrT { base, mutable, .. }), ty, BinaryOp::Assign(_)) => { - match *base == ty { - true => Ok(builder.store(lhs, rhs).unwrap()), - false => Err(CompilationError { - kind: Kind::InvalidType, - message: format!( - "Cannot assign a value of type `{ty}` to a value of type `{base}`." - ), + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Gt(_)) if a_ty == b_ty => { + Ok(builder.cmp(lhs, rhs, Cmp::Gt).unwrap()) + } + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Le(_)) if a_ty == b_ty => { + Ok(builder.cmp(lhs, rhs, Cmp::Le).unwrap()) + } + (Type::Int(a_ty), Type::Int(b_ty), BinaryOp::Ge(_)) if a_ty == b_ty => { + Ok(builder.cmp(lhs, rhs, Cmp::Ge).unwrap()) + } + (Type::Ptr(PtrT { base, .. }), ty, BinaryOp::Assign(_)) => match *base == ty { + true => Ok(builder.store(lhs, rhs).unwrap()), + false => Err(CompilationError { + kind: Kind::InvalidType, + message: format!( + "Cannot assign a value of type `{ty}` to a value of type `{base}`." + ), + location: Location::Range { + file: self.source.clone(), + range: rhs_expr.range(), + }, + cause: None, + }), + }, + (src_ty, Type::Type, BinaryOp::Cast(_)) => { + let Value::Constant(AnyConst::Type(dst_ty)) = rhs else { + return Err(CompilationError { + kind: Kind::NotAType, + message: format!("Cannot perform cast."), location: Location::Range { file: self.source.clone(), - range: rhs_expr.range(), + range: expr.range(), }, - cause: None, - }), + cause: Some(Box::new(CompilationError { + kind: Kind::NotAType, + message: format!("Cast target is not a type."), + location: Location::Range { + file: self.source.clone(), + range: rhs_expr.range(), + }, + cause: None, + })), + }); + }; + match (src_ty, dst_ty) { + (Type::Int(src_ty), Type::Int(dst_ty)) => { + if dst_ty.precision < src_ty.precision { + return Ok(builder.trunc(lhs, dst_ty).unwrap().into()); + } + todo!("{src_ty} as {dst_ty}"); + } + _ => todo!("{src_ty} as {dst_ty}"), } } - (a, b, _) => unimplemented!("{a} {op:?} {b} | {lhs:?} {op:?} {rhs:?}"), + (a, b, _) => todo!("{a} {op:?} {b} | {lhs:?} {op:?} {rhs:?}"), } } @@ -282,15 +338,123 @@ impl<'l> Scope<'l> { Ok(builder.call(func, args).unwrap()) } - Expr::Type(expr) => match &**expr { + Expr::Type(ty_expr) => match &**ty_expr { ast::Type::Ptr { base, mutable } => match self.compile_expression(base, ctx)? { Value::Constant(AnyConst::Type(ty)) => { Ok(AnyConst::Type(Type::Ptr(ty.make_ptr(*mutable))).into()) } - _ => todo!(), + Value::Instruction(inst) if inst.value_flags().contains(ValueFlags::LValue) => { + let Type::Ptr(PtrT { + base, + mutable: is_mut, + .. + }) = inst.value_ty() + else { + unreachable!() + }; + if *mutable && !*is_mut { + return Err(CompilationError { + kind: Kind::NotAFunction, + message: "Cannot obtain a mutable pointer to an immutable value." + .into(), + location: Location::Range { + file: self.source.clone(), + range: expr.range(), + }, + cause: None, + }); + } + let mut flags = inst.value_flags(); + let builder = ctx.builder.as_mut().unwrap(); + flags.remove( + ValueFlags::Mutable | ValueFlags::Volatile | ValueFlags::LValue, + ); + let ptr = Type::Ptr(base.make_ptr(*mutable)); + unsafe { + Ok(builder + .reinterpret(Value::Instruction(inst), ptr, flags) + .unwrap() + .into()) + } + } + v => todo!("{v:?}"), }, }, + Expr::List(expr) => { + let mut expr = expr.iter(); + let mut values = Vec::with_capacity(expr.len()); + match expr.next() { + None => return Ok(Value::Constant(AnyConst::Array(&[]))), + Some(expr) => { + let value = self.compile_expression(expr, ctx)?; + // TODO Check if it matches the ctx type hint + values.push(value); + } + }; + let element_ty = values[0].ty(); + for expr in expr { + let value = self.compile_expression(expr, ctx)?; + if value.ty() != element_ty { + return Err(CompilationError { + kind: Kind::InvalidType, + message: format!( + "Expected type `{}`, found `{}`", + element_ty, + value.ty() + ), + location: Location::Range { + file: self.source.clone(), + range: expr.range(), + }, + cause: None, + }); + } + values.push(value); + } + if values.iter().all(|v| matches!(v, Value::Constant(_))) { + let alloc = self.assembly.ctx().alloc(); + return Ok(Value::Constant(AnyConst::Array(alloc.alloc_slice( + values.into_iter().map(|v| match v { + Value::Constant(c) => c, + _ => unreachable!(), + }), + )))); + } + todo!() + } + + Expr::Index(expr) => { + let IndexingExpr { value, index } = &**expr; + let value = self.compile_expression(value, ctx)?; + let mut index = self.compile_expression(index, ctx)?; + let builder = ctx.builder.as_mut().unwrap(); + + if index.flags().contains(ValueFlags::LValue) { + index = builder.load(index).unwrap(); + } + + // TODO Add support for custom indexing operations + if index.ty() != Type::USIZE { + return Err(CompilationError { + kind: Kind::InvalidType, + message: "Value is not of type `usize`".into(), + location: Location::Range { + file: self.source.clone(), + range: expr.index.range(), + }, + cause: None, + }); + } + + if value.flags().contains(ValueFlags::LValue) { + let gep = builder.gep(value, index).unwrap(); + return Ok(gep); + } + + todo!("{:#?}", value.ty()); + } + _ => todo!("{expr:#?}"), } } @@ -342,7 +506,17 @@ impl<'l> Scope<'l> { } if !builder.current_block().has_termination() { - builder.ret(last_expr).unwrap(); + match func.ty.ret_t { + Type::Void => builder.ret(None).unwrap(), + _ => { + if let Some(expr) = last_expr.as_mut() { + if expr.flags().contains(ValueFlags::LValue) { + *expr = builder.load(*expr).unwrap(); + } + } + builder.ret(last_expr).unwrap() + } + }; } builder.build().unwrap(); diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml deleted file mode 100644 index c353cb5..0000000 --- a/interpreter/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "leaf_interpreter" -version = "0.1.0" -edition = "2024" - -[dependencies] -fxhash = "0.2.1" -leaf_allocators = { path = "../allocators" } -leaf_assembly = { path = "../assembly" } -leaf_compiler = { path = "../compiler" } -leaf_parser = { path = "../parser" } -scc = "3.3.7" -smallvec = "1.15.1" diff --git a/interpreter/src/instruction_cache.rs b/interpreter/src/instruction_cache.rs deleted file mode 100644 index ed54a6a..0000000 --- a/interpreter/src/instruction_cache.rs +++ /dev/null @@ -1,328 +0,0 @@ -use crate::layout_cache::{GetLayout, LayoutCache}; -use fxhash::{FxBuildHasher, FxHashMap}; -use leaf_assembly::{ - functions::{ - Function, - ir::{Cmp, Instruction, InstructionVariant}, - }, - types::{IntT, Type}, - values::{AnyConst, Int, Value}, -}; -use scc::HashMap; -use std::{alloc::Layout, fmt::Debug, ops::Range, sync::Arc}; - -#[derive(Debug)] -#[allow(non_camel_case_types)] -pub enum OpCode<'l> { - Store_CL_U8(u8, usize), - Store_CL_U16(u16, usize), - Store_CL_U32(u32, usize), - Store_CL_U64(u64, usize), - - Add_LL_U32(usize, usize, usize), - Add_LC_U32(usize, u32, usize), - - CmpEq_LC_U32(usize, u32, usize), - CmpLt_LC_U32(usize, u32, usize), - CmpGt_LC_U32(usize, u32, usize), - CmpLe_LC_U32(usize, u32, usize), - CmpGe_LC_U32(usize, u32, usize), - - Call(&'l Function<'l>, Vec>, Range), - Jump(usize), - Branch { - cond: usize, - true_case: usize, - false_case: usize, - }, - - CopyRange(Range, Range), - ReturnRange(Range), -} - -#[non_exhaustive] -pub struct InstructionCacheEntry<'l> { - pub layout: Layout, - pub opcodes: Vec>, -} - -impl Debug for InstructionCacheEntry<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - struct OpCodes<'a, 'b>(&'a [OpCode<'b>]); - impl Debug for OpCodes<'_, '_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut dbg = f.debug_list(); - for op in self.0 { - dbg.entry(&format_args!("{op:?}")); - } - dbg.finish() - } - } - - f.debug_struct("InstructionCacheEntry") - .field("layout", &self.layout) - .field("opcodes", &OpCodes(&self.opcodes)) - .finish() - } -} - -impl<'l> InstructionCacheEntry<'l> { - pub fn new(func: &'l Function<'l>, layouts: &LayoutCache<'l>) -> Self { - let mut opcodes = Vec::new(); - let mut layout = Layout::new::<()>(); - let mut memory_ranges = FxHashMap::default(); - - macro_rules! alloc_range { - ($ty:expr, $id:expr) => {{ - &*memory_ranges.entry($id).or_insert_with(|| { - let l = layouts.get_layout($ty); - let (new_layout, offset) = layout.extend(l.layout).unwrap(); - layout = new_layout; - offset..offset + l.layout.size() - }) - }}; - } - - let Some(body) = func.body() else { - unreachable!("InstructionCacheEntry::new called with a function without a body."); - }; - - let mut backpatch = vec![]; - let mut block_starts = Vec::with_capacity(body.blocks.len()); - - for inst in body.blocks.iter().flat_map(|b| b.instructions()) { - match &inst.variant { - InstructionVariant::StackAlloc(ty) => { - let _ = alloc_range!(*ty, inst.id()); - } - _ => {} - } - } - - for block in &body.blocks { - block_starts.push(opcodes.len()); - for inst in block.instructions() { - match &inst.variant { - InstructionVariant::StackAlloc(_) => {} - - InstructionVariant::Store( - Value::Instruction( - t @ Instruction { - variant: InstructionVariant::StackAlloc(ty), - .. - }, - ), - Value::Constant(AnyConst::Int(Int::U8(v))), - ) => { - assert_eq!(Type::U8, *ty); - opcodes.push(OpCode::Store_CL_U8(*v, memory_ranges[&t.id()].start)); - } - - InstructionVariant::Store( - Value::Instruction( - t @ Instruction { - variant: InstructionVariant::StackAlloc(ty), - .. - }, - ), - Value::Constant(AnyConst::Int(Int::U16(v))), - ) => { - assert_eq!(Type::U16, *ty); - opcodes.push(OpCode::Store_CL_U16(*v, memory_ranges[&t.id()].start)); - } - - InstructionVariant::Store( - Value::Instruction( - t @ Instruction { - variant: InstructionVariant::StackAlloc(ty), - .. - }, - ), - Value::Constant(AnyConst::Int(Int::U32(v))), - ) => { - assert_eq!(Type::U32, *ty); - opcodes.push(OpCode::Store_CL_U32(*v, memory_ranges[&t.id()].start)); - } - - InstructionVariant::Store( - Value::Instruction( - t @ Instruction { - variant: InstructionVariant::StackAlloc(ty), - .. - }, - ), - Value::Constant(AnyConst::Int(Int::U64(v))), - ) => { - assert_eq!(Type::U64, *ty); - opcodes.push(OpCode::Store_CL_U64(*v, memory_ranges[&t.id()].start)); - } - - InstructionVariant::Store( - Value::Instruction( - t @ Instruction { - variant: InstructionVariant::StackAlloc(ty), - .. - }, - ), - Value::Instruction(v), - ) => { - let src = alloc_range!(*ty, v.id()).clone(); - let dst = alloc_range!(*ty, t.id()).clone(); - opcodes.push(OpCode::CopyRange(src, dst)); - } - - InstructionVariant::Load(Value::Instruction( - t @ Instruction { - variant: InstructionVariant::StackAlloc(ty), - .. - }, - )) => { - let src = alloc_range!(*ty, t.id()).clone(); - let dst = alloc_range!(*ty, inst.id()).clone(); - opcodes.push(OpCode::CopyRange(src, dst)); - } - - InstructionVariant::IAdd(Value::Instruction(a), Value::Instruction(b)) => { - match a.value_ty() { - ty @ Type::Int(IntT { - signed: false, - precision: 32, - .. - }) => { - let a = memory_ranges[&a.id()].start; - let b = memory_ranges[&b.id()].start; - let c = alloc_range!(ty, inst.id()).start; - opcodes.push(OpCode::Add_LL_U32(a, b, c)); - } - _ => todo!("Unimplemented type `{}`", a.value_ty()), - } - } - - InstructionVariant::IAdd( - Value::Instruction(a), - Value::Constant(AnyConst::Int(Int::U32(b))), - ) => { - assert!(matches!( - a.value_ty(), - Type::Int(IntT { - signed: false, - precision: 32, - .. - }) - )); - let a = memory_ranges[&a.id()].start; - let c = alloc_range!(Type::U32, inst.id()).start; - opcodes.push(OpCode::Add_LC_U32(a, *b, c)); - } - - InstructionVariant::ICmp( - Value::Instruction(a), - Value::Constant(AnyConst::Int(Int::U32(b))), - cmp, - ) => { - assert!(matches!( - a.value_ty(), - Type::Int(IntT { - signed: false, - precision: 32, - .. - }) - )); - let a = memory_ranges[&a.id()].start; - let target = alloc_range!(Type::Bool, inst.id()).start; - match *cmp { - Cmp::Eq => opcodes.push(OpCode::CmpEq_LC_U32(a, *b, target)), - Cmp::Lt => opcodes.push(OpCode::CmpLt_LC_U32(a, *b, target)), - Cmp::Gt => opcodes.push(OpCode::CmpGt_LC_U32(a, *b, target)), - Cmp::Le => opcodes.push(OpCode::CmpLe_LC_U32(a, *b, target)), - Cmp::Ge => opcodes.push(OpCode::CmpGe_LC_U32(a, *b, target)), - }; - } - - InstructionVariant::Call(func, args) => { - let mut ranges = Vec::with_capacity(args.len()); - for arg in args { - match arg { - Value::Instruction(i) => { - ranges.push(memory_ranges[&i.id()].clone()); - } - _ => todo!("Unimplemented variant `{arg:?}`."), - } - } - opcodes.push(OpCode::Call( - func, - ranges, - alloc_range!(func.ty.ret_t, inst.id()).clone(), - )); - } - - InstructionVariant::Jump(target) => { - backpatch.push(opcodes.len()); - opcodes.push(OpCode::Jump(target.id as usize)); - } - - InstructionVariant::Branch { - cond: Value::Instruction(i), - true_case, - false_case, - } => { - assert!(matches!(i.value_ty(), Type::Bool)); - backpatch.push(opcodes.len()); - opcodes.push(OpCode::Branch { - cond: memory_ranges[&i.id()].start, - true_case: true_case.id as usize, - false_case: false_case.id as usize, - }); - } - - InstructionVariant::Return(Some(Value::Instruction(t))) => { - assert_eq!(func.ty.ret_t, t.value_ty()); - opcodes.push(OpCode::ReturnRange(memory_ranges[&t.id()].clone())); - } - - _ => todo!("Unimplemented instruction `{inst:?}`"), - } - } - } - - for idx in backpatch { - match &mut opcodes[idx] { - OpCode::Jump(target) => { - *target = block_starts[*target]; - } - OpCode::Branch { - true_case, - false_case, - .. - } => { - *true_case = block_starts[*true_case]; - *false_case = block_starts[*false_case]; - } - _ => unreachable!(), - } - } - - Self { layout, opcodes } - } -} - -pub struct InstructionCache<'l> { - layouts: Arc>, - entries: HashMap<*const Function<'l>, Arc>, FxBuildHasher>, -} - -impl<'l> InstructionCache<'l> { - pub fn new(layouts: Arc>) -> Self { - Self { - layouts, - entries: HashMap::default(), - } - } - - pub fn get(&self, func: &'l Function<'l>) -> Arc> { - self.entries - .entry_sync(func as *const Function<'l>) - .or_insert_with(|| Arc::new(InstructionCacheEntry::new(func, &self.layouts))) - .clone() - } -} diff --git a/interpreter/src/interpreter.rs b/interpreter/src/interpreter.rs deleted file mode 100644 index f6859a6..0000000 --- a/interpreter/src/interpreter.rs +++ /dev/null @@ -1,233 +0,0 @@ -use crate::{ - instruction_cache::{InstructionCache, InstructionCacheEntry, OpCode}, - layout_cache::LayoutCache, -}; -use fxhash::FxHashMap; -use leaf_assembly::{ - assembly::Ctx, - functions::Function, - types::{IntT, Type}, - values::{AnyConst, Int, Value}, -}; -use smallvec::SmallVec; -use std::{ops::Range, sync::Arc}; - -#[derive(Debug)] -pub enum Error { - InvalidParameterCount, - InvalidParameterType, - FunctionHasNoBody, -} - -#[derive(Debug)] -pub enum AnyValue { - Void, - Int(Int), - Ptr(*const u8), -} - -#[derive(Clone)] -pub enum NativeFunction { - Plain(fn(&mut Interpreter, &[Range]) -> Result), - Closure(Arc]) -> Result>), -} - -pub struct Interpreter<'l> { - ctx: Ctx<'l>, - stack: Vec, - layouts: Arc>, - instructions: Arc>, - native_funcs: FxHashMap<*const Function<'l>, NativeFunction>, -} - -impl<'l> Interpreter<'l> { - pub fn new(ctx: Ctx<'l>) -> Self { - let layouts = Arc::new(LayoutCache::default()); - Self { - ctx, - stack: vec![0; 1024], - layouts: layouts.clone(), - instructions: Arc::new(InstructionCache::new(layouts)), - native_funcs: FxHashMap::default(), - } - } - - pub fn register_function(&mut self, func: &'l Function<'l>, native_function: NativeFunction) { - self.native_funcs.insert(func, native_function); - } - - pub fn run(&mut self, func: &'l Function<'l>, args: Vec>) -> Result { - if func.body().is_none() { - return Err(Error::FunctionHasNoBody); - }; - - if func.ty.par_t.len() != args.len() { - return Err(Error::InvalidParameterCount); - } - - if func - .ty - .par_t - .iter() - .zip(&args) - .any(|(ty, arg)| *ty != arg.ty()) - { - return Err(Error::InvalidParameterType); - } - - let mut sp = 0; - let mut args_ranges: SmallVec<[Range; 4]> = args - .into_iter() - .map(|v| self.push_ctx_val(&mut sp, &v)) - .collect(); - - let func_entry = self.instructions.get(func); - let ret_range = self.call(sp, &mut args_ranges, &func_entry); - match func.ty.ret_t { - Type::Int(IntT { - signed: false, - precision: 32, - .. - }) => { - let mem = &self.stack[ret_range]; - let val = u32::from_ne_bytes(mem.try_into().unwrap()); - Ok(AnyValue::Int(Int::U32(val))) - } - _ => todo!("Unsupported type `{}`", func.ty.ret_t), - } - } - - fn call( - &mut self, - sp: usize, - _args: &[Range], - function: &InstructionCacheEntry<'l>, - ) -> Range { - unsafe { - let l = function.layout; - let sp = sp + (self.stack.as_ptr().add(sp)).align_offset(l.align()); - assert!(sp + l.size() < self.stack.len()); - - // Since the earlier assertion guarantees the stack won't overflow, we can skip the range checks. - let mut i = 0; - while let Some(opcode) = function.opcodes.get(i) { - match opcode { - OpCode::Store_CL_U32(v, offset) => { - let start = sp + *offset; - self.stack - .get_unchecked_mut(start..start + 4) - .copy_from_slice(&v.to_ne_bytes()); - } - - OpCode::CopyRange(src, dst) => { - let len = src.len(); - let src = self.stack.as_ptr().add(sp + src.start); - let dst = self.stack.as_mut_ptr().add(sp + dst.start); - std::ptr::copy_nonoverlapping(src, dst, len); - } - - OpCode::Add_LL_U32(a, b, dst) => { - let [a, b, dst] = [ - sp + a..sp + a + 4, - sp + b..sp + b + 4, - sp + dst..sp + dst + 4, - ]; - let a = u32::from_ne_bytes(self.stack.get_unchecked(a).try_into().unwrap()); - let b = u32::from_ne_bytes(self.stack.get_unchecked(b).try_into().unwrap()); - self.stack - .get_unchecked_mut(dst) - .copy_from_slice(&a.wrapping_add(b).to_ne_bytes()); - } - - OpCode::Add_LC_U32(a, b, dst) => { - let [a, dst] = [sp + a..sp + a + 4, sp + dst..sp + dst + 4]; - let a = u32::from_ne_bytes(self.stack.get_unchecked(a).try_into().unwrap()); - self.stack - .get_unchecked_mut(dst) - .copy_from_slice(&a.wrapping_add(*b).to_ne_bytes()); - } - - OpCode::CmpLt_LC_U32(a, b, dst) => { - let a = sp + a..sp + a + 4; - let a = u32::from_ne_bytes(self.stack.get_unchecked(a).try_into().unwrap()); - *self.stack.get_unchecked_mut(*dst) = (a < *b) as u8; - } - - OpCode::Jump(target) => { - i = *target; - continue; - } - - OpCode::Branch { - cond, - true_case, - false_case, - } => { - i = match self.stack().get_unchecked(*cond) { - 0 => *false_case, - _ => *true_case, - }; - continue; - } - - OpCode::Call(func, args, out) => { - let args: SmallVec<[Range; 4]> = args - .iter() - .map(|range| sp + range.start..sp + range.end) - .collect(); - let key = *func as *const Function<'l>; - match self.native_funcs.get(&key).cloned() { - Some(NativeFunction::Plain(native_func)) => { - let res = native_func(self, &args).unwrap(); - self.write_any_val(out, &res); - } - Some(NativeFunction::Closure(native_func)) => { - let res = native_func(self, &args).unwrap(); - self.write_any_val(out, &res); - } - None => { - let func = self.instructions.get(func); - let res = self.call(sp, &args, &func); - self.stack.copy_within(res, out.start); - } - } - } - - OpCode::ReturnRange(range) => { - return sp + range.start..sp + range.end; - } - _ => todo!("Unimplemented opcode `{opcode:?}`"), - } - i += 1; - } - } - unreachable!("Execution has produced no results"); - } - - pub fn stack(&self) -> &[u8] { - &self.stack - } - - fn push_ctx_val(&mut self, sp: &mut usize, value: &Value) -> Range { - let ptr = &self.stack[*sp] as *const u8; - match value { - Value::Constant(AnyConst::Int(Int::U32(v))) => { - *sp += ptr.align_offset(align_of::()); - let range = *sp..*sp + size_of::(); - self.stack[range.clone()].copy_from_slice(&v.to_ne_bytes()); - range - } - _ => todo!("Unsupported type `{}`", value.ty()), - } - } - - fn write_any_val(&mut self, range: &Range, value: &AnyValue) { - match value { - AnyValue::Void => assert_eq!(range.len(), 0), - AnyValue::Int(Int::U32(v)) => { - self.stack[range.clone()].copy_from_slice(&v.to_ne_bytes()); - } - _ => todo!("Unsupported value `{:?}`", value), - } - } -} diff --git a/interpreter/src/layout_cache.rs b/interpreter/src/layout_cache.rs deleted file mode 100644 index 4080b32..0000000 --- a/interpreter/src/layout_cache.rs +++ /dev/null @@ -1,101 +0,0 @@ -use fxhash::FxBuildHasher; -use leaf_assembly::types::{IntT, Type, derivations::PtrT}; -use scc::HashMap; -use std::{ - alloc::Layout, - any::Any, - marker::PhantomData, - ops::Range, - sync::{Arc, OnceLock}, -}; - -#[derive(Debug)] -pub struct LayoutElement { - pub offset: usize, - pub layout: Arc, -} - -impl LayoutElement { - pub fn range(&self) -> Range { - self.offset..self.offset + self.layout.layout.size() - } -} - -#[derive(Debug)] -pub struct TypeLayout { - pub layout: Layout, - pub elements: Vec<(usize, Arc)>, -} - -#[derive(Default)] -pub struct LayoutCache<'l> { - phantom: PhantomData<&'l ()>, - layouts: HashMap<*const u8, Arc, FxBuildHasher>, -} - -pub trait GetLayout { - type LayoutT; - fn get_layout(&self, value: T) -> Arc; -} - -impl<'l> GetLayout> for LayoutCache<'l> { - type LayoutT = TypeLayout; - - fn get_layout(&self, ty: Type<'_>) -> Arc { - match ty { - Type::Void => { - static LAYOUT: OnceLock> = OnceLock::new(); - LAYOUT - .get_or_init(|| { - Arc::new(TypeLayout { - layout: Layout::new::<()>(), - elements: Vec::new(), - }) - }) - .clone() - } - - Type::Bool => { - static LAYOUT: OnceLock> = OnceLock::new(); - LAYOUT - .get_or_init(|| { - Arc::new(TypeLayout { - layout: Layout::new::(), - elements: Vec::new(), - }) - }) - .clone() - } - - Type::Int(IntT { - signed: false, - precision: 32, - .. - }) => { - static LAYOUT: OnceLock> = OnceLock::new(); - LAYOUT - .get_or_init(|| { - Arc::new(TypeLayout { - layout: Layout::new::(), - elements: Vec::new(), - }) - }) - .clone() - } - - Type::Ptr(PtrT { .. }) => { - static LAYOUT: OnceLock> = OnceLock::new(); - LAYOUT - .get_or_init(|| { - Arc::new(TypeLayout { - layout: Layout::new::<*const u8>(), - elements: Vec::new(), - }) - }) - .clone() - } - - _ => todo!("Unsupported type {ty}"), - } - } -} diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs deleted file mode 100644 index 121a5d3..0000000 --- a/interpreter/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod interpreter; -pub mod instruction_cache; -pub mod layout_cache; diff --git a/interpreter/src/main.rs b/interpreter/src/main.rs deleted file mode 100644 index 086d0cf..0000000 --- a/interpreter/src/main.rs +++ /dev/null @@ -1,87 +0,0 @@ -use leaf_allocators::SyncArenaAllocator; -use leaf_assembly::assembly::{AssemblyIdentifier, Version}; -use leaf_compiler::CompilationContext; -use leaf_interpreter::interpreter::{AnyValue, Error, Interpreter, NativeFunction}; -use leaf_parser::SourceCode; -use std::ffi::CStr; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::{Duration, Instant}; - -fn main() { - let allocator = SyncArenaAllocator::default(); - let context = CompilationContext::new(&allocator); - let ident = AssemblyIdentifier { - version: Version::default(), - name: "interpreter.il".into(), - }; - context - .extend( - ident.clone(), - &[Arc::new(SourceCode { - text: std::fs::read_to_string("../test.leaf").unwrap().into(), - file: PathBuf::from("../test.leaf"), - })], - ) - .unwrap(); - - let mut interpreter = Interpreter::new(&context); - let assembly = context.get_assembly(&ident).unwrap(); - - let puts = assembly - .find_function(|f| f.name.get() == Some(&"puts")) - .unwrap(); - - let print_u32 = assembly - .find_function(|f| f.name.get() == Some(&"print_u32")) - .unwrap(); - - let main = assembly - .find_function(|f| f.name.get() == Some(&"main")) - .unwrap(); - - interpreter.register_function( - puts, - NativeFunction::Plain(|int, args| match args { - [range] => unsafe { - let mem = &int.stack()[range.clone()]; - let ptr = usize::from_ne_bytes(mem.try_into().unwrap()); - let cstr = CStr::from_ptr(ptr as *const i8); - print!("{cstr:?}"); - Ok(AnyValue::Void) - }, - _ => Err(Error::InvalidParameterCount), - }), - ); - - interpreter.register_function( - print_u32, - NativeFunction::Plain(|int, args| match args { - [range] => { - let mem = &int.stack()[range.clone()]; - let val = u32::from_ne_bytes(mem.try_into().unwrap()); - println!("{val}"); - Ok(AnyValue::Void) - } - _ => Err(Error::InvalidParameterCount), - }), - ); - - { - let now = Instant::now(); - let result = interpreter.run(main, vec![]); - let elapsed = now.elapsed(); - println!("Ret Value: {result:?}"); - println!("Cold Time: {:?}", elapsed) - } - { - const LOOPS: u32 = 1; - let mut duration = Duration::default(); - for _ in 0..LOOPS { - let now = Instant::now(); - let _ = interpreter.run(main, vec![]); - duration += now.elapsed(); - } - println!("Warm Time: {:?}", duration / LOOPS); - } -} diff --git a/parser/src/ast.rs b/parser/src/ast.rs index b653e87..5da4315 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -55,7 +55,8 @@ pub enum Expr { impl Expr { pub fn range(&self) -> Range { match self { - _ => todo!(), + Self::Ident(e) => e.range(), + _ => todo!("{self:?}"), } } } @@ -82,12 +83,15 @@ pub enum BinaryOp { #[debug("{_0}")] Div(Substr), #[debug("{_0}")] Mod(Substr), #[debug("{_0}")] Dot(Substr), + #[debug("{_0}")] Eq(Substr), + #[debug("{_0}")] Ne(Substr), #[debug("{_0}")] Lt(Substr), #[debug("{_0}")] Gt(Substr), #[debug("{_0}")] Le(Substr), #[debug("{_0}")] Ge(Substr), #[debug("{_0}")] Range(Substr), #[debug("{_0}")] Assign(Substr), + #[debug("{_0}")] Cast(Substr), } #[derive(Debug)] diff --git a/parser/src/parser.rs b/parser/src/parser.rs index 6044ce4..84316ed 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -69,7 +69,9 @@ peg::parser! { // ### EXPRESSIONS #### rule expr() -> Expr = precedence! { - lhs:(@) __ op:$("=") __ rhs:@ { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Assign(op), rhs })) } + lhs:(@) __ op:$("as") __ rhs:@ { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Cast(op), rhs })) } + -- + lhs:@ __ op:$("=") __ rhs:expr() { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Assign(op), rhs })) } lhs:(@) __ op:$(".") __ rhs:@ { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Dot(op), rhs })) } lhs:(@) "(" __ args:(expr() ** ("," __)) __ ")" { Expr::Call { func: Box::new(lhs), args } } value:(@) "[" __ index:expr() __ "]" { Expr::Index(Box::new(IndexingExpr { value, index })) } @@ -84,6 +86,8 @@ peg::parser! { -- lhs:(@) __ op:$("..") __ rhs:@ { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Range(op), rhs })) } -- + lhs:(@) __ op:$("==") __ rhs:@ { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Eq(op), rhs })) } + lhs:(@) __ op:$("!=") __ rhs:@ { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Ne(op), rhs })) } lhs:(@) __ op:$("<") __ rhs:@ { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Lt(op), rhs })) } lhs:(@) __ op:$(">") __ rhs:@ { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Gt(op), rhs })) } lhs:(@) __ op:$("<=") __ rhs:@ { Expr::Binary(Box::new(BinaryExpr { lhs, op: BinaryOp::Le(op), rhs })) } @@ -96,7 +100,7 @@ peg::parser! { var_decl:var_decl() { Expr::VarDecl(Box::new(var_decl)) } const_decl:const_decl() { Expr::ConstDecl(Box::new(const_decl)) } "(" __ tuple:(expr() **<2,> ("," __)) __ ")" { Expr::Tuple(tuple) } - "[" __ list:(expr() **<0,> ("," __)) __ "]" { Expr::List(list) } + "[" __ list:(expr() ** ("," __)) __ "]" { Expr::List(list) } "(" __ v:expr() __ ")" { v } "*" __ m:"mut"? __ v:expr() { Expr::Type(Box::new(Type::Ptr { base:v, mutable: m.is_some() })) } v:string() { Expr::String(v) } diff --git a/test.leaf b/test.leaf deleted file mode 100644 index 84d2746..0000000 --- a/test.leaf +++ /dev/null @@ -1,14 +0,0 @@ -puts :: fn(v: *u8) -> i32; -print_u32 :: fn(v: u32); - -print_up_to :: fn(max: u32) { - i := 0u32 - while i < max { - print_u32(i) - i = i + 1u32 - } -} - -main :: fn() { - print_up_to(42u32) -}