diff --git a/Cargo.lock b/Cargo.lock index 7e1feca44c..2a22b9af18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2570,6 +2570,12 @@ dependencies = [ "syn 1.0.72", ] +[[package]] +name = "parity-wasm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" + [[package]] name = "parking_lot" version = "0.11.1" @@ -3642,6 +3648,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "roc_gen_wasm" +version = "0.1.0" +dependencies = [ + "bumpalo", + "indoc 0.3.6", + "libc", + "parity-wasm", + "pretty_assertions 0.5.1", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_load", + "roc_module", + "roc_mono", + "roc_std", + "roc_types", + "target-lexicon", + "tempfile", + "wasmer", + "wasmer-wasi", +] + [[package]] name = "roc_ident" version = "0.1.0" @@ -4348,6 +4377,7 @@ dependencies = [ "roc_collections", "roc_constrain", "roc_gen_llvm", + "roc_gen_wasm", "roc_load", "roc_module", "roc_mono", diff --git a/Cargo.toml b/Cargo.toml index 01b160bf9a..27242439f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ members = [ "compiler/load", "compiler/gen_llvm", "compiler/gen_dev", + "compiler/gen_wasm", "compiler/build", "compiler/arena_pool", "compiler/test_gen", diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml new file mode 100644 index 0000000000..2804be6eab --- /dev/null +++ b/compiler/gen_wasm/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "roc_gen_wasm" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +roc_collections = { path = "../collections" } +roc_module = { path = "../module" } +roc_mono = { path = "../mono" } +bumpalo = { version = "3.6.1", features = ["collections"] } +parity-wasm = "0.42" + +roc_std = { path = "../../roc_std" } +wasmer = "2.0.0" +wasmer-wasi = "2.0.0" + +[dev-dependencies] +roc_can = { path = "../can" } +roc_builtins = { path = "../builtins" } +roc_load = { path = "../load" } +roc_types = { path = "../types" } +roc_module = { path = "../module" } +indoc = "0.3.3" +pretty_assertions = "0.5.1" +libc = "0.2" +target-lexicon = "0.12.2" +tempfile = "3.1.0" diff --git a/compiler/gen_wasm/src/from_wasm32_memory.rs b/compiler/gen_wasm/src/from_wasm32_memory.rs new file mode 100644 index 0000000000..d544488f7b --- /dev/null +++ b/compiler/gen_wasm/src/from_wasm32_memory.rs @@ -0,0 +1,230 @@ +use roc_std::{RocDec, RocList, RocOrder, RocStr}; + +pub trait FromWasm32Memory: Sized { + const SIZE_OF_WASM: usize; + const ALIGN_OF_WASM: usize; + const ACTUAL_WIDTH: usize = if (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM) == 0 { + Self::SIZE_OF_WASM + } else { + Self::SIZE_OF_WASM + (Self::ALIGN_OF_WASM - (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM)) + }; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self; +} + +macro_rules! from_wasm_memory_primitive_decode { + ($type_name:ident) => { + const SIZE_OF_WASM: usize = core::mem::size_of::<$type_name>(); + const ALIGN_OF_WASM: usize = core::mem::align_of::<$type_name>(); + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + use core::mem::MaybeUninit; + + let mut output: MaybeUninit = MaybeUninit::uninit(); + let width = std::mem::size_of::(); + + let ptr = output.as_mut_ptr(); + let raw_ptr = ptr as *mut u8; + let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) }; + + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset as u32); + let foobar = (ptr.deref(memory, 0, width as u32)).unwrap(); + let wasm_slice = unsafe { std::mem::transmute(foobar) }; + + slice.copy_from_slice(wasm_slice); + + unsafe { output.assume_init() } + } + }; +} + +macro_rules! from_wasm_memory_primitive { + ($($type_name:ident ,)+) => { + $( + impl FromWasm32Memory for $type_name { + from_wasm_memory_primitive_decode!($type_name); + } + )* + } +} + +from_wasm_memory_primitive!( + u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder, +); + +impl FromWasm32Memory for () { + const SIZE_OF_WASM: usize = 0; + const ALIGN_OF_WASM: usize = 0; + + fn decode(_: &wasmer::Memory, _: u32) -> Self {} +} + +impl FromWasm32Memory for RocStr { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let bytes = ::decode(memory, offset); + + let length = (bytes >> 32) as u32; + let elements = bytes as u32; + + if length == 0 { + RocStr::default() + } else if (length as i32) < 0 { + // this is a small string + let last_byte = bytes.to_ne_bytes()[7]; + let actual_length = (last_byte ^ 0b1000_0000) as usize; + + let slice = &bytes.to_ne_bytes()[..actual_length as usize]; + RocStr::from_slice(slice) + } else { + // this is a big string + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); + let foobar = (ptr.deref(memory, 0, length)).unwrap(); + let wasm_slice = unsafe { std::mem::transmute(foobar) }; + + RocStr::from_slice(wasm_slice) + } + } +} + +impl FromWasm32Memory for RocList { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let bytes = ::decode(memory, offset); + + let length = (bytes >> 32) as u32; + let elements = bytes as u32; + + let mut items = Vec::with_capacity(length as usize); + + for i in 0..length { + let item = ::decode( + memory, + elements + i * ::SIZE_OF_WASM as u32, + ); + items.push(item); + } + + RocList::from_slice(&items) + } +} + +impl FromWasm32Memory for &'_ [T] { + const SIZE_OF_WASM: usize = 8; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let bytes = ::decode(memory, offset); + + let length = (bytes >> 32) as u32; + let elements = bytes as u32; + + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); + let width = ::SIZE_OF_WASM as u32 * length; + let foobar = (ptr.deref(memory, 0, width)).unwrap(); + let wasm_slice = + unsafe { std::slice::from_raw_parts(foobar as *const _ as *const _, length as usize) }; + + wasm_slice + } +} + +impl FromWasm32Memory for &'_ T { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let elements = ::decode(memory, offset); + + let actual = ::decode(memory, elements); + + let b = Box::new(actual); + + std::boxed::Box::::leak(b) + } +} + +impl FromWasm32Memory for [T; N] { + const SIZE_OF_WASM: usize = N * T::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = T::ALIGN_OF_WASM; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset); + let width = ::SIZE_OF_WASM as u32 * N as u32; + let foobar = (ptr.deref(memory, 0, width)).unwrap(); + let wasm_slice: &[T; N] = unsafe { &*(foobar as *const _ as *const [T; N]) }; + + wasm_slice.clone() + } +} + +impl FromWasm32Memory for usize { + const SIZE_OF_WASM: usize = 4; + const ALIGN_OF_WASM: usize = 4; + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + ::decode(memory, offset) as usize + } +} + +impl FromWasm32Memory for (T, U) { + const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = max2(T::SIZE_OF_WASM, U::SIZE_OF_WASM); + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + assert!( + T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, + "this function does not handle alignment" + ); + + let t = ::decode(memory, offset); + + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + + (t, u) + } +} + +const fn max2(a: usize, b: usize) -> usize { + if a > b { + a + } else { + b + } +} + +const fn max3(a: usize, b: usize, c: usize) -> usize { + max2(max2(a, b), c) +} + +impl FromWasm32Memory for (T, U, V) { + const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM; + const ALIGN_OF_WASM: usize = max3(T::SIZE_OF_WASM, U::SIZE_OF_WASM, V::SIZE_OF_WASM); + + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + assert!( + T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, + "this function does not handle alignment" + ); + + assert!( + U::ALIGN_OF_WASM >= V::ALIGN_OF_WASM, + "this function does not handle alignment" + ); + + let t = ::decode(memory, offset); + + let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); + + let v = ::decode( + memory, + offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32, + ); + + (t, u, v) + } +} diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs new file mode 100644 index 0000000000..b620f374d7 --- /dev/null +++ b/compiler/gen_wasm/src/lib.rs @@ -0,0 +1 @@ +pub mod from_wasm32_memory; diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index e255efcf5a..ab08fc1527 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -23,6 +23,7 @@ roc_can = { path = "../can" } roc_parse = { path = "../parse" } roc_build = { path = "../build" } roc_std = { path = "../../roc_std" } +roc_gen_wasm = { path = "../gen_wasm" } im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.6.1", features = ["collections"] } diff --git a/compiler/test_gen/src/helpers/eval.rs b/compiler/test_gen/src/helpers/eval.rs index f380b8ac88..94c8ac7756 100644 --- a/compiler/test_gen/src/helpers/eval.rs +++ b/compiler/test_gen/src/helpers/eval.rs @@ -6,9 +6,9 @@ use roc_can::builtins::builtin_defs_map; use roc_can::def::Def; use roc_collections::all::{MutMap, MutSet}; use roc_gen_llvm::llvm::externs::add_default_roc_externs; +use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; use roc_module::symbol::Symbol; use roc_mono::ir::OptLevel; -use roc_std::{RocDec, RocList, RocOrder, RocStr}; use roc_types::subs::VarStore; use target_lexicon::Triple; @@ -500,7 +500,7 @@ fn fake_wasm_main_function(_: u32, _: u32) -> u32 { #[allow(dead_code)] pub fn assert_wasm_evals_to_help(src: &str, ignore_problems: bool) -> Result where - T: FromWasmMemory, + T: FromWasm32Memory, { let arena = bumpalo::Bump::new(); let context = inkwell::context::Context::create(); @@ -534,7 +534,7 @@ where _ => panic!(), }; - let output = ::decode( + let output = ::decode( memory, // skip the RocCallResult tag id address as u32 + 8, @@ -643,232 +643,3 @@ macro_rules! assert_non_opt_evals_to { $crate::assert_llvm_evals_to!($src, $expected, $ty, $transform); }}; } - -pub trait FromWasmMemory: Sized { - const SIZE_OF_WASM: usize; - const ALIGN_OF_WASM: usize; - const ACTUAL_WIDTH: usize = if (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM) == 0 { - Self::SIZE_OF_WASM - } else { - Self::SIZE_OF_WASM + (Self::ALIGN_OF_WASM - (Self::SIZE_OF_WASM % Self::ALIGN_OF_WASM)) - }; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self; -} - -macro_rules! from_wasm_memory_primitive_decode { - ($type_name:ident) => { - const SIZE_OF_WASM: usize = core::mem::size_of::<$type_name>(); - const ALIGN_OF_WASM: usize = core::mem::align_of::<$type_name>(); - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - use core::mem::MaybeUninit; - - let mut output: MaybeUninit = MaybeUninit::uninit(); - let width = std::mem::size_of::(); - - let ptr = output.as_mut_ptr(); - let raw_ptr = ptr as *mut u8; - let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) }; - - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset as u32); - let foobar = (ptr.deref(memory, 0, width as u32)).unwrap(); - let wasm_slice = unsafe { std::mem::transmute(foobar) }; - - slice.copy_from_slice(wasm_slice); - - unsafe { output.assume_init() } - } - }; -} - -macro_rules! from_wasm_memory_primitive { - ($($type_name:ident ,)+) => { - $( - impl FromWasmMemory for $type_name { - from_wasm_memory_primitive_decode!($type_name); - } - )* - } -} - -from_wasm_memory_primitive!( - u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, f32, f64, bool, RocDec, RocOrder, -); - -impl FromWasmMemory for () { - const SIZE_OF_WASM: usize = 0; - const ALIGN_OF_WASM: usize = 0; - - fn decode(_: &wasmer::Memory, _: u32) -> Self {} -} - -impl FromWasmMemory for RocStr { - const SIZE_OF_WASM: usize = 8; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); - - let length = (bytes >> 32) as u32; - let elements = bytes as u32; - - if length == 0 { - RocStr::default() - } else if (length as i32) < 0 { - // this is a small string - let last_byte = bytes.to_ne_bytes()[7]; - let actual_length = (last_byte ^ 0b1000_0000) as usize; - - let slice = &bytes.to_ne_bytes()[..actual_length as usize]; - RocStr::from_slice(slice) - } else { - // this is a big string - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); - let foobar = (ptr.deref(memory, 0, length)).unwrap(); - let wasm_slice = unsafe { std::mem::transmute(foobar) }; - - RocStr::from_slice(wasm_slice) - } - } -} - -impl FromWasmMemory for RocList { - const SIZE_OF_WASM: usize = 8; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); - - let length = (bytes >> 32) as u32; - let elements = bytes as u32; - - let mut items = Vec::with_capacity(length as usize); - - for i in 0..length { - let item = ::decode( - memory, - elements + i * ::SIZE_OF_WASM as u32, - ); - items.push(item); - } - - RocList::from_slice(&items) - } -} - -impl FromWasmMemory for &'_ [T] { - const SIZE_OF_WASM: usize = 8; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let bytes = ::decode(memory, offset); - - let length = (bytes >> 32) as u32; - let elements = bytes as u32; - - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(elements); - let width = ::SIZE_OF_WASM as u32 * length; - let foobar = (ptr.deref(memory, 0, width)).unwrap(); - let wasm_slice = - unsafe { std::slice::from_raw_parts(foobar as *const _ as *const _, length as usize) }; - - wasm_slice - } -} - -impl FromWasmMemory for &'_ T { - const SIZE_OF_WASM: usize = 4; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let elements = ::decode(memory, offset); - - let actual = ::decode(memory, elements); - - let b = Box::new(actual); - - std::boxed::Box::::leak(b) - } -} - -impl FromWasmMemory for [T; N] { - const SIZE_OF_WASM: usize = N * T::SIZE_OF_WASM; - const ALIGN_OF_WASM: usize = T::ALIGN_OF_WASM; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - let ptr: wasmer::WasmPtr = wasmer::WasmPtr::new(offset); - let width = ::SIZE_OF_WASM as u32 * N as u32; - let foobar = (ptr.deref(memory, 0, width)).unwrap(); - let wasm_slice: &[T; N] = unsafe { &*(foobar as *const _ as *const [T; N]) }; - - wasm_slice.clone() - } -} - -impl FromWasmMemory for usize { - const SIZE_OF_WASM: usize = 4; - const ALIGN_OF_WASM: usize = 4; - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - ::decode(memory, offset) as usize - } -} - -impl FromWasmMemory for (T, U) { - const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM; - const ALIGN_OF_WASM: usize = max2(T::SIZE_OF_WASM, U::SIZE_OF_WASM); - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - assert!( - T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, - "this function does not handle alignment" - ); - - let t = ::decode(memory, offset); - - let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); - - (t, u) - } -} - -const fn max2(a: usize, b: usize) -> usize { - if a > b { - a - } else { - b - } -} - -const fn max3(a: usize, b: usize, c: usize) -> usize { - max2(max2(a, b), c) -} - -impl FromWasmMemory for (T, U, V) { - const SIZE_OF_WASM: usize = T::SIZE_OF_WASM + U::SIZE_OF_WASM + V::SIZE_OF_WASM; - const ALIGN_OF_WASM: usize = max3(T::SIZE_OF_WASM, U::SIZE_OF_WASM, V::SIZE_OF_WASM); - - fn decode(memory: &wasmer::Memory, offset: u32) -> Self { - assert!( - T::ALIGN_OF_WASM >= U::ALIGN_OF_WASM, - "this function does not handle alignment" - ); - - assert!( - U::ALIGN_OF_WASM >= V::ALIGN_OF_WASM, - "this function does not handle alignment" - ); - - let t = ::decode(memory, offset); - - let u = ::decode(memory, offset + T::ACTUAL_WIDTH as u32); - - let v = ::decode( - memory, - offset + T::ACTUAL_WIDTH as u32 + U::ACTUAL_WIDTH as u32, - ); - - (t, u, v) - } -}