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;