diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index e923f3cc..a374e6a7 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -28,7 +28,7 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: nightly - target: x86_64-unknown-none + target: x86_64-unknown-none,wasm32-unknown-unknown components: clippy - name: Setup run: sudo apt update && sudo apt install gcc-multilib just @@ -43,7 +43,7 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: nightly - target: x86_64-unknown-none + target: x86_64-unknown-none,wasm32-unknown-unknown - name: Setup run: | sudo apt update && sudo apt install gcc-multilib just diff --git a/.gitignore b/.gitignore index 4cb4dcef..40673047 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .build .fix fix/wasm/coupon-collector.wat +.vscode +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index 164483f2..92e6d8b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,9 +69,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "ar_archive_writer" @@ -669,6 +669,17 @@ dependencies = [ "objc2", ] +[[package]] +name = "dlmalloc" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5208a115eaba24916f7456929832e310a81518c641f93fee4f89aa93aa3675" +dependencies = [ + "cfg-if", + "libc", + "windows-sys", +] + [[package]] name = "either" version = "1.16.0" @@ -796,6 +807,8 @@ dependencies = [ "crossbeam-queue", "derive_more", "fixhandle", + "fixparser", + "fixpostprocessor", "fixshell", "futures", "hex", @@ -819,6 +832,24 @@ dependencies = [ "derive_more", ] +[[package]] +name = "fixparser" +version = "0.1.0" +dependencies = [ + "dlmalloc", + "fixutils", + "hex", +] + +[[package]] +name = "fixpostprocessor" +version = "0.1.0" +dependencies = [ + "anyhow", + "wasm-encoder", + "wasmparser", +] + [[package]] name = "fixshell" version = "0.1.0" @@ -832,12 +863,27 @@ dependencies = [ "user", ] +[[package]] +name = "fixutils" +version = "0.1.0" +dependencies = [ + "cc", + "fixhandle", + "macros", +] + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "futures" version = "0.3.32" @@ -974,11 +1020,22 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", "rustc-std-workspace-alloc", "rustc-std-workspace-core", ] +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] + [[package]] name = "hashbrown" version = "0.17.1" @@ -1047,6 +1104,8 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", + "serde", + "serde_core", ] [[package]] @@ -1133,6 +1192,12 @@ dependencies = [ "vmm-sys-util", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.186" @@ -2046,6 +2111,29 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasm-encoder" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.16.1", + "indexmap", + "semver", + "serde", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 604fc715..85a44fb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = [ "common", "vmm", "kernel", "macros" , "user", "arca" , "arcane", "fix", "fix/handle", "fix/shell" ] +members = [ "common", "vmm", "kernel", "macros" , "user", "arca" , "arcane", "fix", "fix/handle", "fix/shell", "fix/parser" ] default-members = [ "common", "vmm", "macros", "arca", "fix/handle" ] resolver = "2" diff --git a/addblob.fix b/addblob.fix index 0ce79428..f99e553b 100644 --- a/addblob.fix +++ b/addblob.fix @@ -1,2 +1,2 @@ -(let ((add @"./target/x86_64-unknown-none/addblob")) - !*(add !*(add 2 3) 1)) \ No newline at end of file +(let ((add @"./target/x86_64-unknown-none/debug/addblob")) + *#(add *#(add 2u64 3u64) 1u64)) \ No newline at end of file diff --git a/addblob_extended.fix b/addblob_extended.fix index f221d6d6..12b89109 100644 --- a/addblob_extended.fix +++ b/addblob_extended.fix @@ -1,27 +1,26 @@ -(let ((add @"./target/x86_64-unknown-none/addblob") - (a 1) (b 2) (c 3) (d 4) (e 5) (f 6) (g 7) - (h 8) (i 9) (j 10) (k 11) (l 12) (m 13) - (n 14) (o 15) (p 16)) - !*(add - !*(add - !*(add - !*(add a b) - !*(add c d) +(let ((add @"./target/x86_64-unknown-none/debug/addblob") + (a 1u64) (b 2u64) (c 3u64) (d 4u64) (e 5u64) (f 6u64) (g 7u64) (h 8u64) + (i 9u64) (j 10u64) (k 11u64) (l 12u64) (m 13u64) (n 14u64) (o 15u64) (p 16u64)) + *#(add + *#(add + *#(add + *#(add a b) + *#(add c d) ) - !*(add - !*(add e f) - !*(add g h) + *#(add + *#(add e f) + *#(add g h) ) ) - !*(add - !*(add - !*(add i j) - !*(add k l) + *#(add + *#(add + *#(add i j) + *#(add k l) ) - !*(add - !*(add m n) - !*(add o p) + *#(add + *#(add m n) + *#(add o p) ) ) ) -) +) \ No newline at end of file diff --git a/common/src/lib.rs b/common/src/lib.rs index 66425c37..43a154ac 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -12,21 +12,28 @@ #![feature(unboxed_closures)] #![cfg_attr(feature = "thread_local_cache", feature(thread_local))] -pub mod buddy; -pub mod refcnt; -pub use buddy::BuddyAllocator; -pub mod arrayvec; pub mod bitpack; -pub mod controlreg; -pub mod elfloader; -pub mod ipaddr; -pub mod pipe; -pub mod protocol; -pub mod sendable; -pub mod util; -#[cfg(feature = "std")] -pub mod mmap; +#[cfg(not(target_family = "wasm"))] +#[path = "."] +mod not_wasm { + pub mod arrayvec; + pub mod buddy; + pub mod controlreg; + pub mod elfloader; + pub mod ipaddr; + pub mod pipe; + pub mod protocol; + pub mod refcnt; + pub mod sendable; + pub mod util; + + #[cfg(feature = "std")] + pub mod mmap; +} + +#[cfg(not(target_family = "wasm"))] +pub use not_wasm::{buddy::BuddyAllocator, *}; #[repr(C)] #[derive(Debug)] diff --git a/demo.fix b/demo.fix index 2aa9a39c..f7c04eb9 100644 --- a/demo.fix +++ b/demo.fix @@ -2,33 +2,40 @@ -- The whole file is interpreted as a single expression. Expressions can be comprised of: -- Integer Literals - 42 + 42u8 -- String Literals "hello" -- Trees - (1 2 3) + (1u8 2u8 3u8) -- References &"hello" -- Identification Thunks - ^&4 + '&4u8 -- Application Thunks - *(1 2) + #(1u8 2u8) + + -- Selection Thunks + ["hello" 2u8] -- Zero-indexed + ["hello" 1u8 3u8] -- Inclusive start and exclusive end -- Strict Encodes - !^&2 + *'&2u8 + + -- Shallow Encodes + +'&2u8 -- Let bindings that tie expressions to variables - (let ((x 42)) x) + (let ((x 42u8)) x) -- @ path executables @"./target/x86_64-unknown-none/addblob" - -- Primitives (To be implemented) - $interpret + -- Primitives + *#($identity 2u8) -} \ No newline at end of file diff --git a/fix/Cargo.toml b/fix/Cargo.toml index 42f58af4..54cb074e 100644 --- a/fix/Cargo.toml +++ b/fix/Cargo.toml @@ -48,6 +48,8 @@ crossbeam-queue = { [build-dependencies] fixshell = { path = "shell", artifact="staticlib", target = "x86_64-unknown-none" } +fixpostprocessor = { path = "postprocessor" } +fixparser = { path = "parser", artifact = "cdylib", target = "wasm32-unknown-unknown" } anyhow = "1.0.98" bindgen = "0.72.1" cc = "1.2.30" diff --git a/fix/build.rs b/fix/build.rs index ba0b4635..0a01fa6a 100644 --- a/fix/build.rs +++ b/fix/build.rs @@ -203,7 +203,7 @@ fn main() -> Result<()> { ); } let wat = std::fs::read(f.path())?; - let wasm = wat2wasm(&wat)?; + let wasm = fixpostprocessor::process(&wat2wasm(&wat)?)?; let (c, h) = wasm2c(&wasm)?; let elf = c2elf(&c, &h)?; std::fs::write(&dst, elf)?; @@ -213,6 +213,10 @@ fn main() -> Result<()> { symlink(dst, link)?; } } + let parser = PathBuf::from(env::var_os("CARGO_CDYLIB_FILE_FIXPARSER").unwrap()); + let wasm = fixpostprocessor::process(&std::fs::read(&parser)?)?; + let (c, h) = wasm2c(&wasm)?; + std::fs::write(Path::new(&out_dir).join("fixparser"), c2elf(&c, &h)?)?; let cwd = std::env::var("CARGO_MANIFEST_DIR").unwrap(); diff --git a/fix/etc/memmap.ld b/fix/etc/memmap.ld index 215a1f24..25a41548 100644 --- a/fix/etc/memmap.ld +++ b/fix/etc/memmap.ld @@ -2,7 +2,7 @@ ENTRY(_start) MEMORY { cls (rwx) : ORIGIN = 0x0000000000000000, LENGTH = 2M - upper (rw) : ORIGIN = 0xFFFF800100100000, LENGTH = 8M + upper (rw) : ORIGIN = 0xFFFF800100100000, LENGTH = 32M } SECTIONS { diff --git a/fix/parser/Cargo.toml b/fix/parser/Cargo.toml new file mode 100644 index 00000000..05c11dab --- /dev/null +++ b/fix/parser/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "fixparser" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +fixutils = { path = "../utils" } +dlmalloc = { version = "0.2.14", features = ["global"] } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } \ No newline at end of file diff --git a/fix/parser/src/grammar.md b/fix/parser/src/grammar.md new file mode 100644 index 00000000..af1c2e90 --- /dev/null +++ b/fix/parser/src/grammar.md @@ -0,0 +1,47 @@ +### Text Format + +``` +A ::= O | K | E Any + | name Identifier + | $name Stdlib primitive + | @"/path/" Executable path + +binding ::= name = A Binds identifier to a value + +int ::= num_u8 Integer + | num_u16 + | num_u32 + | num_u64 + | num_u128 + +B ::= int Blob + | string + +T ::= (A*) Tree + +R ::= &B | &T Ref + +O ::= B | T | R Data + +K ::= 'O Thunk (Identify) + | #T Thunk (Apply) + | [A*] Thunk (Select) + | [O int] Thunk (Select 1) + | [O int int] Thunk (Select N) + +E ::= *K Encode (Strict) + | +K Encode (Shallow) + +comment ::= -- Single line + | {- -} Multi-line +``` + +### Example + +``` +compiler = @"/path/" + +*#(*#(compiler $def_limits + "int f(int x, int y) {return x + y;}") + $def_limits 19u32 4u32) +``` diff --git a/fix/src/parser/lexer.rs b/fix/parser/src/lexer.rs similarity index 64% rename from fix/src/parser/lexer.rs rename to fix/parser/src/lexer.rs index 188479ae..8b4f2523 100644 --- a/fix/src/parser/lexer.rs +++ b/fix/parser/src/lexer.rs @@ -1,6 +1,10 @@ -use super::token::Token; +use crate::token::Token; +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; use core::{iter::Peekable, str::Chars}; -use kernel::prelude::*; pub struct Lexer<'a> { characters: Peekable>, @@ -36,10 +40,14 @@ impl<'a> Lexer<'a> { let token = match character { '(' => Token::LParen, ')' => Token::RParen, + '[' => Token::LBracket, + ']' => Token::RBracket, '&' => Token::Ampersand, '*' => Token::Asterisk, - '^' => Token::Caret, - '!' => Token::Bang, + '+' => Token::Plus, + '\'' => Token::Apostrophe, + '#' => Token::Pound, + '$' => Token::Primitive(self.take(String::new(), Self::is_identifier)), '"' => { let text = self.take(String::new(), |ch| ch != '"'); if self.characters.next() != Some('"') { @@ -52,14 +60,21 @@ impl<'a> Lexer<'a> { let digits = self.take(String::new(), |ch| ch.is_ascii_hexdigit()); Token::Bytes(hex::decode(&digits).map_err(|error| error.to_string())?) } - // Negative numbers - '-' if self.peek(|character| character.is_ascii_digit()) => { - let number = self.take(String::new(), |ch| ch.is_ascii_digit()); - Token::Number(-number.parse::().map_err(|error| error.to_string())?) - } character if character.is_ascii_digit() => { - let number = self.take(String::from(character), |ch| ch.is_ascii_digit()); - Token::Number(number.parse::().map_err(|error| error.to_string())?) + let digits = self.take(String::from(character), |ch| { + ch.is_ascii_digit() || ch == '_' + }); + let suffix = self.take(String::new(), |ch| ch.is_ascii_alphanumeric()); + let bytes = match suffix.as_str() { + "u8" => digits.parse::().map(|n| n.to_le_bytes().to_vec()), + "u16" => digits.parse::().map(|n| n.to_le_bytes().to_vec()), + "u32" => digits.parse::().map(|n| n.to_le_bytes().to_vec()), + "u64" => digits.parse::().map(|n| n.to_le_bytes().to_vec()), + "u128" => digits.parse::().map(|n| n.to_le_bytes().to_vec()), + "" => return Err(format!("integer literal {digits} missing suffix")), + other => return Err(format!("unknown integer suffix: {other}")), + }; + Token::Bytes(bytes.map_err(|error| error.to_string())?) } character if Self::is_identifier(character) => { Token::Identifier(self.take(String::from(character), Self::is_identifier)) diff --git a/fix/parser/src/lib.rs b/fix/parser/src/lib.rs new file mode 100644 index 00000000..8bd263f8 --- /dev/null +++ b/fix/parser/src/lib.rs @@ -0,0 +1,30 @@ +#![cfg_attr(target_arch = "wasm32", no_std, feature(asm_experimental_arch))] +extern crate alloc; + +use dlmalloc::GlobalDlmalloc; +#[global_allocator] +static ALLOCATOR: GlobalDlmalloc = GlobalDlmalloc; + +mod lexer; +mod parser; +mod token; + +use fixutils::*; +use lexer::Lexer; +use parser::Parser; + +num_memories!(48); +num_tables!(24); + +#[procedure_entrypoint] +pub fn _fixpoint_apply(combination: RustHandle<'static>) -> Result, Error> { + let arguments = combination.to_entries()?; + + let source_handle = arguments.get(1).expect("expected source"); + let source = source_handle.to_bytes()?; + let source = core::str::from_utf8(&source).expect("source should be valid UTF-8"); + + let tokens = Lexer::new(source).tokenize().expect("failed to tokenize"); + let mut parser = Parser::new(tokens, arguments.get(2).expect("expected environment"))?; + parser.parse_program() +} diff --git a/fix/parser/src/parser.rs b/fix/parser/src/parser.rs new file mode 100644 index 00000000..14fb50d5 --- /dev/null +++ b/fix/parser/src/parser.rs @@ -0,0 +1,119 @@ +use alloc::{collections::BTreeMap, string::String, vec::Vec}; + +use crate::token::Token; +use fixutils::*; + +pub struct Parser { + tokens: Vec, + position: usize, + environment: BTreeMap>, + context: BTreeMap>, +} + +impl Parser { + pub fn new( + tokens: Vec, + environment_handle: &RustHandle<'static>, + ) -> Result { + let mut environment = BTreeMap::new(); + for entry in environment_handle.to_entries()? { + let entry = entry.to_entries()?; + let name = *entry.first().expect("expect name"); + let object = entry.get(1).expect("expect object"); + environment.insert( + String::from_utf8(name.to_bytes()?).expect("valid name"), + *object, + ); + } + + Ok(Self { + tokens, + position: 0, + environment, + context: BTreeMap::new(), + }) + } + + pub fn parse_program(&mut self) -> Result, Error> { + let handle = self.parse_expr()?; + self.expect(&Token::Eof, "expected end of program"); + Ok(handle) + } + + fn parse_expr(&mut self) -> Result, Error> { + Ok(match self.advance() { + Token::String(string) => RustHandle::from_bytes(string.as_bytes())?, + Token::Bytes(bytes) => RustHandle::from_bytes(&bytes)?, + Token::Identifier(name) => *self.context.get(&name).expect("undefined identifier"), + Token::Primitive(name) => *self.environment.get(&name).expect("undefined primitive"), + Token::Ampersand => create_ref(self.parse_expr()?), + Token::Apostrophe => create_identification_thunk(self.parse_expr()?), + Token::Pound => create_application_thunk(self.parse_expr()?), + Token::Asterisk => create_strict_encode(self.parse_expr()?), + Token::Plus => create_shallow_encode(self.parse_expr()?), + Token::LParen => { + if let Some(Token::Identifier(token)) = self.peek(self.position) + && token == "let" + { + self.advance(); + self.parse_let()? + } else { + RustHandle::from_entries(&self.parse_handles(&Token::RParen)?)? + } + } + Token::LBracket => create_selection_thunk(RustHandle::from_entries( + &self.parse_handles(&Token::RBracket)?, + )?), + token => panic!("unexpected token: {token:?}"), + }) + } + + fn parse_handles(&mut self, close: &Token) -> Result>, Error> { + let mut handles = Vec::new(); + while !self.matches(close) { + handles.push(self.parse_expr()?); + } + Ok(handles) + } + + fn parse_let(&mut self) -> Result, Error> { + self.expect(&Token::LParen, "expected '(' for let bindings"); + let outer_context = self.context.clone(); + while self.matches(&Token::LParen) { + let Token::Identifier(name) = self.advance() else { + panic!("expected name in let binding") + }; + let handle = self.parse_expr()?; + self.expect(&Token::RParen, "expected ')' for let binding"); + self.context.insert(name, handle); + } + self.expect(&Token::RParen, "expected ')' for let bindings"); + let body = self.parse_expr()?; + self.expect(&Token::RParen, "expected ')' for let"); + self.context = outer_context; + Ok(body) + } + + fn expect(&mut self, token: &Token, message: &str) { + assert!(self.matches(token), "{message}"); + } + + fn matches(&mut self, token: &Token) -> bool { + if self.peek(self.position) == Some(token) { + self.position += 1; + true + } else { + false + } + } + + fn peek(&self, position: usize) -> Option<&Token> { + self.tokens.get(position) + } + + fn advance(&mut self) -> Token { + let token = self.peek(self.position).cloned().unwrap_or(Token::Eof); + self.position += 1; + token + } +} diff --git a/fix/parser/src/token.rs b/fix/parser/src/token.rs new file mode 100644 index 00000000..06f600b7 --- /dev/null +++ b/fix/parser/src/token.rs @@ -0,0 +1,19 @@ +use alloc::{string::String, vec::Vec}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Token { + Identifier(String), + String(String), + Bytes(Vec), + Primitive(String), + LParen, + RParen, + LBracket, + RBracket, + Ampersand, + Apostrophe, + Asterisk, + Plus, + Pound, + Eof, +} diff --git a/fix/postprocessor/Cargo.toml b/fix/postprocessor/Cargo.toml new file mode 100644 index 00000000..b3935f0e --- /dev/null +++ b/fix/postprocessor/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "fixpostprocessor" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = "1.0.104" +wasm-encoder = { version = "0.245.1", features = ["wasmparser"] } +wasmparser = "0.245.1" diff --git a/fix/postprocessor/src/lib.rs b/fix/postprocessor/src/lib.rs new file mode 100644 index 00000000..54b02581 --- /dev/null +++ b/fix/postprocessor/src/lib.rs @@ -0,0 +1,59 @@ +use anyhow::Result; +use wasm_encoder::{ + MemorySection, MemoryType, Module, RawSection, + reencode::{Reencode, RoundtripReencoder}, +}; +use wasmparser::{Parser, Payload}; + +pub fn process(wasm: &[u8]) -> Result> { + let mut module = Module::new(); + let mut num_memories: u32 = 0; + let mut memory_section: Option = None; + // Sections that come after memory_section + let mut trailing_sections = Vec::new(); + + for payload in Parser::new(0).parse_all(wasm) { + match payload? { + Payload::MemorySection(section) => { + let mut memories = MemorySection::new(); + RoundtripReencoder.parse_memory_section(&mut memories, section)?; + memory_section = Some(memories); + } + Payload::CustomSection(section) if section.name() == "wasm_num_memories" => { + num_memories = u32::from_le_bytes(section.data().try_into()?); + } + // Don't change other sections + payload => { + if let Some((id, range)) = payload.as_section() { + let section = RawSection { + id, + data: &wasm[range], + }; + if memory_section.is_some() { + trailing_sections.push(section); + } else { + module.section(§ion); + } + } + } + } + } + + if let Some(mut mem_section) = memory_section { + for _ in 0..num_memories { + mem_section.memory(MemoryType { + minimum: 1, + maximum: None, + memory64: false, + shared: false, + page_size_log2: None, + }); + } + module.section(&mem_section); + } + for section in trailing_sections { + module.section(§ion); + } + + Ok(module.finish()) +} diff --git a/fix/shell/src/fixpoint.rs b/fix/shell/src/fixpoint.rs index 66946837..c62f859f 100644 --- a/fix/shell/src/fixpoint.rs +++ b/fix/shell/src/fixpoint.rs @@ -21,9 +21,9 @@ pub unsafe extern "C" fn w2c_fixpoint_attach_blob( let addr = (1usize << 32) * memory_idx as usize; let len = shell::fixpoint_attach_blob(addr as *mut c_void, handle.bytes); // TODO: this math is wrong - (*memory).pages = (len as u64 / PAGE_SIZE as u64) + 1; + (*memory).pages = len.div_ceil(PAGE_SIZE as usize) as u64; (*memory).max_pages = (1u64 << 32) / PAGE_SIZE as u64; - (*memory).size = len as u64; + (*memory).size = (*memory).pages * PAGE_SIZE as u64; } } @@ -33,7 +33,7 @@ pub unsafe extern "C" fn w2c_fixpoint_attach_tree( table_idx: u32, handle: wasm_rt_externref_t, ) { - assert!(table_idx < 63); + assert!(table_idx < 32); unsafe { let table = crate::rt::TABLES[table_idx as usize]; if (table.is_null()) { @@ -50,15 +50,15 @@ pub unsafe extern "C" fn w2c_fixpoint_attach_tree( pub unsafe extern "C" fn w2c_fixpoint_create_tree( fixpoint: *mut w2c_fixpoint, table_idx: u32, + length: u32, ) -> wasm_rt_externref_t { - assert!(table_idx < 63); + assert!(table_idx < 32); unsafe { let table = crate::rt::TABLES[table_idx as usize]; - let addr = (1usize << 32) * (64 + table_idx as usize); wasm_rt_externref_t { bytes: shell::fixpoint_create_tree(core::slice::from_raw_parts( - addr as *const u8, - (*table).size as usize, + (*table).data.cast::(), + length as usize * 32, )), } } @@ -68,15 +68,15 @@ pub unsafe extern "C" fn w2c_fixpoint_create_tree( pub unsafe extern "C" fn w2c_fixpoint_create_tag( fixpoint: *mut w2c_fixpoint, table_idx: u32, + length: u32, ) -> wasm_rt_externref_t { - assert!(table_idx < 63); + assert!(table_idx < 32); unsafe { let table = crate::rt::TABLES[table_idx as usize]; - let addr = (1usize << 32) * (64 + table_idx as usize); wasm_rt_externref_t { bytes: shell::fixpoint_create_tag(core::slice::from_raw_parts( - addr as *const u8, - (*table).size as usize, + (*table).data.cast::(), + length as usize * 32, )), } } @@ -102,6 +102,24 @@ pub unsafe extern "C" fn w2c_fixpoint_create_blob_i32( } } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_blob( + fixpoint: *mut w2c_fixpoint, + memory_index: u32, + length: u32, +) -> wasm_rt_externref_t { + assert!(memory_index < 63); + unsafe { + let memory = crate::rt::MEMORIES[memory_index as usize]; + wasm_rt_externref_t { + bytes: shell::fixpoint_create_blob(core::slice::from_raw_parts( + (*memory).data, + length as usize, + )), + } + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn w2c_fixpoint_is_blob_obj( fixpoint: *mut w2c_fixpoint, @@ -143,6 +161,26 @@ pub unsafe extern "C" fn w2c_fixpoint_is_equal( shell::fixpoint_is_equal(lhs.bytes, rhs.bytes) as i32 } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_ref( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_ref(handle.bytes), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_identification_thunk( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_identification_thunk(handle.bytes), + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn w2c_fixpoint_create_application_thunk( fixpoint: *mut w2c_fixpoint, @@ -153,6 +191,16 @@ pub unsafe extern "C" fn w2c_fixpoint_create_application_thunk( } } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_selection_thunk( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_selection_thunk(handle.bytes), + } +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn w2c_fixpoint_create_strict_encode( fixpoint: *mut w2c_fixpoint, @@ -162,3 +210,21 @@ pub unsafe extern "C" fn w2c_fixpoint_create_strict_encode( bytes: shell::fixpoint_create_strict_encode(handle.bytes), } } + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_shallow_encode( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: shell::fixpoint_create_shallow_encode(handle.bytes), + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_len( + fixpoint: *mut w2c_fixpoint, + handle: wasm_rt_externref_t, +) -> usize { + shell::fixpoint_len(handle.bytes) +} diff --git a/fix/shell/src/lib.rs b/fix/shell/src/lib.rs index 9d589a33..491ab2db 100644 --- a/fix/shell/src/lib.rs +++ b/fix/shell/src/lib.rs @@ -77,7 +77,8 @@ pub unsafe extern "C" fn _rsstart() -> ! { main(); } -static mut MODULE_BUF: [u8; 1024] = [0; 1024]; +const MODULE_BUF_SIZE: usize = 8192; +static mut MODULE_BUF: [u8; MODULE_BUF_SIZE] = [0; MODULE_BUF_SIZE]; pub fn main() -> ! { let combination = os::argument(); @@ -89,7 +90,7 @@ pub fn main() -> ! { wasm_rt_init(); let module_size = wasm_rt_module_size(); let module = unsafe { - assert!(module_size <= 1024); + assert!(module_size <= MODULE_BUF_SIZE); &raw mut MODULE_BUF[0] as *mut c_void }; wasm2c_module_instantiate(module, core::ptr::null()); diff --git a/fix/shell/src/rt.rs b/fix/shell/src/rt.rs index 9dbcfc05..b0441125 100644 --- a/fix/shell/src/rt.rs +++ b/fix/shell/src/rt.rs @@ -2,7 +2,10 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] -use core::sync::atomic::{AtomicUsize, Ordering}; +use core::{ + slice::from_raw_parts_mut, + sync::atomic::{AtomicUsize, Ordering}, +}; use arcane::{__MODE_read_write, arca_compat_mmap}; use user::error; @@ -16,7 +19,7 @@ unsafe extern "C" { } pub static mut MEMORY_IDX: usize = 0; -pub static mut TABLE_IDX: usize = 0; +pub static mut TABLE_IDX: usize = 1; pub static mut FUNCREF_TABLE_IDX: usize = 0; pub static mut MEMORIES: [*mut wasm_rt_memory_t; 64] = [core::ptr::null_mut(); 64]; @@ -113,7 +116,11 @@ pub extern "C" fn wasm_rt_allocate_externref_table( max_elements = 1 << (32 - 5); } let data = ((1 << 32) * (64 + idx)) as *mut u8; - arca_compat_mmap(data as *mut _, (elements * 32) as usize, __MODE_read_write); + arca_compat_mmap( + data as *mut _, + (elements * 32).next_multiple_of(PAGE_SIZE) as usize, + __MODE_read_write, + ); table.write(wasm_rt_externref_table_t { data: data as *mut _, size: elements, @@ -134,10 +141,17 @@ pub extern "C" fn wasm_rt_grow_externref_table( return u32::MAX; } - let start = unsafe { table.data.byte_add(current as usize * 32) }; - let size = delta * 32; + let mapped = (current * 32).next_multiple_of(PAGE_SIZE) as usize; + let required = ((current + delta) * 32).next_multiple_of(PAGE_SIZE) as usize; unsafe { - arca_compat_mmap(start as *mut _, size as usize, __MODE_read_write); + if required > mapped { + arca_compat_mmap( + table.data.byte_add(mapped) as *mut _, + required - mapped, + __MODE_read_write, + ); + } + from_raw_parts_mut(table.data.add(current as usize), delta as usize).fill(init); table.size += delta; } current diff --git a/fix/shell/src/shell.rs b/fix/shell/src/shell.rs index a22f94c5..22bd372a 100644 --- a/fix/shell/src/shell.rs +++ b/fix/shell/src/shell.rs @@ -12,7 +12,6 @@ use core::ffi::c_void; use fixhandle::*; use user::ArcaError; -use user::Ref; use user::Runtime; use user::error::log as arca_log; use user::error::log_int as arca_log_int; @@ -242,20 +241,57 @@ pub fn fixpoint_is_equal(lhs: [u8; 32], rhs: [u8; 32]) -> bool { result == 1 } +pub fn fixpoint_create_ref(handle: [u8; 32]) -> [u8; 32] { + Handle::Ref(match Handle::unpack(handle) { + Handle::Object(Object::Blob(blob)) => Ref::Blob(blob), + Handle::Object(Object::Tree(tree)) => Ref::Tree(tree), + _ => { + arca_log("create_ref: handle does not refer to an Object"); + panic!() + } + }) + .pack() +} + +pub fn fixpoint_create_identification_thunk(handle: [u8; 32]) -> [u8; 32] { + let reference = match Handle::unpack(handle) { + Handle::Ref(reference) => reference, + Handle::Object(Object::Blob(blob)) => Ref::Blob(blob), + Handle::Object(Object::Tree(tree)) => Ref::Tree(tree), + _ => { + arca_log("create_identification_thunk: handle does not refer to an Object or Ref"); + panic!() + } + }; + let thunk: Handle = Thunk::Identification(reference).into(); + thunk.pack() +} + pub fn fixpoint_create_application_thunk(handle: [u8; 32]) -> [u8; 32] { let handle = Handle::unpack(handle); - // TODO: handle refs let thunk: Handle = Thunk::Application(handle.unwrap_object().unwrap_tree()).into(); thunk.pack() } +pub fn fixpoint_create_selection_thunk(handle: [u8; 32]) -> [u8; 32] { + let handle = Handle::unpack(handle); + let thunk: Handle = Thunk::Selection(handle.unwrap_object().unwrap_tree()).into(); + thunk.pack() +} + pub fn fixpoint_create_strict_encode(handle: [u8; 32]) -> [u8; 32] { let handle = Handle::unpack(handle); let encode: Handle = Encode::Strict(handle.unwrap_thunk()).into(); encode.pack() } -fn fixpoint_len(handle: [u8; 32]) -> usize { +pub fn fixpoint_create_shallow_encode(handle: [u8; 32]) -> [u8; 32] { + let handle = Handle::unpack(handle); + let encode: Handle = Encode::Shallow(handle.unwrap_thunk()).into(); + encode.pack() +} + +pub fn fixpoint_len(handle: [u8; 32]) -> usize { let handle = Handle::unpack(handle); handle.len() } diff --git a/fix/src/evaluator.rs b/fix/src/evaluator.rs index 0a62aaa8..4bdc7378 100644 --- a/fix/src/evaluator.rs +++ b/fix/src/evaluator.rs @@ -36,6 +36,62 @@ impl Evaluator { self.runtime.execute(combination) } + pub fn select(&self, selection: Tree) -> Handle { + let handles = self.storage().get_tree(selection).unwrap(); + match *handles { + [target, index] => self.select_index(target, self.read_index(index)), + [target, start, end] => { + self.select_range(target, self.read_index(start), self.read_index(end)) + } + _ => panic!("selection thunk got {} handles", handles.len()), + } + } + + pub fn select_index(&self, target: Handle, index: usize) -> Handle { + if index >= target.len() { + panic!("Invalid index {index} for selection thunk"); + } + match target { + Handle::Object(Object::Tree(tree)) | Handle::Ref(Ref::Tree(tree)) => { + self.storage().get_tree(tree).unwrap()[index] + } + Handle::Object(Object::Blob(blob)) | Handle::Ref(Ref::Blob(blob)) => { + let data = self.storage().get_blob(blob).unwrap(); + Ref::Blob(self.storage().add_blob(&[data[index]])).into() + } + _ => panic!("expected blob or tree handle for selection thunk"), + } + } + + pub fn select_range(&self, target: Handle, begin: usize, end: usize) -> Handle { + if begin >= end { + panic!("Invalid range [{begin}, {end}) for seleciton thunk"); + } + match target { + Handle::Object(Object::Tree(tree)) | Handle::Ref(Ref::Tree(tree)) => { + let data = self.storage().get_tree(tree).unwrap(); + Ref::Tree(self.storage().add_tree(&data[begin..end])).into() + } + Handle::Object(Object::Blob(blob)) | Handle::Ref(Ref::Blob(blob)) => { + let data = self.storage().get_blob(blob).unwrap(); + Ref::Blob(self.storage().add_blob(&data[begin..end])).into() + } + _ => panic!("expected blob or tree handle for selection thunk"), + } + } + + pub fn read_index(&self, handle: Handle) -> usize { + let Handle::Object(Object::Blob(blob)) = handle else { + panic!("expected blob handle for selection index") + }; + let bytes = self.storage().get_blob(blob).unwrap(); + // Make buffer fit all supported integer widths + let mut buffer = [0; 16]; + assert!(bytes.len() <= buffer.len()); + buffer[..bytes.len()].copy_from_slice(&bytes); + usize::try_from(u128::from_le_bytes(buffer)).expect("selection index should be in range") + } + pub fn lift(&self, handle: Handle) -> Handle { match handle { Handle::Ref(r) => match r { @@ -59,7 +115,10 @@ impl Evaluator { fn think(&self, thunk: Thunk) -> Handle { match thunk { Thunk::Identification(reference) => self.lift(Handle::Ref(reference)), - Thunk::Selection(_) => todo!(), + Thunk::Selection(tree) => { + let evaled = self.eval_tree(tree); + self.select(evaled) + } Thunk::Application(tree) => { let evaled = self.eval_tree(tree); self.apply(evaled) @@ -72,7 +131,8 @@ impl Evaluator { match thought { Handle::Object(_) => thought, Handle::Ref(_) => self.lift(thought), - Handle::Thunk(_) | Handle::Encode(_) => todo!(), + Handle::Thunk(thunk) => self.force(thunk), + Handle::Encode(encode) => self.lift(self.encode(encode)), } } diff --git a/fix/src/interpreter.rs b/fix/src/interpreter.rs deleted file mode 100644 index 9d4c13bb..00000000 --- a/fix/src/interpreter.rs +++ /dev/null @@ -1,111 +0,0 @@ -extern crate alloc; -use alloc::collections::BTreeMap; - -use crate::{Storage, parser::Expr}; -mod fixshell; -use fixhandle::*; -use fixshell::FixShell; -use kernel::prelude::*; - -pub struct Interpreter<'a> { - storage: &'a dyn Storage, - context: BTreeMap, -} - -impl<'a> Interpreter<'a> { - pub fn new(storage: &'a dyn Storage) -> Self { - Self { - storage, - context: BTreeMap::new(), - } - } - - pub fn interpret(&mut self, expression: &Expr) -> Handle { - match expression { - Expr::String(str) => self.create_blob(str.as_bytes()), - Expr::Number(num) => self.create_blob(&i64::to_le_bytes(*num)), - Expr::Bytes(bytes) => self.create_blob(bytes), - Expr::Identifier(name) => *self.context.get(name).expect("undefined identifier"), - Expr::Ref(object) => Self::create_ref(self.interpret(object)), - Expr::Tree(handles) => { - let handles: Vec = handles.iter().map(|x| self.interpret(x)).collect(); - self.create_tree(&handles) - } - Expr::Application(tree) => Self::create_application_thunk(self.interpret(tree)), - Expr::Identification(tree) => Self::create_identification_thunk(self.interpret(tree)), - Expr::StrictEncode(thunk) => Self::create_strict_encode(self.interpret(thunk)), - Expr::Let { bindings, body } => { - let outer_context = self.context.clone(); - for (name, expr) in bindings { - let handle = self.interpret(expr); - self.context.insert(name.clone(), handle); - } - let handle = self.interpret(body); - self.context = outer_context; - handle - } - } - } -} - -impl FixShell for Interpreter<'_> { - type Handle = Handle; - - fn create_blob(&self, data: &[u8]) -> Self::Handle { - self.storage.add_blob(data).into() - } - - fn create_tree(&self, data: &[Self::Handle]) -> Self::Handle { - self.storage.add_tree(data).into() - } - - fn create_ref(handle: Self::Handle) -> Self::Handle { - match handle { - Handle::Object(Object::Blob(blob)) => Handle::Ref(Ref::Blob(blob)), - Handle::Object(Object::Tree(tree)) => Handle::Ref(Ref::Tree(tree)), - _ => panic!("expected blob or tree handle"), - } - } - - fn get_blob_data(&self, handle: Self::Handle) -> Box<[u8]> { - let Handle::Object(Object::Blob(blob)) = handle else { - panic!("expected blob handle") - }; - self.storage - .get_blob(blob) - .expect("blob data exists for handle") - } - - fn get_tree_data(&self, handle: Self::Handle) -> Box<[Self::Handle]> { - let Handle::Object(Object::Tree(tree)) = handle else { - panic!("expected tree handle") - }; - self.storage - .get_tree(tree) - .expect("tree data exists for handle") - } - - fn create_application_thunk(handle: Self::Handle) -> Self::Handle { - let Handle::Object(Object::Tree(tree)) = handle else { - panic!("expected tree handle for applicaiton") - }; - Thunk::Application(tree).into() - } - - fn create_identification_thunk(handle: Self::Handle) -> Self::Handle { - Thunk::Identification(match handle { - Handle::Object(Object::Blob(blob)) => Ref::Blob(blob), - Handle::Object(Object::Tree(tree)) => Ref::Tree(tree), - Handle::Ref(reference) => reference, - _ => panic!("expected blob or tree handle"), - }) - .into() - } - - fn create_strict_encode(handle: Self::Handle) -> Self::Handle { - let Handle::Thunk(thunk) = handle else { - panic!("expected thunk for strict encode") - }; - Encode::Strict(thunk).into() - } -} diff --git a/fix/src/interpreter/fixshell.rs b/fix/src/interpreter/fixshell.rs deleted file mode 100644 index 154466a3..00000000 --- a/fix/src/interpreter/fixshell.rs +++ /dev/null @@ -1,20 +0,0 @@ -use kernel::prelude::Box; - -pub trait FixShell: Sized { - type Handle; - - fn create_blob(&self, data: &[u8]) -> Self::Handle; - fn create_tree(&self, data: &[Self::Handle]) -> Self::Handle; - fn create_ref(handle: Self::Handle) -> Self::Handle; - - #[allow(dead_code)] // these functions are unused so far - // (and ultimately the interpreter should use the "real" Fix shell) - fn get_blob_data(&self, handle: Self::Handle) -> Box<[u8]>; - #[allow(dead_code)] - fn get_tree_data(&self, handle: Self::Handle) -> Box<[Self::Handle]>; - - fn create_application_thunk(handle: Self::Handle) -> Self::Handle; - fn create_identification_thunk(handle: Self::Handle) -> Self::Handle; - - fn create_strict_encode(handle: Self::Handle) -> Self::Handle; -} diff --git a/fix/src/interpreter/grammar.md b/fix/src/interpreter/grammar.md deleted file mode 100644 index 5c9bc33e..00000000 --- a/fix/src/interpreter/grammar.md +++ /dev/null @@ -1,27 +0,0 @@ -``` -A ::= O | K | E | L Any - | $atom Special/Primitive - | @path Executable path - -B ::= int literal Blob - | string literal Blob - -T ::= (A*) Tree - -R ::= &B | &T Ref - -O ::= B | T | R Data - -K ::= ^O Thunk (Identify) - | *T Thunk (Apply) - | #T Thunk (Digest) - | ~T Thunk (Select) - | O[B] Thunk (Select 1) - | O[B:B] Thunk (Select N) - -E ::= !K Encode (Strict) - | ?K Encode (Shallow) - -L ::= (let ((name A)*) A) Let -``` - diff --git a/fix/src/lib.rs b/fix/src/lib.rs index b74973d4..78f1caca 100644 --- a/fix/src/lib.rs +++ b/fix/src/lib.rs @@ -2,9 +2,9 @@ #![no_std] pub mod evaluator; -pub mod interpreter; -pub mod parser; +pub mod preprocessor; pub mod runtime; +pub mod stdlib; pub mod storage; pub mod handle { @@ -13,6 +13,6 @@ pub mod handle { pub use evaluator::*; pub use handle::*; -pub use interpreter::*; +pub use preprocessor::*; pub use runtime::*; pub use storage::*; diff --git a/fix/src/main.rs b/fix/src/main.rs index 9903ff34..2aec9e55 100644 --- a/fix/src/main.rs +++ b/fix/src/main.rs @@ -12,9 +12,8 @@ use kernel::prelude::*; use fix::arca::FixOnArca; use fix::*; -use lexer::*; -use parser::*; -use preprocessor::*; + +pub const PARSER: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/fixparser")); #[cfg(test)] mod testing; @@ -88,11 +87,14 @@ fn eval_parallel_program( evaluator: &Arc>, ) -> Handle { let processed = Preprocessor::new(source).preprocess().unwrap(); - let tokens = Lexer::new(&processed).tokenize().unwrap(); - let program = Parser::new(&tokens).parse_program().unwrap(); - - let mut interpreter = Interpreter::new(evaluator.storage()); - evaluator.eval(interpreter.interpret(&program)) + let parser: Handle = evaluator.storage().add_blob(PARSER).into(); + let source = evaluator.storage().add_blob(processed.as_bytes()); + let environment = stdlib::build_environment(evaluator.storage()); + let combination = evaluator + .storage() + .add_tree(&[parser, source.into(), environment]); + + evaluator.eval(Encode::Strict(Thunk::Application(combination)).into()) } // `fix eval `: read command file and print result. @@ -116,11 +118,14 @@ fn eval_file(path: &str) { // parse, interpret, and evaluate source text. fn eval_program(source: &str, evaluator: &Evaluator) -> Handle { let processed = Preprocessor::new(source).preprocess().unwrap(); - let tokens = Lexer::new(&processed).tokenize().unwrap(); - let program = Parser::new(&tokens).parse_program().unwrap(); - - let mut interpreter = Interpreter::new(evaluator.storage()); - evaluator.eval(interpreter.interpret(&program)) + let parser: Handle = evaluator.storage().add_blob(PARSER).into(); + let source = evaluator.storage().add_blob(processed.as_bytes()); + let environment = stdlib::build_environment(evaluator.storage()); + let combination = evaluator + .storage() + .add_tree(&[parser, source.into(), environment]); + + evaluator.eval(Encode::Strict(Thunk::Application(combination)).into()) } #[cfg(test)] @@ -144,34 +149,49 @@ mod tests { let evaluator = Evaluator::new(FixOnArca::default()); { - assert_eq!(eval_value("42", &evaluator), 42i64.to_le_bytes()); - assert_eq!(eval_value("-1", &evaluator), (-1i64).to_le_bytes()); + assert_eq!(eval_value("42u8", &evaluator), 42u8.to_le_bytes()); + //assert_eq!(eval_value("-1", &evaluator), (-1i64).to_le_bytes()); assert_eq!(eval_value("\"hello\"", &evaluator), b"hello"); - assert_eq!(eval_value("(1 2 3)", &evaluator), 3i64.to_le_bytes()); - assert_eq!(eval_value("()", &evaluator), 0i64.to_le_bytes()); + assert_eq!(eval_value("(1u8 2u8 3u8)", &evaluator), 3u64.to_le_bytes()); + assert_eq!(eval_value("()", &evaluator), 0u64.to_le_bytes()); assert_eq!(eval_value("&\"hello\"", &evaluator), b"hello"); - assert_eq!(eval_value("&(1 2 3)", &evaluator), 3i64.to_le_bytes()); + assert_eq!(eval_value("&(1u8 2u8 3u8)", &evaluator), 3u64.to_le_bytes()); - assert_eq!(eval_value("!^&2", &evaluator), 2i64.to_le_bytes()); + assert_eq!(eval_value("*'&2u8", &evaluator), 2u8.to_le_bytes()); + assert_eq!( + eval_value("*[(1u8 2u8 3u8 4u8) 2u8]", &evaluator), + 3u8.to_le_bytes() + ); assert_eq!( - eval_value("(let ((x 42)) x)", &evaluator), - 42i64.to_le_bytes() + eval_value("(let ((x 42u64)) x)", &evaluator), + 42u64.to_le_bytes() ); assert_eq!( - eval_value("(let ((x 1) (y 2)) (x y))", &evaluator), - 2i64.to_le_bytes() + eval_value("(let ((x 1u8) (y 2u8)) (x y))", &evaluator), + 2u64.to_le_bytes() ); assert_eq!( - eval_value("(let ((x 1)) (let ((x 2)) x))", &evaluator), - 2i64.to_le_bytes() + eval_value("(let ((x 1u8)) (let ((x 2u64)) x))", &evaluator), + 2u64.to_le_bytes() ); assert_eq!( - eval_value("(let ((x 1)) (let ((y (let ((x 2)) x))) x))", &evaluator), - 1i64.to_le_bytes() + eval_value( + "(let ((x 1u64)) (let ((y (let ((x 2u8)) x))) x))", + &evaluator + ), + 1u64.to_le_bytes() + ); + } + + // primitive test + { + assert_eq!( + eval_value("*#($identity 2u8)", &evaluator), + 2u64.to_le_bytes() ); } } diff --git a/fix/src/parallel_evaluator.rs b/fix/src/parallel_evaluator.rs index e1886b65..28272966 100644 --- a/fix/src/parallel_evaluator.rs +++ b/fix/src/parallel_evaluator.rs @@ -83,6 +83,62 @@ impl Evaluator { self.runtime.execute(combination) } + pub fn select(&self, selection: Tree) -> Handle { + let handles = self.storage().get_tree(selection).unwrap(); + match *handles { + [target, index] => self.select_index(target, self.read_index(index)), + [target, start, end] => { + self.select_range(target, self.read_index(start), self.read_index(end)) + } + _ => panic!("selection thunk got {} handles", handles.len()), + } + } + + pub fn select_index(&self, target: Handle, index: usize) -> Handle { + if index >= target.len() { + panic!("Invalid index {index} for selection thunk"); + } + match target { + Handle::Object(Object::Tree(tree)) | Handle::Ref(Ref::Tree(tree)) => { + self.storage().get_tree(tree).unwrap()[index] + } + Handle::Object(Object::Blob(blob)) | Handle::Ref(Ref::Blob(blob)) => { + let data = self.storage().get_blob(blob).unwrap(); + Ref::Blob(self.storage().add_blob(&[data[index]])).into() + } + _ => panic!("expected blob or tree handle for selection thunk"), + } + } + + pub fn select_range(&self, target: Handle, begin: usize, end: usize) -> Handle { + if begin >= end { + panic!("Invalid range [{begin}, {end}) for seleciton thunk"); + } + match target { + Handle::Object(Object::Tree(tree)) | Handle::Ref(Ref::Tree(tree)) => { + let data = self.storage().get_tree(tree).unwrap(); + Ref::Tree(self.storage().add_tree(&data[begin..end])).into() + } + Handle::Object(Object::Blob(blob)) | Handle::Ref(Ref::Blob(blob)) => { + let data = self.storage().get_blob(blob).unwrap(); + Ref::Blob(self.storage().add_blob(&data[begin..end])).into() + } + _ => panic!("expected blob or tree handle for selection thunk"), + } + } + + pub fn read_index(&self, handle: Handle) -> usize { + let Handle::Object(Object::Blob(blob)) = handle else { + panic!("expected blob handle for selection index") + }; + let bytes = self.storage().get_blob(blob).unwrap(); + // Make buffer fit all supported integer widths + let mut buffer = [0; 16]; + assert!(bytes.len() <= buffer.len()); + buffer[..bytes.len()].copy_from_slice(&bytes); + usize::try_from(u128::from_le_bytes(buffer)).expect("selection index should be in range") + } + pub fn lift(&self, handle: Handle) -> Handle { match handle { Handle::Ref(r) => match r { @@ -106,7 +162,10 @@ impl Evaluator { fn think(&self, thunk: Thunk, eval_mode: EvalType) -> Handle { match thunk { Thunk::Identification(reference) => self.lift(Handle::Ref(reference)), - Thunk::Selection(_) => todo!(), + Thunk::Selection(tree) => { + let evaled = self.eval_tree(tree, eval_mode); + self.select(evaled) + } Thunk::Application(tree) => { let evaled = self.eval_tree(tree, eval_mode); self.apply(evaled) @@ -119,7 +178,8 @@ impl Evaluator { match thought { Handle::Object(_) => thought, Handle::Ref(_) => self.lift(thought), - Handle::Thunk(_) | Handle::Encode(_) => todo!(), + Handle::Thunk(thunk) => self.force(thunk, eval_mode), + Handle::Encode(encode) => self.lift(self.encode(encode, eval_mode)), } } @@ -170,7 +230,7 @@ impl Evaluator { //println!("evaluating {handle}"); match handle { Handle::Ref(reference) => self.eval(self.lift(Handle::Ref(reference))), - Handle::Thunk(_) => todo!(), + Handle::Thunk(_) => handle, Handle::Object(obj) => match obj { Object::Blob(blob) => blob.into(), Object::Tree(tree) => self.eval_tree(tree, eval_mode).into(), diff --git a/fix/src/parser.rs b/fix/src/parser.rs deleted file mode 100644 index 50c73894..00000000 --- a/fix/src/parser.rs +++ /dev/null @@ -1,102 +0,0 @@ -use kernel::prelude::*; -pub use token::{Expr, Token}; - -pub mod lexer; -pub mod preprocessor; -pub mod token; - -pub struct Parser<'a> { - tokens: &'a [Token], - position: usize, -} - -impl<'a> Parser<'a> { - pub fn new(tokens: &'a [Token]) -> Self { - Self { - tokens, - position: 0, - } - } - - pub fn parse_program(&mut self) -> Result { - let expr = self.parse_expr()?; - self.expect(&Token::Eof, "expected end of program")?; - Ok(expr) - } - - fn parse_expr(&mut self) -> Result { - match self.advance() { - Token::Number(number) => Ok(Expr::Number(number)), - Token::String(string) => Ok(Expr::String(string)), - Token::Bytes(bytes) => Ok(Expr::Bytes(bytes)), - Token::Identifier(value) => Ok(Expr::Identifier(value)), - Token::Ampersand => Ok(Expr::Ref(Box::new(self.parse_expr()?))), - Token::Caret => Ok(Expr::Identification(Box::new(self.parse_expr()?))), - Token::Asterisk => Ok(Expr::Application(Box::new(self.parse_expr()?))), - Token::Bang => Ok(Expr::StrictEncode(Box::new(self.parse_expr()?))), - Token::LParen => { - if let Some(Token::Identifier(token)) = self.peek(self.position) - && token == "let" - { - self.advance(); - self.parse_let() - } else { - Ok(Expr::Tree(self.parse_handles()?)) - } - } - token => Err(format!("unexpected token: {token:?}")), - } - } - - fn parse_handles(&mut self) -> Result, String> { - let mut handles = Vec::new(); - while !self.matches(&Token::RParen) { - handles.push(self.parse_expr()?); - } - Ok(handles) - } - - fn parse_let(&mut self) -> Result { - self.expect(&Token::LParen, "expected '(' for let bindings")?; - let mut bindings = Vec::new(); - while self.matches(&Token::LParen) { - let Token::Identifier(name) = self.advance() else { - return Err(String::from("expected name in let binding")); - }; - let value = self.parse_expr()?; - self.expect(&Token::RParen, "expected ')' for let binding")?; - bindings.push((name, value)); - } - self.expect(&Token::RParen, "expected ')' for let bindings")?; - let body = Box::new(self.parse_expr()?); - self.expect(&Token::RParen, "expected ')' for let")?; - Ok(Expr::Let { bindings, body }) - } - - fn expect(&mut self, token: &Token, message: &str) -> Result<(), String> { - if self.matches(token) { - Ok(()) - } else { - Err(String::from(message)) - } - } - - fn matches(&mut self, token: &Token) -> bool { - if self.peek(self.position) == Some(token) { - self.position += 1; - true - } else { - false - } - } - - fn peek(&self, position: usize) -> Option<&Token> { - self.tokens.get(position) - } - - fn advance(&mut self) -> Token { - let token = self.peek(self.position).cloned().unwrap_or(Token::Eof); - self.position += 1; - token - } -} diff --git a/fix/src/parser/token.rs b/fix/src/parser/token.rs deleted file mode 100644 index 891f14aa..00000000 --- a/fix/src/parser/token.rs +++ /dev/null @@ -1,34 +0,0 @@ -use core::clone::Clone; -use kernel::prelude::*; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Token { - Identifier(String), - Number(i64), - String(String), - Bytes(Vec), - LParen, - RParen, - Ampersand, - Caret, - Asterisk, - Bang, - Eof, -} - -#[derive(Debug, Clone)] -pub enum Expr { - Number(i64), - String(String), - Bytes(Vec), - Identifier(String), - Ref(Box), - Tree(Vec), - Application(Box), - Identification(Box), - StrictEncode(Box), - Let { - bindings: Vec<(String, Expr)>, - body: Box, - }, -} diff --git a/fix/src/parser/preprocessor.rs b/fix/src/preprocessor.rs similarity index 82% rename from fix/src/parser/preprocessor.rs rename to fix/src/preprocessor.rs index a5ca8ae6..2a600662 100644 --- a/fix/src/parser/preprocessor.rs +++ b/fix/src/preprocessor.rs @@ -1,11 +1,7 @@ -use super::lexer::*; use core::{iter::Peekable, str::Chars}; use kernel::host::fs::{File, Whence}; use kernel::prelude::*; -// Temporary placeholder until the standard library format is finalized -pub const STDLIB: &str = "./fix/stdlib"; - pub struct Preprocessor<'a> { characters: Peekable>, } @@ -45,16 +41,6 @@ impl<'a> Preprocessor<'a> { } } } - '$' => { - let mut name = String::new(); - while let Some(character) = - self.characters.next_if(|&ch| Lexer::is_identifier(ch)) - { - name.push(character); - } - let program = read_file(&format!("{STDLIB}/{name}"))?; - output.push_str(&format!("0x{}", hex::encode(program))); - } '@' => { if self.characters.next() != Some('"') { return Err(String::from("expected path after '@'")); diff --git a/fix/src/stdlib.rs b/fix/src/stdlib.rs new file mode 100644 index 00000000..2a34f453 --- /dev/null +++ b/fix/src/stdlib.rs @@ -0,0 +1,18 @@ +use crate::handle::*; +use crate::storage::Storage; +use kernel::prelude::Vec; + +pub const PRIMITIVES: &[(&str, &[u8])] = &[( + "identity", + include_bytes!(concat!(env!("OUT_DIR"), "/identity")), +)]; + +pub fn build_environment(storage: &dyn Storage) -> Handle { + let mut environment: Vec = Vec::new(); + for (name, blob) in PRIMITIVES { + let name = storage.add_blob(name.as_bytes()); + let primitive = storage.add_blob(blob); + environment.push(storage.add_tree(&[name.into(), primitive.into()]).into()); + } + storage.add_tree(&environment).into() +} diff --git a/fix/utils/Cargo.toml b/fix/utils/Cargo.toml new file mode 100644 index 00000000..c5b50182 --- /dev/null +++ b/fix/utils/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "fixutils" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["rlib"] + +[dependencies] +fixhandle = { path = "../handle", default-features = false } +macros = { path = "../../macros" } + +[build-dependencies] +cc = "1.2.30" \ No newline at end of file diff --git a/fix/utils/build.rs b/fix/utils/build.rs new file mode 100644 index 00000000..db5a77aa --- /dev/null +++ b/fix/utils/build.rs @@ -0,0 +1,11 @@ +fn main() { + println!("cargo::rerun-if-changed=src/utils.h"); + println!("cargo::rerun-if-changed=src/utils.c"); + + cc::Build::new() + .file("src/utils.c") + .include("src") + .flag("-mreference-types") + .opt_level(2) + .compile("fixutils"); +} diff --git a/fix/utils/src/lib.rs b/fix/utils/src/lib.rs new file mode 100644 index 00000000..8621e602 --- /dev/null +++ b/fix/utils/src/lib.rs @@ -0,0 +1,146 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] +extern crate alloc; +#[cfg(target_arch = "wasm32")] +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + core::arch::wasm32::unreachable() +} + +use alloc::vec::Vec; +use core::marker::PhantomData; +use fixhandle::{ + BitPack, Blob, BlobName, Encode, Handle, Object, RawName, Ref, Thunk, Tree, TreeName, +}; +pub use macros::{num_memories, num_tables, procedure_entrypoint}; + +pub mod memory; +pub mod table; + +pub use memory::*; +pub use table::*; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Error { + AllOccupied, // All memories/tables occupied + Unavailable, // Resource unavailable + GrowFailed, // Memory/Table growth failed + OutOfBounds, // Memory/Table access out of bounds +} + +#[repr(u16)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Producer { + // Combination = 0, used on C side + // table_index, entry_index + TableGet = 1, + // memory_index, length + CreateBlob = 2, + // table_index, length + CreateTree = 3, +} + +fn encode_args(producer: Producer, index: u16, entry: usize) -> RawName { + let mut bytes = [0; 32]; + // 4 bytes for entry/length argument + bytes[24..28].copy_from_slice(&(entry as u32).to_le_bytes()); + // two bytes for table/memory index + bytes[28..30].copy_from_slice(&index.to_le_bytes()); + // two bits for producer type + bytes[30..32].copy_from_slice(&((producer as u16) << 12).to_le_bytes()); + RawName::forge(bytes) +} + +#[repr(C, align(8))] +#[derive(Clone, Copy)] +pub struct RustHandle<'a> { + pub raw_handle: [u8; 32], + source: PhantomData<&'a ()>, // Lifetimes for memories/tables to prevent overwriting +} + +impl<'a> RustHandle<'a> { + fn new(handle: Handle) -> Self { + Self { + raw_handle: handle.pack(), + source: PhantomData, + } + } + + fn unpack(self) -> Handle { + Handle::unpack(self.raw_handle) + } + + pub fn len(&self) -> usize { + unsafe { util_len(&self.raw_handle) } + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + Memory::from_bytes(bytes)?.to_blob(bytes.len()) + } + + pub fn from_entries(entries: &[RustHandle<'_>]) -> Result { + Table::from_entries(entries)?.to_tree(entries.len()) + } + + pub fn to_bytes(&self) -> Result, Error> { + Memory::from_blob(*self)?.to_bytes(self.len()) + } + + pub fn to_entries(&self) -> Result>, Error> { + Table::from_tree(*self)?.to_entries(self.len()) + } +} + +pub fn create_ref<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Handle::Ref(match handle.unpack() { + Handle::Ref(reference) => reference, + Handle::Object(Object::Blob(blob)) => Ref::Blob(blob), + Handle::Object(Object::Tree(tree)) => Ref::Tree(tree), + _ => panic!("create_ref: handle does not refer to an Object"), + })) +} + +pub fn create_identification_thunk<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + let reference = match handle.unpack() { + Handle::Ref(reference) => reference, + Handle::Object(Object::Blob(blob)) => Ref::Blob(blob), + Handle::Object(Object::Tree(tree)) => Ref::Tree(tree), + _ => panic!("create_identification_thunk: handle does not refer to an Object or Ref"), + }; + RustHandle::new(Thunk::Identification(reference).into()) +} + +pub fn create_application_thunk<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Thunk::Application(handle.unpack().unwrap_object().unwrap_tree()).into()) +} + +pub fn create_selection_thunk<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Thunk::Selection(handle.unpack().unwrap_object().unwrap_tree()).into()) +} + +pub fn create_strict_encode<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Encode::Strict(handle.unpack().unwrap_thunk()).into()) +} + +pub fn create_shallow_encode<'a>(handle: RustHandle<'a>) -> RustHandle<'a> { + RustHandle::new(Encode::Shallow(handle.unpack().unwrap_thunk()).into()) +} + +unsafe extern "C" { + // Defined with inline assembly + pub fn wasm_memory_read(memory_index: u32, destination: u32, length: usize); + pub fn wasm_memory_write(memory_index: u32, source: u32, length: usize); + pub fn wasm_memory_size(memory_index: u32) -> usize; + pub fn wasm_memory_grow(memory_index: u32, num_pages: usize) -> usize; + + pub fn wasm_table_size(table_index: u32) -> usize; + pub fn wasm_table_grow(table_index: u32, entries: usize) -> usize; + + pub fn util_attach_blob(memory_index: u32, handle: *const [u8; 32]); + pub fn util_attach_tree(table_index: u32, handle: *const [u8; 32]); + pub fn util_len(handle: *const [u8; 32]) -> usize; + pub fn util_table_set(table_index: u32, entry_index: usize, handle: *const [u8; 32]); +} diff --git a/fix/utils/src/memory.rs b/fix/utils/src/memory.rs new file mode 100644 index 00000000..a803458d --- /dev/null +++ b/fix/utils/src/memory.rs @@ -0,0 +1,129 @@ +use crate::*; + +unsafe extern "C" { + fn util_allocate_memory(index: u16) -> *mut Memory; + static UTIL_NUM_MEMORIES: u16; +} +static mut POSITION: u16 = 0; +const PAGE_SIZE: usize = 65536; + +#[repr(transparent)] +pub struct Memory(u16); + +impl Memory { + #[doc(hidden)] + pub const EMPTY: Self = Self(0); + + pub fn new(index: u16) -> Result<&'static mut Self, Error> { + let slot = unsafe { util_allocate_memory(index) }; + if slot.is_null() { + return Err(Error::Unavailable); + } + let memory = unsafe { &mut *slot }; + memory.0 = index; + Ok(memory) + } + + pub fn next() -> Result<&'static mut Self, Error> { + unsafe { + while POSITION < UTIL_NUM_MEMORIES { + POSITION += 1; + if let Ok(memory) = Memory::new(POSITION) { + return Ok(memory); + } + } + } + Err(Error::AllOccupied) + } + + /// Calls the fixshell's create_blob function when resolved. + /// Borrows the memory until the handle is consumed. + /// + /// # Safety + /// + /// `length` must be <= size() * PAGE_SIZE + pub unsafe fn create_blob(&self, length: usize) -> RustHandle<'_> { + RustHandle::new(Handle::Object(Object::Blob(Blob::Blob(unsafe { + BlobName::new(encode_args(Producer::CreateBlob, self.0, length)) + })))) + } + + /// Fills the destination slice by copying bytes from the memory + /// + /// # Safety + /// + /// The `destination` slice's length must be <= size() * PAGE_SIZE + pub unsafe fn read(&self, destination: &mut [u8]) { + unsafe { + wasm_memory_read( + self.0 as u32, + destination.as_mut_ptr() as u32, + destination.len(), + ) + } + } + + /// Copies the bytes from the source slice into the memory + /// + /// # Safety + /// + /// The `source` slice's length must be <= size() * PAGE_SIZE + pub unsafe fn write(&mut self, source: &[u8]) { + unsafe { wasm_memory_write(self.0 as u32, source.as_ptr() as u32, source.len()) } + } + + /// Calls the fixshell's attach_blob after resolving the provided `handle` + /// + /// # Safety + /// + /// `handle` must refer to a blob + pub unsafe fn attach_blob(&mut self, handle: RustHandle<'_>) { + unsafe { util_attach_blob(self.0 as u32, &handle.raw_handle) } + } + + pub fn size(&self) -> usize { + unsafe { wasm_memory_size(self.0 as u32) } + } + + pub fn grow(&mut self, num_pages: usize) -> usize { + unsafe { wasm_memory_grow(self.0 as u32, num_pages) } + } + + pub fn from_bytes(bytes: &[u8]) -> Result<&'static mut Self, Error> { + let memory = Memory::next()?; + let mapped = memory.size(); + let required = bytes.len().div_ceil(PAGE_SIZE); + if required > mapped && memory.grow(required - mapped) == usize::MAX { + return Err(Error::GrowFailed); + } + unsafe { memory.write(bytes) }; + Ok(memory) + } + + pub fn from_blob(handle: RustHandle<'_>) -> Result<&'static mut Self, Error> { + let memory = Memory::next()?; + let mapped = memory.size(); + let required = handle.len().div_ceil(PAGE_SIZE); + if required > mapped && memory.grow(required - mapped) == usize::MAX { + return Err(Error::GrowFailed); + } + unsafe { memory.attach_blob(handle) }; + Ok(memory) + } + + pub fn to_bytes(&self, length: usize) -> Result, Error> { + if length > self.size() * PAGE_SIZE { + return Err(Error::OutOfBounds); + } + let mut bytes = alloc::vec![0; length]; + unsafe { self.read(&mut bytes) }; + Ok(bytes) + } + + pub fn to_blob(&self, length: usize) -> Result, Error> { + if length > self.size() * PAGE_SIZE { + return Err(Error::OutOfBounds); + } + Ok(unsafe { self.create_blob(length) }) + } +} diff --git a/fix/utils/src/table.rs b/fix/utils/src/table.rs new file mode 100644 index 00000000..04e7f7ee --- /dev/null +++ b/fix/utils/src/table.rs @@ -0,0 +1,128 @@ +use crate::*; + +unsafe extern "C" { + fn util_allocate_table(index: u16) -> *mut Table; + static UTIL_NUM_TABLES: u16; +} +static mut POSITION: u16 = 0; + +#[repr(transparent)] +pub struct Table(u16); + +impl Table { + #[doc(hidden)] + pub const EMPTY: Self = Self(0); + + pub fn new(index: u16) -> Result<&'static mut Self, Error> { + let slot = unsafe { util_allocate_table(index) }; + if slot.is_null() { + return Err(Error::Unavailable); + } + let table = unsafe { &mut *slot }; + table.0 = index; + Ok(table) + } + + pub fn next() -> Result<&'static mut Self, Error> { + unsafe { + while POSITION < UTIL_NUM_TABLES { + POSITION += 1; + if let Ok(table) = Table::new(POSITION) { + return Ok(table); + } + } + } + Err(Error::AllOccupied) + } + + /// Calls the fixshell's create_tree function when resolved. + /// Borrows the table until the handle is consumed + /// + /// # Safety + /// + /// `length` must be <= size() + pub unsafe fn create_tree(&self, length: usize) -> RustHandle<'_> { + RustHandle::new(Handle::Object(Object::Tree(Tree::Tree(unsafe { + TreeName::new(encode_args(Producer::CreateTree, self.0, length)) + })))) + } + + /// Gets the externref with index `entry` from the table when resolved + /// + /// # Safety + /// + /// `entry` must be < size() + pub unsafe fn get(&self, entry: usize) -> RustHandle<'_> { + RustHandle::new(Handle::Object(Object::Tree(Tree::Tree(unsafe { + TreeName::new(encode_args(Producer::TableGet, self.0, entry)) + })))) + } + + /// Sets index `entry` in the table with the externref resolved from `handle` + /// + /// # Safety + /// + /// `entry` must be < size() + pub unsafe fn set(&mut self, entry: usize, handle: RustHandle<'_>) { + unsafe { util_table_set(self.0 as u32, entry, &handle.raw_handle) } + } + + /// Calls the fixshell's attach_tree after resolving the provided `handle` + /// + /// # Safety + /// + /// `handle` must refer to a tree + pub unsafe fn attach_tree(&mut self, handle: RustHandle<'_>) { + unsafe { util_attach_tree(self.0 as u32, &handle.raw_handle) } + } + + pub fn size(&self) -> usize { + unsafe { wasm_table_size(self.0 as u32) } + } + + pub fn grow(&mut self, entries: usize) -> usize { + unsafe { wasm_table_grow(self.0 as u32, entries) } + } + + pub fn from_entries(entries: &[RustHandle<'_>]) -> Result<&'static mut Self, Error> { + let table = Table::next()?; + let mapped = table.size(); + let required = entries.len(); + if required > mapped && table.grow(required - mapped) == usize::MAX { + return Err(Error::GrowFailed); + } + for (entry, handle) in entries.iter().enumerate() { + unsafe { table.set(entry, *handle) }; + } + Ok(table) + } + + pub fn from_tree(handle: RustHandle<'_>) -> Result<&'static mut Self, Error> { + let table = Table::next()?; + let mapped = table.size(); + let required = handle.len(); + if required > mapped && table.grow(required - mapped) == usize::MAX { + return Err(Error::GrowFailed); + } + unsafe { table.attach_tree(handle) }; + Ok(table) + } + + pub fn to_entries(&self, length: usize) -> Result>, Error> { + if self.size() < length { + return Err(Error::OutOfBounds); + } + let mut entries = Vec::with_capacity(length); + for entry in 0..length { + entries.push(unsafe { self.get(entry) }); + } + Ok(entries) + } + + pub fn to_tree(&self, length: usize) -> Result, Error> { + if self.size() < length { + return Err(Error::OutOfBounds); + } + Ok(unsafe { self.create_tree(length) }) + } +} diff --git a/fix/utils/src/utils.c b/fix/utils/src/utils.c new file mode 100644 index 00000000..344bc84c --- /dev/null +++ b/fix/utils/src/utils.c @@ -0,0 +1,67 @@ +#include "utils.h" + +static externref __attribute__((address_space(1))) combination_global; + +static externref create_thunk(uint16_t meta, externref value) { + switch (THUNK_TAG(meta)) { + case IDENTIFICATION: return fixpoint_create_identification_thunk(value); + case APPLICATION: return fixpoint_create_application_thunk(value); + case SELECTION: return fixpoint_create_selection_thunk(value); + default: __builtin_unreachable(); + } +} + +static externref create_encode(uint16_t meta, externref value) { + switch (ENCODE_TAG(meta)) { + case STRICT: return fixpoint_create_strict_encode(value); + case SHALLOW: return fixpoint_create_shallow_encode(value); + default: __builtin_unreachable(); + } +} + +static externref resolve(const struct RustHandle* handle) { + externref value; + + switch (PRODUCER_TAG(handle->meta)) { + case COMBINATION: value = combination_global; break; + case TABLE_GET: value = wasm_table_get(handle->index, handle->entry); break; + case CREATE_BLOB: value = fixpoint_create_blob(handle->index, handle->entry); break; + case CREATE_TREE: value = fixpoint_create_tree(handle->index, handle->entry); break; + default: __builtin_unreachable(); + } + + switch (HANDLE_TAG(handle->meta)) { + case OBJECT: return value; + case REF: return fixpoint_create_ref(value); + case THUNK: return create_thunk(handle->meta, value); + case ENCODE: return create_encode(handle->meta, create_thunk(handle->meta, value)); + default: __builtin_unreachable(); + } +} + +void util_attach_blob(uint32_t memory_index, const struct RustHandle* handle) { + fixpoint_attach_blob(memory_index, resolve(handle)); +} + +void util_attach_tree(uint32_t table_index, const struct RustHandle* handle) { + fixpoint_attach_tree(table_index, resolve(handle)); +} + +uint32_t util_len(const struct RustHandle* handle) { + return fixpoint_len(resolve(handle)); +} + +void util_table_set(uint32_t table_index, uint32_t entry_index, const struct RustHandle* handle) { + wasm_table_set(table_index, entry_index, resolve(handle)); +} + +static const struct RustHandle combination = { + .meta = (COMBINATION << 12) | (OBJECT << 10) | (1 << 6), +}; + +__attribute__((export_name("_fixpoint_apply"))) +externref fixpoint_apply(externref input) { + combination_global = input; + struct RustHandle output = _fixpoint_apply_inner(combination); + return resolve(&output); +} \ No newline at end of file diff --git a/fix/utils/src/utils.h b/fix/utils/src/utils.h new file mode 100644 index 00000000..f3b924b9 --- /dev/null +++ b/fix/utils/src/utils.h @@ -0,0 +1,85 @@ +#ifndef FIXUTILS_H +#define FIXUTILS_H +#include + +typedef __externref_t externref; + +enum producer { + COMBINATION = 0, + TABLE_GET = 1, + CREATE_BLOB = 2, + CREATE_TREE = 3, +}; + +enum handle { + REF = 0, + OBJECT = 1, + THUNK = 2, + ENCODE = 3 +}; +enum thunk { + IDENTIFICATION = 0, + APPLICATION = 1, + SELECTION = 2 +}; +enum encode { + STRICT = 0, + SHALLOW = 1 +}; + +#define PRODUCER_TAG(meta) (((meta) >> 12) & 0x3) +#define HANDLE_TAG(meta) (((meta) >> 10) & 0x3) +#define ENCODE_TAG(meta) (((meta) >> 9) & 0x1) +#define THUNK_TAG(meta) (((meta) >> 7) & 0x3) + +// Shares the shape of RawName from fixhandle. Todo: parameterize out 24 byte name field +struct RustHandle { + uint8_t name[24]; + union { + uint64_t body; + struct { + uint32_t entry; + uint16_t index; + uint16_t meta; + }; + }; +}; + +__attribute__((import_module("fixpoint"), import_name("create_blob"))) +extern externref fixpoint_create_blob(uint32_t memory_index, uint32_t length); + +__attribute__((import_module("fixpoint"), import_name("create_tree"))) +extern externref fixpoint_create_tree(uint32_t table_index, uint32_t length); + +__attribute__((import_module("fixpoint"), import_name("create_ref"))) +extern externref fixpoint_create_ref(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_identification_thunk"))) +extern externref fixpoint_create_identification_thunk(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_application_thunk"))) +extern externref fixpoint_create_application_thunk(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_selection_thunk"))) +extern externref fixpoint_create_selection_thunk(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_strict_encode"))) +extern externref fixpoint_create_strict_encode(externref handle); + +__attribute__((import_module("fixpoint"), import_name("create_shallow_encode"))) +extern externref fixpoint_create_shallow_encode(externref handle); + +__attribute__((import_module("fixpoint"), import_name("attach_blob"))) +extern void fixpoint_attach_blob(uint32_t memory_index, externref handle); + +__attribute__((import_module("fixpoint"), import_name("attach_tree"))) +extern void fixpoint_attach_tree(uint32_t table_index, externref handle); + +__attribute__((import_module("fixpoint"), import_name("len"))) +extern uint32_t fixpoint_len(externref handle); + +extern externref wasm_table_get(uint32_t table_index, uint32_t entry_index); +extern void wasm_table_set(uint32_t table_index, uint32_t entry_index, externref value); +extern struct RustHandle _fixpoint_apply_inner(struct RustHandle combination); + +#endif diff --git a/fix/wasm/addblob.wat b/fix/wasm/addblob.wat index d4f1683e..a0c7408b 100644 --- a/fix/wasm/addblob.wat +++ b/fix/wasm/addblob.wat @@ -9,7 +9,7 @@ (func (export "_fixpoint_apply") (param $encode externref) (result externref) ;; attach combination tree (call $attach_tree - (i32.const 0) + (i32.const 1) (local.get $encode)) ;; grow rw-memory (memory.grow diff --git a/fix/wasm/identity.wat b/fix/wasm/identity.wat new file mode 100644 index 00000000..84ea9c12 --- /dev/null +++ b/fix/wasm/identity.wat @@ -0,0 +1,5 @@ +(module + (func (export "_fixpoint_apply") (param $encode externref) (result externref) + local.get 0 + ) +) \ No newline at end of file diff --git a/fix/wasm/slowaddblob.wat b/fix/wasm/slowaddblob.wat index d992ad6a..5c5b0919 100644 --- a/fix/wasm/slowaddblob.wat +++ b/fix/wasm/slowaddblob.wat @@ -22,7 +22,7 @@ ;; Attach the combination tree. (call $attach_tree - (i32.const 0) + (i32.const 1) (local.get $encode)) ;; Grow rw-memory by zero pages, preserving the original behavior. diff --git a/macros/src/fixutils.rs b/macros/src/fixutils.rs new file mode 100644 index 00000000..805e0234 --- /dev/null +++ b/macros/src/fixutils.rs @@ -0,0 +1,160 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, ItemFn, LitInt}; + +pub fn entrypoint(_attr: TokenStream, item: TokenStream) -> TokenStream { + let item = parse_macro_input!(item as ItemFn); + let _fixpoint_apply = &item.sig.ident; + + quote! { + #item + #[unsafe(export_name = "_fixpoint_apply_inner")] + pub extern "C" fn _fixpoint_apply_inner(combination: ::fixutils::RustHandle<'static>) -> ::fixutils::RustHandle<'static> { + #_fixpoint_apply(combination).expect("expected _fixpoint_apply to succeed") + } + } + .into() +} + +fn memory_asm(count: usize) -> String { + let mut asm = String::new(); + for (name, signature, body) in [ + ( + "wasm_memory_read", + "(i32, i32, i32) -> ()", + "local.get 1\ni32.const 0\nlocal.get 2\nmemory.copy 0, {}", + ), + ( + "wasm_memory_write", + "(i32, i32, i32) -> ()", + "i32.const 0\nlocal.get 1\nlocal.get 2\nmemory.copy {}, 0", + ), + ("wasm_memory_size", "(i32) -> (i32)", "memory.size {}"), + ( + "wasm_memory_grow", + "(i32, i32) -> (i32)", + "local.get 1\nmemory.grow {}", + ), + ] { + asm += &format!( + ".section .text.{name},\"\",@\n.globl {name}\n{name}:\n.functype {name} {signature}\n" + ); + // Match statement + for index in 1..=count { + let body = body.replace("{}", &index.to_string()); + asm += &format!("local.get 0\ni32.const {index}\ni32.eq\nif\n{body}\nreturn\nend_if\n"); + } + asm += "unreachable\nend_function\n"; + } + // Number of memories encoded in custom section + asm + &format!(".section .custom_section.wasm_num_memories,\"\",@\n.int32 {count}\n") +} + +fn table_asm(count: usize) -> String { + let mut asm = String::new(); + // Tables + for index in 1..=count { + asm += &format!( + ".section .text.wasm_table_{index},\"\",@\n.globl wasm_table_{index}\n.tabletype wasm_table_{index}, externref\nwasm_table_{index}:\n" + ); + } + for (name, signature, body) in [ + ( + "wasm_table_get", + "(i32, i32) -> (externref)", + "local.get 1\ntable.get wasm_table_{}", + ), + ( + "wasm_table_set", + "(i32, i32, externref) -> ()", + "local.get 1\nlocal.get 2\ntable.set wasm_table_{}", + ), + ( + "wasm_table_size", + "(i32) -> (i32)", + "table.size wasm_table_{}", + ), + ( + "wasm_table_grow", + "(i32, i32) -> (i32)", + "ref.null_extern\nlocal.get 1\ntable.grow wasm_table_{}", + ), + ] { + asm += &format!( + ".section .text.{name},\"\",@\n.globl {name}\n{name}:\n.functype {name} {signature}\n" + ); + // Match statement + for index in 1..=count { + let body = body.replace("{}", &index.to_string()); + asm += &format!("local.get 0\ni32.const {index}\ni32.eq\nif\n{body}\nreturn\nend_if\n"); + } + asm += "unreachable\nend_function\n"; + } + asm +} + +pub fn num_memories(input: TokenStream) -> TokenStream { + let count: usize = match parse_macro_input!(input as LitInt).base10_parse() { + Ok(count) => count, + Err(error) => return error.to_compile_error().into(), + }; + let asm = memory_asm(count); + quote! { + #[doc(hidden)] + #[unsafe(no_mangle)] + pub static UTIL_NUM_MEMORIES: u16 = #count as u16; + + #[doc(hidden)] + #[unsafe(no_mangle)] + pub extern "C" fn util_allocate_memory(index: u16) -> *mut ::fixutils::Memory { + use ::core::sync::atomic::{AtomicBool, Ordering}; + + const COUNT: usize = #count; + static mut SLOTS: [::fixutils::Memory; COUNT] = [const { ::fixutils::Memory::EMPTY }; COUNT]; + static OCCUPIED: [AtomicBool; COUNT] = [const { AtomicBool::new(false) }; COUNT]; + let slot_index = index as usize - 1; + + // can't get memory 0, memory above count, or already occupied memory + if index == 0 || index as usize > COUNT || OCCUPIED[slot_index].swap(true, Ordering::Relaxed) { + return ::core::ptr::null_mut(); + } + unsafe { (&raw mut SLOTS).cast::<::fixutils::Memory>().add(index as usize - 1) } + } + + ::core::arch::global_asm!(#asm); + } + .into() +} + +pub fn num_tables(input: TokenStream) -> TokenStream { + let count: usize = match parse_macro_input!(input as LitInt).base10_parse() { + Ok(count) => count, + Err(error) => return error.to_compile_error().into(), + }; + let asm = table_asm(count); + quote! { + #[doc(hidden)] + #[unsafe(no_mangle)] + pub static UTIL_NUM_TABLES: u16 = #count as u16; + + #[doc(hidden)] + #[unsafe(no_mangle)] + pub extern "C" fn util_allocate_table(index: u16) -> *mut ::fixutils::Table { + use ::core::sync::atomic::{AtomicBool, Ordering}; + + const COUNT: usize = #count; + static mut SLOTS: [::fixutils::Table; COUNT] = [const { ::fixutils::Table::EMPTY }; COUNT]; + static OCCUPIED: [AtomicBool; COUNT] = [const { AtomicBool::new(false) }; COUNT]; + let slot_index = index as usize - 1; + + // can't get table 0, table above count, or already occupied table + if index == 0 || index as usize > COUNT || OCCUPIED[slot_index].swap(true, Ordering::Relaxed) { + return ::core::ptr::null_mut(); + } + unsafe { (&raw mut SLOTS).cast::<::fixutils::Table>().add(index as usize - 1) } + } + + ::core::arch::global_asm!(#asm); + } + .into() +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 8ce770b1..f6315fa5 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -2,6 +2,7 @@ use proc_macro::TokenStream; mod bitpack; mod core_local; +mod fixutils; mod testing; mod util; @@ -39,3 +40,18 @@ pub fn kmain(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn bitpack(input: TokenStream) -> TokenStream { bitpack::bitpack(input) } + +#[proc_macro_attribute] +pub fn procedure_entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { + fixutils::entrypoint(attr, item) +} + +#[proc_macro] +pub fn num_memories(input: TokenStream) -> TokenStream { + fixutils::num_memories(input) +} + +#[proc_macro] +pub fn num_tables(input: TokenStream) -> TokenStream { + fixutils::num_tables(input) +} diff --git a/procedure.fix b/procedure.fix new file mode 100644 index 00000000..25b04695 --- /dev/null +++ b/procedure.fix @@ -0,0 +1,2 @@ +(let ((procedure @"./target/x86_64-unknown-none/debug/fixprocedure")) + !*(procedure)) \ No newline at end of file diff --git a/selection.fix b/selection.fix new file mode 100644 index 00000000..97a2a7c2 --- /dev/null +++ b/selection.fix @@ -0,0 +1,2 @@ +(let ((nums &(1u8 2u8 3u8 4u8)) (word &"hello")) + *[word 0u8] ) \ No newline at end of file