From 1bdd526b668bcf9396dbfaef1aa9f8d56d22ff9f Mon Sep 17 00:00:00 2001 From: Yuhan Deng <31569419+yhdengh@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:35:28 -0700 Subject: [PATCH 1/4] feat: fix shell (#25) Implements an MVP of a shell that runs Fix programs under Arca. Supports attaching Fix Blobs and Trees and creating new Blobs. Currently all the Blob and Tree data are copied into the Arca's address space; this can be optimized in future revisions. Co-authored-by: Akshay Srivatsan --- .cargo/config.toml | 11 +- Cargo.lock | 90 +++++- Cargo.toml | 4 +- common/Cargo.toml | 4 +- common/src/bitpack.rs | 7 + common/src/lib.rs | 12 +- fix/Cargo.toml | 8 + fix/build.rs | 70 ++--- fix/fix-shell/fix.c | 0 fix/fix-shell/main.c | 157 ----------- fix/fix-shell/start.S | 9 - fix/fix-shell/wasm-rt-impl.c | 172 ------------ fix/handle/Cargo.toml | 13 + fix/handle/src/lib.rs | 10 + fix/handle/src/rawhandle.rs | 287 ++++++++++++++++++++ fix/handle/src/testing.rs | 5 + fix/runtime/Cargo.toml | 38 +++ fix/runtime/src/bottom.rs | 171 ++++++++++++ fix/runtime/src/data.rs | 156 +++++++++++ fix/runtime/src/fixruntime.rs | 107 ++++++++ fix/runtime/src/lib.rs | 8 + fix/runtime/src/runtime.rs | 29 ++ fix/runtime/src/storage.rs | 100 +++++++ fix/shell/Cargo.toml | 23 ++ fix/shell/build.rs | 25 ++ fix/{fix-shell => shell/etc}/memmap.ld | 1 + fix/shell/etc/wasm-rt.c | 18 ++ fix/{fix-shell => shell/inc}/wasm-rt-impl.h | 0 fix/{fix-shell => shell/inc}/wasm-rt.h | 7 +- fix/shell/src/fixpoint.rs | 57 ++++ fix/shell/src/lib.rs | 97 +++++++ fix/shell/src/rt.rs | 148 ++++++++++ fix/shell/src/runtime.rs | 22 ++ fix/shell/src/shell.rs | 239 ++++++++++++++++ fix/src/main.rs | 68 +++-- fix/src/testing.rs | 5 + fix/wasm/addblob.wat | 21 +- kernel/src/cpu.rs | 2 +- kernel/src/lapic.rs | 3 +- kernel/src/lib.rs | 4 - kernel/src/types/arca.rs | 28 ++ kernel/src/types/function/syscall.rs | 6 +- kernel/src/types/runtime.rs | 1 - kernel/src/types/table.rs | 2 +- macros/Cargo.toml | 2 +- macros/src/bitpack.rs | 158 +++++++++++ macros/src/lib.rs | 6 + modules/arca-musl | 2 +- user/build.rs | 3 +- user/src/lib.rs | 6 +- vmm/src/lib.rs | 3 - vmm/src/main.rs | 4 - 52 files changed, 1969 insertions(+), 460 deletions(-) create mode 100644 common/src/bitpack.rs delete mode 100644 fix/fix-shell/fix.c delete mode 100644 fix/fix-shell/main.c delete mode 100644 fix/fix-shell/start.S delete mode 100644 fix/fix-shell/wasm-rt-impl.c create mode 100644 fix/handle/Cargo.toml create mode 100644 fix/handle/src/lib.rs create mode 100644 fix/handle/src/rawhandle.rs create mode 100644 fix/handle/src/testing.rs create mode 100644 fix/runtime/Cargo.toml create mode 100644 fix/runtime/src/bottom.rs create mode 100644 fix/runtime/src/data.rs create mode 100644 fix/runtime/src/fixruntime.rs create mode 100644 fix/runtime/src/lib.rs create mode 100644 fix/runtime/src/runtime.rs create mode 100644 fix/runtime/src/storage.rs create mode 100644 fix/shell/Cargo.toml create mode 100644 fix/shell/build.rs rename fix/{fix-shell => shell/etc}/memmap.ld (98%) create mode 100644 fix/shell/etc/wasm-rt.c rename fix/{fix-shell => shell/inc}/wasm-rt-impl.h (100%) rename fix/{fix-shell => shell/inc}/wasm-rt.h (98%) create mode 100644 fix/shell/src/fixpoint.rs create mode 100644 fix/shell/src/lib.rs create mode 100644 fix/shell/src/rt.rs create mode 100644 fix/shell/src/runtime.rs create mode 100644 fix/shell/src/shell.rs create mode 100644 fix/src/testing.rs create mode 100644 macros/src/bitpack.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 6614b65..b6a7b10 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,8 +2,15 @@ bindeps = true [target.x86_64-unknown-linux-gnu] -rustflags = ["-Cforce-frame-pointers=on"] +rustflags = [ + "-C", "force-frame-pointers=on" +] [target.x86_64-unknown-none] -rustflags = ["-Cforce-frame-pointers=on"] +rustflags = [ + "-C", "force-frame-pointers=on", + "-C", "code-model=large", + "-C", "relocation-model=static", + # "-C", "target-feature=+crt-static", +] runner = "cargo run -p vmm --" diff --git a/Cargo.lock b/Cargo.lock index 6ff0779..1a1a9c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -279,6 +288,17 @@ dependencies = [ "syn", ] +[[package]] +name = "bitfield-struct" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -313,6 +333,12 @@ dependencies = [ "piper", ] +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + [[package]] name = "byteorder" version = "1.5.0" @@ -327,9 +353,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "cc" -version = "1.2.52" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "shlex", @@ -671,9 +697,9 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fix" @@ -684,15 +710,55 @@ dependencies = [ "arcane", "async-lock", "autotools", + "bindgen", + "bitfield-struct 0.11.0", + "bytemuck", "cc", "chrono", "cmake", "common", "derive_more", + "fixhandle", + "fixruntime", + "fixshell", "futures", "include_directory", "kernel", "log", + "macros", + "postcard", + "serde", + "serde_bytes", + "trait-variant", + "user", +] + +[[package]] +name = "fixhandle" +version = "0.1.0" +dependencies = [ + "common", + "derive_more", +] + +[[package]] +name = "fixruntime" +version = "0.1.0" +dependencies = [ + "arca", + "arcane", + "async-lock", + "bitfield-struct 0.11.0", + "bytemuck", + "chrono", + "chumsky", + "common", + "derive_more", + "fixhandle", + "futures", + "kernel", + "log", + "macros", "postcard", "serde", "serde_bytes", @@ -700,6 +766,20 @@ dependencies = [ "user", ] +[[package]] +name = "fixshell" +version = "0.1.0" +dependencies = [ + "alloca", + "anyhow", + "arca", + "arcane", + "bindgen", + "cc", + "fixhandle", + "user", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -959,7 +1039,7 @@ dependencies = [ "arca", "arcane", "async-lock", - "bitfield-struct", + "bitfield-struct 0.7.0", "cc", "cfg-if", "common", diff --git a/Cargo.toml b/Cargo.toml index f08277f..993a3df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] -members = [ "common", "vmm", "kernel", "macros", "user", "arca" , "arcane", "fix"] -default-members = [ "common", "vmm", "macros", "arca"] +members = [ "common", "vmm", "kernel", "macros" , "user", "arca" , "arcane", "fix", "fix/runtime", "fix/handle", "fix/shell" ] +default-members = [ "common", "vmm", "macros", "arca", "fix/handle" ] resolver = "2" diff --git a/common/Cargo.toml b/common/Cargo.toml index 8275348..6c9e85c 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -8,14 +8,14 @@ default = ["std"] std = ["alloc", "snafu/std", "libc", "nix"] alloc = [] thread_local_cache = ["cache"] -core_local_cache = ["macros", "cache"] +core_local_cache = ["cache"] cache = [] nix = ["dep:nix"] [dependencies] log = "0.4.22" snafu = { version="0.8.5", default-features=false } -macros = { path = "../macros", optional=true } +macros = { path = "../macros" } arca = { path = "../arca" } libc = { version="0.2.164", optional=true } elf = { version = "0.7.4", default-features = false } diff --git a/common/src/bitpack.rs b/common/src/bitpack.rs new file mode 100644 index 0000000..0f0e630 --- /dev/null +++ b/common/src/bitpack.rs @@ -0,0 +1,7 @@ +pub use macros::BitPack; + +pub trait BitPack { + const TAGBITS: u32; + fn pack(&self) -> [u8; 32]; + fn unpack(content: [u8; 32]) -> Self; +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 5af14d8..20d7811 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,24 +1,18 @@ #![cfg_attr(not(feature = "std"), no_std)] #![feature(allocator_api)] -#![feature(box_as_ptr)] #![feature(fn_traits)] -#![feature(layout_for_ptr)] -#![feature(maybe_uninit_as_bytes)] +#![cfg_attr(feature = "std", feature(layout_for_ptr))] #![feature(negative_impls)] -#![feature(new_range_api)] #![feature(ptr_metadata)] -#![feature(slice_from_ptr_range)] -#![feature(sync_unsafe_cell)] -#![feature(try_trait_v2)] -#![feature(test)] +#![cfg_attr(test, feature(test))] #![feature(unboxed_closures)] -#![cfg_attr(feature = "std", feature(thread_id_value))] #![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; diff --git a/fix/Cargo.toml b/fix/Cargo.toml index 4198dea..4df65c9 100644 --- a/fix/Cargo.toml +++ b/fix/Cargo.toml @@ -14,11 +14,15 @@ klog-warn = ["kernel/klog-warn"] klog-error = ["kernel/klog-error"] klog-off = ["kernel/klog-off"] debugcon = ["kernel/debugcon"] +testing-mode = ["fixhandle/testing-mode"] [dependencies] arca = { path = "../arca", features = ["serde"] } kernel = { path = "../kernel" } +macros = { path = "../macros" } common = { path = "../common", default-features = false } +fixhandle = { path = "handle" } +fixruntime = { path = "runtime" } log = "0.4.27" serde = { version = "1.0.219", default-features = false, features = ["alloc", "derive"] } chrono = { version = "0.4.41", default-features = false, features = ["alloc", "serde"] } @@ -30,9 +34,13 @@ trait-variant = "0.1.2" futures = { version = "0.3.31", default-features = false, features = ["alloc", "async-await"] } user = { path = "../user", artifact = "bin", target = "x86_64-unknown-none" } async-lock = { version = "3.4.1", default-features = false } +bytemuck = "1.24.0" +bitfield-struct = "0.11.0" [build-dependencies] +fixshell = { path = "shell", artifact="staticlib", target = "x86_64-unknown-none" } anyhow = "1.0.98" +bindgen = "0.72.1" cc = "1.2.30" autotools = "0.2.7" cmake = "0.1.54" diff --git a/fix/build.rs b/fix/build.rs index 80cb7f4..6fe5f8b 100644 --- a/fix/build.rs +++ b/fix/build.rs @@ -1,5 +1,4 @@ use std::env; -use std::ffi::OsStr; use std::fs::create_dir_all; use std::io::ErrorKind; use std::path::{Path, PathBuf}; @@ -11,10 +10,10 @@ use cmake::Config; use include_directory::{Dir, include_directory}; -static FIX_SHELL: Dir<'_> = include_directory!("$CARGO_MANIFEST_DIR/fix-shell"); +static FIX_SHELL_INC: Dir<'_> = include_directory!("$CARGO_MANIFEST_DIR/shell/inc"); +static FIX_SHELL_ETC: Dir<'_> = include_directory!("$CARGO_MANIFEST_DIR/shell/etc"); static INTERMEDIATEOUT: OnceLock = OnceLock::new(); -static ARCAPREFIX: OnceLock = OnceLock::new(); static WASM2C: OnceLock = OnceLock::new(); static WAT2WASM: OnceLock = OnceLock::new(); @@ -72,7 +71,11 @@ fn wasm2c(wasm: &[u8]) -> Result<(Vec, Vec)> { } fn c2elf(c: &[u8], h: &[u8]) -> Result> { - FIX_SHELL.extract(INTERMEDIATEOUT.get().unwrap())?; + FIX_SHELL_INC.extract(INTERMEDIATEOUT.get().unwrap())?; + FIX_SHELL_ETC.extract(INTERMEDIATEOUT.get().unwrap())?; + + let mut wasm_rt = INTERMEDIATEOUT.get().unwrap().clone(); + wasm_rt.push("wasm-rt.c"); let mut c_file = INTERMEDIATEOUT.get().unwrap().clone(); c_file.push("module.c"); @@ -80,19 +83,13 @@ fn c2elf(c: &[u8], h: &[u8]) -> Result> { let mut h_file = INTERMEDIATEOUT.get().unwrap().clone(); h_file.push("module.h"); - std::fs::write(c_file, c)?; + std::fs::write(c_file.clone(), c)?; std::fs::write(h_file, h)?; - let mut src = vec![]; - let exts = [OsStr::new("c"), OsStr::new("S")]; - for f in std::fs::read_dir(INTERMEDIATEOUT.get().unwrap())? { - let f = f?; - if let Some(ext) = f.path().extension() - && exts.contains(&ext) - { - src.push(f.path()); - } - } + let mut src = vec![c_file, wasm_rt]; + + let shell_top = env::var_os("CARGO_STATICLIB_FILE_FIXSHELL_fixshell").unwrap(); + src.push(PathBuf::from(shell_top)); println!("{src:?}"); @@ -102,10 +99,7 @@ fn c2elf(c: &[u8], h: &[u8]) -> Result> { let mut memmap = INTERMEDIATEOUT.get().unwrap().clone(); memmap.push("memmap.ld"); - let prefix = ARCAPREFIX.get().unwrap(); - let gcc = prefix.join("bin/musl-gcc"); - - let cc = Command::new(gcc) + let cc = Command::new("gcc") .args([ "-o", o_file.to_str().unwrap(), @@ -116,11 +110,14 @@ fn c2elf(c: &[u8], h: &[u8]) -> Result> { "-frounding-math", // "-fsignaling-nans", "-ffreestanding", - // "-nostdlib", + "-nostdlib", "-nostartfiles", - "-mcmodel=large", "--verbose", - "-Wl,-no-pie", + "-mcmodel=large", + // "-fno-pic", + // "-fno-pie", + // "-Wl,-no-pie", + // "-static", ]) .args(src) .status().map_err(|e| if let ErrorKind::NotFound = e.kind() {anyhow!("Compilation failed. Please make sure you have installed gcc-multilib if you are on Ubuntu.")} else {e.into()})?; @@ -136,26 +133,12 @@ fn main() -> Result<()> { let mut intermediateout: PathBuf = out_dir.clone().into(); intermediateout.push("inter-out"); - if !intermediateout.exists() { - create_dir_all(&intermediateout)? + if intermediateout.exists() { + std::fs::remove_dir_all(&intermediateout)?; } + create_dir_all(&intermediateout)?; INTERMEDIATEOUT.set(intermediateout).unwrap(); - let mut prefix: PathBuf = out_dir.clone().into(); - prefix.push("arca-musl-large"); - - if !prefix.exists() { - create_dir_all(&prefix)? - } - - let prefix = autotools::Config::new("../modules/arca-musl") - .cflag("-mcmodel=large") - .cxxflag("-mcmodel=large") - .out_dir(prefix) - .build(); - - ARCAPREFIX.set(prefix).unwrap(); - let mut dst: PathBuf = out_dir.clone().into(); dst.push("wabt"); if !dst.exists() { @@ -166,8 +149,6 @@ fn main() -> Result<()> { .define("BUILD_TESTS", "OFF") .define("BUILD_LIBWASM", "OFF") .define("BUILD_TOOLS", "ON") - .cflag("-fPIE") - .cxxflag("-fPIE") .out_dir(dst) .build(); @@ -190,9 +171,10 @@ fn main() -> Result<()> { std::fs::write(dst, elf)?; } - let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - println!("cargo::rerun-if-changed={dir}/etc/memmap.ld"); - println!("cargo::rustc-link-arg=-T{dir}/etc/memmap.ld"); + let cwd = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + println!("cargo::rerun-if-changed={cwd}/etc/memmap.ld"); + println!("cargo::rustc-link-arg=-T{cwd}/etc/memmap.ld"); println!("cargo::rustc-link-arg=-no-pie"); Ok(()) diff --git a/fix/fix-shell/fix.c b/fix/fix-shell/fix.c deleted file mode 100644 index e69de29..0000000 diff --git a/fix/fix-shell/main.c b/fix/fix-shell/main.c deleted file mode 100644 index fe3f912..0000000 --- a/fix/fix-shell/main.c +++ /dev/null @@ -1,157 +0,0 @@ -#include "module.h" -#include "wasm-rt.h" - - -#include -#include -#include - -#define SELF_PAGE_TABLE 0 - -extern wasm_rt_memory_t *WASM_MEMORIES[128]; -extern size_t WASM_MEMORIES_N; - -static int len(const char *s) { - int i = 0; - while (s[i]) - i++; - return i; -} - -static void error_append(const char *msg) { - arca_debug_log((const uint8_t *)msg, len(msg)); -} - -static void error_append_int(const char *msg, int value) { - arca_debug_log_int((const uint8_t* )msg, len(msg), value); -} - -[[noreturn]] void trap(const char *msg) { - error_append(msg); - arca_exit(0); -} - -[[noreturn]] void abort(void) { - error_append("abort"); - arca_exit(0); -} - -uint64_t check(int64_t ret) { - assert(ret >= 0); - return ret; -} - -wasm_rt_externref_t w2c_fixpoint_create_blob_i64(struct w2c_fixpoint *instance, - uint64_t val) { - return check(arca_word_create(val)); -} - -wasm_rt_externref_t w2c_fixpoint_get_tree_entry(struct w2c_fixpoint *instance, - wasm_rt_externref_t handle, - uint32_t index) { - arcad entry = check(arca_tuple_get(handle, index)); - return entry; -} - -static size_t bytes_to_wasm_pages(size_t bytes) { - return (bytes + PAGE_SIZE - 1) / PAGE_SIZE; -} - -static arcad create_wasm_pages(size_t wasm_pages) { - size_t bytes = wasm_pages * PAGE_SIZE; - size_t pages = (bytes + 4095) / 4096; - arcad table = arca_table_create(bytes); - for (size_t i = 0; i < pages; i++) { - struct arca_entry entry; - entry.mode = __MODE_read_write; - entry.data = check(arca_page_create(4096)); - arca_table_map(table, (void *)(i * 4096), &entry); - } - return table; -} - -static struct arca_entry map_table(void *addr, arcad table, bool write) { - struct arca_entry entry; - entry.mode = write ? __MODE_read_write: __MODE_read_only; - entry.data = table; - check(arca_mmap(addr, &entry)); - return entry; -} - -void w2c_fixpoint_attach_blob(struct w2c_fixpoint *instance, uint32_t n, - wasm_rt_externref_t handle) { - assert(n < WASM_MEMORIES_N); - wasm_rt_memory_t *memory = WASM_MEMORIES[n]; - void *addr = (void *)((size_t)n << 32); - - size_t nbytes; - check(arca_length(handle, &nbytes)); - size_t npages = bytes_to_wasm_pages(nbytes); - memory->size = nbytes; - memory->pages = npages; - - // TODO: map these blobs as read-only - arcad pages; - struct arca_entry entry; - switch (arca_type(handle)) { - case __TYPE_word: { - assert(npages == 1); - pages = create_wasm_pages(npages); - entry = map_table(addr, pages, true); - assert(entry.mode == __MODE_none); - arca_word_read(handle, addr); - arca_mmap(addr, &entry); - assert(entry.mode == __MODE_read_write); - entry.mode = __MODE_read_only; - arca_mmap(addr, &entry); - if (entry.mode != __MODE_none) { - arca_drop(entry.data); - } - return; - } - - case __TYPE_blob: { - pages = check(create_wasm_pages(npages)); - entry = map_table(addr, pages, true); - arca_blob_read(handle, 0, addr, nbytes); - arca_mmap(addr, &entry); - entry.mode = __MODE_read_only; - arca_mmap(addr, &entry); - if (entry.mode != __MODE_none) { - arca_drop(entry.data); - } - return; - } - - case __TYPE_page: { - pages = check(create_wasm_pages(npages)); - entry = map_table(addr, pages, true); - arca_page_read(handle, 0, addr, nbytes); - arca_mmap(addr, &entry); - entry.mode = __MODE_read_only; - arca_mmap(addr, &entry); - if (entry.mode != __MODE_none) { - arca_drop(entry.data); - } - return; - } - - case __TYPE_table: { - map_table(addr, handle, false); - return; - } - - default: - assert(false); - } - - return; -} - -[[noreturn]] void fmain(void) { - w2c_module module; - wasm2c_module_instantiate(&module, (struct w2c_fixpoint *)&module); - wasm_rt_externref_t argument = arca_argument(); - wasm_rt_externref_t result = w2c_module_0x5Ffixpoint_apply(&module, argument); - arca_exit(result); -} diff --git a/fix/fix-shell/start.S b/fix/fix-shell/start.S deleted file mode 100644 index 1b67c57..0000000 --- a/fix/fix-shell/start.S +++ /dev/null @@ -1,9 +0,0 @@ -.intel_syntax noprefix - -.extern fmain -.extern __stack_top -.globl _start -_start: - lea rsp, __stack_top[rip] - call fmain - int3 diff --git a/fix/fix-shell/wasm-rt-impl.c b/fix/fix-shell/wasm-rt-impl.c deleted file mode 100644 index e3ce5b2..0000000 --- a/fix/fix-shell/wasm-rt-impl.c +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2018 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "wasm-rt-impl.h" -#include -#include "wasm-rt.h" - -#include -#include -#include -#include - -wasm_rt_memory_t *WASM_MEMORIES[128]; -size_t WASM_MEMORIES_N = 0; - -uint64_t check(int64_t ret); -[[noreturn]] void trap(const char *msg); - -void wasm_rt_trap(wasm_rt_trap_t code) { - assert(code != WASM_RT_TRAP_NONE); - switch (code) { - case WASM_RT_TRAP_NONE: - trap("Wasm Runtime Trap: None"); - case WASM_RT_TRAP_OOB: - trap( - "Wasm Runtime Trap: Out-of-bounds access in linear memory or a table."); - case WASM_RT_TRAP_INT_OVERFLOW: - trap("Wasm Runtime Trap: Integer overflow on divide or truncation."); - case WASM_RT_TRAP_DIV_BY_ZERO: - trap("Wasm Runtime Trap: Integer divide by zero"); - case WASM_RT_TRAP_INVALID_CONVERSION: - trap("Wasm Runtime Trap: Conversion from NaN to integer."); - case WASM_RT_TRAP_UNREACHABLE: - trap("Wasm Runtime Trap: Unreachable instruction executed."); - case WASM_RT_TRAP_CALL_INDIRECT: /** Invalid call_indirect, for any reason. - */ - trap("Wasm Runtime Trap: Invalid call_indirect."); - case WASM_RT_TRAP_UNCAUGHT_EXCEPTION: - trap("Wasm Runtime Trap: Exception thrown and not caught."); - case WASM_RT_TRAP_UNALIGNED: - trap("Wasm Runtime Trap: Unaligned atomic instruction executed."); -#if WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS - case WASM_RT_TRAP_EXHAUSTION = WASM_RT_TRAP_OOB: -#else - case WASM_RT_TRAP_EXHAUSTION: - trap("Wasm Runtime Trap: Call stack exhausted."); -#endif - }; - abort(); -} - -void wasm_rt_init(void) {} - -bool wasm_rt_is_initialized(void) { return true; } - -void wasm_rt_free(void) {} - -void wasm_rt_allocate_memory(wasm_rt_memory_t *memory, uint64_t initial_pages, - uint64_t max_pages, bool is64) { - size_t n = WASM_MEMORIES_N++; - assert(n < 128); - WASM_MEMORIES[n] = memory; - - assert(max_pages <= (1ul << 32) / PAGE_SIZE); - - memory->data = (void *)(n << 32); - uint64_t byte_length = initial_pages * PAGE_SIZE; - memory->size = byte_length; - memory->pages = initial_pages; - memory->max_pages = max_pages; - memory->is64 = is64; - - for (uint64_t i = 0; i < byte_length >> 12; i++) { - arcad page = check(arca_page_create(1 << 12)); - check(arca_mmap(memory->data + i * 4096, &(struct arca_entry){ - .mode = __MODE_read_write, - .data = page, - })); - } - return; -} - -uint64_t wasm_rt_grow_memory(wasm_rt_memory_t *memory, uint64_t delta) { - uint64_t old_pages = memory->pages; - uint64_t new_pages = memory->pages + delta; - if (new_pages == 0) { - return 0; - } - if (new_pages < old_pages || new_pages > memory->max_pages) { - return (uint64_t)-1; - } - uint64_t old_size = old_pages * PAGE_SIZE; - uint64_t new_size = new_pages * PAGE_SIZE; - uint64_t delta_size = delta * PAGE_SIZE; - - for (uint64_t i = 0; i < delta_size >> 12; i++) { - arcad page = check(arca_page_create(1 << 12)); - check(arca_mmap(memory->data + +memory->size + i * 4096, - &(struct arca_entry){ - .mode = __MODE_read_write, - .data = page, - })); - } - - memory->pages = new_pages; - memory->size = new_size; - return old_pages; -} - -void wasm_rt_free_memory(wasm_rt_memory_t *memory) { return; } - -#define DEFINE_TABLE_OPS(type) \ - void wasm_rt_allocate_##type##_table(wasm_rt_##type##_table_t *table, \ - uint32_t elements, \ - uint32_t max_elements) { \ - abort(); \ - } \ - void wasm_rt_free_##type##_table(wasm_rt_##type##_table_t *table) { \ - abort(); \ - } \ - uint32_t wasm_rt_grow_##type##_table(wasm_rt_##type##_table_t *table, \ - uint32_t delta, \ - wasm_rt_##type##_t init) { \ - abort(); \ - } - -DEFINE_TABLE_OPS(funcref) -DEFINE_TABLE_OPS(externref) - -const char *wasm_rt_strerror(wasm_rt_trap_t trap) { - switch (trap) { - case WASM_RT_TRAP_NONE: - return "No error"; - case WASM_RT_TRAP_OOB: -#if WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS - return "Out-of-bounds access in linear memory or a table, or call stack " - "exhausted"; -#else - return "Out-of-bounds access in linear memory or a table"; - case WASM_RT_TRAP_EXHAUSTION: - return "Call stack exhausted"; -#endif - case WASM_RT_TRAP_INT_OVERFLOW: - return "Integer overflow on divide or truncation"; - case WASM_RT_TRAP_DIV_BY_ZERO: - return "Integer divide by zero"; - case WASM_RT_TRAP_INVALID_CONVERSION: - return "Conversion from NaN to integer"; - case WASM_RT_TRAP_UNREACHABLE: - return "Unreachable instruction executed"; - case WASM_RT_TRAP_CALL_INDIRECT: - return "Invalid call_indirect or return_call_indirect"; - case WASM_RT_TRAP_UNCAUGHT_EXCEPTION: - return "Uncaught exception"; - case WASM_RT_TRAP_UNALIGNED: - return "Unaligned atomic memory access"; - } - return "invalid trap code"; -} diff --git a/fix/handle/Cargo.toml b/fix/handle/Cargo.toml new file mode 100644 index 0000000..148a707 --- /dev/null +++ b/fix/handle/Cargo.toml @@ -0,0 +1,13 @@ +cargo-features = ["per-package-target"] + +[package] +name = "fixhandle" +version = "0.1.0" +edition = "2024" + +[dependencies] +derive_more = { version = "2.0.1", default-features = false, features = ["full"] } +common = { path = "../../common", default-features = false } + +[features] +testing-mode = [] diff --git a/fix/handle/src/lib.rs b/fix/handle/src/lib.rs new file mode 100644 index 0000000..adfa9fc --- /dev/null +++ b/fix/handle/src/lib.rs @@ -0,0 +1,10 @@ +#![no_std] +#![allow(dead_code)] +#![cfg_attr(feature = "testing-mode", feature(custom_test_frameworks))] +#![cfg_attr(feature = "testing-mode", test_runner(crate::testing::test_runner))] +#![cfg_attr(feature = "testing-mode", reexport_test_harness_main = "test_main")] + +#[cfg(feature = "testing-mode")] +mod testing; + +pub mod rawhandle; diff --git a/fix/handle/src/rawhandle.rs b/fix/handle/src/rawhandle.rs new file mode 100644 index 0000000..ff22e39 --- /dev/null +++ b/fix/handle/src/rawhandle.rs @@ -0,0 +1,287 @@ +#![allow(clippy::double_parens)] +pub use common::bitpack::BitPack; +use derive_more::{From, TryInto, TryUnwrap, Unwrap}; + +const fn ceil_log2(n: u32) -> u32 { + if n <= 1 { + 0 + } else { + 32 - (n - 1).leading_zeros() + } +} + +const fn bitmask256() -> [u8; 32] { + assert!(I + WIDTH <= 256); + let mut out = [0u8; 32]; + let mut i = I; + loop { + if i >= I + WIDTH { + break; + } + + let byte = i / 8; + let off = i % 8; + out[byte as usize] |= 1u8 << off; + + i += 1; + } + out +} + +#[derive(Debug, Clone, Copy)] +struct RawHandle { + content: [u8; 32], +} + +impl RawHandle { + fn new(content: [u8; 32]) -> Self { + Self { content } + } +} + +#[derive(Debug, Clone, Copy)] +struct MachineHandle { + inner: RawHandle, +} + +impl MachineHandle { + fn new(payload: u64, size: u64) -> Self { + assert!(size & 0xffff000000000000 == 0); + let field = unsafe { core::mem::transmute::<[u64; 4], [u8; 32]>([payload, 0, 0, size]) }; + let inner = RawHandle::new(field); + Self { inner } + } + + fn get_payload(&self) -> u64 { + let field: &[u64; 4] = unsafe { core::mem::transmute(&self.inner.content) }; + field[0] + } + + fn get_size(&self) -> u64 { + let field: &[u64; 4] = unsafe { core::mem::transmute(&self.inner.content) }; + field[3] & 0xffffffffffff + } +} + +impl BitPack for MachineHandle { + const TAGBITS: u32 = 240; + + fn unpack(content: [u8; 32]) -> Self { + let inner = RawHandle::new(content); + Self { inner } + } + + fn pack(&self) -> [u8; 32] { + self.inner.content + } +} + +#[derive(Debug, Clone, Copy)] +pub struct VirtualHandle { + inner: MachineHandle, +} + +impl BitPack for VirtualHandle { + const TAGBITS: u32 = MachineHandle::TAGBITS; + fn unpack(content: [u8; 32]) -> Self { + let inner = MachineHandle::unpack(content); + Self { inner } + } + + fn pack(&self) -> [u8; 32] { + self.inner.pack() + } +} + +impl VirtualHandle { + pub fn new(addr: usize, size: usize) -> Self { + let inner = MachineHandle::new(addr as u64, size as u64); + Self { inner } + } + + pub fn addr(&self) -> usize { + self.inner.get_payload().try_into().unwrap() + } + + pub fn len(&self) -> usize { + self.inner.get_size().try_into().unwrap() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(Debug, Clone, Copy)] +pub struct PhysicalHandle { + inner: MachineHandle, +} + +impl BitPack for PhysicalHandle { + const TAGBITS: u32 = MachineHandle::TAGBITS; + fn unpack(content: [u8; 32]) -> Self { + let inner = MachineHandle::unpack(content); + Self { inner } + } + + fn pack(&self) -> [u8; 32] { + self.inner.pack() + } +} + +impl PhysicalHandle { + pub fn new(local_id: usize, size: usize) -> Self { + let inner = MachineHandle::new(local_id as u64, size as u64); + Self { inner } + } + + pub fn local_id(&self) -> usize { + self.inner.get_payload().try_into().unwrap() + } + + pub fn len(&self) -> usize { + self.inner.get_size().try_into().unwrap() + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +#[derive(BitPack, Debug, Clone, Copy, From, TryUnwrap)] +pub enum Handle { + VirtualHandle(VirtualHandle), + PhysicalHandle(PhysicalHandle), +} + +#[derive(BitPack, Debug, TryUnwrap, Unwrap, From, Clone, Copy)] +#[unwrap(ref)] +#[try_unwrap(ref)] +pub enum BlobName { + Blob(Handle), +} + +#[derive(BitPack, Debug, Unwrap, Clone, Copy)] +pub enum TreeName { + NotTag(Handle), + Tag(Handle), +} + +impl From for Handle { + fn from(val: TreeName) -> Self { + match val { + TreeName::Tag(h) | TreeName::NotTag(h) => h, + } + } +} + +#[derive(BitPack, Debug, TryUnwrap, Unwrap, From, Clone, Copy)] +#[try_unwrap(ref)] +pub enum Ref { + BlobName(BlobName), + TreeName(TreeName), +} + +#[derive(BitPack, Debug, TryUnwrap, Unwrap, From, Clone, Copy)] +#[try_unwrap(ref)] +pub enum Object { + BlobName(BlobName), + TreeName(TreeName), +} + +#[derive(BitPack, Debug, Unwrap, Clone, Copy)] +pub enum Thunk { + Identification(Ref), + Application(TreeName), + Selection(TreeName), +} + +#[derive(BitPack, Debug, TryUnwrap, Unwrap, Clone, Copy)] +#[try_unwrap(ref)] +pub enum Encode { + Strict(Thunk), + Shallow(Thunk), +} + +#[derive(Debug, BitPack, TryUnwrap, Unwrap, From, Clone, Copy)] +#[try_unwrap(ref)] +pub enum FixHandle { + Ref(Ref), + Object(Object), + Thunk(Thunk), + Encode(Encode), +} + +#[derive(BitPack, Debug, TryInto, Unwrap, From, Clone, Copy)] +pub enum Value { + Ref(Ref), + Object(Object), + Thunk(Thunk), +} + +#[cfg(feature = "testing-mode")] +mod tests { + #[test_case] + fn test_tag_gits() { + assert_eq!(Handle::TAGBITS, 241); + assert_eq!(BlobName::TAGBITS, 241); + assert_eq!(TreeName::TAGBITS, 242); + assert_eq!(Object::TAGBITS, 243); + assert_eq!(Thunk::TAGBITS, 245); + } + + #[test_case] + fn test_tag_masks() { + assert_eq!(Handle::TAGMASK.as_array()[30], 0b00000001); + assert_eq!(Handle::TAGMASK.as_array()[31], 0b00000000); + + let field: u16x16 = unsafe { core::mem::transmute(Handle::TAGMASK) }; + assert_eq!(field[15], 0b0000000000000001); + + assert_eq!(TreeName::TAGMASK.as_array()[30], 0b00000010); + assert_eq!(TreeName::TAGMASK.as_array()[31], 0b00000000); + + assert_eq!(Thunk::TAGMASK.as_array()[30], 0b00011000); + assert_eq!(Thunk::TAGMASK.as_array()[31], 0b00000000); + } + + #[test_case] + fn test_pack() { + let h: Handle = PhysicalHandle::new(42, 10086).into(); + let res = h.pack(); + let field: &u16x16 = unsafe { core::mem::transmute(&res) }; + assert_eq!(field[15], 0b0000000000000001); + + let h: TreeName = TreeName::Tag(PhysicalHandle::new(42, 10086).into()); + let res = h.pack(); + let field: &u16x16 = unsafe { core::mem::transmute(&res) }; + assert_eq!(field[15], 0b0000000000000011); + } + + #[test_case] + fn test_round_trip() { + let h: Handle = PhysicalHandle::new(42, 10086).into(); + let res = Handle::unpack(h.pack()) + .try_unwrap_physical_handle() + .expect("Failed to unwrap to PhysicalHandle"); + assert_eq!(res.local_id(), 42); + assert_eq!(res.len(), 10086); + + let h: FixHandle = FixHandle::Object(Object::BlobName(BlobName::Blob( + PhysicalHandle::new(42, 10086).into(), + ))); + let res = FixHandle::unpack(h.pack()) + .try_unwrap_object() + .expect("Failed to unwrap to Object") + .try_unwrap_blob_name() + .expect("Failed to unwrap to BlobName") + .unwrap_blob() + .try_unwrap_physical_handle() + .expect("Failed to unwrap to PhysicalHandle"); + + assert_eq!(res.local_id(), 42); + assert_eq!(res.len(), 10086); + } +} diff --git a/fix/handle/src/testing.rs b/fix/handle/src/testing.rs new file mode 100644 index 0000000..26aecae --- /dev/null +++ b/fix/handle/src/testing.rs @@ -0,0 +1,5 @@ +pub fn test_runner(tests: &[&dyn Fn()]) { + for test in tests { + test(); + } +} diff --git a/fix/runtime/Cargo.toml b/fix/runtime/Cargo.toml new file mode 100644 index 0000000..86cedb6 --- /dev/null +++ b/fix/runtime/Cargo.toml @@ -0,0 +1,38 @@ +cargo-features = ["per-package-target"] + +[package] +name = "fixruntime" +version = "0.1.0" +edition = "2024" +forced-target = "x86_64-unknown-none" + +[features] +klog-trace = ["kernel/klog-trace"] +klog-debug = ["kernel/klog-debug"] +klog-info = ["kernel/klog-info"] +klog-warn = ["kernel/klog-warn"] +klog-error = ["kernel/klog-error"] +klog-off = ["kernel/klog-off"] +debugcon = ["kernel/debugcon"] +testing-mode = [] + +[dependencies] +arca = { path = "../../arca", features = ["serde"] } +kernel = { path = "../../kernel" } +macros = { path = "../../macros" } +common = { path = "../../common", default-features = false } +fixhandle = { path = "../handle" } +log = "0.4.27" +serde = { version = "1.0.219", default-features = false, features = ["alloc", "derive"] } +chrono = { version = "0.4.41", default-features = false, features = ["alloc", "serde"] } +serde_bytes = { version = "0.11.17", default-features = false, features = ["alloc"] } +arcane = { version = "0.1.0", path = "../../arcane" } +postcard = { version = "1.1.3", features = ["alloc"] } +derive_more = { version = "2.0.1", default-features = false, features = ["full"] } +trait-variant = "0.1.2" +futures = { version = "0.3.31", default-features = false, features = ["alloc", "async-await"] } +chumsky = { version = "0.10.1", default-features = false } +user = { path = "../../user", artifact = "bin", target = "x86_64-unknown-none" } +async-lock = { version = "3.4.1", default-features = false } +bytemuck = "1.24.0" +bitfield-struct = "0.11.0" diff --git a/fix/runtime/src/bottom.rs b/fix/runtime/src/bottom.rs new file mode 100644 index 0000000..156d128 --- /dev/null +++ b/fix/runtime/src/bottom.rs @@ -0,0 +1,171 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] + +use crate::{ + // data::{BlobData, RawData, TreeData}, + fixruntime::FixRuntime, + runtime::{DeterministicEquivRuntime, Executor}, +}; + +use arca::Runtime; +use fixhandle::rawhandle::{BitPack, FixHandle}; +use kernel::{ + prelude::vec::Vec, + types::{Blob, Function, Tuple, Value}, +}; + +#[derive(Debug)] +pub enum Error { + FixRuntimeError, +} + +fn pack_handle(handle: &FixHandle) -> Blob { + let raw = handle.pack(); + Runtime::create_blob(&raw) +} + +fn unpack_handle(blob: &Blob) -> FixHandle { + let mut buf = [0u8; 32]; + if Runtime::read_blob(blob, 0, &mut buf) != 32 { + panic!("Failed to parse Arca Blob to Fix Handle") + } + FixHandle::unpack(buf) +} + +pub struct FixShellBottom<'a, 'b> { + pub parent: &'b mut FixRuntime<'a>, +} + +impl<'a, 'b> DeterministicEquivRuntime for FixShellBottom<'a, 'b> { + type BlobData = Blob; + type TreeData = Tuple; + type Handle = Blob; + type Error = Error; + + fn create_blob_i64(&mut self, data: u64) -> Self::Handle { + pack_handle(&self.parent.create_blob_i64(data)) + } + + fn create_blob(&mut self, data: Self::BlobData) -> Self::Handle { + pack_handle(&self.parent.create_blob(data)) + } + + fn create_tree(&mut self, data: Self::TreeData) -> Self::Handle { + pack_handle(&self.parent.create_tree(data)) + } + + fn get_blob(&self, handle: &Self::Handle) -> Result { + self.parent + .get_blob(&unpack_handle(handle)) + .map_err(|_| Error::FixRuntimeError) + } + + fn get_tree(&self, handle: &Self::Handle) -> Result { + self.parent + .get_tree(&unpack_handle(handle)) + .map_err(|_| Error::FixRuntimeError) + } + + fn is_blob(handle: &Self::Handle) -> bool { + FixRuntime::is_blob(&unpack_handle(handle)) + } + + fn is_tree(handle: &Self::Handle) -> bool { + FixRuntime::is_tree(&unpack_handle(handle)) + } +} + +impl<'a, 'b> FixShellBottom<'a, 'b> { + fn run(&mut self, mut f: Function) -> FixHandle { + loop { + let result = f.force(); + if let Value::Blob(b) = result { + return unpack_handle(&b); + } else { + let Value::Function(g) = result else { + panic!("expected Fix program to return a handle or an effect") + }; + let data = g.into_inner().read(); + let Value::Tuple(mut data) = data else { + unreachable!() + }; + let t: Blob = data.take(0).try_into().unwrap(); + assert_eq!(&*t, b"Symbolic"); + let effect: Blob = data.take(1).try_into().unwrap(); + let args: Tuple = data.take(2).try_into().unwrap(); + let mut args: Vec = args.into_iter().collect(); + let Some(Value::Function(k)) = args.pop() else { + panic!("unexpected non-effect return"); + }; + + f = match &*effect { + b"create_blob_i64" => { + let Some(Value::Word(w)) = args.pop() else { + panic!() + }; + k.apply(self.create_blob_i64(w.read())) + } + b"create_blob" => { + let Some(Value::Blob(b)) = args.pop() else { + panic!() + }; + k.apply(self.create_blob(b)) + } + b"create_tree" => { + let Some(Value::Tuple(t)) = args.pop() else { + panic!() + }; + k.apply(self.create_tree(t)) + } + b"get_blob" => { + let Some(Value::Blob(b)) = args.pop() else { + panic!() + }; + let b = self.get_blob(&b).expect(""); + k.apply(b) + } + b"get_tree" => { + let Some(Value::Blob(b)) = args.pop() else { + panic!() + }; + let t = self.get_tree(&b).expect(""); + k.apply(t) + } + b"is_blob" => { + let Some(Value::Blob(b)) = args.pop() else { + panic!() + }; + k.apply(Runtime::create_word(Self::is_blob(&b) as u64)) + } + b"is_tree" => { + let Some(Value::Blob(b)) = args.pop() else { + panic!() + }; + k.apply(Runtime::create_word(Self::is_tree(&b) as u64)) + } + _ => { + log::info!("{:?}", &*effect); + unreachable!(); + } + }; + } + } + } +} + +impl<'a, 'b> Executor for FixShellBottom<'a, 'b> { + fn execute(&mut self, combination: &FixHandle) -> FixHandle { + let tree = self.parent.get_tree(combination).unwrap(); + let function_handle = tree.get(1); + let function_handle = Blob::try_from(function_handle).unwrap(); + let mut bytes = [0; 32]; + function_handle.read(0, &mut bytes); + let function_handle = FixHandle::unpack(bytes); + let elf = self.parent.get_blob(&function_handle).unwrap(); + + let f = common::elfloader::load_elf(&elf).expect("Failed to load elf"); + let f = Runtime::apply_function(f, Value::from(pack_handle(combination))); + + self.run(f) + } +} diff --git a/fix/runtime/src/data.rs b/fix/runtime/src/data.rs new file mode 100644 index 0000000..85d9c05 --- /dev/null +++ b/fix/runtime/src/data.rs @@ -0,0 +1,156 @@ +use arca::Runtime as _; +use core::cmp; +use core::panic; +use fixhandle::rawhandle::BitPack; +use fixhandle::rawhandle::FixHandle; +use kernel::prelude::*; +use kernel::types::Runtime; +use kernel::types::Table; + +const MAXSIZE: usize = 1 << 32; + +#[derive(Debug, Clone)] +pub struct RawData { + data: Table, + length: usize, +} + +impl RawData { + fn new(length: usize) -> Self { + if length > MAXSIZE { + panic!("Data larger than maximum size") + } + Self { + data: Table::new(MAXSIZE), + length, + } + } + + fn create(data: &[u8]) -> Self { + let mut inner = RawData::new(data.len()); + let pagesize = inner.data.len() / 512; + for i in 0..(data.len() + pagesize - 1) / pagesize { + let mut page = Runtime::create_page(pagesize); + Runtime::write_page(&mut page, 0, &data[i * pagesize..]); + Runtime::set_table(&mut inner.data, i, arca::Entry::ROPage(page)) + .expect("Unable to set entry"); + } + inner + } + + fn get(&self, start: usize, buf: &mut [u8]) { + let pagesize = self.data.len() / 512; + let mut curr_start = start; + let mut index = 0; + while curr_start - start < buf.len() { + // Advance to the end of current page + let mut curr_end = (curr_start / pagesize + 1) * pagesize; + curr_end = cmp::min(curr_end, start + buf.len()); + match Runtime::get_table(&self.data, index) + .expect("Page to get is out of the MAXSIZE range") + { + arca::Entry::Null(_) => (), + arca::Entry::ROPage(page) | arca::Entry::RWPage(page) => { + Runtime::read_page( + &page, + curr_start % pagesize, + &mut buf[curr_start - start..curr_end - start], + ); + } + arca::Entry::ROTable(_) => todo!(), + arca::Entry::RWTable(_) => todo!(), + } + + index += 1; + curr_start = curr_end; + } + } +} + +impl From for Table { + fn from(val: RawData) -> Self { + val.data + } +} + +#[derive(Debug, Clone)] +pub struct BlobData { + inner: RawData, +} + +impl BlobData { + pub fn new(data: Table, length: usize) -> Self { + let inner = RawData { data, length }; + Self { inner } + } + + pub fn create(data: &[u8]) -> Self { + let inner = RawData::create(data); + Self { inner } + } + + pub fn len(&self) -> usize { + self.inner.length + } + + pub fn get(&self, buf: &mut [u8]) { + self.inner.get(0, buf) + } +} + +impl From for RawData { + fn from(val: BlobData) -> Self { + val.inner + } +} + +impl From for BlobData { + fn from(value: RawData) -> Self { + Self { inner: value } + } +} + +#[derive(Debug, Clone)] +pub struct TreeData { + inner: RawData, +} + +impl TreeData { + pub fn new(data: Table, length: usize) -> Self { + let inner = RawData { data, length }; + Self { inner } + } + + pub fn create(data: &[FixHandle]) -> Self { + let mut buffer = vec![0u8; data.len() * 32]; + for (idx, i) in data.iter().enumerate() { + let raw = i.pack(); + buffer.as_mut_slice()[idx * 32..(idx + 1) * 32].copy_from_slice(&raw); + } + + let inner = RawData::create(&buffer); + Self { inner } + } + + pub fn len(&self) -> usize { + self.inner.length / 32 + } + + pub fn get(&self, idx: usize) -> FixHandle { + let mut buffer = [0u8; 32]; + self.inner.get(idx * 32, &mut buffer); + FixHandle::unpack(buffer) + } +} + +impl From for RawData { + fn from(val: TreeData) -> Self { + val.inner + } +} + +impl From for TreeData { + fn from(value: RawData) -> Self { + Self { inner: value } + } +} diff --git a/fix/runtime/src/fixruntime.rs b/fix/runtime/src/fixruntime.rs new file mode 100644 index 0000000..f153f82 --- /dev/null +++ b/fix/runtime/src/fixruntime.rs @@ -0,0 +1,107 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] + +use crate::{ + bottom::FixShellBottom, + // data::{BlobData, TreeData}, + runtime::{DeterministicEquivRuntime, Executor}, + storage::{ObjectStore, Storage}, +}; +use bytemuck::bytes_of; +use derive_more::TryUnwrapError; +use fixhandle::rawhandle::{FixHandle, Object}; +use kernel::types::{Blob, Tuple}; + +#[derive(Debug)] +pub enum Error { + OOB, + TypeMismatch, +} + +impl From> for Error { + fn from(_value: TryUnwrapError) -> Self { + Error::TypeMismatch + } +} + +#[derive(Debug)] +pub struct FixRuntime<'a> { + store: &'a mut ObjectStore, +} + +impl<'a> FixRuntime<'a> { + pub fn new(store: &'a mut ObjectStore) -> Self { + Self { store } + } +} + +impl<'a> DeterministicEquivRuntime for FixRuntime<'a> { + type BlobData = Blob; + type TreeData = Tuple; + type Handle = FixHandle; + type Error = Error; + + fn create_blob_i64(&mut self, data: u64) -> Self::Handle { + let buf = bytes_of(&data); + Object::from(self.store.create_blob(buf.into())).into() + } + + fn create_blob(&mut self, data: Blob) -> Self::Handle { + Object::from(self.store.create_blob(data)).into() + } + + fn create_tree(&mut self, data: Tuple) -> Self::Handle { + Object::from(self.store.create_tree(data)).into() + } + + fn get_blob(&self, handle: &Self::Handle) -> Result { + let b = handle + .try_unwrap_object_ref() + .map_err(Error::from)? + .try_unwrap_blob_name_ref() + .map_err(|_| Error::TypeMismatch)?; + Ok(self.store.get_blob(b)) + } + + fn get_tree(&self, handle: &Self::Handle) -> Result { + let t = handle + .try_unwrap_object_ref() + .map_err(Error::from)? + .try_unwrap_tree_name_ref() + .map_err(Error::from)?; + Ok(self.store.get_tree(t)) + } + + fn is_blob(handle: &Self::Handle) -> bool { + handle + .try_unwrap_object_ref() + .map_err(Error::from) + .and_then(|h| h.try_unwrap_blob_name_ref().map_err(Error::from)) + .is_ok() + || handle + .try_unwrap_ref_ref() + .map_err(Error::from) + .and_then(|h| h.try_unwrap_blob_name_ref().map_err(Error::from)) + .is_ok() + } + + fn is_tree(handle: &Self::Handle) -> bool { + handle + .try_unwrap_object_ref() + .map_err(Error::from) + .and_then(|h| h.try_unwrap_tree_name_ref().map_err(Error::from)) + .is_ok() + || handle + .try_unwrap_ref_ref() + .map_err(Error::from) + .and_then(|h| h.try_unwrap_tree_name_ref().map_err(Error::from)) + .is_ok() + } +} + +impl<'a> Executor for FixRuntime<'a> { + fn execute(&mut self, combination: &FixHandle) -> FixHandle { + let mut bottom = FixShellBottom { parent: self }; + bottom.execute(combination) + } +} diff --git a/fix/runtime/src/lib.rs b/fix/runtime/src/lib.rs new file mode 100644 index 0000000..5c9bf67 --- /dev/null +++ b/fix/runtime/src/lib.rs @@ -0,0 +1,8 @@ +#![no_std] +#![allow(dead_code)] + +pub mod bottom; +// pub mod data; +pub mod fixruntime; +pub mod runtime; +pub mod storage; diff --git a/fix/runtime/src/runtime.rs b/fix/runtime/src/runtime.rs new file mode 100644 index 0000000..8313a1c --- /dev/null +++ b/fix/runtime/src/runtime.rs @@ -0,0 +1,29 @@ +use core::clone::Clone; +use core::result::Result; + +use fixhandle::rawhandle::FixHandle; + +pub trait DeterministicEquivRuntime { + type BlobData: Clone + core::fmt::Debug; + type TreeData: Clone + core::fmt::Debug; + type Handle: Clone + core::fmt::Debug; + type Error; + + fn create_blob_i64(&mut self, data: u64) -> Self::Handle; + fn create_blob(&mut self, data: Self::BlobData) -> Self::Handle; + fn create_tree(&mut self, data: Self::TreeData) -> Self::Handle; + + fn get_blob(&self, handle: &Self::Handle) -> Result; + fn get_tree(&self, handle: &Self::Handle) -> Result; + + fn is_blob(handle: &Self::Handle) -> bool; + fn is_tree(handle: &Self::Handle) -> bool; +} + +pub trait ExecutionRuntime: DeterministicEquivRuntime { + fn request_execution(&mut self, combination: &Self::Handle) -> Result<(), Self::Error>; +} + +pub trait Executor { + fn execute(&mut self, combination: &FixHandle) -> FixHandle; +} diff --git a/fix/runtime/src/storage.rs b/fix/runtime/src/storage.rs new file mode 100644 index 0000000..bca0604 --- /dev/null +++ b/fix/runtime/src/storage.rs @@ -0,0 +1,100 @@ +// use crate::data::{BlobData, RawData, TreeData}; +use fixhandle::rawhandle::{BlobName, Handle, PhysicalHandle, TreeName}; +use kernel::prelude::*; + +#[derive(Debug)] +struct RefCnt { + inner: T, + count: usize, +} + +impl RefCnt { + fn new(inner: T) -> Self { + Self { inner, count: 0 } + } +} + +#[derive(Debug)] +struct RawObjectStore { + table: Vec>, +} + +impl Default for RawObjectStore { + fn default() -> Self { + Self { table: vec![] } + } +} + +impl RawObjectStore { + fn new() -> Self { + Self::default() + } + + fn create(&mut self, data: Data) -> usize { + let idx = self.table.len(); + self.table.push(RefCnt::new(data)); + idx + } + + fn get(&self, idx: usize) -> Data { + self.table[idx].inner.clone() + } +} + +pub trait Storage { + fn create_blob(&mut self, data: Blob) -> BlobName; + fn create_tree(&mut self, data: Tuple) -> TreeName; + fn get_blob(&self, handle: &BlobName) -> Blob; + fn get_tree(&self, handle: &TreeName) -> Tuple; +} + +#[derive(Default, Debug)] +pub struct ObjectStore { + store: RawObjectStore, +} + +impl ObjectStore { + pub fn new() -> Self { + Self::default() + } +} + +impl Storage for ObjectStore { + fn create_blob(&mut self, data: Blob) -> BlobName { + let len = data.len(); + let local_id = self.store.create(data.into()); + BlobName::Blob(Handle::PhysicalHandle(PhysicalHandle::new(local_id, len))) + } + + fn create_tree(&mut self, data: Tuple) -> TreeName { + let len = data.len(); + let local_id = self.store.create(data.into()); + TreeName::NotTag(Handle::PhysicalHandle(PhysicalHandle::new(local_id, len))) + } + + fn get_blob(&self, handle: &BlobName) -> Blob { + match handle { + BlobName::Blob(h) => match h { + Handle::VirtualHandle(_) => todo!(), + Handle::PhysicalHandle(physical_handle) => self + .store + .get(physical_handle.local_id()) + .try_into() + .unwrap(), + }, + } + } + + fn get_tree(&self, handle: &TreeName) -> Tuple { + match handle { + TreeName::NotTag(t) | TreeName::Tag(t) => match t { + Handle::VirtualHandle(_) => todo!(), + Handle::PhysicalHandle(physical_handle) => self + .store + .get(physical_handle.local_id()) + .try_into() + .unwrap(), + }, + } + } +} diff --git a/fix/shell/Cargo.toml b/fix/shell/Cargo.toml new file mode 100644 index 0000000..069a816 --- /dev/null +++ b/fix/shell/Cargo.toml @@ -0,0 +1,23 @@ +cargo-features = ["per-package-target"] + +[package] +name = "fixshell" +version = "0.1.0" +edition = "2024" +forced-target = "x86_64-unknown-none" + +[lib] +name = "fixshell" +crate-type = ["staticlib"] + +[dependencies] +arca = { path = "../../arca" } +user = { path = "../../user" } +arcane = { path = "../../arcane/"} +fixhandle = { path = "../handle", default-features = false} +alloca = "0.4.0" + +[build-dependencies] +anyhow = "1.0.100" +bindgen = "0.72.1" +cc = "1.2.57" diff --git a/fix/shell/build.rs b/fix/shell/build.rs new file mode 100644 index 0000000..242a365 --- /dev/null +++ b/fix/shell/build.rs @@ -0,0 +1,25 @@ +use std::{env, path::PathBuf}; + +use anyhow::Result; + +fn main() -> Result<()> { + println!("cargo::rerun-if-changed=etc/memmap.ld"); + let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + println!("cargo::rustc-link-arg=-T{dir}/etc/memmap.ld"); + println!("cargo::rustc-link-arg=-no-pie"); + println!("cargo::rustc-link-arg=-no-pic"); + + let bindings = bindgen::Builder::default() + .header("inc/wasm-rt.h") + .use_core() + .ignore_functions() + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .expect("Unable to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + bindings + .write_to_file(out_path.join("wasm_rt.rs")) + .expect("Couldn't write bindings!"); + Ok(()) +} diff --git a/fix/fix-shell/memmap.ld b/fix/shell/etc/memmap.ld similarity index 98% rename from fix/fix-shell/memmap.ld rename to fix/shell/etc/memmap.ld index 6e466de..c2cc57a 100644 --- a/fix/fix-shell/memmap.ld +++ b/fix/shell/etc/memmap.ld @@ -28,6 +28,7 @@ SECTIONS { . = ALIGN(16); _sgot = .; *(.got*) + *(.got.plt) . = ALIGN(16); _egot = .; } > memory diff --git a/fix/shell/etc/wasm-rt.c b/fix/shell/etc/wasm-rt.c new file mode 100644 index 0000000..fde5deb --- /dev/null +++ b/fix/shell/etc/wasm-rt.c @@ -0,0 +1,18 @@ +#include + +#include "wasm-rt.h" +#include "module.h" + +void wasm_rt_init(void) { +} + +bool wasm_rt_is_initialized(void) { + return true; +} + +void wasm_rt_free(void) { +} + +size_t wasm_rt_module_size(void) { + return sizeof(w2c_module); +} diff --git a/fix/fix-shell/wasm-rt-impl.h b/fix/shell/inc/wasm-rt-impl.h similarity index 100% rename from fix/fix-shell/wasm-rt-impl.h rename to fix/shell/inc/wasm-rt-impl.h diff --git a/fix/fix-shell/wasm-rt.h b/fix/shell/inc/wasm-rt.h similarity index 98% rename from fix/fix-shell/wasm-rt.h rename to fix/shell/inc/wasm-rt.h index 9557513..255ae83 100644 --- a/fix/fix-shell/wasm-rt.h +++ b/fix/shell/inc/wasm-rt.h @@ -129,10 +129,13 @@ typedef struct { static const wasm_rt_funcref_t wasm_rt_funcref_null_value; /** The type of an external reference (opaque to WebAssembly). */ -typedef int64_t wasm_rt_externref_t; +typedef unsigned char __attribute__((vector_size(32))) u8x32; +typedef struct { + uint8_t bytes[32]; +} wasm_rt_externref_t; /** Default (null) value of an externref */ -static const wasm_rt_externref_t wasm_rt_externref_null_value = 0; +static const wasm_rt_externref_t wasm_rt_externref_null_value = {0}; /** A Memory object. */ typedef struct { diff --git a/fix/shell/src/fixpoint.rs b/fix/shell/src/fixpoint.rs new file mode 100644 index 0000000..4c71c8b --- /dev/null +++ b/fix/shell/src/fixpoint.rs @@ -0,0 +1,57 @@ +use core::ffi::c_void; + +use crate::rt::{PAGE_SIZE, wasm_rt_externref_t, wasm_rt_externref_table_t, wasm_rt_memory_t}; +use crate::shell; + +#[repr(C)] +pub struct w2c_fixpoint(()); + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_attach_blob( + fixpoint: *mut w2c_fixpoint, + memory_idx: u32, + handle: wasm_rt_externref_t, +) { + assert!(memory_idx < 64); + unsafe { + let memory = crate::rt::MEMORIES[memory_idx as usize]; + if (memory.is_null()) { + return; + } + 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).max_pages = (1u64 << 32) / PAGE_SIZE as u64; + (*memory).size = len as u64; + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_attach_tree( + fixpoint: *mut w2c_fixpoint, + table_idx: u32, + handle: wasm_rt_externref_t, +) { + assert!(table_idx < 63); + unsafe { + let table = crate::rt::TABLES[table_idx as usize]; + if (table.is_null()) { + return; + } + let addr = (1usize << 32) * (64 + table_idx as usize); + let len = shell::fixpoint_attach_tree(addr as *mut c_void, handle.bytes); + (*table).size = len as u32; + (*table).max_size = (1 << (32 - 5)) as u32; + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn w2c_fixpoint_create_blob_i64( + fixpoint: *mut w2c_fixpoint, + value: u64, +) -> wasm_rt_externref_t { + wasm_rt_externref_t { + bytes: unsafe { shell::fixpoint_create_blob_i64(value) }, + } +} diff --git a/fix/shell/src/lib.rs b/fix/shell/src/lib.rs new file mode 100644 index 0000000..2e90ed0 --- /dev/null +++ b/fix/shell/src/lib.rs @@ -0,0 +1,97 @@ +#![no_std] +#![allow(unused)] +#![feature(slice_from_ptr_range)] +#![feature(atomic_ptr_null)] + +use core::{ + arch::{asm, global_asm}, + ffi::c_void, + ops::Range, +}; + +use user::{error, os, prelude::*}; + +use crate::{ + fixpoint::w2c_fixpoint, + rt::{wasm_rt_externref_t, wasm_rt_free, wasm_rt_init, wasm_rt_module_size}, +}; + +mod fixpoint; +mod rt; +mod runtime; +pub mod shell; + +global_asm!( + r#" +.section .text.start +.extern _rsstart +.extern __stack_top +.globl _start +_start: + lea rsp, __stack_top[rip] + call _rsstart +.halt: + int3 + jmp .halt +.globl bail +bail: + mov rdi, 0 + mov rax, 3 + syscall + int3 +.section .text +"# +); + +unsafe extern "C" { + static mut _sbss: c_void; + static mut _ebss: c_void; + fn wasm2c_module_instantiate(module: *mut c_void, combination: *const w2c_fixpoint); + fn wasm2c_module_free(module: *mut c_void); + fn w2c_module_0x5Ffixpoint_apply( + module: *const c_void, + combination: wasm_rt_externref_t, + ) -> wasm_rt_externref_t; +} + +/// The Rust entrypoint for Fix-on-Arca programs. This function performs the minimal runtime setup +/// to ensure other Rust code can run correctly. +/// +/// # Safety +/// +/// This function must be called exactly once, before any other Rust code has run. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _rsstart() -> ! { + unsafe { + let bss = core::slice::from_mut_ptr_range(Range { + start: &raw mut _sbss as *mut u8, + end: &raw mut _ebss as *mut u8, + }); + + bss.fill(0); + } + + main(); +} + +pub fn main() -> ! { + let combination = os::argument(); + let combination = + Blob::try_from(combination).expect("fix programs must receive a handle as input"); + let mut handle = [0; 32]; + combination.read(0, &mut handle); + let result = unsafe { + wasm_rt_init(); + let module_size = wasm_rt_module_size(); + let result = alloca::with_alloca_zeroed(module_size, |module_buf| { + let module = &raw mut module_buf[0] as *mut c_void; + wasm2c_module_instantiate(module, core::ptr::null()); + let wasm_rt_externref_t { bytes: result } = + w2c_module_0x5Ffixpoint_apply(module, wasm_rt_externref_t { bytes: handle }); + result + }); + wasm_rt_free(); + result + }; + os::exit(&result[..]); +} diff --git a/fix/shell/src/rt.rs b/fix/shell/src/rt.rs new file mode 100644 index 0000000..239056f --- /dev/null +++ b/fix/shell/src/rt.rs @@ -0,0 +1,148 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] + +use core::sync::atomic::{AtomicUsize, Ordering}; + +use arcane::{__MODE_read_write, arca_compat_mmap}; +use user::error; + +include!(concat!(env!("OUT_DIR"), "/wasm_rt.rs")); + +unsafe extern "C" { + pub fn wasm_rt_init(); + pub fn wasm_rt_module_size() -> usize; + pub fn wasm_rt_free(); +} + +pub static mut MEMORY_IDX: usize = 0; +pub static mut TABLE_IDX: usize = 0; + +pub static mut MEMORIES: [*mut wasm_rt_memory_t; 64] = [core::ptr::null_mut(); 64]; +pub static mut TABLES: [*mut wasm_rt_externref_table_t; 64] = [core::ptr::null_mut(); 64]; + +/** + * Initialize a Memory object with an initial page size of `initial_pages` and + * a maximum page size of `max_pages`, indexed with an i32 or i64. + * + * ``` + * wasm_rt_memory_t my_memory; + * // 1 initial page (65536 bytes), and a maximum of 2 pages, + * // indexed with an i32 + * wasm_rt_allocate_memory(&my_memory, 1, 2, false); + * ``` + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_allocate_memory( + memory: *mut wasm_rt_memory_t, + initial_pages: u64, + max_pages: u64, + is64: bool, +) { + unsafe { + let idx = MEMORY_IDX; + MEMORY_IDX += 1; + assert!(idx < 64); + MEMORIES[idx] = memory; + assert!(!is64); + assert!(max_pages <= (1u64 << 32) / PAGE_SIZE as u64); + let data = ((1 << 32) * idx) as *mut u8; + let size = initial_pages * PAGE_SIZE as u64; + arca_compat_mmap(data as *mut _, size as usize, __MODE_read_write); + memory.write(wasm_rt_memory_t { + data, + pages: initial_pages, + max_pages, + size, + is64, + }); + } +} + +/** + * Grow a Memory object by `pages`, and return the previous page count. If + * this new page count is greater than the maximum page count, the grow fails + * and 0xffffffffu (UINT32_MAX) is returned instead. + * + * ``` + * wasm_rt_memory_t my_memory; + * ... + * // Grow memory by 10 pages. + * uint32_t old_page_size = wasm_rt_grow_memory(&my_memory, 10); + * if (old_page_size == UINT32_MAX) { + * // Failed to grow memory. + * } + * ``` + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_grow_memory(memory: *mut wasm_rt_memory_t, pages: u64) -> u32 { + let memory = unsafe { &mut *memory }; + let current = memory.pages; + if current + pages >= memory.max_pages { + return u32::MAX; + } + let start = unsafe { memory.data.byte_add(current as usize * PAGE_SIZE as usize) }; + let size = pages * PAGE_SIZE as u64; + unsafe { + arca_compat_mmap(start as *mut _, size as usize, __MODE_read_write); + memory.pages += pages; + memory.size += size; + } + current as u32 +} + +/** + * Initialize an externref Table object with an element count + * of `elements` and a maximum size of `max_elements`. + * Usage as per wasm_rt_allocate_funcref_table. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_allocate_externref_table( + table: *mut wasm_rt_externref_table_t, + elements: u32, + mut max_elements: u32, +) { + unsafe { + let idx = TABLE_IDX; + TABLE_IDX += 1; + assert!(idx < 63); + TABLES[idx] = table; + if max_elements > (1 << (32 - 5)) { + 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); + table.write(wasm_rt_externref_table_t { + data: data as *mut _, + size: elements, + max_size: max_elements, + }); + } +} + +/** + * Free a Memory object. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_free_memory(memory: *mut wasm_rt_memory_t) { + todo!(); +} + +/** + * Free an externref Table object. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_free_externref_table(table: *mut wasm_rt_externref_table_t) { + todo!(); +} + +/** + * Stop execution immediately and jump back to the call to `wasm_rt_impl_try`. + * The result of `wasm_rt_impl_try` will be the provided trap reason. + * + * This is typically called by the generated code, and not the embedder. + */ +#[unsafe(no_mangle)] +pub extern "C" fn wasm_rt_trap(trap: wasm_rt_trap_t) { + panic!("wasm rt trap: {trap}"); +} diff --git a/fix/shell/src/runtime.rs b/fix/shell/src/runtime.rs new file mode 100644 index 0000000..7420c13 --- /dev/null +++ b/fix/shell/src/runtime.rs @@ -0,0 +1,22 @@ +use core::clone::Clone; +use core::result::Result; + +use fixhandle::rawhandle::FixHandle; + +pub trait DeterministicEquivRuntime { + type BlobData: Clone + core::fmt::Debug; + type TreeData: Clone + core::fmt::Debug; + type Handle: Clone + core::fmt::Debug; + type Error; + + fn create_blob_i64(data: u64) -> Self::Handle; + fn create_blob(data: Self::BlobData) -> Self::Handle; + fn create_tree(data: Self::TreeData) -> Self::Handle; + + fn get_blob(handle: Self::Handle) -> Result; + fn get_tree(handle: Self::Handle) -> Result; + + fn is_blob(handle: Self::Handle) -> bool; + fn is_tree(handle: Self::Handle) -> bool; + fn len(handle: Self::Handle) -> usize; +} diff --git a/fix/shell/src/shell.rs b/fix/shell/src/shell.rs new file mode 100644 index 0000000..1207ace --- /dev/null +++ b/fix/shell/src/shell.rs @@ -0,0 +1,239 @@ +use crate::runtime::DeterministicEquivRuntime; +use arca::{Blob, Function, Table}; +use arca::{Runtime as _, Tuple}; +use arcane::{ + __MODE_read_only, __MODE_read_write, __NR_length, __TYPE_table, arca_argument, + arca_blob_create, arca_blob_read, arca_compat_mmap, arca_entry, arca_mmap, arca_table_map, + arcad, +}; + +use core::arch::x86_64::*; +use core::ffi::c_void; +use fixhandle::rawhandle::{BitPack, FixHandle, Handle}; +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; + +// FixShell top-half that only handles physical handles +#[derive(Debug, Default)] +struct FixShellPhysical; +// FixShell top-half + +#[derive(Debug, Default)] +struct FixShell; + +impl DeterministicEquivRuntime for FixShellPhysical { + type BlobData = Blob; + type TreeData = Tuple; + type Handle = [u8; 32]; + type Error = ArcaError; + + fn create_blob_i64(data: u64) -> Self::Handle { + let result: Blob = Function::symbolic("create_blob_i64") + .apply(data) + .call_with_current_continuation() + .try_into() + .expect("create_blob_i64 failed"); + let mut buf = [0u8; 32]; + Runtime::read_blob(&result, 0, &mut buf); + buf + } + + fn create_blob(data: Self::BlobData) -> Self::Handle { + let result: Blob = Function::symbolic("create_blob") + .apply(data) + .call_with_current_continuation() + .try_into() + .expect("create_blob failed"); + let mut buf = [0u8; 32]; + Runtime::read_blob(&result, 0, &mut buf); + buf + } + + fn create_tree(data: Self::TreeData) -> Self::Handle { + let result: Blob = Function::symbolic("create_tree") + .apply(data) + .call_with_current_continuation() + .try_into() + .expect("create_tree failed"); + let mut buf = [0u8; 32]; + Runtime::read_blob(&result, 0, &mut buf); + buf + } + + fn get_blob(handle: Self::Handle) -> Result { + let result: Blob = Function::symbolic("get_blob") + .apply(Runtime::create_blob(&handle)) + .call_with_current_continuation() + .try_into() + .map_err(|_| ArcaError::BadType)?; + Ok(result) + } + + fn get_tree(handle: Self::Handle) -> Result { + let result: Tuple = Function::symbolic("get_tree") + .apply(Runtime::create_blob(&handle)) + .call_with_current_continuation() + .try_into() + .map_err(|_| ArcaError::BadType)?; + Ok(result) + } + + fn is_blob(handle: Self::Handle) -> bool { + let handle = FixHandle::unpack(handle); + handle + .try_unwrap_object_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_blob_name_ref().map_err(|_| ArcaError::BadType)) + .is_ok() + || handle + .try_unwrap_ref_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_blob_name_ref().map_err(|_| ArcaError::BadType)) + .is_ok() + } + + fn is_tree(handle: Self::Handle) -> bool { + let handle = FixHandle::unpack(handle); + + handle + .try_unwrap_object_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_tree_name_ref().map_err(|_| ArcaError::BadType)) + .is_ok() + || handle + .try_unwrap_ref_ref() + .map_err(|_| ArcaError::BadType) + .and_then(|h| h.try_unwrap_tree_name_ref().map_err(|_| ArcaError::BadType)) + .is_ok() + } + + fn len(handle: Self::Handle) -> usize { + let handle = FixHandle::unpack(handle); + let len = handle + .try_unwrap_object_ref() + .map_err(|_| ArcaError::BadType) + .map(|h| { + let h: &Handle = match h { + fixhandle::rawhandle::Object::BlobName(blob_name) => { + blob_name.unwrap_blob_ref() + } + fixhandle::rawhandle::Object::TreeName(tree_name) => match tree_name { + fixhandle::rawhandle::TreeName::NotTag(handle) => handle, + fixhandle::rawhandle::TreeName::Tag(handle) => handle, + }, + }; + match h { + Handle::VirtualHandle(virtual_handle) => virtual_handle.len(), + Handle::PhysicalHandle(physical_handle) => physical_handle.len(), + } + }); + len.expect("len: failed to get size") + } +} + +impl DeterministicEquivRuntime for FixShell { + type BlobData = Blob; + type TreeData = Tuple; + type Handle = [u8; 32]; + type Error = ArcaError; + + fn create_blob_i64(data: u64) -> Self::Handle { + FixShellPhysical::create_blob_i64(data) + } + + fn create_blob(data: Self::BlobData) -> Self::Handle { + FixShellPhysical::create_blob(data) + } + + fn create_tree(data: Self::TreeData) -> Self::Handle { + FixShellPhysical::create_tree(data) + } + + fn get_blob(handle: Self::Handle) -> Result { + FixShellPhysical::get_blob(handle) + } + + fn get_tree(handle: Self::Handle) -> Result { + FixShellPhysical::get_tree(handle) + } + + fn is_blob(handle: Self::Handle) -> bool { + FixShellPhysical::is_blob(handle) + } + + fn is_tree(handle: Self::Handle) -> bool { + FixShellPhysical::is_tree(handle) + } + + fn len(handle: Self::Handle) -> usize { + FixShellPhysical::len(handle) + } +} + +pub fn fixpoint_create_blob_i64(val: u64) -> [u8; 32] { + FixShell::create_blob_i64(val) +} + +/// Attaches a blob to a region of memory. Returns the size (in bytes) of the mapped blob. +/// +/// # Safety +/// +/// [addr] must refer to an unused region of memory which is large enough to fit the blob; there +/// must be no Rust references pointing to this region. +pub unsafe fn fixpoint_attach_blob(addr: *mut c_void, handle: [u8; 32]) -> usize { + if (!FixShell::is_blob(handle)) { + arca_log("attach_blob: handle does not refer to a BlobObject"); + panic!() + } + + let result = FixShell::get_blob(handle); + + let Ok(blob) = result else { + arca_log("attach_blob: failed to get BlobData"); + panic!() + }; + let len = FixShell::len(handle); + + unsafe { + arca_compat_mmap(addr, len, __MODE_read_write); + blob.read(0, core::slice::from_raw_parts_mut(addr as *mut u8, len)); + }; + user::error::log_int("attached memory", len as u64); + len +} + +/// Attaches a tree to a region of memory. Returns the size (in elements) of the tree. +/// +/// # Safety +/// +/// [addr] must refer to an unused region of memory which is large enough to fit the tree; there +/// must be no Rust references pointing to this region. Each entry of the tree takes 32 bytes. +pub unsafe fn fixpoint_attach_tree(addr: *mut c_void, handle: [u8; 32]) -> usize { + if (!FixShell::is_tree(handle)) { + arca_log("attach_tree: handle does not refer to a TreeObject"); + panic!() + } + + let result = FixShell::get_tree(handle); + + let Ok(tree) = result else { + arca_log("attach_tree: failed to get TreeData"); + panic!() + }; + + let len = FixShell::len(handle); + user::error::log_int("attached tree", len as u64); + + unsafe { + arca_compat_mmap(addr, len * 32, __MODE_read_write); + let slice = core::slice::from_raw_parts_mut(addr as *mut u8, len * 32); + for i in 0..len { + let element: Blob = tree.get(i).try_into().unwrap(); + element.read(0, &mut slice[i * 32..(i + 1) * 32]); + } + }; + len +} diff --git a/fix/src/main.rs b/fix/src/main.rs index eaf3406..38dc29c 100644 --- a/fix/src/main.rs +++ b/fix/src/main.rs @@ -1,32 +1,62 @@ #![no_main] #![no_std] -#![feature(try_blocks)] -#![feature(try_trait_v2)] -#![feature(iterator_try_collect)] -#![feature(box_patterns)] -#![feature(never_type)] +// #![feature(iterator_try_collect)] +// #![feature(custom_test_frameworks)] +#![cfg_attr(feature = "testing-mode", test_runner(crate::testing::test_runner))] +#![cfg_attr(feature = "testing-mode", reexport_test_harness_main = "test_main")] #![allow(dead_code)] -use arca::Runtime; use kernel::prelude::*; +#[cfg(feature = "testing-mode")] +mod testing; + +use common::bitpack::BitPack; + +use fixruntime::{ + fixruntime::FixRuntime, + runtime::{DeterministicEquivRuntime, Executor}, + storage::ObjectStore, +}; + extern crate alloc; +//use crate::runtime::handle; + const MODULE: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/addblob")); #[kmain] async fn main(_: &[usize]) { - let f = common::elfloader::load_elf(MODULE).expect("Failed to load elf"); - let mut tree = Runtime::create_tuple(4); - let dummy = Runtime::create_word(0xcafeb0ba); - - tree.set(0, dummy); - tree.set(1, dummy); - tree.set(2, Runtime::create_word(7)); - tree.set(3, Runtime::create_word(1024)); - - let f = Runtime::apply_function(f, arca::Value::Tuple(tree)); - let word: Word = f.force().try_into().unwrap(); - log::info!("{:?}", word.read()); - assert_eq!(word.read(), 1031); + log::info!("creating object store"); + let mut store = ObjectStore::new(); + log::info!("creating fix runtime"); + let mut runtime = FixRuntime::new(&mut store); + + log::info!("creating resource limits"); + let dummy = runtime.create_blob_i64(0xcafeb0ba); + log::info!("creating function"); + let function = runtime.create_blob(MODULE.into()); + log::info!("creating addend 1"); + let addend1 = runtime.create_blob_i64(7); + log::info!("creating addend 2"); + let addend2 = runtime.create_blob_i64(1024); + + let mut scratch = Tuple::new(4); + scratch.set(0, Blob::new(dummy.pack())); + scratch.set(1, Blob::new(function.pack())); + scratch.set(2, Blob::new(addend1.pack())); + scratch.set(3, Blob::new(addend2.pack())); + log::info!("creating combination"); + let combination = runtime.create_tree(scratch); + log::info!("about to execute combination"); + let result = runtime.execute(&combination); + log::info!("result is: {result:?}"); + let result_blob = runtime + .get_blob(&result) + .expect("Add did not return a Blob"); + let mut arr = [0u8; 8]; + result_blob.read(0, &mut arr); + let num = u64::from_le_bytes(arr); + log::info!("{:?}", num); + assert_eq!(num, 1031); } diff --git a/fix/src/testing.rs b/fix/src/testing.rs new file mode 100644 index 0000000..26aecae --- /dev/null +++ b/fix/src/testing.rs @@ -0,0 +1,5 @@ +pub fn test_runner(tests: &[&dyn Fn()]) { + for test in tests { + test(); + } +} diff --git a/fix/wasm/addblob.wat b/fix/wasm/addblob.wat index 18c9bbe..8f654e2 100644 --- a/fix/wasm/addblob.wat +++ b/fix/wasm/addblob.wat @@ -1,17 +1,16 @@ (module (import "fixpoint" "create_blob_i64" (func $create_blob_i64 (param i64) (result externref))) (import "fixpoint" "attach_blob" (func $attach_blob (param i32) (param externref))) - (import "fixpoint" "get_tree_entry" (func $get_tree_entry (param externref) (param i32) (result externref))) - ;; memories intended for rw-usage + (import "fixpoint" "attach_tree" (func $attach_tree (param i32) (param externref))) (memory $mem_0 1) (memory $mem_1 0) (memory $mem_2 0) + (table $tab_0 0 externref) (func (export "_fixpoint_apply") (param $encode externref) (result externref) - ;; getting an entry of a tree multiple times - (call $get_tree_entry - (local.get $encode) - (i32.const 2)) - drop + ;; attach combination tree + (call $attach_tree + (i32.const 0) + (local.get $encode)) ;; grow rw-memory (memory.grow (memory $mem_0) @@ -19,14 +18,10 @@ drop (call $attach_blob (i32.const 1) - (call $get_tree_entry - (local.get $encode) - (i32.const 2))) + (table.get $tab_0 (i32.const 2))) (call $attach_blob (i32.const 2) - (call $get_tree_entry - (local.get $encode) - (i32.const 3))) + (table.get $tab_0 (i32.const 3))) ;; write to rw-memory (i64.store (memory $mem_0) (i32.const 0) diff --git a/kernel/src/cpu.rs b/kernel/src/cpu.rs index e62bea4..ea3919d 100644 --- a/kernel/src/cpu.rs +++ b/kernel/src/cpu.rs @@ -152,7 +152,7 @@ pub struct ExitStatus { pub error: u64, } -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum ExitReason { DivisionByZero, Debug, diff --git a/kernel/src/lapic.rs b/kernel/src/lapic.rs index c2b5636..35a06a1 100644 --- a/kernel/src/lapic.rs +++ b/kernel/src/lapic.rs @@ -138,7 +138,8 @@ pub unsafe fn init() { lapic.set_spurious_interrupt_vector(0xff); lapic.set_apic_enabled(true); - lapic.set_divide_configuration(TimerDivider::One); + // TODO: why is this crashing? + // lapic.set_divide_configuration(TimerDivider::One); lapic.set_timer( TimerConfig::new() .with_mask(false) diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index eae2409..b5831ed 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -3,13 +3,9 @@ #![feature(allocator_api)] #![feature(widening_mul)] #![feature(box_as_ptr)] -#![feature(box_into_inner)] -#![feature(maybe_uninit_array_assume_init)] -#![feature(maybe_uninit_uninit_array_transpose)] #![feature(negative_impls)] #![feature(never_type)] #![feature(ptr_metadata)] -#![feature(slice_ptr_get)] #![feature(custom_test_frameworks)] #![test_runner(testing::harness)] #![reexport_test_harness_main = "test_main"] diff --git a/kernel/src/types/arca.rs b/kernel/src/types/arca.rs index 534984f..a033ec6 100644 --- a/kernel/src/types/arca.rs +++ b/kernel/src/types/arca.rs @@ -144,6 +144,34 @@ impl<'a> LoadedArca<'a> { unsafe { self.cpu.run(&mut self.register_file) } } + pub fn single_step(&mut self) -> ExitReason { + self.register_file[Register::RFLAGS] |= 0x100; + let result = self.run(); + self.register_file[Register::RFLAGS] &= !0x100; + result + } + + pub fn single_step_with(&mut self, mut f: impl FnMut(&mut Self)) -> ExitReason { + loop { + let step = self.single_step(); + if step == ExitReason::Debug { + f(self); + } else { + return step; + } + } + } + + pub fn trace(&mut self) -> ExitReason { + self.single_step_with(|this| { + log::info!( + "@{:#x} -> {:#x?}", + this.register_file[Register::RIP], + this.register_file + ); + }) + } + pub fn registers(&self) -> &RegisterFile { &self.register_file } diff --git a/kernel/src/types/function/syscall.rs b/kernel/src/types/function/syscall.rs index 58bfb29..baa2370 100644 --- a/kernel/src/types/function/syscall.rs +++ b/kernel/src/types/function/syscall.rs @@ -76,7 +76,7 @@ pub fn handle_syscall(arca: &mut LoadedArca, argv: &mut VecDeque) -> Cont _ => { log::error!("invalid syscall {num}"); - panic!("invalid syscall @ {:#x}", regs[Register::RIP]); + panic!("invalid syscall @ {:#x} ({args:#x?})", regs[Register::RIP]); // Err(SyscallError::BadSyscall) } }; @@ -486,7 +486,7 @@ pub fn sys_compat_mmap(args: [u64; 6], arca: &mut LoadedArca) -> Result { p += Page2MB::SIZE; continue; } - if p.is_multiple_of(Page4KB::SIZE) && len >= Page4KB::SIZE { + if p.is_multiple_of(Page4KB::SIZE) { if mode == arcane::__MODE_none { let entry = Entry::Null(Page4KB::SIZE); arca.cpu().map(p, entry).unwrap(); @@ -498,7 +498,7 @@ pub fn sys_compat_mmap(args: [u64; 6], arca: &mut LoadedArca) -> Result { p += Page4KB::SIZE; continue; } - panic!("unaligned mmap or bad size"); + panic!("unaligned mmap or bad size: {p:#x}+{len:#x}"); } Ok(p - addr) } diff --git a/kernel/src/types/runtime.rs b/kernel/src/types/runtime.rs index 691a297..6d19783 100644 --- a/kernel/src/types/runtime.rs +++ b/kernel/src/types/runtime.rs @@ -68,7 +68,6 @@ impl arca::Runtime for Runtime { } fn read_blob(blob: &arca::Blob, offset: usize, buf: &mut [u8]) -> usize { - log::error!("read_blob: offset={}, buf_len={}", offset, buf.len()); let len = core::cmp::min(buf.len(), blob.len() - offset); buf[..len].copy_from_slice(&blob[offset..offset + len]); len diff --git a/kernel/src/types/table.rs b/kernel/src/types/table.rs index 2108dc7..6a4015b 100644 --- a/kernel/src/types/table.rs +++ b/kernel/src/types/table.rs @@ -29,7 +29,7 @@ impl Table { } else if size <= Table512GB::SIZE { Table::Table512GB(Default::default()) } else { - panic!(); + panic!("attempted to create table with size {size}"); } } } diff --git a/macros/Cargo.toml b/macros/Cargo.toml index e7c43a0..266a891 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" proc-macro-crate = "3.1.0" proc-macro2 = "1.0.86" quote = "1.0.36" -syn = "2.0.67" +syn = { version = "2.0.67", features = ["full"] } [lib] proc-macro = true diff --git a/macros/src/bitpack.rs b/macros/src/bitpack.rs new file mode 100644 index 0000000..0878539 --- /dev/null +++ b/macros/src/bitpack.rs @@ -0,0 +1,158 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use proc_macro_crate::{crate_name, FoundCrate}; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Ident}; + +fn common_ident() -> Ident { + let found_crate = crate_name("common").expect("common is present in `Cargo.toml`"); + + match found_crate { + FoundCrate::Itself => format_ident!("crate"), + FoundCrate::Name(name) => Ident::new(&name, Span::call_site()), + } +} + +pub fn bitpack(input: TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = input.ident; + + match input.data { + Data::Enum(de) => bitpack_enum(&name, de), + Data::Struct(_) => compile_error("Unable to create bitpack for struct"), + Data::Union(_) => compile_error("Unable to create bitpack for union"), + } +} + +fn compile_error(msg: &str) -> TokenStream { + syn::Error::new(proc_macro2::Span::call_site(), msg) + .to_compile_error() + .into() +} + +const fn ceil_log2(n: u32) -> u32 { + if n <= 1 { + 0 + } else { + 32 - (n - 1).leading_zeros() + } +} + +struct Variant { + index: u32, + pat: proc_macro2::TokenStream, + construct: proc_macro2::TokenStream, + width: proc_macro2::TokenStream, + unpack: proc_macro2::TokenStream, +} + +fn bitpack_enum(name: &Ident, de: DataEnum) -> TokenStream { + let common = common_ident(); + let mut variants = Vec::new(); + for (index, v) in de.variants.iter().enumerate() { + let ident = v.ident.clone(); + + let ty = match &v.fields { + syn::Fields::Named(fields_named) => { + if fields_named.named.len() != 1 { + return compile_error("Unable to create bitpack for variants not of 1 field"); + } + &fields_named.named.first().unwrap().ty + } + syn::Fields::Unnamed(fields_unnamed) => { + if fields_unnamed.unnamed.len() != 1 { + return compile_error("Unable to create bitpack for variants not of 1 field"); + } + &fields_unnamed.unnamed.first().unwrap().ty + } + syn::Fields::Unit => { + return compile_error("Unable to create bitpack for variants not of 1 field") + } + }; + + let pat = quote! { #name::#ident(inner) }; + let construct = quote! { Self::#ident }; + let width = quote! { #ty::TAGBITS }; + let unpack = quote! { #ty::unpack }; + + variants.push(Variant { + index: index as u32, + pat, + construct, + width, + unpack, + }) + } + + let child_widths = variants.iter().map(|v| &v.width); + let max_child_widths = quote! { + { + let mut m: u32 = 0; + #( { + let w = #child_widths; if w > m { m = w; } + })* + m + } + }; + let curr_width = ceil_log2(variants.len().try_into().unwrap()); + + let tag_bits = quote! { #max_child_widths + #curr_width }; + let tag_mask = quote! { bitmask256::<#max_child_widths, #curr_width>() }; + + let unpack_arms = variants.iter().map(|v| { + let index: u64 = v.index.into(); + let construct = &v.construct; + let unpack = &v.unpack; + quote! { + #index => { #construct( #unpack( content )) } + } + }); + + let pack_arms = variants.iter().map(|v| { + let index = v.index; + let pat = &v.pat; + quote! { + #pat => { + use #common::bitpack::BitPack; + let mut result = inner.pack(); + for i in 0..32 { + result[i] &= !Self::TAGMASK[i]; + } + let field: &mut [u16; 16] = unsafe { core::mem::transmute( &mut result ) }; + field[15] |= (#index << (Self::TAGBITS - 240 - 1)) as u16; + result + } + } + }); + + let output = quote! { + impl #name { + const TAGMASK: [u8; 32] = #tag_mask; + } + + impl #common::bitpack::BitPack for #name { + const TAGBITS: u32 = #tag_bits; + + fn pack(&self) -> [u8; 32] { + match self { + #(#pack_arms)* + } + } + + fn unpack(content: [u8; 32]) -> Self { + let mut tag = content; + for i in 0..32 { + tag[i] &= Self::TAGMASK[i]; + } + let field: &[u16; 16] = unsafe { core::mem::transmute( &tag ) }; + let tag = field[15] >> (Self::TAGBITS - 240 - 1); + match tag as u64 { + #(#unpack_arms)* + _ => todo!() + } + } + + } + }; + output.into() +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 9a79cc4..8ce770b 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,5 +1,6 @@ use proc_macro::TokenStream; +mod bitpack; mod core_local; mod testing; mod util; @@ -33,3 +34,8 @@ pub fn arca_test(_attr: TokenStream, item: TokenStream) -> TokenStream { pub fn kmain(attr: TokenStream, item: TokenStream) -> TokenStream { util::kmain(attr, item) } + +#[proc_macro_derive(BitPack)] +pub fn bitpack(input: TokenStream) -> TokenStream { + bitpack::bitpack(input) +} diff --git a/modules/arca-musl b/modules/arca-musl index a88bc69..a83796a 160000 --- a/modules/arca-musl +++ b/modules/arca-musl @@ -1 +1 @@ -Subproject commit a88bc6999eb736d93a0aab0afe07c99e4e1ec559 +Subproject commit a83796a98c009ea92b9dc47a526b24b818b325b7 diff --git a/user/build.rs b/user/build.rs index 075c69f..0f4a2d4 100644 --- a/user/build.rs +++ b/user/build.rs @@ -6,7 +6,8 @@ fn main() { let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); println!("cargo::rustc-link-arg=-T{dir}/etc/memmap.ld"); - let prefix = autotools::build("../modules/arca-musl") + let prefix = autotools::Config::new("../modules/arca-musl") + .build() .as_os_str() .to_string_lossy() .into_owned(); diff --git a/user/src/lib.rs b/user/src/lib.rs index 86feb60..90ece30 100644 --- a/user/src/lib.rs +++ b/user/src/lib.rs @@ -80,15 +80,15 @@ impl Drop for Ref { } impl Ref { - fn from_raw(idx: u32) -> Self { + pub fn from_raw(idx: u32) -> Self { Ref { idx: Some(idx) } } - fn into_raw(mut self) -> u32 { + pub fn into_raw(mut self) -> u32 { self.idx.take().unwrap() } - fn as_raw(&self) -> u32 { + pub fn as_raw(&self) -> u32 { self.idx.unwrap() } } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index a76cd09..e0413c8 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1,11 +1,8 @@ #![feature(allocator_api)] #![feature(ptr_metadata)] -#![feature(box_into_inner)] #![feature(str_from_raw_parts)] -#![feature(negative_impls)] #![feature(exitcode_exit_method)] #![feature(cstr_display)] -#![feature(test)] pub mod runtime; pub mod vhost; diff --git a/vmm/src/main.rs b/vmm/src/main.rs index c0219cc..245b201 100644 --- a/vmm/src/main.rs +++ b/vmm/src/main.rs @@ -1,7 +1,3 @@ -#![feature(allocator_api)] -#![feature(thread_sleep_until)] -#![feature(future_join)] - use std::path::PathBuf; use clap::Parser; From aea6acb59944af3402b50f6df3c134e9b8033fb8 Mon Sep 17 00:00:00 2001 From: shah-niam <148507696+shah-niam@users.noreply.github.com> Date: Mon, 20 Apr 2026 11:44:28 -0700 Subject: [PATCH 2/4] test: new buddy allocator tests + bug fixes (#26) Added new tests for the buddy allocator and fixed bugs which were uncovered in the process. Co-authored-by: Niam Shah Co-authored-by: Akshay Srivatsan --- arca/src/serde.rs | 2 +- common/src/buddy.rs | 181 +------ common/src/buddy/tests.rs | 951 +++++++++++++++++++++++++++++++++ common/src/lib.rs | 1 + kernel/Cargo.toml | 3 +- kernel/src/lib.rs | 5 +- kernel/src/tests/test_serde.rs | 209 ++++---- kernel/src/types/blob.rs | 37 ++ kernel/src/types/function.rs | 48 ++ kernel/src/types/null.rs | 11 + kernel/src/types/page.rs | 35 ++ kernel/src/types/table.rs | 33 ++ kernel/src/types/tuple.rs | 25 + kernel/src/types/value.rs | 30 ++ kernel/src/types/word.rs | 28 + modules/arca-musl | 2 +- vmm/src/lib.rs | 1 + 17 files changed, 1336 insertions(+), 266 deletions(-) create mode 100644 common/src/buddy/tests.rs diff --git a/arca/src/serde.rs b/arca/src/serde.rs index 23257df..a1ef5d3 100644 --- a/arca/src/serde.rs +++ b/arca/src/serde.rs @@ -333,7 +333,7 @@ impl<'de, R: Runtime> Visitor<'de> for TableVisitor { where A: serde::de::MapAccess<'de>, { - let (first_key, first_value): (alloc::string::String, usize) = + let (first_key, first_value): (&str, usize) = map.next_entry()?.expect("at least one element needed"); assert_eq!(first_key, "len"); let mut table = Table::new(first_value); diff --git a/common/src/buddy.rs b/common/src/buddy.rs index 0acd595..cec5b17 100644 --- a/common/src/buddy.rs +++ b/common/src/buddy.rs @@ -413,6 +413,13 @@ impl AllocatorInner { size: 1 << size_log2, }); } + // Check if index is within valid range for this level + if index >= self.size_of_level_bits(size_log2) { + return Err(AllocationError::InvalidReservation { + index, + size: 1 << size_log2, + }); + } self.with_level(base, size_log2, |level: &mut AllocatorLevel<'_>| { if level.reserve(index) { Ok(index) @@ -553,12 +560,15 @@ impl BuddyAllocatorImpl { // prevent physical zero page from being allocated assert_eq!(temp.to_offset(temp.reserve_raw(0, 4096)), 0); - // reserve kernel pages + // reserve kernel pages (only if within range) let mut pages = alloc::vec![]; for i in 0..8 { - let p = temp.reserve_raw(0x100000 * (i + 1), 0x100000); - assert!(!p.is_null()); - pages.push(p); + let addr = 0x100000 * (i + 1); + if addr + 0x100000 <= size { + let p = temp.reserve_raw(addr, 0x100000); + assert!(!p.is_null()); + pages.push(p); + } } let new_inner = AllocatorInner::new_in(slice, &temp); @@ -681,6 +691,7 @@ impl BuddyAllocatorImpl { for (i, item) in ptrs.iter_mut().enumerate() { let result = self.allocate_raw_unchecked(size); if result.is_null() { + self.inner.unlock(); return Some(i); } *item = result; @@ -696,6 +707,7 @@ impl BuddyAllocatorImpl { for (i, item) in ptrs.iter_mut().enumerate() { let result = self.allocate_raw_unchecked(size); if result.is_null() { + self.inner.unlock(); return i; } *item = result; @@ -1139,163 +1151,4 @@ unsafe impl Allocator for BuddyAllocator { } #[cfg(test)] -mod tests { - extern crate test; - - use super::*; - use test::Bencher; - - #[test] - fn test_bitref() { - let mut word = 10; - - let mut r0 = BitRef::new(&mut word, 0); - r0.set(); - - let mut r1 = BitRef::new(&mut word, 1); - r1.clear(); - - let mut r2 = BitRef::new(&mut word, 2); - r2.write(false); - - let mut r3 = BitRef::new(&mut word, 3); - r3.write(true); - - assert_eq!(word, 9); - } - - #[test] - fn test_bitslice() { - let mut words = [0; 2]; - let mut slice = BitSlice::new(128, &mut words); - let mut r0 = slice.bit(0); - r0.set(); - - let mut r1 = slice.bit(1); - r1.set(); - - let mut r127 = slice.bit(127); - r127.set(); - - assert_eq!(words[0], 3); - assert_eq!( - words[127 / (core::mem::size_of::() * 8)], - 1 << (127 % (core::mem::size_of::() * 8)) - ); - } - - #[test] - fn test_buddy_allocator() { - let allocator = BuddyAllocatorImpl::new(0x10000000); - - let test = Box::new_in(10, allocator.clone()); - assert_eq!(*test, 10); - - let mut v = Vec::new_in(allocator.clone()); - for i in 0..10000 { - v.push(i); - } - } - - #[bench] - fn bench_allocate_free(b: &mut Bencher) { - let allocator = BuddyAllocatorImpl::new(0x100000000); - b.iter(|| { - let x: Box<[MaybeUninit], BuddyAllocatorImpl> = - Box::new_uninit_slice_in(128, allocator.clone()); - core::mem::drop(x); - }); - } - - #[bench] - fn bench_allocate_free_no_cache(b: &mut Bencher) { - let allocator = BuddyAllocatorImpl::new(0x100000000); - allocator.set_caching(false); - b.iter(|| { - let x: Box<[MaybeUninit], BuddyAllocatorImpl> = - Box::new_uninit_slice_in(128, allocator.clone()); - core::mem::drop(x); - }); - } - - #[bench] - fn bench_contended_allocate_free(b: &mut Bencher) { - let allocator = BuddyAllocatorImpl::new(0x100000000); - let f = || { - let x: Box<[MaybeUninit], BuddyAllocatorImpl> = - Box::new_uninit_slice_in(128, allocator.clone()); - core::mem::drop(x); - }; - use core::sync::atomic::AtomicBool; - use std::sync::Arc; - std::thread::scope(|s| { - let flag = Arc::new(AtomicBool::new(true)); - for _ in 0..16 { - let flag = flag.clone(); - s.spawn(move || { - while flag.load(Ordering::SeqCst) { - f(); - } - }); - } - b.iter(f); - flag.store(false, Ordering::SeqCst); - }); - } - - #[bench] - #[ignore] - fn bench_contended_allocate_free_no_cache(b: &mut Bencher) { - let allocator = BuddyAllocatorImpl::new(0x100000000); - allocator.set_caching(false); - let f = || { - let x: Box<[MaybeUninit], BuddyAllocatorImpl> = - Box::new_uninit_slice_in(128, allocator.clone()); - core::mem::drop(x); - }; - use core::sync::atomic::AtomicBool; - use std::sync::Arc; - std::thread::scope(|s| { - let flag = Arc::new(AtomicBool::new(true)); - for _ in 0..16 { - let flag = flag.clone(); - s.spawn(move || { - while flag.load(Ordering::SeqCst) { - f(); - } - }); - } - b.iter(f); - flag.store(false, Ordering::SeqCst); - }); - } - - #[test] - fn stress_test() { - use std::hash::{BuildHasher, Hasher, RandomState}; - let allocator = BuddyAllocatorImpl::new(0x10000000); - allocator.set_caching(false); - let mut v = vec![]; - let random = |limit: usize| { - let x: u64 = RandomState::new().build_hasher().finish(); - x as usize % limit - }; - for _ in 0..100000 { - let used_before = allocator.used_size(); - let remaining = allocator.total_size() - used_before; - let size = random(core::cmp::min(1 << 21, remaining / 2)); - let alloc = - Box::<[u8], BuddyAllocatorImpl>::new_uninit_slice_in(size, allocator.clone()); - let used_after = allocator.used_size(); - assert!(used_after >= used_before + size); - if !v.is_empty() && size % 3 == 0 { - let number = random(v.len()); - for _ in 0..number { - let index = random(v.len()); - v.remove(index); - } - } - v.push(alloc); - } - } -} +mod tests; diff --git a/common/src/buddy/tests.rs b/common/src/buddy/tests.rs new file mode 100644 index 0000000..3b579e9 --- /dev/null +++ b/common/src/buddy/tests.rs @@ -0,0 +1,951 @@ +extern crate test; + +use super::*; +#[test] +// Setting/clearing individual bits in u64 words to check that the bit manipulation works +fn test_bitref() { + let mut word = 10; + + let mut r0 = BitRef::new(&mut word, 0); + r0.set(); + + let mut r1 = BitRef::new(&mut word, 1); + r1.clear(); + + let mut r2 = BitRef::new(&mut word, 2); + r2.write(false); + + let mut r3 = BitRef::new(&mut word, 3); + r3.write(true); + + assert_eq!(word, 9); +} + +#[test] +// Setting/clearing individual bits in a BitSlice to check that the bit manipulation works +fn test_bitslice() { + let mut words = [0; 2]; + let mut slice = BitSlice::new(128, &mut words); + let mut r0 = slice.bit(0); + r0.set(); + + let mut r1 = slice.bit(1); + r1.set(); + + let mut r127 = slice.bit(127); + r127.set(); + + assert_eq!(words[0], 3); + assert_eq!( + words[127 / (core::mem::size_of::() * 8)], + 1 << (127 % (core::mem::size_of::() * 8)) + ); +} + +#[test] +// Basic setup + continuous element pushing to check that the allocator grows and adjusts properly +fn test_buddy_allocator() { + let allocator = BuddyAllocatorImpl::new(0x10000000); + + let test = Box::new_in(10, allocator.clone()); + assert_eq!(*test, 10); + + let mut v = Vec::new_in(allocator.clone()); + for i in 0..10000 { + v.push(i); + } +} + +#[test] +// Verifying that too small allocations of allocator do not panic +// Potential issue: reserve_unchecked does not validate that the requested index is within the number of blocks at that level +fn test_too_small_allocation() { + let allocator = BuddyAllocatorImpl::new(1 << 20); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let _used_before = allocator.used_size(); + let _ptr = allocator.allocate_raw(size); +} + +#[test] +// Verifying allocate_raw adds size to used_size, and free_raw subtracts it back, returning usage to the original amount. +fn test_allocate_raw_and_used_size() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let used_before = allocator.used_size(); + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + assert_eq!(allocator.used_size(), used_before + size); + allocator.free_raw(ptr, size); + assert_eq!(allocator.used_size(), used_before); +} + +#[test] +// Verifying allocate_many_raw adds size to used_size, and free_many_raw subtracts it back, returning usage to the original amount. +fn test_allocate_many_and_free_many() { + use std::collections::BTreeSet; + + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let used_before = allocator.used_size(); + let mut ptrs = [core::ptr::null_mut(); 4]; + let count = allocator.allocate_many_raw(size, &mut ptrs); + assert_eq!(count, ptrs.len()); + assert!(ptrs.iter().all(|ptr| !ptr.is_null())); + + let unique: BTreeSet = ptrs.iter().map(|ptr| *ptr as usize).collect(); + assert_eq!(unique.len(), ptrs.len()); + + allocator.free_many_raw(size, &ptrs); + assert_eq!(allocator.used_size(), used_before); +} + +#[test] +// Verifying that to_offset and from_offset roundtrip correctly +fn test_offset_roundtrip() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + + let offset = allocator.to_offset(ptr); + let roundtrip = allocator.from_offset::(offset); + assert_eq!(roundtrip as usize, ptr as usize); + + allocator.free_raw(ptr, size); +} + +#[test] +// Verifying that reserving at zero returns a null pointer and does not add to used_size +fn test_reserve_raw_at_zero() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let used_before = allocator.used_size(); + let ptr = allocator.reserve_raw(0, size); + assert!(ptr.is_null()); + assert_eq!(allocator.used_size(), used_before); +} + +#[test] +// Verifying that allocating too large returns a null pointer and does not add to used_size +fn test_allocate_raw_too_large_returns_null() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let used_before = allocator.used_size(); + let ptr = allocator.allocate_raw(allocator.total_size() * 2); + assert!(ptr.is_null()); + assert_eq!(allocator.used_size(), used_before); +} + +#[test] +// Verifying that refcnt is zero on allocate +fn test_refcnt_zero_on_allocate() { + use core::sync::atomic::Ordering; + + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + + let refcnt = allocator.refcnt(ptr); + assert!(!refcnt.is_null()); + let value = unsafe { (*refcnt).load(Ordering::SeqCst) }; + assert_eq!(value, 0); + + allocator.free_raw(ptr, size); +} + +#[test] +// Stress testing the allocator with random allocations and frees +fn stress_test() { + use std::hash::{BuildHasher, Hasher, RandomState}; + let allocator = BuddyAllocatorImpl::new(0x10000000); + allocator.set_caching(false); + let mut v = vec![]; + let random = |limit: usize| { + let x: u64 = RandomState::new().build_hasher().finish(); + x as usize % limit + }; + for _ in 0..100000 { + let used_before = allocator.used_size(); + let remaining = allocator.total_size() - used_before; + let size = random(core::cmp::min(1 << 21, remaining / 2)); + let alloc = Box::<[u8], BuddyAllocatorImpl>::new_uninit_slice_in(size, allocator.clone()); + let used_after = allocator.used_size(); + assert!(used_after >= used_before + size); + if !v.is_empty() && size % 3 == 0 { + let number = random(v.len()); + for _ in 0..number { + let index = random(v.len()); + v.remove(index); + } + } + v.push(alloc); + } +} + +#[test] +// Test splitting large blocks into smaller ones +fn test_block_splitting() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let small_size = BuddyAllocatorImpl::MIN_ALLOCATION; + let large_size = small_size * 4; + + // Allocate and free a large block + let large_ptr = allocator.allocate_raw(large_size); + assert!(!large_ptr.is_null()); + allocator.free_raw(large_ptr, large_size); + + // Now allocate multiple small blocks - should split the large one + let mut small_ptrs = vec![]; + for _ in 0..4 { + let ptr = allocator.allocate_raw(small_size); + assert!(!ptr.is_null()); + small_ptrs.push(ptr); + } + + // Clean up + for ptr in small_ptrs { + allocator.free_raw(ptr, small_size); + } +} + +#[test] +fn test_reserve_specific_addresses() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + // Get a definitely-available block + let p = allocator.allocate_raw(size); + assert!(!p.is_null()); + let address = allocator.to_offset(p); + allocator.free_raw(p, size); + + // Now we should be able to reserve that exact address + let ptr1 = allocator.reserve_raw(address, size); + assert!(!ptr1.is_null()); + assert_eq!(allocator.to_offset(ptr1), address); + + // Reserving again should fail + let ptr2 = allocator.reserve_raw(address, size); + assert!(ptr2.is_null()); + + allocator.free_raw(ptr1, size); +} + +#[test] +// Test reserving overlapping regions +fn test_reserve_overlapping_regions() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + // Reserve a block + let ptr1 = allocator.reserve_raw(size * 5, size); + assert!(!ptr1.is_null()); + + // Try to reserve a larger block that would overlap + let ptr2 = allocator.reserve_raw(size * 4, size * 4); + assert!(ptr2.is_null()); // Should fail because it overlaps with ptr1 + + allocator.free_raw(ptr1, size); +} + +#[test] +// Test allocating all available memory +fn test_exhaust_memory() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + let mut ptrs = vec![]; + + // Allocate until we can't anymore + loop { + let ptr = allocator.allocate_raw(size); + if ptr.is_null() { + break; + } + ptrs.push(ptr); + } + + // Verify we actually allocated something + assert!(!ptrs.is_empty()); + + // Try one more allocation - should fail + let ptr = allocator.allocate_raw(size); + assert!(ptr.is_null()); + + // Free everything + for ptr in ptrs { + allocator.free_raw(ptr, size); + } + + // Should be able to allocate again + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + allocator.free_raw(ptr, size); +} + +#[test] +// Test mixed allocation sizes +fn test_mixed_allocation_sizes() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + + let small = BuddyAllocatorImpl::MIN_ALLOCATION; + let medium = small * 4; + let large = small * 16; + + let ptr1 = allocator.allocate_raw(small); + let ptr2 = allocator.allocate_raw(large); + let ptr3 = allocator.allocate_raw(medium); + let ptr4 = allocator.allocate_raw(small); + + assert!(!ptr1.is_null()); + assert!(!ptr2.is_null()); + assert!(!ptr3.is_null()); + assert!(!ptr4.is_null()); + + // Verify they're all different + let ptrs = [ptr1, ptr2, ptr3, ptr4]; + for i in 0..ptrs.len() { + for j in (i + 1)..ptrs.len() { + assert_ne!(ptrs[i], ptrs[j]); + } + } + + allocator.free_raw(ptr2, large); + allocator.free_raw(ptr1, small); + allocator.free_raw(ptr4, small); + allocator.free_raw(ptr3, medium); +} + +#[test] +// Test freeing in different order than allocation +fn test_free_reverse_order() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let mut ptrs = vec![]; + for _ in 0..10 { + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + ptrs.push(ptr); + } + + let used_peak = allocator.used_size(); + + // Free in reverse order + for ptr in ptrs.iter().rev() { + allocator.free_raw(*ptr, size); + } + + assert!(allocator.used_size() < used_peak); +} + +#[test] +// Test allocation size rounding. Test after exhausting, allocator should still be usable -- no lock leak +fn allocation_rounds_up_to_pow2_and_min() { + let a = BuddyAllocatorImpl::new(1 << 24); + + // Request sizes that aren't powers of 2 + let ptr1 = a.allocate_raw(5000); // Should round to 8192 + let ptr2 = a.allocate_raw(1000); // Should round to 4096 + let ptr3 = a.allocate_raw(10000); // Should round to 16384 + + assert!(!ptr1.is_null()); + assert!(!ptr2.is_null()); + assert!(!ptr3.is_null()); + + a.free_raw(ptr1, 5000); + a.free_raw(ptr2, 1000); + a.free_raw(ptr3, 10000); + + let used0 = a.used_size(); + let p = a.allocate_raw(5000); // rounds to 8192 (and >= 4096) + assert!(!p.is_null()); + assert_eq!(a.used_size(), used0 + 8192); + + a.free_raw(p, 5000); // free uses same rounding path + assert_eq!(a.used_size(), used0); +} + +#[test] +// Confirm there is no overlap between levels +fn test_offset_calculation() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + + let mut ranges = vec![]; + for level in allocator.inner.meta.level_range.clone() { + let offset = allocator.inner.offset_of_level_words(level); + let size = allocator.inner.size_of_level_words(level); + ranges.push((offset, offset + size, level)); + } + ranges.sort_by_key(|(start, _, _)| *start); + + for w in ranges.windows(2) { + let (_s1, e1, l1) = w[0]; + let (s2, _e2, l2) = w[1]; + assert!(e1 <= s2, "overlap between level {} and level {}", l1, l2); + } +} + +#[test] +// Test bitmap boundaries +fn test_bitmap_boundaries() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + + for level in allocator.inner.meta.level_range.clone() { + let bits = allocator.inner.size_of_level_bits(level); + let words = allocator.inner.size_of_level_words(level); + + // Verify words is enough to hold bits + assert!( + words * 64 >= bits, + "Level {} needs {} bits but only has {} words ({} bits)", + level, + bits, + words, + words * 64 + ); + } +} + +#[test] +// Test try_allocate_many_raw where everything should succeed easily +fn test_try_allocate_many_no_contention() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let mut ptrs = [core::ptr::null_mut(); 10]; + let result = allocator.try_allocate_many_raw(size, &mut ptrs); + + assert_eq!(result, Some(10)); + assert!(ptrs.iter().all(|p| !p.is_null())); + + allocator.free_many_raw(size, &ptrs); +} + +#[test] +// Testing allocating more pointers than space available, making sure bulk alloc stops cleanly when space out, +// and partial success allowed, reported successes are valid. Currently hanging +fn test_allocate_many_partial_success() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + // Request more blocks than available + let mut ptrs = [core::ptr::null_mut(); 10000]; + let count = allocator.allocate_many_raw(size, &mut ptrs); + + // Should have allocated some but not all + assert!(count > 0); + assert!(count < ptrs.len()); + + // All allocated pointers should be non-null + for i in 0..count { + assert!(!ptrs[i].is_null()); + } + + // Remaining should be null + for i in count..ptrs.len() { + assert!(ptrs[i].is_null()); + } + + // Clean up + allocator.free_many_raw(size, &ptrs[0..count]); +} + +#[test] +// Test that refcnt works for different allocation addresses +fn test_refcnt_different_addresses() { + use core::sync::atomic::Ordering; + + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let ptr1 = allocator.allocate_raw(size); + let ptr2 = allocator.allocate_raw(size); + + let refcnt1 = allocator.refcnt(ptr1); + let refcnt2 = allocator.refcnt(ptr2); + + // Should be different refcnt locations + assert_ne!(refcnt1, refcnt2); + + // Both should be 0 + assert_eq!(unsafe { (*refcnt1).load(Ordering::SeqCst) }, 0); + assert_eq!(unsafe { (*refcnt2).load(Ordering::SeqCst) }, 0); + + allocator.free_raw(ptr1, size); + allocator.free_raw(ptr2, size); +} + +#[test] +// Test null pointer refcnt +fn test_refcnt_null_pointer() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let refcnt = allocator.refcnt(core::ptr::null::()); + assert!(refcnt.is_null()); +} + +#[test] +// Test usage calculation +fn test_usage_calculation() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let initial_usage = allocator.usage(); + + let ptr = allocator.allocate_raw(size); + let usage_after = allocator.usage(); + + assert!(usage_after > initial_usage); + assert!(usage_after <= 1.0); + assert!(usage_after >= 0.0); + + allocator.free_raw(ptr, size); +} + +#[test] +// Test request counting +fn test_request_counting() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let mut before = [0; 64]; + let mut after = [0; 64]; + + allocator.requests(&mut before); + + let ptr = allocator.allocate_raw(size); + allocator.free_raw(ptr, size); + + allocator.requests(&mut after); + + // Should have incremented request count for the size level + let level = size.next_power_of_two().ilog2() as usize; + assert!(after[level] > before[level]); +} + +#[test] +fn test_alignment_requirements() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + let base = allocator.base() as usize; + + for power in 12..20 { + let size = 1 << power; + let ptr = allocator.allocate_raw(size); + assert!(!ptr.is_null()); + + let addr = ptr as usize; + assert_eq!( + (addr - base) % size, + 0, + "Allocation of size {} not aligned within arena", + size + ); + + allocator.free_raw(ptr, size); + } +} + +#[test] +// Test clone and drop behavior +fn test_clone_and_drop() { + let allocator = BuddyAllocatorImpl::new(1 << 24); + + let ptr1 = allocator.allocate_raw(4096); + assert!(!ptr1.is_null()); + + { + let clone = allocator.clone(); + let ptr2 = clone.allocate_raw(4096); + assert!(!ptr2.is_null()); + clone.free_raw(ptr2, 4096); + // clone drops here + } + + // Original should still work + let ptr3 = allocator.allocate_raw(4096); + assert!(!ptr3.is_null()); + + allocator.free_raw(ptr1, 4096); + allocator.free_raw(ptr3, 4096); +} + +#[test] +// Allocate one block, compute its buddy address, and verify that reserving the buddy returns that address (if it’s free). +fn buddy_address_math_matches_reserve() { + let a = BuddyAllocatorImpl::new(1 << 24); + let base = a.base() as usize; + + for power in 12..18 { + let size = 1usize << power; + let p = a.allocate_raw(size); + assert!(!p.is_null()); + + let off = a.to_offset(p); + let idx = off / size; + let buddy_idx = idx ^ 1; + let buddy_off = buddy_idx * size; + + // If the buddy is free, reserve_raw must return exactly that address. + let b = a.reserve_raw(buddy_off, size); + if !b.is_null() { + assert_eq!(a.to_offset(b), buddy_off); + a.free_raw(b, size); + } + + a.free_raw(p, size); + + // (optional) base-relative alignment property + assert_eq!(((p as usize) - base) % size, 0); + } +} + +#[test] +// 'Create' a known free block at a known offset by allocating a large block, then freeing it, then reserving/allocating inside it. +fn split_large_block_into_smaller_blocks() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let big = 1usize << 16; // 64KiB + let small = 1usize << 12; // 4KiB + let factor = big / small; + + let p = a.allocate_raw(big); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, big); + + // Now reserve all 4KiB blocks inside that 64KiB region. + let mut blocks = Vec::new(); + for i in 0..factor { + let q = a.reserve_raw(off + i * small, small); + assert!(!q.is_null(), "failed to reserve sub-block {}", i); + blocks.push(q); + } + + // Free them back + for q in blocks { + a.free_raw(q, small); + } +} + +#[test] +// Reserve two buddy halves, free them, verify you can reserve the parent block at the exact parent address. +fn coalesce_two_buddies_into_parent() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let parent = 1usize << 14; // 16KiB + let child = 1usize << 13; // 8KiB + + // Create a known free parent block at a known offset. + let p = a.allocate_raw(parent); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, parent); + + // Reserve both children (buddies). + let c0 = a.reserve_raw(off, child); + let c1 = a.reserve_raw(off + child, child); + assert!(!c0.is_null() && !c1.is_null()); + + // Free both; this should coalesce into the parent. + a.free_raw(c0, child); + a.free_raw(c1, child); + + // Now reserving the parent at 'off' should succeed. + let p2 = a.reserve_raw(off, parent); + assert!( + !p2.is_null(), + "parent block did not reappear after coalescing" + ); + assert_eq!(a.to_offset(p2), off); + + a.free_raw(p2, parent); +} + +#[test] +// Hold one child, free the other, ensure parent reservation fails at that exact parent address. +fn no_coalesce_if_only_one_buddy_free() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let parent = 1usize << 14; // 16KiB + let child = 1usize << 13; // 8KiB + + let p = a.allocate_raw(parent); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, parent); + + let c0 = a.reserve_raw(off, child); + let c1 = a.reserve_raw(off + child, child); + assert!(!c0.is_null() && !c1.is_null()); + + // Free only one child + a.free_raw(c0, child); + + // Parent must NOT be reservable while the other buddy is still held. + let parent_try = a.reserve_raw(off, parent); + assert!( + parent_try.is_null(), + "parent became available with one buddy still reserved" + ); + + // Cleanup + a.free_raw(c1, child); + + // Now parent should be available (coalesced) + let parent_ok = a.reserve_raw(off, parent); + assert!(!parent_ok.is_null()); + a.free_raw(parent_ok, parent); +} + +#[test] +// Free child1 then child0; ensure parent becomes available. +fn coalesce_is_order_independent() { + let a = BuddyAllocatorImpl::new(1 << 24); + let parent = 1usize << 15; // 32KiB + let child = 1usize << 14; // 16KiB + + let p = a.allocate_raw(parent); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, parent); + + let c0 = a.reserve_raw(off, child); + let c1 = a.reserve_raw(off + child, child); + assert!(!c0.is_null() && !c1.is_null()); + + a.free_raw(c1, child); + a.free_raw(c0, child); + + let p2 = a.reserve_raw(off, parent); + assert!(!p2.is_null()); + a.free_raw(p2, parent); +} + +#[test] +// Free 4 children → coalesce to 2 parents → coalesce to 1 grandparent. +fn multi_level_coalesce_cascades() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let grand = 1usize << 15; // 32KiB + let child = 1usize << 13; // 8KiB + let n = grand / child; // 4 + + let p = a.allocate_raw(grand); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, grand); + + let mut kids = Vec::new(); + for i in 0..n { + let k = a.reserve_raw(off + i * child, child); + assert!(!k.is_null()); + kids.push(k); + } + + // Free all kids -> should coalesce up to grand + for k in kids { + a.free_raw(k, child); + } + + let g = a.reserve_raw(off, grand); + assert!( + !g.is_null(), + "expected full cascade coalesce to grand block" + ); + a.free_raw(g, grand); +} + +#[test] +// Size rounding edge cases: allocate_raw rounds up to power-of-two and MIN_ALLOCATION, ensuring allocations don’t fail just because size isn’t a power of two. +// Currently failing; needs to be fixed? +fn reserve_out_of_range_returns_null() { + let a = BuddyAllocatorImpl::new(1 << 24); + let size = 1usize << 12; + + // definitely beyond arena + let ptr = a.reserve_raw(a.len() + size, size); + assert!(ptr.is_null()); +} + +#[test] +// Testing allocate_many_raw where partial failure does not poison the lock +// Currently hanging because partial failures is not working, test after that is fixed +fn allocate_many_partial_failure_does_not_poison_lock() { + let a = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + let mut ptrs = [core::ptr::null_mut(); 10000]; + let n = a.allocate_many_raw(size, &mut ptrs); + + assert!(n > 0); + assert!(n < ptrs.len()); + + a.free_many_raw(size, &ptrs[..n]); + + // If the lock leaked, this would hang. + let p = a.allocate_raw(size); + assert!(!p.is_null()); + a.free_raw(p, size); +} + +#[test] +// ensures try_* returns None when lock is held. +fn try_allocate_many_returns_none_when_locked() { + let a = BuddyAllocatorImpl::new(1 << 24); + let size = BuddyAllocatorImpl::MIN_ALLOCATION; + + // Manually lock allocator and ensure try_* fails. + unsafe { + a.inner.lock(); + } + let mut ptrs = [core::ptr::null_mut(); 4]; + let r = a.try_allocate_many_raw(size, &mut ptrs); + assert_eq!(r, None); + unsafe { + a.inner.unlock(); + } + + // Now it should work + let r2 = a.try_allocate_many_raw(size, &mut ptrs); + assert_eq!(r2, Some(4)); + a.free_many_raw(size, &ptrs); +} + +#[test] +// Interleaved patterns: A,B,C,D where (A,B) and (C,D) are buddy pairs. +// Freeing B and C alone should NOT make either parent available; +// freeing A then enables AB coalesce; freeing D then enables CD coalesce; then both parents can coalesce further. +fn interleaved_buddy_pairs_coalesce_independently_then_merge() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let grand = 1usize << 15; // 32KiB + let parent = 1usize << 14; // 16KiB + let child = 1usize << 13; // 8KiB + + // Known free 32KiB region + let g = a.allocate_raw(grand); + assert!(!g.is_null()); + let off = a.to_offset(g); + a.free_raw(g, grand); + + // Reserve A,B,C,D as 8KiB blocks at offsets 0,1,2,3 within the 32KiB region + let a0 = a.reserve_raw(off + 0 * child, child); // A + let b0 = a.reserve_raw(off + 1 * child, child); // B (buddy of A) + let c0 = a.reserve_raw(off + 2 * child, child); // C + let d0 = a.reserve_raw(off + 3 * child, child); // D (buddy of C) + assert!(!a0.is_null() && !b0.is_null() && !c0.is_null() && !d0.is_null()); + + // Free B and C only -> neither 16KiB parent should be reservable yet. + a.free_raw(b0, child); + a.free_raw(c0, child); + + assert!( + a.reserve_raw(off + 0 * parent, parent).is_null(), + "AB parent should not exist yet" + ); + assert!( + a.reserve_raw(off + 1 * parent, parent).is_null(), + "CD parent should not exist yet" + ); + + // Free A -> AB should coalesce to first 16KiB parent at off + a.free_raw(a0, child); + let p0 = a.reserve_raw(off + 0 * parent, parent); + assert!(!p0.is_null(), "AB should coalesce to 16KiB"); + a.free_raw(p0, parent); + + // Free D -> CD should coalesce to second 16KiB parent at off + 16KiB + a.free_raw(d0, child); + let p1 = a.reserve_raw(off + 1 * parent, parent); + assert!(!p1.is_null(), "CD should coalesce to 16KiB"); + a.free_raw(p1, parent); + + // Now both 16KiB parents are free -> should coalesce into 32KiB grandparent at off + let g2 = a.reserve_raw(off, grand); + assert!( + !g2.is_null(), + "two free 16KiB parents should coalesce to 32KiB" + ); + a.free_raw(g2, grand); +} + +#[test] +// Fragmentation scenario: partial coalescing with an obstacle. +// If one leaf remains reserved, upper levels must not fully coalesce; once obstacle freed, full coalesce should happen. +fn fragmentation_blocks_full_coalesce_until_obstacle_removed() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let big = 1usize << 16; // 64KiB region we control + let leaf = 1usize << 12; // 4KiB + let n = big / leaf; // 16 leaves + + // Known free 64KiB region + let p = a.allocate_raw(big); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, big); + + // Reserve all leaves, keep one as "obstacle", free the rest. + let mut leaves = Vec::new(); + for i in 0..n { + let q = a.reserve_raw(off + i * leaf, leaf); + assert!(!q.is_null()); + leaves.push(q); + } + + let obstacle = leaves[7]; // arbitrary leaf to hold + for (_i, q) in leaves.iter().enumerate() { + if *q == obstacle { + continue; + } + a.free_raw(*q, leaf); + } + + // With one 4KiB still reserved, the full 64KiB block must NOT be available. + assert!( + a.reserve_raw(off, big).is_null(), + "should not fully coalesce with an obstacle leaf reserved" + ); + + // Now free the obstacle leaf -> full coalesce should become possible. + a.free_raw(obstacle, leaf); + let big2 = a.reserve_raw(off, big); + assert!( + !big2.is_null(), + "after removing obstacle, should fully coalesce back to 64KiB" + ); + a.free_raw(big2, big); +} + +#[test] +// Reserved blocks shouldn't participate in coalescing: +// if one buddy is permanently reserved (held), the parent must not become available. +fn reserved_block_prevents_coalescing() { + let a = BuddyAllocatorImpl::new(1 << 24); + + let parent = 1usize << 14; // 16KiB + let child = 1usize << 13; // 8KiB + + // Known free parent region + let p = a.allocate_raw(parent); + assert!(!p.is_null()); + let off = a.to_offset(p); + a.free_raw(p, parent); + + // Reserve both children, but "reserve" one as a held block (simulate reservation that shouldn't coalesce). + let held = a.reserve_raw(off, child); + let other = a.reserve_raw(off + child, child); + assert!(!held.is_null() && !other.is_null()); + + // Free only the other -> parent must not appear + a.free_raw(other, child); + assert!( + a.reserve_raw(off, parent).is_null(), + "parent should not coalesce while one child is held/reserved" + ); + + // Once held is freed too, parent should become available + a.free_raw(held, child); + let p2 = a.reserve_raw(off, parent); + assert!(!p2.is_null()); + a.free_raw(p2, parent); +} diff --git a/common/src/lib.rs b/common/src/lib.rs index 20d7811..78ea10b 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![allow(stable_features, unused_features)] #![feature(allocator_api)] #![feature(fn_traits)] #![cfg_attr(feature = "std", feature(layout_for_ptr))] diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 3ad4cff..9a757d1 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -36,7 +36,8 @@ talc = "4.4.3" spin = "0.10.0" async-lock = { version = "3.4.1", default-features = false } postcard = "1.1.3" -serde = { version = "1.0.228", default-features = false } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } + [build-dependencies] anyhow = "1.0.86" diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index b5831ed..48aba55 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -1,7 +1,10 @@ #![no_main] #![no_std] +#![allow(stable_features, unused_features)] +#![feature(cfg_version)] #![feature(allocator_api)] -#![feature(widening_mul)] +#![cfg_attr(not(version("1.96")), feature(bigint_helper_methods))] +#![cfg_attr(version("1.96"), feature(widening_mul))] #![feature(box_as_ptr)] #![feature(negative_impls)] #![feature(never_type)] diff --git a/kernel/src/tests/test_serde.rs b/kernel/src/tests/test_serde.rs index b9e038a..102de14 100644 --- a/kernel/src/tests/test_serde.rs +++ b/kernel/src/tests/test_serde.rs @@ -1,112 +1,125 @@ -use crate::prelude::*; -extern crate alloc; +// Serialization round-trip tests using postcard. +// Runs with: cargo test -p kernel --target=x86_64-unknown-none -#[test] -fn test_serde_null() { - let null = Value::Null(Null::new()); - let bytes_vec = postcard::to_allocvec(&null).unwrap(); - let deserialized_null: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_null, null); -} +#[cfg(test)] +mod tests { + extern crate alloc; -#[test] -fn test_serde_word() { - let word = Value::Word(1.into()); - let bytes_vec = postcard::to_allocvec(&word).unwrap(); - let deserialized_word: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_word, word); -} + use crate::prelude::*; -#[test] -fn test_serde_blob() { - let blob = Value::Blob("hello, world!".into()); - let bytes_vec = postcard::to_allocvec(&blob).unwrap(); - let deserialized_blob: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_blob, blob); -} + /// Verifies Null serializes and deserializes back to an equal value. + #[test] + fn test_serde_null() { + let null = Value::Null(Null::new()); + let bytes_vec = postcard::to_allocvec(&null).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, null); + } -#[test] -fn test_serde_tuple() { - let tuple = Value::Tuple((1, 2, 3).into()); - let bytes_vec = postcard::to_allocvec(&tuple).unwrap(); - let deserialized_tuple: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_tuple, tuple); -} + /// Verifies Word serializes and deserializes back to an equal value. + #[test] + fn test_serde_word() { + let word = Value::Word(1.into()); + let bytes_vec = postcard::to_allocvec(&word).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, word); + } -#[test] -fn test_serde_page() { - let page = Value::Page(Page::new(1)); - let bytes_vec = postcard::to_allocvec(&page).unwrap(); - let deserialized_page: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_page, page); -} + /// Verifies Blob serializes and deserializes back to an equal value. + #[test] + fn test_serde_blob() { + let blob = Value::Blob("hello, world!".into()); + let bytes_vec = postcard::to_allocvec(&blob).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, blob); + } -#[test] -fn test_serde_table() { - let table = Value::Table(Table::new(1)); - let bytes_vec = postcard::to_allocvec(&table).unwrap(); - let deserialized_table: Value = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_table, table); -} + /// Verifies Tuple serializes and deserializes back to an equal value. + #[test] + fn test_serde_tuple() { + let tuple = Value::Tuple((1, 2, 3).into()); + let bytes_vec = postcard::to_allocvec(&tuple).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, tuple); + } -// #[test] -// fn test_serde_function() { -// let arca = Arca::new(); -// let inner_func: arca::Function = Function::from(arca); -// let func = Value::Function(inner_func); -// let bytes_vec = postcard::to_allocvec(&func).unwrap(); -// let deserialized_func: Value = postcard::from_bytes(&bytes_vec).unwrap(); -// assert_eq!(deserialized_func, func); -// } + /// Verifies Page serializes and deserializes back to an equal value. + #[test] + fn test_serde_page() { + let page = Value::Page(Page::new(1)); + let bytes_vec = postcard::to_allocvec(&page).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, page); + } -#[test] -fn test_serde_ropage() { - let ropage = Entry::ROPage(Page::new(1)); - let bytes_vec = postcard::to_allocvec(&ropage).unwrap(); - let deserialized_ropage: Entry = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_ropage, ropage); -} + /// Verifies Table serializes and deserializes back to an equal value. + #[test] + fn test_serde_table() { + let table = Value::Table(Table::new(1)); + let bytes_vec = postcard::to_allocvec(&table).unwrap(); + let deserialized: Value = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, table); + } -#[test] -fn test_serde_rwpage() { - let rwpage = Entry::RWPage(Page::new(1)); - let bytes_vec = postcard::to_allocvec(&rwpage).unwrap(); - let deserialized_rwpage: Entry = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_rwpage, rwpage); -} + /// Verifies a read-only page Entry round-trips through serde. + #[test] + fn test_serde_ropage() { + let ropage = Entry::ROPage(Page::new(1)); + let bytes_vec = postcard::to_allocvec(&ropage).unwrap(); + let deserialized: Entry = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, ropage); + } -#[test] -fn test_serde_rotable() { - let rotable = Entry::ROTable(Table::new(1)); - let bytes_vec = postcard::to_allocvec(&rotable).unwrap(); - let deserialized_rotable: Entry = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_rotable, rotable); -} + /// Verifies a read-write page Entry round-trips through serde. + #[test] + fn test_serde_rwpage() { + let rwpage = Entry::RWPage(Page::new(1)); + let bytes_vec = postcard::to_allocvec(&rwpage).unwrap(); + let deserialized: Entry = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, rwpage); + } -#[test] -fn test_serde_rwtable() { - let rwtable = Entry::RWTable(Table::new(1)); - let bytes_vec = postcard::to_allocvec(&rwtable).unwrap(); - let deserialized_rwtable: Entry = postcard::from_bytes(&bytes_vec).unwrap(); - assert_eq!(deserialized_rwtable, rwtable); -} + /// Verifies a read-only table Entry round-trips through serde. + #[test] + fn test_serde_rotable() { + let rotable = Entry::ROTable(Table::new(1)); + let bytes_vec = postcard::to_allocvec(&rotable).unwrap(); + let deserialized: Entry = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, rotable); + } -#[test] -fn test_value_error() { - let unknown_variant = [7, 0]; - let deserialized: Result = postcard::from_bytes(&unknown_variant); - let deserialized_error = deserialized.expect_err("should have been err"); - let error = - serde::de::Error::unknown_variant("7", &["Null", "Word", "Blob", "Tuple", "Page", "Table"]); - assert_eq!(deserialized_error, error); -} + /// Verifies a read-write table Entry round-trips through serde. + #[test] + fn test_serde_rwtable() { + let rwtable = Entry::RWTable(Table::new(1)); + let bytes_vec = postcard::to_allocvec(&rwtable).unwrap(); + let deserialized: Entry = postcard::from_bytes(&bytes_vec).unwrap(); + assert_eq!(deserialized, rwtable); + } + + /// Ensures deserializing an unknown Value variant produces the expected error. + #[test] + fn test_value_unknown_variant_error() { + let unknown_variant = [7, 0]; + let deserialized: Result = postcard::from_bytes(&unknown_variant); + let deserialized_error = deserialized.expect_err("should have been err"); + let error = serde::de::Error::unknown_variant( + "7", + &["Null", "Word", "Blob", "Tuple", "Page", "Table"], + ); + assert_eq!(deserialized_error, error); + } -#[test] -fn test_entry_error() { - let unknown_variant = [5, 0]; - let deserialized: Result = postcard::from_bytes(&unknown_variant); - let deserialized_error = deserialized.expect_err("should have been err"); - let error = - serde::de::Error::unknown_variant("5", &["Null", "ROPage", "RWPage", "ROTable", "RWTable"]); - assert_eq!(deserialized_error, error); + /// Ensures deserializing an unknown Entry variant produces the expected error. + #[test] + fn test_entry_unknown_variant_error() { + let unknown_variant = [5, 0]; + let deserialized: Result = postcard::from_bytes(&unknown_variant); + let deserialized_error = deserialized.expect_err("should have been err"); + let error = serde::de::Error::unknown_variant( + "5", + &["Null", "ROPage", "RWPage", "ROTable", "RWTable"], + ); + assert_eq!(deserialized_error, error); + } } diff --git a/kernel/src/types/blob.rs b/kernel/src/types/blob.rs index 5b7780e..d29c502 100644 --- a/kernel/src/types/blob.rs +++ b/kernel/src/types/blob.rs @@ -87,3 +87,40 @@ impl From<&str> for Blob { Blob::from(value.to_string()) } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies len() and into_inner() return correct content. + #[test] + fn test_len_and_into_inner() { + let blob = Blob::new(b"hello".to_vec()); + assert_eq!(blob.len(), 5); + assert_eq!(&*blob.into_inner(), b"hello"); + } + + /// Verifies DerefMut allows in-place byte mutation. + #[test] + fn test_mutation() { + let mut blob = Blob::new(b"hello".to_vec()); + blob[0] = b'j'; + assert_eq!(&*blob.into_inner(), b"jello"); + } + + /// Ensures invalid UTF-8 bytes are preserved as raw data. + #[test] + fn test_invalid_utf8_preserved() { + let bytes = vec![0xffu8, 0xfeu8, 0xfdu8]; + let blob = Blob::new(bytes.clone()); + assert_eq!(&*blob.into_inner(), &bytes[..]); + } + + /// Verifies From<&str> constructs a blob with matching content. + #[test] + fn test_from_str() { + let blob = Blob::from("test"); + assert_eq!(blob.len(), 4); + assert_eq!(&*blob, b"test"); + } +} diff --git a/kernel/src/types/function.rs b/kernel/src/types/function.rs index 29d539c..94c1aa2 100644 --- a/kernel/src/types/function.rs +++ b/kernel/src/types/function.rs @@ -166,3 +166,51 @@ impl Function { } } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies symbolic function parsing and read round-trip. + #[test] + fn test_symbolic_parse_and_read() { + let args = Tuple::from((1u64, "two")); + let value = Value::Tuple(Tuple::from(( + Blob::from("Symbolic"), + Value::Word(Word::new(5)), + Value::Tuple(args), + ))); + let func = Function::new(value.clone()).expect("symbolic parse failed"); + assert!(!func.is_arcane()); + assert_eq!(func.read(), value); + } + + /// Ensures unrecognized function tags are rejected. + #[test] + fn test_invalid_tag_rejected() { + let value = Value::Tuple(Tuple::from((Blob::from("Other"), Value::Null(Null::new())))); + assert!(Function::new(value).is_none()); + } + + /// Verifies arcane function parsing accepts a valid register/memory layout. + #[test] + fn test_arcane_parse_valid_layout() { + let mut registers = Tuple::new(18); + for i in 0..18 { + registers.set(i, Value::Null(Null::new())); + } + let mut data = Tuple::new(4); + data.set(0, Value::Tuple(registers)); + data.set(1, Value::Table(Table::new(1))); + data.set(2, Value::Tuple(Tuple::new(0))); + data.set(3, Value::Tuple(Tuple::new(0))); + + let value = Value::Tuple(Tuple::from(( + Blob::from("Arcane"), + Value::Tuple(data), + Value::Tuple(Tuple::new(0)), + ))); + let func = Function::new(value).expect("arcane parse failed"); + assert!(func.is_arcane()); + } +} diff --git a/kernel/src/types/null.rs b/kernel/src/types/null.rs index 79cfa92..ed84126 100644 --- a/kernel/src/types/null.rs +++ b/kernel/src/types/null.rs @@ -12,3 +12,14 @@ impl Default for Null { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Ensures Null::new() and Null::default() produce identical values. + #[test] + fn test_new_equals_default() { + assert_eq!(Null::new(), Null::default()); + } +} diff --git a/kernel/src/types/page.rs b/kernel/src/types/page.rs index a666c7e..1b77b73 100644 --- a/kernel/src/types/page.rs +++ b/kernel/src/types/page.rs @@ -120,3 +120,38 @@ impl DerefMut for Page { } } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies page size selection at tier boundaries (4KB, 2MB, 1GB). + #[test] + fn test_size_tiers() { + let small = Page::new(1); + assert_eq!(small.size(), 1 << 12); + + let mid = Page::new((1 << 12) + 1); + assert_eq!(mid.size(), 1 << 21); + + let large = Page::new((1 << 21) + 1); + assert_eq!(large.size(), 1 << 30); + } + + /// Verifies DerefMut write and Deref read on page bytes. + #[test] + fn test_write_and_read_back() { + let mut page = Page::new(1); + page[0] = 7; + assert_eq!(page[0], 7); + } + + /// Ensures shared() preserves written content. + #[test] + fn test_shared_preserves_content() { + let mut page = Page::new(1); + page[0] = 42; + let shared = page.shared(); + assert_eq!(shared[0], 42); + } +} diff --git a/kernel/src/types/table.rs b/kernel/src/types/table.rs index 6a4015b..b30f8b2 100644 --- a/kernel/src/types/table.rs +++ b/kernel/src/types/table.rs @@ -206,3 +206,36 @@ impl TryFrom for CowPage { } } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies table size selection at tier boundaries (2MB, 1GB). + #[test] + fn test_size_tiers() { + let small = Table::new(1); + assert_eq!(small.size(), 1 << 21); + + let large = Table::new((1 << 21) + 1); + assert_eq!(large.size(), 1 << 30); + } + + /// Ensures empty table slots return the correct default Null entry. + #[test] + fn test_get_returns_default_null() { + let table = Table::new(1); + let entry = table.get(10); + assert_eq!(entry, arca::Entry::Null(1 << 12)); + } + + /// Verifies set replaces the default entry and get retrieves it back. + #[test] + fn test_set_and_get_roundtrip() { + let mut table = Table::new(1); + let entry = arca::Entry::RWPage(arca::Page::from_inner(Page::new(1))); + let old = table.set(0, entry.clone()).unwrap(); + assert_eq!(old, arca::Entry::Null(1 << 12)); + assert_eq!(table.get(0), entry); + } +} diff --git a/kernel/src/types/tuple.rs b/kernel/src/types/tuple.rs index 8be3307..3257371 100644 --- a/kernel/src/types/tuple.rs +++ b/kernel/src/types/tuple.rs @@ -42,3 +42,28 @@ impl FromIterator for Tuple { Tuple::new(v) } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies new_with_len creates a tuple filled with Null values. + #[test] + fn test_new_with_len_defaults_to_null() { + let tuple = Tuple::new_with_len(2); + assert_eq!(tuple.len(), 2); + assert!(matches!(tuple[0], Value::Null(_))); + assert!(matches!(tuple[1], Value::Null(_))); + } + + /// Verifies FromIterator collects values into a correctly sized tuple. + #[test] + fn test_from_iter() { + let values: alloc::vec::Vec = + alloc::vec![Value::Word(1u64.into()), Value::Blob("x".into()),]; + let tuple: Tuple = values.clone().into_iter().collect(); + assert_eq!(tuple.len(), 2); + assert_eq!(tuple[0], values[0]); + assert_eq!(tuple[1], values[1]); + } +} diff --git a/kernel/src/types/value.rs b/kernel/src/types/value.rs index b4f9f3e..7fbd2c4 100644 --- a/kernel/src/types/value.rs +++ b/kernel/src/types/value.rs @@ -62,3 +62,33 @@ macro_rules! impl_value_from { foreach_type_item! {impl_tryfrom_value} foreach_type_item! {impl_value_from} + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies Word -> Value -> Word conversion round-trips correctly. + #[test] + fn test_word_roundtrip() { + let word = Word::new(99); + let value: Value = word.clone().into(); + let roundtrip = Word::try_from(value).unwrap(); + assert_eq!(roundtrip, word); + } + + /// Verifies Blob -> Value -> Blob conversion round-trips correctly. + #[test] + fn test_blob_roundtrip() { + let blob = Blob::new(b"data".to_vec()); + let value: Value = blob.clone().into(); + let roundtrip = Blob::try_from(value).unwrap(); + assert_eq!(roundtrip, blob); + } + + /// Ensures TryFrom fails when converting to the wrong variant type. + #[test] + fn test_mismatched_conversion_fails() { + let value: Value = Word::new(1).into(); + assert!(Blob::try_from(value).is_err()); + } +} diff --git a/kernel/src/types/word.rs b/kernel/src/types/word.rs index 749877f..291d4b5 100644 --- a/kernel/src/types/word.rs +++ b/kernel/src/types/word.rs @@ -36,3 +36,31 @@ impl AsMut for Word { &mut self.value } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Verifies Word::read returns the value passed to new. + #[test] + fn test_read() { + let word = Word::new(123); + assert_eq!(word.read(), 123); + } + + /// Verifies From and Into round-trip correctly. + #[test] + fn test_from_u64_roundtrip() { + let word = Word::from(0xdeadbeef_u64); + assert_eq!(u64::from(word), 0xdeadbeef); + } + + /// Verifies AsRef and AsMut provide access to the inner value. + #[test] + fn test_as_ref_as_mut() { + let mut word = Word::new(42); + assert_eq!(*word.as_ref(), 42); + *word.as_mut() = 99; + assert_eq!(word.read(), 99); + } +} diff --git a/modules/arca-musl b/modules/arca-musl index a83796a..a88bc69 160000 --- a/modules/arca-musl +++ b/modules/arca-musl @@ -1 +1 @@ -Subproject commit a83796a98c009ea92b9dc47a526b24b818b325b7 +Subproject commit a88bc6999eb736d93a0aab0afe07c99e4e1ec559 diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index e0413c8..040802e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(stable_features, unused_features)] #![feature(allocator_api)] #![feature(ptr_metadata)] #![feature(str_from_raw_parts)] From 65a2d288055afb13df19392b92b0242c7996fda1 Mon Sep 17 00:00:00 2001 From: Akshay Srivatsan Date: Mon, 20 Apr 2026 17:47:08 -0700 Subject: [PATCH 3/4] fix: update arca-musl submodule --- modules/arca-musl | 2 +- user/build.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/arca-musl b/modules/arca-musl index a88bc69..a83796a 160000 --- a/modules/arca-musl +++ b/modules/arca-musl @@ -1 +1 @@ -Subproject commit a88bc6999eb736d93a0aab0afe07c99e4e1ec559 +Subproject commit a83796a98c009ea92b9dc47a526b24b818b325b7 diff --git a/user/build.rs b/user/build.rs index 0f4a2d4..e3e1c2a 100644 --- a/user/build.rs +++ b/user/build.rs @@ -11,6 +11,7 @@ fn main() { .as_os_str() .to_string_lossy() .into_owned(); + println!("cargo::rerun-if-changed=../modules/arca-musl"); println!("cargo::rustc-link-search={prefix}/lib"); println!("cargo::rustc-link-lib=static=c"); } From f137c14ed17597c78b65e9dc128a71494fba367f Mon Sep 17 00:00:00 2001 From: Akshay Srivatsan Date: Tue, 21 Apr 2026 19:52:49 -0700 Subject: [PATCH 4/4] fix: stagecast compatibility --- Cargo.lock | 10 ---------- fix/build.rs | 1 + fix/shell/Cargo.toml | 1 - fix/shell/src/lib.rs | 16 +++++++++------- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a1a9c8..8f973f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,15 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "alloca" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" -dependencies = [ - "cc", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -770,7 +761,6 @@ dependencies = [ name = "fixshell" version = "0.1.0" dependencies = [ - "alloca", "anyhow", "arca", "arcane", diff --git a/fix/build.rs b/fix/build.rs index 6fe5f8b..dfd523a 100644 --- a/fix/build.rs +++ b/fix/build.rs @@ -114,6 +114,7 @@ fn c2elf(c: &[u8], h: &[u8]) -> Result> { "-nostartfiles", "--verbose", "-mcmodel=large", + "-static", // "-fno-pic", // "-fno-pie", // "-Wl,-no-pie", diff --git a/fix/shell/Cargo.toml b/fix/shell/Cargo.toml index 069a816..60ed64d 100644 --- a/fix/shell/Cargo.toml +++ b/fix/shell/Cargo.toml @@ -15,7 +15,6 @@ arca = { path = "../../arca" } user = { path = "../../user" } arcane = { path = "../../arcane/"} fixhandle = { path = "../handle", default-features = false} -alloca = "0.4.0" [build-dependencies] anyhow = "1.0.100" diff --git a/fix/shell/src/lib.rs b/fix/shell/src/lib.rs index 2e90ed0..7f51e25 100644 --- a/fix/shell/src/lib.rs +++ b/fix/shell/src/lib.rs @@ -74,6 +74,8 @@ pub unsafe extern "C" fn _rsstart() -> ! { main(); } +static mut MODULE_BUF: [u8; 1024] = [0; 1024]; + pub fn main() -> ! { let combination = os::argument(); let combination = @@ -83,13 +85,13 @@ pub fn main() -> ! { let result = unsafe { wasm_rt_init(); let module_size = wasm_rt_module_size(); - let result = alloca::with_alloca_zeroed(module_size, |module_buf| { - let module = &raw mut module_buf[0] as *mut c_void; - wasm2c_module_instantiate(module, core::ptr::null()); - let wasm_rt_externref_t { bytes: result } = - w2c_module_0x5Ffixpoint_apply(module, wasm_rt_externref_t { bytes: handle }); - result - }); + let module = unsafe { + assert!(module_size <= 1024); + &raw mut MODULE_BUF[0] as *mut c_void + }; + wasm2c_module_instantiate(module, core::ptr::null()); + let wasm_rt_externref_t { bytes: result } = + w2c_module_0x5Ffixpoint_apply(module, wasm_rt_externref_t { bytes: handle }); wasm_rt_free(); result };