wasm test harnass

This commit is contained in:
Folkert 2021-08-29 00:36:16 +02:00
parent b57632e4cc
commit 71c0a325ee
13 changed files with 258 additions and 154 deletions

View file

@ -32,10 +32,11 @@ libc = "0.2"
inkwell = { path = "../../vendor/inkwell" } inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.2" target-lexicon = "0.12.2"
libloading = "0.6" libloading = "0.6"
[dev-dependencies]
wasmer = "2.0.0" wasmer = "2.0.0"
wasmer-wasi = "2.0.0" wasmer-wasi = "2.0.0"
tempfile = "3.1.0"
[dev-dependencies]
maplit = "1.0.1" maplit = "1.0.1"
quickcheck = "0.8" quickcheck = "0.8"
quickcheck_macros = "0.8" quickcheck_macros = "0.8"

View file

@ -1,7 +1,6 @@
#[cfg(test)] #[cfg(test)]
mod gen_compare { mod gen_compare {
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
#[test] #[test]

View file

@ -1,7 +1,6 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::RocStr; use roc_std::RocStr;

View file

@ -1,7 +1,6 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
#[test] #[test]

View file

@ -1,7 +1,6 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use crate::helpers::with_larger_debug_stack; use crate::helpers::with_larger_debug_stack;
use core::ffi::c_void; use core::ffi::c_void;
use indoc::indoc; use indoc::indoc;

View file

@ -1,7 +1,7 @@
#[cfg(test)] #[cfg(test)]
mod gen_num { mod gen_num {
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; // use crate::assert_wasm_evals_to as assert_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::{RocDec, RocOrder}; use roc_std::{RocDec, RocOrder};

View file

@ -3,9 +3,9 @@
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to; use crate::assert_llvm_evals_to;
use crate::assert_non_opt_evals_to; use crate::assert_non_opt_evals_to;
use crate::helpers::eval; use crate::assert_wasm_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::RocStr; use roc_std::{RocList, RocStr};
#[test] #[test]
fn basic_int() { fn basic_int() {
@ -2781,25 +2781,81 @@ fn value_not_exposed_hits_panic() {
} }
#[test] #[test]
fn wasm_test() { fn wasm_test_i32() {
use bumpalo::Bump; assert_wasm_evals_to!(
use inkwell::context::Context; indoc!(
r#"
app "test" provides [ main ] to "./platform"
let arena = Bump::new(); main : I32
let context = Context::create(); main = 4 + 5
"#
),
9,
i32
);
}
// NOTE the stdlib must be in the arena; just taking a reference will segfault #[test]
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib()); fn wasm_test_small_string() {
assert_wasm_evals_to!(
let source = indoc!( indoc!(
r#" r#"
app "test" provides [ main ] to "./platform" app "test" provides [ main ] to "./platform"
main : Str main : Str
main = "hellow" main = "hello"
"# "#
),
RocStr::from_slice(b"hello"),
RocStr
);
}
#[test]
fn wasm_test_big_string() {
assert_wasm_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main : Str
main = "goodday may fellow human"
"#
),
RocStr::from_slice(b"goodday may fellow human"),
RocStr
);
}
#[test]
fn wasm_test_u8() {
assert_wasm_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main : U8
main = 3 + 6
"#
),
3 + 6,
u8
);
}
#[test]
fn wasm_test_list_u8() {
assert_wasm_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main : List U8
main = [ 3 + 6 , 5, 6 ]
"#
),
RocList::from_slice(&[3 + 6, 5, 6]),
RocList<u8>
); );
let arena = bumpalo::Bump::new();
eval::helper_wasm(&arena, source, stdlib, true, false, &context);
} }

View file

@ -1,7 +1,6 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
#[test] #[test]

View file

@ -1,7 +1,6 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
#[test] #[test]

View file

@ -1,7 +1,6 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
#[test] #[test]

View file

@ -1,7 +1,6 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::RocStr; use roc_std::RocStr;
use std::cmp::min; use std::cmp::min;

View file

@ -1,7 +1,6 @@
#![cfg(test)] #![cfg(test)]
use crate::assert_evals_to; use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc; use indoc::indoc;
use roc_std::{RocList, RocStr}; use roc_std::{RocList, RocStr};

View file

@ -8,7 +8,7 @@ use roc_collections::all::{MutMap, MutSet};
use roc_gen_llvm::llvm::externs::add_default_roc_externs; use roc_gen_llvm::llvm::externs::add_default_roc_externs;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use roc_std::RocStr; use roc_std::{RocDec, RocList, RocOrder, RocStr};
use roc_types::subs::VarStore; use roc_types::subs::VarStore;
use target_lexicon::Triple; use target_lexicon::Triple;
@ -290,7 +290,6 @@ pub fn helper<'a>(
(main_fn_name, delayed_errors, lib) (main_fn_name, delayed_errors, lib)
} }
#[cfg(test)]
fn wasm32_target_tripple() -> Triple { fn wasm32_target_tripple() -> Triple {
use target_lexicon::{Architecture, BinaryFormat}; use target_lexicon::{Architecture, BinaryFormat};
@ -302,7 +301,6 @@ fn wasm32_target_tripple() -> Triple {
triple triple
} }
#[cfg(test)]
pub fn helper_wasm<'a>( pub fn helper_wasm<'a>(
arena: &'a bumpalo::Bump, arena: &'a bumpalo::Bump,
src: &str, src: &str,
@ -310,7 +308,7 @@ pub fn helper_wasm<'a>(
is_gen_test: bool, is_gen_test: bool,
ignore_problems: bool, ignore_problems: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) { ) -> wasmer::Instance {
let target = wasm32_target_tripple(); let target = wasm32_target_tripple();
let opt_level = if cfg!(debug_assertions) { let opt_level = if cfg!(debug_assertions) {
@ -332,19 +330,26 @@ pub fn helper_wasm<'a>(
use inkwell::targets::{InitializationConfig, Target, TargetTriple}; use inkwell::targets::{InitializationConfig, Target, TargetTriple};
Target::initialize_webassembly(&InitializationConfig::default()); let dir = tempfile::tempdir().unwrap();
// let target = Target::from_name("wasm32-unknown-unknown").unwrap();
// let triple = TargetTriple::create("wasm32-wasi"); let test_a_path = dir.path().join("test.a");
let triple = TargetTriple::create("wasm32-unknown-unknown-wasi"); let test_wasm_path = dir.path().join("main.wasm");
let target_machine = Target::from_triple(&triple)
Target::initialize_webassembly(&InitializationConfig::default());
let triple = TargetTriple::create("wasm32-unknown-unknown-wasm");
llvm_module.set_triple(&triple);
llvm_module.set_source_file_name("Test.roc");
let target_machine = Target::from_name("wasm32")
.unwrap() .unwrap()
.create_target_machine( .create_target_machine(
&triple, &triple,
"generic", "",
"", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features. "", // TODO: this probably should be TargetMachine::get_host_cpu_features() to enable all features.
inkwell::OptimizationLevel::None, inkwell::OptimizationLevel::None,
inkwell::targets::RelocMode::PIC, inkwell::targets::RelocMode::Default,
inkwell::targets::CodeModel::Default, inkwell::targets::CodeModel::Default,
) )
.unwrap(); .unwrap();
@ -352,11 +357,7 @@ pub fn helper_wasm<'a>(
let file_type = inkwell::targets::FileType::Object; let file_type = inkwell::targets::FileType::Object;
target_machine target_machine
.write_to_file( .write_to_file(llvm_module, file_type, &test_a_path)
llvm_module,
file_type,
std::path::Path::new("/home/folkertdev/roc/wasm/test.a"),
)
.unwrap(); .unwrap();
use std::process::Command; use std::process::Command;
@ -364,11 +365,11 @@ pub fn helper_wasm<'a>(
// .env_clear() // .env_clear()
// .env("PATH", env_path) // .env("PATH", env_path)
// .env("HOME", env_home) // .env("HOME", env_home)
.current_dir("/home/folkertdev/roc/wasm") .current_dir(dir.path())
.args(&[ .args(&[
"build-lib", "build-lib",
"main.zig", "/home/folkertdev/roc/wasm/main.zig",
"test.a", test_a_path.to_str().unwrap(),
"-target", "-target",
"wasm32-wasi", "wasm32-wasi",
"-dynamic", "-dynamic",
@ -377,11 +378,12 @@ pub fn helper_wasm<'a>(
.status() .status()
.unwrap(); .unwrap();
{ // now, do wasmer stuff
use wasmer::{Function, Instance, Module, Store, Value};
use wasmer::{Function, Instance, Module, Store};
let store = Store::default(); let store = Store::default();
let module = Module::from_file(&store, "/home/folkertdev/roc/wasm/main.wasm").unwrap(); let module = Module::from_file(&store, &test_wasm_path).unwrap();
// First, we create the `WasiEnv` // First, we create the `WasiEnv`
use wasmer_wasi::WasiState; use wasmer_wasi::WasiState;
@ -391,107 +393,72 @@ pub fn helper_wasm<'a>(
.finalize() .finalize()
.unwrap(); .unwrap();
println!("Instantiating module with WASI imports...");
// Then, we get the import object related to our WASI // Then, we get the import object related to our WASI
// and attach it to the Wasm instance. // and attach it to the Wasm instance.
let mut import_object = wasi_env.import_object(&module).unwrap(); let mut import_object = wasi_env.import_object(&module).unwrap();
let main_function = Function::new_native(&store, bar); let main_function = Function::new_native(&store, fake_wasm_main_function);
let ext = wasmer::Extern::Function(main_function); let ext = wasmer::Extern::Function(main_function);
let mut exts = wasmer::Exports::new(); let mut exts = wasmer::Exports::new();
exts.insert("main", ext); exts.insert("main", ext);
import_object.register("env", exts); import_object.register("env", exts);
let instance = Instance::new(&module, &import_object).unwrap(); Instance::new(&module, &import_object).unwrap()
}
#[allow(dead_code)]
fn fake_wasm_main_function(_: u32, _: u32) -> u32 {
panic!("wasm entered the main function; this should never happen!")
}
#[macro_export]
macro_rules! assert_wasm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems:expr) => {
let arena = bumpalo::Bump::new();
let context = inkwell::context::Context::create();
// NOTE the stdlib must be in the arena; just taking a reference will segfault
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let is_gen_test = true;
let instance = $crate::helpers::eval::helper_wasm(
&arena,
$src,
stdlib,
is_gen_test,
$ignore_problems,
&context,
);
let memory = instance.exports.get_memory("memory").unwrap(); let memory = instance.exports.get_memory("memory").unwrap();
let add_one = instance.exports.get_function("test_wrapper").unwrap(); let test_wrapper = instance.exports.get_function("test_wrapper").unwrap();
let result = add_one.call(&[]).unwrap();
match test_wrapper.call(&[]) {
Err(e) => println!("{:?}", e),
Ok(result) => {
let address = match result[0] { let address = match result[0] {
Value::I32(a) => a, wasmer::Value::I32(a) => a,
_ => panic!(), _ => panic!(),
}; };
let output = <u64 as FromWasmMemory>::decode(&memory, address as u32 + 8); let output = <$ty as $crate::helpers::eval::FromWasmMemory>::decode(
memory,
address as u32 + 8,
);
dbg!(output); assert_eq!(output, $expected)
// assert_eq!(output, 222);
}
todo!()
}
#[cfg(test)]
trait FromWasmMemory {
fn decode(memory: &wasmer::Memory, offset: u32) -> Self;
}
#[cfg(test)]
impl FromWasmMemory for u32 {
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let mut output: Self = 0;
let width = std::mem::size_of::<Self>();
let ptr = (&mut output) as *mut Self;
let raw_ptr = ptr as *mut u8;
let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) };
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = 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);
output
} }
} }
};
#[cfg(test)] ($src:expr, $expected:expr, $ty:ty) => {
impl FromWasmMemory for u64 { $crate::assert_wasm_evals_to!($src, $expected, $ty, |x| x, false);
fn decode(memory: &wasmer::Memory, offset: u32) -> Self { };
let mut output: Self = 0;
let width = std::mem::size_of::<Self>();
let ptr = (&mut output) as *mut Self; ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
let raw_ptr = ptr as *mut u8; $crate::assert_wasm_evals_to!($src, $expected, $ty, $transform, false);
let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) }; };
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = 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);
output
}
}
#[cfg(test)]
impl FromWasmMemory for RocStr {
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
use core::mem::MaybeUninit;
let mut output = MaybeUninit::uninit();
let width = std::mem::size_of::<Self>();
let raw_ptr = (&mut output) as *mut _ as *mut u8;
let slice = unsafe { std::slice::from_raw_parts_mut(raw_ptr, width) };
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = 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() }
}
}
fn bar(_: u32, _: u32) -> u32 {
println!("we are in main!");
return 0;
} }
#[macro_export] #[macro_export]
@ -527,7 +494,7 @@ macro_rules! assert_llvm_evals_to {
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
assert_llvm_evals_to!($src, $expected, $ty, $transform, false); $crate::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
}; };
} }
@ -539,7 +506,7 @@ macro_rules! assert_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument. // Same as above, except with an additional transformation argument.
{ {
assert_llvm_evals_to!($src, $expected, $ty, $transform, false); $crate::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
} }
}; };
} }
@ -547,15 +514,104 @@ macro_rules! assert_evals_to {
#[macro_export] #[macro_export]
macro_rules! assert_non_opt_evals_to { macro_rules! assert_non_opt_evals_to {
($src:expr, $expected:expr, $ty:ty) => {{ ($src:expr, $expected:expr, $ty:ty) => {{
assert_llvm_evals_to!($src, $expected, $ty, (|val| val)); $crate::assert_llvm_evals_to!($src, $expected, $ty, (|val| val));
}}; }};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => { ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
// Same as above, except with an additional transformation argument. // Same as above, except with an additional transformation argument.
{ {
assert_llvm_evals_to!($src, $expected, $ty, $transform, false); $crate::assert_llvm_evals_to!($src, $expected, $ty, $transform, false);
} }
}; };
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {{ ($src:expr, $expected:expr, $ty:ty, $transform:expr) => {{
assert_llvm_evals_to!($src, $expected, $ty, $transform); $crate::assert_llvm_evals_to!($src, $expected, $ty, $transform);
}}; }};
} }
pub trait FromWasmMemory {
fn decode(memory: &wasmer::Memory, offset: u32) -> Self;
}
macro_rules! from_wasm_memory_primitive_decode {
($type_name:ident) => {
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
use core::mem::MaybeUninit;
let mut output: MaybeUninit<Self> = MaybeUninit::uninit();
let width = std::mem::size_of::<Self>();
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<u8, wasmer::Array> = 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 RocStr {
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let bytes = <u64 as FromWasmMemory>::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<u8, wasmer::Array> = 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<T: FromWasmMemory + Clone> FromWasmMemory for RocList<T> {
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let bytes = <u64 as FromWasmMemory>::decode(memory, offset);
let length = (bytes >> 32) as u32;
let elements = bytes as u32;
let ptr: wasmer::WasmPtr<u8, wasmer::Array> = wasmer::WasmPtr::new(elements);
let foobar = (ptr.deref(memory, 0, core::mem::size_of::<T>() as u32 * length)).unwrap();
let wasm_slice = unsafe { std::mem::transmute(foobar) };
RocList::from_slice(wasm_slice)
}
}
impl FromWasmMemory for usize {
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
<u32 as FromWasmMemory>::decode(memory, offset) as usize
}
}