diff --git a/.travis.yml b/.travis.yml index 3343db91..c0768336 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,9 @@ before_install: - export PATH=$PATH:`pwd`/sccache-0.2.7-$target - export RUSTC_WRAPPER=sccache +before_script: + - export RUST_BACKTRACE=1 + after_script: - sccache -s @@ -33,15 +36,11 @@ install: matrix: include: - - name: "test (stable)" - rust: stable - - name: "test (beta)" - rust: beta - - name: "test (nightly)" - rust: nightly - - # A job to run our fuzzers a bit longer than the default. - - name: "fuzz (stable)" + # First up: some jobs to run our fuzzers a bit longer than the default when + # we do `cargo test`. + # + # We run these first because they take the longest. + - name: "fuzz-utils (stable)" rust: stable env: # 300 seconds = 5 minutes. @@ -50,9 +49,52 @@ matrix: | # When the fuzzing fails, the logs are too big for Travis, so just # show the relevant tail portion of the log. - cargo test -p walrus-fuzz > fuzz.log || { + cargo test -p walrus-fuzz-utils > fuzz.log || { + tail -n 1000 fuzz.log && exit 1 + } + - name: "cargo fuzz run watgen (nightly)" + rust: nightly + script: + | + which cargo-fuzz || cargo install cargo-fuzz + # When the fuzzing fails, the logs are too big for Travis, so just + # show the relevant tail portion of the log. + cargo fuzz run watgen -- -max_total_time=300 > fuzz.log 2>&1 || { tail -n 1000 fuzz.log && exit 1 } + - name: "cargo fuzz run wasm-opt-ttf (nightly)" + rust: nightly + script: + | + which cargo-fuzz || cargo install cargo-fuzz + + (for (( ; ; )); do + tail -n 100 fuzz.log || true + sleep 10 + done) & + + # When the fuzzing fails, the logs are too big for Travis, so just + # show the relevant tail portion of the log. + cargo fuzz run wasm-opt-ttf -- -max_total_time=300 > fuzz.log 2>&1 || { + tail -n 1000 fuzz.log && exit 1 + } + - name: "cargo fuzz run raw (nightly)" + rust: nightly + script: + | + which cargo-fuzz || cargo install cargo-fuzz + # When the fuzzing fails, the logs are too big for Travis, so just + # show the relevant tail portion of the log. + cargo fuzz run raw -- -max_total_time=300 > fuzz.log 2>&1 || { + tail -n 1000 fuzz.log && exit 1 + } + + - name: "test (stable)" + rust: stable + - name: "test (beta)" + rust: beta + - name: "test (nightly)" + rust: nightly - name: "check benches" rust: stable @@ -71,7 +113,6 @@ matrix: on: branch: master - script: - cargo build --all - cargo test --all diff --git a/Cargo.toml b/Cargo.toml index ef79f2a6..d80ebbfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ criterion = "0.3.0" [workspace] members = [ - "./crates/fuzz", + "./crates/fuzz-utils", "./crates/macro", "./crates/tests", "./crates/tests-utils", diff --git a/benches/benches.rs b/benches/benches.rs index aa54c971..3735a51d 100644 --- a/benches/benches.rs +++ b/benches/benches.rs @@ -10,7 +10,7 @@ fn criterion_benchmark(c: &mut Criterion) { let input_wasm = black_box(input_wasm); let mut module = Module::from_buffer(input_wasm).unwrap(); walrus::passes::gc::run(&mut module); - let output_wasm = module.emit_wasm().unwrap(); + let output_wasm = module.emit_wasm(); black_box(output_wasm); }); }), diff --git a/crates/fuzz/Cargo.toml b/crates/fuzz-utils/Cargo.toml similarity index 84% rename from crates/fuzz/Cargo.toml rename to crates/fuzz-utils/Cargo.toml index 0cf4b678..804857d1 100644 --- a/crates/fuzz/Cargo.toml +++ b/crates/fuzz-utils/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["Nick Fitzgerald "] edition = "2018" -name = "walrus-fuzz" +name = "walrus-fuzz-utils" version = "0.1.0" publish = false @@ -16,3 +16,6 @@ path = "../.." [dependencies.walrus-tests-utils] path = "../tests-utils" + +[dev-dependencies] +bufrng = "1.0.1" diff --git a/crates/fuzz/before-after.sh b/crates/fuzz-utils/before-after.sh similarity index 100% rename from crates/fuzz/before-after.sh rename to crates/fuzz-utils/before-after.sh diff --git a/crates/fuzz/src/lib.rs b/crates/fuzz-utils/src/lib.rs similarity index 74% rename from crates/fuzz/src/lib.rs rename to crates/fuzz-utils/src/lib.rs index 178d8688..e363e8d4 100755 --- a/crates/fuzz/src/lib.rs +++ b/crates/fuzz-utils/src/lib.rs @@ -3,7 +3,7 @@ #![deny(missing_docs)] use failure::ResultExt; -use rand::{Rng, SeedableRng}; +use rand::{rngs::SmallRng, Rng, SeedableRng}; use std::cmp; use std::fmt; use std::fs; @@ -25,20 +25,28 @@ pub trait TestCaseGenerator { /// The name of this test case generator. const NAME: &'static str; - /// Generate a string of WAT deterministically using the given RNG seed and - /// fuel. - fn generate(seed: u64, fuel: usize) -> String; + /// Generate a string of WAT deterministically using the given RNG and fuel. + fn generate(rng: &mut impl Rng, fuel: usize) -> String; } /// Configuration for fuzzing. -pub struct Config { +pub struct Config +where + G: TestCaseGenerator, + R: Rng, +{ _generator: PhantomData, + rng: R, fuel: usize, timeout: u64, scratch: tempfile::NamedTempFile, } -impl Config { +impl Config +where + G: TestCaseGenerator, + R: Rng, +{ /// The default fuel level. pub const DEFAULT_FUEL: usize = 64; @@ -46,7 +54,7 @@ impl Config { pub const DEFAULT_TIMEOUT_SECS: u64 = 5; /// Construct a new fuzzing configuration. - pub fn new() -> Config { + pub fn new(rng: R) -> Config { static INIT_LOGS: std::sync::Once = std::sync::Once::new(); INIT_LOGS.call_once(|| { env_logger::init(); @@ -66,6 +74,7 @@ impl Config { Config { _generator: PhantomData, + rng, fuel, timeout, scratch, @@ -75,19 +84,19 @@ impl Config { /// Set the fuel level. /// /// `fuel` must be greater than zero. - pub fn set_fuel(mut self, fuel: usize) -> Config { + pub fn set_fuel(mut self, fuel: usize) -> Config { assert!(fuel > 0); self.fuel = fuel; self } - fn gen_wat(&self, seed: u64) -> String { - G::generate(seed, self.fuel) + fn gen_wat(&mut self) -> String { + G::generate(&mut self.rng, self.fuel) } fn wat2wasm(&self, wat: &str) -> Result> { fs::write(self.scratch.path(), wat).context("failed to write to scratch file")?; - wat2wasm(self.scratch.path()) + wat2wasm(self.scratch.path(), &[]) } fn interp(&self, wasm: &[u8]) -> Result { @@ -98,13 +107,11 @@ impl Config { fn round_trip_through_walrus(&self, wasm: &[u8]) -> Result> { let module = walrus::Module::from_buffer(&wasm).context("walrus failed to parse the wasm buffer")?; - let buf = module - .emit_wasm() - .context("walrus failed to serialize a module to wasm")?; + let buf = module.emit_wasm(); Ok(buf) } - fn run_one(&self, wat: &str) -> Result<()> { + fn test_wat(&self, wat: &str) -> Result<()> { let wasm = self.wat2wasm(&wat)?; let expected = self.interp(&wasm)?; @@ -124,21 +131,27 @@ impl Config { .into()) } - /// Generate a wasm file and then compare its output in the reference + /// Generate a single wasm file and then compare its output in the reference /// interpreter before and after round tripping it through `walrus`. /// + /// Does not attempt to reduce any failing test cases. + pub fn run_one(&mut self) -> Result<()> { + let wat = self.gen_wat(); + self.test_wat(&wat) + .with_context(|_| format!("wat = {}", wat))?; + Ok(()) + } + + /// Generate and test as many wasm files as we can within the configured + /// timeout budget. + /// /// Returns the reduced failing test case, if any. pub fn run(&mut self) -> Result<()> { let start = time::Instant::now(); let timeout = time::Duration::from_secs(self.timeout); - let mut seed = rand::thread_rng().gen(); let mut failing = Ok(()); loop { - let wat = self.gen_wat(seed); - match self - .run_one(&wat) - .with_context(|_| format!("wat = {}", wat)) - { + match self.run_one() { Ok(()) => { // We reduced fuel as far as we could, so return the last // failing test case. @@ -152,14 +165,12 @@ impl Config { return Ok(()); } - // This RNG seed did not produce a failing test case, so choose - // a new one. - seed = rand::thread_rng().gen(); + // This did not produce a failing test case, so generate a + // new one. continue; } Err(e) => { - let e: failure::Error = e.into(); print_err(&e); failing = Err(e); @@ -218,7 +229,7 @@ Here is a standalone test case: ----------------8<----------------8<----------------8<---------------- #[test] fn test_name() {{ - walrus_fuzz::assert_round_trip_execution_is_same::<{generator}>(\"\\ + walrus_fuzz::assert_round_trip_execution_is_same(\"\\ {wat}\"); }} ----------------8<----------------8<----------------8<---------------- @@ -226,7 +237,6 @@ fn test_name() {{ wat = self.wat, before = self.expected, after = self.actual, - generator = self.generator, ) } } @@ -235,25 +245,24 @@ impl std::error::Error for FailingTestCase {} /// Assert that the given WAT has the same execution trace before and after /// round tripping it through walrus. -pub fn assert_round_trip_execution_is_same(wat: &str) { - let config = Config::::new(); - if let Err(e) = config.run_one(wat) { +pub fn assert_round_trip_execution_is_same(wat: &str) { + let config = Config::::new(SmallRng::seed_from_u64(0)); + if let Err(e) = config.test_wat(wat) { print_err(&e); panic!("round trip execution is not the same!"); } } /// A simple WAT generator. -pub struct WatGen { - rng: rand::rngs::SmallRng, +pub struct WatGen { + rng: R, wat: String, } -impl TestCaseGenerator for WatGen { +impl TestCaseGenerator for WatGen { const NAME: &'static str = "WatGen"; - fn generate(seed: u64, fuel: usize) -> String { - let rng = rand::rngs::SmallRng::seed_from_u64(seed); + fn generate(rng: &mut impl Rng, fuel: usize) -> String { let wat = String::new(); let mut g = WatGen { rng, wat }; g.prefix(); @@ -263,7 +272,7 @@ impl TestCaseGenerator for WatGen { } } -impl WatGen { +impl WatGen { fn prefix(&mut self) { self.wat.push_str( "\ @@ -374,8 +383,9 @@ pub struct WasmOptTtf; impl TestCaseGenerator for WasmOptTtf { const NAME: &'static str = "WasmOptTtf"; - fn generate(seed: u64, fuel: usize) -> String { - let mut rng = rand::rngs::SmallRng::seed_from_u64(seed); + fn generate(rng: &mut impl Rng, fuel: usize) -> String { + // The wat we generated in the last iteration of the loop below, if any. + let mut last_wat = None; loop { let input: Vec = (0..fuel).map(|_| rng.gen()).collect(); @@ -385,6 +395,15 @@ impl TestCaseGenerator for WasmOptTtf { let wat = match walrus_tests_utils::wasm_opt(input_tmp.path(), vec!["-ttf", "--emit-text"]) { + Ok(ref w) if Some(w) == last_wat.as_ref() => { + // We're stuck in a loop generating the same wat that + // `wat2wasm` can't handle over and over. This is + // typically because we're using an RNG that is derived + // from some fuzzer's output, and it is yielding all + // zeros or something. Just return the most basic wat + // module. + return "(module)".to_string(); + } Ok(w) => w, Err(e) => { // Sometimes `wasm-opt -ttf` fails to generate wasm @@ -400,15 +419,24 @@ impl TestCaseGenerator for WasmOptTtf { // Only generate programs that wat2wasm can handle. let tmp = tempfile::NamedTempFile::new().unwrap(); fs::write(tmp.path(), &wat).unwrap(); - wat2wasm(tmp.path()).is_ok() + wat2wasm(tmp.path(), &[]).is_err() } { - return String::from_utf8(wat).unwrap(); + eprintln!( + "Warning: `wasm-opt -fff` generated wat that wat2wasm cannot handle; \ + skipping. wat =\n{}", + String::from_utf8_lossy(&wat) + ); + last_wat = Some(wat); + continue; } + + return String::from_utf8(wat).unwrap(); } } } -fn print_err(e: &failure::Error) { +/// Print a `failure::Error` with its chain. +pub fn print_err(e: &failure::Error) { eprintln!("Error:"); for c in e.iter_chain() { eprintln!(" - {}", c); @@ -418,6 +446,7 @@ fn print_err(e: &failure::Error) { #[cfg(test)] mod tests { use super::*; + use rand::rngs::SmallRng; fn get_timeout() -> Option { use std::str::FromStr; @@ -428,7 +457,9 @@ mod tests { #[test] fn watgen_fuzz() { - let mut config = Config::::new(); + let mut config = Config::, SmallRng>::new(SmallRng::seed_from_u64( + rand::thread_rng().gen(), + )); if let Some(t) = get_timeout() { config.timeout = t; } @@ -440,7 +471,8 @@ mod tests { #[test] fn wasm_opt_ttf_fuzz() { - let mut config = Config::::new(); + let mut config = + Config::::new(SmallRng::seed_from_u64(rand::thread_rng().gen())); if let Some(t) = get_timeout() { config.timeout = t; } @@ -452,7 +484,7 @@ mod tests { #[test] fn fuzz0() { - super::assert_round_trip_execution_is_same::( + super::assert_round_trip_execution_is_same( r#" (module (memory 1) @@ -460,7 +492,29 @@ mod tests { (export "" (func $b)) (func $b data.drop 0)) - "#, + "#, ); } + + #[test] + fn fuzz1() { + super::assert_round_trip_execution_is_same( + r#" + (module + (func (result i32) (local i32) + local.get 0)) + "#, + ); + } + + #[test] + fn fuzz2() { + // This was causing us to infinite loop in `WasmOptTtf::generate`. + let rng = bufrng::BufRng::new(&[0]); + let mut config = Config::::new(rng).set_fuel(1); + if let Err(e) = config.run_one() { + print_err(&e); + panic!("Found an error! {}", e); + } + } } diff --git a/crates/tests-utils/src/lib.rs b/crates/tests-utils/src/lib.rs index 7055e007..a2529f97 100644 --- a/crates/tests-utils/src/lib.rs +++ b/crates/tests-utils/src/lib.rs @@ -12,6 +12,7 @@ pub const FEATURES: &[&str] = &[ "--enable-bulk-memory", "--enable-reference-types", "--enable-simd", + "--enable-multi-value", // TODO // "--enable-saturating-float-to-int", // "--enable-sign-extension", @@ -35,7 +36,7 @@ fn require_wat2wasm() { } /// Compile the `.wat` file at the given path into a `.wasm`. -pub fn wat2wasm(path: &Path) -> Result> { +pub fn wat2wasm(path: &Path, extra_args: &[&str]) -> Result> { static CHECK: Once = Once::new(); CHECK.call_once(require_wat2wasm); @@ -49,7 +50,8 @@ pub fn wat2wasm(path: &Path) -> Result> { .args(FEATURES) .arg("--debug-names") .arg("-o") - .arg(file.path()); + .arg(file.path()) + .args(extra_args); println!("running: {:?}", cmd); let output = cmd.output().context("could not spawn wat2wasm")?; diff --git a/crates/tests/build.rs b/crates/tests/build.rs index 453c8eb3..12f6806a 100644 --- a/crates/tests/build.rs +++ b/crates/tests/build.rs @@ -57,4 +57,5 @@ fn main() { generate_tests("round_trip"); generate_tests("spec-tests"); generate_tests("function_imports"); + generate_tests("invalid"); } diff --git a/crates/tests/tests/custom_sections.rs b/crates/tests/tests/custom_sections.rs index 6d677b8a..9ccd65ab 100644 --- a/crates/tests/tests/custom_sections.rs +++ b/crates/tests/tests/custom_sections.rs @@ -50,7 +50,7 @@ fn round_trip_unkown_custom_sections() { [(world_id.into(), world.data(&indices))] ); - let wasm = module.emit_wasm().unwrap(); + let wasm = module.emit_wasm(); let mut module = config.parse(&wasm).unwrap(); let world_round_tripped = module.customs.remove_raw("hello").unwrap(); @@ -60,6 +60,6 @@ fn round_trip_unkown_custom_sections() { assert_eq!(new_world.data(&indices), world.data(&indices)); module.customs.add(new_world); - let new_wasm = module.emit_wasm().unwrap(); + let new_wasm = module.emit_wasm(); assert_eq!(wasm, new_wasm); } diff --git a/crates/tests/tests/function_imports.rs b/crates/tests/tests/function_imports.rs index d3c70e60..8da7440d 100644 --- a/crates/tests/tests/function_imports.rs +++ b/crates/tests/tests/function_imports.rs @@ -7,7 +7,7 @@ fn run(wat_path: &Path) -> Result<(), failure::Error> { env_logger::init(); }); - let wasm = wat2wasm(wat_path)?; + let wasm = wat2wasm(wat_path, &[])?; let module = walrus::Module::from_buffer(&wasm)?; assert!(module.imports.find("doggo", "husky").is_some()); diff --git a/crates/tests/tests/invalid.rs b/crates/tests/tests/invalid.rs new file mode 100644 index 00000000..14372e25 --- /dev/null +++ b/crates/tests/tests/invalid.rs @@ -0,0 +1,20 @@ +use std::path::Path; +use std::sync::Once; + +fn run(wat: &Path) -> Result<(), failure::Error> { + static INIT_LOGS: Once = Once::new(); + INIT_LOGS.call_once(|| { + env_logger::init(); + }); + + let wasm = walrus_tests_utils::wat2wasm(wat, &["--no-check"])?; + + // NB: reading the module will do the validation. + if let Ok(_) = walrus::Module::from_buffer(&wasm) { + failure::bail!("expected {} to be invalid, but it was valid", wat.display()); + } + + Ok(()) +} + +include!(concat!(env!("OUT_DIR"), "/invalid.rs")); diff --git a/crates/tests/tests/invalid/multi-value-0.wat b/crates/tests/tests/invalid/multi-value-0.wat new file mode 100644 index 00000000..62d9308d --- /dev/null +++ b/crates/tests/tests/invalid/multi-value-0.wat @@ -0,0 +1,5 @@ +(module + (func (export "i64.dup") (param i64) (result i64 i64) + ;; With this commented out, there's only a single i64 on the stack. + ;; get_local 0 + get_local 0)) diff --git a/crates/tests/tests/invalid/multi-value-1.wat b/crates/tests/tests/invalid/multi-value-1.wat new file mode 100644 index 00000000..f27cfdbf --- /dev/null +++ b/crates/tests/tests/invalid/multi-value-1.wat @@ -0,0 +1,6 @@ +(module + (func (export "i64.dup") (param i64) (result i64 i64) + ;; Too many i64s on the stack. + get_local 0 + get_local 0 + get_local 0)) diff --git a/crates/tests/tests/invalid/multi-value-2.wat b/crates/tests/tests/invalid/multi-value-2.wat new file mode 100644 index 00000000..1192ddc7 --- /dev/null +++ b/crates/tests/tests/invalid/multi-value-2.wat @@ -0,0 +1,7 @@ +(module + (func (export "multiBlock") (param i64 i64) (result i64 i64 i64) + ;; With this commented out, not enough i64s on the stack for the block. + ;; (local.get 1) + (local.get 0) + (block (param i64 i64) (result i64 i64 i64) + (i64.const 1234)))) diff --git a/crates/tests/tests/invalid/multi-value-3.wat b/crates/tests/tests/invalid/multi-value-3.wat new file mode 100644 index 00000000..22644400 --- /dev/null +++ b/crates/tests/tests/invalid/multi-value-3.wat @@ -0,0 +1,8 @@ +(module + (func (export "multiBlock") (param i64 i64) (result i64 i64 i64) + ;; Too many i64s on the stack for the block. + (local.get 0) + (local.get 1) + (local.get 0) + (block (param i64 i64) (result i64 i64 i64) + (i64.const 1234)))) diff --git a/crates/tests/tests/round_trip.rs b/crates/tests/tests/round_trip.rs index f5595d19..ac80ffbd 100644 --- a/crates/tests/tests/round_trip.rs +++ b/crates/tests/tests/round_trip.rs @@ -8,7 +8,7 @@ fn run(wat_path: &Path) -> Result<(), failure::Error> { env_logger::init(); }); - let wasm = wat2wasm(wat_path)?; + let wasm = wat2wasm(wat_path, &[])?; let mut module = walrus::Module::from_buffer(&wasm)?; if env::var("WALRUS_TESTS_DOT").is_ok() { diff --git a/crates/tests/tests/round_trip/entry-block-type.wat b/crates/tests/tests/round_trip/entry-block-type.wat new file mode 100644 index 00000000..71e501a2 --- /dev/null +++ b/crates/tests/tests/round_trip/entry-block-type.wat @@ -0,0 +1,16 @@ +;; The function entry's `InstrSeq` implicitly has type `[] -> [i64 i64]` but +;; that type should *NOT* appear in the emitted Type section. + +(module + (func (export "multiLoop") (param i64 i64) (result i64 i64) + (local.get 1) + (local.get 0))) + +(; CHECK-ALL: + (module + (type (;0;) (func (param i64 i64) (result i64 i64))) + (func (;0;) (type 0) (param i64 i64) (result i64 i64) + local.get 1 + local.get 0) + (export "multiLoop" (func 0))) +;) diff --git a/crates/tests/tests/round_trip/fac-multi-value.wat b/crates/tests/tests/round_trip/fac-multi-value.wat new file mode 100644 index 00000000..333a194c --- /dev/null +++ b/crates/tests/tests/round_trip/fac-multi-value.wat @@ -0,0 +1,52 @@ +(module + ;; Iterative factorial without locals. + (func $pick0 (param i64) (result i64 i64) + (get_local 0) (get_local 0) + ) + (func $pick1 (param i64 i64) (result i64 i64 i64) + (get_local 0) (get_local 1) (get_local 0) + ) + (func (export "fac-ssa") (param i64) (result i64) + (i64.const 1) (get_local 0) + (loop $l (param i64 i64) (result i64) + (call $pick1) (call $pick1) (i64.mul) + (call $pick1) (i64.const 1) (i64.sub) + (call $pick0) (i64.const 0) (i64.gt_u) + (br_if $l) + (drop) (return) + ) + ) +) + +(; CHECK-ALL: + (module + (type (;0;) (func (param i64) (result i64))) + (type (;1;) (func (param i64) (result i64 i64))) + (type (;2;) (func (param i64 i64) (result i64))) + (type (;3;) (func (param i64 i64) (result i64 i64 i64))) + (func (;0;) (type 0) (param i64) (result i64) + i64.const 1 + local.get 0 + loop (param i64 i64) (result i64) ;; label = @1 + call $pick1 + call $pick1 + i64.mul + call $pick1 + i64.const 1 + i64.sub + call $pick0 + i64.const 0 + i64.gt_u + br_if 0 (;@1;) + drop + return + end) + (func $pick1 (type 3) (param i64 i64) (result i64 i64 i64) + local.get 0 + local.get 1 + local.get 0) + (func $pick0 (type 1) (param i64) (result i64 i64) + local.get 0 + local.get 0) + (export "fac-ssa" (func 0))) +;) diff --git a/crates/tests/tests/round_trip/gc_unused_block.wat b/crates/tests/tests/round_trip/gc_unused_block.wat index 9e4205fd..1214f2c6 100644 --- a/crates/tests/tests/round_trip/gc_unused_block.wat +++ b/crates/tests/tests/round_trip/gc_unused_block.wat @@ -11,8 +11,6 @@ i32.const 42) (export "f" (func $f))) -;; CHECK-ALL: (module - (; CHECK-ALL: (module (type (;0;) (func (result i32))) diff --git a/crates/tests/tests/round_trip/multi-0.wat b/crates/tests/tests/round_trip/multi-0.wat new file mode 100644 index 00000000..ff0abd6d --- /dev/null +++ b/crates/tests/tests/round_trip/multi-0.wat @@ -0,0 +1,12 @@ +(module + (func (export "i64.dup") (param i64) (result i64 i64) + (get_local 0) (get_local 0))) + +(; CHECK-ALL: + (module + (type (;0;) (func (param i64) (result i64 i64))) + (func (;0;) (type 0) (param i64) (result i64 i64) + local.get 0 + local.get 0) + (export "i64.dup" (func 0))) +;) diff --git a/crates/tests/tests/round_trip/multi-1.wat b/crates/tests/tests/round_trip/multi-1.wat new file mode 100644 index 00000000..ff74ba6c --- /dev/null +++ b/crates/tests/tests/round_trip/multi-1.wat @@ -0,0 +1,18 @@ +(module + (func (export "multiBlock") (param i64 i64) (result i64 i64 i64) + (local.get 1) + (local.get 0) + (block (param i64 i64) (result i64 i64 i64) + (i64.const 1234)))) + +(; CHECK-ALL: + (module + (type (;0;) (func (param i64 i64) (result i64 i64 i64))) + (func (;0;) (type 0) (param i64 i64) (result i64 i64 i64) + local.get 1 + local.get 0 + block (param i64 i64) (result i64 i64 i64) ;; label = @1 + i64.const 1234 + end) + (export "multiBlock" (func 0))) +;) diff --git a/crates/tests/tests/round_trip/multi-2.wat b/crates/tests/tests/round_trip/multi-2.wat new file mode 100644 index 00000000..f878b8f6 --- /dev/null +++ b/crates/tests/tests/round_trip/multi-2.wat @@ -0,0 +1,18 @@ +(module + (func (export "multiLoop") (param i64 i64) (result i64 i64) + (local.get 1) + (local.get 0) + (loop (param i64 i64) (result i64 i64) + return))) + +(; CHECK-ALL: + (module + (type (;0;) (func (param i64 i64) (result i64 i64))) + (func (;0;) (type 0) (param i64 i64) (result i64 i64) + local.get 1 + local.get 0 + loop (param i64 i64) (result i64 i64) ;; label = @1 + return + end) + (export "multiLoop" (func 0))) +;) diff --git a/crates/tests/tests/round_trip/multi-3.wat b/crates/tests/tests/round_trip/multi-3.wat new file mode 100644 index 00000000..dff03456 --- /dev/null +++ b/crates/tests/tests/round_trip/multi-3.wat @@ -0,0 +1,31 @@ +(module + (func (export "multiLoop") (param i32 i64 i64) (result i64 i64) + (local.get 2) + (local.get 1) + (local.get 0) + (if (param i64 i64) (result i64 i64) + (then return) + (else + (drop) + (drop) + (i64.const 0) + (i64.const 0))))) + +(; CHECK-ALL: + (module + (type (;0;) (func (param i32 i64 i64) (result i64 i64))) + (type (;1;) (func (param i64 i64) (result i64 i64))) + (func (;0;) (type 0) (param i32 i64 i64) (result i64 i64) + local.get 2 + local.get 1 + local.get 0 + if (param i64 i64) (result i64 i64) ;; label = @1 + return + else + drop + drop + i64.const 0 + i64.const 0 + end) + (export "multiLoop" (func 0))) +;) diff --git a/crates/tests/tests/round_trip/not-tree-like.wat b/crates/tests/tests/round_trip/not-tree-like.wat index 7f4f97e7..13574139 100644 --- a/crates/tests/tests/round_trip/not-tree-like.wat +++ b/crates/tests/tests/round_trip/not-tree-like.wat @@ -9,19 +9,19 @@ i32.add )) -;; CHECK: (module -;; NEXT: (type (;0;) (func (param i32) (result i32))) -;; NEXT: (type (;1;) (func (result i32))) -;; NEXT: (import "env" "blackbox" (func $blackbox (type 0))) -;; NEXT: (func (;1;) (type 1) (result i32) -;; NEXT: i32.const 1 -;; NEXT: call $blackbox -;; NEXT: i32.const 2 -;; NEXT: call $blackbox -;; NEXT: drop -;; NEXT: i32.const 3 -;; NEXT: call $blackbox -;; NEXT: i32.add) -;; NEXT: (export "$f" (func 1))) - - +(; CHECK-ALL: + (module + (type (;0;) (func (result i32))) + (type (;1;) (func (param i32) (result i32))) + (import "env" "blackbox" (func $blackbox (type 1))) + (func (;1;) (type 0) (result i32) + i32.const 1 + call $blackbox + i32.const 2 + call $blackbox + drop + i32.const 3 + call $blackbox + i32.add) + (export "$f" (func 1))) +;) diff --git a/crates/tests/tests/round_trip/simd.wat b/crates/tests/tests/round_trip/simd.wat index 0821be10..eecb260f 100644 --- a/crates/tests/tests/round_trip/simd.wat +++ b/crates/tests/tests/round_trip/simd.wat @@ -528,19 +528,19 @@ (type (;0;) (func (result v128))) (type (;1;) (func (param i32) (result v128))) (type (;2;) (func (param i32 v128))) - (type (;3;) (func (param v128) (result i32))) - (type (;4;) (func (param v128 i32) (result v128))) - (type (;5;) (func (param i64) (result v128))) - (type (;6;) (func (param v128) (result i64))) - (type (;7;) (func (param v128 i64) (result v128))) - (type (;8;) (func (param f32) (result v128))) - (type (;9;) (func (param v128) (result f32))) - (type (;10;) (func (param v128 f32) (result v128))) - (type (;11;) (func (param f64) (result v128))) - (type (;12;) (func (param v128) (result f64))) - (type (;13;) (func (param v128 f64) (result v128))) - (type (;14;) (func (param v128 v128) (result v128))) - (type (;15;) (func (param v128) (result v128))) + (type (;3;) (func (param i64) (result v128))) + (type (;4;) (func (param f32) (result v128))) + (type (;5;) (func (param f64) (result v128))) + (type (;6;) (func (param v128) (result i32))) + (type (;7;) (func (param v128) (result i64))) + (type (;8;) (func (param v128) (result f32))) + (type (;9;) (func (param v128) (result f64))) + (type (;10;) (func (param v128) (result v128))) + (type (;11;) (func (param v128 i32) (result v128))) + (type (;12;) (func (param v128 i64) (result v128))) + (type (;13;) (func (param v128 f32) (result v128))) + (type (;14;) (func (param v128 f64) (result v128))) + (type (;15;) (func (param v128 v128) (result v128))) (type (;16;) (func (param v128 v128 v128) (result v128))) (func $v128.bitselect (type 16) (param v128 v128 v128) (result v128) local.get 0 @@ -551,359 +551,359 @@ local.get 0 local.get 1 v128.store) - (func $i8x16.replace_lane (type 4) (param v128 i32) (result v128) + (func $i8x16.replace_lane (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i8x16.replace_lane 2 ) - (func $i16x8.replace_lane (type 4) (param v128 i32) (result v128) + (func $i16x8.replace_lane (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i16x8.replace_lane 2 ) - (func $i32x4.replace_lane (type 4) (param v128 i32) (result v128) + (func $i32x4.replace_lane (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i32x4.replace_lane 2 ) - (func $i64x2.replace_lane (type 7) (param v128 i64) (result v128) + (func $i64x2.replace_lane (type 12) (param v128 i64) (result v128) local.get 0 local.get 1 i64x2.replace_lane 0 ) - (func $f32x4.replace_lane (type 10) (param v128 f32) (result v128) + (func $f32x4.replace_lane (type 13) (param v128 f32) (result v128) local.get 0 local.get 1 f32x4.replace_lane 2 ) - (func $f64x2.replace_lane (type 13) (param v128 f64) (result v128) + (func $f64x2.replace_lane (type 14) (param v128 f64) (result v128) local.get 0 local.get 1 f64x2.replace_lane 0 ) - (func $i8x16.eq (type 14) (param v128 v128) (result v128) + (func $i8x16.eq (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.eq) - (func $i8x16.lt_s (type 14) (param v128 v128) (result v128) + (func $i8x16.lt_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.lt_s) - (func $i8x16.lt_u (type 14) (param v128 v128) (result v128) + (func $i8x16.lt_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.lt_u) - (func $i8x16.gt_s (type 14) (param v128 v128) (result v128) + (func $i8x16.gt_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.gt_s) - (func $i8x16.gt_u (type 14) (param v128 v128) (result v128) + (func $i8x16.gt_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.gt_u) - (func $i8x16.le_s (type 14) (param v128 v128) (result v128) + (func $i8x16.le_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.le_s) - (func $i8x16.le_u (type 14) (param v128 v128) (result v128) + (func $i8x16.le_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.le_u) - (func $i8x16.ge_s (type 14) (param v128 v128) (result v128) + (func $i8x16.ge_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.ge_s) - (func $i8x16.ge_u (type 14) (param v128 v128) (result v128) + (func $i8x16.ge_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.ge_u) - (func $i16x8.eq (type 14) (param v128 v128) (result v128) + (func $i16x8.eq (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.eq) - (func $i16x8.lt_s (type 14) (param v128 v128) (result v128) + (func $i16x8.lt_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.lt_s) - (func $i16x8.lt_u (type 14) (param v128 v128) (result v128) + (func $i16x8.lt_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.lt_u) - (func $i16x8.gt_s (type 14) (param v128 v128) (result v128) + (func $i16x8.gt_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.gt_s) - (func $i16x8.gt_u (type 14) (param v128 v128) (result v128) + (func $i16x8.gt_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.gt_u) - (func $i16x8.le_s (type 14) (param v128 v128) (result v128) + (func $i16x8.le_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.le_s) - (func $i16x8.le_u (type 14) (param v128 v128) (result v128) + (func $i16x8.le_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.le_u) - (func $i16x8.ge_s (type 14) (param v128 v128) (result v128) + (func $i16x8.ge_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.ge_s) - (func $i16x8.ge_u (type 14) (param v128 v128) (result v128) + (func $i16x8.ge_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.ge_u) - (func $i32x4.eq (type 14) (param v128 v128) (result v128) + (func $i32x4.eq (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.eq) - (func $i32x4.lt_s (type 14) (param v128 v128) (result v128) + (func $i32x4.lt_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.lt_s) - (func $i32x4.lt_u (type 14) (param v128 v128) (result v128) + (func $i32x4.lt_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.lt_u) - (func $i32x4.gt_s (type 14) (param v128 v128) (result v128) + (func $i32x4.gt_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.gt_s) - (func $i32x4.gt_u (type 14) (param v128 v128) (result v128) + (func $i32x4.gt_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.gt_u) - (func $i32x4.le_s (type 14) (param v128 v128) (result v128) + (func $i32x4.le_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.le_s) - (func $i32x4.le_u (type 14) (param v128 v128) (result v128) + (func $i32x4.le_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.le_u) - (func $i32x4.ge_s (type 14) (param v128 v128) (result v128) + (func $i32x4.ge_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.ge_s) - (func $i32x4.ge_u (type 14) (param v128 v128) (result v128) + (func $i32x4.ge_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.ge_u) - (func $f32x4.eq (type 14) (param v128 v128) (result v128) + (func $f32x4.eq (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.eq) - (func $f32x4.lt (type 14) (param v128 v128) (result v128) + (func $f32x4.lt (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.lt) - (func $f32x4.gt (type 14) (param v128 v128) (result v128) + (func $f32x4.gt (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.gt) - (func $f32x4.le (type 14) (param v128 v128) (result v128) + (func $f32x4.le (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.le) - (func $f32x4.ge (type 14) (param v128 v128) (result v128) + (func $f32x4.ge (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.ge) - (func $f64x2.eq (type 14) (param v128 v128) (result v128) + (func $f64x2.eq (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.eq) - (func $f64x2.lt (type 14) (param v128 v128) (result v128) + (func $f64x2.lt (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.lt) - (func $f64x2.gt (type 14) (param v128 v128) (result v128) + (func $f64x2.gt (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.gt) - (func $f64x2.le (type 14) (param v128 v128) (result v128) + (func $f64x2.le (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.le) - (func $f64x2.ge (type 14) (param v128 v128) (result v128) + (func $f64x2.ge (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.ge) - (func $v128.and (type 14) (param v128 v128) (result v128) + (func $v128.and (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 v128.and) - (func $v128.or (type 14) (param v128 v128) (result v128) + (func $v128.or (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 v128.or) - (func $v128.xor (type 14) (param v128 v128) (result v128) + (func $v128.xor (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 v128.xor) - (func $i8x16.shl (type 4) (param v128 i32) (result v128) + (func $i8x16.shl (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i8x16.shl) - (func $i8x16.shr_s (type 4) (param v128 i32) (result v128) + (func $i8x16.shr_s (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i8x16.shr_s) - (func $i8x16.shr_u (type 4) (param v128 i32) (result v128) + (func $i8x16.shr_u (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i8x16.shr_u) - (func $i8x16.add (type 14) (param v128 v128) (result v128) + (func $i8x16.add (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.add) - (func $i8x16.add_saturate_u (type 14) (param v128 v128) (result v128) + (func $i8x16.add_saturate_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.add_saturate_u) - (func $i8x16.add_saturate_s (type 14) (param v128 v128) (result v128) + (func $i8x16.add_saturate_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.add_saturate_s) - (func $i8x16.sub (type 14) (param v128 v128) (result v128) + (func $i8x16.sub (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.sub) - (func $i8x16.sub_saturate_u (type 14) (param v128 v128) (result v128) + (func $i8x16.sub_saturate_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.sub_saturate_u) - (func $i8x16.sub_saturate_s (type 14) (param v128 v128) (result v128) + (func $i8x16.sub_saturate_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.sub_saturate_s) - (func $i8x16.mul (type 14) (param v128 v128) (result v128) + (func $i8x16.mul (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.mul) - (func $i16x8.shl (type 4) (param v128 i32) (result v128) + (func $i16x8.shl (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i16x8.shl) - (func $i16x8.shr_s (type 4) (param v128 i32) (result v128) + (func $i16x8.shr_s (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i16x8.shr_s) - (func $i16x8.shr_u (type 4) (param v128 i32) (result v128) + (func $i16x8.shr_u (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i16x8.shr_u) - (func $i16x8.add (type 14) (param v128 v128) (result v128) + (func $i16x8.add (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.add) - (func $i16x8.add_saturate_u (type 14) (param v128 v128) (result v128) + (func $i16x8.add_saturate_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.add_saturate_u) - (func $i16x8.add_saturate_s (type 14) (param v128 v128) (result v128) + (func $i16x8.add_saturate_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.add_saturate_s) - (func $i16x8.sub (type 14) (param v128 v128) (result v128) + (func $i16x8.sub (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.sub) - (func $i16x8.sub_saturate_u (type 14) (param v128 v128) (result v128) + (func $i16x8.sub_saturate_u (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.sub_saturate_u) - (func $i16x8.sub_saturate_s (type 14) (param v128 v128) (result v128) + (func $i16x8.sub_saturate_s (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.sub_saturate_s) - (func $i16x8.mul (type 14) (param v128 v128) (result v128) + (func $i16x8.mul (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i16x8.mul) - (func $i32x4.shl (type 4) (param v128 i32) (result v128) + (func $i32x4.shl (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i32x4.shl) - (func $i32x4.shr_s (type 4) (param v128 i32) (result v128) + (func $i32x4.shr_s (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i32x4.shr_s) - (func $i32x4.shr_u (type 4) (param v128 i32) (result v128) + (func $i32x4.shr_u (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i32x4.shr_u) - (func $i32x4.add (type 14) (param v128 v128) (result v128) + (func $i32x4.add (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.add) - (func $i32x4.sub (type 14) (param v128 v128) (result v128) + (func $i32x4.sub (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.sub) - (func $i32x4.mul (type 14) (param v128 v128) (result v128) + (func $i32x4.mul (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i32x4.mul) - (func $i64x2.shl (type 4) (param v128 i32) (result v128) + (func $i64x2.shl (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i64x2.shl) - (func $i64x2.shr_s (type 4) (param v128 i32) (result v128) + (func $i64x2.shr_s (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i64x2.shr_s) - (func $i64x2.shr_u (type 4) (param v128 i32) (result v128) + (func $i64x2.shr_u (type 11) (param v128 i32) (result v128) local.get 0 local.get 1 i64x2.shr_u) - (func $i64x2.add (type 14) (param v128 v128) (result v128) + (func $i64x2.add (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i64x2.add) - (func $i64x2.sub (type 14) (param v128 v128) (result v128) + (func $i64x2.sub (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 i64x2.sub) - (func $f32x4.add (type 14) (param v128 v128) (result v128) + (func $f32x4.add (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.add) - (func $f32x4.sub (type 14) (param v128 v128) (result v128) + (func $f32x4.sub (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.sub) - (func $f32x4.mul (type 14) (param v128 v128) (result v128) + (func $f32x4.mul (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.mul) - (func $f32x4.div (type 14) (param v128 v128) (result v128) + (func $f32x4.div (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.div) - (func $f32x4.min (type 14) (param v128 v128) (result v128) + (func $f32x4.min (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.min) - (func $f32x4.max (type 14) (param v128 v128) (result v128) + (func $f32x4.max (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f32x4.max) - (func $f64x2.add (type 14) (param v128 v128) (result v128) + (func $f64x2.add (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.add) - (func $f64x2.sub (type 14) (param v128 v128) (result v128) + (func $f64x2.sub (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.sub) - (func $f64x2.mul (type 14) (param v128 v128) (result v128) + (func $f64x2.mul (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.mul) - (func $f64x2.div (type 14) (param v128 v128) (result v128) + (func $f64x2.div (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.div) - (func $f64x2.min (type 14) (param v128 v128) (result v128) + (func $f64x2.min (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.min) - (func $f64x2.max (type 14) (param v128 v128) (result v128) + (func $f64x2.max (type 15) (param v128 v128) (result v128) local.get 0 local.get 1 f64x2.max) @@ -913,124 +913,124 @@ (func $i8x16.splat (type 1) (param i32) (result v128) local.get 0 i8x16.splat) - (func $i8x16.extract_lane_s (type 3) (param v128) (result i32) + (func $i8x16.extract_lane_s (type 6) (param v128) (result i32) local.get 0 i8x16.extract_lane_s 1 ) - (func $i8x16.extract_lane_u (type 3) (param v128) (result i32) + (func $i8x16.extract_lane_u (type 6) (param v128) (result i32) local.get 0 i8x16.extract_lane_u 2 ) (func $i16x8.splat (type 1) (param i32) (result v128) local.get 0 i16x8.splat) - (func $i16x8.extract_lane_s (type 3) (param v128) (result i32) + (func $i16x8.extract_lane_s (type 6) (param v128) (result i32) local.get 0 i16x8.extract_lane_s 1 ) - (func $i16x8.extract_lane_u (type 3) (param v128) (result i32) + (func $i16x8.extract_lane_u (type 6) (param v128) (result i32) local.get 0 i16x8.extract_lane_u 2 ) (func $i32x4.splat (type 1) (param i32) (result v128) local.get 0 i32x4.splat) - (func $i32x4.extract_lane (type 3) (param v128) (result i32) + (func $i32x4.extract_lane (type 6) (param v128) (result i32) local.get 0 i32x4.extract_lane 1 ) - (func $i64x2.splat (type 5) (param i64) (result v128) + (func $i64x2.splat (type 3) (param i64) (result v128) local.get 0 i64x2.splat) - (func $i64x2.extract_lane (type 6) (param v128) (result i64) + (func $i64x2.extract_lane (type 7) (param v128) (result i64) local.get 0 i64x2.extract_lane 1 ) - (func $f32x4.splat (type 8) (param f32) (result v128) + (func $f32x4.splat (type 4) (param f32) (result v128) local.get 0 f32x4.splat) - (func $f32x4.extract_lane (type 9) (param v128) (result f32) + (func $f32x4.extract_lane (type 8) (param v128) (result f32) local.get 0 f32x4.extract_lane 1 ) - (func $f64x2.splat (type 11) (param f64) (result v128) + (func $f64x2.splat (type 5) (param f64) (result v128) local.get 0 f64x2.splat) - (func $f64x2.extract_lane (type 12) (param v128) (result f64) + (func $f64x2.extract_lane (type 9) (param v128) (result f64) local.get 0 f64x2.extract_lane 1 ) - (func $v128.not (type 15) (param v128) (result v128) + (func $v128.not (type 10) (param v128) (result v128) local.get 0 v128.not) - (func $i8x16.neg (type 15) (param v128) (result v128) + (func $i8x16.neg (type 10) (param v128) (result v128) local.get 0 i8x16.neg) - (func $i8x16.any_true (type 3) (param v128) (result i32) + (func $i8x16.any_true (type 6) (param v128) (result i32) local.get 0 i8x16.any_true) - (func $i8x16.all_true (type 3) (param v128) (result i32) + (func $i8x16.all_true (type 6) (param v128) (result i32) local.get 0 i8x16.all_true) - (func $i16x8.neg (type 15) (param v128) (result v128) + (func $i16x8.neg (type 10) (param v128) (result v128) local.get 0 i16x8.neg) - (func $i16x8.any_true (type 3) (param v128) (result i32) + (func $i16x8.any_true (type 6) (param v128) (result i32) local.get 0 i16x8.any_true) - (func $i16x8.all_true (type 3) (param v128) (result i32) + (func $i16x8.all_true (type 6) (param v128) (result i32) local.get 0 i16x8.all_true) - (func $i32x4.neg (type 15) (param v128) (result v128) + (func $i32x4.neg (type 10) (param v128) (result v128) local.get 0 i32x4.neg) - (func $i32x4.any_true (type 3) (param v128) (result i32) + (func $i32x4.any_true (type 6) (param v128) (result i32) local.get 0 i32x4.any_true) - (func $i32x4.all_true (type 3) (param v128) (result i32) + (func $i32x4.all_true (type 6) (param v128) (result i32) local.get 0 i32x4.all_true) - (func $i64x2.neg (type 15) (param v128) (result v128) + (func $i64x2.neg (type 10) (param v128) (result v128) local.get 0 i64x2.neg) - (func $i64x2.any_true (type 3) (param v128) (result i32) + (func $i64x2.any_true (type 6) (param v128) (result i32) local.get 0 i64x2.any_true) - (func $i64x2.all_true (type 3) (param v128) (result i32) + (func $i64x2.all_true (type 6) (param v128) (result i32) local.get 0 i64x2.all_true) - (func $f32x4.abs (type 15) (param v128) (result v128) + (func $f32x4.abs (type 10) (param v128) (result v128) local.get 0 f32x4.abs) - (func $f32x4.neg (type 15) (param v128) (result v128) + (func $f32x4.neg (type 10) (param v128) (result v128) local.get 0 f32x4.neg) - (func $f32x4.sqrt (type 15) (param v128) (result v128) + (func $f32x4.sqrt (type 10) (param v128) (result v128) local.get 0 f32x4.sqrt) - (func $f64x2.abs (type 15) (param v128) (result v128) + (func $f64x2.abs (type 10) (param v128) (result v128) local.get 0 f64x2.abs) - (func $f64x2.neg (type 15) (param v128) (result v128) + (func $f64x2.neg (type 10) (param v128) (result v128) local.get 0 f64x2.neg) - (func $f64x2.sqrt (type 15) (param v128) (result v128) + (func $f64x2.sqrt (type 10) (param v128) (result v128) local.get 0 f64x2.sqrt) - (func $i32x4_trunc_s_f32x4_sat (type 15) (param v128) (result v128) + (func $i32x4_trunc_s_f32x4_sat (type 10) (param v128) (result v128) local.get 0 i32x4.trunc_sat_f32x4_s) - (func $i32x4_trunc_u_f32x4_sat (type 15) (param v128) (result v128) + (func $i32x4_trunc_u_f32x4_sat (type 10) (param v128) (result v128) local.get 0 i32x4.trunc_sat_f32x4_u) - (func $i64x2_trunc_s_f64x2_sat (type 15) (param v128) (result v128) + (func $i64x2_trunc_s_f64x2_sat (type 10) (param v128) (result v128) local.get 0 i64x2.trunc_sat_f64x2_s) - (func $i64x2_trunc_u_f64x2_sat (type 15) (param v128) (result v128) + (func $i64x2_trunc_u_f64x2_sat (type 10) (param v128) (result v128) local.get 0 i64x2.trunc_sat_f64x2_u) - (func $f32x4.convert_i32x4_s (type 15) (param v128) (result v128) + (func $f32x4.convert_i32x4_s (type 10) (param v128) (result v128) local.get 0 f32x4.convert_i32x4_s) - (func $f32x4.convert_i32x4_u (type 15) (param v128) (result v128) + (func $f32x4.convert_i32x4_u (type 10) (param v128) (result v128) local.get 0 f32x4.convert_i32x4_u) - (func $f64x2.convert_i64x2_s (type 15) (param v128) (result v128) + (func $f64x2.convert_i64x2_s (type 10) (param v128) (result v128) local.get 0 f64x2.convert_i64x2_s) - (func $f64x2.convert_i64x2_u (type 15) (param v128) (result v128) + (func $f64x2.convert_i64x2_u (type 10) (param v128) (result v128) local.get 0 f64x2.convert_i64x2_u) (func $v128.const (type 0) (result v128) diff --git a/crates/tests/tests/spec-tests.rs b/crates/tests/tests/spec-tests.rs index 3fb1b477..5ee8363a 100644 --- a/crates/tests/tests/spec-tests.rs +++ b/crates/tests/tests/spec-tests.rs @@ -73,6 +73,10 @@ fn run(wast: &Path) -> Result<(), failure::Error> { let path = tempdir.path().join(filename); match command["type"].as_str().unwrap() { "assert_invalid" | "assert_malformed" => { + if command["text"].as_str().unwrap() == "invalid result arity" { + // These tests are valid with multi-value! + continue; + } let wasm = fs::read(&path)?; println!("{:?}", command); if config.parse(&wasm).is_ok() { @@ -99,13 +103,11 @@ fn run(wast: &Path) -> Result<(), failure::Error> { let wasm = config .parse(&wasm) .context(format!("error parsing wasm (line {})", line))?; - let wasm1 = wasm - .emit_wasm() - .context(format!("error emitting wasm (line {})", line))?; + let wasm1 = wasm.emit_wasm(); fs::write(&path, &wasm1)?; let wasm2 = config .parse(&wasm1) - .and_then(|m| m.emit_wasm()) + .map(|m| m.emit_wasm()) .context(format!("error re-parsing wasm (line {})", line))?; if wasm1 != wasm2 { panic!("wasm module at line {} isn't deterministic", line); @@ -134,7 +136,7 @@ fn run(wast: &Path) -> Result<(), failure::Error> { walrus::passes::gc::run(&mut module); } - let wasm = module.emit_wasm()?; + let wasm = module.emit_wasm(); fs::write(&file, wasm)?; } diff --git a/crates/tests/tests/valid.rs b/crates/tests/tests/valid.rs index 3c9167c0..db4a6dbc 100644 --- a/crates/tests/tests/valid.rs +++ b/crates/tests/tests/valid.rs @@ -1,23 +1,17 @@ use std::env; use std::path::Path; +use std::sync::Once; fn run(wat: &Path) -> Result<(), failure::Error> { - static INIT_LOGS: std::sync::Once = std::sync::Once::new(); + static INIT_LOGS: Once = Once::new(); INIT_LOGS.call_once(|| { env_logger::init(); }); - let wasm = walrus_tests_utils::wat2wasm(wat)?; - let module = walrus::Module::from_buffer(&wasm)?; + let wasm = walrus_tests_utils::wat2wasm(wat, &[])?; - let local_funcs: Vec<_> = module - .functions() - .filter(|f| match f.kind { - walrus::FunctionKind::Local(_) => true, - _ => false, - }) - .collect(); - assert_eq!(local_funcs.len(), 1); + // NB: reading the module will do the validation. + let module = walrus::Module::from_buffer(&wasm)?; if env::var("WALRUS_TESTS_DOT").is_ok() { module.write_graphviz_dot(wat.with_extension("dot"))?; diff --git a/crates/tests/tests/valid/fac-multi-value.wat b/crates/tests/tests/valid/fac-multi-value.wat new file mode 100644 index 00000000..d6db8902 --- /dev/null +++ b/crates/tests/tests/valid/fac-multi-value.wat @@ -0,0 +1,21 @@ +;; Taken from `spec-tests/proposals/multi-value/fac.wast`. + +(module + ;; Iterative factorial without locals. + (func $pick0 (param i64) (result i64 i64) + (get_local 0) (get_local 0) + ) + (func $pick1 (param i64 i64) (result i64 i64 i64) + (get_local 0) (get_local 1) (get_local 0) + ) + (func (export "fac-ssa") (param i64) (result i64) + (i64.const 1) (get_local 0) + (loop $l (param i64 i64) (result i64) + (call $pick1) (call $pick1) (i64.mul) + (call $pick1) (i64.const 1) (i64.sub) + (call $pick0) (i64.const 0) (i64.gt_u) + (br_if $l) + (drop) (return) + ) + ) +) diff --git a/examples/build-wasm-from-scratch.rs b/examples/build-wasm-from-scratch.rs index dafd7ae4..f6efb684 100644 --- a/examples/build-wasm-from-scratch.rs +++ b/examples/build-wasm-from-scratch.rs @@ -58,9 +58,9 @@ fn main() -> walrus::Result<()> { // (local.set $res (i32.const 1)) .i32_const(1) .local_set(res) - .block(Box::new([]), Box::new([]), |done| { + .block(None, |done| { let done_id = done.id(); - done.loop_(Box::new([]), |loop_| { + done.loop_(None, |loop_| { let loop_id = loop_.id(); loop_ // (call $log (local.get $res)) @@ -71,8 +71,7 @@ fn main() -> walrus::Result<()> { .i32_const(0) .binop(BinaryOp::I32Eq) .if_else( - Box::new([]), - Box::new([]), + None, |then| { // (then (br $done)) then.br(done_id); diff --git a/examples/round-trip.rs b/examples/round-trip.rs index b1062c06..5a556aec 100644 --- a/examples/round-trip.rs +++ b/examples/round-trip.rs @@ -18,7 +18,7 @@ fn try_main() -> Result<(), failure::Error> { failure::format_err!("must provide the input wasm file as the first argument") })?; let m = walrus::Module::from_file(&a)?; - let wasm = m.emit_wasm()?; + let wasm = m.emit_wasm(); if let Some(destination) = std::env::args().nth(2) { std::fs::write(destination, wasm)?; } diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 00000000..572e03bd --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ + +target +corpus +artifacts diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 00000000..2a6bdc04 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "walrus-fuzz" +version = "0.0.1" +authors = ["Automatically generated"] +publish = false +edition = "2018" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +bufrng = "1.0.1" + +[dependencies.walrus] +path = ".." + +[dependencies.walrus-fuzz-utils] +path = "../crates/fuzz-utils" + +[dependencies.libfuzzer-sys] +git = "https://github.com/rust-fuzz/libfuzzer-sys.git" + +# Prevent this from interfering with workspaces. +[workspace] +members = ["."] + +[[bin]] +name = "watgen" +path = "fuzz_targets/watgen.rs" + +[[bin]] +name = "wasm-opt-ttf" +path = "fuzz_targets/wasm-opt-ttf.rs" + +[[bin]] +name = "raw" +path = "fuzz_targets/raw.rs" diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 00000000..ed21496c --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,20 @@ +# Fuzzing Walrus with `cargo fuzz`! + +## Prerequisites + +``` +cargo install cargo-fuzz +``` + +## Fuzzing + +``` +cargo fuzz run watgen +cargo fuzz run wasm-opt-ttf +cargo fuzz run raw +``` + +## Learn More + +[Learn more about `cargo fuzz` from the `rust-fuzz` +book.](https://rust-fuzz.github.io/book/cargo-fuzz.html) diff --git a/fuzz/fuzz_targets/raw.rs b/fuzz/fuzz_targets/raw.rs new file mode 100755 index 00000000..86714815 --- /dev/null +++ b/fuzz/fuzz_targets/raw.rs @@ -0,0 +1,19 @@ +#![no_main] + +#[macro_use] +extern crate libfuzzer_sys; + +fuzz_target!(|data: &[u8]| { + let module = match walrus::Module::from_buffer(data) { + Ok(m) => m, + Err(_) => return, + }; + let serialized = module.emit_wasm(); + let module = + walrus::Module::from_buffer(&serialized).expect("we should only emit valid Wasm data"); + let reserialized = module.emit_wasm(); + assert_eq!( + serialized, reserialized, + "emitting wasm should be deterministic" + ); +}); diff --git a/fuzz/fuzz_targets/wasm-opt-ttf.rs b/fuzz/fuzz_targets/wasm-opt-ttf.rs new file mode 100644 index 00000000..72c1a83b --- /dev/null +++ b/fuzz/fuzz_targets/wasm-opt-ttf.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[macro_use] +extern crate libfuzzer_sys; + +use bufrng::BufRng; +use walrus_fuzz_utils::{Config, WasmOptTtf}; + +fuzz_target!(|data: &[u8]| { + let data = if data.is_empty() { &[0] } else { data }; + let fuel = data.len(); + let rng = BufRng::new(data); + let mut config = Config::::new(rng).set_fuel(fuel); + if let Err(e) = config.run_one() { + walrus_fuzz_utils::print_err(&e); + panic!("Found an error! {}", e); + } +}); diff --git a/fuzz/fuzz_targets/watgen.rs b/fuzz/fuzz_targets/watgen.rs new file mode 100644 index 00000000..db8d83ab --- /dev/null +++ b/fuzz/fuzz_targets/watgen.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[macro_use] +extern crate libfuzzer_sys; + +use bufrng::BufRng; +use walrus_fuzz_utils::{Config, WatGen}; + +fuzz_target!(|data: &[u8]| { + let data = if data.is_empty() { &[0] } else { data }; + let fuel = data.len(); + let rng = BufRng::new(data); + let mut config = Config::, BufRng>::new(rng).set_fuel(fuel); + if let Err(e) = config.run_one() { + walrus_fuzz_utils::print_err(&e); + panic!("Found an error! {}", e); + } +}); diff --git a/publish.sh b/publish.sh index 9f7d72f3..7110ccda 100755 --- a/publish.sh +++ b/publish.sh @@ -11,5 +11,8 @@ set -eux cd "$(dirname "$0")/crates/macro" cargo publish +# Let crates.io's index notice that we published the macro. +sleep 10 + cd ../.. cargo publish diff --git a/src/arena_set.rs b/src/arena_set.rs index 3a4aeed6..da8500c1 100644 --- a/src/arena_set.rs +++ b/src/arena_set.rs @@ -32,7 +32,7 @@ impl ArenaSet { } /// Get the id that will be used for the next unique item added to this set. - pub fn next_id(&mut self) -> Id { + pub fn next_id(&self) -> Id { self.arena.next_id() } diff --git a/src/error.rs b/src/error.rs index 6e63bd40..55dd32bc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,7 @@ //! Error types and utilities. -use std::fmt; pub use failure::Error; +use std::fmt; /// Either `Ok(T)` or `Err(failure::Error)`. pub type Result = ::std::result::Result; @@ -19,9 +19,7 @@ pub enum ErrorKind { impl fmt::Display for ErrorKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - ErrorKind::InvalidWasm => { - "The input WebAssembly is invalid".fmt(f) - } + ErrorKind::InvalidWasm => "The input WebAssembly is invalid".fmt(f), } } } diff --git a/src/function_builder.rs b/src/function_builder.rs index d018ccac..d71a3744 100644 --- a/src/function_builder.rs +++ b/src/function_builder.rs @@ -28,9 +28,8 @@ impl FunctionBuilder { ) -> FunctionBuilder { let ty = types.add(params, results); let mut builder = FunctionBuilder::without_entry(ty); - let entry = builder - .dangling_instr_seq(Box::new([]), results.to_vec().into_boxed_slice()) - .id; + let entry_ty = types.add_entry_ty(results); + let entry = builder.dangling_instr_seq(entry_ty).id; builder.entry = Some(entry); builder } @@ -65,7 +64,7 @@ impl FunctionBuilder { /// let mut module = walrus::Module::default(); /// let mut builder = walrus::FunctionBuilder::new(&mut module.types, &[], &[]); /// - /// let mut block = builder.dangling_instr_seq(Box::new([]), Box::new([])); + /// let mut block = builder.dangling_instr_seq(None); /// let id = block.id(); /// // Build up the block some. /// block @@ -107,7 +106,7 @@ impl FunctionBuilder { /// let mut builder = walrus::FunctionBuilder::new(&mut module.types, &[], &[]); /// /// // Create an empty, dangling instruction sequemce. - /// let mut seq = builder.dangling_instr_seq(Box::new([]), Box::new([])); + /// let mut seq = builder.dangling_instr_seq(None); /// let seq_id = seq.id(); /// /// // Do stuff with the sequence... @@ -120,14 +119,9 @@ impl FunctionBuilder { /// .func_body() /// .instr(Block { seq: seq_id }); /// ``` - pub fn dangling_instr_seq( - &mut self, - params: Box<[ValType]>, - results: Box<[ValType]>, - ) -> InstrSeqBuilder { - let id = self - .arena - .alloc_with_id(|id| InstrSeq::new(id, params, results)); + pub fn dangling_instr_seq(&mut self, ty: impl Into) -> InstrSeqBuilder { + let ty = ty.into(); + let id = self.arena.alloc_with_id(|id| InstrSeq::new(id, ty)); InstrSeqBuilder { id, builder: self } } @@ -239,7 +233,7 @@ impl InstrSeqBuilder<'_> { /// // end /// builder /// .func_body() - /// .block(Box::new([]), Box::new([]), |block| { + /// .block(None, |block| { /// block /// .i32_const(1337) /// .drop(); @@ -247,11 +241,10 @@ impl InstrSeqBuilder<'_> { /// ``` pub fn block( &mut self, - params: Box<[ValType]>, - results: Box<[ValType]>, + ty: impl Into, make_block: impl FnOnce(&mut InstrSeqBuilder), ) -> &mut Self { - let mut builder = self.dangling_instr_seq(params, results); + let mut builder = self.dangling_instr_seq(ty); make_block(&mut builder); let seq = builder.id; self.instr(Block { seq }) @@ -278,7 +271,7 @@ impl InstrSeqBuilder<'_> { /// // end /// builder /// .func_body() - /// .block_at(0, Box::new([]), Box::new([]), |block| { + /// .block_at(0, None, |block| { /// block /// .i32_const(1337) /// .drop(); @@ -287,11 +280,10 @@ impl InstrSeqBuilder<'_> { pub fn block_at( &mut self, position: usize, - params: Box<[ValType]>, - results: Box<[ValType]>, + ty: impl Into, make_block: impl FnOnce(&mut InstrSeqBuilder), ) -> &mut Self { - let mut builder = self.dangling_instr_seq(params, results); + let mut builder = self.dangling_instr_seq(ty); make_block(&mut builder); let seq = builder.id; self.instr_at(position, Block { seq }) @@ -313,7 +305,7 @@ impl InstrSeqBuilder<'_> { /// // end /// builder /// .func_body() - /// .loop_(Box::new([]), |loop_| { + /// .loop_(None, |loop_| { /// loop_ /// .i32_const(1337) /// .drop(); @@ -321,10 +313,10 @@ impl InstrSeqBuilder<'_> { /// ``` pub fn loop_( &mut self, - results: Box<[ValType]>, + ty: impl Into, make_loop: impl FnOnce(&mut InstrSeqBuilder), ) -> &mut Self { - let mut builder = self.dangling_instr_seq(Box::new([]), results); + let mut builder = self.dangling_instr_seq(ty); make_loop(&mut builder); let seq = builder.id; self.instr(Loop { seq }) @@ -352,7 +344,7 @@ impl InstrSeqBuilder<'_> { /// // end /// builder /// .func_body() - /// .loop_at(0, Box::new([]), |loop_| { + /// .loop_at(0, None, |loop_| { /// loop_ /// .i32_const(1337) /// .drop(); @@ -361,10 +353,10 @@ impl InstrSeqBuilder<'_> { pub fn loop_at( &mut self, position: usize, - results: Box<[ValType]>, + ty: impl Into, make_loop: impl FnOnce(&mut InstrSeqBuilder), ) -> &mut Self { - let mut builder = self.dangling_instr_seq(Box::new([]), results); + let mut builder = self.dangling_instr_seq(ty); make_loop(&mut builder); let seq = builder.id; self.instr_at(position, Loop { seq }) @@ -391,8 +383,7 @@ impl InstrSeqBuilder<'_> { /// // (else (i32.const 34))) /// .call(flip_coin) /// .if_else( - /// Box::new([]), - /// Box::new([ValType::I32]), + /// ValType::I32, /// |then| { /// then.i32_const(12); /// }, @@ -403,19 +394,20 @@ impl InstrSeqBuilder<'_> { /// ``` pub fn if_else( &mut self, - params: Box<[ValType]>, - results: Box<[ValType]>, + ty: impl Into, consequent: impl FnOnce(&mut InstrSeqBuilder), alternative: impl FnOnce(&mut InstrSeqBuilder), ) -> &mut Self { + let ty = ty.into(); + let consequent = { - let mut builder = self.dangling_instr_seq(params.clone(), results.clone()); + let mut builder = self.dangling_instr_seq(ty); consequent(&mut builder); builder.id }; let alternative = { - let mut builder = self.dangling_instr_seq(params, results); + let mut builder = self.dangling_instr_seq(ty); alternative(&mut builder); builder.id }; @@ -451,8 +443,7 @@ impl InstrSeqBuilder<'_> { /// .func_body() /// .if_else_at( /// 1, - /// Box::new([]), - /// Box::new([ValType::I32]), + /// ValType::I32, /// |then| { /// then.i32_const(12); /// }, @@ -464,19 +455,20 @@ impl InstrSeqBuilder<'_> { pub fn if_else_at( &mut self, position: usize, - params: Box<[ValType]>, - results: Box<[ValType]>, + ty: impl Into, consequent: impl FnOnce(&mut InstrSeqBuilder), alternative: impl FnOnce(&mut InstrSeqBuilder), ) -> &mut Self { + let ty = ty.into(); + let consequent = { - let mut builder = self.dangling_instr_seq(params.clone(), results.clone()); + let mut builder = self.dangling_instr_seq(ty); consequent(&mut builder); builder.id }; let alternative = { - let mut builder = self.dangling_instr_seq(params, results); + let mut builder = self.dangling_instr_seq(ty); alternative(&mut builder); builder.id }; diff --git a/src/ir/mod.rs b/src/ir/mod.rs index c1b206a6..87978b38 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -8,7 +8,9 @@ mod traversals; pub use self::traversals::*; use crate::encode::Encoder; -use crate::{DataId, FunctionId, GlobalId, LocalFunction, MemoryId, TableId, TypeId, ValType}; +use crate::{ + DataId, FunctionId, GlobalId, LocalFunction, MemoryId, ModuleTypes, TableId, TypeId, ValType, +}; use id_arena::Id; use std::fmt; use std::ops::{Deref, DerefMut}; @@ -43,21 +45,89 @@ impl Local { } } -/// TODO FITZGEN +/// The identifier for a `InstrSeq` within some `LocalFunction`. pub type InstrSeqId = Id; +/// The type of an instruction sequence. +/// +// NB: We purposefully match the encoding for block types here, with MVP Wasm +// types inlined and multi-value types outlined. If we tried to simplify this +// type representation by always using `TypeId`, then the `used` pass would +// think that a bunch of types that are only internally used by `InstrSeq`s are +// generally used, and we would emit them in the module's "Types" section. We +// don't want to bloat the modules we emit, nor do we want to make the used/GC +// passes convoluted, so we intentionally let the shape of this type guide us. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum InstrSeqType { + /// MVP Wasm blocks/loops/ifs can only push zero or one resulting value onto + /// the stack. They cannot take parameters on the stack. + Simple(Option), + /// The multi-value extension to Wasm allows arbitrary stack parameters and + /// results, which are expressed via the same mechanism as function types. + MultiValue(TypeId), +} + +impl InstrSeqType { + /// Construct a new `InstrSeqType` of the correct form for the given + /// parameter and result types. + pub fn new(types: &mut ModuleTypes, params: &[ValType], results: &[ValType]) -> InstrSeqType { + match (params.len(), results.len()) { + (0, 0) => InstrSeqType::Simple(None), + (0, 1) => InstrSeqType::Simple(Some(results[0])), + _ => InstrSeqType::MultiValue(types.add(params, results)), + } + } + + /// Construct an `InstrSeqType` with a signature that is known to either be + /// `Simple` or uses a `Type` that has already been inserted into the + /// `ModuleTypes`. + /// + /// Returns `None` if this is an instruction sequence signature that + /// requires multi-value and `ModuleTypes` does not already have a `Type` + /// for it. + pub fn existing( + types: &ModuleTypes, + params: &[ValType], + results: &[ValType], + ) -> Option { + Some(match (params.len(), results.len()) { + (0, 0) => InstrSeqType::Simple(None), + (0, 1) => InstrSeqType::Simple(Some(results[0])), + _ => InstrSeqType::MultiValue(types.find(params, results)?), + }) + } +} + +impl From> for InstrSeqType { + #[inline] + fn from(x: Option) -> InstrSeqType { + InstrSeqType::Simple(x) + } +} + +impl From for InstrSeqType { + #[inline] + fn from(x: ValType) -> InstrSeqType { + InstrSeqType::Simple(Some(x)) + } +} + +impl From for InstrSeqType { + #[inline] + fn from(x: TypeId) -> InstrSeqType { + InstrSeqType::MultiValue(x) + } +} + /// A sequence of instructions. #[derive(Debug)] pub struct InstrSeq { id: InstrSeqId, - /// The types of the expected values on the stack when entering this - /// block. - pub params: Box<[ValType]>, - - /// The types of the resulting values added to the stack after this - /// block is evaluated. - pub results: Box<[ValType]>, + /// This block's type: its the types of values that are expected on the + /// stack when entering this instruction sequence and the types that are + /// left on the stack afterwards. + pub ty: InstrSeqType, /// The instructions that make up the body of this block. pub instrs: Vec, @@ -81,14 +151,9 @@ impl DerefMut for InstrSeq { impl InstrSeq { /// Construct a new instruction sequence. - pub(crate) fn new(id: InstrSeqId, params: Box<[ValType]>, results: Box<[ValType]>) -> InstrSeq { + pub(crate) fn new(id: InstrSeqId, ty: InstrSeqType) -> InstrSeq { let instrs = vec![]; - InstrSeq { - id, - params, - results, - instrs, - } + InstrSeq { id, ty, instrs } } /// Get the id of this instruction sequence. @@ -144,17 +209,17 @@ pub(crate) enum BlockKind { #[walrus_instr] #[derive(Clone, Debug)] pub enum Instr { - /// A block of multiple instructions, and also a control frame. + /// `block ... end` #[walrus(skip_builder)] Block { /// The id of this `block` instruction's inner `InstrSeq`. seq: InstrSeqId, }, - /// TODO FITZGEN + /// `loop ... end` #[walrus(skip_builder)] Loop { - /// TODO FITZGEN + /// The id of this `loop` instruction's inner `InstrSeq`. seq: InstrSeqId, }, @@ -1043,11 +1108,11 @@ impl Instr { } /// Anything that can be visited by a `Visitor`. -pub(crate) trait Visit<'expr> { +pub(crate) trait Visit<'instr> { /// Visit this thing with the given visitor. fn visit(&self, visitor: &mut V) where - V: Visitor<'expr>; + V: Visitor<'instr>; } /// Anything that can be mutably visited by a `VisitorMut`. @@ -1057,3 +1122,25 @@ pub(crate) trait VisitMut { where V: VisitorMut; } + +impl<'instr> Visit<'instr> for InstrSeq { + fn visit(&self, visitor: &mut V) + where + V: Visitor<'instr>, + { + if let InstrSeqType::MultiValue(ref ty) = self.ty { + visitor.visit_type_id(ty); + } + } +} + +impl VisitMut for InstrSeq { + fn visit_mut(&mut self, visitor: &mut V) + where + V: VisitorMut, + { + if let InstrSeqType::MultiValue(ref mut ty) = self.ty { + visitor.visit_type_id_mut(ty); + } + } +} diff --git a/src/ir/traversals.rs b/src/ir/traversals.rs index 0e1d4963..6a984725 100644 --- a/src/ir/traversals.rs +++ b/src/ir/traversals.rs @@ -75,6 +75,7 @@ pub fn dfs_in_order<'instr>( // instructions in this sequence yet, and it is the first time we // are entering it, so let the visitor know. visitor.start_instr_seq(seq); + seq.visit(visitor); } 'traversing_instrs: for (index, instr) in seq.instrs.iter().enumerate().skip(index) { @@ -189,6 +190,7 @@ pub fn dfs_pre_order_mut( while let Some(seq_id) = stack.pop() { let seq = func.block_mut(seq_id); visitor.start_instr_seq_mut(seq); + seq.visit_mut(visitor); for instr in &mut seq.instrs { visitor.visit_instr_mut(instr); @@ -292,19 +294,19 @@ mod tests { } fn make_test_func(module: &mut crate::Module) -> &mut LocalFunction { + let block_ty = module.types.add(&[], &[]); let mut builder = crate::FunctionBuilder::new(&mut module.types, &[], &[]); builder .func_body() .i32_const(1) .drop() - .block(Box::new([]), Box::new([]), |block| { + .block(block_ty, |block| { block .i32_const(2) .drop() .if_else( - Box::new([]), - Box::new([]), + block_ty, |then| { then.i32_const(3).drop(); }, diff --git a/src/module/functions/local_function/context.rs b/src/module/functions/local_function/context.rs index 493367c7..fd74037f 100644 --- a/src/module/functions/local_function/context.rs +++ b/src/module/functions/local_function/context.rs @@ -1,17 +1,18 @@ //! Context needed when validating instructions and constructing our `Instr` IR. use crate::error::{ErrorKind, Result}; -use crate::ir::{BlockKind, Instr, InstrSeq, InstrSeqId}; +use crate::ir::{BlockKind, Instr, InstrSeq, InstrSeqId, InstrSeqType}; use crate::module::functions::{FunctionId, LocalFunction}; use crate::module::Module; use crate::parse::IndicesToIds; use crate::ty::ValType; +use crate::{ModuleTypes, TypeId}; use failure::Fail; #[derive(Debug)] pub(crate) struct ControlFrame { - /// The type of the associated label (used to type-check branches). - pub label_types: Box<[ValType]>, + /// The parameter types of the block (checked before entering the block). + pub start_types: Box<[ValType]>, /// The result type of the block (used to check its result). pub end_types: Box<[ValType]>, @@ -31,6 +32,17 @@ pub(crate) struct ControlFrame { pub kind: BlockKind, } +impl ControlFrame { + /// Get the expected types on the stack for branches to this block. + pub fn label_types(&self) -> &[ValType] { + if let BlockKind::Loop = self.kind { + &self.start_types + } else { + &self.end_types + } + } +} + /// The operand stack. /// /// `None` is used for `Unknown` stack-polymophic values. @@ -116,22 +128,40 @@ impl<'a> ValidationContext<'a> { pub fn push_control( &mut self, kind: BlockKind, - label_types: Box<[ValType]>, + start_types: Box<[ValType]>, end_types: Box<[ValType]>, - ) -> InstrSeqId { + ) -> Result { impl_push_control( + &self.module.types, + kind, + self.func, + self.controls, + self.operands, + start_types, + end_types, + ) + } + + pub fn push_control_with_ty(&mut self, kind: BlockKind, ty: TypeId) -> InstrSeqId { + let (start_types, end_types) = self.module.types.params_results(ty); + let start_types: Box<[_]> = start_types.into(); + let end_types: Box<[_]> = end_types.into(); + impl_push_control_with_ty( + &self.module.types, kind, self.func, self.controls, self.operands, - label_types, + ty.into(), + start_types, end_types, ) } - pub fn pop_control(&mut self) -> Result<(Box<[ValType]>, InstrSeqId, BlockKind)> { + pub fn pop_control(&mut self) -> Result<(ControlFrame, InstrSeqId)> { let frame = impl_pop_control(&mut self.controls, &mut self.operands)?; - Ok((frame.end_types, frame.block, frame.kind)) + let block = frame.block; + Ok((frame, block)) } pub fn unreachable(&mut self) { @@ -173,6 +203,7 @@ impl<'a> ValidationContext<'a> { } fn impl_push_operand(operands: &mut OperandStack, op: Option) { + log::trace!("push operand: {:?}", op); operands.push(op); } @@ -183,6 +214,7 @@ fn impl_pop_operand( if let Some(f) = controls.last() { if operands.len() == f.height { if f.unreachable { + log::trace!("pop operand: None"); return Ok(None); } return Err(ErrorKind::InvalidWasm @@ -190,7 +222,9 @@ fn impl_pop_operand( .into()); } } - Ok(operands.pop().unwrap()) + let op = operands.pop().unwrap(); + log::trace!("pop operand: {:?}", op); + Ok(op) } fn impl_pop_operand_expected( @@ -232,23 +266,63 @@ fn impl_pop_operands( } fn impl_push_control( + types: &ModuleTypes, kind: BlockKind, func: &mut LocalFunction, controls: &mut ControlStack, - operands: &OperandStack, - label_types: Box<[ValType]>, + operands: &mut OperandStack, + start_types: Box<[ValType]>, + end_types: Box<[ValType]>, +) -> Result { + let ty = InstrSeqType::existing(types, &start_types, &end_types).ok_or_else(|| { + failure::format_err!( + "attempted to push a control frame for an instruction \ + sequence with a type that does not exist" + ) + .context(format!("type: {:?} -> {:?}", &start_types, &end_types)) + })?; + + Ok(impl_push_control_with_ty( + types, + kind, + func, + controls, + operands, + ty, + start_types, + end_types, + )) +} + +fn impl_push_control_with_ty( + types: &ModuleTypes, + kind: BlockKind, + func: &mut LocalFunction, + controls: &mut ControlStack, + operands: &mut OperandStack, + ty: InstrSeqType, + start_types: Box<[ValType]>, end_types: Box<[ValType]>, ) -> InstrSeqId { - let block = func.add_block(|id| InstrSeq::new(id, label_types.clone(), end_types.clone())); - let frame = ControlFrame { - label_types, + if let InstrSeqType::MultiValue(ty) = ty { + debug_assert_eq!(types.params(ty), &start_types[..]); + debug_assert_eq!(types.results(ty), &end_types[..]); + } + + let height = operands.len(); + impl_push_operands(operands, &start_types); + + let block = func.add_block(|id| InstrSeq::new(id, ty)); + + controls.push(ControlFrame { + start_types, end_types, - height: operands.len(), + height, unreachable: false, block, kind, - }; - controls.push(frame); + }); + block } diff --git a/src/module/functions/local_function/emit.rs b/src/module/functions/local_function/emit.rs index ba73eea1..4ba33eb8 100644 --- a/src/module/functions/local_function/emit.rs +++ b/src/module/functions/local_function/emit.rs @@ -4,7 +4,6 @@ use crate::ir::*; use crate::map::IdHashMap; use crate::module::functions::LocalFunction; use crate::module::memories::MemoryId; -use crate::ty::ValType; pub(crate) fn run( func: &LocalFunction, @@ -53,15 +52,15 @@ impl<'instr> Visitor<'instr> for Emit<'_, '_> { match self.block_kinds.last().unwrap() { BlockKind::Block => { self.encoder.byte(0x02); // block - self.block_type(&seq.results); + self.block_type(seq.ty); } BlockKind::Loop => { self.encoder.byte(0x03); // loop - self.block_type(&seq.results); + self.block_type(seq.ty); } BlockKind::If => { self.encoder.byte(0x04); // if - self.block_type(&seq.results); + self.block_type(seq.ty); } // Function entries are implicitly started, and don't need any // opcode to start them. `Else` blocks are started when `If` blocks @@ -780,14 +779,15 @@ impl Emit<'_, '_> { ) as u32 } - fn block_type(&mut self, ty: &[ValType]) { - match ty.len() { - 0 => self.encoder.byte(0x40), - 1 => ty[0].emit(self.encoder), - _ => panic!( - "multiple return values not yet supported; write a transformation to \ - rewrite them into single value returns" - ), + fn block_type(&mut self, ty: InstrSeqType) { + match ty { + InstrSeqType::Simple(None) => self.encoder.byte(0x40), + InstrSeqType::Simple(Some(ty)) => ty.emit(self.encoder), + InstrSeqType::MultiValue(ty) => { + let index = self.indices.get_type_index(ty); + assert!(index < std::i32::MAX as u32); + self.encoder.i32(index as i32); + } } } diff --git a/src/module/functions/local_function/mod.rs b/src/module/functions/local_function/mod.rs index a426bf8b..f9ba925c 100644 --- a/src/module/functions/local_function/mod.rs +++ b/src/module/functions/local_function/mod.rs @@ -61,7 +61,10 @@ impl LocalFunction { let mut ctx = ValidationContext::new(module, indices, id, &mut func, operands, controls); - let entry = ctx.push_control(BlockKind::FunctionEntry, result.clone(), result); + let ty = module.types.find_for_function_entry(&result).expect( + "the function entry type should have already been created before parsing the body", + ); + let entry = ctx.push_control_with_ty(BlockKind::FunctionEntry, ty); ctx.func.builder.entry = Some(entry); for inst in body { let inst = inst?; @@ -247,10 +250,38 @@ impl LocalFunction { } } +fn block_result_tys( + ctx: &ValidationContext, + ty: wasmparser::TypeOrFuncType, +) -> Result> { + match ty { + wasmparser::TypeOrFuncType::Type(ty) => ValType::from_wasmparser_type(ty).map(Into::into), + wasmparser::TypeOrFuncType::FuncType(idx) => { + let ty = ctx.indices.get_type(idx)?; + Ok(ctx.module.types.results(ty).into()) + } + } +} + +fn block_param_tys( + ctx: &ValidationContext, + ty: wasmparser::TypeOrFuncType, +) -> Result> { + match ty { + wasmparser::TypeOrFuncType::Type(_) => Ok([][..].into()), + wasmparser::TypeOrFuncType::FuncType(idx) => { + let ty = ctx.indices.get_type(idx)?; + Ok(ctx.module.types.params(ty).into()) + } + } +} + fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<()> { use crate::ir::ExtendedLoad::*; use crate::ValType::*; + log::trace!("validate instruction: {:?}", inst); + let const_ = |ctx: &mut ValidationContext, ty, value| { ctx.alloc_instr(Const { value }); ctx.push_operand(Some(ty)); @@ -595,33 +626,40 @@ fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<( ctx.unreachable(); } Operator::Block { ty } => { - let results = ValType::from_wasmparser_type(ty)?; - let seq = ctx.push_control(BlockKind::Block, results.clone(), results); + let param_tys = block_param_tys(ctx, ty)?; + let result_tys = block_result_tys(ctx, ty)?; + ctx.pop_operands(¶m_tys)?; + let seq = ctx.push_control(BlockKind::Block, param_tys, result_tys)?; ctx.alloc_instr_in_control(1, Block { seq })?; } Operator::Loop { ty } => { - let t = ValType::from_wasmparser_type(ty)?; - let seq = ctx.push_control(BlockKind::Loop, vec![].into_boxed_slice(), t); + let result_tys = block_result_tys(ctx, ty)?; + let param_tys = block_param_tys(ctx, ty)?; + ctx.pop_operands(¶m_tys)?; + let seq = ctx.push_control(BlockKind::Loop, param_tys, result_tys)?; ctx.alloc_instr_in_control(1, Loop { seq })?; } Operator::If { ty } => { - let ty = ValType::from_wasmparser_type(ty)?; + let result_tys = block_result_tys(ctx, ty)?; + let param_tys = block_param_tys(ctx, ty)?; + ctx.pop_operand_expected(Some(I32))?; + ctx.pop_operands(¶m_tys)?; - let consequent = ctx.push_control(BlockKind::If, ty.clone(), ty.clone()); + let consequent = ctx.push_control(BlockKind::If, param_tys, result_tys)?; ctx.if_else.push(context::IfElseState { consequent, alternative: None, }); } Operator::End => { - let (results, _block_id, kind) = ctx.pop_control()?; + let (frame, _block) = ctx.pop_control()?; // If we just finished an if/else block then the actual // instruction which produces the value will be an `IfElse` node, // not the block itself. Do some postprocessing here to create // such a node. - match kind { + match frame.kind { BlockKind::If | BlockKind::Else => { let context::IfElseState { consequent, @@ -630,13 +668,16 @@ fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<( let alternative = match alternative { Some(alt) => { - debug_assert_eq!(kind, BlockKind::Else); + debug_assert_eq!(frame.kind, BlockKind::Else); alt } None => { - debug_assert_eq!(kind, BlockKind::If); - let alternative = - ctx.push_control(BlockKind::Else, results.clone(), results.clone()); + debug_assert_eq!(frame.kind, BlockKind::If); + let alternative = ctx.push_control( + BlockKind::Else, + frame.start_types.clone(), + frame.end_types.clone(), + )?; ctx.pop_control()?; alternative } @@ -650,21 +691,21 @@ fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<( _ => {} } - ctx.push_operands(&results); + ctx.push_operands(&frame.end_types); } Operator::Else => { - let (results, _consequent, kind) = ctx.pop_control()?; - + let (frame, _consequent) = ctx.pop_control()?; // An `else` instruction is only valid immediately inside an if/else // block which is denoted by the `IfElse` block kind. - match kind { + match frame.kind { BlockKind::If => {} _ => bail!("`else` without a leading `if`"), } // But we still need to parse the alternative block, so allocate the // block here to parse. - let alternative = ctx.push_control(BlockKind::Else, results.clone(), results); + let alternative = + ctx.push_control(BlockKind::Else, frame.start_types, frame.end_types)?; let last = ctx.if_else.last_mut().unwrap(); if last.alternative.is_some() { bail!("`else` without a leading `if`") @@ -673,7 +714,7 @@ fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<( } Operator::Br { relative_depth } => { let n = relative_depth as usize; - let expected = ctx.control(n)?.label_types.clone(); + let expected = ctx.control(n)?.label_types().to_vec(); ctx.pop_operands(&expected)?; let block = ctx.control(n)?.block; @@ -684,7 +725,7 @@ fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<( let n = relative_depth as usize; ctx.pop_operand_expected(Some(I32))?; - let expected = ctx.control(n)?.label_types.clone(); + let expected = ctx.control(n)?.label_types().to_vec(); ctx.pop_operands(&expected)?; let block = ctx.control(n)?.block; @@ -695,6 +736,7 @@ fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<( let len = table.len(); let mut blocks = Vec::with_capacity(len); let mut label_types = None; + let label_types_ref = &mut label_types; let mut iter = table.into_iter(); let mut next = || { let n = match iter.next() { @@ -702,10 +744,10 @@ fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<( None => bail!("malformed `br_table"), }; let control = ctx.control(n as usize)?; - match label_types { - None => label_types = Some(&control.label_types), + match label_types_ref { + None => *label_types_ref = Some(control.label_types().to_vec()), Some(n) => { - if n != &control.label_types { + if &n[..] != control.label_types() { bail!("br_table jump with non-uniform label types") } } @@ -840,7 +882,7 @@ fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<( if flags != 0 { bail!("fence with nonzero flags not supported yet"); } - ctx.alloc_instr(AtomicFence { }); + ctx.alloc_instr(AtomicFence {}); } Operator::I32AtomicLoad { memarg } => { @@ -1134,7 +1176,6 @@ fn validate_instruction(ctx: &mut ValidationContext, inst: Operator) -> Result<( ctx.push_operand(Some(I32)); } - // Operator::V8x16Shuffle { .. } => bail!("v8x16.shuffle not supported"), Operator::V8x16Swizzle => { ctx.pop_operand_expected(Some(V128))?; ctx.pop_operand_expected(Some(V128))?; diff --git a/src/module/functions/mod.rs b/src/module/functions/mod.rs index ab6b5293..4018a4a1 100644 --- a/src/module/functions/mod.rs +++ b/src/module/functions/mod.rs @@ -349,7 +349,8 @@ impl Module { // First up, implicitly add locals for all function arguments. We also // record these in the function itself for later processing. let mut args = Vec::new(); - for ty in self.types.get(ty).params().iter() { + let type_ = self.types.get(ty); + for ty in type_.params().iter() { let local_id = self.locals.add(*ty); let idx = indices.push_local(id, local_id); args.push(local_id); @@ -359,6 +360,13 @@ impl Module { } } + // Ensure that there exists a `Type` for the function's entry + // block. This is required because multi-value blocks reference a + // `Type`, however function entry's type is implicit in the + // encoding, and doesn't already exist in the `ModuleTypes`. + let results = type_.results().to_vec(); + self.types.add_entry_ty(&results); + // WebAssembly local indices are 32 bits, so it's a validation error to // have more than 2^32 locals. Sure enough there's a spec test for this! let mut total = 0u32; diff --git a/src/module/mod.rs b/src/module/mod.rs index 5831f4c7..1bf63925 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -235,13 +235,13 @@ impl Module { where P: AsRef, { - let buffer = self.emit_wasm()?; + let buffer = self.emit_wasm(); fs::write(path, buffer).context("failed to write wasm module")?; Ok(()) } /// Emit this module into an in-memory wasm buffer. - pub fn emit_wasm(&self) -> Result> { + pub fn emit_wasm(&self) -> Vec { log::debug!("start emit"); let indices = &mut IdsToIndices::default(); @@ -293,7 +293,7 @@ impl Module { } log::debug!("emission finished"); - Ok(wasm) + wasm } /// Returns an iterator over all functions in this module diff --git a/src/module/types.rs b/src/module/types.rs index 0610b3e0..14b00115 100644 --- a/src/module/types.rs +++ b/src/module/types.rs @@ -24,6 +24,22 @@ impl ModuleTypes { &mut self.arena[id] } + /// Get the parameters and results for the given type. + pub fn params_results(&self, id: TypeId) -> (&[ValType], &[ValType]) { + let ty = self.get(id); + (ty.params(), ty.results()) + } + + /// Get the parameters for the given type. + pub fn params(&self, id: TypeId) -> &[ValType] { + self.get(id).params() + } + + /// Get the results for the given type. + pub fn results(&self, id: TypeId) -> &[ValType] { + self.get(id).results() + } + /// Get a type ID by its name. /// /// This is currently only intended for in-memory modifications, and by @@ -63,6 +79,35 @@ impl ModuleTypes { results.to_vec().into_boxed_slice(), )) } + + pub(crate) fn add_entry_ty(&mut self, results: &[ValType]) -> TypeId { + let id = self.arena.next_id(); + self.arena.insert(Type::for_function_entry( + id, + results.to_vec().into_boxed_slice(), + )) + } + + /// Find the existing type for the given parameters and results. + pub fn find(&self, params: &[ValType], results: &[ValType]) -> Option { + self.arena.iter().find_map(|(id, ty)| { + if !ty.is_for_function_entry() && ty.params() == params && ty.results() == results { + Some(id) + } else { + None + } + }) + } + + pub(crate) fn find_for_function_entry(&self, results: &[ValType]) -> Option { + self.arena.iter().find_map(|(id, ty)| { + if ty.is_for_function_entry() && ty.params().is_empty() && ty.results() == results { + Some(id) + } else { + None + } + }) + } } impl Module { @@ -99,14 +144,24 @@ impl Module { impl Emit for ModuleTypes { fn emit(&self, cx: &mut EmitContext) { log::debug!("emitting type section"); - let ntypes = self.iter().count(); - if ntypes == 0 { + + let mut tys = self + .arena + .iter() + .filter(|(_, ty)| !ty.is_for_function_entry()) + .collect::>(); + + if tys.is_empty() { return; } + let mut cx = cx.start_section(Section::Type); - cx.encoder.usize(ntypes); + cx.encoder.usize(tys.len()); + + // Sort for deterministic ordering. + tys.sort_by_key(|&(_, ty)| ty); - for (id, ty) in self.arena.iter() { + for (id, ty) in tys { cx.indices.push_type(id); ty.emit(&mut cx); } diff --git a/src/ty.rs b/src/ty.rs index 93bd0abe..eb7a3755 100644 --- a/src/ty.rs +++ b/src/ty.rs @@ -4,8 +4,8 @@ use crate::emit::{Emit, EmitContext}; use crate::encode::Encoder; use crate::error::Result; use crate::tombstone_arena::Tombstone; -use failure::bail; use id_arena::Id; +use std::cmp::Ordering; use std::fmt; use std::hash; @@ -19,6 +19,11 @@ pub struct Type { params: Box<[ValType]>, results: Box<[ValType]>, + // Whether or not this type is for a multi-value function entry block, and + // therefore is for internal use only and shouldn't be emitted when we + // serialize the Type section. + is_for_function_entry: bool, + /// An optional name for debugging. /// /// This is not really used by anything currently, but a theoretical WAT to @@ -30,18 +35,35 @@ impl PartialEq for Type { #[inline] fn eq(&self, rhs: &Type) -> bool { // NB: do not compare id or name. - self.params == rhs.params && self.results == rhs.results + self.params == rhs.params + && self.results == rhs.results + && self.is_for_function_entry == rhs.is_for_function_entry } } impl Eq for Type {} +impl PartialOrd for Type { + fn partial_cmp(&self, rhs: &Type) -> Option { + Some(self.cmp(rhs)) + } +} + +impl Ord for Type { + fn cmp(&self, rhs: &Type) -> Ordering { + self.params() + .cmp(rhs.params()) + .then_with(|| self.results().cmp(rhs.results())) + } +} + impl hash::Hash for Type { #[inline] fn hash(&self, h: &mut H) { // Do not hash id or name. self.params.hash(h); - self.results.hash(h) + self.results.hash(h); + self.is_for_function_entry.hash(h); } } @@ -55,11 +77,25 @@ impl Tombstone for Type { impl Type { /// Construct a new function type. #[inline] - pub fn new(id: TypeId, params: Box<[ValType]>, results: Box<[ValType]>) -> Type { + pub(crate) fn new(id: TypeId, params: Box<[ValType]>, results: Box<[ValType]>) -> Type { Type { id, params, results, + is_for_function_entry: false, + name: None, + } + } + + /// Construct a new type for function entry blocks. + #[inline] + pub(crate) fn for_function_entry(id: TypeId, results: Box<[ValType]>) -> Type { + let params = vec![].into(); + Type { + id, + params, + results, + is_for_function_entry: true, name: None, } } @@ -81,10 +117,15 @@ impl Type { pub fn results(&self) -> &[ValType] { &*self.results } + + pub(crate) fn is_for_function_entry(&self) -> bool { + self.is_for_function_entry + } } impl Emit for Type { fn emit(&self, cx: &mut EmitContext) { + assert!(!self.is_for_function_entry()); cx.encoder.byte(0x60); cx.list(self.params.iter()); cx.list(self.results.iter()); @@ -109,12 +150,7 @@ pub enum ValType { } impl ValType { - /// Construct a vector of `ValType`s from a `wasmparser::TypeOrFuncType` - pub fn from_wasmparser_type(ty: wasmparser::TypeOrFuncType) -> Result> { - let ty = match ty { - wasmparser::TypeOrFuncType::Type(t) => t, - wasmparser::TypeOrFuncType::FuncType(_) => bail!("function types not supported yet"), - }; + pub(crate) fn from_wasmparser_type(ty: wasmparser::Type) -> Result> { let v = match ty { wasmparser::Type::EmptyBlockType => Vec::new(), _ => vec![ValType::parse(&ty)?],