diff --git a/Cargo.lock b/Cargo.lock index 6071264125f9..afd7eada2997 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3471,6 +3471,9 @@ dependencies = [ "clap", "component-macro-test", "component-test-util", + "cranelift-codegen", + "cranelift-filetests", + "cranelift-reader", "criterion", "env_logger", "filecheck", diff --git a/Cargo.toml b/Cargo.toml index c7431f823653..563ea9a683b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ rustix = { workspace = true, features = ["mm", "param", "process"] } [dev-dependencies] # depend again on wasmtime to activate its default features for tests -wasmtime = { workspace = true, features = ['component-model', 'async', 'default', 'winch', 'debug-builtins'] } +wasmtime = { workspace = true, features = ['component-model', 'async', 'default', 'winch', 'debug-builtins', 'all-arch'] } env_logger = { workspace = true } log = { workspace = true } filecheck = { workspace = true } @@ -91,6 +91,9 @@ walkdir = { workspace = true } test-programs-artifacts = { workspace = true } bytesize = "1.3.0" wit-component = { workspace = true } +cranelift-filetests = { workspace = true } +cranelift-codegen = { workspace = true } +cranelift-reader = { workspace = true } [target.'cfg(windows)'.dev-dependencies] windows-sys = { workspace = true, features = ["Win32_System_Memory"] } @@ -391,6 +394,10 @@ run = ["dep:wasmtime-wasi", "wasmtime/runtime", "wasmtime-runtime", "dep:listenf name = "host_segfault" harness = false +[[test]] +name = "disas" +harness = false + [[example]] name = "tokio" required-features = ["wasi-common/tokio"] diff --git a/cranelift/filetests/filetests/wasm/aarch64-relaxed-simd.wat b/cranelift/filetests/filetests/wasm/aarch64-relaxed-simd.wat deleted file mode 100644 index b1a0bcd4cb87..000000000000 --- a/cranelift/filetests/filetests/wasm/aarch64-relaxed-simd.wat +++ /dev/null @@ -1,87 +0,0 @@ -;;! target = "aarch64" -;;! compile = true - -(module - (func (param v128) (result v128) - local.get 0 - i32x4.relaxed_trunc_f32x4_s - ) - - (func (param v128) (result v128) - local.get 0 - i32x4.relaxed_trunc_f32x4_u - ) - - (func (param v128) (result v128) - local.get 0 - i32x4.relaxed_trunc_f64x2_s_zero - ) - - (func (param v128) (result v128) - local.get 0 - i32x4.relaxed_trunc_f64x2_u_zero - ) - - (func (param v128 v128) (result v128) - local.get 0 - local.get 1 - i16x8.relaxed_dot_i8x16_i7x16_s - ) - - (func (param v128 v128 v128) (result v128) - local.get 0 - local.get 1 - local.get 2 - i32x4.relaxed_dot_i8x16_i7x16_add_s - ) -) - -;; function u0:0: -;; block0: -;; fcvtzs v0.4s, v0.4s -;; b label1 -;; block1: -;; ret -;; -;; function u0:1: -;; block0: -;; fcvtzu v0.4s, v0.4s -;; b label1 -;; block1: -;; ret -;; -;; function u0:2: -;; block0: -;; fcvtzs v4.2d, v0.2d -;; sqxtn v0.2s, v4.2d -;; b label1 -;; block1: -;; ret -;; -;; function u0:3: -;; block0: -;; fcvtzu v4.2d, v0.2d -;; uqxtn v0.2s, v4.2d -;; b label1 -;; block1: -;; ret -;; -;; function u0:4: -;; block0: -;; smull v6.8h, v0.8b, v1.8b -;; smull2 v7.8h, v0.16b, v1.16b -;; addp v0.8h, v6.8h, v7.8h -;; b label1 -;; block1: -;; ret -;; -;; function u0:5: -;; block0: -;; smull v17.8h, v0.8b, v1.8b -;; smull2 v18.8h, v0.16b, v1.16b -;; addp v17.8h, v17.8h, v18.8h -;; saddlp v17.4s, v17.8h -;; add v0.4s, v17.4s, v2.4s -;; b label1 -;; block1: -;; ret diff --git a/cranelift/filetests/filetests/wasm/basic-wat-test.wat b/cranelift/filetests/filetests/wasm/basic-wat-test.wat deleted file mode 100644 index f42bc37bf720..000000000000 --- a/cranelift/filetests/filetests/wasm/basic-wat-test.wat +++ /dev/null @@ -1,45 +0,0 @@ -;;! target = "x86_64" -;;! -;;! [globals.vmctx] -;;! type = "i64" -;;! vmctx = true -;;! -;;! [globals.heap_base] -;;! type = "i64" -;;! load = { base = "vmctx", offset = 0, readonly = true } -;;! -;;! [[heaps]] -;;! base = "heap_base" -;;! min_size = 0 -;;! offset_guard_size = 0xFFFFFFFF -;;! index_type = "i32" -;;! style = { kind = "static", bound = 0x1000 } - -(module - (memory 0) - (func (param i32 i32) (result i32) - local.get 0 - i32.load - local.get 1 - i32.load - i32.add)) - -;; function u0:0(i32, i32, i64 vmctx) -> i32 fast { -;; gv0 = vmctx -;; gv1 = load.i64 notrap aligned readonly gv0 -;; -;; block0(v0: i32, v1: i32, v2: i64): -;; @0021 v4 = uextend.i64 v0 -;; @0021 v5 = global_value.i64 gv1 -;; @0021 v6 = iadd v5, v4 -;; @0021 v7 = load.i32 little heap v6 -;; @0026 v8 = uextend.i64 v1 -;; @0026 v9 = global_value.i64 gv1 -;; @0026 v10 = iadd v9, v8 -;; @0026 v11 = load.i32 little heap v10 -;; @0029 v12 = iadd v7, v11 -;; @002a jump block1(v12) -;; -;; block1(v3: i32): -;; @002a return v3 -;; } \ No newline at end of file diff --git a/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory.wat b/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory.wat deleted file mode 100644 index 524f7eb83657..000000000000 --- a/cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory.wat +++ /dev/null @@ -1,95 +0,0 @@ -;;! target = "x86_64" -;;! -;;! optimize = true -;;! -;;! settings = [ -;;! "enable_heap_access_spectre_mitigation=true", -;;! "opt_level=speed_and_size", -;;! ] -;;! -;;! [globals.vmctx] -;;! type = "i64" -;;! vmctx = true -;;! -;;! [globals.heap_base] -;;! type = "i64" -;;! load = { base = "vmctx", offset = 0 } -;;! -;;! [globals.heap_bound] -;;! type = "i64" -;;! load = { base = "vmctx", offset = 8 } -;;! -;;! [[heaps]] -;;! base = "heap_base" -;;! min_size = 0 -;;! offset_guard_size = 0xffffffff -;;! index_type = "i32" -;;! style = { kind = "dynamic", bound = "heap_bound" } - -(module - (memory (export "memory") 0) - (func (export "load-without-offset") (param i32) (result i32 i32) - local.get 0 - i32.load - local.get 0 - i32.load - ) - (func (export "load-with-offset") (param i32) (result i32 i32) - local.get 0 - i32.load offset=1234 - local.get 0 - i32.load offset=1234 - ) -) - -;; function u0:0(i32, i64 vmctx) -> i32, i32 fast { -;; gv0 = vmctx -;; gv1 = load.i64 notrap aligned gv0+8 -;; gv2 = load.i64 notrap aligned gv0 -;; -;; block0(v0: i32, v1: i64): -;; v20 -> v1 -;; v21 -> v1 -;; v22 -> v1 -;; v23 -> v1 -;; @0057 v5 = load.i64 notrap aligned v1+8 -;; @0057 v7 = load.i64 notrap aligned v1 -;; @0057 v4 = uextend.i64 v0 -;; @0057 v6 = icmp ugt v4, v5 -;; @0057 v9 = iconst.i64 0 -;; @0057 v8 = iadd v7, v4 -;; @0057 v10 = select_spectre_guard v6, v9, v8 ; v9 = 0 -;; @0057 v11 = load.i32 little heap v10 -;; v2 -> v11 -;; @005f jump block1 -;; -;; block1: -;; @005f return v11, v11 -;; } -;; -;; function u0:1(i32, i64 vmctx) -> i32, i32 fast { -;; gv0 = vmctx -;; gv1 = load.i64 notrap aligned gv0+8 -;; gv2 = load.i64 notrap aligned gv0 -;; -;; block0(v0: i32, v1: i64): -;; v24 -> v1 -;; v25 -> v1 -;; v26 -> v1 -;; v27 -> v1 -;; @0064 v5 = load.i64 notrap aligned v1+8 -;; @0064 v7 = load.i64 notrap aligned v1 -;; @0064 v4 = uextend.i64 v0 -;; @0064 v6 = icmp ugt v4, v5 -;; @0064 v11 = iconst.i64 0 -;; @0064 v8 = iadd v7, v4 -;; @0064 v9 = iconst.i64 1234 -;; @0064 v10 = iadd v8, v9 ; v9 = 1234 -;; @0064 v12 = select_spectre_guard v6, v11, v10 ; v11 = 0 -;; @0064 v13 = load.i32 little heap v12 -;; v2 -> v13 -;; @006e jump block1 -;; -;; block1: -;; @006e return v13, v13 -;; } diff --git a/cranelift/filetests/src/lib.rs b/cranelift/filetests/src/lib.rs index 9357f57bab0a..1f33370f5e91 100644 --- a/cranelift/filetests/src/lib.rs +++ b/cranelift/filetests/src/lib.rs @@ -30,7 +30,7 @@ mod test_run; mod test_safepoint; mod test_unwind; mod test_verifier; -mod test_wasm; +pub mod test_wasm; /// Main entry point for `clif-util test`. /// diff --git a/cranelift/filetests/src/test_wasm.rs b/cranelift/filetests/src/test_wasm.rs index fe3b0b082974..e5d6c031b8c1 100644 --- a/cranelift/filetests/src/test_wasm.rs +++ b/cranelift/filetests/src/test_wasm.rs @@ -5,8 +5,12 @@ mod env; use anyhow::{bail, ensure, Context, Result}; use config::TestConfig; +use cranelift_codegen::ir::Function; +use cranelift_codegen::isa::TargetIsa; use cranelift_control::ControlPlane; use env::ModuleEnv; +use serde::de::DeserializeOwned; +use serde_derive::Deserialize; use similar::TextDiff; use std::{fmt::Write, path::Path}; @@ -53,23 +57,82 @@ pub fn run(path: &Path, wat: &str) -> Result<()> { cranelift_wasm::translate_module(&wasm, &mut env) .context("failed to translate the test case into CLIF")?; + let kind = if config.compile { + TestKind::Compile + } else if config.optimize { + TestKind::Optimize + } else { + TestKind::Clif + }; + run_functions( + path, + wat, + isa, + kind, + env.inner.info.function_bodies.values().as_slice(), + ) +} + +/// Parse test configuration from the specified test, comments starting with +/// `;;!`. +pub fn parse_test_config(wat: &str) -> Result +where + T: DeserializeOwned, +{ + // The test config source is the leading lines of the WAT file that are + // prefixed with `;;!`. + let config_lines: Vec<_> = wat + .lines() + .take_while(|l| l.starts_with(";;!")) + .map(|l| &l[3..]) + .collect(); + let config_text = config_lines.join("\n"); + + toml::from_str(&config_text).context("failed to parse the test configuration") +} + +/// Which kind of test is being performed. +#[derive(Default, Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum TestKind { + /// Test the CLIF output, raw from translation. + #[default] + Clif, + /// Compile output to machine code. + Compile, + /// Test the CLIF output, optimized. + Optimize, +} + +/// Assert that `wat` contains the test expectations necessary for `funcs`. +pub fn run_functions( + path: &Path, + wat: &str, + isa: &dyn TargetIsa, + kind: TestKind, + funcs: &[Function], +) -> Result<()> { let mut actual = String::new(); - for (_index, func) in env.inner.info.function_bodies.iter() { - if config.compile { - let mut ctx = cranelift_codegen::Context::for_function(func.clone()); - ctx.set_disasm(true); - let code = ctx - .compile(isa, &mut Default::default()) - .map_err(|e| crate::pretty_anyhow_error(&e.func, e.inner))?; - writeln!(&mut actual, "function {}:", func.name).unwrap(); - writeln!(&mut actual, "{}", code.vcode.as_ref().unwrap()).unwrap(); - } else if config.optimize { - let mut ctx = cranelift_codegen::Context::for_function(func.clone()); - ctx.optimize(isa, &mut ControlPlane::default()) - .map_err(|e| crate::pretty_anyhow_error(&ctx.func, e))?; - writeln!(&mut actual, "{}", ctx.func.display()).unwrap(); - } else { - writeln!(&mut actual, "{}", func.display()).unwrap(); + for func in funcs { + match kind { + TestKind::Compile => { + let mut ctx = cranelift_codegen::Context::for_function(func.clone()); + ctx.set_disasm(true); + let code = ctx + .compile(isa, &mut Default::default()) + .map_err(|e| crate::pretty_anyhow_error(&e.func, e.inner))?; + writeln!(&mut actual, "function {}:", func.name).unwrap(); + writeln!(&mut actual, "{}", code.vcode.as_ref().unwrap()).unwrap(); + } + TestKind::Optimize => { + let mut ctx = cranelift_codegen::Context::for_function(func.clone()); + ctx.optimize(isa, &mut ControlPlane::default()) + .map_err(|e| crate::pretty_anyhow_error(&ctx.func, e))?; + writeln!(&mut actual, "{}", ctx.func.display()).unwrap(); + } + TestKind::Clif => { + writeln!(&mut actual, "{}", func.display()).unwrap(); + } } } let actual = actual.trim(); diff --git a/tests/disas.rs b/tests/disas.rs new file mode 100644 index 000000000000..26372b5eeef4 --- /dev/null +++ b/tests/disas.rs @@ -0,0 +1,209 @@ +//! A filetest-lookalike test suite using Cranelift tooling but built on +//! Wasmtime's code generator. +//! +//! This test will read the `tests/disas/*` directory and interpret all files in +//! that directory as a test. Each test must be in the wasm text format and +//! start with directives that look like: +//! +//! ```wasm +//! ;;! target = "x86_64" +//! ;;! compile = true +//! +//! (module +//! ;; ... +//! ) +//! ``` +//! +//! Tests must configure a `target` and then can optionally specify a kind of +//! test: +//! +//! * No specifier - the output CLIF from translation is inspected. +//! * `optimize = true` - CLIF is emitted, then optimized, then inspected. +//! * `compile = true` - backends are run to produce machine code and that's inspected. +//! +//! Tests may also have a `flags` directive which are CLI flags to Wasmtime +//! itself: +//! +//! ```wasm +//! ;;! target = "x86_64" +//! ;;! flags = "-O opt-level=s" +//! +//! (module +//! ;; ... +//! ) +//! ``` +//! +//! Flags are parsed by the `wasmtime_cli_flags` crate to build a `Config`. +//! +//! Configuration of tests is prefixed with `;;!` comments and must be present +//! at the start of the file. These comments are then parsed as TOML and +//! deserialized into `TestConfig` in this crate. + +use anyhow::{bail, Context, Result}; +use clap::Parser; +use cranelift_codegen::isa::{lookup_by_name, TargetIsa}; +use cranelift_codegen::settings::{Configurable, Flags, SetError}; +use cranelift_filetests::test_wasm::{parse_test_config, run_functions, TestKind}; +use serde_derive::Deserialize; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use tempfile::TempDir; +use wasmtime::{Engine, OptLevel}; +use wasmtime_cli_flags::CommonOptions; + +fn main() { + if cfg!(miri) { + return; + } + // First discover all tests ... + let mut tests = Vec::new(); + for file in std::fs::read_dir("./tests/disas").unwrap() { + tests.push(file.unwrap().path()); + } + + // ... then run all tests! + for test in tests.iter() { + run_test(&test) + .with_context(|| format!("failed to run tests {test:?}")) + .unwrap(); + } +} + +fn run_test(path: &Path) -> Result<()> { + let mut test = Test::new(path)?; + let clifs = test.generate_clif()?; + let isa = test.build_target_isa()?; + + // Parse the text format CLIF which is emitted by Wasmtime back into + // in-memory data structures. + let functions = clifs + .iter() + .map(|clif| { + let mut funcs = cranelift_reader::parse_functions(clif)?; + if funcs.len() != 1 { + bail!("expected one function per clif"); + } + Ok(funcs.remove(0)) + }) + .collect::>>()?; + + // And finally, use `cranelift_filetests` to perform the rest of the test. + run_functions( + &test.path, + &test.contents, + &*isa, + test.config.test, + &functions, + )?; + + Ok(()) +} +#[derive(Debug, Deserialize)] +struct TestConfig { + target: String, + #[serde(default)] + test: TestKind, + flags: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(untagged)] +enum TestConfigFlags { + SpaceSeparated(String), + List(Vec), +} + +struct Test { + path: PathBuf, + contents: String, + opts: CommonOptions, + config: TestConfig, +} + +impl Test { + /// Parse the contents of `path` looking for directive-based comments + /// starting with `;;!` near the top of the file. + fn new(path: &Path) -> Result { + let contents = + std::fs::read_to_string(path).with_context(|| format!("failed to read {path:?}"))?; + let config: TestConfig = + parse_test_config(&contents).context("failed to parse test configuration as TOML")?; + let mut flags = vec!["wasmtime"]; + match &config.flags { + Some(TestConfigFlags::SpaceSeparated(s)) => flags.extend(s.split_whitespace()), + Some(TestConfigFlags::List(s)) => flags.extend(s.iter().map(|s| s.as_str())), + None => {} + } + let opts = wasmtime_cli_flags::CommonOptions::try_parse_from(&flags)?; + + Ok(Test { + path: path.to_path_buf(), + config, + opts, + contents, + }) + } + + /// Generates CLIF for all the wasm functions in this test. + fn generate_clif(&mut self) -> Result> { + // Use wasmtime::Config with its `emit_clif` option to get Wasmtime's + // code generator to jettison CLIF out the back. + let tempdir = TempDir::new().context("failed to make a tempdir")?; + let mut config = self.opts.config(Some(&self.config.target))?; + config.emit_clif(tempdir.path()); + let engine = Engine::new(&config).context("failed to create engine")?; + let module = wat::parse_file(&self.path)?; + engine + .precompile_module(&module) + .context("failed to compile module")?; + + // Read all `*.clif` files from the clif directory that the compilation + // process just emitted. + let mut clifs = Vec::new(); + for entry in tempdir + .path() + .read_dir() + .context("failed to read tempdir")? + { + let entry = entry.context("failed to iterate over tempdir")?; + let path = entry.path(); + let clif = std::fs::read_to_string(&path) + .with_context(|| format!("failed to read clif file {path:?}"))?; + clifs.push(clif); + } + clifs.sort(); + Ok(clifs) + } + + /// Use the test configuration present with CLI flags to build a + /// `TargetIsa` to compile/optimize the CLIF. + fn build_target_isa(&self) -> Result> { + let mut builder = lookup_by_name(&self.config.target)?; + let mut flags = cranelift_codegen::settings::builder(); + let opt_level = match self.opts.opts.opt_level { + None | Some(OptLevel::Speed) => "speed", + Some(OptLevel::SpeedAndSize) => "speed_and_size", + Some(OptLevel::None) => "none", + _ => unreachable!(), + }; + flags.set("opt_level", opt_level)?; + for (key, val) in self.opts.codegen.cranelift.iter() { + let key = &key.replace("-", "_"); + let target_res = match val { + Some(val) => builder.set(key, val), + None => builder.enable(key), + }; + match target_res { + Ok(()) => continue, + Err(SetError::BadName(_)) => {} + Err(e) => bail!(e), + } + match val { + Some(val) => flags.set(key, val)?, + None => flags.enable(key)?, + } + } + let isa = builder.finish(Flags::new(flags))?; + Ok(isa) + } +} diff --git a/tests/disas/aarch64-relaxed-simd.wat b/tests/disas/aarch64-relaxed-simd.wat new file mode 100644 index 000000000000..5276f36c7d67 --- /dev/null +++ b/tests/disas/aarch64-relaxed-simd.wat @@ -0,0 +1,141 @@ +;;! target = "aarch64" +;;! test = "compile" + +(module + (func (param v128) (result v128) + local.get 0 + i32x4.relaxed_trunc_f32x4_s + ) + + (func (param v128) (result v128) + local.get 0 + i32x4.relaxed_trunc_f32x4_u + ) + + (func (param v128) (result v128) + local.get 0 + i32x4.relaxed_trunc_f64x2_s_zero + ) + + (func (param v128) (result v128) + local.get 0 + i32x4.relaxed_trunc_f64x2_u_zero + ) + + (func (param v128 v128) (result v128) + local.get 0 + local.get 1 + i16x8.relaxed_dot_i8x16_i7x16_s + ) + + (func (param v128 v128 v128) (result v128) + local.get 0 + local.get 1 + local.get 2 + i32x4.relaxed_dot_i8x16_i7x16_add_s + ) +) + +;; function u0:0: +;; stp fp, lr, [sp, #-16]! +;; unwind PushFrameRegs { offset_upward_to_caller_sp: 16 } +;; mov fp, sp +;; ldr x16, [x0, #8] +;; ldr x16, [x16] +;; subs xzr, sp, x16, UXTX +;; b.lo #trap=stk_ovf +;; unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 } +;; block0: +;; b label1 +;; block1: +;; fcvtzs v0.4s, v0.4s +;; ldp fp, lr, [sp], #16 +;; ret +;; +;; function u0:1: +;; stp fp, lr, [sp, #-16]! +;; unwind PushFrameRegs { offset_upward_to_caller_sp: 16 } +;; mov fp, sp +;; ldr x16, [x0, #8] +;; ldr x16, [x16] +;; subs xzr, sp, x16, UXTX +;; b.lo #trap=stk_ovf +;; unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 } +;; block0: +;; b label1 +;; block1: +;; fcvtzu v0.4s, v0.4s +;; ldp fp, lr, [sp], #16 +;; ret +;; +;; function u0:2: +;; stp fp, lr, [sp, #-16]! +;; unwind PushFrameRegs { offset_upward_to_caller_sp: 16 } +;; mov fp, sp +;; ldr x16, [x0, #8] +;; ldr x16, [x16] +;; subs xzr, sp, x16, UXTX +;; b.lo #trap=stk_ovf +;; unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 } +;; block0: +;; b label1 +;; block1: +;; fcvtzs v6.2d, v0.2d +;; sqxtn v0.2s, v6.2d +;; ldp fp, lr, [sp], #16 +;; ret +;; +;; function u0:3: +;; stp fp, lr, [sp, #-16]! +;; unwind PushFrameRegs { offset_upward_to_caller_sp: 16 } +;; mov fp, sp +;; ldr x16, [x0, #8] +;; ldr x16, [x16] +;; subs xzr, sp, x16, UXTX +;; b.lo #trap=stk_ovf +;; unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 } +;; block0: +;; b label1 +;; block1: +;; fcvtzu v6.2d, v0.2d +;; uqxtn v0.2s, v6.2d +;; ldp fp, lr, [sp], #16 +;; ret +;; +;; function u0:4: +;; stp fp, lr, [sp, #-16]! +;; unwind PushFrameRegs { offset_upward_to_caller_sp: 16 } +;; mov fp, sp +;; ldr x16, [x0, #8] +;; ldr x16, [x16] +;; subs xzr, sp, x16, UXTX +;; b.lo #trap=stk_ovf +;; unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 } +;; block0: +;; b label1 +;; block1: +;; smull v16.8h, v0.8b, v1.8b +;; smull2 v17.8h, v0.16b, v1.16b +;; addp v0.8h, v16.8h, v17.8h +;; ldp fp, lr, [sp], #16 +;; ret +;; +;; function u0:5: +;; stp fp, lr, [sp, #-16]! +;; unwind PushFrameRegs { offset_upward_to_caller_sp: 16 } +;; mov fp, sp +;; ldr x16, [x0, #8] +;; ldr x16, [x16] +;; subs xzr, sp, x16, UXTX +;; b.lo #trap=stk_ovf +;; unwind DefineNewFrame { offset_upward_to_caller_sp: 16, offset_downward_to_clobbers: 0 } +;; block0: +;; b label1 +;; block1: +;; smull v19.8h, v0.8b, v1.8b +;; smull2 v20.8h, v0.16b, v1.16b +;; addp v19.8h, v19.8h, v20.8h +;; saddlp v19.4s, v19.8h +;; add v0.4s, v19.4s, v2.4s +;; ldp fp, lr, [sp], #16 +;; ret diff --git a/tests/disas/basic-wat-test.wat b/tests/disas/basic-wat-test.wat new file mode 100644 index 000000000000..35663b6d64b1 --- /dev/null +++ b/tests/disas/basic-wat-test.wat @@ -0,0 +1,37 @@ +;;! target = "x86_64" + +(module + (memory 0) + (func (param i32 i32) (result i32) + local.get 0 + i32.load + local.get 1 + i32.load + i32.add)) + +;; function u0:0(i64 vmctx, i64, i32, i32) -> i32 fast { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1 +;; gv3 = vmctx +;; gv4 = load.i64 notrap aligned readonly checked gv3+80 +;; sig0 = (i64 vmctx, i32 uext, i32 uext, i32 uext) -> i32 uext system_v +;; sig1 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v +;; sig2 = (i64 vmctx, i32 uext) -> i32 uext system_v +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32, v3: i32): +;; @0021 v5 = uextend.i64 v2 +;; @0021 v6 = global_value.i64 gv4 +;; @0021 v7 = iadd v6, v5 +;; @0021 v8 = load.i32 little heap v7 +;; @0026 v9 = uextend.i64 v3 +;; @0026 v10 = global_value.i64 gv4 +;; @0026 v11 = iadd v10, v9 +;; @0026 v12 = load.i32 little heap v11 +;; @0029 v13 = iadd v8, v12 +;; @002a jump block1(v13) +;; +;; block1(v4: i32): +;; @002a return v4 +;; } diff --git a/cranelift/filetests/filetests/wasm/byteswap.wat b/tests/disas/byteswap.wat similarity index 55% rename from cranelift/filetests/filetests/wasm/byteswap.wat rename to tests/disas/byteswap.wat index 81ccba004455..a2f559de0c97 100644 --- a/cranelift/filetests/filetests/wasm/byteswap.wat +++ b/tests/disas/byteswap.wat @@ -1,6 +1,5 @@ ;;! target = "x86_64" -;;! optimize = true -;;! settings = ["opt_level=speed"] +;;! test = "optimize" (module (func (export "bswap32") (param i32) (result i32) @@ -72,20 +71,34 @@ ) ) -;; function u0:0(i32, i64 vmctx) -> i32 fast { -;; block0(v0: i32, v1: i64): +;; function u0:0(i64 vmctx, i64, i32) -> i32 fast { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1 +;; sig0 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v +;; sig1 = (i64 vmctx, i32 uext) -> i32 uext system_v +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32): ;; @0057 jump block1 ;; ;; block1: -;; v18 = bswap.i32 v0 -;; @0057 return v18 +;; v19 = bswap.i32 v2 +;; @0057 return v19 ;; } ;; -;; function u0:1(i64, i64 vmctx) -> i64 fast { -;; block0(v0: i64, v1: i64): +;; function u0:1(i64 vmctx, i64, i64) -> i64 fast { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1 +;; sig0 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v +;; sig1 = (i64 vmctx, i32 uext) -> i32 uext system_v +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i64): ;; @00ad jump block1 ;; ;; block1: -;; v38 = bswap.i64 v0 -;; @00ad return v38 +;; v39 = bswap.i64 v2 +;; @00ad return v39 ;; } diff --git a/tests/disas/duplicate-loads-dynamic-memory.wat b/tests/disas/duplicate-loads-dynamic-memory.wat new file mode 100644 index 000000000000..3fb25eb4d06e --- /dev/null +++ b/tests/disas/duplicate-loads-dynamic-memory.wat @@ -0,0 +1,89 @@ +;;! target = "x86_64" +;;! test = "optimize" +;;! flags = [ +;;! "-Ccranelift-enable-heap-access-spectre-mitigation", +;;! "-Oopt-level=s", +;;! "-Ostatic-memory-maximum-size=0", +;;! ] + +(module + (memory (export "memory") 0) + (func (export "load-without-offset") (param i32) (result i32 i32) + local.get 0 + i32.load + local.get 0 + i32.load + ) + (func (export "load-with-offset") (param i32) (result i32 i32) + local.get 0 + i32.load offset=1234 + local.get 0 + i32.load offset=1234 + ) +) + +;; function u0:0(i64 vmctx, i64, i32) -> i32, i32 fast { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1 +;; gv3 = vmctx +;; gv4 = load.i64 notrap aligned gv3+88 +;; gv5 = load.i64 notrap aligned checked gv3+80 +;; sig0 = (i64 vmctx, i32 uext, i32 uext, i32 uext) -> i32 uext system_v +;; sig1 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v +;; sig2 = (i64 vmctx, i32 uext) -> i32 uext system_v +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32): +;; v21 -> v0 +;; v22 -> v0 +;; v23 -> v0 +;; v24 -> v0 +;; @0057 v6 = load.i64 notrap aligned v0+88 +;; @0057 v8 = load.i64 notrap aligned checked v0+80 +;; @0057 v5 = uextend.i64 v2 +;; @0057 v7 = icmp ugt v5, v6 +;; @0057 v10 = iconst.i64 0 +;; @0057 v9 = iadd v8, v5 +;; @0057 v11 = select_spectre_guard v7, v10, v9 ; v10 = 0 +;; @0057 v12 = load.i32 little heap v11 +;; v3 -> v12 +;; @005f jump block1 +;; +;; block1: +;; @005f return v12, v12 +;; } +;; +;; function u0:1(i64 vmctx, i64, i32) -> i32, i32 fast { +;; gv0 = vmctx +;; gv1 = load.i64 notrap aligned readonly gv0+8 +;; gv2 = load.i64 notrap aligned gv1 +;; gv3 = vmctx +;; gv4 = load.i64 notrap aligned gv3+88 +;; gv5 = load.i64 notrap aligned checked gv3+80 +;; sig0 = (i64 vmctx, i32 uext, i32 uext, i32 uext) -> i32 uext system_v +;; sig1 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v +;; sig2 = (i64 vmctx, i32 uext) -> i32 uext system_v +;; stack_limit = gv2 +;; +;; block0(v0: i64, v1: i64, v2: i32): +;; v25 -> v0 +;; v26 -> v0 +;; v27 -> v0 +;; v28 -> v0 +;; @0064 v6 = load.i64 notrap aligned v0+88 +;; @0064 v8 = load.i64 notrap aligned checked v0+80 +;; @0064 v5 = uextend.i64 v2 +;; @0064 v7 = icmp ugt v5, v6 +;; @0064 v12 = iconst.i64 0 +;; @0064 v9 = iadd v8, v5 +;; @0064 v10 = iconst.i64 1234 +;; @0064 v11 = iadd v9, v10 ; v10 = 1234 +;; @0064 v13 = select_spectre_guard v7, v12, v11 ; v12 = 0 +;; @0064 v14 = load.i32 little heap v13 +;; v3 -> v14 +;; @006e jump block1 +;; +;; block1: +;; @006e return v14, v14 +;; }