From f2e1eaa84720027a48e2be4468b0b9a36d158db3 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 12 Dec 2022 11:31:29 -0800 Subject: [PATCH] cranelift-filetest: Add support for Wasm-to-CLIF translation filetests (#5412) This adds support for `.wat` tests in `cranelift-filetest`. The test runner translates the WAT to Wasm and then uses `cranelift-wasm` to translate the Wasm to CLIF. These tests are always precise output tests. The test expectations can be updated by running tests with the `CRANELIFT_TEST_BLESS=1` environment variable set, similar to our compile precise output tests. The test's expected output is contained in the last comment in the test file. The tests allow for configuring the kinds of heaps used to implement Wasm linear memory via TOML in a `;;!` comment at the start of the test. To get ISA and Cranelift flags parsing available in the filetests crate, I had to move the `parse_sets_and_triple` helper from the `cranelift-tools` binary crate to the `cranelift-reader` crate, where I think it logically fits. Additionally, I had to make some more bits of `cranelift-wasm`'s dummy environment `pub` so that I could properly wrap and compose it with the environment used for the `.wat` tests. I don't think this is a big deal, but if we eventually want to clean this stuff up, we can probably remove the dummy environments completely, remove `translate_module`, and fold them into these new test environments and test runner (since Wasmtime isn't using those things anyways). --- Cargo.lock | 7 + Cargo.toml | 2 +- cranelift/filetests/Cargo.toml | 6 + .../filetests/wasm/basic-wat-test.wat | 42 ++ cranelift/filetests/src/lib.rs | 1 + cranelift/filetests/src/runone.rs | 6 + cranelift/filetests/src/test_wasm.rs | 110 ++++ cranelift/filetests/src/test_wasm/config.rs | 204 ++++++ cranelift/filetests/src/test_wasm/env.rs | 583 ++++++++++++++++++ cranelift/reader/Cargo.toml | 1 + cranelift/reader/src/lib.rs | 86 +++ cranelift/src/bugpoint.rs | 4 +- cranelift/src/compile.rs | 4 +- cranelift/src/souper_harvest.rs | 2 +- cranelift/src/utils.rs | 88 --- cranelift/src/wasm.rs | 2 +- cranelift/wasm/src/environ/dummy.rs | 18 +- cranelift/wasm/src/environ/mod.rs | 4 +- cranelift/wasm/src/lib.rs | 3 +- 19 files changed, 1069 insertions(+), 104 deletions(-) create mode 100644 cranelift/filetests/filetests/wasm/basic-wat-test.wat create mode 100644 cranelift/filetests/src/test_wasm.rs create mode 100644 cranelift/filetests/src/test_wasm/config.rs create mode 100644 cranelift/filetests/src/test_wasm/env.rs diff --git a/Cargo.lock b/Cargo.lock index 73154abbac..ce77be8677 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -591,6 +591,7 @@ name = "cranelift-filetests" version = "0.0.0" dependencies = [ "anyhow", + "cranelift", "cranelift-codegen", "cranelift-frontend", "cranelift-interpreter", @@ -599,14 +600,19 @@ dependencies = [ "cranelift-native", "cranelift-preopt", "cranelift-reader", + "cranelift-wasm", "file-per-thread-logger", "filecheck", "gimli", "log", "num_cpus", + "serde", "similar", "target-lexicon", "thiserror", + "toml", + "wasmparser", + "wat", ] [[package]] @@ -716,6 +722,7 @@ dependencies = [ name = "cranelift-reader" version = "0.92.0" dependencies = [ + "anyhow", "cranelift-codegen", "smallvec", "target-lexicon", diff --git a/Cargo.toml b/Cargo.toml index 37515c9e64..45d9031efd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,7 +154,7 @@ cranelift = { path = "cranelift/umbrella", version = "0.92.0" } winch-codegen = { path = "winch/codegen", version = "=0.3.0" } -target-lexicon = { version = "0.12.3", default-features = false } +target-lexicon = { version = "0.12.3", default-features = false, features = ["std"] } anyhow = "1.0.22" wasmparser = "0.95.0" wat = "1.0.52" diff --git a/cranelift/filetests/Cargo.toml b/cranelift/filetests/Cargo.toml index c770d1d466..872326ad56 100644 --- a/cranelift/filetests/Cargo.toml +++ b/cranelift/filetests/Cargo.toml @@ -27,3 +27,9 @@ target-lexicon = { workspace = true } thiserror = { workspace = true } anyhow = { workspace = true } similar = "2.1.0" +wat.workspace = true +toml = "0.5.9" +serde = "1.0.94" +cranelift-wasm.workspace = true +wasmparser.workspace = true +cranelift.workspace = true diff --git a/cranelift/filetests/filetests/wasm/basic-wat-test.wat b/cranelift/filetests/filetests/wasm/basic-wat-test.wat new file mode 100644 index 0000000000..d9dd38e0ad --- /dev/null +++ b/cranelift/filetests/filetests/wasm/basic-wat-test.wat @@ -0,0 +1,42 @@ +;;! 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 +;; heap0 = static gv1, min 0, bound 4096, offset_guard 0xffff_ffff, index_type i32 +;; +;; block0(v0: i32, v1: i32, v2: i64): +;; @0021 v4 = heap_addr.i64 heap0, v0, 0, 4 +;; @0021 v5 = load.i32 little heap v4 +;; @0026 v6 = heap_addr.i64 heap0, v1, 0, 4 +;; @0026 v7 = load.i32 little heap v6 +;; @0029 v8 = iadd v5, v7 +;; @002a jump block1(v8) +;; +;; block1(v3: i32): +;; @002a return v3 +;; } diff --git a/cranelift/filetests/src/lib.rs b/cranelift/filetests/src/lib.rs index 8bff0eb29d..5fd5255df6 100644 --- a/cranelift/filetests/src/lib.rs +++ b/cranelift/filetests/src/lib.rs @@ -54,6 +54,7 @@ mod test_simple_gvn; mod test_simple_preopt; mod test_unwind; mod test_verifier; +mod test_wasm; /// Main entry point for `clif-util test`. /// diff --git a/cranelift/filetests/src/runone.rs b/cranelift/filetests/src/runone.rs index fe8e04b4ee..885b6742e2 100644 --- a/cranelift/filetests/src/runone.rs +++ b/cranelift/filetests/src/runone.rs @@ -29,6 +29,12 @@ pub fn run( let started = time::Instant::now(); let buffer = fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?; + + if path.extension().map_or(false, |ext| ext == "wat") { + crate::test_wasm::run(path, &buffer)?; + return Ok(started.elapsed()); + } + let options = ParseOptions { target, passes, diff --git a/cranelift/filetests/src/test_wasm.rs b/cranelift/filetests/src/test_wasm.rs new file mode 100644 index 0000000000..a6cb47b2da --- /dev/null +++ b/cranelift/filetests/src/test_wasm.rs @@ -0,0 +1,110 @@ +//! Test runner for `.wat` files to exercise CLIF-to-Wasm translations. + +mod config; +mod env; + +use anyhow::{bail, ensure, Context, Result}; +use config::TestConfig; +use env::ModuleEnv; +use similar::TextDiff; +use std::{fmt::Write, path::Path}; + +/// Run one `.wat` test. +pub fn run(path: &Path, wat: &str) -> Result<()> { + debug_assert_eq!(path.extension().unwrap_or_default(), "wat"); + + // 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"); + + let config: TestConfig = + toml::from_str(&config_text).context("failed to parse the test configuration")?; + + config + .validate() + .context("test configuration is malformed")?; + + let wasm = wat::parse_str(wat).context("failed to parse the test WAT")?; + wasmparser::validate(&wasm).context("test WAT failed to validate")?; + + let parsed = cranelift_reader::parse_sets_and_triple(&config.settings, &config.target) + .context("invalid ISA target or Cranelift settings")?; + let fisa = parsed.as_fisa(); + ensure!( + fisa.isa.is_some(), + "Running `.wat` tests requires specifying an ISA" + ); + + let mut env = ModuleEnv::new(fisa.isa.as_ref().unwrap().frontend_config(), config); + + cranelift_wasm::translate_module(&wasm, &mut env) + .context("failed to translate the test case into CLIF")?; + + let mut actual = String::new(); + for (_index, func) in env.inner.info.function_bodies.iter() { + writeln!(&mut actual, "{}", func.display()).unwrap(); + } + let actual = actual.trim(); + log::debug!("=== actual ===\n{actual}"); + + // The test's expectation is the final comment. + let mut expected_lines: Vec<_> = wat + .lines() + .rev() + .take_while(|l| l.starts_with(";;")) + .map(|l| { + if l.starts_with(";; ") { + &l[3..] + } else { + &l[2..] + } + }) + .collect(); + expected_lines.reverse(); + let expected = expected_lines.join("\n"); + let expected = expected.trim(); + log::debug!("=== expected ===\n{expected}"); + + if actual == expected { + return Ok(()); + } + + if std::env::var("CRANELIFT_TEST_BLESS").unwrap_or_default() == "1" { + let old_expectation_line_count = wat + .lines() + .rev() + .take_while(|l| l.starts_with(";;")) + .count(); + let old_wat_line_count = wat.lines().count(); + let new_wat_lines: Vec<_> = wat + .lines() + .take(old_wat_line_count - old_expectation_line_count) + .map(|l| l.to_string()) + .chain(actual.lines().map(|l| { + if l.is_empty() { + ";;".to_string() + } else { + format!(";; {l}") + } + })) + .collect(); + std::fs::write(path, new_wat_lines.join("\n")) + .with_context(|| format!("failed to write file: {}", path.display()))?; + return Ok(()); + } + + bail!( + "Did not get the expected CLIF translation:\n\n\ + {}\n\n\ + Note: You can re-run with the `CRANELIFT_TEST_BLESS=1` environment\n\ + variable set to update test expectations.", + TextDiff::from_lines(expected, actual) + .unified_diff() + .header("expected", "actual") + ) +} diff --git a/cranelift/filetests/src/test_wasm/config.rs b/cranelift/filetests/src/test_wasm/config.rs new file mode 100644 index 0000000000..ec14780b97 --- /dev/null +++ b/cranelift/filetests/src/test_wasm/config.rs @@ -0,0 +1,204 @@ +//! Configuration of `.wat` tests. +//! +//! The config is the leading `;;!` comments in the WAT. It is in TOML. + +use anyhow::{bail, ensure, Result}; +use cranelift_codegen::ir; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TestConfig { + #[serde(default)] + pub debug_info: bool, + + #[serde(default)] + pub target: String, + + #[serde(default)] + pub settings: Vec, + + #[serde(default)] + pub globals: BTreeMap, + + #[serde(default)] + pub heaps: Vec, +} + +impl TestConfig { + pub fn validate(&self) -> Result<()> { + for global in self.globals.values() { + ensure!( + global.vmctx || global.load.is_some(), + "global must be either `vmctx` or a `load`" + ); + ensure!( + !(global.vmctx && global.load.is_some()), + "global cannot be both a `vmctx` and a `load`" + ); + + if let Some(load) = &global.load { + ensure!( + self.globals.contains_key(&load.base), + "global's load base must be another global" + ); + } + } + + for heap in &self.heaps { + ensure!( + self.globals.contains_key(&heap.base), + "heap base must be a declared global" + ); + + match heap.style.kind.as_str() { + "static" => match &heap.style.bound { + toml::value::Value::Integer(x) => { + ensure!(*x >= 0, "static heap bound cannot be negative") + } + _ => bail!("static heap bounds must be integers"), + }, + "dynamic" => match &heap.style.bound { + toml::value::Value::String(g) => { + ensure!( + self.globals.contains_key(g), + "dynamic heap bound must be a declared global" + ) + } + _ => bail!("dynamic heap bounds must be strings"), + }, + other => { + bail!( + "heap style must be 'static' or 'dynamic', found '{}'", + other + ) + } + } + } + + Ok(()) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TestGlobal { + #[serde(rename = "type")] + pub type_: String, + + #[serde(default)] + pub vmctx: bool, + + #[serde(default)] + pub load: Option, +} + +impl TestGlobal { + pub fn to_ir( + &self, + name_to_ir_global: &BTreeMap, + ) -> ir::GlobalValueData { + if self.vmctx { + ir::GlobalValueData::VMContext + } else if let Some(load) = &self.load { + ir::GlobalValueData::Load { + base: name_to_ir_global[&load.base], + offset: i32::try_from(load.offset).unwrap().into(), + global_type: match self.type_.as_str() { + "i32" => ir::types::I32, + "i64" => ir::types::I64, + other => panic!("test globals cannot be of type '{other}'"), + }, + readonly: load.readonly, + } + } else { + unreachable!() + } + } + + pub fn dependencies<'a>(&'a self) -> impl Iterator + 'a { + let mut deps = None; + if let Some(load) = &self.load { + deps = Some(load.base.as_str()); + } + deps.into_iter() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TestGlobalLoad { + pub base: String, + + #[serde(default)] + pub offset: u32, + + #[serde(default)] + pub readonly: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TestHeap { + pub base: String, + + #[serde(default)] + pub min_size: u64, + + #[serde(default)] + pub offset_guard_size: u64, + + pub style: TestHeapStyle, + + pub index_type: String, +} + +impl TestHeap { + pub fn to_ir(&self, name_to_ir_global: &BTreeMap) -> ir::HeapData { + ir::HeapData { + base: name_to_ir_global[&self.base], + min_size: self.min_size.into(), + offset_guard_size: self.offset_guard_size.into(), + style: self.style.to_ir(name_to_ir_global), + index_type: match self.index_type.as_str() { + "i32" => ir::types::I32, + "i64" => ir::types::I64, + other => panic!("heap indices may only be i32 or i64, found '{other}'"), + }, + } + } + + pub fn dependencies<'a>(&'a self) -> impl Iterator + 'a { + let mut deps = vec![self.base.as_str()]; + if self.style.kind == "dynamic" { + deps.push(match &self.style.bound { + toml::Value::String(g) => g.as_str(), + _ => unreachable!(), + }); + } + deps.into_iter() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TestHeapStyle { + pub kind: String, + pub bound: toml::value::Value, +} + +impl TestHeapStyle { + pub fn to_ir(&self, name_to_ir_global: &BTreeMap) -> ir::HeapStyle { + match self.kind.as_str() { + "static" => ir::HeapStyle::Static { + bound: match &self.bound { + toml::Value::Integer(x) => u64::try_from(*x).unwrap().into(), + _ => unreachable!(), + }, + }, + "dynamic" => ir::HeapStyle::Dynamic { + bound_gv: match &self.bound { + toml::Value::String(g) => name_to_ir_global[g], + _ => unreachable!(), + }, + }, + _ => unreachable!(), + } + } +} diff --git a/cranelift/filetests/src/test_wasm/env.rs b/cranelift/filetests/src/test_wasm/env.rs new file mode 100644 index 0000000000..7debdb3a8d --- /dev/null +++ b/cranelift/filetests/src/test_wasm/env.rs @@ -0,0 +1,583 @@ +//! `cranelift_wasm` environments for translating Wasm to CLIF. +//! +//! Mostly wrappers around the dummy environments, but also supports +//! pre-configured heaps. + +use std::collections::{BTreeMap, HashSet}; + +use super::config::TestConfig; +use cranelift::prelude::EntityRef; +use cranelift_codegen::{ir, isa::TargetFrontendConfig}; +use cranelift_wasm::{ + DummyEnvironment, FuncEnvironment, FuncIndex, ModuleEnvironment, TargetEnvironment, +}; + +pub struct ModuleEnv { + pub inner: DummyEnvironment, + pub config: TestConfig, +} + +impl ModuleEnv { + pub fn new(frontend_config: TargetFrontendConfig, config: TestConfig) -> Self { + let inner = DummyEnvironment::new(frontend_config, config.debug_info); + Self { inner, config } + } +} + +impl<'data> ModuleEnvironment<'data> for ModuleEnv { + fn define_function_body( + &mut self, + mut validator: wasmparser::FuncValidator, + body: wasmparser::FunctionBody<'data>, + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .func_bytecode_sizes + .push(body.get_binary_reader().bytes_remaining()); + + let func = { + let mut func_environ = FuncEnv::new( + &self.inner.info, + self.inner.expected_reachability.clone(), + self.config.clone(), + ); + let func_index = FuncIndex::new( + self.inner.get_num_func_imports() + self.inner.info.function_bodies.len(), + ); + + let sig = func_environ + .inner + .vmctx_sig(self.inner.get_func_type(func_index)); + let mut func = ir::Function::with_name_signature( + ir::UserFuncName::user(0, func_index.as_u32()), + sig, + ); + + if self.inner.debug_info { + func.collect_debug_info(); + } + + self.inner + .trans + .translate_body(&mut validator, body, &mut func, &mut func_environ)?; + func + }; + + self.inner.info.function_bodies.push(func); + + Ok(()) + } + + // ================================================================ + // ====== Everything below here is delegated to `self.inner` ====== + // ================================================================ + + fn declare_type_func( + &mut self, + wasm_func_type: cranelift_wasm::WasmFuncType, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_type_func(wasm_func_type) + } + + fn declare_func_import( + &mut self, + index: cranelift_wasm::TypeIndex, + module: &'data str, + field: &'data str, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_func_import(index, module, field) + } + + fn declare_table_import( + &mut self, + table: cranelift_wasm::Table, + module: &'data str, + field: &'data str, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_table_import(table, module, field) + } + + fn declare_memory_import( + &mut self, + memory: cranelift_wasm::Memory, + module: &'data str, + field: &'data str, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_memory_import(memory, module, field) + } + + fn declare_global_import( + &mut self, + global: cranelift_wasm::Global, + module: &'data str, + field: &'data str, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_global_import(global, module, field) + } + + fn declare_func_type( + &mut self, + index: cranelift_wasm::TypeIndex, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_func_type(index) + } + + fn declare_table(&mut self, table: cranelift_wasm::Table) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_table(table) + } + + fn declare_memory(&mut self, memory: cranelift_wasm::Memory) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_memory(memory) + } + + fn declare_global(&mut self, global: cranelift_wasm::Global) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_global(global) + } + + fn declare_func_export( + &mut self, + func_index: cranelift_wasm::FuncIndex, + name: &'data str, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_func_export(func_index, name) + } + + fn declare_table_export( + &mut self, + table_index: cranelift_wasm::TableIndex, + name: &'data str, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_table_export(table_index, name) + } + + fn declare_memory_export( + &mut self, + memory_index: cranelift_wasm::MemoryIndex, + name: &'data str, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_memory_export(memory_index, name) + } + + fn declare_global_export( + &mut self, + global_index: cranelift_wasm::GlobalIndex, + name: &'data str, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_global_export(global_index, name) + } + + fn declare_start_func( + &mut self, + index: cranelift_wasm::FuncIndex, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_start_func(index) + } + + fn declare_table_elements( + &mut self, + table_index: cranelift_wasm::TableIndex, + base: Option, + offset: u32, + elements: Box<[cranelift_wasm::FuncIndex]>, + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .declare_table_elements(table_index, base, offset, elements) + } + + fn declare_passive_element( + &mut self, + index: cranelift_wasm::ElemIndex, + elements: Box<[cranelift_wasm::FuncIndex]>, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_passive_element(index, elements) + } + + fn declare_passive_data( + &mut self, + data_index: cranelift_wasm::DataIndex, + data: &'data [u8], + ) -> cranelift_wasm::WasmResult<()> { + self.inner.declare_passive_data(data_index, data) + } + + fn declare_data_initialization( + &mut self, + memory_index: cranelift_wasm::MemoryIndex, + base: Option, + offset: u64, + data: &'data [u8], + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .declare_data_initialization(memory_index, base, offset, data) + } +} + +pub struct FuncEnv<'a> { + pub inner: cranelift_wasm::DummyFuncEnvironment<'a>, + pub config: TestConfig, + pub name_to_ir_global: BTreeMap, + pub next_heap: usize, +} + +impl<'a> FuncEnv<'a> { + pub fn new( + mod_info: &'a cranelift_wasm::DummyModuleInfo, + expected_reachability: Option, + config: TestConfig, + ) -> Self { + let inner = cranelift_wasm::DummyFuncEnvironment::new(mod_info, expected_reachability); + Self { + inner, + config, + name_to_ir_global: Default::default(), + next_heap: 0, + } + } +} + +impl<'a> TargetEnvironment for FuncEnv<'a> { + fn target_config(&self) -> TargetFrontendConfig { + self.inner.target_config() + } +} + +impl<'a> FuncEnvironment for FuncEnv<'a> { + fn make_heap( + &mut self, + func: &mut ir::Function, + index: cranelift_wasm::MemoryIndex, + ) -> cranelift_wasm::WasmResult { + if self.next_heap < self.config.heaps.len() { + let heap = &self.config.heaps[self.next_heap]; + self.next_heap += 1; + + // Create all of the globals our test heap depends on in topological + // order. + let mut worklist: Vec<&str> = heap + .dependencies() + .filter(|g| !self.name_to_ir_global.contains_key(*g)) + .collect(); + let mut in_worklist: HashSet<&str> = worklist.iter().copied().collect(); + 'worklist_fixpoint: while let Some(global_name) = worklist.pop() { + let was_in_set = in_worklist.remove(global_name); + debug_assert!(was_in_set); + + let global = &self.config.globals[global_name]; + + // Check that all of this global's dependencies have already + // been created. If not, then enqueue them to be created + // first and re-enqueue this global. + for g in global.dependencies() { + if !self.name_to_ir_global.contains_key(g) { + if in_worklist.contains(&g) { + return Err(cranelift_wasm::WasmError::User(format!( + "dependency cycle between global '{global_name}' and global '{g}'" + ))); + } + + worklist.push(global_name); + let is_new_entry = in_worklist.insert(global_name); + debug_assert!(is_new_entry); + + worklist.push(g); + let is_new_entry = in_worklist.insert(g); + debug_assert!(is_new_entry); + + continue 'worklist_fixpoint; + } + } + + // All of this globals dependencies have already been + // created, we can create it now! + let data = global.to_ir(&self.name_to_ir_global); + let g = func.create_global_value(data); + self.name_to_ir_global.insert(global_name.to_string(), g); + } + + Ok(func.create_heap(heap.to_ir(&self.name_to_ir_global))) + } else { + self.inner.make_heap(func, index) + } + } + + // ================================================================ + // ====== Everything below here is delegated to `self.inner` ====== + // ================================================================ + + fn make_global( + &mut self, + func: &mut ir::Function, + index: cranelift_wasm::GlobalIndex, + ) -> cranelift_wasm::WasmResult { + self.inner.make_global(func, index) + } + + fn make_table( + &mut self, + func: &mut ir::Function, + index: cranelift_wasm::TableIndex, + ) -> cranelift_wasm::WasmResult { + self.inner.make_table(func, index) + } + + fn make_indirect_sig( + &mut self, + func: &mut ir::Function, + index: cranelift_wasm::TypeIndex, + ) -> cranelift_wasm::WasmResult { + self.inner.make_indirect_sig(func, index) + } + + fn make_direct_func( + &mut self, + func: &mut ir::Function, + index: FuncIndex, + ) -> cranelift_wasm::WasmResult { + self.inner.make_direct_func(func, index) + } + + fn translate_call_indirect( + &mut self, + builder: &mut cranelift_frontend::FunctionBuilder, + table_index: cranelift_wasm::TableIndex, + table: ir::Table, + sig_index: cranelift_wasm::TypeIndex, + sig_ref: ir::SigRef, + callee: ir::Value, + call_args: &[ir::Value], + ) -> cranelift_wasm::WasmResult { + self.inner.translate_call_indirect( + builder, + table_index, + table, + sig_index, + sig_ref, + callee, + call_args, + ) + } + + fn translate_memory_grow( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + index: cranelift_wasm::MemoryIndex, + heap: ir::Heap, + val: ir::Value, + ) -> cranelift_wasm::WasmResult { + self.inner.translate_memory_grow(pos, index, heap, val) + } + + fn translate_memory_size( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + index: cranelift_wasm::MemoryIndex, + heap: ir::Heap, + ) -> cranelift_wasm::WasmResult { + self.inner.translate_memory_size(pos, index, heap) + } + + fn translate_memory_copy( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + src_index: cranelift_wasm::MemoryIndex, + src_heap: ir::Heap, + dst_index: cranelift_wasm::MemoryIndex, + dst_heap: ir::Heap, + dst: ir::Value, + src: ir::Value, + len: ir::Value, + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .translate_memory_copy(pos, src_index, src_heap, dst_index, dst_heap, dst, src, len) + } + + fn translate_memory_fill( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + index: cranelift_wasm::MemoryIndex, + heap: ir::Heap, + dst: ir::Value, + val: ir::Value, + len: ir::Value, + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .translate_memory_fill(pos, index, heap, dst, val, len) + } + + fn translate_memory_init( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + index: cranelift_wasm::MemoryIndex, + heap: ir::Heap, + seg_index: u32, + dst: ir::Value, + src: ir::Value, + len: ir::Value, + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .translate_memory_init(pos, index, heap, seg_index, dst, src, len) + } + + fn translate_data_drop( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + seg_index: u32, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.translate_data_drop(pos, seg_index) + } + + fn translate_table_size( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + index: cranelift_wasm::TableIndex, + table: ir::Table, + ) -> cranelift_wasm::WasmResult { + self.inner.translate_table_size(pos, index, table) + } + + fn translate_table_grow( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + table_index: cranelift_wasm::TableIndex, + table: ir::Table, + delta: ir::Value, + init_value: ir::Value, + ) -> cranelift_wasm::WasmResult { + self.inner + .translate_table_grow(pos, table_index, table, delta, init_value) + } + + fn translate_table_get( + &mut self, + builder: &mut cranelift_frontend::FunctionBuilder, + table_index: cranelift_wasm::TableIndex, + table: ir::Table, + index: ir::Value, + ) -> cranelift_wasm::WasmResult { + self.inner + .translate_table_get(builder, table_index, table, index) + } + + fn translate_table_set( + &mut self, + builder: &mut cranelift_frontend::FunctionBuilder, + table_index: cranelift_wasm::TableIndex, + table: ir::Table, + value: ir::Value, + index: ir::Value, + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .translate_table_set(builder, table_index, table, value, index) + } + + fn translate_table_copy( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + dst_table_index: cranelift_wasm::TableIndex, + dst_table: ir::Table, + src_table_index: cranelift_wasm::TableIndex, + src_table: ir::Table, + dst: ir::Value, + src: ir::Value, + len: ir::Value, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.translate_table_copy( + pos, + dst_table_index, + dst_table, + src_table_index, + src_table, + dst, + src, + len, + ) + } + + fn translate_table_fill( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + table_index: cranelift_wasm::TableIndex, + dst: ir::Value, + val: ir::Value, + len: ir::Value, + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .translate_table_fill(pos, table_index, dst, val, len) + } + + fn translate_table_init( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + seg_index: u32, + table_index: cranelift_wasm::TableIndex, + table: ir::Table, + dst: ir::Value, + src: ir::Value, + len: ir::Value, + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .translate_table_init(pos, seg_index, table_index, table, dst, src, len) + } + + fn translate_elem_drop( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + seg_index: u32, + ) -> cranelift_wasm::WasmResult<()> { + self.inner.translate_elem_drop(pos, seg_index) + } + + fn translate_ref_func( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + func_index: FuncIndex, + ) -> cranelift_wasm::WasmResult { + self.inner.translate_ref_func(pos, func_index) + } + + fn translate_custom_global_get( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + global_index: cranelift_wasm::GlobalIndex, + ) -> cranelift_wasm::WasmResult { + self.inner.translate_custom_global_get(pos, global_index) + } + + fn translate_custom_global_set( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + global_index: cranelift_wasm::GlobalIndex, + val: ir::Value, + ) -> cranelift_wasm::WasmResult<()> { + self.inner + .translate_custom_global_set(pos, global_index, val) + } + + fn translate_atomic_wait( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + index: cranelift_wasm::MemoryIndex, + heap: ir::Heap, + addr: ir::Value, + expected: ir::Value, + timeout: ir::Value, + ) -> cranelift_wasm::WasmResult { + self.inner + .translate_atomic_wait(pos, index, heap, addr, expected, timeout) + } + + fn translate_atomic_notify( + &mut self, + pos: cranelift_codegen::cursor::FuncCursor, + index: cranelift_wasm::MemoryIndex, + heap: ir::Heap, + addr: ir::Value, + count: ir::Value, + ) -> cranelift_wasm::WasmResult { + self.inner + .translate_atomic_notify(pos, index, heap, addr, count) + } + + fn unsigned_add_overflow_condition(&self) -> ir::condcodes::IntCC { + self.inner.unsigned_add_overflow_condition() + } +} diff --git a/cranelift/reader/Cargo.toml b/cranelift/reader/Cargo.toml index 0f1366f78e..fb66392f45 100644 --- a/cranelift/reader/Cargo.toml +++ b/cranelift/reader/Cargo.toml @@ -10,6 +10,7 @@ readme = "README.md" edition.workspace = true [dependencies] +anyhow.workspace = true cranelift-codegen = { workspace = true } smallvec = { workspace = true } target-lexicon = { workspace = true } diff --git a/cranelift/reader/src/lib.rs b/cranelift/reader/src/lib.rs index 33ac7b2e67..5307e1d4c6 100644 --- a/cranelift/reader/src/lib.rs +++ b/cranelift/reader/src/lib.rs @@ -45,3 +45,89 @@ mod run_command; mod sourcemap; mod testcommand; mod testfile; + +use anyhow::{Error, Result}; +use cranelift_codegen::isa::{self, TargetIsa}; +use cranelift_codegen::settings::{self, FlagsOrIsa}; +use std::str::FromStr; +use target_lexicon::Triple; + +/// Like `FlagsOrIsa`, but holds ownership. +#[allow(missing_docs)] +pub enum OwnedFlagsOrIsa { + Flags(settings::Flags), + Isa(Box), +} + +impl OwnedFlagsOrIsa { + /// Produce a FlagsOrIsa reference. + pub fn as_fisa(&self) -> FlagsOrIsa { + match *self { + Self::Flags(ref flags) => FlagsOrIsa::from(flags), + Self::Isa(ref isa) => FlagsOrIsa::from(&**isa), + } + } +} + +/// Parse "set" and "triple" commands. +pub fn parse_sets_and_triple(flag_set: &[String], flag_triple: &str) -> Result { + let mut flag_builder = settings::builder(); + + // Collect unknown system-wide settings, so we can try to parse them as target specific + // settings, if a target is defined. + let mut unknown_settings = Vec::new(); + match parse_options( + flag_set.iter().map(|x| x.as_str()), + &mut flag_builder, + Location { line_number: 0 }, + ) { + Err(ParseOptionError::UnknownFlag { name, .. }) => { + unknown_settings.push(name); + } + Err(ParseOptionError::UnknownValue { name, value, .. }) => { + unknown_settings.push(format!("{}={}", name, value)); + } + Err(ParseOptionError::Generic(err)) => return Err(err.into()), + Ok(()) => {} + } + + let mut words = flag_triple.trim().split_whitespace(); + // Look for `target foo`. + if let Some(triple_name) = words.next() { + let triple = match Triple::from_str(triple_name) { + Ok(triple) => triple, + Err(parse_error) => return Err(Error::from(parse_error)), + }; + + let mut isa_builder = isa::lookup(triple).map_err(|err| match err { + isa::LookupError::SupportDisabled => { + anyhow::anyhow!("support for triple '{}' is disabled", triple_name) + } + isa::LookupError::Unsupported => anyhow::anyhow!( + "support for triple '{}' is not implemented yet", + triple_name + ), + })?; + + // Try to parse system-wide unknown settings as target-specific settings. + parse_options( + unknown_settings.iter().map(|x| x.as_str()), + &mut isa_builder, + Location { line_number: 0 }, + ) + .map_err(ParseError::from)?; + + // Apply the ISA-specific settings to `isa_builder`. + parse_options(words, &mut isa_builder, Location { line_number: 0 }) + .map_err(ParseError::from)?; + + Ok(OwnedFlagsOrIsa::Isa( + isa_builder.finish(settings::Flags::new(flag_builder))?, + )) + } else { + if !unknown_settings.is_empty() { + anyhow::bail!("unknown settings: '{}'", unknown_settings.join("', '")); + } + Ok(OwnedFlagsOrIsa::Flags(settings::Flags::new(flag_builder))) + } +} diff --git a/cranelift/src/bugpoint.rs b/cranelift/src/bugpoint.rs index 54142f1321..5ac279eafd 100644 --- a/cranelift/src/bugpoint.rs +++ b/cranelift/src/bugpoint.rs @@ -1,6 +1,6 @@ //! CLI tool to reduce Cranelift IR files crashing during compilation. -use crate::utils::{parse_sets_and_triple, read_to_string}; +use crate::utils::read_to_string; use anyhow::{Context as _, Result}; use clap::Parser; use cranelift::prelude::Value; @@ -14,7 +14,7 @@ use cranelift_codegen::ir::{ use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::Context; use cranelift_entity::PrimaryMap; -use cranelift_reader::{parse_test, ParseOptions}; +use cranelift_reader::{parse_sets_and_triple, parse_test, ParseOptions}; use indicatif::{ProgressBar, ProgressDrawTarget, ProgressStyle}; use std::collections::HashMap; use std::path::PathBuf; diff --git a/cranelift/src/compile.rs b/cranelift/src/compile.rs index 2631c00331..8852c6aa8d 100644 --- a/cranelift/src/compile.rs +++ b/cranelift/src/compile.rs @@ -1,14 +1,14 @@ //! CLI tool to read Cranelift IR files and compile them into native code. use crate::disasm::print_all; -use crate::utils::{parse_sets_and_triple, read_to_string}; +use crate::utils::read_to_string; use anyhow::{Context as _, Result}; use clap::Parser; use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::settings::FlagsOrIsa; use cranelift_codegen::timing; use cranelift_codegen::Context; -use cranelift_reader::{parse_test, ParseOptions}; +use cranelift_reader::{parse_sets_and_triple, parse_test, ParseOptions}; use std::path::Path; use std::path::PathBuf; diff --git a/cranelift/src/souper_harvest.rs b/cranelift/src/souper_harvest.rs index 4aa7567f06..0a616023ea 100644 --- a/cranelift/src/souper_harvest.rs +++ b/cranelift/src/souper_harvest.rs @@ -1,7 +1,7 @@ -use crate::utils::parse_sets_and_triple; use anyhow::{Context as _, Result}; use clap::Parser; use cranelift_codegen::Context; +use cranelift_reader::parse_sets_and_triple; use cranelift_wasm::DummyEnvironment; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use std::path::{Path, PathBuf}; diff --git a/cranelift/src/utils.rs b/cranelift/src/utils.rs index 5ba65f5bac..b164553454 100644 --- a/cranelift/src/utils.rs +++ b/cranelift/src/utils.rs @@ -1,15 +1,9 @@ //! Utility functions. use anyhow::Context; -use cranelift_codegen::isa; -use cranelift_codegen::isa::TargetIsa; -use cranelift_codegen::settings::{self, FlagsOrIsa}; -use cranelift_reader::{parse_options, Location, ParseError, ParseOptionError}; use std::fs::File; use std::io::{self, Read}; use std::path::{Path, PathBuf}; -use std::str::FromStr; -use target_lexicon::Triple; use walkdir::WalkDir; /// Read an entire file into a string. @@ -30,88 +24,6 @@ pub fn read_to_string>(path: P) -> anyhow::Result { Ok(buffer) } -/// Like `FlagsOrIsa`, but holds ownership. -pub enum OwnedFlagsOrIsa { - Flags(settings::Flags), - Isa(Box), -} - -impl OwnedFlagsOrIsa { - /// Produce a FlagsOrIsa reference. - pub fn as_fisa(&self) -> FlagsOrIsa { - match *self { - Self::Flags(ref flags) => FlagsOrIsa::from(flags), - Self::Isa(ref isa) => FlagsOrIsa::from(&**isa), - } - } -} - -/// Parse "set" and "triple" commands. -pub fn parse_sets_and_triple( - flag_set: &[String], - flag_triple: &str, -) -> anyhow::Result { - let mut flag_builder = settings::builder(); - - // Collect unknown system-wide settings, so we can try to parse them as target specific - // settings, if a target is defined. - let mut unknown_settings = Vec::new(); - match parse_options( - flag_set.iter().map(|x| x.as_str()), - &mut flag_builder, - Location { line_number: 0 }, - ) { - Err(ParseOptionError::UnknownFlag { name, .. }) => { - unknown_settings.push(name); - } - Err(ParseOptionError::UnknownValue { name, value, .. }) => { - unknown_settings.push(format!("{}={}", name, value)); - } - Err(ParseOptionError::Generic(err)) => return Err(err.into()), - Ok(()) => {} - } - - let mut words = flag_triple.trim().split_whitespace(); - // Look for `target foo`. - if let Some(triple_name) = words.next() { - let triple = match Triple::from_str(triple_name) { - Ok(triple) => triple, - Err(parse_error) => return Err(parse_error.into()), - }; - - let mut isa_builder = isa::lookup(triple).map_err(|err| match err { - isa::LookupError::SupportDisabled => { - anyhow::anyhow!("support for triple '{}' is disabled", triple_name) - } - isa::LookupError::Unsupported => anyhow::anyhow!( - "support for triple '{}' is not implemented yet", - triple_name - ), - })?; - - // Try to parse system-wide unknown settings as target-specific settings. - parse_options( - unknown_settings.iter().map(|x| x.as_str()), - &mut isa_builder, - Location { line_number: 0 }, - ) - .map_err(ParseError::from)?; - - // Apply the ISA-specific settings to `isa_builder`. - parse_options(words, &mut isa_builder, Location { line_number: 0 }) - .map_err(ParseError::from)?; - - Ok(OwnedFlagsOrIsa::Isa( - isa_builder.finish(settings::Flags::new(flag_builder))?, - )) - } else { - if !unknown_settings.is_empty() { - anyhow::bail!("unknown settings: '{}'", unknown_settings.join("', '")); - } - Ok(OwnedFlagsOrIsa::Flags(settings::Flags::new(flag_builder))) - } -} - /// Iterate over all of the files passed as arguments, recursively iterating through directories. pub fn iterate_files<'a>(files: &'a [PathBuf]) -> impl Iterator + 'a { files diff --git a/cranelift/src/wasm.rs b/cranelift/src/wasm.rs index 1ccf788f7d..7456b0554e 100644 --- a/cranelift/src/wasm.rs +++ b/cranelift/src/wasm.rs @@ -8,7 +8,6 @@ )] use crate::disasm::print_all; -use crate::utils::parse_sets_and_triple; use anyhow::{Context as _, Result}; use clap::Parser; use cranelift_codegen::ir::DisplayFunctionAnnotations; @@ -17,6 +16,7 @@ use cranelift_codegen::settings::FlagsOrIsa; use cranelift_codegen::timing; use cranelift_codegen::Context; use cranelift_entity::EntityRef; +use cranelift_reader::parse_sets_and_triple; use cranelift_wasm::{translate_module, DummyEnvironment, FuncIndex}; use std::io::Read; use std::path::Path; diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs index 332269c437..41e56932e2 100644 --- a/cranelift/wasm/src/environ/dummy.rs +++ b/cranelift/wasm/src/environ/dummy.rs @@ -138,13 +138,13 @@ pub struct DummyEnvironment { pub info: DummyModuleInfo, /// Function translation. - trans: FuncTranslator, + pub trans: FuncTranslator, /// Vector of wasm bytecode size for each function. pub func_bytecode_sizes: Vec, /// Instructs to collect debug data during translation. - debug_info: bool, + pub debug_info: bool, /// Name of the module from the wasm file. pub module_name: Option, @@ -153,7 +153,8 @@ pub struct DummyEnvironment { function_names: SecondaryMap, /// Expected reachability data (before/after for each op) to assert. This is used for testing. - expected_reachability: Option, + #[doc(hidden)] + pub expected_reachability: Option, } impl DummyEnvironment { @@ -176,7 +177,8 @@ impl DummyEnvironment { DummyFuncEnvironment::new(&self.info, self.expected_reachability.clone()) } - fn get_func_type(&self, func_index: FuncIndex) -> TypeIndex { + /// Get the type for the function at the given index. + pub fn get_func_type(&self, func_index: FuncIndex) -> TypeIndex { self.info.functions[func_index].entity } @@ -205,6 +207,7 @@ impl DummyEnvironment { /// The `FuncEnvironment` implementation for use by the `DummyEnvironment`. pub struct DummyFuncEnvironment<'dummy_environment> { + /// This function environment's module info. pub mod_info: &'dummy_environment DummyModuleInfo, /// Expected reachability data (before/after for each op) to assert. This is used for testing. @@ -212,6 +215,7 @@ pub struct DummyFuncEnvironment<'dummy_environment> { } impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> { + /// Construct a new `DummyFuncEnvironment`. pub fn new( mod_info: &'dummy_environment DummyModuleInfo, expected_reachability: Option, @@ -222,9 +226,9 @@ impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> { } } - // Create a signature for `sigidx` amended with a `vmctx` argument after the standard wasm - // arguments. - fn vmctx_sig(&self, sigidx: TypeIndex) -> ir::Signature { + /// Create a signature for `sigidx` amended with a `vmctx` argument after + /// the standard wasm arguments. + pub fn vmctx_sig(&self, sigidx: TypeIndex) -> ir::Signature { let mut sig = self.mod_info.signatures[sigidx].clone(); sig.params.push(ir::AbiParam::special( self.pointer_type(), diff --git a/cranelift/wasm/src/environ/mod.rs b/cranelift/wasm/src/environ/mod.rs index 03b6cec371..34d930ac60 100644 --- a/cranelift/wasm/src/environ/mod.rs +++ b/cranelift/wasm/src/environ/mod.rs @@ -4,7 +4,9 @@ mod dummy; #[macro_use] mod spec; -pub use crate::environ::dummy::DummyEnvironment; +pub use crate::environ::dummy::{ + DummyEnvironment, DummyFuncEnvironment, DummyModuleInfo, ExpectedReachability, +}; pub use crate::environ::spec::{ FuncEnvironment, GlobalVariable, ModuleEnvironment, TargetEnvironment, }; diff --git a/cranelift/wasm/src/lib.rs b/cranelift/wasm/src/lib.rs index b8388848b5..f4d57e0aa4 100644 --- a/cranelift/wasm/src/lib.rs +++ b/cranelift/wasm/src/lib.rs @@ -57,7 +57,8 @@ mod state; mod translation_utils; pub use crate::environ::{ - DummyEnvironment, FuncEnvironment, GlobalVariable, ModuleEnvironment, TargetEnvironment, + DummyEnvironment, DummyFuncEnvironment, DummyModuleInfo, ExpectedReachability, FuncEnvironment, + GlobalVariable, ModuleEnvironment, TargetEnvironment, }; pub use crate::func_translator::FuncTranslator; pub use crate::module_translator::translate_module;