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..8f973f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,6 +279,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 +324,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 +344,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 +688,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 +701,22 @@ 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", @@ -700,6 +724,52 @@ dependencies = [ "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", + "trait-variant", + "user", +] + +[[package]] +name = "fixshell" +version = "0.1.0" +dependencies = [ + "anyhow", + "arca", + "arcane", + "bindgen", + "cc", + "fixhandle", + "user", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -959,7 +1029,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/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/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/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 5af14d8..78ea10b 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,24 +1,19 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![allow(stable_features, unused_features)] #![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..dfd523a 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,15 @@ fn c2elf(c: &[u8], h: &[u8]) -> Result> { "-frounding-math", // "-fsignaling-nans", "-ffreestanding", - // "-nostdlib", + "-nostdlib", "-nostartfiles", - "-mcmodel=large", "--verbose", - "-Wl,-no-pie", + "-mcmodel=large", + "-static", + // "-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 +134,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 +150,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 +172,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..60ed64d --- /dev/null +++ b/fix/shell/Cargo.toml @@ -0,0 +1,22 @@ +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} + +[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..7f51e25 --- /dev/null +++ b/fix/shell/src/lib.rs @@ -0,0 +1,99 @@ +#![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(); +} + +static mut MODULE_BUF: [u8; 1024] = [0; 1024]; + +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 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 + }; + 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/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/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..48aba55 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -1,15 +1,14 @@ #![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(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/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/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/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/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/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/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..b30f8b2 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}"); } } } @@ -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/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..e3e1c2a 100644 --- a/user/build.rs +++ b/user/build.rs @@ -6,10 +6,12 @@ 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(); + println!("cargo::rerun-if-changed=../modules/arca-musl"); println!("cargo::rustc-link-search={prefix}/lib"); println!("cargo::rustc-link-lib=static=c"); } 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..040802e 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -1,11 +1,9 @@ +#![allow(stable_features, unused_features)] #![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;