mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 15:51:12 +00:00
Merge pull request #2517 from rtfeldman/repl-www
Web REPL initial version
This commit is contained in:
commit
e8571de1ae
19 changed files with 915 additions and 391 deletions
140
Cargo.lock
generated
140
Cargo.lock
generated
|
@ -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"
|
||||
|
@ -1193,6 +1133,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"
|
||||
|
@ -1280,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"
|
||||
|
@ -1505,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"
|
||||
|
@ -3124,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"
|
||||
|
@ -3194,6 +3132,8 @@ dependencies = [
|
|||
"roc_repl_cli",
|
||||
"roc_test_utils",
|
||||
"strip-ansi-escapes",
|
||||
"wasmer",
|
||||
"wasmer-wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3705,6 +3645,7 @@ name = "roc_repl_wasm"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"futures",
|
||||
"js-sys",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
|
@ -4213,12 +4154,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"
|
||||
|
@ -4746,7 +4681,7 @@ dependencies = [
|
|||
"target-lexicon",
|
||||
"thiserror",
|
||||
"wasmer-compiler",
|
||||
"wasmer-compiler-cranelift",
|
||||
"wasmer-compiler-singlepass",
|
||||
"wasmer-derive",
|
||||
"wasmer-engine",
|
||||
"wasmer-engine-dylib",
|
||||
|
@ -4776,20 +4711,19 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmer-compiler-cranelift"
|
||||
name = "wasmer-compiler-singlepass"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a570746cbec434179e2d53357973a34dfdb208043104e8fac3b7b0023015cf6"
|
||||
checksum = "9429b9f7708c582d855b1787f09c7029ff23fb692550d4a1cc351c8ea84c3014"
|
||||
dependencies = [
|
||||
"cranelift-codegen",
|
||||
"cranelift-entity",
|
||||
"cranelift-frontend",
|
||||
"gimli 0.24.0",
|
||||
"byteorder",
|
||||
"dynasm",
|
||||
"dynasmrt",
|
||||
"lazy_static",
|
||||
"loupe",
|
||||
"more-asserts",
|
||||
"rayon",
|
||||
"smallvec",
|
||||
"tracing",
|
||||
"wasmer-compiler",
|
||||
"wasmer-types",
|
||||
"wasmer-vm",
|
||||
|
|
|
@ -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" }
|
||||
|
|
3
cli_utils/Cargo.lock
generated
3
cli_utils/Cargo.lock
generated
|
@ -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]]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -489,10 +489,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 = <T as crate::helpers::llvm::FromWasmerMemory>::decode(
|
||||
memory,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = []
|
||||
|
|
6
repl_test/run_wasm.sh
Executable file
6
repl_test/run_wasm.sh
Executable file
|
@ -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
|
|
@ -1,10 +1,16 @@
|
|||
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")]
|
||||
#[allow(unused_imports)]
|
||||
use crate::wasm::{expect_failure, expect_success};
|
||||
|
||||
#[test]
|
||||
fn literal_0() {
|
||||
expect_success("0", "0 : Num *");
|
||||
|
@ -50,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(
|
||||
|
@ -68,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 }");
|
||||
|
@ -87,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 ]*");
|
||||
|
@ -118,6 +132,7 @@ fn byte_tag_union() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn tag_in_record() {
|
||||
expect_success(
|
||||
|
@ -137,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(
|
||||
|
@ -157,6 +174,7 @@ fn newtype_of_big_data() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn newtype_nested() {
|
||||
expect_success(
|
||||
|
@ -172,6 +190,7 @@ fn newtype_nested() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn newtype_of_big_of_newtype() {
|
||||
expect_success(
|
||||
|
@ -202,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(
|
||||
|
@ -230,6 +252,7 @@ fn literal_empty_list() {
|
|||
expect_success("[]", "[] : List *");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn literal_empty_list_empty_record() {
|
||||
expect_success("[ {} ]", "[ {} ] : List {}");
|
||||
|
@ -250,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(
|
||||
|
@ -322,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 ]*");
|
||||
|
@ -331,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 ]*");
|
||||
|
@ -340,6 +367,7 @@ fn num_sub_checked() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn num_mul_checked() {
|
||||
expect_success(
|
||||
|
@ -352,6 +380,7 @@ fn num_mul_checked() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn list_concat() {
|
||||
expect_success(
|
||||
|
@ -360,6 +389,7 @@ fn list_concat() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn list_contains() {
|
||||
expect_success("List.contains [] 0", "False : Bool");
|
||||
|
@ -367,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 *");
|
||||
|
@ -374,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(
|
||||
|
@ -386,6 +418,7 @@ fn list_first() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn list_last() {
|
||||
expect_success(
|
||||
|
@ -399,6 +432,7 @@ fn list_last() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
expect_success("{}", "{} : {}");
|
||||
|
@ -522,16 +556,19 @@ fn identity_lambda() {
|
|||
expect_success("\\x -> x", "<function> : a -> a");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn sum_lambda() {
|
||||
expect_success("\\x, y -> x + y", "<function> : Num a, Num a -> Num a");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn stdlib_function() {
|
||||
expect_success("Num.abs", "<function> : Num a -> Num a");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn too_few_args() {
|
||||
expect_failure(
|
||||
|
@ -552,6 +589,7 @@ fn too_few_args() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn type_problem() {
|
||||
expect_failure(
|
||||
|
@ -608,6 +646,7 @@ fn multiline_input() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn recursive_tag_union_flat_variant() {
|
||||
expect_success(
|
||||
|
@ -623,6 +662,7 @@ fn recursive_tag_union_flat_variant() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn large_recursive_tag_union_flat_variant() {
|
||||
expect_success(
|
||||
|
@ -639,6 +679,7 @@ fn large_recursive_tag_union_flat_variant() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn recursive_tag_union_recursive_variant() {
|
||||
expect_success(
|
||||
|
@ -654,6 +695,7 @@ fn recursive_tag_union_recursive_variant() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn large_recursive_tag_union_recursive_variant() {
|
||||
expect_success(
|
||||
|
@ -670,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(
|
||||
|
@ -685,6 +728,7 @@ fn recursive_tag_union_into_flat_tag_union() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn non_nullable_unwrapped_tag_union() {
|
||||
expect_success(
|
||||
|
@ -704,6 +748,7 @@ fn non_nullable_unwrapped_tag_union() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn nullable_unwrapped_tag_union() {
|
||||
expect_success(
|
||||
|
@ -723,6 +768,7 @@ fn nullable_unwrapped_tag_union() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn nullable_wrapped_tag_union() {
|
||||
expect_success(
|
||||
|
@ -746,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
|
||||
|
@ -770,6 +817,7 @@ fn large_nullable_wrapped_tag_union() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn issue_2300() {
|
||||
expect_success(
|
||||
|
@ -778,6 +826,7 @@ fn issue_2300() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn function_in_list() {
|
||||
expect_success(
|
||||
|
@ -786,6 +835,7 @@ fn function_in_list() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn function_in_record() {
|
||||
expect_success(
|
||||
|
@ -794,6 +844,7 @@ fn function_in_record() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn function_in_unwrapped_record() {
|
||||
expect_success(
|
||||
|
@ -802,6 +853,7 @@ fn function_in_unwrapped_record() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn function_in_tag() {
|
||||
expect_success(
|
||||
|
@ -832,6 +884,7 @@ fn print_u8s() {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn parse_problem() {
|
||||
expect_failure(
|
||||
|
@ -855,6 +908,7 @@ fn parse_problem() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn mono_problem() {
|
||||
expect_failure(
|
||||
|
@ -887,6 +941,7 @@ fn mono_problem() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wasm"))]
|
||||
#[test]
|
||||
fn issue_2343_complete_mono_with_shadowed_vars() {
|
||||
expect_failure(
|
||||
|
|
261
repl_test/src/wasm.rs
Normal file
261
repl_test/src/wasm.rs
Normal file
|
@ -0,0 +1,261 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
fs,
|
||||
ops::{Deref, DerefMut},
|
||||
path::Path,
|
||||
thread_local,
|
||||
};
|
||||
use wasmer::{imports, Function, Instance, Module, Store, Value};
|
||||
use wasmer_wasi::WasiState;
|
||||
|
||||
const WASM_REPL_COMPILER_PATH: &str = "../target/wasm32-unknown-unknown/release/roc_repl_wasm.wasm";
|
||||
|
||||
thread_local! {
|
||||
static COMPILER: RefCell<Option<Instance>> = RefCell::new(None)
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static REPL_STATE: RefCell<Option<ReplState>> = RefCell::new(None)
|
||||
}
|
||||
|
||||
struct ReplState {
|
||||
src: &'static str,
|
||||
app: Option<Instance>,
|
||||
result_addr: Option<u32>,
|
||||
output: Option<String>,
|
||||
}
|
||||
|
||||
fn wasmer_create_app(app_bytes_ptr: u32, app_bytes_len: u32) {
|
||||
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] = &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. If this was ELF, it would be the OS's job.)
|
||||
Instance::new(&wasmer_module, &import_object).unwrap()
|
||||
} else {
|
||||
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 {
|
||||
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) => result[0].unwrap_i32(),
|
||||
};
|
||||
state.result_addr = Some(result_addr as u32);
|
||||
|
||||
let memory = app.exports.get_memory("memory").unwrap();
|
||||
memory.size().bytes().0 as u32
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 result_addr = state.result_addr.unwrap();
|
||||
|
||||
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.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 {
|
||||
panic!("REPL app not found")
|
||||
}
|
||||
} else {
|
||||
panic!("REPL state not found")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn wasmer_copy_input_string(src_buffer_addr: u32) {
|
||||
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 = 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) {
|
||||
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 output string
|
||||
let ptr = output_ptr as usize;
|
||||
let len = output_len as usize;
|
||||
let output_bytes: &[u8] = &memory_bytes[ptr..][..len];
|
||||
|
||||
// Copy it out of the Wasm module
|
||||
let copied_bytes = output_bytes.to_vec();
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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) => panic!("{}", format_compiler_load_error(e)),
|
||||
};
|
||||
|
||||
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),
|
||||
"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) -> (bool, String) {
|
||||
REPL_STATE.with(|rs| {
|
||||
*rs.borrow_mut().deref_mut() = Some(ReplState {
|
||||
src,
|
||||
app: None,
|
||||
result_addr: None,
|
||||
output: None,
|
||||
});
|
||||
});
|
||||
|
||||
let ok = COMPILER.with(|c| {
|
||||
*c.borrow_mut().deref_mut() = Some(init_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();
|
||||
wasm_ok != 0
|
||||
} else {
|
||||
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 {
|
||||
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);
|
||||
assert_eq!(output, expected);
|
||||
}
|
||||
|
||||
pub fn expect_failure(input: &'static str, expected: &str) {
|
||||
let (ok, output) = run(input);
|
||||
assert_eq!(ok, false);
|
||||
assert_eq!(output, expected);
|
||||
}
|
|
@ -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"
|
||||
|
@ -22,3 +23,6 @@ roc_parse = {path = "../compiler/parse"}
|
|||
roc_repl_eval = {path = "../repl_eval"}
|
||||
roc_target = {path = "../compiler/roc_target"}
|
||||
roc_types = {path = "../compiler/types"}
|
||||
|
||||
[features]
|
||||
wasmer = ["futures"]
|
||||
|
|
24
repl_wasm/src/interface_js.rs
Normal file
24
repl_wasm/src/interface_js.rs
Normal file
|
@ -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<String, String> {
|
||||
crate::repl::entrypoint_from_js(src).await
|
||||
}
|
47
repl_wasm/src/interface_test.rs
Normal file
47
repl_wasm/src/interface_test.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use futures::executor;
|
||||
|
||||
extern "C" {
|
||||
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.as_ptr(), wasm_module_bytes.len());
|
||||
}
|
||||
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) }
|
||||
}
|
||||
|
||||
/// Entrypoint for Wasmer tests
|
||||
/// - 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);
|
||||
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
|
||||
}
|
|
@ -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(feature = "wasmer"))]
|
||||
mod interface_js;
|
||||
#[cfg(not(feature = "wasmer"))]
|
||||
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(feature = "wasmer")]
|
||||
mod interface_test;
|
||||
#[cfg(feature = "wasmer")]
|
||||
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;
|
||||
|
||||
#[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> {
|
||||
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::<usize>());
|
||||
let bytes = &self.copied_bytes[elems_addr..][..len];
|
||||
std::str::from_utf8(bytes).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
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<Return, F>(&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();
|
||||
|
||||
// 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::<u64>()) + 1);
|
||||
let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) };
|
||||
|
||||
let app_result_addr = js_get_result_and_memory(copied_bytes.as_mut_ptr());
|
||||
|
||||
let result: Return = unsafe {
|
||||
let ptr: *const Return = std::mem::transmute(&copied_bytes[app_result_addr]);
|
||||
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<T, F>(
|
||||
&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();
|
||||
|
||||
// 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::<u64>()) + 1);
|
||||
let copied_bytes: &mut [u8] = unsafe { std::mem::transmute(copy_buffer_aligned) };
|
||||
|
||||
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<String, String> {
|
||||
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!("<function> : {}", 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::<MutSet<_>>(),
|
||||
};
|
||||
|
||||
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"))),
|
||||
}
|
||||
}
|
||||
|
|
243
repl_wasm/src/repl.rs
Normal file
243
repl_wasm/src/repl.rs
Normal file
|
@ -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::<usize>());
|
||||
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::<u64>()) + 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<Return, F>(&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<T, F>(
|
||||
&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<String, String> {
|
||||
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!("<function> : {}", 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::<MutSet<_>>(),
|
||||
};
|
||||
|
||||
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!("{} : {}", expr, expr_type)),
|
||||
ReplOutput::Problems(lines) => Err(format!("\n{}\n", lines.join("\n\n"))),
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
<title>Mock REPL</title>
|
||||
<title>Roc REPL</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="body-wrapper">
|
||||
|
|
|
@ -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,15 +75,21 @@ 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;
|
||||
}
|
||||
|
||||
// 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 };
|
||||
|
||||
|
|
192
repl_www/public/wasi.js
Normal file
192
repl_www/public/wasi.js
Normal file
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue