Add *.wast support for invoking components (#4526)
This commit builds on bytecodealliance/wasm-tools#690 to add support to testing of the component model to execute functions when running `*.wast` files. This support is all built on #4442 as functions are invoked through a "dynamic" API. Right now the testing and integration is fairly crude but I'm hoping that we can try to improve it over time as necessary. For now this should provide a hopefully more convenient syntax for unit tests and the like.
This commit is contained in:
34
Cargo.lock
generated
34
Cargo.lock
generated
@@ -3216,18 +3216,18 @@ checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-encoder"
|
name = "wasm-encoder"
|
||||||
version = "0.14.0"
|
version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f76068e87fe9b837a6bc2ccded66784173eadb828c4168643e9fddf6f9ed2e61"
|
checksum = "8905fd25fdadeb0e7e8bf43a9f46f9f972d6291ad0c7a32573b88dd13a6cfa6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"leb128",
|
"leb128",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-mutate"
|
name = "wasm-mutate"
|
||||||
version = "0.2.5"
|
version = "0.2.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fde0df58bcb700325907d1b024b58de418bd9c48abf5de6f802ad63d28c7d08d"
|
checksum = "80e6de18ed96f27d3942041e5ae02177aff18e4425196a3d4b1f14145d027f71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"egg",
|
"egg",
|
||||||
"log",
|
"log",
|
||||||
@@ -3239,9 +3239,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-smith"
|
name = "wasm-smith"
|
||||||
version = "0.11.2"
|
version = "0.11.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b73250e61e41d0e467b78559c7d761841005d724384bb0b78d52ff974acf5520"
|
checksum = "d54f72dd89c036847831ef4d3b8f7fd8618d87509422728f12b0937f96d6dd04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"flagset",
|
"flagset",
|
||||||
@@ -3286,18 +3286,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmparser"
|
name = "wasmparser"
|
||||||
version = "0.87.0"
|
version = "0.88.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5c04e207cd2e8ecb6f9bd28a2cf3119b4c6bfeee6fe3a25cc1daf8041d00a875"
|
checksum = "fb8cf7dd82407fe68161bedcd57fde15596f32ebf6e9b3bdbf3ae1da20e38e5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmprinter"
|
name = "wasmprinter"
|
||||||
version = "0.2.37"
|
version = "0.2.38"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "550bde1d5aec6aa1584c9f227ca2ab60621e002a4b15b8bee83f92c7c516db87"
|
checksum = "04f2786f19a25211ddfa331e28b7579a6d6880f5f4b18d21253cd90274aa4c21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"wasmparser",
|
"wasmparser",
|
||||||
@@ -3441,7 +3441,7 @@ dependencies = [
|
|||||||
"wasmtime-wasi-crypto",
|
"wasmtime-wasi-crypto",
|
||||||
"wasmtime-wasi-nn",
|
"wasmtime-wasi-nn",
|
||||||
"wasmtime-wast",
|
"wasmtime-wast",
|
||||||
"wast 44.0.0",
|
"wast 45.0.0",
|
||||||
"wat",
|
"wat",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
@@ -3681,7 +3681,7 @@ version = "0.40.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"wasmtime",
|
"wasmtime",
|
||||||
"wast 44.0.0",
|
"wast 45.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3695,9 +3695,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wast"
|
name = "wast"
|
||||||
version = "44.0.0"
|
version = "45.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f474d1b1cb7d92e5360b293f28e8bc9b2d115197a5bbf76bdbfba9161cf9cdc"
|
checksum = "186c474c4f9bb92756b566d592a16591b4526b1a4841171caa3f31d7fe330d96"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"leb128",
|
"leb128",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -3707,11 +3707,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wat"
|
name = "wat"
|
||||||
version = "1.0.46"
|
version = "1.0.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "82d002ce2eca0730c6df2c21719e9c4d8d0cafe74fb0cb8ff137c0774b8e4ed1"
|
checksum = "c2d4bc4724b4f02a482c8cab053dac5ef26410f264c06ce914958f9a42813556"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wast 44.0.0",
|
"wast 45.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ test-programs = { path = "crates/test-programs" }
|
|||||||
wasmtime-runtime = { path = "crates/runtime" }
|
wasmtime-runtime = { path = "crates/runtime" }
|
||||||
tokio = { version = "1.8.0", features = ["rt", "time", "macros", "rt-multi-thread"] }
|
tokio = { version = "1.8.0", features = ["rt", "time", "macros", "rt-multi-thread"] }
|
||||||
tracing-subscriber = "0.3.1"
|
tracing-subscriber = "0.3.1"
|
||||||
wast = "44.0.0"
|
wast = "45.0.0"
|
||||||
criterion = "0.3.4"
|
criterion = "0.3.4"
|
||||||
num_cpus = "1.13.0"
|
num_cpus = "1.13.0"
|
||||||
memchr = "2.4"
|
memchr = "2.4"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
wat = "1.0.46"
|
wat = "1.0.47"
|
||||||
once_cell = "1.9.0"
|
once_cell = "1.9.0"
|
||||||
rayon = "1.5.0"
|
rayon = "1.5.0"
|
||||||
component-macro-test = { path = "crates/misc/component-macro-test" }
|
component-macro-test = { path = "crates/misc/component-macro-test" }
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ filecheck = "0.5.0"
|
|||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
termcolor = "1.1.2"
|
termcolor = "1.1.2"
|
||||||
capstone = { version = "0.9.0", optional = true }
|
capstone = { version = "0.9.0", optional = true }
|
||||||
wat = { version = "1.0.45", optional = true }
|
wat = { version = "1.0.47", optional = true }
|
||||||
target-lexicon = { version = "0.12", features = ["std"] }
|
target-lexicon = { version = "0.12", features = ["std"] }
|
||||||
pretty_env_logger = "0.4.0"
|
pretty_env_logger = "0.4.0"
|
||||||
rayon = { version = "1", optional = true }
|
rayon = { version = "1", optional = true }
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ keywords = ["webassembly", "wasm"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasmparser = { version = "0.87.0", default-features = false }
|
wasmparser = { version = "0.88.0", default-features = false }
|
||||||
cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
|
cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
|
||||||
cranelift-entity = { path = "../entity", version = "0.87.0" }
|
cranelift-entity = { path = "../entity", version = "0.87.0" }
|
||||||
cranelift-frontend = { path = "../frontend", version = "0.87.0", default-features = false }
|
cranelift-frontend = { path = "../frontend", version = "0.87.0", default-features = false }
|
||||||
@@ -24,7 +24,7 @@ serde = { version = "1.0.94", features = ["derive"], optional = true }
|
|||||||
smallvec = "1.6.1"
|
smallvec = "1.6.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wat = "1.0.45"
|
wat = "1.0.47"
|
||||||
target-lexicon = "0.12"
|
target-lexicon = "0.12"
|
||||||
cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
|
cranelift-codegen = { path = "../codegen", version = "0.87.0", default-features = false }
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ wasmtime = { path = "../wasmtime", default-features = false, features = ['cranel
|
|||||||
wasmtime-c-api-macros = { path = "macros" }
|
wasmtime-c-api-macros = { path = "macros" }
|
||||||
|
|
||||||
# Optional dependency for the `wat2wasm` API
|
# Optional dependency for the `wat2wasm` API
|
||||||
wat = { version = "1.0.45", optional = true }
|
wat = { version = "1.0.47", optional = true }
|
||||||
|
|
||||||
# Optional dependencies for the `wasi` feature
|
# Optional dependencies for the `wasi` feature
|
||||||
wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", optional = true }
|
wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", optional = true }
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ cranelift-codegen = { path = "../../cranelift/codegen", version = "0.87.0" }
|
|||||||
cranelift-frontend = { path = "../../cranelift/frontend", version = "0.87.0" }
|
cranelift-frontend = { path = "../../cranelift/frontend", version = "0.87.0" }
|
||||||
cranelift-entity = { path = "../../cranelift/entity", version = "0.87.0" }
|
cranelift-entity = { path = "../../cranelift/entity", version = "0.87.0" }
|
||||||
cranelift-native = { path = "../../cranelift/native", version = "0.87.0" }
|
cranelift-native = { path = "../../cranelift/native", version = "0.87.0" }
|
||||||
wasmparser = "0.87.0"
|
wasmparser = "0.88.0"
|
||||||
target-lexicon = "0.12"
|
target-lexicon = "0.12"
|
||||||
gimli = { version = "0.26.0", default-features = false, features = ['read', 'std'] }
|
gimli = { version = "0.26.0", default-features = false, features = ['read', 'std'] }
|
||||||
object = { version = "0.29.0", default-features = false, features = ['write'] }
|
object = { version = "0.29.0", default-features = false, features = ['write'] }
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ edition = "2021"
|
|||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
cranelift-entity = { path = "../../cranelift/entity", version = "0.87.0" }
|
cranelift-entity = { path = "../../cranelift/entity", version = "0.87.0" }
|
||||||
wasmtime-types = { path = "../types", version = "0.40.0" }
|
wasmtime-types = { path = "../types", version = "0.40.0" }
|
||||||
wasmparser = "0.87.0"
|
wasmparser = "0.88.0"
|
||||||
indexmap = { version = "1.0.2", features = ["serde-1"] }
|
indexmap = { version = "1.0.2", features = ["serde-1"] }
|
||||||
thiserror = "1.0.4"
|
thiserror = "1.0.4"
|
||||||
serde = { version = "1.0.94", features = ["derive"] }
|
serde = { version = "1.0.94", features = ["derive"] }
|
||||||
@@ -22,8 +22,8 @@ log = { version = "0.4.8", default-features = false }
|
|||||||
gimli = { version = "0.26.0", default-features = false, features = ['read'] }
|
gimli = { version = "0.26.0", default-features = false, features = ['read'] }
|
||||||
object = { version = "0.29.0", default-features = false, features = ['read_core', 'write_core', 'elf'] }
|
object = { version = "0.29.0", default-features = false, features = ['read_core', 'write_core', 'elf'] }
|
||||||
target-lexicon = "0.12"
|
target-lexicon = "0.12"
|
||||||
wasm-encoder = { version = "0.14.0", optional = true }
|
wasm-encoder = { version = "0.15.0", optional = true }
|
||||||
wasmprinter = { version = "0.2.37", optional = true }
|
wasmprinter = { version = "0.2.38", optional = true }
|
||||||
wasmtime-component-util = { path = "../component-util", version = "=0.40.0", optional = true }
|
wasmtime-component-util = { path = "../component-util", version = "=0.40.0", optional = true }
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ cargo-fuzz = true
|
|||||||
arbitrary = { version = "1.1.0", features = ["derive"] }
|
arbitrary = { version = "1.1.0", features = ["derive"] }
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
libfuzzer-sys = "0.4"
|
libfuzzer-sys = "0.4"
|
||||||
wasmparser = "0.87.0"
|
wasmparser = "0.88.0"
|
||||||
wasmprinter = "0.2.37"
|
wasmprinter = "0.2.37"
|
||||||
wasmtime-environ = { path = ".." }
|
wasmtime-environ = { path = ".." }
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ log = "0.4.8"
|
|||||||
rayon = "1.2.1"
|
rayon = "1.2.1"
|
||||||
target-lexicon = "0.12.3"
|
target-lexicon = "0.12.3"
|
||||||
tempfile = "3.3.0"
|
tempfile = "3.3.0"
|
||||||
wasmparser = "0.87.0"
|
wasmparser = "0.88.0"
|
||||||
wasmprinter = "0.2.37"
|
wasmprinter = "0.2.38"
|
||||||
wasmtime = { path = "../wasmtime" }
|
wasmtime = { path = "../wasmtime" }
|
||||||
wasmtime-wast = { path = "../wast" }
|
wasmtime-wast = { path = "../wast" }
|
||||||
wasm-encoder = "0.14.0"
|
wasm-encoder = "0.15.0"
|
||||||
wasm-smith = "0.11.2"
|
wasm-smith = "0.11.3"
|
||||||
wasm-mutate = "0.2.5"
|
wasm-mutate = "0.2.6"
|
||||||
wasm-spec-interpreter = { path = "./wasm-spec-interpreter", optional = true }
|
wasm-spec-interpreter = { path = "./wasm-spec-interpreter", optional = true }
|
||||||
wasmi = "0.11.0"
|
wasmi = "0.11.0"
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ wasmi = "0.11.0"
|
|||||||
v8 = "0.44.3"
|
v8 = "0.44.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wat = "1.0.45"
|
wat = "1.0.47"
|
||||||
rand = { version = "0.8.0", features = ["small_rng"] }
|
rand = { version = "0.8.0", features = ["small_rng"] }
|
||||||
|
|
||||||
# Only enable the `build-libinterpret` feature when fuzzing is enabled, enabling
|
# Only enable the `build-libinterpret` feature when fuzzing is enabled, enabling
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
use arbitrary::{Arbitrary, Result, Unstructured};
|
use arbitrary::{Arbitrary, Result, Unstructured};
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
use wasm_encoder::{
|
use wasm_encoder::{
|
||||||
CodeSection, EntityType, ExportKind, ExportSection, Function, FunctionSection, GlobalSection,
|
CodeSection, ConstExpr, EntityType, ExportKind, ExportSection, Function, FunctionSection,
|
||||||
ImportSection, Instruction, Module, TableSection, TableType, TypeSection, ValType,
|
GlobalSection, ImportSection, Instruction, Module, TableSection, TableType, TypeSection,
|
||||||
|
ValType,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A description of a Wasm module that makes a series of `externref` table
|
/// A description of a Wasm module that makes a series of `externref` table
|
||||||
@@ -94,7 +95,7 @@ impl TableOps {
|
|||||||
val_type: wasm_encoder::ValType::ExternRef,
|
val_type: wasm_encoder::ValType::ExternRef,
|
||||||
mutable: true,
|
mutable: true,
|
||||||
},
|
},
|
||||||
&Instruction::RefNull(wasm_encoder::ValType::ExternRef),
|
&ConstExpr::ref_null(wasm_encoder::ValType::ExternRef),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ ocaml-interop = { version = "0.8", optional = true }
|
|||||||
once_cell = { version = "1.12.0", optional = true }
|
once_cell = { version = "1.12.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wat = "1.0.45"
|
wat = "1.0.47"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
build-libinterpret = ["ocaml-interop", "once_cell"]
|
build-libinterpret = ["ocaml-interop", "once_cell"]
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pretty_env_logger = "0.4.0"
|
|||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
os_pipe = "0.9"
|
os_pipe = "0.9"
|
||||||
anyhow = "1.0.19"
|
anyhow = "1.0.19"
|
||||||
wat = "1.0.45"
|
wat = "1.0.47"
|
||||||
cap-std = "0.25.0"
|
cap-std = "0.25.0"
|
||||||
tokio = { version = "1.8.0", features = ["rt-multi-thread"] }
|
tokio = { version = "1.8.0", features = ["rt-multi-thread"] }
|
||||||
|
|
||||||
|
|||||||
@@ -12,4 +12,4 @@ edition = "2021"
|
|||||||
cranelift-entity = { path = "../../cranelift/entity", version = "0.87.0", features = ['enable-serde'] }
|
cranelift-entity = { path = "../../cranelift/entity", version = "0.87.0", features = ['enable-serde'] }
|
||||||
serde = { version = "1.0.94", features = ["derive"] }
|
serde = { version = "1.0.94", features = ["derive"] }
|
||||||
thiserror = "1.0.4"
|
thiserror = "1.0.4"
|
||||||
wasmparser = { version = "0.87.0", default-features = false }
|
wasmparser = { version = "0.88.0", default-features = false }
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ wasmtime-cranelift = { path = "../cranelift", version = "=0.40.0", optional = tr
|
|||||||
wasmtime-component-macro = { path = "../component-macro", version = "=0.40.0", optional = true }
|
wasmtime-component-macro = { path = "../component-macro", version = "=0.40.0", optional = true }
|
||||||
wasmtime-component-util = { path = "../component-util", version = "=0.40.0", optional = true }
|
wasmtime-component-util = { path = "../component-util", version = "=0.40.0", optional = true }
|
||||||
target-lexicon = { version = "0.12.0", default-features = false }
|
target-lexicon = { version = "0.12.0", default-features = false }
|
||||||
wasmparser = "0.87.0"
|
wasmparser = "0.88.0"
|
||||||
anyhow = "1.0.19"
|
anyhow = "1.0.19"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
cfg-if = "1.0"
|
cfg-if = "1.0"
|
||||||
backtrace = { version = "0.3.61" }
|
backtrace = { version = "0.3.61" }
|
||||||
log = "0.4.8"
|
log = "0.4.8"
|
||||||
wat = { version = "1.0.45", optional = true }
|
wat = { version = "1.0.47", optional = true }
|
||||||
serde = { version = "1.0.94", features = ["derive"] }
|
serde = { version = "1.0.94", features = ["derive"] }
|
||||||
bincode = "1.2.1"
|
bincode = "1.2.1"
|
||||||
indexmap = "1.6"
|
indexmap = "1.6"
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ impl Record {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieve the fields of this `record` in declaration order.
|
/// Retrieve the fields of this `record` in declaration order.
|
||||||
pub fn fields(&self) -> impl ExactSizeIterator<Item = Field> {
|
pub fn fields(&self) -> impl ExactSizeIterator<Item = Field<'_>> {
|
||||||
self.0.types[self.0.index].fields.iter().map(|field| Field {
|
self.0.types[self.0.index].fields.iter().map(|field| Field {
|
||||||
name: &field.name,
|
name: &field.name,
|
||||||
ty: Type::from(&field.ty, &self.0.types),
|
ty: Type::from(&field.ty, &self.0.types),
|
||||||
|
|||||||
@@ -30,6 +30,19 @@ impl List {
|
|||||||
values,
|
values,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the corresponding type of this list
|
||||||
|
pub fn ty(&self) -> &types::List {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for List {
|
||||||
|
type Target = [Val];
|
||||||
|
|
||||||
|
fn deref(&self) -> &[Val] {
|
||||||
|
&self.values
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@@ -76,6 +89,20 @@ impl Record {
|
|||||||
values: values.into(),
|
values: values.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the corresponding type of this record.
|
||||||
|
pub fn ty(&self) -> &types::Record {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of the specified field `name` from this record.
|
||||||
|
pub fn fields(&self) -> impl Iterator<Item = (&str, &Val)> {
|
||||||
|
assert_eq!(self.values.len(), self.ty.fields().len());
|
||||||
|
self.ty
|
||||||
|
.fields()
|
||||||
|
.zip(self.values.iter())
|
||||||
|
.map(|(ty, val)| (ty.name, val))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@@ -105,6 +132,16 @@ impl Tuple {
|
|||||||
values,
|
values,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this tuple.
|
||||||
|
pub fn ty(&self) -> &types::Tuple {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the list of values that this tuple contains.
|
||||||
|
pub fn values(&self) -> &[Val] {
|
||||||
|
&self.values
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@@ -139,6 +176,25 @@ impl Variant {
|
|||||||
value: Box::new(value),
|
value: Box::new(value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this variant.
|
||||||
|
pub fn ty(&self) -> &types::Variant {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns name of the discriminant of this value within the variant type.
|
||||||
|
pub fn discriminant(&self) -> &str {
|
||||||
|
self.ty
|
||||||
|
.cases()
|
||||||
|
.nth(self.discriminant as usize)
|
||||||
|
.unwrap()
|
||||||
|
.name
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the payload value for this variant.
|
||||||
|
pub fn payload(&self) -> &Val {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@@ -161,6 +217,16 @@ impl Enum {
|
|||||||
discriminant,
|
discriminant,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this value.
|
||||||
|
pub fn ty(&self) -> &types::Enum {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns name of this enum value.
|
||||||
|
pub fn discriminant(&self) -> &str {
|
||||||
|
self.ty.names().nth(self.discriminant as usize).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@@ -190,6 +256,21 @@ impl Union {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this value.
|
||||||
|
pub fn ty(&self) -> &types::Union {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns name of the discriminant of this value within the union type.
|
||||||
|
pub fn discriminant(&self) -> u32 {
|
||||||
|
self.discriminant
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the payload value for this union.
|
||||||
|
pub fn payload(&self) -> &Val {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@@ -216,6 +297,20 @@ impl Option {
|
|||||||
value: Box::new(value.unwrap_or(Val::Unit)),
|
value: Box::new(value.unwrap_or(Val::Unit)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this value.
|
||||||
|
pub fn ty(&self) -> &types::Option {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the optional value contained within.
|
||||||
|
pub fn value(&self) -> std::option::Option<&Val> {
|
||||||
|
if self.discriminant == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@@ -247,6 +342,20 @@ impl Expected {
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this value.
|
||||||
|
pub fn ty(&self) -> &types::Expected {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the result value contained within.
|
||||||
|
pub fn value(&self) -> Result<&Val, &Val> {
|
||||||
|
if self.discriminant == 0 {
|
||||||
|
Ok(&self.value)
|
||||||
|
} else {
|
||||||
|
Err(&self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
@@ -280,6 +389,23 @@ impl Flags {
|
|||||||
value: values.into(),
|
value: values.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the type of this value.
|
||||||
|
pub fn ty(&self) -> &types::Flags {
|
||||||
|
&self.ty
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the set of names that this flags set contains.
|
||||||
|
pub fn flags(&self) -> impl Iterator<Item = &str> {
|
||||||
|
(0..self.count).filter_map(|i| {
|
||||||
|
let (idx, bit) = ((i / 32) as usize, i % 32);
|
||||||
|
if self.value[idx] & (1 << bit) != 0 {
|
||||||
|
Some(self.ty.names().nth(i as usize).unwrap())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents possible runtime values which a component function can either consume or produce
|
/// Represents possible runtime values which a component function can either consume or produce
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.19"
|
anyhow = "1.0.19"
|
||||||
wasmtime = { path = "../wasmtime", version = "0.40.0", default-features = false, features = ['cranelift'] }
|
wasmtime = { path = "../wasmtime", version = "0.40.0", default-features = false, features = ['cranelift'] }
|
||||||
wast = "44.0.0"
|
wast = "45.0.0"
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
maintenance = { status = "actively-developed" }
|
maintenance = { status = "actively-developed" }
|
||||||
|
|||||||
364
crates/wast/src/component.rs
Normal file
364
crates/wast/src/component.rs
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
use crate::core;
|
||||||
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
use std::collections::{BTreeSet, HashMap};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use wast::component::WastVal;
|
||||||
|
use wast::core::NanPattern;
|
||||||
|
|
||||||
|
pub use wasmtime::component::*;
|
||||||
|
|
||||||
|
pub fn val(v: &WastVal<'_>, ty: &Type) -> Result<Val> {
|
||||||
|
Ok(match v {
|
||||||
|
WastVal::Unit => Val::Unit,
|
||||||
|
WastVal::Bool(b) => Val::Bool(*b),
|
||||||
|
WastVal::U8(b) => Val::U8(*b),
|
||||||
|
WastVal::S8(b) => Val::S8(*b),
|
||||||
|
WastVal::U16(b) => Val::U16(*b),
|
||||||
|
WastVal::S16(b) => Val::S16(*b),
|
||||||
|
WastVal::U32(b) => Val::U32(*b),
|
||||||
|
WastVal::S32(b) => Val::S32(*b),
|
||||||
|
WastVal::U64(b) => Val::U64(*b),
|
||||||
|
WastVal::S64(b) => Val::S64(*b),
|
||||||
|
WastVal::Float32(b) => Val::Float32(b.bits),
|
||||||
|
WastVal::Float64(b) => Val::Float64(b.bits),
|
||||||
|
WastVal::Char(b) => Val::Char(*b),
|
||||||
|
WastVal::String(s) => Val::String(s.to_string().into()),
|
||||||
|
WastVal::List(vals) => match ty {
|
||||||
|
Type::List(t) => {
|
||||||
|
let element = t.ty();
|
||||||
|
let vals = vals
|
||||||
|
.iter()
|
||||||
|
.map(|v| val(v, &element))
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
t.new_val(vals.into())?
|
||||||
|
}
|
||||||
|
_ => bail!("expected a list value"),
|
||||||
|
},
|
||||||
|
WastVal::Record(fields) => match ty {
|
||||||
|
Type::Record(t) => {
|
||||||
|
let mut fields_by_name = HashMap::new();
|
||||||
|
for (name, val) in fields {
|
||||||
|
let prev = fields_by_name.insert(*name, val);
|
||||||
|
if prev.is_some() {
|
||||||
|
bail!("field `{name}` specified twice");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut values = Vec::new();
|
||||||
|
for field in t.fields() {
|
||||||
|
let name = field.name;
|
||||||
|
let v = fields_by_name
|
||||||
|
.remove(name)
|
||||||
|
.ok_or_else(|| anyhow!("field `{name}` not specified"))?;
|
||||||
|
values.push((name, val(v, &field.ty)?));
|
||||||
|
}
|
||||||
|
if let Some((field, _)) = fields_by_name.iter().next() {
|
||||||
|
bail!("extra field `{field}` specified");
|
||||||
|
}
|
||||||
|
t.new_val(values)?
|
||||||
|
}
|
||||||
|
_ => bail!("expected a record value"),
|
||||||
|
},
|
||||||
|
WastVal::Tuple(vals) => match ty {
|
||||||
|
Type::Tuple(t) => {
|
||||||
|
if vals.len() != t.types().len() {
|
||||||
|
bail!("expected {} values got {}", t.types().len(), vals.len());
|
||||||
|
}
|
||||||
|
t.new_val(
|
||||||
|
vals.iter()
|
||||||
|
.zip(t.types())
|
||||||
|
.map(|(v, ty)| val(v, &ty))
|
||||||
|
.collect::<Result<Vec<_>>>()?
|
||||||
|
.into(),
|
||||||
|
)?
|
||||||
|
}
|
||||||
|
_ => bail!("expected a tuple value"),
|
||||||
|
},
|
||||||
|
WastVal::Enum(name) => match ty {
|
||||||
|
Type::Enum(t) => t.new_val(name)?,
|
||||||
|
_ => bail!("expected an enum value"),
|
||||||
|
},
|
||||||
|
WastVal::Variant(name, payload) => match ty {
|
||||||
|
Type::Variant(t) => {
|
||||||
|
let case = match t.cases().find(|c| c.name == *name) {
|
||||||
|
Some(case) => case.ty,
|
||||||
|
None => bail!("no case named `{}", name),
|
||||||
|
};
|
||||||
|
let payload = val(payload, &case)?;
|
||||||
|
t.new_val(name, payload)?
|
||||||
|
}
|
||||||
|
_ => bail!("expected a variant value"),
|
||||||
|
},
|
||||||
|
WastVal::Union(idx, payload) => match ty {
|
||||||
|
Type::Union(t) => {
|
||||||
|
let case = match t.types().nth(*idx as usize) {
|
||||||
|
Some(case) => case,
|
||||||
|
None => bail!("case {idx} too large"),
|
||||||
|
};
|
||||||
|
let payload = val(payload, &case)?;
|
||||||
|
t.new_val(*idx, payload)?
|
||||||
|
}
|
||||||
|
_ => bail!("expected a union value"),
|
||||||
|
},
|
||||||
|
WastVal::Option(v) => match ty {
|
||||||
|
Type::Option(t) => {
|
||||||
|
let v = match v {
|
||||||
|
Some(v) => Some(val(v, &t.ty())?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
t.new_val(v)?
|
||||||
|
}
|
||||||
|
_ => bail!("expected an option value"),
|
||||||
|
},
|
||||||
|
WastVal::Expected(v) => match ty {
|
||||||
|
Type::Expected(t) => {
|
||||||
|
let v = match v {
|
||||||
|
Ok(v) => Ok(val(v, &t.ok())?),
|
||||||
|
Err(v) => Err(val(v, &t.err())?),
|
||||||
|
};
|
||||||
|
t.new_val(v)?
|
||||||
|
}
|
||||||
|
_ => bail!("expected an expected value"),
|
||||||
|
},
|
||||||
|
WastVal::Flags(v) => match ty {
|
||||||
|
Type::Flags(t) => t.new_val(v)?,
|
||||||
|
_ => bail!("expected a flags value"),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_val(expected: &WastVal<'_>, actual: &Val) -> Result<()> {
|
||||||
|
match expected {
|
||||||
|
WastVal::Unit => match actual {
|
||||||
|
Val::Unit => Ok(()),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Bool(e) => match actual {
|
||||||
|
Val::Bool(a) => match_debug(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::U8(e) => match actual {
|
||||||
|
Val::U8(a) => core::match_int(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::S8(e) => match actual {
|
||||||
|
Val::S8(a) => core::match_int(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::U16(e) => match actual {
|
||||||
|
Val::U16(a) => core::match_int(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::S16(e) => match actual {
|
||||||
|
Val::S16(a) => core::match_int(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::U32(e) => match actual {
|
||||||
|
Val::U32(a) => core::match_int(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::S32(e) => match actual {
|
||||||
|
Val::S32(a) => core::match_int(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::U64(e) => match actual {
|
||||||
|
Val::U64(a) => core::match_int(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::S64(e) => match actual {
|
||||||
|
Val::S64(a) => core::match_int(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Float32(e) => match actual {
|
||||||
|
Val::Float32(a) => core::match_f32(*a, &NanPattern::Value(*e)),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Float64(e) => match actual {
|
||||||
|
Val::Float64(a) => core::match_f64(*a, &NanPattern::Value(*e)),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Char(e) => match actual {
|
||||||
|
Val::Char(a) => match_debug(a, e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::String(e) => match actual {
|
||||||
|
Val::String(a) => match_debug(&a[..], *e),
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::List(e) => match actual {
|
||||||
|
Val::List(a) => {
|
||||||
|
if e.len() != a.len() {
|
||||||
|
bail!("expected {} values got {}", e.len(), a.len());
|
||||||
|
}
|
||||||
|
for (i, (expected, actual)) in e.iter().zip(a.iter()).enumerate() {
|
||||||
|
match_val(expected, actual)
|
||||||
|
.with_context(|| format!("failed to match list element {i}"))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Record(e) => match actual {
|
||||||
|
Val::Record(a) => {
|
||||||
|
let mut fields_by_name = HashMap::new();
|
||||||
|
for (name, val) in e {
|
||||||
|
let prev = fields_by_name.insert(*name, val);
|
||||||
|
if prev.is_some() {
|
||||||
|
bail!("field `{name}` specified twice");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (name, actual) in a.fields() {
|
||||||
|
let expected = fields_by_name
|
||||||
|
.remove(name)
|
||||||
|
.ok_or_else(|| anyhow!("field `{name}` not specified"))?;
|
||||||
|
match_val(expected, actual)
|
||||||
|
.with_context(|| format!("failed to match field `{name}`"))?;
|
||||||
|
}
|
||||||
|
if let Some((field, _)) = fields_by_name.iter().next() {
|
||||||
|
bail!("extra field `{field}` specified");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Tuple(e) => match actual {
|
||||||
|
Val::Tuple(a) => {
|
||||||
|
if e.len() != a.values().len() {
|
||||||
|
bail!(
|
||||||
|
"expected {}-tuple, found {}-tuple",
|
||||||
|
e.len(),
|
||||||
|
a.values().len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (i, (expected, actual)) in e.iter().zip(a.values()).enumerate() {
|
||||||
|
match_val(expected, actual)
|
||||||
|
.with_context(|| format!("failed to match tuple element {i}"))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Variant(name, e) => match actual {
|
||||||
|
Val::Variant(a) => {
|
||||||
|
if a.discriminant() != *name {
|
||||||
|
bail!("expected discriminant `{name}` got `{}`", a.discriminant());
|
||||||
|
}
|
||||||
|
match_val(e, a.payload())
|
||||||
|
}
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Enum(name) => match actual {
|
||||||
|
Val::Enum(a) => {
|
||||||
|
if a.discriminant() != *name {
|
||||||
|
bail!("expected discriminant `{name}` got `{}`", a.discriminant());
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Union(idx, e) => match actual {
|
||||||
|
Val::Union(a) => {
|
||||||
|
if a.discriminant() != *idx {
|
||||||
|
bail!("expected discriminant `{idx}` got `{}`", a.discriminant());
|
||||||
|
}
|
||||||
|
match_val(e, a.payload())
|
||||||
|
}
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Option(e) => match actual {
|
||||||
|
Val::Option(a) => match (e, a.value()) {
|
||||||
|
(None, None) => Ok(()),
|
||||||
|
(Some(expected), Some(actual)) => match_val(expected, actual),
|
||||||
|
(None, Some(_)) => bail!("expected `none`, found `some`"),
|
||||||
|
(Some(_), None) => bail!("expected `some`, found `none`"),
|
||||||
|
},
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Expected(e) => match actual {
|
||||||
|
Val::Expected(a) => match (e, a.value()) {
|
||||||
|
(Ok(_), Err(_)) => bail!("expected `ok`, found `err`"),
|
||||||
|
(Err(_), Ok(_)) => bail!("expected `err`, found `ok`"),
|
||||||
|
(Err(e), Err(a)) => match_val(e, a),
|
||||||
|
(Ok(e), Ok(a)) => match_val(e, a),
|
||||||
|
},
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
WastVal::Flags(e) => match actual {
|
||||||
|
Val::Flags(a) => {
|
||||||
|
let expected = e.iter().copied().collect::<BTreeSet<_>>();
|
||||||
|
let actual = a.flags().collect::<BTreeSet<_>>();
|
||||||
|
match_debug(&actual, &expected)
|
||||||
|
}
|
||||||
|
_ => mismatch(expected, actual),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_debug<T>(actual: &T, expected: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: Eq + Debug + ?Sized,
|
||||||
|
{
|
||||||
|
if actual == expected {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"
|
||||||
|
expected {expected:?}
|
||||||
|
actual {actual:?}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mismatch(expected: &WastVal<'_>, actual: &Val) -> Result<()> {
|
||||||
|
let expected = match expected {
|
||||||
|
WastVal::Unit => "unit",
|
||||||
|
WastVal::Bool(..) => "bool",
|
||||||
|
WastVal::U8(..) => "u8",
|
||||||
|
WastVal::S8(..) => "s8",
|
||||||
|
WastVal::U16(..) => "u16",
|
||||||
|
WastVal::S16(..) => "s16",
|
||||||
|
WastVal::U32(..) => "u32",
|
||||||
|
WastVal::S32(..) => "s32",
|
||||||
|
WastVal::U64(..) => "u64",
|
||||||
|
WastVal::S64(..) => "s64",
|
||||||
|
WastVal::Float32(..) => "float32",
|
||||||
|
WastVal::Float64(..) => "float64",
|
||||||
|
WastVal::Char(..) => "char",
|
||||||
|
WastVal::String(..) => "string",
|
||||||
|
WastVal::List(..) => "list",
|
||||||
|
WastVal::Record(..) => "record",
|
||||||
|
WastVal::Tuple(..) => "tuple",
|
||||||
|
WastVal::Enum(..) => "enum",
|
||||||
|
WastVal::Variant(..) => "variant",
|
||||||
|
WastVal::Union(..) => "union",
|
||||||
|
WastVal::Option(..) => "option",
|
||||||
|
WastVal::Expected(..) => "expected",
|
||||||
|
WastVal::Flags(..) => "flags",
|
||||||
|
};
|
||||||
|
let actual = match actual {
|
||||||
|
Val::Unit => "unit",
|
||||||
|
Val::Bool(..) => "bool",
|
||||||
|
Val::U8(..) => "u8",
|
||||||
|
Val::S8(..) => "s8",
|
||||||
|
Val::U16(..) => "u16",
|
||||||
|
Val::S16(..) => "s16",
|
||||||
|
Val::U32(..) => "u32",
|
||||||
|
Val::S32(..) => "s32",
|
||||||
|
Val::U64(..) => "u64",
|
||||||
|
Val::S64(..) => "s64",
|
||||||
|
Val::Float32(..) => "float32",
|
||||||
|
Val::Float64(..) => "float64",
|
||||||
|
Val::Char(..) => "char",
|
||||||
|
Val::String(..) => "string",
|
||||||
|
Val::List(..) => "list",
|
||||||
|
Val::Record(..) => "record",
|
||||||
|
Val::Tuple(..) => "tuple",
|
||||||
|
Val::Enum(..) => "enum",
|
||||||
|
Val::Variant(..) => "variant",
|
||||||
|
Val::Union(..) => "union",
|
||||||
|
Val::Option(..) => "option",
|
||||||
|
Val::Expected(..) => "expected",
|
||||||
|
Val::Flags(..) => "flags",
|
||||||
|
};
|
||||||
|
bail!("expected `{expected}` got `{actual}`")
|
||||||
|
}
|
||||||
346
crates/wast/src/core.rs
Normal file
346
crates/wast/src/core.rs
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use std::fmt::{Display, LowerHex};
|
||||||
|
use wasmtime::{ExternRef, Val};
|
||||||
|
use wast::core::{HeapType, NanPattern, V128Pattern, WastArgCore, WastRetCore};
|
||||||
|
use wast::token::{Float32, Float64};
|
||||||
|
|
||||||
|
/// Translate from a `script::Value` to a `RuntimeValue`.
|
||||||
|
pub fn val(v: &WastArgCore<'_>) -> Result<Val> {
|
||||||
|
use wast::core::WastArgCore::*;
|
||||||
|
|
||||||
|
Ok(match v {
|
||||||
|
I32(x) => Val::I32(*x),
|
||||||
|
I64(x) => Val::I64(*x),
|
||||||
|
F32(x) => Val::F32(x.bits),
|
||||||
|
F64(x) => Val::F64(x.bits),
|
||||||
|
V128(x) => Val::V128(u128::from_le_bytes(x.to_le_bytes())),
|
||||||
|
RefNull(HeapType::Extern) => Val::ExternRef(None),
|
||||||
|
RefNull(HeapType::Func) => Val::FuncRef(None),
|
||||||
|
RefExtern(x) => Val::ExternRef(Some(ExternRef::new(*x))),
|
||||||
|
other => bail!("couldn't convert {:?} to a runtime value", other),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_lane_as_i8(bytes: u128, lane: usize) -> i8 {
|
||||||
|
(bytes >> (lane * 8)) as i8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_lane_as_i16(bytes: u128, lane: usize) -> i16 {
|
||||||
|
(bytes >> (lane * 16)) as i16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_lane_as_i32(bytes: u128, lane: usize) -> i32 {
|
||||||
|
(bytes >> (lane * 32)) as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_lane_as_i64(bytes: u128, lane: usize) -> i64 {
|
||||||
|
(bytes >> (lane * 64)) as i64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_val(actual: &Val, expected: &WastRetCore) -> Result<()> {
|
||||||
|
match (actual, expected) {
|
||||||
|
(Val::I32(a), WastRetCore::I32(b)) => match_int(a, b),
|
||||||
|
(Val::I64(a), WastRetCore::I64(b)) => match_int(a, b),
|
||||||
|
// Note that these float comparisons are comparing bits, not float
|
||||||
|
// values, so we're testing for bit-for-bit equivalence
|
||||||
|
(Val::F32(a), WastRetCore::F32(b)) => match_f32(*a, b),
|
||||||
|
(Val::F64(a), WastRetCore::F64(b)) => match_f64(*a, b),
|
||||||
|
(Val::V128(a), WastRetCore::V128(b)) => match_v128(*a, b),
|
||||||
|
(Val::ExternRef(x), WastRetCore::RefNull(Some(HeapType::Extern))) => {
|
||||||
|
if let Some(x) = x {
|
||||||
|
let x = x
|
||||||
|
.data()
|
||||||
|
.downcast_ref::<u32>()
|
||||||
|
.expect("only u32 externrefs created in wast test suites");
|
||||||
|
bail!("expected null externref, found {}", x);
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Val::ExternRef(x), WastRetCore::RefExtern(y)) => {
|
||||||
|
if let Some(x) = x {
|
||||||
|
let x = x
|
||||||
|
.data()
|
||||||
|
.downcast_ref::<u32>()
|
||||||
|
.expect("only u32 externrefs created in wast test suites");
|
||||||
|
if x == y {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!("expected {} found {}", y, x);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("expected non-null externref, found null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Val::FuncRef(x), WastRetCore::RefNull(_)) => {
|
||||||
|
if x.is_none() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!("expected null funcref, found non-null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => bail!(
|
||||||
|
"don't know how to compare {:?} and {:?} yet",
|
||||||
|
actual,
|
||||||
|
expected
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_int<T>(actual: &T, expected: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
T: Eq + Display + LowerHex,
|
||||||
|
{
|
||||||
|
if actual == expected {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"expected {:18} / {0:#018x}\n\
|
||||||
|
actual {:18} / {1:#018x}",
|
||||||
|
expected,
|
||||||
|
actual
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_f32(actual: u32, expected: &NanPattern<Float32>) -> Result<()> {
|
||||||
|
match expected {
|
||||||
|
// Check if an f32 (as u32 bits to avoid possible quieting when moving values in registers, e.g.
|
||||||
|
// https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
|
||||||
|
// is a canonical NaN:
|
||||||
|
// - the sign bit is unspecified,
|
||||||
|
// - the 8-bit exponent is set to all 1s
|
||||||
|
// - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
|
||||||
|
// See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
||||||
|
NanPattern::CanonicalNan => {
|
||||||
|
let canon_nan = 0x7fc0_0000;
|
||||||
|
if (actual & 0x7fff_ffff) == canon_nan {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"expected {:10} / {:#010x}\n\
|
||||||
|
actual {:10} / {:#010x}",
|
||||||
|
"canon-nan",
|
||||||
|
canon_nan,
|
||||||
|
f32::from_bits(actual),
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an f32 (as u32, see comments above) is an arithmetic NaN.
|
||||||
|
// This is the same as a canonical NaN including that the payload MSB is
|
||||||
|
// set to 1, but one or more of the remaining payload bits MAY BE set to
|
||||||
|
// 1 (a canonical NaN specifies all 0s). See
|
||||||
|
// https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
||||||
|
NanPattern::ArithmeticNan => {
|
||||||
|
const AF32_NAN: u32 = 0x7f80_0000;
|
||||||
|
let is_nan = actual & AF32_NAN == AF32_NAN;
|
||||||
|
const AF32_PAYLOAD_MSB: u32 = 0x0040_0000;
|
||||||
|
let is_msb_set = actual & AF32_PAYLOAD_MSB == AF32_PAYLOAD_MSB;
|
||||||
|
if is_nan && is_msb_set {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"expected {:>10} / {:>10}\n\
|
||||||
|
actual {:10} / {:#010x}",
|
||||||
|
"arith-nan",
|
||||||
|
"0x7fc*****",
|
||||||
|
f32::from_bits(actual),
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NanPattern::Value(expected_value) => {
|
||||||
|
if actual == expected_value.bits {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"expected {:10} / {:#010x}\n\
|
||||||
|
actual {:10} / {:#010x}",
|
||||||
|
f32::from_bits(expected_value.bits),
|
||||||
|
expected_value.bits,
|
||||||
|
f32::from_bits(actual),
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_f64(actual: u64, expected: &NanPattern<Float64>) -> Result<()> {
|
||||||
|
match expected {
|
||||||
|
// Check if an f64 (as u64 bits to avoid possible quieting when moving values in registers, e.g.
|
||||||
|
// https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
|
||||||
|
// is a canonical NaN:
|
||||||
|
// - the sign bit is unspecified,
|
||||||
|
// - the 11-bit exponent is set to all 1s
|
||||||
|
// - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
|
||||||
|
// See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
||||||
|
NanPattern::CanonicalNan => {
|
||||||
|
let canon_nan = 0x7ff8_0000_0000_0000;
|
||||||
|
if (actual & 0x7fff_ffff_ffff_ffff) == canon_nan {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"expected {:18} / {:#018x}\n\
|
||||||
|
actual {:18} / {:#018x}",
|
||||||
|
"canon-nan",
|
||||||
|
canon_nan,
|
||||||
|
f64::from_bits(actual),
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if an f64 (as u64, see comments above) is an arithmetic NaN. This is the same as a
|
||||||
|
// canonical NaN including that the payload MSB is set to 1, but one or more of the remaining
|
||||||
|
// payload bits MAY BE set to 1 (a canonical NaN specifies all 0s). See
|
||||||
|
// https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
||||||
|
NanPattern::ArithmeticNan => {
|
||||||
|
const AF64_NAN: u64 = 0x7ff0_0000_0000_0000;
|
||||||
|
let is_nan = actual & AF64_NAN == AF64_NAN;
|
||||||
|
const AF64_PAYLOAD_MSB: u64 = 0x0008_0000_0000_0000;
|
||||||
|
let is_msb_set = actual & AF64_PAYLOAD_MSB == AF64_PAYLOAD_MSB;
|
||||||
|
if is_nan && is_msb_set {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"expected {:>18} / {:>18}\n\
|
||||||
|
actual {:18} / {:#018x}",
|
||||||
|
"arith-nan",
|
||||||
|
"0x7ff8************",
|
||||||
|
f64::from_bits(actual),
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NanPattern::Value(expected_value) => {
|
||||||
|
if actual == expected_value.bits {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"expected {:18} / {:#018x}\n\
|
||||||
|
actual {:18} / {:#018x}",
|
||||||
|
f64::from_bits(expected_value.bits),
|
||||||
|
expected_value.bits,
|
||||||
|
f64::from_bits(actual),
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_v128(actual: u128, expected: &V128Pattern) -> Result<()> {
|
||||||
|
match expected {
|
||||||
|
V128Pattern::I8x16(expected) => {
|
||||||
|
let actual = [
|
||||||
|
extract_lane_as_i8(actual, 0),
|
||||||
|
extract_lane_as_i8(actual, 1),
|
||||||
|
extract_lane_as_i8(actual, 2),
|
||||||
|
extract_lane_as_i8(actual, 3),
|
||||||
|
extract_lane_as_i8(actual, 4),
|
||||||
|
extract_lane_as_i8(actual, 5),
|
||||||
|
extract_lane_as_i8(actual, 6),
|
||||||
|
extract_lane_as_i8(actual, 7),
|
||||||
|
extract_lane_as_i8(actual, 8),
|
||||||
|
extract_lane_as_i8(actual, 9),
|
||||||
|
extract_lane_as_i8(actual, 10),
|
||||||
|
extract_lane_as_i8(actual, 11),
|
||||||
|
extract_lane_as_i8(actual, 12),
|
||||||
|
extract_lane_as_i8(actual, 13),
|
||||||
|
extract_lane_as_i8(actual, 14),
|
||||||
|
extract_lane_as_i8(actual, 15),
|
||||||
|
];
|
||||||
|
if actual == *expected {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
bail!(
|
||||||
|
"expected {:4?}\n\
|
||||||
|
actual {:4?}\n\
|
||||||
|
\n\
|
||||||
|
expected (hex) {0:02x?}\n\
|
||||||
|
actual (hex) {1:02x?}",
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
V128Pattern::I16x8(expected) => {
|
||||||
|
let actual = [
|
||||||
|
extract_lane_as_i16(actual, 0),
|
||||||
|
extract_lane_as_i16(actual, 1),
|
||||||
|
extract_lane_as_i16(actual, 2),
|
||||||
|
extract_lane_as_i16(actual, 3),
|
||||||
|
extract_lane_as_i16(actual, 4),
|
||||||
|
extract_lane_as_i16(actual, 5),
|
||||||
|
extract_lane_as_i16(actual, 6),
|
||||||
|
extract_lane_as_i16(actual, 7),
|
||||||
|
];
|
||||||
|
if actual == *expected {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
bail!(
|
||||||
|
"expected {:6?}\n\
|
||||||
|
actual {:6?}\n\
|
||||||
|
\n\
|
||||||
|
expected (hex) {0:04x?}\n\
|
||||||
|
actual (hex) {1:04x?}",
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
V128Pattern::I32x4(expected) => {
|
||||||
|
let actual = [
|
||||||
|
extract_lane_as_i32(actual, 0),
|
||||||
|
extract_lane_as_i32(actual, 1),
|
||||||
|
extract_lane_as_i32(actual, 2),
|
||||||
|
extract_lane_as_i32(actual, 3),
|
||||||
|
];
|
||||||
|
if actual == *expected {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
bail!(
|
||||||
|
"expected {:11?}\n\
|
||||||
|
actual {:11?}\n\
|
||||||
|
\n\
|
||||||
|
expected (hex) {0:08x?}\n\
|
||||||
|
actual (hex) {1:08x?}",
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
V128Pattern::I64x2(expected) => {
|
||||||
|
let actual = [
|
||||||
|
extract_lane_as_i64(actual, 0),
|
||||||
|
extract_lane_as_i64(actual, 1),
|
||||||
|
];
|
||||||
|
if actual == *expected {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
bail!(
|
||||||
|
"expected {:20?}\n\
|
||||||
|
actual {:20?}\n\
|
||||||
|
\n\
|
||||||
|
expected (hex) {0:016x?}\n\
|
||||||
|
actual (hex) {1:016x?}",
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
V128Pattern::F32x4(expected) => {
|
||||||
|
for (i, expected) in expected.iter().enumerate() {
|
||||||
|
let a = extract_lane_as_i32(actual, i) as u32;
|
||||||
|
match_f32(a, expected).with_context(|| format!("difference in lane {}", i))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
V128Pattern::F64x2(expected) => {
|
||||||
|
for (i, expected) in expected.iter().enumerate() {
|
||||||
|
let a = extract_lane_as_i64(actual, i) as u64;
|
||||||
|
match_f64(a, expected).with_context(|| format!("difference in lane {}", i))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,6 +21,9 @@
|
|||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
mod component;
|
||||||
|
mod core;
|
||||||
mod spectest;
|
mod spectest;
|
||||||
mod wast;
|
mod wast;
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,28 @@
|
|||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
use crate::component;
|
||||||
|
use crate::core;
|
||||||
use crate::spectest::*;
|
use crate::spectest::*;
|
||||||
use anyhow::{anyhow, bail, Context as _, Result};
|
use anyhow::{anyhow, bail, Context as _, Result};
|
||||||
use std::fmt::{Display, LowerHex};
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::str;
|
use std::str;
|
||||||
use wasmtime::*;
|
use wasmtime::*;
|
||||||
use wast::core::{Expression, HeapType};
|
|
||||||
use wast::lexer::Lexer;
|
use wast::lexer::Lexer;
|
||||||
use wast::parser::{self, ParseBuffer};
|
use wast::parser::{self, ParseBuffer};
|
||||||
use wast::token::{Float32, Float64};
|
use wast::{QuoteWat, Wast, WastArg, WastDirective, WastExecute, WastInvoke, WastRet, Wat};
|
||||||
use wast::{
|
|
||||||
AssertExpression, NanPattern, QuoteWat, V128Pattern, Wast, WastDirective, WastExecute,
|
|
||||||
WastInvoke, Wat,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Translate from a `script::Value` to a `RuntimeValue`.
|
|
||||||
fn runtime_value(v: &Expression<'_>) -> Result<Val> {
|
|
||||||
use wast::core::Instruction::*;
|
|
||||||
|
|
||||||
if v.instrs.len() != 1 {
|
|
||||||
bail!("too many instructions in {:?}", v);
|
|
||||||
}
|
|
||||||
Ok(match &v.instrs[0] {
|
|
||||||
I32Const(x) => Val::I32(*x),
|
|
||||||
I64Const(x) => Val::I64(*x),
|
|
||||||
F32Const(x) => Val::F32(x.bits),
|
|
||||||
F64Const(x) => Val::F64(x.bits),
|
|
||||||
V128Const(x) => Val::V128(u128::from_le_bytes(x.to_le_bytes())),
|
|
||||||
RefNull(HeapType::Extern) => Val::ExternRef(None),
|
|
||||||
RefNull(HeapType::Func) => Val::FuncRef(None),
|
|
||||||
RefExtern(x) => Val::ExternRef(Some(ExternRef::new(*x))),
|
|
||||||
other => bail!("couldn't convert {:?} to a runtime value", other),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The wast test script language allows modules to be defined and actions
|
/// The wast test script language allows modules to be defined and actions
|
||||||
/// to be performed on them.
|
/// to be performed on them.
|
||||||
pub struct WastContext<T> {
|
pub struct WastContext<T> {
|
||||||
/// Wast files have a concept of a "current" module, which is the most
|
/// Wast files have a concept of a "current" module, which is the most
|
||||||
/// recently defined.
|
/// recently defined.
|
||||||
current: Option<Instance>,
|
current: Option<InstanceKind>,
|
||||||
core_linker: Linker<T>,
|
core_linker: Linker<T>,
|
||||||
#[cfg(feature = "component-model")]
|
#[cfg(feature = "component-model")]
|
||||||
component_linker: component::Linker<T>,
|
component_linker: component::Linker<T>,
|
||||||
store: Store<T>,
|
store: Store<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Outcome<T = Vec<Val>> {
|
enum Outcome<T = Results> {
|
||||||
Ok(T),
|
Ok(T),
|
||||||
Trap(Trap),
|
Trap(Trap),
|
||||||
}
|
}
|
||||||
@@ -66,6 +43,25 @@ impl<T> Outcome<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Results {
|
||||||
|
Core(Vec<Val>),
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
Component(component::Val),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum InstanceKind {
|
||||||
|
Core(Instance),
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
Component(component::Instance),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Export {
|
||||||
|
Core(Extern),
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
Component(component::Func),
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> WastContext<T> {
|
impl<T> WastContext<T> {
|
||||||
/// Construct a new instance of `WastContext`.
|
/// Construct a new instance of `WastContext`.
|
||||||
pub fn new(store: Store<T>) -> Self {
|
pub fn new(store: Store<T>) -> Self {
|
||||||
@@ -87,19 +83,30 @@ impl<T> WastContext<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Extern> {
|
fn get_export(&mut self, module: Option<&str>, name: &str) -> Result<Export> {
|
||||||
match module {
|
if let Some(module) = module {
|
||||||
Some(module) => self
|
return Ok(Export::Core(
|
||||||
.core_linker
|
self.core_linker
|
||||||
.get(&mut self.store, module, name)
|
.get(&mut self.store, module, name)
|
||||||
.ok_or_else(|| anyhow!("no item named `{}::{}` found", module, name)),
|
.ok_or_else(|| anyhow!("no item named `{}::{}` found", module, name))?,
|
||||||
None => self
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let cur = self
|
||||||
.current
|
.current
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or_else(|| anyhow!("no previous instance found"))?
|
.ok_or_else(|| anyhow!("no previous instance found"))?;
|
||||||
.get_export(&mut self.store, name)
|
Ok(match cur {
|
||||||
.ok_or_else(|| anyhow!("no item named `{}` found", name)),
|
InstanceKind::Core(i) => Export::Core(
|
||||||
}
|
i.get_export(&mut self.store, name)
|
||||||
|
.ok_or_else(|| anyhow!("no item named `{}` found", name))?,
|
||||||
|
),
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
InstanceKind::Component(i) => Export::Component(
|
||||||
|
i.get_func(&mut self.store, name)
|
||||||
|
.ok_or_else(|| anyhow!("no func named `{}` found", name))?,
|
||||||
|
),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instantiate_module(&mut self, module: &[u8]) -> Result<Outcome<Instance>> {
|
fn instantiate_module(&mut self, module: &[u8]) -> Result<Outcome<Instance>> {
|
||||||
@@ -134,30 +141,67 @@ impl<T> WastContext<T> {
|
|||||||
fn perform_execute(&mut self, exec: WastExecute<'_>) -> Result<Outcome> {
|
fn perform_execute(&mut self, exec: WastExecute<'_>) -> Result<Outcome> {
|
||||||
match exec {
|
match exec {
|
||||||
WastExecute::Invoke(invoke) => self.perform_invoke(invoke),
|
WastExecute::Invoke(invoke) => self.perform_invoke(invoke),
|
||||||
WastExecute::Wat(mut module) => {
|
WastExecute::Wat(mut module) => Ok(match &mut module {
|
||||||
let result = match &mut module {
|
Wat::Module(m) => self
|
||||||
Wat::Module(m) => self.instantiate_module(&m.encode()?)?.map(|_| ()),
|
.instantiate_module(&m.encode()?)?
|
||||||
|
.map(|_| Results::Core(Vec::new())),
|
||||||
#[cfg(feature = "component-model")]
|
#[cfg(feature = "component-model")]
|
||||||
Wat::Component(m) => self.instantiate_component(&m.encode()?)?.map(|_| ()),
|
Wat::Component(m) => self
|
||||||
|
.instantiate_component(&m.encode()?)?
|
||||||
|
.map(|_| Results::Component(component::Val::Unit)),
|
||||||
#[cfg(not(feature = "component-model"))]
|
#[cfg(not(feature = "component-model"))]
|
||||||
Wat::Component(_) => bail!("component-model support not enabled"),
|
Wat::Component(_) => bail!("component-model support not enabled"),
|
||||||
};
|
}),
|
||||||
Ok(match result {
|
|
||||||
Outcome::Ok(_) => Outcome::Ok(Vec::new()),
|
|
||||||
Outcome::Trap(e) => Outcome::Trap(e),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
WastExecute::Get { module, global } => self.get(module.map(|s| s.name()), global),
|
WastExecute::Get { module, global } => self.get(module.map(|s| s.name()), global),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn perform_invoke(&mut self, exec: WastInvoke<'_>) -> Result<Outcome> {
|
fn perform_invoke(&mut self, exec: WastInvoke<'_>) -> Result<Outcome> {
|
||||||
|
match self.get_export(exec.module.map(|i| i.name()), exec.name)? {
|
||||||
|
Export::Core(export) => {
|
||||||
|
let func = export
|
||||||
|
.into_func()
|
||||||
|
.ok_or_else(|| anyhow!("no function named `{}`", exec.name))?;
|
||||||
let values = exec
|
let values = exec
|
||||||
.args
|
.args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| runtime_value(v))
|
.map(|v| match v {
|
||||||
|
WastArg::Core(v) => core::val(v),
|
||||||
|
WastArg::Component(_) => bail!("expected component function, found core"),
|
||||||
|
})
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
self.invoke(exec.module.map(|i| i.name()), exec.name, &values)
|
|
||||||
|
let mut results = vec![Val::null(); func.ty(&self.store).results().len()];
|
||||||
|
Ok(match func.call(&mut self.store, &values, &mut results) {
|
||||||
|
Ok(()) => Outcome::Ok(Results::Core(results.into())),
|
||||||
|
Err(e) => Outcome::Trap(e.downcast()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
Export::Component(func) => {
|
||||||
|
let params = func.params(&self.store);
|
||||||
|
if exec.args.len() != params.len() {
|
||||||
|
bail!("mismatched number of parameters")
|
||||||
|
}
|
||||||
|
let values = exec
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.zip(params.iter())
|
||||||
|
.map(|(v, t)| match v {
|
||||||
|
WastArg::Component(v) => component::val(v, t),
|
||||||
|
WastArg::Core(_) => bail!("expected core function, found component"),
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(match func.call(&mut self.store, &values) {
|
||||||
|
Ok(results) => {
|
||||||
|
func.post_return(&mut self.store)?;
|
||||||
|
Outcome::Ok(Results::Component(results.into()))
|
||||||
|
}
|
||||||
|
Err(e) => Outcome::Trap(e.downcast()?),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define a module and register it.
|
/// Define a module and register it.
|
||||||
@@ -178,7 +222,7 @@ impl<T> WastContext<T> {
|
|||||||
self.core_linker
|
self.core_linker
|
||||||
.instance(&mut self.store, name.name(), instance)?;
|
.instance(&mut self.store, name.name(), instance)?;
|
||||||
}
|
}
|
||||||
self.current = Some(instance);
|
self.current = Some(InstanceKind::Core(instance));
|
||||||
} else {
|
} else {
|
||||||
#[cfg(feature = "component-model")]
|
#[cfg(feature = "component-model")]
|
||||||
{
|
{
|
||||||
@@ -197,6 +241,7 @@ impl<T> WastContext<T> {
|
|||||||
linker.module(name, module)?;
|
linker.module(name, module)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.current = Some(InstanceKind::Component(instance));
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "component-model"))]
|
#[cfg(not(feature = "component-model"))]
|
||||||
bail!("component-model support not enabled");
|
bail!("component-model support not enabled");
|
||||||
@@ -209,49 +254,65 @@ impl<T> WastContext<T> {
|
|||||||
match name {
|
match name {
|
||||||
Some(name) => self.core_linker.alias_module(name, as_name),
|
Some(name) => self.core_linker.alias_module(name, as_name),
|
||||||
None => {
|
None => {
|
||||||
let current = *self
|
let current = self
|
||||||
.current
|
.current
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(anyhow!("no previous instance"))?;
|
.ok_or(anyhow!("no previous instance"))?;
|
||||||
|
match current {
|
||||||
|
InstanceKind::Core(current) => {
|
||||||
self.core_linker
|
self.core_linker
|
||||||
.instance(&mut self.store, as_name, current)?;
|
.instance(&mut self.store, as_name, *current)?;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
InstanceKind::Component(_) => {
|
||||||
|
bail!("register not implemented for components");
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invoke an exported function from an instance.
|
|
||||||
fn invoke(
|
|
||||||
&mut self,
|
|
||||||
instance_name: Option<&str>,
|
|
||||||
field: &str,
|
|
||||||
args: &[Val],
|
|
||||||
) -> Result<Outcome> {
|
|
||||||
let func = self
|
|
||||||
.get_export(instance_name, field)?
|
|
||||||
.into_func()
|
|
||||||
.ok_or_else(|| anyhow!("no function named `{}`", field))?;
|
|
||||||
|
|
||||||
let mut results = vec![Val::null(); func.ty(&self.store).results().len()];
|
|
||||||
Ok(match func.call(&mut self.store, args, &mut results) {
|
|
||||||
Ok(()) => Outcome::Ok(results.into()),
|
|
||||||
Err(e) => Outcome::Trap(e.downcast()?),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the value of an exported global from an instance.
|
/// Get the value of an exported global from an instance.
|
||||||
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
|
fn get(&mut self, instance_name: Option<&str>, field: &str) -> Result<Outcome> {
|
||||||
let global = self
|
let global = match self.get_export(instance_name, field)? {
|
||||||
.get_export(instance_name, field)?
|
Export::Core(e) => e
|
||||||
.into_global()
|
.into_global()
|
||||||
.ok_or_else(|| anyhow!("no global named `{}`", field))?;
|
.ok_or_else(|| anyhow!("no global named `{field}`"))?,
|
||||||
Ok(Outcome::Ok(vec![global.get(&mut self.store)]))
|
#[cfg(feature = "component-model")]
|
||||||
|
Export::Component(_) => bail!("no global named `{field}`"),
|
||||||
|
};
|
||||||
|
Ok(Outcome::Ok(Results::Core(
|
||||||
|
vec![global.get(&mut self.store)],
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_return(&self, result: Outcome, results: &[AssertExpression]) -> Result<()> {
|
fn assert_return(&self, result: Outcome, results: &[WastRet<'_>]) -> Result<()> {
|
||||||
let values = result.into_result()?;
|
match result.into_result()? {
|
||||||
|
Results::Core(values) => {
|
||||||
for (i, (v, e)) in values.iter().zip(results).enumerate() {
|
for (i, (v, e)) in values.iter().zip(results).enumerate() {
|
||||||
match_val(v, e).with_context(|| format!("result {} didn't match", i))?;
|
let e = match e {
|
||||||
|
WastRet::Core(core) => core,
|
||||||
|
WastRet::Component(_) => {
|
||||||
|
bail!("expected component value found core value")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
core::match_val(v, e).with_context(|| format!("result {} didn't match", i))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
Results::Component(value) => {
|
||||||
|
if results.len() != 1 {
|
||||||
|
bail!("expected one result value assertion");
|
||||||
|
}
|
||||||
|
let result = match &results[0] {
|
||||||
|
WastRet::Component(ret) => ret,
|
||||||
|
WastRet::Core(_) => {
|
||||||
|
bail!("expected core value found component value")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
component::match_val(&result, &value)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -414,327 +475,3 @@ fn is_matching_assert_invalid_error_message(expected: &str, actual: &str) -> boo
|
|||||||
// the memory64 tests to pass.
|
// the memory64 tests to pass.
|
||||||
|| (expected.contains("memory size must be at most 65536 pages") && actual.contains("invalid u32 number"))
|
|| (expected.contains("memory size must be at most 65536 pages") && actual.contains("invalid u32 number"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_lane_as_i8(bytes: u128, lane: usize) -> i8 {
|
|
||||||
(bytes >> (lane * 8)) as i8
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_lane_as_i16(bytes: u128, lane: usize) -> i16 {
|
|
||||||
(bytes >> (lane * 16)) as i16
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_lane_as_i32(bytes: u128, lane: usize) -> i32 {
|
|
||||||
(bytes >> (lane * 32)) as i32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_lane_as_i64(bytes: u128, lane: usize) -> i64 {
|
|
||||||
(bytes >> (lane * 64)) as i64
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_val(actual: &Val, expected: &AssertExpression) -> Result<()> {
|
|
||||||
match (actual, expected) {
|
|
||||||
(Val::I32(a), AssertExpression::I32(b)) => match_int(a, b),
|
|
||||||
(Val::I64(a), AssertExpression::I64(b)) => match_int(a, b),
|
|
||||||
// Note that these float comparisons are comparing bits, not float
|
|
||||||
// values, so we're testing for bit-for-bit equivalence
|
|
||||||
(Val::F32(a), AssertExpression::F32(b)) => match_f32(*a, b),
|
|
||||||
(Val::F64(a), AssertExpression::F64(b)) => match_f64(*a, b),
|
|
||||||
(Val::V128(a), AssertExpression::V128(b)) => match_v128(*a, b),
|
|
||||||
(Val::ExternRef(x), AssertExpression::RefNull(Some(HeapType::Extern))) => {
|
|
||||||
if let Some(x) = x {
|
|
||||||
let x = x
|
|
||||||
.data()
|
|
||||||
.downcast_ref::<u32>()
|
|
||||||
.expect("only u32 externrefs created in wast test suites");
|
|
||||||
bail!("expected null externref, found {}", x);
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Val::ExternRef(x), AssertExpression::RefExtern(y)) => {
|
|
||||||
if let Some(x) = x {
|
|
||||||
let x = x
|
|
||||||
.data()
|
|
||||||
.downcast_ref::<u32>()
|
|
||||||
.expect("only u32 externrefs created in wast test suites");
|
|
||||||
if x == y {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!("expected {} found {}", y, x);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bail!("expected non-null externref, found null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(Val::FuncRef(x), AssertExpression::RefNull(_)) => {
|
|
||||||
if x.is_none() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!("expected null funcref, found non-null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => bail!(
|
|
||||||
"don't know how to compare {:?} and {:?} yet",
|
|
||||||
actual,
|
|
||||||
expected
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_int<T>(actual: &T, expected: &T) -> Result<()>
|
|
||||||
where
|
|
||||||
T: Eq + Display + LowerHex,
|
|
||||||
{
|
|
||||||
if actual == expected {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"expected {:18} / {0:#018x}\n\
|
|
||||||
actual {:18} / {1:#018x}",
|
|
||||||
expected,
|
|
||||||
actual
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_f32(actual: u32, expected: &NanPattern<Float32>) -> Result<()> {
|
|
||||||
match expected {
|
|
||||||
// Check if an f32 (as u32 bits to avoid possible quieting when moving values in registers, e.g.
|
|
||||||
// https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
|
|
||||||
// is a canonical NaN:
|
|
||||||
// - the sign bit is unspecified,
|
|
||||||
// - the 8-bit exponent is set to all 1s
|
|
||||||
// - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
|
|
||||||
// See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
|
||||||
NanPattern::CanonicalNan => {
|
|
||||||
let canon_nan = 0x7fc0_0000;
|
|
||||||
if (actual & 0x7fff_ffff) == canon_nan {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"expected {:10} / {:#010x}\n\
|
|
||||||
actual {:10} / {:#010x}",
|
|
||||||
"canon-nan",
|
|
||||||
canon_nan,
|
|
||||||
f32::from_bits(actual),
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if an f32 (as u32, see comments above) is an arithmetic NaN.
|
|
||||||
// This is the same as a canonical NaN including that the payload MSB is
|
|
||||||
// set to 1, but one or more of the remaining payload bits MAY BE set to
|
|
||||||
// 1 (a canonical NaN specifies all 0s). See
|
|
||||||
// https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
|
||||||
NanPattern::ArithmeticNan => {
|
|
||||||
const AF32_NAN: u32 = 0x7f80_0000;
|
|
||||||
let is_nan = actual & AF32_NAN == AF32_NAN;
|
|
||||||
const AF32_PAYLOAD_MSB: u32 = 0x0040_0000;
|
|
||||||
let is_msb_set = actual & AF32_PAYLOAD_MSB == AF32_PAYLOAD_MSB;
|
|
||||||
if is_nan && is_msb_set {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"expected {:>10} / {:>10}\n\
|
|
||||||
actual {:10} / {:#010x}",
|
|
||||||
"arith-nan",
|
|
||||||
"0x7fc*****",
|
|
||||||
f32::from_bits(actual),
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NanPattern::Value(expected_value) => {
|
|
||||||
if actual == expected_value.bits {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"expected {:10} / {:#010x}\n\
|
|
||||||
actual {:10} / {:#010x}",
|
|
||||||
f32::from_bits(expected_value.bits),
|
|
||||||
expected_value.bits,
|
|
||||||
f32::from_bits(actual),
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_f64(actual: u64, expected: &NanPattern<Float64>) -> Result<()> {
|
|
||||||
match expected {
|
|
||||||
// Check if an f64 (as u64 bits to avoid possible quieting when moving values in registers, e.g.
|
|
||||||
// https://developer.arm.com/documentation/ddi0344/i/neon-and-vfp-programmers-model/modes-of-operation/default-nan-mode?lang=en)
|
|
||||||
// is a canonical NaN:
|
|
||||||
// - the sign bit is unspecified,
|
|
||||||
// - the 11-bit exponent is set to all 1s
|
|
||||||
// - the MSB of the payload is set to 1 (a quieted NaN) and all others to 0.
|
|
||||||
// See https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
|
||||||
NanPattern::CanonicalNan => {
|
|
||||||
let canon_nan = 0x7ff8_0000_0000_0000;
|
|
||||||
if (actual & 0x7fff_ffff_ffff_ffff) == canon_nan {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"expected {:18} / {:#018x}\n\
|
|
||||||
actual {:18} / {:#018x}",
|
|
||||||
"canon-nan",
|
|
||||||
canon_nan,
|
|
||||||
f64::from_bits(actual),
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if an f64 (as u64, see comments above) is an arithmetic NaN. This is the same as a
|
|
||||||
// canonical NaN including that the payload MSB is set to 1, but one or more of the remaining
|
|
||||||
// payload bits MAY BE set to 1 (a canonical NaN specifies all 0s). See
|
|
||||||
// https://webassembly.github.io/spec/core/syntax/values.html#floating-point.
|
|
||||||
NanPattern::ArithmeticNan => {
|
|
||||||
const AF64_NAN: u64 = 0x7ff0_0000_0000_0000;
|
|
||||||
let is_nan = actual & AF64_NAN == AF64_NAN;
|
|
||||||
const AF64_PAYLOAD_MSB: u64 = 0x0008_0000_0000_0000;
|
|
||||||
let is_msb_set = actual & AF64_PAYLOAD_MSB == AF64_PAYLOAD_MSB;
|
|
||||||
if is_nan && is_msb_set {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"expected {:>18} / {:>18}\n\
|
|
||||||
actual {:18} / {:#018x}",
|
|
||||||
"arith-nan",
|
|
||||||
"0x7ff8************",
|
|
||||||
f64::from_bits(actual),
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NanPattern::Value(expected_value) => {
|
|
||||||
if actual == expected_value.bits {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"expected {:18} / {:#018x}\n\
|
|
||||||
actual {:18} / {:#018x}",
|
|
||||||
f64::from_bits(expected_value.bits),
|
|
||||||
expected_value.bits,
|
|
||||||
f64::from_bits(actual),
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn match_v128(actual: u128, expected: &V128Pattern) -> Result<()> {
|
|
||||||
match expected {
|
|
||||||
V128Pattern::I8x16(expected) => {
|
|
||||||
let actual = [
|
|
||||||
extract_lane_as_i8(actual, 0),
|
|
||||||
extract_lane_as_i8(actual, 1),
|
|
||||||
extract_lane_as_i8(actual, 2),
|
|
||||||
extract_lane_as_i8(actual, 3),
|
|
||||||
extract_lane_as_i8(actual, 4),
|
|
||||||
extract_lane_as_i8(actual, 5),
|
|
||||||
extract_lane_as_i8(actual, 6),
|
|
||||||
extract_lane_as_i8(actual, 7),
|
|
||||||
extract_lane_as_i8(actual, 8),
|
|
||||||
extract_lane_as_i8(actual, 9),
|
|
||||||
extract_lane_as_i8(actual, 10),
|
|
||||||
extract_lane_as_i8(actual, 11),
|
|
||||||
extract_lane_as_i8(actual, 12),
|
|
||||||
extract_lane_as_i8(actual, 13),
|
|
||||||
extract_lane_as_i8(actual, 14),
|
|
||||||
extract_lane_as_i8(actual, 15),
|
|
||||||
];
|
|
||||||
if actual == *expected {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
bail!(
|
|
||||||
"expected {:4?}\n\
|
|
||||||
actual {:4?}\n\
|
|
||||||
\n\
|
|
||||||
expected (hex) {0:02x?}\n\
|
|
||||||
actual (hex) {1:02x?}",
|
|
||||||
expected,
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
V128Pattern::I16x8(expected) => {
|
|
||||||
let actual = [
|
|
||||||
extract_lane_as_i16(actual, 0),
|
|
||||||
extract_lane_as_i16(actual, 1),
|
|
||||||
extract_lane_as_i16(actual, 2),
|
|
||||||
extract_lane_as_i16(actual, 3),
|
|
||||||
extract_lane_as_i16(actual, 4),
|
|
||||||
extract_lane_as_i16(actual, 5),
|
|
||||||
extract_lane_as_i16(actual, 6),
|
|
||||||
extract_lane_as_i16(actual, 7),
|
|
||||||
];
|
|
||||||
if actual == *expected {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
bail!(
|
|
||||||
"expected {:6?}\n\
|
|
||||||
actual {:6?}\n\
|
|
||||||
\n\
|
|
||||||
expected (hex) {0:04x?}\n\
|
|
||||||
actual (hex) {1:04x?}",
|
|
||||||
expected,
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
V128Pattern::I32x4(expected) => {
|
|
||||||
let actual = [
|
|
||||||
extract_lane_as_i32(actual, 0),
|
|
||||||
extract_lane_as_i32(actual, 1),
|
|
||||||
extract_lane_as_i32(actual, 2),
|
|
||||||
extract_lane_as_i32(actual, 3),
|
|
||||||
];
|
|
||||||
if actual == *expected {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
bail!(
|
|
||||||
"expected {:11?}\n\
|
|
||||||
actual {:11?}\n\
|
|
||||||
\n\
|
|
||||||
expected (hex) {0:08x?}\n\
|
|
||||||
actual (hex) {1:08x?}",
|
|
||||||
expected,
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
V128Pattern::I64x2(expected) => {
|
|
||||||
let actual = [
|
|
||||||
extract_lane_as_i64(actual, 0),
|
|
||||||
extract_lane_as_i64(actual, 1),
|
|
||||||
];
|
|
||||||
if actual == *expected {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
bail!(
|
|
||||||
"expected {:20?}\n\
|
|
||||||
actual {:20?}\n\
|
|
||||||
\n\
|
|
||||||
expected (hex) {0:016x?}\n\
|
|
||||||
actual (hex) {1:016x?}",
|
|
||||||
expected,
|
|
||||||
actual,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
V128Pattern::F32x4(expected) => {
|
|
||||||
for (i, expected) in expected.iter().enumerate() {
|
|
||||||
let a = extract_lane_as_i32(actual, i) as u32;
|
|
||||||
match_f32(a, expected).with_context(|| format!("difference in lane {}", i))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
V128Pattern::F64x2(expected) => {
|
|
||||||
for (i, expected) in expected.iter().enumerate() {
|
|
||||||
let a = extract_lane_as_i64(actual, i) as u64;
|
|
||||||
match_f64(a, expected).with_context(|| format!("difference in lane {}", i))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -31,30 +31,60 @@ criteria = "safe-to-deploy"
|
|||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
notes = "The Bytecode Alliance is the author of this crate."
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
|
[[audits.wasm-encoder]]
|
||||||
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
version = "0.15.0"
|
||||||
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
[[audits.wasm-mutate]]
|
[[audits.wasm-mutate]]
|
||||||
who = "Alex Crichton <alex@alexcrichton.com>"
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
notes = "The Bytecode Alliance is the author of this crate."
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
|
[[audits.wasm-mutate]]
|
||||||
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
version = "0.2.6"
|
||||||
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
[[audits.wasm-smith]]
|
[[audits.wasm-smith]]
|
||||||
who = "Alex Crichton <alex@alexcrichton.com>"
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
notes = "The Bytecode Alliance is the author of this crate."
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
|
[[audits.wasm-smith]]
|
||||||
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
version = "0.11.3"
|
||||||
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
[[audits.wasmparser]]
|
[[audits.wasmparser]]
|
||||||
who = "Alex Crichton <alex@alexcrichton.com>"
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
version = "0.87.0"
|
version = "0.87.0"
|
||||||
notes = "The Bytecode Alliance is the author of this crate."
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
|
[[audits.wasmparser]]
|
||||||
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
version = "0.88.0"
|
||||||
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
[[audits.wasmprinter]]
|
[[audits.wasmprinter]]
|
||||||
who = "Alex Crichton <alex@alexcrichton.com>"
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
version = "0.2.37"
|
version = "0.2.37"
|
||||||
notes = "The Bytecode Alliance is the author of this crate."
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
|
[[audits.wasmprinter]]
|
||||||
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
version = "0.2.38"
|
||||||
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
[[audits.wast]]
|
[[audits.wast]]
|
||||||
who = "Alex Crichton <alex@alexcrichton.com>"
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
@@ -67,9 +97,21 @@ criteria = "safe-to-deploy"
|
|||||||
version = "44.0.0"
|
version = "44.0.0"
|
||||||
notes = "The Bytecode Alliance is the author of this crate"
|
notes = "The Bytecode Alliance is the author of this crate"
|
||||||
|
|
||||||
|
[[audits.wast]]
|
||||||
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
version = "45.0.0"
|
||||||
|
notes = "The Bytecode Alliance is the author of this crate"
|
||||||
|
|
||||||
[[audits.wat]]
|
[[audits.wat]]
|
||||||
who = "Alex Crichton <alex@alexcrichton.com>"
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
criteria = "safe-to-deploy"
|
criteria = "safe-to-deploy"
|
||||||
version = "1.0.46"
|
version = "1.0.46"
|
||||||
notes = "The Bytecode Alliance is the author of this crate."
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
|
[[audits.wat]]
|
||||||
|
who = "Alex Crichton <alex@alexcrichton.com>"
|
||||||
|
criteria = "safe-to-deploy"
|
||||||
|
version = "1.0.47"
|
||||||
|
notes = "The Bytecode Alliance is the author of this crate."
|
||||||
|
|
||||||
|
|||||||
@@ -160,9 +160,17 @@
|
|||||||
(component $c
|
(component $c
|
||||||
(import "roundtrip" (func $roundtrip (type $roundtrip)))
|
(import "roundtrip" (func $roundtrip (type $roundtrip)))
|
||||||
|
|
||||||
(core module $libc (memory (export "memory") 1))
|
(core module $libc
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable)
|
||||||
|
)
|
||||||
(core instance $libc (instantiate $libc))
|
(core instance $libc (instantiate $libc))
|
||||||
(core func $roundtrip (canon lower (func $roundtrip) (memory $libc "memory")))
|
(core func $roundtrip
|
||||||
|
(canon lower (func $roundtrip)
|
||||||
|
(memory $libc "memory")
|
||||||
|
(realloc (func $libc "realloc")) ;; FIXME(wasm-tools#693) should not be necessary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
(core module $m2
|
(core module $m2
|
||||||
(import "libc" "memory" (memory 1))
|
(import "libc" "memory" (memory 1))
|
||||||
@@ -312,9 +320,18 @@
|
|||||||
(component $c
|
(component $c
|
||||||
(import "foo" (func $foo (param $tuple20)))
|
(import "foo" (func $foo (param $tuple20)))
|
||||||
|
|
||||||
(core module $libc (memory (export "memory") 1))
|
(core module $libc
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32)
|
||||||
|
unreachable)
|
||||||
|
)
|
||||||
(core instance $libc (instantiate $libc))
|
(core instance $libc (instantiate $libc))
|
||||||
(core func $foo (canon lower (func $foo) (memory $libc "memory")))
|
(core func $foo
|
||||||
|
(canon lower (func $foo)
|
||||||
|
(memory $libc "memory")
|
||||||
|
(realloc (func $libc "realloc")) ;; FIXME(wasm-tools#693) should not be necessary
|
||||||
|
)
|
||||||
|
)
|
||||||
(core module $something
|
(core module $something
|
||||||
(import "" "foo" (func (param i32)))
|
(import "" "foo" (func (param i32)))
|
||||||
)
|
)
|
||||||
@@ -620,9 +637,17 @@
|
|||||||
)
|
)
|
||||||
(component $c2
|
(component $c2
|
||||||
(import "r" (func $r (param $big)))
|
(import "r" (func $r (param $big)))
|
||||||
(core module $libc (memory (export "memory") 1))
|
(core module $libc
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable)
|
||||||
|
)
|
||||||
(core instance $libc (instantiate $libc))
|
(core instance $libc (instantiate $libc))
|
||||||
(core func $r (canon lower (func $r) (memory $libc "memory")))
|
(core func $r
|
||||||
|
(canon lower (func $r)
|
||||||
|
(memory $libc "memory")
|
||||||
|
(realloc (func $libc "realloc")) ;; FIXME(wasm-tools#693) should not be necessary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
(core module $m
|
(core module $m
|
||||||
(import "" "r" (func $r (param i32)))
|
(import "" "r" (func $r (param i32)))
|
||||||
@@ -660,9 +685,18 @@
|
|||||||
)
|
)
|
||||||
(component $c2
|
(component $c2
|
||||||
(import "r" (func $r (param $big)))
|
(import "r" (func $r (param $big)))
|
||||||
(core module $libc (memory (export "memory") 1))
|
(core module $libc
|
||||||
|
(memory (export "memory") 1)
|
||||||
|
(func (export "realloc") (param i32 i32 i32 i32) (result i32) unreachable)
|
||||||
|
)
|
||||||
(core instance $libc (instantiate $libc))
|
(core instance $libc (instantiate $libc))
|
||||||
(core func $r (canon lower (func $r) (memory $libc "memory")))
|
(core func $r
|
||||||
|
(canon lower (func $r)
|
||||||
|
(memory $libc "memory")
|
||||||
|
(realloc (func $libc "realloc")) ;; FIXME(wasm-tools#693) should not be necessary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
(core module $m
|
(core module $m
|
||||||
(import "" "r" (func $r (param i32)))
|
(import "" "r" (func $r (param i32)))
|
||||||
|
|||||||
@@ -32,3 +32,11 @@
|
|||||||
(component (export ""))
|
(component (export ""))
|
||||||
)
|
)
|
||||||
"exporting a component from the root component is not supported")
|
"exporting a component from the root component is not supported")
|
||||||
|
|
||||||
|
(component
|
||||||
|
(core module $m (func (export "")))
|
||||||
|
(core instance $m (instantiate $m))
|
||||||
|
(func (export "") (canon lift (core func $m "")))
|
||||||
|
)
|
||||||
|
|
||||||
|
(assert_return (invoke "") (unit.const))
|
||||||
|
|||||||
@@ -101,3 +101,28 @@
|
|||||||
))
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(component
|
||||||
|
(core module $m (func (export "") (param i32) (result i32) local.get 0))
|
||||||
|
(core instance $m (instantiate $m))
|
||||||
|
(func (export "i-to-b") (param u32) (result bool) (canon lift (core func $m "")))
|
||||||
|
(func (export "i-to-u8") (param u32) (result u8) (canon lift (core func $m "")))
|
||||||
|
(func (export "i-to-s8") (param u32) (result s8) (canon lift (core func $m "")))
|
||||||
|
(func (export "i-to-u16") (param u32) (result u16) (canon lift (core func $m "")))
|
||||||
|
(func (export "i-to-s16") (param u32) (result s16) (canon lift (core func $m "")))
|
||||||
|
)
|
||||||
|
(assert_return (invoke "i-to-b" (u32.const 0)) (bool.const false))
|
||||||
|
(assert_return (invoke "i-to-b" (u32.const 1)) (bool.const true))
|
||||||
|
(assert_return (invoke "i-to-b" (u32.const 2)) (bool.const true))
|
||||||
|
(assert_return (invoke "i-to-u8" (u32.const 0x00)) (u8.const 0))
|
||||||
|
(assert_return (invoke "i-to-u8" (u32.const 0x01)) (u8.const 1))
|
||||||
|
(assert_return (invoke "i-to-u8" (u32.const 0xf01)) (u8.const 1))
|
||||||
|
(assert_return (invoke "i-to-u8" (u32.const 0xf00)) (u8.const 0))
|
||||||
|
(assert_return (invoke "i-to-s8" (u32.const 0xffffffff)) (s8.const -1))
|
||||||
|
(assert_return (invoke "i-to-s8" (u32.const 127)) (s8.const 127))
|
||||||
|
(assert_return (invoke "i-to-u16" (u32.const 0)) (u16.const 0))
|
||||||
|
(assert_return (invoke "i-to-u16" (u32.const 1)) (u16.const 1))
|
||||||
|
(assert_return (invoke "i-to-u16" (u32.const 0xffffffff)) (u16.const 0xffff))
|
||||||
|
(assert_return (invoke "i-to-s16" (u32.const 0)) (s16.const 0))
|
||||||
|
(assert_return (invoke "i-to-s16" (u32.const 1)) (s16.const 1))
|
||||||
|
(assert_return (invoke "i-to-s16" (u32.const 0xffffffff)) (s16.const -1))
|
||||||
|
|||||||
Reference in New Issue
Block a user