From 26ce34e4e8914bd4f96654bafc3eb8e0b1e51a35 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 15 Feb 2022 23:58:03 +0000 Subject: [PATCH 01/18] repl_www: add WASI imports for app --- repl_www/public/repl.js | 9 +- repl_www/public/wasi.js | 192 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 repl_www/public/wasi.js diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index ecd07d8b5b..336c08e9e8 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -3,6 +3,7 @@ window.js_create_app = js_create_app; window.js_run_app = js_run_app; window.js_get_result_and_memory = js_get_result_and_memory; import * as roc_repl_wasm from "./roc_repl_wasm.js"; +import { getMockWasiImports } from "./wasi.js"; // ---------------------------------------------------------------------------- // REPL state @@ -74,7 +75,13 @@ async function processInputQueue() { // Create an executable Wasm instance from an array of bytes // (Browser validates the module and does the final compilation to the host's machine code.) async function js_create_app(wasm_module_bytes) { - const { instance } = await WebAssembly.instantiate(wasm_module_bytes); + const wasiLinkObject = {}; // gives the WASI functions a reference to the app so they can write to its memory + const importObj = getMockWasiImports(wasiLinkObject); + const { instance } = await WebAssembly.instantiate( + wasm_module_bytes, + importObj + ); + wasiLinkObject.instance = instance; repl.app = instance; } diff --git a/repl_www/public/wasi.js b/repl_www/public/wasi.js new file mode 100644 index 0000000000..f636d055c7 --- /dev/null +++ b/repl_www/public/wasi.js @@ -0,0 +1,192 @@ +/** + * Browser implementation of the WebAssembly System Interface (WASI) + * The REPL generates an "app" from the user's Roc code and it can import from WASI + * + * We only implement writes to stdout/stderr as console.log/console.err, and proc_exit as `throw` + * The rest of the interface just consists of dummy functions with the right number of arguments + * + * The wasiLinkObject provides a reference to the app so we can write to its memory + */ + +export function getMockWasiImports(wasiLinkObject) { + const decoder = new TextDecoder(); + + // If the app ever resizes its memory, there will be a new buffer instance + // so we get a fresh reference every time, just in case + function getMemory8() { + return new Uint8Array(wasiLinkObject.instance.exports.memory.buffer); + } + + function getMemory32() { + return new Uint32Array(wasiLinkObject.instance.exports.memory.buffer); + } + + // fd_close : (i32) -> i32 + // Close a file descriptor. Note: This is similar to close in POSIX. + // https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_close.html + function fd_close(fd) { + console.warn(`fd_close: ${{ fd }}`); + return 0; // error code + } + + // fd_fdstat_get : (i32, i32) -> i32 + // Get the attributes of a file descriptor. + // https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_fdstat_get.html + function fd_fdstat_get(fd, stat_mut_ptr) { + /* + Tell WASI that stdout is a tty (no seek or tell) + + https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/isatty.c + + *Not* a tty if: + (statbuf.fs_filetype != __WASI_FILETYPE_CHARACTER_DEVICE || + (statbuf.fs_rights_base & (__WASI_RIGHTS_FD_SEEK | __WASI_RIGHTS_FD_TELL)) != 0) + + So it's sufficient to set: + .fs_filetype = __WASI_FILETYPE_CHARACTER_DEVICE + .fs_rights_base = 0 + + https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/headers/public/wasi/api.h + + typedef uint8_t __wasi_filetype_t; + typedef uint16_t __wasi_fdflags_t; + typedef uint64_t __wasi_rights_t; + #define __WASI_FILETYPE_CHARACTER_DEVICE (UINT8_C(2)) + typedef struct __wasi_fdstat_t { // 24 bytes total + __wasi_filetype_t fs_filetype; // 1 byte + // 1 byte padding + __wasi_fdflags_t fs_flags; // 2 bytes + // 4 bytes padding + __wasi_rights_t fs_rights_base; // 8 bytes + __wasi_rights_t fs_rights_inheriting; // 8 bytes + } __wasi_fdstat_t; + */ + // console.warn(`fd_fdstat_get: ${{ fd, stat_mut_ptr }}`); + const WASI_FILETYPE_CHARACTER_DEVICE = 2; + const memory8 = getMemory8(); + memory8[stat_mut_ptr] = WASI_FILETYPE_CHARACTER_DEVICE; + memory8.slice(stat_mut_ptr + 1, stat_mut_ptr + 24).fill(0); + + return 0; // error code + } + + // fd_seek : (i32, i64, i32, i32) -> i32 + // Move the offset of a file descriptor. Note: This is similar to lseek in POSIX. + // https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_seek.html + function fd_seek(fd, offset, whence, newoffset_mut_ptr) { + console.warn(`fd_seek: ${{ fd, offset, whence, newoffset_mut_ptr }}`); + return 0; + } + + // fd_write : (i32, i32, i32, i32) -> i32 + // Write to a file descriptor. Note: This is similar to `writev` in POSIX. + // https://docs.rs/wasi/latest/wasi/wasi_snapshot_preview1/fn.fd_write.html + function fd_write(fd, iovs_ptr, iovs_len, nwritten_mut_ptr) { + let string_buffer = ""; + let nwritten = 0; + const memory32 = getMemory32(); + + for (let i = 0; i < iovs_len; i++) { + const index32 = iovs_ptr >> 2; + const base = memory32[index32]; + const len = memory32[index32 + 1]; + iovs_ptr += 8; + + if (!len) continue; + + nwritten += len; + + // For some reason we often get negative-looking buffer lengths with junk data. + // Just skip the console.log, but still increase nwritten or it will loop forever. + // Dunno why this happens, but it's working fine for printf debugging ¯\_(ツ)_/¯ + if (len >> 31) { + break; + } + + const buf = getMemory8().slice(base, base + len); + const chunk = decoder.decode(buf); + string_buffer += chunk; + } + memory32[nwritten_mut_ptr >> 2] = nwritten; + if (string_buffer) { + console.log(string_buffer); + } + return 0; + } + + // proc_exit : (i32) -> nil + function proc_exit(exit_code) { + if (exit_code) { + throw new Error(`Wasm exited with code ${exit_code}`); + } + } + + // Signatures from wasm_test_platform.o + const sig2 = (i32) => {}; + const sig6 = (i32a, i32b) => 0; + const sig7 = (i32a, i32b, i32c) => 0; + const sig9 = (i32a, i64b, i32c) => 0; + const sig10 = (i32a, i64b, i64c, i32d) => 0; + const sig11 = (i32a, i64b, i64c) => 0; + const sig12 = (i32a) => 0; + const sig13 = (i32a, i64b) => 0; + const sig14 = (i32a, i32b, i32c, i64d, i32e) => 0; + const sig15 = (i32a, i32b, i32c, i32d) => 0; + const sig16 = (i32a, i64b, i32c, i32d) => 0; + const sig17 = (i32a, i32b, i32c, i32d, i32e) => 0; + const sig18 = (i32a, i32b, i32c, i32d, i64e, i64f, i32g) => 0; + const sig19 = (i32a, i32b, i32c, i32d, i32e, i32f, i32g) => 0; + const sig20 = (i32a, i32b, i32c, i32d, i32e, i64f, i64g, i32h, i32i) => 0; + const sig21 = (i32a, i32b, i32c, i32d, i32e, i32f) => 0; + const sig22 = () => 0; + + return { + wasi_snapshot_preview1: { + args_get: sig6, + args_sizes_get: sig6, + environ_get: sig6, + environ_sizes_get: sig6, + clock_res_get: sig6, + clock_time_get: sig9, + fd_advise: sig10, + fd_allocate: sig11, + fd_close, + fd_datasync: sig12, + fd_fdstat_get, + fd_fdstat_set_flags: sig6, + fd_fdstat_set_rights: sig11, + fd_filestat_get: sig6, + fd_filestat_set_size: sig13, + fd_filestat_set_times: sig10, + fd_pread: sig14, + fd_prestat_get: sig6, + fd_prestat_dir_name: sig7, + fd_pwrite: sig14, + fd_read: sig15, + fd_readdir: sig14, + fd_renumber: sig6, + fd_seek, + fd_sync: sig12, + fd_tell: sig6, + fd_write, + path_create_directory: sig7, + path_filestat_get: sig17, + path_filestat_set_times: sig18, + path_link: sig19, + path_open: sig20, + path_readlink: sig21, + path_remove_directory: sig7, + path_rename: sig21, + path_symlink: sig17, + path_unlink_file: sig7, + poll_oneoff: sig15, + proc_exit, + proc_raise: sig12, + sched_yield: sig22, + random_get: sig6, + sock_recv: sig21, + sock_send: sig17, + sock_shutdown: sig6, + }, + }; +} From 7286f81091e12397396af3ba166c308381db72c5 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 16 Feb 2022 20:40:19 +0000 Subject: [PATCH 02/18] repl_www: faster build script, without wasm-opt --- repl_www/build.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/repl_www/build.sh b/repl_www/build.sh index 549dbfaf1c..bf1d944864 100755 --- a/repl_www/build.sh +++ b/repl_www/build.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -eux +set -ex if [[ ! -d repl_www ]] then @@ -17,12 +17,15 @@ WWW_DIR="repl_www/build" mkdir -p $WWW_DIR cp repl_www/public/* $WWW_DIR -# Pass all script arguments through to wasm-pack # For debugging, pass the --profiling option, which enables optimizations + debug info # (We need optimizations to get rid of dead code that otherwise causes compile errors!) -cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release -wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/ -# wasm-pack build --target web "$@" repl_wasm +if [ -n "$REPL_DEBUG" ] +then + cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --release + wasm-bindgen --target web --keep-debug target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm --out-dir repl_wasm/pkg/ +else + wasm-pack build --target web repl_wasm +fi cp repl_wasm/pkg/*.wasm $WWW_DIR From d4dc683cbb75e424a1205d9fe9a3ffcd0aaed64c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 18 Feb 2022 17:35:29 +0000 Subject: [PATCH 03/18] repl_www: rename the wrapper function around main on the JS side to match Wasm --- repl_www/public/repl.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/repl_www/public/repl.js b/repl_www/public/repl.js index 336c08e9e8..350ac9a066 100644 --- a/repl_www/public/repl.js +++ b/repl_www/public/repl.js @@ -88,8 +88,8 @@ async function js_create_app(wasm_module_bytes) { // Call the main function of the app, via the test wrapper // Cache the result and return the size of the app's memory function js_run_app() { - const { run, memory } = repl.app.exports; - const addr = run(); + const { wrapper, memory } = repl.app.exports; + const addr = wrapper(); const { buffer } = memory; repl.result = { addr, buffer }; From 5587fae4e62492407a34e3f6856b57f6ac1d5d87 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 18 Feb 2022 17:36:24 +0000 Subject: [PATCH 04/18] repl_www: fix a bug in buffer alignment (transmuted slice had wrong length) --- repl_wasm/src/lib.rs | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 48910defba..6752ae1190 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -94,6 +94,23 @@ impl<'a> ReplAppMemory for WasmMemory<'a> { } } +impl<'a> WasmReplApp<'a> { + /// Allocate a buffer to copy the app memory into + /// Buffer is aligned to 64 bits to preserve the original alignment of all Wasm numbers + fn allocate_buffer(&self, size: usize) -> &'a mut [u8] { + let size64 = (size / size_of::()) + 1; + let buffer64: &mut [u64] = self.arena.alloc_slice_fill_default(size64); + + // Note: Need `from_raw_parts_mut` as well as `transmute` to ensure slice has correct length! + let buffer: &mut [u8] = unsafe { + let ptr8: *mut u8 = std::mem::transmute(buffer64.as_mut_ptr()); + std::slice::from_raw_parts_mut(ptr8, size) + }; + + buffer + } +} + impl<'a> ReplApp<'a> for WasmReplApp<'a> { type Memory = WasmMemory<'a>; @@ -108,19 +125,16 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> { { let app_final_memory_size: usize = js_run_app(); - // Allocate a buffer to copy the app memory into - // Aligning it to 64 bits will preserve the original alignment of all Wasm numbers - let copy_buffer_aligned: &mut [u64] = self - .arena - .alloc_slice_fill_default((app_final_memory_size / size_of::()) + 1); - let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) }; + let copied_bytes: &mut [u8] = self.allocate_buffer(app_final_memory_size); let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); + let result_bytes = &copied_bytes[app_result_addr..]; let result: Return = unsafe { - let ptr: *const Return = std::mem::transmute(&copied_bytes[app_result_addr]); + let ptr: *const Return = std::mem::transmute(result_bytes.as_ptr()); ptr.read() }; + let mem = self.arena.alloc(WasmMemory { copied_bytes }); transform(mem, result) @@ -142,12 +156,7 @@ impl<'a> ReplApp<'a> for WasmReplApp<'a> { { let app_final_memory_size: usize = js_run_app(); - // Allocate a buffer to copy the app memory into - // Aligning it to 64 bits will preserve the original alignment of all Wasm numbers - let copy_buffer_aligned: &mut [u64] = self - .arena - .alloc_slice_fill_default((app_final_memory_size / size_of::()) + 1); - let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) }; + let copied_bytes: &mut [u8] = self.allocate_buffer(app_final_memory_size); let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); let mem = self.arena.alloc(WasmMemory { copied_bytes }); From 3433e01411e9e08e58405b3191f7b2a0057a24a1 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 18 Feb 2022 17:58:20 +0000 Subject: [PATCH 05/18] repl_www: Change page title. It's not a mock anymore! --- repl_www/public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repl_www/public/index.html b/repl_www/public/index.html index 20e4ed3ba2..e08ec457a1 100644 --- a/repl_www/public/index.html +++ b/repl_www/public/index.html @@ -70,7 +70,7 @@ margin-bottom: 16px; } - Mock REPL + Roc REPL
From 7eab8d840cf7789e7c901749fdf247638314f31f Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 19 Feb 2022 10:23:52 +0000 Subject: [PATCH 06/18] repl_wasm: delete console_log debug helper --- repl_wasm/src/lib.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 6752ae1190..04483a3d49 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -29,15 +29,6 @@ extern "C" { fn js_run_app() -> usize; fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; - - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); -} - -// In-browser debugging -#[allow(unused_macros)] -macro_rules! console_log { - ($($t:tt)*) => (log(&format_args!($($t)*).to_string())) } pub struct WasmReplApp<'a> { From ef97ad69aea002ff2c72de6ca129328fb3fb0cb3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 19 Feb 2022 18:21:52 +0000 Subject: [PATCH 07/18] repl_wasm: refactor external interface to support Wasmer tests --- Cargo.lock | 2 + repl_wasm/Cargo.toml | 3 + repl_wasm/src/interface_js.rs | 24 +++ repl_wasm/src/interface_test.rs | 28 ++++ repl_wasm/src/lib.rs | 271 ++------------------------------ repl_wasm/src/repl.rs | 243 ++++++++++++++++++++++++++++ 6 files changed, 316 insertions(+), 255 deletions(-) create mode 100644 repl_wasm/src/interface_js.rs create mode 100644 repl_wasm/src/interface_test.rs create mode 100644 repl_wasm/src/repl.rs diff --git a/Cargo.lock b/Cargo.lock index 1414f20e6f..63bcb190ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3192,6 +3192,7 @@ dependencies = [ "indoc", "roc_cli", "roc_repl_cli", + "roc_repl_wasm", "roc_test_utils", "strip-ansi-escapes", ] @@ -3705,6 +3706,7 @@ name = "roc_repl_wasm" version = "0.1.0" dependencies = [ "bumpalo", + "futures", "js-sys", "roc_builtins", "roc_collections", diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml index 03dd9536a2..1e3723cf7f 100644 --- a/repl_wasm/Cargo.toml +++ b/repl_wasm/Cargo.toml @@ -22,3 +22,6 @@ roc_parse = {path = "../compiler/parse"} roc_repl_eval = {path = "../repl_eval"} roc_target = {path = "../compiler/roc_target"} roc_types = {path = "../compiler/types"} + +[dev-dependencies] +futures = "0.3.17" diff --git a/repl_wasm/src/interface_js.rs b/repl_wasm/src/interface_js.rs new file mode 100644 index 0000000000..ec29dded39 --- /dev/null +++ b/repl_wasm/src/interface_js.rs @@ -0,0 +1,24 @@ +// wasm_bindgen procedural macro breaks this clippy rule +// https://github.com/rustwasm/wasm-bindgen/issues/2774 +#![allow(clippy::unused_unit)] + +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(catch)] + pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), JsValue>; + + pub fn js_run_app() -> usize; + + pub fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; +} + +/// Async entrypoint for the browser +/// The browser only has an async API to generate a Wasm module from bytes +/// wasm_bindgen manages the interaction between Rust Futures and JS Promises +#[wasm_bindgen] +pub async fn entrypoint_from_js(src: String) -> Result { + crate::repl::entrypoint_from_js(src).await +} diff --git a/repl_wasm/src/interface_test.rs b/repl_wasm/src/interface_test.rs new file mode 100644 index 0000000000..cd60db953b --- /dev/null +++ b/repl_wasm/src/interface_test.rs @@ -0,0 +1,28 @@ +extern "C" { + fn wasmer_create_app(wasm_module_bytes: &[u8]); + fn wasmer_run_app() -> usize; + fn wasmer_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; +} + +/// Async wrapper to match the equivalent JS function +pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), String> { + unsafe { + wasmer_create_app(wasm_module_bytes); + } + Ok(()) +} + +pub fn js_run_app() -> usize { + unsafe { wasmer_run_app() } +} + +pub fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize { + unsafe { wasmer_get_result_and_memory(buffer_alloc_addr) } +} + +/// Synchronous entrypoint for Wasmer tests +/// In tests we avoid the complexity of async calls across the Wasm/native boundary +pub fn entrypoint_from_test(src: String) -> Result { + let result_async = crate::repl::entrypoint_from_js(src); + executor::block_on(result_async) +} diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index 04483a3d49..ba6265928a 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -1,258 +1,19 @@ -// wasm_bindgen procedural macro breaks this clippy rule -// https://github.com/rustwasm/wasm-bindgen/issues/2774 -#![allow(clippy::unused_unit)] +mod repl; -use bumpalo::{collections::vec::Vec, Bump}; -use std::mem::size_of; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::JsValue; +// +// Interface with external JS in the browser +// +#[cfg(not(test))] +mod interface_js; +#[cfg(not(test))] +pub use interface_js::{entrypoint_from_js, js_create_app, js_get_result_and_memory, js_run_app}; -use roc_collections::all::MutSet; -use roc_gen_wasm::wasm32_result; -use roc_load::file::MonomorphizedModule; -use roc_parse::ast::Expr; -use roc_repl_eval::{ - eval::jit_to_ast, - gen::{compile_to_mono, format_answer, ReplOutput}, - ReplApp, ReplAppMemory, +// +// Interface with test code outside the Wasm module +// +#[cfg(test)] +mod interface_test; +#[cfg(test)] +pub use interface_test::{ + entrypoint_from_test, js_create_app, js_get_result_and_memory, js_run_app, }; -use roc_target::TargetInfo; -use roc_types::pretty_print::{content_to_string, name_all_type_vars}; - -const WRAPPER_NAME: &str = "wrapper"; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(catch)] - async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), JsValue>; - - fn js_run_app() -> usize; - - fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; -} - -pub struct WasmReplApp<'a> { - arena: &'a Bump, -} - -/// A copy of the app's memory, made after running the main function -/// The Wasm app ran in a separate address space from the compiler and the eval code. -/// This means we can't simply dereference its pointers as if they were local, because -/// an unrelated value may exist at the same-numbered address in our own address space! -/// Instead we have dereferencing methods that index into the copied bytes. -pub struct WasmMemory<'a> { - copied_bytes: &'a [u8], -} - -macro_rules! deref_number { - ($name: ident, $t: ty) => { - fn $name(&self, address: usize) -> $t { - const N: usize = size_of::<$t>(); - let mut array = [0; N]; - array.copy_from_slice(&self.copied_bytes[address..][..N]); - <$t>::from_le_bytes(array) - } - }; -} - -impl<'a> ReplAppMemory for WasmMemory<'a> { - fn deref_bool(&self, address: usize) -> bool { - self.copied_bytes[address] != 0 - } - - deref_number!(deref_u8, u8); - deref_number!(deref_u16, u16); - deref_number!(deref_u32, u32); - deref_number!(deref_u64, u64); - deref_number!(deref_u128, u128); - deref_number!(deref_usize, usize); - - deref_number!(deref_i8, i8); - deref_number!(deref_i16, i16); - deref_number!(deref_i32, i32); - deref_number!(deref_i64, i64); - deref_number!(deref_i128, i128); - deref_number!(deref_isize, isize); - - deref_number!(deref_f32, f32); - deref_number!(deref_f64, f64); - - fn deref_str(&self, addr: usize) -> &str { - let elems_addr = self.deref_usize(addr); - let len = self.deref_usize(addr + size_of::()); - let bytes = &self.copied_bytes[elems_addr..][..len]; - std::str::from_utf8(bytes).unwrap() - } -} - -impl<'a> WasmReplApp<'a> { - /// Allocate a buffer to copy the app memory into - /// Buffer is aligned to 64 bits to preserve the original alignment of all Wasm numbers - fn allocate_buffer(&self, size: usize) -> &'a mut [u8] { - let size64 = (size / size_of::()) + 1; - let buffer64: &mut [u64] = self.arena.alloc_slice_fill_default(size64); - - // Note: Need `from_raw_parts_mut` as well as `transmute` to ensure slice has correct length! - let buffer: &mut [u8] = unsafe { - let ptr8: *mut u8 = std::mem::transmute(buffer64.as_mut_ptr()); - std::slice::from_raw_parts_mut(ptr8, size) - }; - - buffer - } -} - -impl<'a> ReplApp<'a> for WasmReplApp<'a> { - type Memory = WasmMemory<'a>; - - /// Run user code that returns a type with a `Builtin` layout - /// Size of the return value is statically determined from its Rust type - /// The `transform` callback takes the app's memory and the returned value - /// _main_fn_name is always the same and we don't use it here - fn call_function(&self, _main_fn_name: &str, transform: F) -> Expr<'a> - where - F: Fn(&'a Self::Memory, Return) -> Expr<'a>, - Self::Memory: 'a, - { - let app_final_memory_size: usize = js_run_app(); - - let copied_bytes: &mut [u8] = self.allocate_buffer(app_final_memory_size); - - let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); - - let result_bytes = &copied_bytes[app_result_addr..]; - let result: Return = unsafe { - let ptr: *const Return = std::mem::transmute(result_bytes.as_ptr()); - ptr.read() - }; - - let mem = self.arena.alloc(WasmMemory { copied_bytes }); - - transform(mem, result) - } - - /// Run user code that returns a struct or union, whose size is provided as an argument - /// The `transform` callback takes the app's memory and the address of the returned value - /// _main_fn_name and _ret_bytes are only used for the CLI REPL. For Wasm they are compiled-in - /// to the test_wrapper function of the app itself - fn call_function_dynamic_size( - &self, - _main_fn_name: &str, - _ret_bytes: usize, - transform: F, - ) -> T - where - F: Fn(&'a Self::Memory, usize) -> T, - Self::Memory: 'a, - { - let app_final_memory_size: usize = js_run_app(); - - let copied_bytes: &mut [u8] = self.allocate_buffer(app_final_memory_size); - - let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); - let mem = self.arena.alloc(WasmMemory { copied_bytes }); - - transform(mem, app_result_addr) - } -} - -#[wasm_bindgen] -pub async fn entrypoint_from_js(src: String) -> Result { - let arena = &Bump::new(); - let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); - - // Compile the app - let target_info = TargetInfo::default_wasm32(); - let mono = match compile_to_mono(arena, &src, target_info) { - Ok(m) => m, - Err(messages) => return Err(messages.join("\n\n")), - }; - - let MonomorphizedModule { - module_id, - procedures, - mut interns, - mut subs, - exposed_to_host, - .. - } = mono; - - debug_assert_eq!(exposed_to_host.values.len(), 1); - let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); - let main_fn_symbol = *main_fn_symbol; - let main_fn_var = *main_fn_var; - - // pretty-print the expr type string for later. - name_all_type_vars(main_fn_var, &mut subs); - let content = subs.get_content_without_compacting(main_fn_var); - let expr_type_str = content_to_string(content, &subs, module_id, &interns); - - let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { - Some(layout) => *layout, - None => return Ok(format!(" : {}", expr_type_str)), - }; - - let app_module_bytes = { - let env = roc_gen_wasm::Env { - arena, - module_id, - exposed_to_host: exposed_to_host - .values - .keys() - .copied() - .collect::>(), - }; - - let (mut module, called_preload_fns, main_fn_index) = { - roc_gen_wasm::build_module_without_wrapper( - &env, - &mut interns, // NOTE: must drop this mutable ref before jit_to_ast - pre_linked_binary, - procedures, - ) - }; - - wasm32_result::insert_wrapper_for_layout( - arena, - &mut module, - WRAPPER_NAME, - main_fn_index, - &main_fn_layout.result, - ); - - module.remove_dead_preloads(env.arena, called_preload_fns); - - let mut buffer = Vec::with_capacity_in(module.size(), arena); - module.serialize(&mut buffer); - - buffer - }; - - // Send the compiled binary out to JS and create an executable instance from it - js_create_app(&app_module_bytes) - .await - .map_err(|js| format!("{:?}", js))?; - - let app = WasmReplApp { arena }; - - // Run the app and transform the result value to an AST `Expr` - // Restore type constructor names, and other user-facing info that was erased during compilation. - let res_answer = jit_to_ast( - arena, - &app, - "", // main_fn_name is ignored (only passed to WasmReplApp methods) - main_fn_layout, - content, - &interns, - module_id, - &subs, - target_info, - ); - - // Transform the Expr to a string - // `Result::Err` becomes a JS exception that will be caught and displayed - match format_answer(arena, res_answer, expr_type_str) { - ReplOutput::NoProblems { expr, expr_type } => Ok(format!("\n{}: {}", expr, expr_type)), - ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))), - } -} diff --git a/repl_wasm/src/repl.rs b/repl_wasm/src/repl.rs new file mode 100644 index 0000000000..6234225c66 --- /dev/null +++ b/repl_wasm/src/repl.rs @@ -0,0 +1,243 @@ +use bumpalo::{collections::vec::Vec, Bump}; +use std::mem::size_of; + +use roc_collections::all::MutSet; +use roc_gen_wasm::wasm32_result; +use roc_load::file::MonomorphizedModule; +use roc_parse::ast::Expr; +use roc_repl_eval::{ + eval::jit_to_ast, + gen::{compile_to_mono, format_answer, ReplOutput}, + ReplApp, ReplAppMemory, +}; +use roc_target::TargetInfo; +use roc_types::pretty_print::{content_to_string, name_all_type_vars}; + +use crate::{js_create_app, js_get_result_and_memory, js_run_app}; + +const WRAPPER_NAME: &str = "wrapper"; + +pub struct WasmReplApp<'a> { + arena: &'a Bump, +} + +/// A copy of the app's memory, made after running the main function +/// The Wasm app ran in a separate address space from the compiler and the eval code. +/// This means we can't simply dereference its pointers as if they were local, because +/// an unrelated value may exist at the same-numbered address in our own address space! +/// Instead we have dereferencing methods that index into the copied bytes. +pub struct WasmMemory<'a> { + copied_bytes: &'a [u8], +} + +macro_rules! deref_number { + ($name: ident, $t: ty) => { + fn $name(&self, address: usize) -> $t { + const N: usize = size_of::<$t>(); + let mut array = [0; N]; + array.copy_from_slice(&self.copied_bytes[address..][..N]); + <$t>::from_le_bytes(array) + } + }; +} + +impl<'a> ReplAppMemory for WasmMemory<'a> { + fn deref_bool(&self, address: usize) -> bool { + self.copied_bytes[address] != 0 + } + + deref_number!(deref_u8, u8); + deref_number!(deref_u16, u16); + deref_number!(deref_u32, u32); + deref_number!(deref_u64, u64); + deref_number!(deref_u128, u128); + deref_number!(deref_usize, usize); + + deref_number!(deref_i8, i8); + deref_number!(deref_i16, i16); + deref_number!(deref_i32, i32); + deref_number!(deref_i64, i64); + deref_number!(deref_i128, i128); + deref_number!(deref_isize, isize); + + deref_number!(deref_f32, f32); + deref_number!(deref_f64, f64); + + fn deref_str(&self, addr: usize) -> &str { + let elems_addr = self.deref_usize(addr); + let len = self.deref_usize(addr + size_of::()); + let bytes = &self.copied_bytes[elems_addr..][..len]; + std::str::from_utf8(bytes).unwrap() + } +} + +impl<'a> WasmReplApp<'a> { + /// Allocate a buffer to copy the app memory into + /// Buffer is aligned to 64 bits to preserve the original alignment of all Wasm numbers + fn allocate_buffer(&self, size: usize) -> &'a mut [u8] { + let size64 = (size / size_of::()) + 1; + let buffer64: &mut [u64] = self.arena.alloc_slice_fill_default(size64); + + // Note: Need `from_raw_parts_mut` as well as `transmute` to ensure slice has correct length! + let buffer: &mut [u8] = unsafe { + let ptr8: *mut u8 = std::mem::transmute(buffer64.as_mut_ptr()); + std::slice::from_raw_parts_mut(ptr8, size) + }; + + buffer + } +} + +impl<'a> ReplApp<'a> for WasmReplApp<'a> { + type Memory = WasmMemory<'a>; + + /// Run user code that returns a type with a `Builtin` layout + /// Size of the return value is statically determined from its Rust type + /// The `transform` callback takes the app's memory and the returned value + /// _main_fn_name is always the same and we don't use it here + fn call_function(&self, _main_fn_name: &str, transform: F) -> Expr<'a> + where + F: Fn(&'a Self::Memory, Return) -> Expr<'a>, + Self::Memory: 'a, + { + let app_final_memory_size: usize = js_run_app(); + + let copied_bytes: &mut [u8] = self.allocate_buffer(app_final_memory_size); + + let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); + + let result_bytes = &copied_bytes[app_result_addr..]; + let result: Return = unsafe { + let ptr: *const Return = std::mem::transmute(result_bytes.as_ptr()); + ptr.read() + }; + + let mem = self.arena.alloc(WasmMemory { copied_bytes }); + + transform(mem, result) + } + + /// Run user code that returns a struct or union, whose size is provided as an argument + /// The `transform` callback takes the app's memory and the address of the returned value + /// _main_fn_name and _ret_bytes are only used for the CLI REPL. For Wasm they are compiled-in + /// to the test_wrapper function of the app itself + fn call_function_dynamic_size( + &self, + _main_fn_name: &str, + _ret_bytes: usize, + transform: F, + ) -> T + where + F: Fn(&'a Self::Memory, usize) -> T, + Self::Memory: 'a, + { + let app_final_memory_size: usize = js_run_app(); + + let copied_bytes: &mut [u8] = self.allocate_buffer(app_final_memory_size); + + let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr()); + let mem = self.arena.alloc(WasmMemory { copied_bytes }); + + transform(mem, app_result_addr) + } +} + +pub async fn entrypoint_from_js(src: String) -> Result { + let arena = &Bump::new(); + let pre_linked_binary: &'static [u8] = include_bytes!("../data/pre_linked_binary.o"); + + // Compile the app + let target_info = TargetInfo::default_wasm32(); + let mono = match compile_to_mono(arena, &src, target_info) { + Ok(m) => m, + Err(messages) => return Err(messages.join("\n\n")), + }; + + let MonomorphizedModule { + module_id, + procedures, + mut interns, + mut subs, + exposed_to_host, + .. + } = mono; + + debug_assert_eq!(exposed_to_host.values.len(), 1); + let (main_fn_symbol, main_fn_var) = exposed_to_host.values.iter().next().unwrap(); + let main_fn_symbol = *main_fn_symbol; + let main_fn_var = *main_fn_var; + + // pretty-print the expr type string for later. + name_all_type_vars(main_fn_var, &mut subs); + let content = subs.get_content_without_compacting(main_fn_var); + let expr_type_str = content_to_string(content, &subs, module_id, &interns); + + let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) { + Some(layout) => *layout, + None => return Ok(format!(" : {}", expr_type_str)), + }; + + let app_module_bytes = { + let env = roc_gen_wasm::Env { + arena, + module_id, + exposed_to_host: exposed_to_host + .values + .keys() + .copied() + .collect::>(), + }; + + let (mut module, called_preload_fns, main_fn_index) = { + roc_gen_wasm::build_module_without_wrapper( + &env, + &mut interns, // NOTE: must drop this mutable ref before jit_to_ast + pre_linked_binary, + procedures, + ) + }; + + wasm32_result::insert_wrapper_for_layout( + arena, + &mut module, + WRAPPER_NAME, + main_fn_index, + &main_fn_layout.result, + ); + + module.remove_dead_preloads(env.arena, called_preload_fns); + + let mut buffer = Vec::with_capacity_in(module.size(), arena); + module.serialize(&mut buffer); + + buffer + }; + + // Send the compiled binary out to JS and create an executable instance from it + js_create_app(&app_module_bytes) + .await + .map_err(|js| format!("{:?}", js))?; + + let app = WasmReplApp { arena }; + + // Run the app and transform the result value to an AST `Expr` + // Restore type constructor names, and other user-facing info that was erased during compilation. + let res_answer = jit_to_ast( + arena, + &app, + "", // main_fn_name is ignored (only passed to WasmReplApp methods) + main_fn_layout, + content, + &interns, + module_id, + &subs, + target_info, + ); + + // Transform the Expr to a string + // `Result::Err` becomes a JS exception that will be caught and displayed + match format_answer(arena, res_answer, expr_type_str) { + ReplOutput::NoProblems { expr, expr_type } => Ok(format!("\n{}: {}", expr, expr_type)), + ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))), + } +} From a75aa52b9185cd1f04c1e2e597e5d006a1f62402 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 19 Feb 2022 23:29:06 +0000 Subject: [PATCH 08/18] repl_test: write Rust replacements for JS functions --- Cargo.lock | 49 ++++++++++++- compiler/test_gen/Cargo.toml | 2 +- repl_test/Cargo.toml | 7 +- repl_test/src/tests.rs | 9 ++- repl_test/src/wasm.rs | 129 +++++++++++++++++++++++++++++++++++ 5 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 repl_test/src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 63bcb190ab..b71fefa36d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1193,6 +1193,32 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +[[package]] +name = "dynasm" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c" +dependencies = [ + "bitflags", + "byteorder", + "lazy_static", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dynasmrt" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061" +dependencies = [ + "byteorder", + "dynasm", + "memmap2 0.5.0", +] + [[package]] name = "either" version = "1.6.1" @@ -3192,9 +3218,10 @@ dependencies = [ "indoc", "roc_cli", "roc_repl_cli", - "roc_repl_wasm", "roc_test_utils", "strip-ansi-escapes", + "wasmer", + "wasmer-wasi", ] [[package]] @@ -4748,6 +4775,7 @@ dependencies = [ "thiserror", "wasmer-compiler", "wasmer-compiler-cranelift", + "wasmer-compiler-singlepass", "wasmer-derive", "wasmer-engine", "wasmer-engine-dylib", @@ -4796,6 +4824,25 @@ dependencies = [ "wasmer-vm", ] +[[package]] +name = "wasmer-compiler-singlepass" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9429b9f7708c582d855b1787f09c7029ff23fb692550d4a1cc351c8ea84c3014" +dependencies = [ + "byteorder", + "dynasm", + "dynasmrt", + "lazy_static", + "loupe", + "more-asserts", + "rayon", + "smallvec", + "wasmer-compiler", + "wasmer-types", + "wasmer-vm", +] + [[package]] name = "wasmer-derive" version = "2.0.0" diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index 607b14b7ff..53e7e968c0 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -39,7 +39,7 @@ libc = "0.2.106" inkwell = { path = "../../vendor/inkwell" } target-lexicon = "0.12.2" libloading = "0.7.1" -wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] } +wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] } wasmer-wasi = "2.0.0" tempfile = "3.2.0" indoc = "1.0.3" diff --git a/repl_test/Cargo.toml b/repl_test/Cargo.toml index 2d86ad19d4..4bd8992b6d 100644 --- a/repl_test/Cargo.toml +++ b/repl_test/Cargo.toml @@ -12,13 +12,12 @@ roc_cli = {path = "../cli"} [dev-dependencies] indoc = "1.0.3" -roc_test_utils = {path = "../test_utils"} strip-ansi-escapes = "0.1.1" +wasmer = {version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"]} +wasmer-wasi = "2.0.0" roc_repl_cli = {path = "../repl_cli"} -# roc_repl_wasm = {path = "../repl_wasm"} +roc_test_utils = {path = "../test_utils"} [features] -default = ["cli"] -cli = [] wasm = [] diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index 49a166386b..b862a6fdc3 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -1,10 +1,15 @@ use indoc::indoc; +#[cfg(not(feature = "wasm"))] mod cli; - -#[cfg(feature = "cli")] +#[cfg(not(feature = "wasm"))] use crate::cli::{expect_failure, expect_success}; +#[cfg(feature = "wasm")] +mod wasm; +#[cfg(feature = "wasm")] +use crate::wasm::{expect_failure, expect_success}; + #[test] fn literal_0() { expect_success("0", "0 : Num *"); diff --git a/repl_test/src/wasm.rs b/repl_test/src/wasm.rs new file mode 100644 index 0000000000..7a9f643ed1 --- /dev/null +++ b/repl_test/src/wasm.rs @@ -0,0 +1,129 @@ +use std::{ + cell::RefCell, + ops::{Deref, DerefMut}, + thread_local, +}; +use wasmer::Instance; + +thread_local! { + static REPL_STATE: RefCell> = RefCell::new(None) +} + +struct ReplState { + compiler: Instance, + app: Option, + result_addr: Option, +} + +fn instantiate_compiler(wasm_module_bytes: &[u8]) { + use wasmer::{Module, Store}; + use wasmer_wasi::WasiState; + + let store = Store::default(); + let wasmer_module = Module::new(&store, &wasm_module_bytes).unwrap(); + + // First, we create the `WasiEnv` + let mut wasi_env = WasiState::new("hello").finalize().unwrap(); + + // Then, we get the import object related to our WASI + // and attach it to the Wasm instance. + let import_object = wasi_env + .import_object(&wasmer_module) + .unwrap_or_else(|_| wasmer::imports!()); + + let instance = wasmer::Instance::new(&wasmer_module, &import_object).unwrap(); + REPL_STATE.with(|f| { + if let Some(state) = f.borrow_mut().deref_mut() { + state.app = Some(instance) + } else { + panic!("REPL state not found") + } + }) +} + +fn wasmer_create_app(wasm_module_bytes: &[u8]) { + use wasmer::{Module, Store}; + use wasmer_wasi::WasiState; + + let store = Store::default(); + let wasmer_module = Module::new(&store, &wasm_module_bytes).unwrap(); + + // First, we create the `WasiEnv` + let mut wasi_env = WasiState::new("hello").finalize().unwrap(); + + // Then, we get the import object related to our WASI + // and attach it to the Wasm instance. + let import_object = wasi_env + .import_object(&wasmer_module) + .unwrap_or_else(|_| wasmer::imports!()); + + let instance = wasmer::Instance::new(&wasmer_module, &import_object).unwrap(); + REPL_STATE.with(|f| { + if let Some(state) = f.borrow_mut().deref_mut() { + state.app = Some(instance) + } else { + panic!("REPL state not found") + } + }) +} + +fn wasmer_run_app() -> u32 { + REPL_STATE.with(|f| { + if let Some(state) = f.borrow_mut().deref_mut() { + if let Some(app) = &state.app { + let wrapper = app.exports.get_function("wrapper").unwrap(); + + let result_addr: i32 = match wrapper.call(&[]) { + Err(e) => panic!("{:?}", e), + Ok(result) => match result[0] { + wasmer::Value::I32(a) => a, + _ => panic!("Expected an i32 address, got {:?}", result), + }, + }; + state.result_addr = Some(result_addr as u32); + + let memory = app.exports.get_memory("memory").unwrap(); + memory.size().bytes().0 as u32 + } else { + panic!("App not found") + } + } else { + panic!("REPL state not found") + } + }) +} + +fn wasmer_get_result_and_memory(buffer_alloc_addr: u32) -> u32 { + REPL_STATE.with(|f| { + if let Some(state) = f.borrow().deref() { + if let Some(app) = &state.app { + let app_memory = app.exports.get_memory("memory").unwrap(); + let compiler_memory = state.compiler.exports.get_memory("memory").unwrap(); + let result_addr = state.result_addr.unwrap(); + + let compiler_memory_bytes: &mut [u8] = + unsafe { compiler_memory.data_unchecked_mut() }; + + let app_memory_bytes: &[u8] = unsafe { app_memory.data_unchecked() }; + + let buf_addr = buffer_alloc_addr as usize; + let len = app_memory_bytes.len(); + compiler_memory_bytes[buf_addr..][..len].copy_from_slice(app_memory_bytes); + + result_addr + } else { + panic!("REPL app not found") + } + } else { + panic!("REPL state not found") + } + }) +} + +pub fn expect_success(input: &str, expected: &str) { + todo!() +} + +pub fn expect_failure(input: &str, expected: &str) { + todo!() +} From a4d83f69169d46298bdc5b9284d1ab3cb2837b7a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 20 Feb 2022 09:03:59 +0000 Subject: [PATCH 09/18] repl_test: developing wasm test setup --- repl_test/src/wasm.rs | 111 +++++++++++++++++++++----------- repl_wasm/src/interface_test.rs | 14 +++- 2 files changed, 85 insertions(+), 40 deletions(-) diff --git a/repl_test/src/wasm.rs b/repl_test/src/wasm.rs index 7a9f643ed1..f6d62cf430 100644 --- a/repl_test/src/wasm.rs +++ b/repl_test/src/wasm.rs @@ -1,26 +1,29 @@ use std::{ cell::RefCell, + env, fs, ops::{Deref, DerefMut}, + path::Path, thread_local, }; -use wasmer::Instance; +use wasmer::{imports, Function, Instance, Module, Store}; +use wasmer_wasi::WasiState; + +const COMPILER_PATH_ENV_VAR: &str = "COMPILER_PATH"; thread_local! { - static REPL_STATE: RefCell> = RefCell::new(None) + static REPL_STATE: RefCell>> = RefCell::new(None) } -struct ReplState { +struct Env { + src: &'static str, compiler: Instance, app: Option, result_addr: Option, } -fn instantiate_compiler(wasm_module_bytes: &[u8]) { - use wasmer::{Module, Store}; - use wasmer_wasi::WasiState; - +fn wasmer_create_app(app_bytes: &[u8]) { let store = Store::default(); - let wasmer_module = Module::new(&store, &wasm_module_bytes).unwrap(); + let wasmer_module = Module::new(&store, &app_bytes).unwrap(); // First, we create the `WasiEnv` let mut wasi_env = WasiState::new("hello").finalize().unwrap(); @@ -29,35 +32,10 @@ fn instantiate_compiler(wasm_module_bytes: &[u8]) { // and attach it to the Wasm instance. let import_object = wasi_env .import_object(&wasmer_module) - .unwrap_or_else(|_| wasmer::imports!()); + .unwrap_or_else(|_| imports!()); - let instance = wasmer::Instance::new(&wasmer_module, &import_object).unwrap(); - REPL_STATE.with(|f| { - if let Some(state) = f.borrow_mut().deref_mut() { - state.app = Some(instance) - } else { - panic!("REPL state not found") - } - }) -} + let instance = Instance::new(&wasmer_module, &import_object).unwrap(); -fn wasmer_create_app(wasm_module_bytes: &[u8]) { - use wasmer::{Module, Store}; - use wasmer_wasi::WasiState; - - let store = Store::default(); - let wasmer_module = Module::new(&store, &wasm_module_bytes).unwrap(); - - // First, we create the `WasiEnv` - let mut wasi_env = WasiState::new("hello").finalize().unwrap(); - - // Then, we get the import object related to our WASI - // and attach it to the Wasm instance. - let import_object = wasi_env - .import_object(&wasmer_module) - .unwrap_or_else(|_| wasmer::imports!()); - - let instance = wasmer::Instance::new(&wasmer_module, &import_object).unwrap(); REPL_STATE.with(|f| { if let Some(state) = f.borrow_mut().deref_mut() { state.app = Some(instance) @@ -120,10 +98,69 @@ fn wasmer_get_result_and_memory(buffer_alloc_addr: u32) -> u32 { }) } +fn wasmer_copy_input_string(src_buffer_addr: u32) { + REPL_STATE.with(|f| { + if let Some(state) = f.borrow().deref() { + let compiler_memory = state.compiler.exports.get_memory("memory").unwrap(); + let compiler_memory_bytes: &mut [u8] = unsafe { compiler_memory.data_unchecked_mut() }; + + let buf_addr = src_buffer_addr as usize; + let len = state.src.len(); + compiler_memory_bytes[buf_addr..][..len].copy_from_slice(state.src.as_bytes()); + } + }) +} + +fn init_compiler() -> Vec { + let path_str = env::var(COMPILER_PATH_ENV_VAR).unwrap(); + let path = Path::new(&path_str); + let wasm_module_bytes = fs::read(&path).unwrap(); + + let store = Store::default(); + let wasmer_module = Module::new(&store, &wasm_module_bytes).unwrap(); + + let import_object = imports! { + "env" => { + "wasmer_create_app" => Function::new_native(&store, wasmer_create_app), + "wasmer_run_app" => Function::new_native(&store, wasmer_run_app), + "wasmer_get_result_and_memory" => Function::new_native(&store, wasmer_get_result_and_memory), + "wasmer_copy_input_string" => Function::new_native(&store, wasmer_copy_input_string), + } + }; + + Instance::new(&wasmer_module, &import_object).unwrap() +} + +fn run(src: &str) -> String { + let compiler = init_compiler(); + + let entrypoint = compiler + .exports + .get_function("entrypoint_from_test") + .unwrap(); + + REPL_STATE.with(|f| { + let new_state = Env { + src, + compiler, + app: None, + result_addr: None, + }; + let current_state = f.borrow_mut().deref_mut(); + assert_eq!(current_state, None); + *current_state = Some(new_state); + }); + + let actual = String::new(); + actual +} + pub fn expect_success(input: &str, expected: &str) { - todo!() + let actual = run(input); + assert_eq!(actual, expected); } pub fn expect_failure(input: &str, expected: &str) { - todo!() + let actual = run(input); + assert_eq!(actual, expected); } diff --git a/repl_wasm/src/interface_test.rs b/repl_wasm/src/interface_test.rs index cd60db953b..23fb9dcd01 100644 --- a/repl_wasm/src/interface_test.rs +++ b/repl_wasm/src/interface_test.rs @@ -2,6 +2,7 @@ extern "C" { fn wasmer_create_app(wasm_module_bytes: &[u8]); fn wasmer_run_app() -> usize; fn wasmer_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; + fn wasmer_copy_input_string(src_buffer_addr: *mut u8); } /// Async wrapper to match the equivalent JS function @@ -20,9 +21,16 @@ pub fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize { unsafe { wasmer_get_result_and_memory(buffer_alloc_addr) } } -/// Synchronous entrypoint for Wasmer tests -/// In tests we avoid the complexity of async calls across the Wasm/native boundary -pub fn entrypoint_from_test(src: String) -> Result { +/// Entrypoint for Wasmer tests +/// - Synchronous API, to avoid the need to manage async calls across the Wasm/native boundary. +/// In the JS version, wasm_bindgen deals with this, as well as translating Rust Future <-> JS Promise. +/// - Manually copy the source bytes and convert to String. Again, wasm_bindgen does this for JS. +pub fn entrypoint_from_test(src_len: usize) -> Result { + let mut src_buffer = std::vec![0; src_len]; + let src = unsafe { + wasmer_copy_input_string(src_buffer.as_mut_ptr()); + String::from_utf8_unchecked(src_buffer) + }; let result_async = crate::repl::entrypoint_from_js(src); executor::block_on(result_async) } From 603c98d1d9958bf676a47bad600b02f75c9b46e4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 20 Feb 2022 09:05:08 +0000 Subject: [PATCH 10/18] wasm tests: use wasmer .unwrap_i32 --- compiler/test_gen/src/helpers/llvm.rs | 5 +---- compiler/test_gen/src/helpers/wasm.rs | 10 ++-------- repl_test/src/wasm.rs | 5 +---- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/compiler/test_gen/src/helpers/llvm.rs b/compiler/test_gen/src/helpers/llvm.rs index d7a24effa9..c5fa4b20fc 100644 --- a/compiler/test_gen/src/helpers/llvm.rs +++ b/compiler/test_gen/src/helpers/llvm.rs @@ -492,10 +492,7 @@ where match test_wrapper.call(&[]) { Err(e) => Err(format!("call to `test_wrapper`: {:?}", e)), Ok(result) => { - let address = match result[0] { - wasmer::Value::I32(a) => a, - _ => panic!(), - }; + let address = result[0].unwrap_i32(); let output = ::decode( memory, diff --git a/compiler/test_gen/src/helpers/wasm.rs b/compiler/test_gen/src/helpers/wasm.rs index 3420cb2498..6813459933 100644 --- a/compiler/test_gen/src/helpers/wasm.rs +++ b/compiler/test_gen/src/helpers/wasm.rs @@ -194,10 +194,7 @@ where match test_wrapper.call(&[]) { Err(e) => Err(format!("{:?}", e)), Ok(result) => { - let address = match result[0] { - wasmer::Value::I32(a) => a, - _ => panic!(), - }; + let address = result[0].unwrap_i32(); if false { println!("test_wrapper returned 0x{:x}", address); @@ -239,10 +236,7 @@ where let init_result = init_refcount_test.call(&[wasmer::Value::I32(expected_len)]); let refcount_vector_addr = match init_result { Err(e) => return Err(format!("{:?}", e)), - Ok(result) => match result[0] { - wasmer::Value::I32(a) => a, - _ => panic!(), - }, + Ok(result) => result[0].unwrap_i32(), }; // Run the test diff --git a/repl_test/src/wasm.rs b/repl_test/src/wasm.rs index f6d62cf430..1a779a6251 100644 --- a/repl_test/src/wasm.rs +++ b/repl_test/src/wasm.rs @@ -53,10 +53,7 @@ fn wasmer_run_app() -> u32 { let result_addr: i32 = match wrapper.call(&[]) { Err(e) => panic!("{:?}", e), - Ok(result) => match result[0] { - wasmer::Value::I32(a) => a, - _ => panic!("Expected an i32 address, got {:?}", result), - }, + Ok(result) => result[0].unwrap_i32(), }; state.result_addr = Some(result_addr as u32); From 1e010e185cc8c47937fb77012bf48fb8fa1c2b1f Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 20 Feb 2022 10:38:42 +0000 Subject: [PATCH 11/18] repl_test: wasm tests compile but do not run --- repl_test/src/wasm.rs | 55 +++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/repl_test/src/wasm.rs b/repl_test/src/wasm.rs index 1a779a6251..0f129d2712 100644 --- a/repl_test/src/wasm.rs +++ b/repl_test/src/wasm.rs @@ -11,33 +11,39 @@ use wasmer_wasi::WasiState; const COMPILER_PATH_ENV_VAR: &str = "COMPILER_PATH"; thread_local! { - static REPL_STATE: RefCell>> = RefCell::new(None) + static REPL_STATE: RefCell> = RefCell::new(None) } -struct Env { +struct ReplState { src: &'static str, compiler: Instance, app: Option, result_addr: Option, } -fn wasmer_create_app(app_bytes: &[u8]) { - let store = Store::default(); - let wasmer_module = Module::new(&store, &app_bytes).unwrap(); - - // First, we create the `WasiEnv` - let mut wasi_env = WasiState::new("hello").finalize().unwrap(); - - // Then, we get the import object related to our WASI - // and attach it to the Wasm instance. - let import_object = wasi_env - .import_object(&wasmer_module) - .unwrap_or_else(|_| imports!()); - - let instance = Instance::new(&wasmer_module, &import_object).unwrap(); - +fn wasmer_create_app(app_bytes_ptr: i32, app_bytes_len: i32) { REPL_STATE.with(|f| { if let Some(state) = f.borrow_mut().deref_mut() { + let compiler_memory = state.compiler.exports.get_memory("memory").unwrap(); + let compiler_memory_bytes: &mut [u8] = unsafe { compiler_memory.data_unchecked_mut() }; + + // Find the slice of bytes for the compiled Roc app + let ptr = app_bytes_ptr as usize; + let len = app_bytes_len as usize; + let app_module_bytes: &[u8] = &compiler_memory_bytes[ptr..][..len]; + + // Parse the bytes into a Wasmer module + let store = Store::default(); + let wasmer_module = Module::new(&store, app_module_bytes).unwrap(); + + // Get the WASI imports for the app + let mut wasi_env = WasiState::new("hello").finalize().unwrap(); + let import_object = wasi_env + .import_object(&wasmer_module) + .unwrap_or_else(|_| imports!()); + + // Create an executable instance (give it a stack & heap, etc. For ELF, this would be the OS's job.) + let instance = Instance::new(&wasmer_module, &import_object).unwrap(); state.app = Some(instance) } else { panic!("REPL state not found") @@ -108,7 +114,7 @@ fn wasmer_copy_input_string(src_buffer_addr: u32) { }) } -fn init_compiler() -> Vec { +fn init_compiler() -> Instance { let path_str = env::var(COMPILER_PATH_ENV_VAR).unwrap(); let path = Path::new(&path_str); let wasm_module_bytes = fs::read(&path).unwrap(); @@ -128,7 +134,7 @@ fn init_compiler() -> Vec { Instance::new(&wasmer_module, &import_object).unwrap() } -fn run(src: &str) -> String { +fn run(src: &'static str) -> String { let compiler = init_compiler(); let entrypoint = compiler @@ -137,27 +143,26 @@ fn run(src: &str) -> String { .unwrap(); REPL_STATE.with(|f| { - let new_state = Env { + let new_state = ReplState { src, compiler, app: None, result_addr: None, }; - let current_state = f.borrow_mut().deref_mut(); - assert_eq!(current_state, None); - *current_state = Some(new_state); + + *f.borrow_mut().deref_mut() = Some(new_state); }); let actual = String::new(); actual } -pub fn expect_success(input: &str, expected: &str) { +pub fn expect_success(input: &'static str, expected: &str) { let actual = run(input); assert_eq!(actual, expected); } -pub fn expect_failure(input: &str, expected: &str) { +pub fn expect_failure(input: &'static str, expected: &str) { let actual = run(input); assert_eq!(actual, expected); } From 305968a5c033c55b485ffda3daeb5079f843c730 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 20 Feb 2022 11:31:00 +0000 Subject: [PATCH 12/18] repl_test: Apologetic error message about the build dependency situation --- repl_test/src/wasm.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/repl_test/src/wasm.rs b/repl_test/src/wasm.rs index 0f129d2712..7522721d01 100644 --- a/repl_test/src/wasm.rs +++ b/repl_test/src/wasm.rs @@ -8,7 +8,7 @@ use std::{ use wasmer::{imports, Function, Instance, Module, Store}; use wasmer_wasi::WasiState; -const COMPILER_PATH_ENV_VAR: &str = "COMPILER_PATH"; +const WASM_REPL_COMPILER_PATH: &str = "../repl_wasm/pkg/roc_repl_wasm_bg.wasm"; thread_local! { static REPL_STATE: RefCell> = RefCell::new(None) @@ -115,9 +115,30 @@ fn wasmer_copy_input_string(src_buffer_addr: u32) { } fn init_compiler() -> Instance { - let path_str = env::var(COMPILER_PATH_ENV_VAR).unwrap(); - let path = Path::new(&path_str); - let wasm_module_bytes = fs::read(&path).unwrap(); + let path = Path::new(WASM_REPL_COMPILER_PATH); + let wasm_module_bytes = match fs::read(&path) { + Ok(bytes) => bytes, + Err(e) => { + if matches!(e.kind(), std::io::ErrorKind::NotFound) { + panic!( + "\n\n {}\n\n", + [ + "CANNOT BUILD WASM REPL TESTS", + "Please run repl_www/build.sh and try again!", + "", + "We have not yet automated this build dependency using Cargo.", + "The Cargo build for the tests would have to launch another Cargo build", + "for a *different target* (wasm), which is tricky.", + "It probably requires a second target directory to avoid locks.", + "We'll get to it eventually but it's not done yet.", + ] + .join("\n ") + ) + } else { + panic!("{:?}", e) + } + } + }; let store = Store::default(); let wasmer_module = Module::new(&store, &wasm_module_bytes).unwrap(); From 3b050d3e2a2861268afea6b2d42c07f75679cb76 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 20 Feb 2022 17:47:56 +0000 Subject: [PATCH 13/18] repl_wasm: Finish interface to Wasmer. Compiles but throws runtime borrow error. --- repl_test/run_wasm.sh | 6 ++ repl_test/src/wasm.rs | 121 ++++++++++++++++++++++---------- repl_wasm/Cargo.toml | 5 +- repl_wasm/src/interface_test.rs | 25 +++++-- repl_wasm/src/lib.rs | 8 +-- 5 files changed, 113 insertions(+), 52 deletions(-) create mode 100755 repl_test/run_wasm.sh diff --git a/repl_test/run_wasm.sh b/repl_test/run_wasm.sh new file mode 100755 index 0000000000..4d5ffbce3c --- /dev/null +++ b/repl_test/run_wasm.sh @@ -0,0 +1,6 @@ +# At the moment we are using this script instead of `cargo test` +# Cargo doesn't really have a good way to build two targets (host and wasm). +# We can try to make the build nicer later + +cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --features wasmer --release +cargo test -p repl_test --features wasm diff --git a/repl_test/src/wasm.rs b/repl_test/src/wasm.rs index 7522721d01..9216f2266b 100644 --- a/repl_test/src/wasm.rs +++ b/repl_test/src/wasm.rs @@ -1,14 +1,14 @@ use std::{ cell::RefCell, - env, fs, + fs, ops::{Deref, DerefMut}, path::Path, thread_local, }; -use wasmer::{imports, Function, Instance, Module, Store}; +use wasmer::{imports, Function, Instance, Module, Store, Value}; use wasmer_wasi::WasiState; -const WASM_REPL_COMPILER_PATH: &str = "../repl_wasm/pkg/roc_repl_wasm_bg.wasm"; +const WASM_REPL_COMPILER_PATH: &str = "../target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm"; thread_local! { static REPL_STATE: RefCell> = RefCell::new(None) @@ -19,9 +19,10 @@ struct ReplState { compiler: Instance, app: Option, result_addr: Option, + output: Option, } -fn wasmer_create_app(app_bytes_ptr: i32, app_bytes_len: i32) { +fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) { REPL_STATE.with(|f| { if let Some(state) = f.borrow_mut().deref_mut() { let compiler_memory = state.compiler.exports.get_memory("memory").unwrap(); @@ -114,30 +115,35 @@ fn wasmer_copy_input_string(src_buffer_addr: u32) { }) } +fn wasmer_copy_output_string(output_ptr: u32, output_len: u32) { + REPL_STATE.with(|f| { + if let Some(state) = f.borrow_mut().deref_mut() { + let compiler_memory = state.compiler.exports.get_memory("memory").unwrap(); + let compiler_memory_bytes: &mut [u8] = unsafe { compiler_memory.data_unchecked_mut() }; + + // Find the slice of bytes for the compiled Roc app + let ptr = output_ptr as usize; + let len = output_len as usize; + let output_bytes: &[u8] = &compiler_memory_bytes[ptr..][..len]; + + // Copy it out of the Wasm module + let copied_bytes = output_bytes.to_vec(); + let output = unsafe { String::from_utf8_unchecked(copied_bytes) }; + + state.output = Some(output) + } + }) +} + +fn dummy_system_time_now() -> f64 { + 0.0 +} + fn init_compiler() -> Instance { let path = Path::new(WASM_REPL_COMPILER_PATH); let wasm_module_bytes = match fs::read(&path) { Ok(bytes) => bytes, - Err(e) => { - if matches!(e.kind(), std::io::ErrorKind::NotFound) { - panic!( - "\n\n {}\n\n", - [ - "CANNOT BUILD WASM REPL TESTS", - "Please run repl_www/build.sh and try again!", - "", - "We have not yet automated this build dependency using Cargo.", - "The Cargo build for the tests would have to launch another Cargo build", - "for a *different target* (wasm), which is tricky.", - "It probably requires a second target directory to avoid locks.", - "We'll get to it eventually but it's not done yet.", - ] - .join("\n ") - ) - } else { - panic!("{:?}", e) - } - } + Err(e) => panic!("{}", format_compiler_load_error(e)), }; let store = Store::default(); @@ -149,41 +155,78 @@ fn init_compiler() -> Instance { "wasmer_run_app" => Function::new_native(&store, wasmer_run_app), "wasmer_get_result_and_memory" => Function::new_native(&store, wasmer_get_result_and_memory), "wasmer_copy_input_string" => Function::new_native(&store, wasmer_copy_input_string), + "wasmer_copy_output_string" => Function::new_native(&store, wasmer_copy_output_string), + "now" => Function::new_native(&store, dummy_system_time_now), } }; Instance::new(&wasmer_module, &import_object).unwrap() } -fn run(src: &'static str) -> String { - let compiler = init_compiler(); - - let entrypoint = compiler - .exports - .get_function("entrypoint_from_test") - .unwrap(); +fn format_compiler_load_error(e: std::io::Error) -> String { + if matches!(e.kind(), std::io::ErrorKind::NotFound) { + format!( + "\n\n {}\n\n", + [ + "CANNOT BUILD WASM REPL TESTS", + "Please run these tests using repl_test/run_wasm.sh!", + "", + "These tests combine two builds for two different targets,", + "which Cargo doesn't handle very well.", + "It probably requires a second target directory to avoid locks.", + "We'll get to it eventually but it's not done yet.", + ] + .join("\n ") + ) + } else { + format!("{:?}", e) + } +} +fn run(src: &'static str) -> (bool, String) { REPL_STATE.with(|f| { + let compiler = init_compiler(); + let new_state = ReplState { src, compiler, app: None, result_addr: None, + output: None, }; - *f.borrow_mut().deref_mut() = Some(new_state); - }); + { + *f.borrow_mut().deref_mut() = Some(new_state); + } - let actual = String::new(); - actual + if let Some(state) = f.borrow().deref() { + let entrypoint = state + .compiler + .exports + .get_function("entrypoint_from_test") + .unwrap(); + + let src_len = Value::I32(src.len() as i32); + let wasm_ok: i32 = entrypoint.call(&[src_len]).unwrap().deref()[0].unwrap_i32(); + let ok = wasm_ok != 0; + + let final_state = f.take().unwrap(); + + (ok, final_state.output.unwrap()) + } else { + panic!() + } + }) } pub fn expect_success(input: &'static str, expected: &str) { - let actual = run(input); - assert_eq!(actual, expected); + let (ok, output) = run(input); + assert_eq!(ok, true); + assert_eq!(output, expected); } pub fn expect_failure(input: &'static str, expected: &str) { - let actual = run(input); - assert_eq!(actual, expected); + let (ok, output) = run(input); + assert_eq!(ok, false); + assert_eq!(output, expected); } diff --git a/repl_wasm/Cargo.toml b/repl_wasm/Cargo.toml index 1e3723cf7f..5e34c1b389 100644 --- a/repl_wasm/Cargo.toml +++ b/repl_wasm/Cargo.toml @@ -11,6 +11,7 @@ roc_builtins = {path = "../compiler/builtins"} [dependencies] bumpalo = {version = "3.8.0", features = ["collections"]} +futures = {version = "0.3.17", optional = true} js-sys = "0.3.56" wasm-bindgen = "0.2.79" wasm-bindgen-futures = "0.4.29" @@ -23,5 +24,5 @@ roc_repl_eval = {path = "../repl_eval"} roc_target = {path = "../compiler/roc_target"} roc_types = {path = "../compiler/types"} -[dev-dependencies] -futures = "0.3.17" +[features] +wasmer = ["futures"] diff --git a/repl_wasm/src/interface_test.rs b/repl_wasm/src/interface_test.rs index 23fb9dcd01..8fc99f2c87 100644 --- a/repl_wasm/src/interface_test.rs +++ b/repl_wasm/src/interface_test.rs @@ -1,14 +1,17 @@ +use futures::executor; + extern "C" { - fn wasmer_create_app(wasm_module_bytes: &[u8]); + fn wasmer_create_app(app_bytes_ptr: *const u8, app_bytes_len: usize); fn wasmer_run_app() -> usize; fn wasmer_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize; fn wasmer_copy_input_string(src_buffer_addr: *mut u8); + fn wasmer_copy_output_string(output_ptr: *const u8, output_len: usize); } /// Async wrapper to match the equivalent JS function pub async fn js_create_app(wasm_module_bytes: &[u8]) -> Result<(), String> { unsafe { - wasmer_create_app(wasm_module_bytes); + wasmer_create_app(wasm_module_bytes.as_ptr(), wasm_module_bytes.len()); } Ok(()) } @@ -22,15 +25,23 @@ pub fn js_get_result_and_memory(buffer_alloc_addr: *mut u8) -> usize { } /// Entrypoint for Wasmer tests -/// - Synchronous API, to avoid the need to manage async calls across the Wasm/native boundary. -/// In the JS version, wasm_bindgen deals with this, as well as translating Rust Future <-> JS Promise. -/// - Manually copy the source bytes and convert to String. Again, wasm_bindgen does this for JS. -pub fn entrypoint_from_test(src_len: usize) -> Result { +/// - Synchronous API, to avoid the need to run an async executor across the Wasm/native boundary. +/// (wasmer has a sync API for creating an Instance, whereas browsers don't) +/// - Uses an extra callback to allocate & copy the input string (wasm_bindgen does this for JS) +#[no_mangle] +pub extern "C" fn entrypoint_from_test(src_len: usize) -> bool { let mut src_buffer = std::vec![0; src_len]; let src = unsafe { wasmer_copy_input_string(src_buffer.as_mut_ptr()); String::from_utf8_unchecked(src_buffer) }; let result_async = crate::repl::entrypoint_from_js(src); - executor::block_on(result_async) + let result = executor::block_on(result_async); + let ok = result.is_ok(); + + let output = result.unwrap_or_else(|s| s); + + unsafe { wasmer_copy_output_string(output.as_ptr(), output.len()) } + + ok } diff --git a/repl_wasm/src/lib.rs b/repl_wasm/src/lib.rs index ba6265928a..be90212c28 100644 --- a/repl_wasm/src/lib.rs +++ b/repl_wasm/src/lib.rs @@ -3,17 +3,17 @@ mod repl; // // Interface with external JS in the browser // -#[cfg(not(test))] +#[cfg(not(feature = "wasmer"))] mod interface_js; -#[cfg(not(test))] +#[cfg(not(feature = "wasmer"))] pub use interface_js::{entrypoint_from_js, js_create_app, js_get_result_and_memory, js_run_app}; // // Interface with test code outside the Wasm module // -#[cfg(test)] +#[cfg(feature = "wasmer")] mod interface_test; -#[cfg(test)] +#[cfg(feature = "wasmer")] pub use interface_test::{ entrypoint_from_test, js_create_app, js_get_result_and_memory, js_run_app, }; From 25cd7f9fabb15e9001e24ab085b7dac769edcb00 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 20 Feb 2022 21:38:59 +0000 Subject: [PATCH 14/18] repl_test: move some functions around --- repl_test/run_wasm.sh | 2 +- repl_test/src/wasm.rs | 48 +++++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/repl_test/run_wasm.sh b/repl_test/run_wasm.sh index 4d5ffbce3c..037082ac52 100755 --- a/repl_test/run_wasm.sh +++ b/repl_test/run_wasm.sh @@ -3,4 +3,4 @@ # We can try to make the build nicer later cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --features wasmer --release -cargo test -p repl_test --features wasm +cargo test -p repl_test --features wasm -- --nocapture literal_42 diff --git a/repl_test/src/wasm.rs b/repl_test/src/wasm.rs index 9216f2266b..d8cdcd6594 100644 --- a/repl_test/src/wasm.rs +++ b/repl_test/src/wasm.rs @@ -135,10 +135,6 @@ fn wasmer_copy_output_string(output_ptr: u32, output_len: u32) { }) } -fn dummy_system_time_now() -> f64 { - 0.0 -} - fn init_compiler() -> Instance { let path = Path::new(WASM_REPL_COMPILER_PATH); let wasm_module_bytes = match fs::read(&path) { @@ -163,26 +159,6 @@ fn init_compiler() -> Instance { Instance::new(&wasmer_module, &import_object).unwrap() } -fn format_compiler_load_error(e: std::io::Error) -> String { - if matches!(e.kind(), std::io::ErrorKind::NotFound) { - format!( - "\n\n {}\n\n", - [ - "CANNOT BUILD WASM REPL TESTS", - "Please run these tests using repl_test/run_wasm.sh!", - "", - "These tests combine two builds for two different targets,", - "which Cargo doesn't handle very well.", - "It probably requires a second target directory to avoid locks.", - "We'll get to it eventually but it's not done yet.", - ] - .join("\n ") - ) - } else { - format!("{:?}", e) - } -} - fn run(src: &'static str) -> (bool, String) { REPL_STATE.with(|f| { let compiler = init_compiler(); @@ -219,6 +195,30 @@ fn run(src: &'static str) -> (bool, String) { }) } +fn format_compiler_load_error(e: std::io::Error) -> String { + if matches!(e.kind(), std::io::ErrorKind::NotFound) { + format!( + "\n\n {}\n\n", + [ + "CANNOT BUILD WASM REPL TESTS", + "Please run these tests using repl_test/run_wasm.sh!", + "", + "These tests combine two builds for two different targets,", + "which Cargo doesn't handle very well.", + "It probably requires a second target directory to avoid locks.", + "We'll get to it eventually but it's not done yet.", + ] + .join("\n ") + ) + } else { + format!("{:?}", e) + } +} + +fn dummy_system_time_now() -> f64 { + 0.0 +} + pub fn expect_success(input: &'static str, expected: &str) { let (ok, output) = run(input); assert_eq!(ok, true); From 02889300d08efde7316a270653158b30052ebbd0 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 20 Feb 2022 22:59:16 +0000 Subject: [PATCH 15/18] repl_test: 2 thread_locals to avoid mutable borrow issues. Tests running but very slow. --- repl_test/run_wasm.sh | 2 +- repl_test/src/wasm.rs | 129 ++++++++++++++++++++++++++---------------- repl_wasm/src/repl.rs | 2 +- 3 files changed, 81 insertions(+), 52 deletions(-) diff --git a/repl_test/run_wasm.sh b/repl_test/run_wasm.sh index 037082ac52..4d5ffbce3c 100755 --- a/repl_test/run_wasm.sh +++ b/repl_test/run_wasm.sh @@ -3,4 +3,4 @@ # We can try to make the build nicer later cargo build --target wasm32-unknown-unknown -p roc_repl_wasm --features wasmer --release -cargo test -p repl_test --features wasm -- --nocapture literal_42 +cargo test -p repl_test --features wasm diff --git a/repl_test/src/wasm.rs b/repl_test/src/wasm.rs index d8cdcd6594..a6f0a86a99 100644 --- a/repl_test/src/wasm.rs +++ b/repl_test/src/wasm.rs @@ -10,28 +10,31 @@ use wasmer_wasi::WasiState; const WASM_REPL_COMPILER_PATH: &str = "../target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm"; +thread_local! { + static COMPILER: RefCell> = RefCell::new(None) +} + thread_local! { static REPL_STATE: RefCell> = RefCell::new(None) } struct ReplState { src: &'static str, - compiler: Instance, app: Option, result_addr: Option, output: Option, } fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) { - REPL_STATE.with(|f| { - if let Some(state) = f.borrow_mut().deref_mut() { - let compiler_memory = state.compiler.exports.get_memory("memory").unwrap(); - let compiler_memory_bytes: &mut [u8] = unsafe { compiler_memory.data_unchecked_mut() }; + let app = COMPILER.with(|f| { + if let Some(compiler) = f.borrow().deref() { + let memory = compiler.exports.get_memory("memory").unwrap(); + let memory_bytes: &[u8] = unsafe { memory.data_unchecked() }; // Find the slice of bytes for the compiled Roc app let ptr = app_bytes_ptr as usize; let len = app_bytes_len as usize; - let app_module_bytes: &[u8] = &compiler_memory_bytes[ptr..][..len]; + let app_module_bytes: &[u8] = &memory_bytes[ptr..][..len]; // Parse the bytes into a Wasmer module let store = Store::default(); @@ -43,13 +46,20 @@ fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) { .import_object(&wasmer_module) .unwrap_or_else(|_| imports!()); - // Create an executable instance (give it a stack & heap, etc. For ELF, this would be the OS's job.) - let instance = Instance::new(&wasmer_module, &import_object).unwrap(); - state.app = Some(instance) + // Create an executable instance. (Give it a stack & heap, etc. If this was ELF, it would be the OS's job.) + Instance::new(&wasmer_module, &import_object).unwrap() } else { - panic!("REPL state not found") + unreachable!() } - }) + }); + + REPL_STATE.with(|f| { + if let Some(state) = f.borrow_mut().deref_mut() { + state.app = Some(app) + } else { + unreachable!() + } + }); } fn wasmer_run_app() -> u32 { @@ -67,10 +77,10 @@ fn wasmer_run_app() -> u32 { let memory = app.exports.get_memory("memory").unwrap(); memory.size().bytes().0 as u32 } else { - panic!("App not found") + unreachable!() } } else { - panic!("REPL state not found") + unreachable!() } }) } @@ -80,17 +90,23 @@ fn wasmer_get_result_and_memory(buffer_alloc_addr: u32) -> u32 { if let Some(state) = f.borrow().deref() { if let Some(app) = &state.app { let app_memory = app.exports.get_memory("memory").unwrap(); - let compiler_memory = state.compiler.exports.get_memory("memory").unwrap(); let result_addr = state.result_addr.unwrap(); - let compiler_memory_bytes: &mut [u8] = - unsafe { compiler_memory.data_unchecked_mut() }; - let app_memory_bytes: &[u8] = unsafe { app_memory.data_unchecked() }; let buf_addr = buffer_alloc_addr as usize; let len = app_memory_bytes.len(); - compiler_memory_bytes[buf_addr..][..len].copy_from_slice(app_memory_bytes); + + COMPILER.with(|f| { + if let Some(compiler) = f.borrow().deref() { + let memory = compiler.exports.get_memory("memory").unwrap(); + let compiler_memory_bytes: &mut [u8] = + unsafe { memory.data_unchecked_mut() }; + compiler_memory_bytes[buf_addr..][..len].copy_from_slice(app_memory_bytes); + } else { + unreachable!() + } + }); result_addr } else { @@ -103,33 +119,49 @@ fn wasmer_get_result_and_memory(buffer_alloc_addr: u32) -> u32 { } fn wasmer_copy_input_string(src_buffer_addr: u32) { - REPL_STATE.with(|f| { - if let Some(state) = f.borrow().deref() { - let compiler_memory = state.compiler.exports.get_memory("memory").unwrap(); - let compiler_memory_bytes: &mut [u8] = unsafe { compiler_memory.data_unchecked_mut() }; + let src = REPL_STATE.with(|rs| { + if let Some(state) = rs.borrow_mut().deref_mut() { + std::mem::take(&mut state.src) + } else { + unreachable!() + } + }); + + COMPILER.with(|c| { + if let Some(compiler) = c.borrow().deref() { + let memory = compiler.exports.get_memory("memory").unwrap(); + let memory_bytes: &mut [u8] = unsafe { memory.data_unchecked_mut() }; let buf_addr = src_buffer_addr as usize; - let len = state.src.len(); - compiler_memory_bytes[buf_addr..][..len].copy_from_slice(state.src.as_bytes()); + let len = src.len(); + memory_bytes[buf_addr..][..len].copy_from_slice(src.as_bytes()); + } else { + unreachable!() } }) } fn wasmer_copy_output_string(output_ptr: u32, output_len: u32) { - REPL_STATE.with(|f| { - if let Some(state) = f.borrow_mut().deref_mut() { - let compiler_memory = state.compiler.exports.get_memory("memory").unwrap(); - let compiler_memory_bytes: &mut [u8] = unsafe { compiler_memory.data_unchecked_mut() }; + let output: String = COMPILER.with(|c| { + if let Some(compiler) = c.borrow().deref() { + let memory = compiler.exports.get_memory("memory").unwrap(); + let memory_bytes: &[u8] = unsafe { memory.data_unchecked() }; - // Find the slice of bytes for the compiled Roc app + // Find the slice of bytes for the output string let ptr = output_ptr as usize; let len = output_len as usize; - let output_bytes: &[u8] = &compiler_memory_bytes[ptr..][..len]; + let output_bytes: &[u8] = &memory_bytes[ptr..][..len]; // Copy it out of the Wasm module let copied_bytes = output_bytes.to_vec(); - let output = unsafe { String::from_utf8_unchecked(copied_bytes) }; + unsafe { String::from_utf8_unchecked(copied_bytes) } + } else { + unreachable!() + } + }); + REPL_STATE.with(|f| { + if let Some(state) = f.borrow_mut().deref_mut() { state.output = Some(output) } }) @@ -160,39 +192,36 @@ fn init_compiler() -> Instance { } fn run(src: &'static str) -> (bool, String) { - REPL_STATE.with(|f| { - let compiler = init_compiler(); - - let new_state = ReplState { + REPL_STATE.with(|rs| { + *rs.borrow_mut().deref_mut() = Some(ReplState { src, - compiler, app: None, result_addr: None, output: None, - }; + }); + }); - { - *f.borrow_mut().deref_mut() = Some(new_state); - } + let ok = COMPILER.with(|c| { + *c.borrow_mut().deref_mut() = Some(init_compiler()); - if let Some(state) = f.borrow().deref() { - let entrypoint = state - .compiler + if let Some(compiler) = c.borrow().deref() { + let entrypoint = compiler .exports .get_function("entrypoint_from_test") .unwrap(); let src_len = Value::I32(src.len() as i32); let wasm_ok: i32 = entrypoint.call(&[src_len]).unwrap().deref()[0].unwrap_i32(); - let ok = wasm_ok != 0; - - let final_state = f.take().unwrap(); - - (ok, final_state.output.unwrap()) + wasm_ok != 0 } else { - panic!() + unreachable!() } - }) + }); + + let final_state: ReplState = REPL_STATE.with(|rs| rs.take()).unwrap(); + let output: String = final_state.output.unwrap(); + + (ok, output) } fn format_compiler_load_error(e: std::io::Error) -> String { diff --git a/repl_wasm/src/repl.rs b/repl_wasm/src/repl.rs index 6234225c66..5c3686b664 100644 --- a/repl_wasm/src/repl.rs +++ b/repl_wasm/src/repl.rs @@ -237,7 +237,7 @@ pub async fn entrypoint_from_js(src: String) -> Result { // Transform the Expr to a string // `Result::Err` becomes a JS exception that will be caught and displayed match format_answer(arena, res_answer, expr_type_str) { - ReplOutput::NoProblems { expr, expr_type } => Ok(format!("\n{}: {}", expr, expr_type)), + ReplOutput::NoProblems { expr, expr_type } => Ok(format!("{} : {}", expr, expr_type)), ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))), } } From 9344aa89e50a4b303a999413e381e289bf46625b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 20 Feb 2022 23:09:38 +0000 Subject: [PATCH 16/18] repl_test: ignore broken wasm tests --- repl_test/src/tests.rs | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index b862a6fdc3..8e6e0f89ee 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -8,6 +8,7 @@ use crate::cli::{expect_failure, expect_success}; #[cfg(feature = "wasm")] mod wasm; #[cfg(feature = "wasm")] +#[allow(unused_imports)] use crate::wasm::{expect_failure, expect_success}; #[test] @@ -55,16 +56,19 @@ fn float_addition() { expect_success("1.1 + 2", "3.1 : F64"); } +#[cfg(not(feature = "wasm"))] #[test] fn num_rem() { expect_success("299 % 10", "Ok 9 : Result (Int *) [ DivByZero ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn num_floor_division_success() { expect_success("Num.divFloor 4 3", "Ok 1 : Result (Int *) [ DivByZero ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn num_floor_division_divby_zero() { expect_success( @@ -73,11 +77,13 @@ fn num_floor_division_divby_zero() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn num_ceil_division_success() { expect_success("Num.divCeil 4 3", "Ok 2 : Result (Int *) [ DivByZero ]*") } +#[cfg(not(feature = "wasm"))] #[test] fn bool_in_record() { expect_success("{ x: 1 == 1 }", "{ x: True } : { x : Bool }"); @@ -92,18 +98,21 @@ fn bool_in_record() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn bool_basic_equality() { expect_success("1 == 1", "True : Bool"); expect_success("1 != 1", "False : Bool"); } +#[cfg(not(feature = "wasm"))] #[test] fn arbitrary_tag_unions() { expect_success("if 1 == 1 then Red else Green", "Red : [ Green, Red ]*"); expect_success("if 1 != 1 then Red else Green", "Green : [ Green, Red ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn tag_without_arguments() { expect_success("True", "True : [ True ]*"); @@ -123,6 +132,7 @@ fn byte_tag_union() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn tag_in_record() { expect_success( @@ -142,11 +152,13 @@ fn single_element_tag_union() { expect_success("Foo 1 3.14", "Foo 1 3.14 : [ Foo (Num *) (Float *) ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn newtype_of_unit() { expect_success("Foo Bar", "Foo Bar : [ Foo [ Bar ]* ]*"); } +#[cfg(not(feature = "wasm"))] #[test] fn newtype_of_big_data() { expect_success( @@ -162,6 +174,7 @@ fn newtype_of_big_data() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn newtype_nested() { expect_success( @@ -177,6 +190,7 @@ fn newtype_nested() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn newtype_of_big_of_newtype() { expect_success( @@ -207,16 +221,19 @@ fn literal_empty_str() { expect_success("\"\"", "\"\" : Str"); } +#[cfg(not(feature = "wasm"))] #[test] fn literal_ascii_str() { expect_success("\"Hello, World!\"", "\"Hello, World!\" : Str"); } +#[cfg(not(feature = "wasm"))] #[test] fn literal_utf8_str() { expect_success("\"👩‍👩‍👦‍👦\"", "\"👩‍👩‍👦‍👦\" : Str"); } +#[cfg(not(feature = "wasm"))] #[test] fn str_concat() { expect_success( @@ -235,6 +252,7 @@ fn literal_empty_list() { expect_success("[]", "[] : List *"); } +#[cfg(not(feature = "wasm"))] #[test] fn literal_empty_list_empty_record() { expect_success("[ {} ]", "[ {} ] : List {}"); @@ -255,11 +273,13 @@ fn literal_float_list() { expect_success("[ 1.1, 2.2, 3.3 ]", "[ 1.1, 2.2, 3.3 ] : List (Float *)"); } +#[cfg(not(feature = "wasm"))] #[test] fn literal_string_list() { expect_success(r#"[ "a", "b", "cd" ]"#, r#"[ "a", "b", "cd" ] : List Str"#); } +#[cfg(not(feature = "wasm"))] #[test] fn nested_string_list() { expect_success( @@ -327,6 +347,7 @@ fn num_mul_wrap() { expect_success("Num.mulWrap Num.maxI64 2", "-2 : I64"); } +#[cfg(not(feature = "wasm"))] #[test] fn num_add_checked() { expect_success("Num.addChecked 1 1", "Ok 2 : Result (Num *) [ Overflow ]*"); @@ -336,6 +357,7 @@ fn num_add_checked() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn num_sub_checked() { expect_success("Num.subChecked 1 1", "Ok 0 : Result (Num *) [ Overflow ]*"); @@ -345,6 +367,7 @@ fn num_sub_checked() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn num_mul_checked() { expect_success( @@ -357,6 +380,7 @@ fn num_mul_checked() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn list_concat() { expect_success( @@ -365,6 +389,7 @@ fn list_concat() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn list_contains() { expect_success("List.contains [] 0", "False : Bool"); @@ -372,6 +397,7 @@ fn list_contains() { expect_success("List.contains [ 1, 2, 3 ] 4", "False : Bool"); } +#[cfg(not(feature = "wasm"))] #[test] fn list_sum() { expect_success("List.sum []", "0 : Num *"); @@ -379,6 +405,7 @@ fn list_sum() { expect_success("List.sum [ 1.1, 2.2, 3.3 ]", "6.6 : F64"); } +#[cfg(not(feature = "wasm"))] #[test] fn list_first() { expect_success( @@ -391,6 +418,7 @@ fn list_first() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn list_last() { expect_success( @@ -404,6 +432,7 @@ fn list_last() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn empty_record() { expect_success("{}", "{} : {}"); @@ -527,16 +556,19 @@ fn identity_lambda() { expect_success("\\x -> x", " : a -> a"); } +#[cfg(not(feature = "wasm"))] #[test] fn sum_lambda() { expect_success("\\x, y -> x + y", " : Num a, Num a -> Num a"); } +#[cfg(not(feature = "wasm"))] #[test] fn stdlib_function() { expect_success("Num.abs", " : Num a -> Num a"); } +#[cfg(not(feature = "wasm"))] #[test] fn too_few_args() { expect_failure( @@ -557,6 +589,7 @@ fn too_few_args() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn type_problem() { expect_failure( @@ -613,6 +646,7 @@ fn multiline_input() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn recursive_tag_union_flat_variant() { expect_success( @@ -628,6 +662,7 @@ fn recursive_tag_union_flat_variant() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn large_recursive_tag_union_flat_variant() { expect_success( @@ -644,6 +679,7 @@ fn large_recursive_tag_union_flat_variant() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn recursive_tag_union_recursive_variant() { expect_success( @@ -659,6 +695,7 @@ fn recursive_tag_union_recursive_variant() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn large_recursive_tag_union_recursive_variant() { expect_success( @@ -675,6 +712,7 @@ fn large_recursive_tag_union_recursive_variant() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn recursive_tag_union_into_flat_tag_union() { expect_success( @@ -690,6 +728,7 @@ fn recursive_tag_union_into_flat_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn non_nullable_unwrapped_tag_union() { expect_success( @@ -709,6 +748,7 @@ fn non_nullable_unwrapped_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn nullable_unwrapped_tag_union() { expect_success( @@ -728,6 +768,7 @@ fn nullable_unwrapped_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn nullable_wrapped_tag_union() { expect_success( @@ -751,6 +792,7 @@ fn nullable_wrapped_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn large_nullable_wrapped_tag_union() { // > 7 non-empty variants so that to force tag storage alongside the data @@ -775,6 +817,7 @@ fn large_nullable_wrapped_tag_union() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn issue_2300() { expect_success( @@ -783,6 +826,7 @@ fn issue_2300() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn function_in_list() { expect_success( @@ -791,6 +835,7 @@ fn function_in_list() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn function_in_record() { expect_success( @@ -799,6 +844,7 @@ fn function_in_record() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn function_in_unwrapped_record() { expect_success( @@ -807,6 +853,7 @@ fn function_in_unwrapped_record() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn function_in_tag() { expect_success( @@ -837,6 +884,7 @@ fn print_u8s() { ) } +#[cfg(not(feature = "wasm"))] #[test] fn parse_problem() { expect_failure( @@ -860,6 +908,7 @@ fn parse_problem() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn mono_problem() { expect_failure( @@ -892,6 +941,7 @@ fn mono_problem() { ); } +#[cfg(not(feature = "wasm"))] #[test] fn issue_2343_complete_mono_with_shadowed_vars() { expect_failure( @@ -925,3 +975,4 @@ fn issue_2343_complete_mono_with_shadowed_vars() { ), ); } + From e5be6bfdd14569a2dafe88cbdb40f5fb3b589c55 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 20 Feb 2022 23:55:36 +0000 Subject: [PATCH 17/18] Fix formatting --- cli_utils/Cargo.lock | 3 --- repl_test/src/tests.rs | 1 - 2 files changed, 4 deletions(-) diff --git a/cli_utils/Cargo.lock b/cli_utils/Cargo.lock index c25e9072c9..c385582180 100644 --- a/cli_utils/Cargo.lock +++ b/cli_utils/Cargo.lock @@ -2771,7 +2771,6 @@ dependencies = [ "roc_types", "roc_unify", "ven_pretty", - "wasm-bindgen", ] [[package]] @@ -2880,7 +2879,6 @@ dependencies = [ "roc_reporting", "roc_target", "roc_types", - "wasm-bindgen", ] [[package]] @@ -2913,7 +2911,6 @@ dependencies = [ "roc_region", "roc_types", "roc_unify", - "wasm-bindgen", ] [[package]] diff --git a/repl_test/src/tests.rs b/repl_test/src/tests.rs index 8e6e0f89ee..d6e88e44c3 100644 --- a/repl_test/src/tests.rs +++ b/repl_test/src/tests.rs @@ -975,4 +975,3 @@ fn issue_2343_complete_mono_with_shadowed_vars() { ), ); } - From bfca8ec955efa70995c9501a396201bec7b1fc81 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 21 Feb 2022 08:36:34 +0000 Subject: [PATCH 18/18] Make wasmer features common across all packages --- Cargo.lock | 117 +------------------------------------------------ cli/Cargo.toml | 4 +- 2 files changed, 3 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b71fefa36d..1b315a7b5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli", ] [[package]] @@ -739,66 +739,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cranelift-bforest" -version = "0.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ca3560686e7c9c7ed7e0fe77469f2410ba5d7781b1acaa9adc8d8deea28e3e" -dependencies = [ - "cranelift-entity", -] - -[[package]] -name = "cranelift-codegen" -version = "0.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf9bf1ffffb6ce3d2e5ebc83549bd2436426c99b31cc550d521364cbe35d276" -dependencies = [ - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-entity", - "gimli 0.24.0", - "log", - "regalloc", - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cranelift-codegen-meta" -version = "0.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cc21936a5a6d07e23849ffe83e5c1f6f50305c074f4b2970ca50c13bf55b821" -dependencies = [ - "cranelift-codegen-shared", - "cranelift-entity", -] - -[[package]] -name = "cranelift-codegen-shared" -version = "0.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5b6ffaa87560bebe69a5446449da18090b126037920b0c1c6d5945f72faf6b" - -[[package]] -name = "cranelift-entity" -version = "0.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6b4a8bef04f82e4296782646f733c641d09497df2fabf791323fefaa44c64c" - -[[package]] -name = "cranelift-frontend" -version = "0.74.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b783b351f966fce33e3c03498cb116d16d97a8f9978164a60920bd0d3a99c" -dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon", -] - [[package]] name = "crc32fast" version = "1.2.1" @@ -1306,12 +1246,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fd-lock" version = "3.0.2" @@ -1531,17 +1465,6 @@ dependencies = [ "syn", ] -[[package]] -name = "gimli" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] - [[package]] name = "gimli" version = "0.26.1" @@ -3150,17 +3073,6 @@ dependencies = [ "redox_syscall", ] -[[package]] -name = "regalloc" -version = "0.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" -dependencies = [ - "log", - "rustc-hash", - "smallvec", -] - [[package]] name = "regex" version = "1.5.4" @@ -4241,12 +4153,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "static_assertions" version = "1.1.0" @@ -4774,7 +4680,6 @@ dependencies = [ "target-lexicon", "thiserror", "wasmer-compiler", - "wasmer-compiler-cranelift", "wasmer-compiler-singlepass", "wasmer-derive", "wasmer-engine", @@ -4804,26 +4709,6 @@ dependencies = [ "wasmparser", ] -[[package]] -name = "wasmer-compiler-cranelift" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a570746cbec434179e2d53357973a34dfdb208043104e8fac3b7b0023015cf6" -dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", - "gimli 0.24.0", - "loupe", - "more-asserts", - "rayon", - "smallvec", - "tracing", - "wasmer-compiler", - "wasmer-types", - "wasmer-vm", -] - [[package]] name = "wasmer-compiler-singlepass" version = "2.0.0" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index dfffe25ee0..d47d8cce8a 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -66,11 +66,11 @@ mimalloc = { version = "0.1.26", default-features = false } target-lexicon = "0.12.2" tempfile = "3.2.0" -wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-cranelift", "default-universal"] } +wasmer = { version = "2.0.0", optional = true, default-features = false, features = ["default-singlepass", "default-universal"] } wasmer-wasi = { version = "2.0.0", optional = true } [dev-dependencies] -wasmer = { version = "2.0.0", default-features = false, features = ["default-cranelift", "default-universal"] } +wasmer = { version = "2.0.0", default-features = false, features = ["default-singlepass", "default-universal"] } wasmer-wasi = "2.0.0" pretty_assertions = "1.0.0" roc_test_utils = { path = "../test_utils" }