From 3799e106b5c7c192d77fbba4c88347c41880987e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Fri, 29 Jul 2022 17:46:00 +0100 Subject: [PATCH 01/81] Make wasmtime-types type check --- Cargo.lock | 28 +++++--- crates/types/Cargo.toml | 2 +- crates/types/src/lib.rs | 144 ++++++++++++++++++++++++++++++++++++---- 3 files changed, 150 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 518b7d8b0c29..8200bfbec7c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -754,7 +754,7 @@ dependencies = [ "serde", "smallvec", "target-lexicon", - "wasmparser", + "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmtime-types", "wat", ] @@ -3256,7 +3256,7 @@ dependencies = [ "rand 0.8.5", "thiserror", "wasm-encoder", - "wasmparser", + "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3270,7 +3270,7 @@ dependencies = [ "indexmap", "leb128", "wasm-encoder", - "wasmparser", + "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3315,6 +3315,14 @@ dependencies = [ "indexmap", ] +[[package]] +name = "wasmparser" +version = "0.88.0" +source = "git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2#b9989767b52f2d629be9bfa40a726a09a17f8119" +dependencies = [ + "indexmap", +] + [[package]] name = "wasmprinter" version = "0.2.38" @@ -3322,7 +3330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04f2786f19a25211ddfa331e28b7579a6d6880f5f4b18d21253cd90274aa4c21" dependencies = [ "anyhow", - "wasmparser", + "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3345,7 +3353,7 @@ dependencies = [ "target-lexicon", "tempfile", "wasi-cap-std-sync", - "wasmparser", + "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmtime-cache", "wasmtime-component-macro", "wasmtime-component-util", @@ -3510,7 +3518,7 @@ dependencies = [ "object", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmtime-environ", ] @@ -3531,7 +3539,7 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-encoder", - "wasmparser", + "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -3546,7 +3554,7 @@ dependencies = [ "component-fuzz-util", "env_logger 0.9.0", "libfuzzer-sys", - "wasmparser", + "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmprinter", "wasmtime-environ", ] @@ -3606,7 +3614,7 @@ dependencies = [ "wasm-smith", "wasm-spec-interpreter", "wasmi", - "wasmparser", + "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", "wasmprinter", "wasmtime", "wasmtime-wast", @@ -3677,7 +3685,7 @@ dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser", + "wasmparser 0.88.0 (git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2)", ] [[package]] diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml index 6cacf58509cc..5e0df9ad03c1 100644 --- a/crates/types/Cargo.toml +++ b/crates/types/Cargo.toml @@ -12,4 +12,4 @@ edition = "2021" cranelift-entity = { path = "../../cranelift/entity", version = "0.88.0", features = ['enable-serde'] } serde = { version = "1.0.94", features = ["derive"] } thiserror = "1.0.4" -wasmparser = { version = "0.88.0", default-features = false } +wasmparser = { git = "https://github.com/effect-handlers/wasm-tools", branch = "func-ref-2", default-features = false } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index d0e9d06a6578..6f059b7b2330 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -25,10 +25,10 @@ pub enum WasmType { F64, /// V128 type V128, - /// FuncRef type - FuncRef, - /// ExternRef type - ExternRef, + /// Reference type + Ref(WasmRefType), + /// Bottom type + Bot, } impl TryFrom for WasmType { @@ -41,8 +41,8 @@ impl TryFrom for WasmType { F32 => Ok(WasmType::F32), F64 => Ok(WasmType::F64), V128 => Ok(WasmType::V128), - FuncRef => Ok(WasmType::FuncRef), - ExternRef => Ok(WasmType::ExternRef), + Ref(rt) => Ok(WasmType::Ref(WasmRefType::try_from(rt)?)), + Bot => Ok(WasmType::Bot), } } } @@ -55,8 +55,8 @@ impl From for wasmparser::ValType { WasmType::F32 => wasmparser::ValType::F32, WasmType::F64 => wasmparser::ValType::F64, WasmType::V128 => wasmparser::ValType::V128, - WasmType::FuncRef => wasmparser::ValType::FuncRef, - WasmType::ExternRef => wasmparser::ValType::ExternRef, + WasmType::Ref(rt) => wasmparser::ValType::Ref(wasmparser::RefType::from(rt)), + WasmType::Bot => wasmparser::ValType::Bot, } } } @@ -69,8 +69,117 @@ impl fmt::Display for WasmType { WasmType::F32 => write!(f, "f32"), WasmType::F64 => write!(f, "f64"), WasmType::V128 => write!(f, "v128"), - WasmType::ExternRef => write!(f, "externref"), - WasmType::FuncRef => write!(f, "funcref"), + WasmType::Ref(rt) => write!(f, "ref {}", rt), + WasmType::Bot => write!(f, "bot"), + } + } +} + +/// WebAssembly reference type -- equivalent of `wasmparser`'s RefType +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct WasmRefType { + nullable: bool, + heap_type: WasmHeapType, +} + +pub const WASM_EXTERN_REF: WasmRefType = WasmRefType { + nullable: true, + heap_type: WasmHeapType::Extern, +}; + +pub const WASM_FUNC_REF: WasmRefType = WasmRefType { + nullable: true, + heap_type: WasmHeapType::Func, +}; + +impl TryFrom for WasmRefType { + type Error = WasmError; + fn try_from( + wasmparser::RefType { + nullable, + heap_type, + }: wasmparser::RefType, + ) -> Result { + Ok(WasmRefType { + nullable, + heap_type: WasmHeapType::try_from(heap_type)?, + }) + } +} + +impl From for wasmparser::RefType { + fn from( + WasmRefType { + nullable, + heap_type, + }: WasmRefType, + ) -> wasmparser::RefType { + wasmparser::RefType { + nullable, + heap_type: wasmparser::HeapType::from(heap_type), + } + } +} + +impl fmt::Display for WasmRefType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &WASM_EXTERN_REF => write!(f, "externref"), + &WASM_FUNC_REF => write!(f, "funcref"), + WasmRefType { + heap_type, + nullable, + } => { + if *nullable { + write!(f, "(ref null {})", heap_type) + } else { + write!(f, "(ref {})", heap_type) + } + } + } + } +} + +/// WebAssembly heap type -- equivalent of `wasmparser`'s HeapType +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum WasmHeapType { + Bot, + Func, + Extern, + Index(u32), +} + +impl TryFrom for WasmHeapType { + type Error = WasmError; + fn try_from(ht: wasmparser::HeapType) -> Result { + use wasmparser::HeapType::*; + match ht { + Bot => Ok(WasmHeapType::Bot), + Func => Ok(WasmHeapType::Func), + Extern => Ok(WasmHeapType::Extern), + Index(i) => Ok(WasmHeapType::Index(i)), + } + } +} + +impl From for wasmparser::HeapType { + fn from(ht: WasmHeapType) -> wasmparser::HeapType { + match ht { + WasmHeapType::Bot => wasmparser::HeapType::Bot, + WasmHeapType::Func => wasmparser::HeapType::Func, + WasmHeapType::Extern => wasmparser::HeapType::Extern, + WasmHeapType::Index(i) => wasmparser::HeapType::Index(i), + } + } +} + +impl fmt::Display for WasmHeapType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WasmHeapType::Bot => write!(f, "bot"), + WasmHeapType::Func => write!(f, "func"), + WasmHeapType::Extern => write!(f, "extern"), + WasmHeapType::Index(i) => write!(f, "{}", i), } } } @@ -87,10 +196,19 @@ pub struct WasmFuncType { impl WasmFuncType { #[inline] pub fn new(params: Box<[WasmType]>, returns: Box<[WasmType]>) -> Self { - let externref_params_count = params.iter().filter(|p| **p == WasmType::ExternRef).count(); + let externref_params_count = params + .iter() + .filter(|p| match **p { + WasmType::Ref(rt) => rt.heap_type == WasmHeapType::Extern, + _ => false, + }) + .count(); let externref_returns_count = returns .iter() - .filter(|r| **r == WasmType::ExternRef) + .filter(|r| match **r { + WasmType::Ref(rt) => rt.heap_type == WasmHeapType::Extern, + _ => false, + }) .count(); WasmFuncType { params, @@ -324,7 +442,7 @@ impl Global { #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)] pub struct Table { /// The table elements' Wasm type. - pub wasm_ty: WasmType, + pub wasm_ty: WasmRefType, /// The minimum number of elements in the table. pub minimum: u32, /// The maximum number of elements in the table. From 7a4bc7e02f61945b3d9b2f54fbd134a10ad07379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Wed, 16 Nov 2022 22:42:42 -0500 Subject: [PATCH 02/81] Make wasmtime-environ type check. --- Cargo.lock | 4 ++-- crates/environ/Cargo.toml | 2 +- crates/environ/src/module.rs | 9 ++++++++- crates/runtime/src/instance.rs | 7 +++---- crates/types/src/lib.rs | 4 ++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8200bfbec7c7..6cc1480ef289 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3318,7 +3318,7 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.88.0" -source = "git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2#b9989767b52f2d629be9bfa40a726a09a17f8119" +source = "git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2#b70721d912152e5e238bd7014e920d80946a8a6f" dependencies = [ "indexmap", ] @@ -3539,7 +3539,7 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-encoder", - "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.88.0 (git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2)", "wasmprinter", "wasmtime-component-util", "wasmtime-types", diff --git a/crates/environ/Cargo.toml b/crates/environ/Cargo.toml index 412cbaeb714e..f21187e6e165 100644 --- a/crates/environ/Cargo.toml +++ b/crates/environ/Cargo.toml @@ -14,7 +14,7 @@ edition = "2021" anyhow = "1.0" cranelift-entity = { path = "../../cranelift/entity", version = "0.88.0" } wasmtime-types = { path = "../types", version = "0.41.0" } -wasmparser = "0.88.0" +wasmparser = { git = "https://github.com/effect-handlers/wasm-tools", branch = "func-ref-2" } indexmap = { version = "1.0.2", features = ["serde-1"] } thiserror = "1.0.4" serde = { version = "1.0.94", features = ["derive"] } diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index c937af024bad..9bd18d851945 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -413,6 +413,13 @@ impl ModuleTranslation<'_> { // Keep the "leftovers" for eager init. let mut leftovers = vec![]; + fn is_func_ref(rt: WasmRefType) -> bool { + match rt.heap_type { + WasmHeapType::Func => true, + _ => false + } + } + for segment in segments { // Skip imported tables: we can't provide a preconstructed // table for them, because their values depend on the @@ -428,7 +435,7 @@ impl ModuleTranslation<'_> { // If this is not a funcref table, then we can't support a // pre-computed table of function indices. - if self.module.table_plans[segment.table_index].table.wasm_ty != WasmType::FuncRef { + if !is_func_ref(self.module.table_plans[segment.table_index].table.wasm_ty) { leftovers.push(segment.clone()); continue; } diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 7cd729a889fd..3d12532ffbd4 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -30,7 +30,7 @@ use wasmtime_environ::{ packed_option::ReservedValue, DataIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex, GlobalInit, HostPtr, MemoryIndex, Module, PrimaryMap, SignatureIndex, TableIndex, - TableInitialization, TrapCode, VMOffsets, WasmType, + TableInitialization, TrapCode, VMOffsets, WasmType, WASM_EXTERN_REF, WASM_FUNC_REF, }; mod allocator; @@ -994,7 +994,7 @@ impl Instance { // count as values move between globals, everything else is just // copy-able bits. match global.wasm_ty { - WasmType::ExternRef => { + WASM_EXTERN_REF => { *(*to).as_externref_mut() = from.as_externref().clone() } _ => ptr::copy_nonoverlapping(from, to, 1), @@ -1006,8 +1006,7 @@ impl Instance { } GlobalInit::RefNullConst => match global.wasm_ty { // `VMGlobalDefinition::new()` already zeroed out the bits - WasmType::FuncRef => {} - WasmType::ExternRef => {} + WASM_EXTERN_REF | WASM_FUNC_REFWasmType::FuncRef => {} ty => panic!("unsupported reference type for global: {:?}", ty), }, GlobalInit::Import => panic!("locally-defined global initialized as import"), diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 6f059b7b2330..3e53d659db6a 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -78,8 +78,8 @@ impl fmt::Display for WasmType { /// WebAssembly reference type -- equivalent of `wasmparser`'s RefType #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct WasmRefType { - nullable: bool, - heap_type: WasmHeapType, + pub nullable: bool, + pub heap_type: WasmHeapType, } pub const WASM_EXTERN_REF: WasmRefType = WasmRefType { From 6e73ec4687d562b03e4fa415563aa78d925db440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Fri, 29 Jul 2022 18:26:53 +0100 Subject: [PATCH 03/81] Make wasmtime-runtime type check --- crates/runtime/src/instance.rs | 6 +++--- crates/runtime/src/table.rs | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 3d12532ffbd4..0b6da1b944ce 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -994,7 +994,7 @@ impl Instance { // count as values move between globals, everything else is just // copy-able bits. match global.wasm_ty { - WASM_EXTERN_REF => { + WasmType::Ref(WASM_EXTERN_REF) => { *(*to).as_externref_mut() = from.as_externref().clone() } _ => ptr::copy_nonoverlapping(from, to, 1), @@ -1006,7 +1006,7 @@ impl Instance { } GlobalInit::RefNullConst => match global.wasm_ty { // `VMGlobalDefinition::new()` already zeroed out the bits - WASM_EXTERN_REF | WASM_FUNC_REFWasmType::FuncRef => {} + WasmType::Ref(WASM_EXTERN_REF) | WasmType::Ref(WASM_FUNC_REF) => {} ty => panic!("unsupported reference type for global: {:?}", ty), }, GlobalInit::Import => panic!("locally-defined global initialized as import"), @@ -1025,7 +1025,7 @@ impl Drop for Instance { }; match global.wasm_ty { // For now only externref globals need to get destroyed - WasmType::ExternRef => {} + WasmType::Ref(WASM_EXTERN_REF) => {} _ => continue, } unsafe { diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index ca13a846b13a..763596566924 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -8,7 +8,7 @@ use anyhow::{bail, format_err, Error, Result}; use std::convert::{TryFrom, TryInto}; use std::ops::Range; use std::ptr; -use wasmtime_environ::{TablePlan, TrapCode, WasmType, FUNCREF_INIT_BIT, FUNCREF_MASK}; +use wasmtime_environ::{TablePlan, TrapCode, WasmRefType, WasmHeapType, FUNCREF_INIT_BIT, FUNCREF_MASK}; /// An element going into or coming out of a table. /// @@ -163,11 +163,11 @@ pub enum Table { }, } -fn wasm_to_table_type(ty: WasmType) -> Result { - match ty { - WasmType::FuncRef => Ok(TableElementType::Func), - WasmType::ExternRef => Ok(TableElementType::Extern), - ty => bail!("invalid table element type {:?}", ty), +fn wasm_to_table_type(rt: WasmRefType) -> Result { + match rt.heap_type { + WasmHeapType::Func => Ok(TableElementType::Func), + WasmHeapType::Extern => Ok(TableElementType::Extern), + ht => bail!("invalid table element type {:?}", ht), } } From cb0fa63271f782785b2fa3b56f609c1b19a75b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Tue, 2 Aug 2022 22:40:19 +0100 Subject: [PATCH 04/81] Make cranelift-wasm type check --- Cargo.lock | 2 +- cranelift/wasm/Cargo.toml | 2 +- cranelift/wasm/src/code_translator.rs | 8 ++++ cranelift/wasm/src/environ/dummy.rs | 6 ++- cranelift/wasm/src/environ/spec.rs | 6 +-- cranelift/wasm/src/func_translator.rs | 5 +-- cranelift/wasm/src/state/module_state.rs | 8 +++- cranelift/wasm/src/translation_utils.rs | 49 +++++++++++++++++++----- 8 files changed, 66 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6cc1480ef289..b3e876617597 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -754,7 +754,7 @@ dependencies = [ "serde", "smallvec", "target-lexicon", - "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.88.0 (git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2)", "wasmtime-types", "wat", ] diff --git a/cranelift/wasm/Cargo.toml b/cranelift/wasm/Cargo.toml index 0e89f9d3af33..16edd285cb47 100644 --- a/cranelift/wasm/Cargo.toml +++ b/cranelift/wasm/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["webassembly", "wasm"] edition = "2021" [dependencies] -wasmparser = { version = "0.88.0", default-features = false } +wasmparser = { git = "https://github.com/effect-handlers/wasm-tools", branch = "func-ref-2", default-features = false } cranelift-codegen = { path = "../codegen", version = "0.88.0", default-features = false } cranelift-entity = { path = "../entity", version = "0.88.0" } cranelift-frontend = { path = "../frontend", version = "0.88.0", default-features = false } diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index be9323043a15..122ea15b7e58 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -2019,6 +2019,14 @@ pub fn translate_operator( | Operator::F64x2RelaxedMax => { return Err(wasm_unsupported!("proposed relaxed-simd operator {:?}", op)); } + + // TODO(dhil) fixme: merge into the above list. + // Function references instructions + Operator::BrOnNull { .. } + | Operator::BrOnNonNull { .. } + | Operator::CallRef + | Operator::ReturnCallRef + | Operator::RefAsNonNull => todo!("Implement Operator::[BrOnNull,BrOnNonNull,CallRef] for translate_operator"), // TODO(dhil) fixme }; Ok(()) } diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs index 70833483aedc..27529954de9b 100644 --- a/cranelift/wasm/src/environ/dummy.rs +++ b/cranelift/wasm/src/environ/dummy.rs @@ -271,7 +271,8 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ WasmType::F32 => ir::types::F32, WasmType::F64 => ir::types::F64, WasmType::V128 => ir::types::I8X16, - WasmType::FuncRef | WasmType::ExternRef => ir::types::R64, + WasmType::Ref(_) => ir::types::R64, + WasmType::Bot => panic!("WasmType::Bot won't exist soon"), }, }) } @@ -667,7 +668,8 @@ impl<'data> ModuleEnvironment<'data> for DummyEnvironment { WasmType::F32 => ir::types::F32, WasmType::F64 => ir::types::F64, WasmType::V128 => ir::types::I8X16, - WasmType::FuncRef | WasmType::ExternRef => reference_type, + WasmType::Ref(_) => reference_type, // TODO(dhil) fixme: verify this is indeed the correct thing to do. + WasmType::Bot => todo!("Implement WasmType::Bot for declare_func_type"), // TODO(dhil) fixme }) }; sig.params.extend(wasm.params().iter().map(&mut cvt)); diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index bfdaa2b426d2..8fa4b2bf3e30 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -9,7 +9,7 @@ use crate::state::FuncTranslationState; use crate::{ DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, - Table, TableIndex, Tag, TagIndex, TypeIndex, WasmError, WasmFuncType, WasmResult, WasmType, + Table, TableIndex, Tag, TagIndex, TypeIndex, WasmError, WasmFuncType, WasmResult, WasmHeapType, }; use core::convert::From; use cranelift_codegen::cursor::FuncCursor; @@ -65,7 +65,7 @@ pub trait TargetEnvironment { /// 32-bit architectures. If you override this, then you should also /// override `FuncEnvironment::{translate_ref_null, translate_ref_is_null}` /// as well. - fn reference_type(&self, ty: WasmType) -> ir::Type { + fn reference_type(&self, ty: WasmHeapType) -> ir::Type { let _ = ty; match self.pointer_type() { ir::types::I32 => ir::types::R32, @@ -359,7 +359,7 @@ pub trait FuncEnvironment: TargetEnvironment { /// null sentinel is not a null reference type pointer for your type. If you /// override this method, then you should also override /// `translate_ref_is_null` as well. - fn translate_ref_null(&mut self, mut pos: FuncCursor, ty: WasmType) -> WasmResult { + fn translate_ref_null(&mut self, mut pos: FuncCursor, ty: WasmHeapType) -> WasmResult { let _ = ty; Ok(pos.ins().null(self.reference_type(ty))) } diff --git a/cranelift/wasm/src/func_translator.rs b/cranelift/wasm/src/func_translator.rs index 4404324b0462..b96571d664c9 100644 --- a/cranelift/wasm/src/func_translator.rs +++ b/cranelift/wasm/src/func_translator.rs @@ -202,9 +202,8 @@ fn declare_locals( let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into()); builder.ins().vconst(ir::types::I8X16, constant_handle) } - ExternRef | FuncRef => { - environ.translate_ref_null(builder.cursor(), wasm_type.try_into()?)? - } + Ref(rt) => environ.translate_ref_null(builder.cursor(), rt.heap_type.try_into()?)?, + Bot => panic!("ValType::Bot won't ever actually exist"), }; let ty = builder.func.dfg.value_type(zeroval); diff --git a/cranelift/wasm/src/state/module_state.rs b/cranelift/wasm/src/state/module_state.rs index 8b857bf6a974..9dc6e2c1bb91 100644 --- a/cranelift/wasm/src/state/module_state.rs +++ b/cranelift/wasm/src/state/module_state.rs @@ -23,13 +23,19 @@ pub struct ModuleTranslationState { pub(crate) wasm_types: WasmTypes, } +/// TODO(dhil): Temporary workaround, should be available from wasmparser/readers/core/types.rs +const EXTERN_REF: wasmparser::RefType = wasmparser::RefType { + nullable: true, + heap_type: wasmparser::HeapType::Extern, +}; + fn cranelift_to_wasmparser_type(ty: Type) -> WasmResult { Ok(match ty { types::I32 => wasmparser::ValType::I32, types::I64 => wasmparser::ValType::I64, types::F32 => wasmparser::ValType::F32, types::F64 => wasmparser::ValType::F64, - types::R32 | types::R64 => wasmparser::ValType::ExternRef, + types::R32 | types::R64 => wasmparser::ValType::Ref(EXTERN_REF), _ => { return Err(WasmError::Unsupported(format!( "Cannot convert Cranelift type to Wasm signature: {:?}", diff --git a/cranelift/wasm/src/translation_utils.rs b/cranelift/wasm/src/translation_utils.rs index 56c56c8b8f37..2c56f60f4722 100644 --- a/cranelift/wasm/src/translation_utils.rs +++ b/cranelift/wasm/src/translation_utils.rs @@ -30,9 +30,8 @@ pub fn type_to_type( wasmparser::ValType::F32 => Ok(ir::types::F32), wasmparser::ValType::F64 => Ok(ir::types::F64), wasmparser::ValType::V128 => Ok(ir::types::I8X16), - wasmparser::ValType::ExternRef | wasmparser::ValType::FuncRef => { - Ok(environ.reference_type(ty.try_into()?)) - } + wasmparser::ValType::Ref(rt) => Ok(environ.reference_type(rt.heap_type.try_into()?)), + wasmparser::ValType::Bot => todo!("ValType::Bot will not exist in final wasm-tools"), } } @@ -48,11 +47,28 @@ pub fn tabletype_to_type( wasmparser::ValType::F32 => Ok(Some(ir::types::F32)), wasmparser::ValType::F64 => Ok(Some(ir::types::F64)), wasmparser::ValType::V128 => Ok(Some(ir::types::I8X16)), - wasmparser::ValType::ExternRef => Ok(Some(environ.reference_type(ty.try_into()?))), - wasmparser::ValType::FuncRef => Ok(None), + wasmparser::ValType::Ref(rt) => { + match rt.heap_type { + wasmparser::HeapType::Extern => { + Ok(Some(environ.reference_type(rt.heap_type.try_into()?))) + } + _ => Ok(None), // TODO(dhil) fixme: verify this is indeed the right thing to do. + } + } + wasmparser::ValType::Bot => todo!("ValType::Bot will not exist in final wasm-tools"), } } +/// TODO(dhil): Temporary workaround, should be available from wasmparser/readers/core/types.rs +const FUNC_REF: wasmparser::RefType = wasmparser::RefType { + nullable: true, + heap_type: wasmparser::HeapType::Func, +}; +const EXTERN_REF: wasmparser::RefType = wasmparser::RefType { + nullable: true, + heap_type: wasmparser::HeapType::Extern, +}; + /// Get the parameter and result types for the given Wasm blocktype. pub fn blocktype_params_results<'a, T>( validator: &'a FuncValidator, @@ -81,8 +97,14 @@ where wasmparser::ValType::F32 => &[wasmparser::ValType::F32], wasmparser::ValType::F64 => &[wasmparser::ValType::F64], wasmparser::ValType::V128 => &[wasmparser::ValType::V128], - wasmparser::ValType::ExternRef => &[wasmparser::ValType::ExternRef], - wasmparser::ValType::FuncRef => &[wasmparser::ValType::FuncRef], + wasmparser::ValType::Ref(rt) => { + match rt.heap_type { + wasmparser::HeapType::Extern => &[wasmparser::ValType::Ref(EXTERN_REF)], + wasmparser::HeapType::Func => &[wasmparser::ValType::Ref(FUNC_REF)], + _ => todo!("Implement blocktype_params_results for HeapType::Bot/Index"), // TODO(dhil) fixme: I have a feeling this one is going to be somewhat painful. + } + } + wasmparser::ValType::Bot => &[wasmparser::ValType::Bot], }; ( itertools::Either::Left(params.iter().copied()), @@ -123,12 +145,21 @@ pub fn block_with_params( wasmparser::ValType::F64 => { builder.append_block_param(block, ir::types::F64); } - wasmparser::ValType::ExternRef | wasmparser::ValType::FuncRef => { - builder.append_block_param(block, environ.reference_type(ty.try_into()?)); + wasmparser::ValType::Ref(rt) => { + match rt.heap_type { + wasmparser::HeapType::Func | wasmparser::HeapType::Extern => { + builder.append_block_param( + block, + environ.reference_type(rt.heap_type.try_into()?), + ); + } // TODO(dhil) fixme: verify that this is indeed the correct thing to do. + _ => todo!("Implement block_with_params for HeapType::Bot/Index"), // TODO(dhil) fixme + } } wasmparser::ValType::V128 => { builder.append_block_param(block, ir::types::I8X16); } + wasmparser::ValType::Bot => todo!("ValType::Bot will not exist in actual wasmparser"), } } Ok(block) From 11acbe27b107acb461642047a295e40180e17e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Wed, 16 Nov 2022 22:20:24 -0500 Subject: [PATCH 05/81] Make wasmtime-cranelift type check --- Cargo.lock | 2 +- crates/cranelift/Cargo.toml | 2 +- crates/cranelift/src/func_environ.rs | 63 ++++++++++++++-------------- crates/cranelift/src/lib.rs | 11 ++--- 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b3e876617597..34fdb99b9033 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3518,7 +3518,7 @@ dependencies = [ "object", "target-lexicon", "thiserror", - "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.88.0 (git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2)", "wasmtime-environ", ] diff --git a/crates/cranelift/Cargo.toml b/crates/cranelift/Cargo.toml index 23df5dd71220..cbe44ccd5b04 100644 --- a/crates/cranelift/Cargo.toml +++ b/crates/cranelift/Cargo.toml @@ -19,7 +19,7 @@ cranelift-codegen = { path = "../../cranelift/codegen", version = "0.88.0" } cranelift-frontend = { path = "../../cranelift/frontend", version = "0.88.0" } cranelift-entity = { path = "../../cranelift/entity", version = "0.88.0" } cranelift-native = { path = "../../cranelift/native", version = "0.88.0" } -wasmparser = "0.88.0" +wasmparser = { git = "https://github.com/effect-handlers/wasm-tools", branch = "func-ref-2" } target-lexicon = "0.12" gimli = { version = "0.26.0", default-features = false, features = ['read', 'std'] } object = { version = "0.29.0", default-features = false, features = ['write'] } diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 7d5e053b02eb..2f13ecfd3442 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -10,7 +10,7 @@ use cranelift_frontend::FunctionBuilder; use cranelift_frontend::Variable; use cranelift_wasm::{ self, FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, MemoryIndex, TableIndex, - TargetEnvironment, TypeIndex, WasmError, WasmResult, WasmType, + TargetEnvironment, TypeIndex, WasmError, WasmResult, WasmType, WasmRefType, WasmHeapType, WASM_EXTERN_REF, }; use std::convert::TryFrom; use std::mem; @@ -812,7 +812,7 @@ impl<'module_environment> TargetEnvironment for FuncEnvironment<'module_environm self.isa.frontend_config() } - fn reference_type(&self, ty: WasmType) -> ir::Type { + fn reference_type(&self, ty: WasmHeapType) -> ir::Type { crate::reference_type(ty, self.pointer_type()) } } @@ -877,7 +877,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m }); let element_size = u64::from( - self.reference_type(self.module.table_plans[index].table.wasm_ty) + self.reference_type(self.module.table_plans[index].table.wasm_ty.heap_type) .bytes(), ); @@ -899,13 +899,13 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m init_value: ir::Value, ) -> WasmResult { let (func_idx, func_sig) = - match self.module.table_plans[table_index].table.wasm_ty { - WasmType::FuncRef => ( + match self.module.table_plans[table_index].table.wasm_ty.heap_type { + WasmHeapType::Func => ( BuiltinFunctionIndex::table_grow_funcref(), self.builtin_function_signatures .table_grow_funcref(&mut pos.func), ), - WasmType::ExternRef => ( + WasmHeapType::Extern => ( BuiltinFunctionIndex::table_grow_externref(), self.builtin_function_signatures .table_grow_externref(&mut pos.func), @@ -938,13 +938,13 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let pointer_type = self.pointer_type(); let plan = &self.module.table_plans[table_index]; - match plan.table.wasm_ty { - WasmType::FuncRef => match plan.style { + match plan.table.wasm_ty.heap_type { + WasmHeapType::Func => match plan.style { TableStyle::CallerChecksSignature => { Ok(self.get_or_init_funcref_table_elem(builder, table_index, table, index)) } }, - WasmType::ExternRef => { + WasmHeapType::Extern => { // Our read barrier for `externref` tables is roughly equivalent // to the following pseudocode: // @@ -965,7 +965,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m // onto the stack are safely held alive by the // `VMExternRefActivationsTable`. - let reference_type = self.reference_type(WasmType::ExternRef); + let reference_type = self.reference_type(WasmHeapType::Extern); builder.ensure_inserted_block(); let continue_block = builder.create_block(); @@ -1076,8 +1076,8 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let pointer_type = self.pointer_type(); let plan = &self.module.table_plans[table_index]; - match plan.table.wasm_ty { - WasmType::FuncRef => match plan.style { + match plan.table.wasm_ty.heap_type { + WasmHeapType::Func => match plan.style { TableStyle::CallerChecksSignature => { let table_entry_addr = builder.ins().table_addr(pointer_type, table, index, 0); // Set the "initialized bit". See doc-comment on @@ -1093,7 +1093,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m Ok(()) } }, - WasmType::ExternRef => { + WasmHeapType::Extern => { // Our write barrier for `externref`s being copied out of the // stack and into a table is roughly equivalent to the following // pseudocode: @@ -1233,13 +1233,13 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m len: ir::Value, ) -> WasmResult<()> { let (builtin_idx, builtin_sig) = - match self.module.table_plans[table_index].table.wasm_ty { - WasmType::FuncRef => ( + match self.module.table_plans[table_index].table.wasm_ty.heap_type { + WasmHeapType::Func => ( BuiltinFunctionIndex::table_fill_funcref(), self.builtin_function_signatures .table_fill_funcref(&mut pos.func), ), - WasmType::ExternRef => ( + WasmHeapType::Extern => ( BuiltinFunctionIndex::table_fill_externref(), self.builtin_function_signatures .table_fill_externref(&mut pos.func), @@ -1266,11 +1266,11 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m fn translate_ref_null( &mut self, mut pos: cranelift_codegen::cursor::FuncCursor, - ty: WasmType, + ht: WasmHeapType, ) -> WasmResult { - Ok(match ty { - WasmType::FuncRef => pos.ins().iconst(self.pointer_type(), 0), - WasmType::ExternRef => pos.ins().null(self.reference_type(ty)), + Ok(match ht { + WasmHeapType::Func => pos.ins().iconst(self.pointer_type(), 0), + WasmHeapType::Extern => pos.ins().null(self.reference_type(ht)), _ => { return Err(WasmError::Unsupported( "`ref.null T` that is not a `funcref` or an `externref`".into(), @@ -1322,7 +1322,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m ) -> WasmResult { debug_assert_eq!( self.module.globals[index].wasm_ty, - WasmType::ExternRef, + WasmType::Ref(WASM_EXTERN_REF), "We only use GlobalVariable::Custom for externref" ); @@ -1350,7 +1350,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m ) -> WasmResult<()> { debug_assert_eq!( self.module.globals[index].wasm_ty, - WasmType::ExternRef, + WasmType::Ref(WASM_EXTERN_REF), "We only use GlobalVariable::Custom for externref" ); @@ -1480,16 +1480,17 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m // `GlobalVariable::Custom`, as that is the only kind of // `GlobalVariable` for which `cranelift-wasm` supports custom access // translation. - if self.module.globals[index].wasm_ty == WasmType::ExternRef { - return Ok(GlobalVariable::Custom); + match self.module.globals[index].wasm_ty { + WasmType::Ref(WasmRefType { heap_type: WasmHeapType::Extern, .. }) => Ok(GlobalVariable::Custom), + _ => { + let (gv, offset) = self.get_global_location(func, index); + Ok(GlobalVariable::Memory { + gv, + offset: offset.into(), + ty: super::value_type(self.isa, self.module.globals[index].wasm_ty), + }) + } } - - let (gv, offset) = self.get_global_location(func, index); - Ok(GlobalVariable::Memory { - gv, - offset: offset.into(), - ty: super::value_type(self.isa, self.module.globals[index].wasm_ty), - }) } fn make_indirect_sig( diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index a5bf431800eb..431d0c861a8e 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -143,7 +143,8 @@ fn value_type(isa: &dyn TargetIsa, ty: WasmType) -> ir::types::Type { WasmType::F32 => ir::types::F32, WasmType::F64 => ir::types::F64, WasmType::V128 => ir::types::I8X16, - WasmType::FuncRef | WasmType::ExternRef => reference_type(ty, isa.pointer_type()), + WasmType::Ref(rt) => reference_type(rt.heap_type, isa.pointer_type()), + WasmType::Bot => panic!("WasmType::Bot will soon not exist"), } } @@ -206,10 +207,10 @@ fn func_signature( } /// Returns the reference type to use for the provided wasm type. -fn reference_type(wasm_ty: cranelift_wasm::WasmType, pointer_type: ir::Type) -> ir::Type { - match wasm_ty { - cranelift_wasm::WasmType::FuncRef => pointer_type, - cranelift_wasm::WasmType::ExternRef => match pointer_type { +fn reference_type(wasm_ht: cranelift_wasm::WasmHeapType, pointer_type: ir::Type) -> ir::Type { + match wasm_ht { + cranelift_wasm::WasmHeapType::Func => pointer_type, + cranelift_wasm::WasmHeapType::Extern => match pointer_type { ir::types::I32 => ir::types::R32, ir::types::I64 => ir::types::R64, _ => panic!("unsupported pointer type"), From 37c32fc3f4189bbe9c04056ee2d81a4185842eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Tue, 2 Aug 2022 23:50:24 +0100 Subject: [PATCH 06/81] Make wasmtime type check --- Cargo.lock | 2 +- crates/wasmtime/Cargo.toml | 2 +- crates/wasmtime/src/externals.rs | 32 +++-- crates/wasmtime/src/func/typed.rs | 6 +- crates/wasmtime/src/module/serialization.rs | 8 ++ crates/wasmtime/src/types.rs | 135 +++++++++++++++++--- crates/wasmtime/src/types/matching.rs | 2 +- crates/wasmtime/src/values.rs | 56 ++++++-- 8 files changed, 196 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34fdb99b9033..50bade114b69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3353,7 +3353,7 @@ dependencies = [ "target-lexicon", "tempfile", "wasi-cap-std-sync", - "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.88.0 (git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2)", "wasmtime-cache", "wasmtime-component-macro", "wasmtime-component-util", diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 65223caab723..f6601eebeb0f 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -22,7 +22,7 @@ wasmtime-cranelift = { path = "../cranelift", version = "=0.41.0", optional = tr wasmtime-component-macro = { path = "../component-macro", version = "=0.41.0", optional = true } wasmtime-component-util = { path = "../component-util", version = "=0.41.0", optional = true } target-lexicon = { version = "0.12.0", default-features = false } -wasmparser = "0.88.0" +wasmparser = { git = "https://github.com/effect-handlers/wasm-tools", branch = "func-ref-2" } anyhow = "1.0.19" libc = "0.2" cfg-if = "1.0" diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index 953801d2739c..cbe2d1c74808 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -2,7 +2,7 @@ use crate::store::{StoreData, StoreOpaque, Stored}; use crate::trampoline::{generate_global_export, generate_table_export}; use crate::{ AsContext, AsContextMut, Engine, ExternRef, ExternType, Func, GlobalType, Memory, Mutability, - SharedMemory, TableType, Trap, Val, ValType, + SharedMemory, TableType, Trap, Val, ValType, HeapType, }; use anyhow::{anyhow, bail, Result}; use std::mem; @@ -283,16 +283,22 @@ impl Global { ValType::I64 => Val::from(*definition.as_i64()), ValType::F32 => Val::F32(*definition.as_u32()), ValType::F64 => Val::F64(*definition.as_u64()), - ValType::ExternRef => Val::ExternRef( - definition - .as_externref() - .clone() - .map(|inner| ExternRef { inner }), - ), - ValType::FuncRef => { - Val::FuncRef(Func::from_raw(store, definition.as_anyfunc() as usize)) + ValType::Ref(rt) => { + match rt.heap_type { + HeapType::Extern => Val::ExternRef( + definition + .as_externref() + .clone() + .map(|inner| ExternRef { inner }), + ), + HeapType::Func => { + Val::FuncRef(Func::from_raw(store, definition.as_anyfunc() as usize)) + } + _ => todo!("Implement HeapType::Bot/Index for get") // TODO(dhil) fixme + } } ValType::V128 => Val::V128(*definition.as_u128()), + ValType::Bot => todo!("Implement ValType::Bot for get"), // TODO(dhil) fixme: I think this one is trivial. } } } @@ -459,7 +465,7 @@ impl Table { fn _new(store: &mut StoreOpaque, ty: TableType, init: Val) -> Result { let wasmtime_export = generate_table_export(store, &ty)?; - let init = init.into_table_element(store, ty.element())?; + let init = init.into_table_element(store, ValType::Ref(ty.element()))?; unsafe { let table = Table::from_wasmtime_table(wasmtime_export, store); (*table.wasmtime_table(store, std::iter::empty())) @@ -536,7 +542,7 @@ impl Table { pub fn set(&self, mut store: impl AsContextMut, index: u32, val: Val) -> Result<()> { let store = store.as_context_mut().0; let ty = self.ty(&store).element().clone(); - let val = val.into_table_element(store, ty)?; + let val = val.into_table_element(store, ValType::Ref(ty))?; let table = self.wasmtime_table(store, std::iter::empty()); unsafe { (*table) @@ -582,7 +588,7 @@ impl Table { pub fn grow(&self, mut store: impl AsContextMut, delta: u32, init: Val) -> Result { let store = store.as_context_mut().0; let ty = self.ty(&store).element().clone(); - let init = init.into_table_element(store, ty)?; + let init = init.into_table_element(store, ValType::Ref(ty))?; let table = self.wasmtime_table(store, std::iter::empty()); unsafe { match (*table).grow(delta, init, store)? { @@ -677,7 +683,7 @@ impl Table { pub fn fill(&self, mut store: impl AsContextMut, dst: u32, val: Val, len: u32) -> Result<()> { let store = store.as_context_mut().0; let ty = self.ty(&store).element().clone(); - let val = val.into_table_element(store, ty)?; + let val = val.into_table_element(store, ValType::Ref(ty))?; let table = self.wasmtime_table(store, std::iter::empty()); unsafe { diff --git a/crates/wasmtime/src/func/typed.rs b/crates/wasmtime/src/func/typed.rs index 83565829e0f7..f95db66107cc 100644 --- a/crates/wasmtime/src/func/typed.rs +++ b/crates/wasmtime/src/func/typed.rs @@ -1,6 +1,6 @@ use super::{invoke_wasm_and_catch_traps, HostAbi}; use crate::store::{AutoAssertNoGc, StoreOpaque}; -use crate::{AsContextMut, ExternRef, Func, FuncType, StoreContextMut, Trap, ValRaw, ValType}; +use crate::{AsContextMut, ExternRef, Func, FuncType, StoreContextMut, Trap, ValRaw, ValType, RefType, HeapType}; use anyhow::{bail, Result}; use std::marker; use std::mem::{self, MaybeUninit}; @@ -321,7 +321,7 @@ unsafe impl WasmTy for Option { #[inline] fn valtype() -> ValType { - ValType::ExternRef + ValType::Ref(RefType { nullable: true, heap_type: HeapType::Extern }) } #[inline] @@ -403,7 +403,7 @@ unsafe impl WasmTy for Option { #[inline] fn valtype() -> ValType { - ValType::FuncRef + ValType::Ref(RefType { nullable: true, heap_type: HeapType::Func }) } #[inline] diff --git a/crates/wasmtime/src/module/serialization.rs b/crates/wasmtime/src/module/serialization.rs index 23c83e4d42df..0872c7954c29 100644 --- a/crates/wasmtime/src/module/serialization.rs +++ b/crates/wasmtime/src/module/serialization.rs @@ -69,6 +69,7 @@ struct WasmFeatures { pub memory64: bool, pub relaxed_simd: bool, pub extended_const: bool, + pub function_references: bool, } impl From<&wasmparser::WasmFeatures> for WasmFeatures { @@ -87,6 +88,7 @@ impl From<&wasmparser::WasmFeatures> for WasmFeatures { memory64, relaxed_simd, extended_const, + function_references, // Always on; we don't currently have knobs for these. mutable_global: _, @@ -108,6 +110,7 @@ impl From<&wasmparser::WasmFeatures> for WasmFeatures { memory64, relaxed_simd, extended_const, + function_references, } } } @@ -491,6 +494,7 @@ impl<'a> SerializedModule<'a> { memory64, relaxed_simd, extended_const, + function_references, } = self.metadata.features; Self::check_bool( @@ -546,6 +550,10 @@ impl<'a> SerializedModule<'a> { other.relaxed_simd, "WebAssembly relaxed-simd support", )?; + Self::check_bool( + function_references, + other.function_references, + "WebAssembly typeful references support")?; Ok(()) } diff --git a/crates/wasmtime/src/types.rs b/crates/wasmtime/src/types.rs index 7286eb819afa..eb3fd917e200 100644 --- a/crates/wasmtime/src/types.rs +++ b/crates/wasmtime/src/types.rs @@ -1,8 +1,20 @@ use std::fmt; -use wasmtime_environ::{EntityType, Global, Memory, ModuleTypes, Table, WasmFuncType, WasmType}; +use wasmtime_environ::{ + EntityType, Global, Memory, ModuleTypes, Table, WasmFuncType, WasmHeapType, WasmRefType, + WasmType, +}; pub(crate) mod matching; +const FUNC_REF: RefType = RefType { + nullable: true, + heap_type: HeapType::Func, +}; +const EXTERN_REF: RefType = RefType { + nullable: true, + heap_type: HeapType::Extern, +}; + // Type Representations // Type attributes @@ -33,10 +45,10 @@ pub enum ValType { F64, /// A 128 bit number. V128, - /// A reference to a Wasm function. - FuncRef, - /// A reference to opaque data in the Wasm instance. - ExternRef, + /// A typeful reference type. + Ref(RefType), + /// Special bottom type. + Bot, } impl fmt::Display for ValType { @@ -47,8 +59,10 @@ impl fmt::Display for ValType { ValType::F32 => write!(f, "f32"), ValType::F64 => write!(f, "f64"), ValType::V128 => write!(f, "v128"), - ValType::ExternRef => write!(f, "externref"), - ValType::FuncRef => write!(f, "funcref"), + ValType::Ref(rt) => write!(f, "{}", rt), + ValType::Bot => write!(f, "bot"), + // ValType::ExternRef => write!(f, "externref"), + // ValType::FuncRef => write!(f, "funcref"), } } } @@ -66,7 +80,7 @@ impl ValType { /// Returns true if `ValType` matches either of the reference types. pub fn is_ref(&self) -> bool { match self { - ValType::ExternRef | ValType::FuncRef => true, + ValType::Ref(_) => true, _ => false, } } @@ -78,8 +92,8 @@ impl ValType { Self::F32 => WasmType::F32, Self::F64 => WasmType::F64, Self::V128 => WasmType::V128, - Self::FuncRef => WasmType::FuncRef, - Self::ExternRef => WasmType::ExternRef, + Self::Ref(rt) => WasmType::Ref(RefType::to_wasm_ref_type(rt)), + Self::Bot => WasmType::Bot, } } @@ -90,14 +104,101 @@ impl ValType { WasmType::F32 => Self::F32, WasmType::F64 => Self::F64, WasmType::V128 => Self::V128, - WasmType::FuncRef => Self::FuncRef, - WasmType::ExternRef => Self::ExternRef, + WasmType::Ref(rt) => Self::Ref(RefType::from_wasm_ref_type(&rt)), + WasmType::Bot => Self::Bot, } } } -// External Types +/// A reference type holds what it refers to and whether it is nullable +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct RefType { + /// Indicates whether the reference is nullable. + pub nullable: bool, + /// The reference's heap type. + pub heap_type: HeapType, +} + +impl fmt::Display for RefType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &FUNC_REF => write!(f, "funcref"), + &EXTERN_REF => write!(f, "externref"), + RefType { + nullable, + heap_type, + } => { + if *nullable { + write!(f, "(ref null {})", heap_type) + } else { + write!(f, "(ref {})", heap_type) + } + } + } + } +} + +impl RefType { + pub(crate) fn to_wasm_ref_type(&self) -> WasmRefType { + WasmRefType { + nullable: self.nullable, + heap_type: HeapType::to_wasm_heap_type(&self.heap_type), + } + } + + pub(crate) fn from_wasm_ref_type(rt: &WasmRefType) -> Self { + RefType { + nullable: rt.nullable, + heap_type: HeapType::from_wasm_heap_type(&rt.heap_type), + } + } +} +/// A list of all possible heap types in WebAssembly +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub enum HeapType { + /// A reference to a Wasm function. + Func, + /// A reference to opaque data in the Wasm instance. + Extern, + /// A typed reference to a Wasm function. + Index(u32), + /// A special bottom heap type. + Bot, +} + +impl fmt::Display for HeapType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Func => write!(f, "func"), + Self::Extern => write!(f, "extern"), + Self::Index(i) => write!(f, "index({})", i), // TODO(dhil) fixme + Self::Bot => write!(f, "bot"), + } + } +} + +impl HeapType { + pub(crate) fn to_wasm_heap_type(&self) -> WasmHeapType { + match self { + Self::Func => WasmHeapType::Func, + Self::Extern => WasmHeapType::Extern, + Self::Index(i) => WasmHeapType::Index(*i), + Self::Bot => WasmHeapType::Bot, + } + } + + pub(crate) fn from_wasm_heap_type(ht: &WasmHeapType) -> Self { + match ht { + WasmHeapType::Func => Self::Func, + WasmHeapType::Extern => Self::Extern, + WasmHeapType::Index(i) => Self::Index(*i), + WasmHeapType::Bot => Self::Bot, + } + } +} + +// External Types /// A list of all possible types which can be externally referenced from a /// WebAssembly module. /// @@ -289,10 +390,10 @@ pub struct TableType { impl TableType { /// Creates a new table descriptor which will contain the specified /// `element` and have the `limits` applied to its length. - pub fn new(element: ValType, min: u32, max: Option) -> TableType { + pub fn new(element: RefType, min: u32, max: Option) -> TableType { TableType { ty: Table { - wasm_ty: element.to_wasm_type(), + wasm_ty: element.to_wasm_ref_type(), minimum: min, maximum: max, }, @@ -300,8 +401,8 @@ impl TableType { } /// Returns the element value type of this table. - pub fn element(&self) -> ValType { - ValType::from_wasm_type(&self.ty.wasm_ty) + pub fn element(&self) -> RefType { + RefType::from_wasm_ref_type(&self.ty.wasm_ty) } /// Returns minimum number of elements this table must have diff --git a/crates/wasmtime/src/types/matching.rs b/crates/wasmtime/src/types/matching.rs index 4e2047359792..f3e677ccf961 100644 --- a/crates/wasmtime/src/types/matching.rs +++ b/crates/wasmtime/src/types/matching.rs @@ -190,7 +190,7 @@ fn global_ty(expected: &Global, actual: &Global) -> Result<()> { } fn table_ty(expected: &Table, actual: &Table, actual_runtime_size: Option) -> Result<()> { - match_ty(expected.wasm_ty, actual.wasm_ty, "table")?; + match_ty(WasmType::Ref(expected.wasm_ty), WasmType::Ref(actual.wasm_ty), "table")?; match_limits( expected.minimum.into(), expected.maximum.map(|i| i.into()), diff --git a/crates/wasmtime/src/values.rs b/crates/wasmtime/src/values.rs index 377c309249a2..5880f26d0c88 100644 --- a/crates/wasmtime/src/values.rs +++ b/crates/wasmtime/src/values.rs @@ -1,6 +1,6 @@ use crate::r#ref::ExternRef; use crate::store::StoreOpaque; -use crate::{AsContextMut, Func, ValType}; +use crate::{AsContextMut, Func, HeapType, RefType, ValType}; use anyhow::{bail, Result}; use std::ptr; use wasmtime_runtime::TableElement; @@ -89,8 +89,14 @@ impl Val { Val::I64(_) => ValType::I64, Val::F32(_) => ValType::F32, Val::F64(_) => ValType::F64, - Val::ExternRef(_) => ValType::ExternRef, - Val::FuncRef(_) => ValType::FuncRef, + Val::ExternRef(_) => ValType::Ref(RefType { + nullable: true, + heap_type: HeapType::Extern, + }), + Val::FuncRef(_) => ValType::Ref(RefType { + nullable: true, + heap_type: HeapType::Func, + }), Val::V128(_) => ValType::V128, } } @@ -139,8 +145,14 @@ impl Val { ValType::F32 => Val::F32(raw.get_f32()), ValType::F64 => Val::F64(raw.get_f64()), ValType::V128 => Val::V128(raw.get_v128()), - ValType::ExternRef => Val::ExternRef(ExternRef::from_raw(raw.get_externref())), - ValType::FuncRef => Val::FuncRef(Func::from_raw(store, raw.get_funcref())), + ValType::Ref(rt) => match rt.heap_type { + HeapType::Extern => Val::ExternRef(ExternRef::from_raw(raw.get_externref())), + HeapType::Func | HeapType::Index(_) => { + Val::FuncRef(Func::from_raw(store, raw.get_funcref())) + } + HeapType::Bot => panic!("no bot"), + }, + ValType::Bot => panic!("ValType::Bot disappears soon"), } } @@ -190,7 +202,13 @@ impl Val { ty: ValType, ) -> Result { match (self, ty) { - (Val::FuncRef(Some(f)), ValType::FuncRef) => { + ( + Val::FuncRef(Some(f)), + ValType::Ref(RefType { + heap_type: HeapType::Func, + .. + }), + ) => { if !f.comes_from_same_store(store) { bail!("cross-`Store` values are not supported in tables"); } @@ -198,11 +216,27 @@ impl Val { f.caller_checked_anyfunc(store).as_ptr(), )) } - (Val::FuncRef(None), ValType::FuncRef) => Ok(TableElement::FuncRef(ptr::null_mut())), - (Val::ExternRef(Some(x)), ValType::ExternRef) => { - Ok(TableElement::ExternRef(Some(x.inner))) - } - (Val::ExternRef(None), ValType::ExternRef) => Ok(TableElement::ExternRef(None)), + ( + Val::FuncRef(None), + ValType::Ref(RefType { + heap_type: HeapType::Func, + .. + }), + ) => Ok(TableElement::FuncRef(ptr::null_mut())), + ( + Val::ExternRef(Some(x)), + ValType::Ref(RefType { + heap_type: HeapType::Extern, + .. + }), + ) => Ok(TableElement::ExternRef(Some(x.inner))), + ( + Val::ExternRef(None), + ValType::Ref(RefType { + heap_type: HeapType::Extern, + .. + }), + ) => Ok(TableElement::ExternRef(None)), _ => bail!("value does not match table element type"), } } From 5af12e69a80ffd9ddb0e93f32ff0e98be38e4016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Tue, 2 Aug 2022 23:52:10 +0100 Subject: [PATCH 07/81] Make wasmtime-wast type check --- crates/wast/src/spectest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs index 74905d02dc52..9b2778200f79 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -34,7 +34,7 @@ pub fn link_spectest(linker: &mut Linker, store: &mut Store) -> Result< let g = Global::new(&mut *store, ty, Val::F64(0x4084_d000_0000_0000))?; linker.define("spectest", "global_f64", g)?; - let ty = TableType::new(ValType::FuncRef, 10, Some(20)); + let ty = TableType::new(RefType { nullable: true, heap_type: HeapType::Func }, 10, Some(20)); let table = Table::new(&mut *store, ty, Val::FuncRef(None))?; linker.define("spectest", "table", table)?; From 98b26b003c670708daa0cb5aae6586b44da57e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Sun, 13 Nov 2022 19:37:35 -0500 Subject: [PATCH 08/81] Make testsuite compile --- tests/all/externals.rs | 44 ++++++++++++++++++++++------------------- tests/all/func.rs | 11 +++++++---- tests/all/funcref.rs | 4 +++- tests/all/gc.rs | 11 ++++++++--- tests/all/host_funcs.rs | 8 ++++++-- tests/all/limits.rs | 12 ++++++----- tests/all/linker.rs | 6 ++++-- tests/all/table.rs | 15 ++++++++------ 8 files changed, 68 insertions(+), 43 deletions(-) diff --git a/tests/all/externals.rs b/tests/all/externals.rs index 04742fa50662..7f4d1455de0a 100644 --- a/tests/all/externals.rs +++ b/tests/all/externals.rs @@ -1,5 +1,8 @@ use wasmtime::*; +const EXTERN_REF : RefType = RefType { nullable: true, heap_type: HeapType::Extern }; +const FUNC_REF : RefType = RefType { nullable: true, heap_type: HeapType::Func }; + #[test] fn bad_globals() { let mut store = Store::<()>::default(); @@ -21,36 +24,37 @@ fn bad_globals() { fn bad_tables() { let mut store = Store::<()>::default(); + // TODO(dhil) fixme: this test is not meaningful since the refactoring of the ValType. // i32 not supported yet - let ty = TableType::new(ValType::I32, 0, Some(1)); - assert!(Table::new(&mut store, ty.clone(), Val::I32(0)).is_err()); + // let ty = TableType::new(ValType::I32, 0, Some(1)); + // assert!(Table::new(&mut store, ty.clone(), Val::I32(0)).is_err()); // mismatched initializer - let ty = TableType::new(ValType::FuncRef, 0, Some(1)); + let ty = TableType::new(FUNC_REF, 0, Some(1)); assert!(Table::new(&mut store, ty.clone(), Val::I32(0)).is_err()); // get out of bounds - let ty = TableType::new(ValType::FuncRef, 0, Some(1)); + let ty = TableType::new(FUNC_REF, 0, Some(1)); let t = Table::new(&mut store, ty.clone(), Val::FuncRef(None)).unwrap(); assert!(t.get(&mut store, 0).is_none()); assert!(t.get(&mut store, u32::max_value()).is_none()); // set out of bounds or wrong type - let ty = TableType::new(ValType::FuncRef, 1, Some(1)); + let ty = TableType::new(FUNC_REF, 1, Some(1)); let t = Table::new(&mut store, ty.clone(), Val::FuncRef(None)).unwrap(); assert!(t.set(&mut store, 0, Val::I32(0)).is_err()); assert!(t.set(&mut store, 0, Val::FuncRef(None)).is_ok()); assert!(t.set(&mut store, 1, Val::FuncRef(None)).is_err()); // grow beyond max - let ty = TableType::new(ValType::FuncRef, 1, Some(1)); + let ty = TableType::new(FUNC_REF, 1, Some(1)); let t = Table::new(&mut store, ty.clone(), Val::FuncRef(None)).unwrap(); assert!(t.grow(&mut store, 0, Val::FuncRef(None)).is_ok()); assert!(t.grow(&mut store, 1, Val::FuncRef(None)).is_err()); assert_eq!(t.size(&store), 1); // grow wrong type - let ty = TableType::new(ValType::FuncRef, 1, Some(2)); + let ty = TableType::new(FUNC_REF, 1, Some(2)); let t = Table::new(&mut store, ty.clone(), Val::FuncRef(None)).unwrap(); assert!(t.grow(&mut store, 1, Val::I32(0)).is_err()); assert_eq!(t.size(&store), 1); @@ -71,7 +75,7 @@ fn cross_store() -> anyhow::Result<()> { let global = Global::new(&mut store2, ty, Val::I32(0))?; let ty = MemoryType::new(1, None); let memory = Memory::new(&mut store2, ty)?; - let ty = TableType::new(ValType::FuncRef, 1, None); + let ty = TableType::new(FUNC_REF, 1, None); let table = Table::new(&mut store2, ty, Val::FuncRef(None))?; let need_func = Module::new(&engine, r#"(module (import "" "" (func)))"#)?; @@ -91,7 +95,7 @@ fn cross_store() -> anyhow::Result<()> { let store1val = Val::FuncRef(Some(Func::wrap(&mut store1, || {}))); let store2val = Val::FuncRef(Some(Func::wrap(&mut store2, || {}))); - let ty = GlobalType::new(ValType::FuncRef, Mutability::Var); + let ty = GlobalType::new(ValType::Ref(FUNC_REF), Mutability::Var); assert!(Global::new(&mut store2, ty.clone(), store1val.clone()).is_err()); if let Ok(g) = Global::new(&mut store2, ty.clone(), store2val.clone()) { assert!(g.set(&mut store2, store1val.clone()).is_err()); @@ -99,7 +103,7 @@ fn cross_store() -> anyhow::Result<()> { // ============ Cross-store tables ============== - let ty = TableType::new(ValType::FuncRef, 1, None); + let ty = TableType::new(FUNC_REF, 1, None); assert!(Table::new(&mut store2, ty.clone(), store1val.clone()).is_err()); let t1 = Table::new(&mut store2, ty.clone(), store2val.clone())?; assert!(t1.set(&mut store2, 0, store1val.clone()).is_err()); @@ -157,7 +161,7 @@ fn get_set_externref_globals_via_api() -> anyhow::Result<()> { let global = Global::new( &mut store, - GlobalType::new(ValType::ExternRef, Mutability::Var), + GlobalType::new(ValType::Ref(EXTERN_REF), Mutability::Var), Val::ExternRef(None), )?; assert!(global.get(&mut store).unwrap_externref().is_none()); @@ -174,7 +178,7 @@ fn get_set_externref_globals_via_api() -> anyhow::Result<()> { let global = Global::new( &mut store, - GlobalType::new(ValType::ExternRef, Mutability::Const), + GlobalType::new(ValType::Ref(EXTERN_REF), Mutability::Const), Val::ExternRef(Some(ExternRef::new(42_i32))), )?; let r = global.get(&mut store).unwrap_externref().unwrap(); @@ -197,7 +201,7 @@ fn get_set_funcref_globals_via_api() -> anyhow::Result<()> { let global = Global::new( &mut store, - GlobalType::new(ValType::FuncRef, Mutability::Var), + GlobalType::new(ValType::Ref(FUNC_REF), Mutability::Var), Val::FuncRef(None), )?; assert!(global.get(&mut store).unwrap_funcref().is_none()); @@ -210,7 +214,7 @@ fn get_set_funcref_globals_via_api() -> anyhow::Result<()> { let global = Global::new( &mut store, - GlobalType::new(ValType::FuncRef, Mutability::Var), + GlobalType::new(ValType::Ref(FUNC_REF), Mutability::Var), Val::FuncRef(Some(f.clone())), )?; let f2 = global.get(&mut store).unwrap_funcref().cloned().unwrap(); @@ -226,7 +230,7 @@ fn create_get_set_funcref_tables_via_api() -> anyhow::Result<()> { let engine = Engine::new(&cfg)?; let mut store = Store::new(&engine, ()); - let table_ty = TableType::new(ValType::FuncRef, 10, None); + let table_ty = TableType::new(FUNC_REF, 10, None); let init = Val::FuncRef(Some(Func::wrap(&mut store, || {}))); let table = Table::new(&mut store, table_ty, init)?; @@ -244,7 +248,7 @@ fn fill_funcref_tables_via_api() -> anyhow::Result<()> { let engine = Engine::new(&cfg)?; let mut store = Store::new(&engine, ()); - let table_ty = TableType::new(ValType::FuncRef, 10, None); + let table_ty = TableType::new(FUNC_REF, 10, None); let table = Table::new(&mut store, table_ty, Val::FuncRef(None))?; for i in 0..10 { @@ -271,7 +275,7 @@ fn grow_funcref_tables_via_api() -> anyhow::Result<()> { let engine = Engine::new(&cfg)?; let mut store = Store::new(&engine, ()); - let table_ty = TableType::new(ValType::FuncRef, 10, None); + let table_ty = TableType::new(FUNC_REF, 10, None); let table = Table::new(&mut store, table_ty, Val::FuncRef(None))?; assert_eq!(table.size(&store), 10); @@ -288,7 +292,7 @@ fn create_get_set_externref_tables_via_api() -> anyhow::Result<()> { let engine = Engine::new(&cfg)?; let mut store = Store::new(&engine, ()); - let table_ty = TableType::new(ValType::ExternRef, 10, None); + let table_ty = TableType::new(EXTERN_REF, 10, None); let table = Table::new( &mut store, table_ty, @@ -323,7 +327,7 @@ fn fill_externref_tables_via_api() -> anyhow::Result<()> { let engine = Engine::new(&cfg)?; let mut store = Store::new(&engine, ()); - let table_ty = TableType::new(ValType::ExternRef, 10, None); + let table_ty = TableType::new(EXTERN_REF, 10, None); let table = Table::new(&mut store, table_ty, Val::ExternRef(None))?; for i in 0..10 { @@ -372,7 +376,7 @@ fn grow_externref_tables_via_api() -> anyhow::Result<()> { let engine = Engine::new(&cfg)?; let mut store = Store::new(&engine, ()); - let table_ty = TableType::new(ValType::ExternRef, 10, None); + let table_ty = TableType::new(EXTERN_REF, 10, None); let table = Table::new(&mut store, table_ty, Val::ExternRef(None))?; assert_eq!(table.size(&store), 10); diff --git a/tests/all/func.rs b/tests/all/func.rs index 79a0efbbe491..9308b5d74397 100644 --- a/tests/all/func.rs +++ b/tests/all/func.rs @@ -3,6 +3,9 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst}; use std::sync::Arc; use wasmtime::*; +const EXTERN_REF : RefType = RefType { nullable: true, heap_type: HeapType::Extern }; +const FUNC_REF : RefType = RefType { nullable: true, heap_type: HeapType::Func }; + #[test] fn func_constructors() { let mut store = Store::<()>::default(); @@ -114,8 +117,8 @@ fn signatures_match() { ValType::I32, ValType::I64, ValType::I32, - ValType::ExternRef, - ValType::FuncRef, + ValType::Ref(EXTERN_REF), + ValType::Ref(FUNC_REF), ] ); assert_eq!(f.ty(&store).results().collect::>(), &[ValType::F64]); @@ -533,8 +536,8 @@ fn externref_signature_no_reference_types() -> anyhow::Result<()> { Func::new( &mut store, FuncType::new( - [ValType::FuncRef, ValType::ExternRef].iter().cloned(), - [ValType::FuncRef, ValType::ExternRef].iter().cloned(), + [ValType::Ref(FUNC_REF), ValType::Ref(EXTERN_REF)].iter().cloned(), + [ValType::Ref(FUNC_REF), ValType::Ref(EXTERN_REF)].iter().cloned(), ), |_, _, _| Ok(()), ); diff --git a/tests/all/funcref.rs b/tests/all/funcref.rs index 74980bb21cda..cd5c8a4df69d 100644 --- a/tests/all/funcref.rs +++ b/tests/all/funcref.rs @@ -3,6 +3,8 @@ use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; use std::sync::Arc; use wasmtime::*; +const FUNC_REF : RefType = RefType { nullable: true, heap_type: HeapType::Func }; + #[test] fn pass_funcref_in_and_out_of_wasm() -> anyhow::Result<()> { let (mut store, module) = ref_types_module( @@ -136,7 +138,7 @@ fn func_new_returns_wrong_store() -> anyhow::Result<()> { let f1 = Func::wrap(&mut store1, move || drop(&set)); let f2 = Func::new( &mut store2, - FuncType::new(None, Some(ValType::FuncRef)), + FuncType::new(None, Some(ValType::Ref(FUNC_REF))), move |_, _, results| { results[0] = f1.clone().into(); Ok(()) diff --git a/tests/all/gc.rs b/tests/all/gc.rs index 4730f79418a9..6aba5168e4de 100644 --- a/tests/all/gc.rs +++ b/tests/all/gc.rs @@ -6,6 +6,11 @@ use wasmtime::*; struct SetFlagOnDrop(Arc); +const EXTERN_REF: RefType = RefType { + nullable: true, + heap_type: HeapType::Extern, +}; + impl Drop for SetFlagOnDrop { fn drop(&mut self) { self.0.store(true, SeqCst); @@ -264,7 +269,7 @@ fn global_drops_externref() -> anyhow::Result<()> { let externref = ExternRef::new(SetFlagOnDrop(flag.clone())); Global::new( &mut store, - GlobalType::new(ValType::ExternRef, Mutability::Const), + GlobalType::new(ValType::Ref(EXTERN_REF), Mutability::Const), externref.into(), )?; drop(store); @@ -313,7 +318,7 @@ fn table_drops_externref() -> anyhow::Result<()> { let externref = ExternRef::new(SetFlagOnDrop(flag.clone())); Table::new( &mut store, - TableType::new(ValType::ExternRef, 1, None), + TableType::new(EXTERN_REF, 1, None), externref.into(), )?; drop(store); @@ -424,7 +429,7 @@ fn global_init_no_leak() -> anyhow::Result<()> { let externref = ExternRef::new(()); let global = Global::new( &mut store, - GlobalType::new(ValType::ExternRef, Mutability::Const), + GlobalType::new(ValType::Ref(EXTERN_REF), Mutability::Const), externref.clone().into(), )?; Instance::new(&mut store, &module, &[global.into()])?; diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs index 0c33c60a90f8..c9213cbf5c55 100644 --- a/tests/all/host_funcs.rs +++ b/tests/all/host_funcs.rs @@ -3,6 +3,10 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use wasmtime::*; use wasmtime_wasi::sync::WasiCtxBuilder; +const EXTERN_REF : RefType = RefType { nullable: true, heap_type: HeapType::Extern }; +const FUNC_REF : RefType = RefType { nullable: true, heap_type: HeapType::Func }; + + #[test] #[should_panic = "cannot use `func_new_async` without enabling async support"] fn async_required() { @@ -199,8 +203,8 @@ fn signatures_match() -> Result<()> { ValType::I32, ValType::I64, ValType::I32, - ValType::ExternRef, - ValType::FuncRef, + ValType::Ref(EXTERN_REF), + ValType::Ref(FUNC_REF), ] ); assert_eq!(f.ty(&store).results().collect::>(), &[ValType::F64]); diff --git a/tests/all/limits.rs b/tests/all/limits.rs index 49224ef857a3..feea634866fc 100644 --- a/tests/all/limits.rs +++ b/tests/all/limits.rs @@ -3,6 +3,8 @@ use wasmtime::*; const WASM_PAGE_SIZE: usize = wasmtime_environ::WASM_PAGE_SIZE as usize; +const FUNC_REF : RefType = RefType { nullable: true, heap_type: HeapType::Func }; + #[test] fn test_limits() -> Result<()> { let engine = Engine::default(); @@ -50,7 +52,7 @@ fn test_limits() -> Result<()> { instance.get_table(&mut store, "t").unwrap(), Table::new( &mut store, - TableType::new(ValType::FuncRef, 0, None), + TableType::new(FUNC_REF, 0, None), Val::FuncRef(None), )?, ]) { @@ -160,7 +162,7 @@ async fn test_limits_async() -> Result<()> { instance.get_table(&mut store, "t").unwrap(), Table::new_async( &mut store, - TableType::new(ValType::FuncRef, 0, None), + TableType::new(FUNC_REF, 0, None), Val::FuncRef(None), ) .await?, @@ -223,7 +225,7 @@ fn test_limits_memory_only() -> Result<()> { instance.get_table(&mut store, "t").unwrap(), Table::new( &mut store, - TableType::new(ValType::FuncRef, 0, None), + TableType::new(FUNC_REF, 0, None), Val::FuncRef(None), )?, ]) { @@ -297,7 +299,7 @@ fn test_limits_table_only() -> Result<()> { instance.get_table(&mut store, "t").unwrap(), Table::new( &mut store, - TableType::new(ValType::FuncRef, 0, None), + TableType::new(FUNC_REF, 0, None), Val::FuncRef(None), )?, ]) { @@ -335,7 +337,7 @@ fn test_initial_table_limits_exceeded() -> Result<()> { match Table::new( &mut store, - TableType::new(ValType::FuncRef, 99, None), + TableType::new(FUNC_REF, 99, None), Val::FuncRef(None), ) { Ok(_) => unreachable!(), diff --git a/tests/all/linker.rs b/tests/all/linker.rs index cc8e060afd05..1e0ce4bde163 100644 --- a/tests/all/linker.rs +++ b/tests/all/linker.rs @@ -5,6 +5,8 @@ use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use std::sync::Arc; use wasmtime::*; +const FUNC_REF : RefType = RefType { nullable: true, heap_type: HeapType::Func }; + #[test] fn link_undefined() -> Result<()> { let mut store = Store::<()>::default(); @@ -61,11 +63,11 @@ fn link_twice_bad() -> Result<()> { assert!(linker.define("m", "", memory.clone()).is_err()); // tables - let ty = TableType::new(ValType::FuncRef, 1, None); + let ty = TableType::new(FUNC_REF, 1, None); let table = Table::new(&mut store, ty, Val::FuncRef(None))?; linker.define("t", "", table.clone())?; assert!(linker.define("t", "", table.clone()).is_err()); - let ty = TableType::new(ValType::FuncRef, 2, None); + let ty = TableType::new(FUNC_REF, 2, None); let table = Table::new(&mut store, ty, Val::FuncRef(None))?; assert!(linker.define("t", "", table.clone()).is_err()); Ok(()) diff --git a/tests/all/table.rs b/tests/all/table.rs index 8bc62f4f1a14..abbbf7ce1a40 100644 --- a/tests/all/table.rs +++ b/tests/all/table.rs @@ -1,10 +1,13 @@ use anyhow::Result; use wasmtime::*; +const EXTERN_REF : RefType = RefType { nullable: true, heap_type: HeapType::Extern }; +const FUNC_REF : RefType = RefType { nullable: true, heap_type: HeapType::Func }; + #[test] fn get_none() { let mut store = Store::<()>::default(); - let ty = TableType::new(ValType::FuncRef, 1, None); + let ty = TableType::new(FUNC_REF, 1, None); let table = Table::new(&mut store, ty, Val::FuncRef(None)).unwrap(); match table.get(&mut store, 0) { Some(Val::FuncRef(None)) => {} @@ -16,7 +19,7 @@ fn get_none() { #[test] fn fill_wrong() { let mut store = Store::<()>::default(); - let ty = TableType::new(ValType::FuncRef, 1, None); + let ty = TableType::new(FUNC_REF, 1, None); let table = Table::new(&mut store, ty, Val::FuncRef(None)).unwrap(); assert_eq!( table @@ -26,7 +29,7 @@ fn fill_wrong() { "value does not match table element type" ); - let ty = TableType::new(ValType::ExternRef, 1, None); + let ty = TableType::new(EXTERN_REF, 1, None); let table = Table::new(&mut store, ty, Val::ExternRef(None)).unwrap(); assert_eq!( table @@ -40,9 +43,9 @@ fn fill_wrong() { #[test] fn copy_wrong() { let mut store = Store::<()>::default(); - let ty = TableType::new(ValType::FuncRef, 1, None); + let ty = TableType::new(FUNC_REF, 1, None); let table1 = Table::new(&mut store, ty, Val::FuncRef(None)).unwrap(); - let ty = TableType::new(ValType::ExternRef, 1, None); + let ty = TableType::new(EXTERN_REF, 1, None); let table2 = Table::new(&mut store, ty, Val::ExternRef(None)).unwrap(); assert_eq!( Table::copy(&mut store, &table1, 0, &table2, 0, 1) @@ -55,7 +58,7 @@ fn copy_wrong() { #[test] fn null_elem_segment_works_with_imported_table() -> Result<()> { let mut store = Store::<()>::default(); - let ty = TableType::new(ValType::FuncRef, 1, None); + let ty = TableType::new(FUNC_REF, 1, None); let table = Table::new(&mut store, ty, Val::FuncRef(None))?; let module = Module::new( store.engine(), From 1c6eb7ee76b1285c1b73994c1add2085d72dfc8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Wed, 16 Nov 2022 22:52:01 -0500 Subject: [PATCH 09/81] Address Luna's comments --- crates/cranelift/src/lib.rs | 2 +- crates/environ/src/module.rs | 9 +-------- crates/runtime/src/table.rs | 1 + crates/types/src/lib.rs | 2 +- crates/wasmtime/src/externals.rs | 14 +++++++------- crates/wasmtime/src/types.rs | 4 +--- crates/wasmtime/src/values.rs | 18 +++++++++--------- 7 files changed, 21 insertions(+), 29 deletions(-) diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 431d0c861a8e..32015b6e3d1f 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -209,7 +209,7 @@ fn func_signature( /// Returns the reference type to use for the provided wasm type. fn reference_type(wasm_ht: cranelift_wasm::WasmHeapType, pointer_type: ir::Type) -> ir::Type { match wasm_ht { - cranelift_wasm::WasmHeapType::Func => pointer_type, + cranelift_wasm::WasmHeapType::Func | cranelift_wasm::WasmHeapType::Index(_) => pointer_type, cranelift_wasm::WasmHeapType::Extern => match pointer_type { ir::types::I32 => ir::types::R32, ir::types::I64 => ir::types::R64, diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 9bd18d851945..7c21e46953c8 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -413,13 +413,6 @@ impl ModuleTranslation<'_> { // Keep the "leftovers" for eager init. let mut leftovers = vec![]; - fn is_func_ref(rt: WasmRefType) -> bool { - match rt.heap_type { - WasmHeapType::Func => true, - _ => false - } - } - for segment in segments { // Skip imported tables: we can't provide a preconstructed // table for them, because their values depend on the @@ -435,7 +428,7 @@ impl ModuleTranslation<'_> { // If this is not a funcref table, then we can't support a // pre-computed table of function indices. - if !is_func_ref(self.module.table_plans[segment.table_index].table.wasm_ty) { + if self.module.table_plans[segment.table_index].table.wasm_ty.heap_type == WasmHeapType::Func { leftovers.push(segment.clone()); continue; } diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index 763596566924..f76b6bb7b7b0 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -167,6 +167,7 @@ fn wasm_to_table_type(rt: WasmRefType) -> Result { match rt.heap_type { WasmHeapType::Func => Ok(TableElementType::Func), WasmHeapType::Extern => Ok(TableElementType::Extern), + WasmHeapType::Index(_) => todo!("Implement WasmHeapType::Index for wasm_to_table_type"), ht => bail!("invalid table element type {:?}", ht), } } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 3e53d659db6a..d18f8d169f11 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -69,7 +69,7 @@ impl fmt::Display for WasmType { WasmType::F32 => write!(f, "f32"), WasmType::F64 => write!(f, "f64"), WasmType::V128 => write!(f, "v128"), - WasmType::Ref(rt) => write!(f, "ref {}", rt), + WasmType::Ref(rt) => write!(f, "{}", rt), WasmType::Bot => write!(f, "bot"), } } diff --git a/crates/wasmtime/src/externals.rs b/crates/wasmtime/src/externals.rs index cbe2d1c74808..a956345fe0d7 100644 --- a/crates/wasmtime/src/externals.rs +++ b/crates/wasmtime/src/externals.rs @@ -465,7 +465,7 @@ impl Table { fn _new(store: &mut StoreOpaque, ty: TableType, init: Val) -> Result
{ let wasmtime_export = generate_table_export(store, &ty)?; - let init = init.into_table_element(store, ValType::Ref(ty.element()))?; + let init = init.into_table_element(store, ty.element())?; unsafe { let table = Table::from_wasmtime_table(wasmtime_export, store); (*table.wasmtime_table(store, std::iter::empty())) @@ -541,8 +541,8 @@ impl Table { /// Panics if `store` does not own this table. pub fn set(&self, mut store: impl AsContextMut, index: u32, val: Val) -> Result<()> { let store = store.as_context_mut().0; - let ty = self.ty(&store).element().clone(); - let val = val.into_table_element(store, ValType::Ref(ty))?; + let rt = self.ty(&store).element().clone(); + let val = val.into_table_element(store, rt)?; let table = self.wasmtime_table(store, std::iter::empty()); unsafe { (*table) @@ -587,8 +587,8 @@ impl Table { /// instead. pub fn grow(&self, mut store: impl AsContextMut, delta: u32, init: Val) -> Result { let store = store.as_context_mut().0; - let ty = self.ty(&store).element().clone(); - let init = init.into_table_element(store, ValType::Ref(ty))?; + let rt = self.ty(&store).element().clone(); + let init = init.into_table_element(store, rt)?; let table = self.wasmtime_table(store, std::iter::empty()); unsafe { match (*table).grow(delta, init, store)? { @@ -682,8 +682,8 @@ impl Table { /// Panics if `store` does not own either `dst_table` or `src_table`. pub fn fill(&self, mut store: impl AsContextMut, dst: u32, val: Val, len: u32) -> Result<()> { let store = store.as_context_mut().0; - let ty = self.ty(&store).element().clone(); - let val = val.into_table_element(store, ValType::Ref(ty))?; + let rt = self.ty(&store).element().clone(); + let val = val.into_table_element(store, rt)?; let table = self.wasmtime_table(store, std::iter::empty()); unsafe { diff --git a/crates/wasmtime/src/types.rs b/crates/wasmtime/src/types.rs index eb3fd917e200..76dd828c2555 100644 --- a/crates/wasmtime/src/types.rs +++ b/crates/wasmtime/src/types.rs @@ -61,8 +61,6 @@ impl fmt::Display for ValType { ValType::V128 => write!(f, "v128"), ValType::Ref(rt) => write!(f, "{}", rt), ValType::Bot => write!(f, "bot"), - // ValType::ExternRef => write!(f, "externref"), - // ValType::FuncRef => write!(f, "funcref"), } } } @@ -172,7 +170,7 @@ impl fmt::Display for HeapType { match self { Self::Func => write!(f, "func"), Self::Extern => write!(f, "extern"), - Self::Index(i) => write!(f, "index({})", i), // TODO(dhil) fixme + Self::Index(i) => write!(f, "{}", i), Self::Bot => write!(f, "bot"), } } diff --git a/crates/wasmtime/src/values.rs b/crates/wasmtime/src/values.rs index 5880f26d0c88..de81f97ef3e8 100644 --- a/crates/wasmtime/src/values.rs +++ b/crates/wasmtime/src/values.rs @@ -199,15 +199,15 @@ impl Val { pub(crate) fn into_table_element( self, store: &mut StoreOpaque, - ty: ValType, + ty: RefType, ) -> Result { match (self, ty) { ( Val::FuncRef(Some(f)), - ValType::Ref(RefType { + RefType { heap_type: HeapType::Func, .. - }), + }, ) => { if !f.comes_from_same_store(store) { bail!("cross-`Store` values are not supported in tables"); @@ -218,24 +218,24 @@ impl Val { } ( Val::FuncRef(None), - ValType::Ref(RefType { + RefType { heap_type: HeapType::Func, .. - }), + }, ) => Ok(TableElement::FuncRef(ptr::null_mut())), ( Val::ExternRef(Some(x)), - ValType::Ref(RefType { + RefType { heap_type: HeapType::Extern, .. - }), + }, ) => Ok(TableElement::ExternRef(Some(x.inner))), ( Val::ExternRef(None), - ValType::Ref(RefType { + RefType { heap_type: HeapType::Extern, .. - }), + }, ) => Ok(TableElement::ExternRef(None)), _ => bail!("value does not match table element type"), } From 0c69cbad69eb325dbcb4738bbdd98f40ff9175a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Sat, 6 Aug 2022 00:13:12 +0100 Subject: [PATCH 10/81] Restore compatibility with effect-handlers/wasm-tools#func-ref-2 --- Cargo.lock | 456 ++++++++++------------ cranelift/wasm/src/sections_translator.rs | 4 +- crates/environ/src/module_environ.rs | 4 +- 3 files changed, 218 insertions(+), 246 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50bade114b69..1904d6b3f1fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,7 +58,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "once_cell", "version_check", ] @@ -89,24 +89,24 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" [[package]] name = "arbitrary" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38b6b6b79f671c25e1a3e785b7b82d7562ffc9cd3efdc98627e5668a2472490" +checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" dependencies = [ "derive_arbitrary", ] [[package]] name = "async-trait" -version = "0.1.53" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] @@ -219,9 +219,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byteorder" @@ -231,15 +231,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cap-fs-ext" -version = "0.25.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "337ddae0c23990d98099a34db274fda588a3ddf89e1961aa2d3ae08d4572b746" +checksum = "04e142bbbe9d5d6a2dd0387f887a000b41f4c82fb1226316dfb4cc8dbc3b1a29" dependencies = [ "cap-primitives", "cap-std", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "cap-primitives" -version = "0.25.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c13977868250c3102a1737c766c0fe0abea4c9d64b60566b55e3df084a46eb6" +checksum = "7f22f4975282dd4f2330ee004f001c4e22f420da9fb474ea600e9af330f1e548" dependencies = [ "ambient-authority", "errno", @@ -268,9 +268,9 @@ dependencies = [ [[package]] name = "cap-rand" -version = "0.25.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1aa052bd5af24e9d1ad26db37c4bed43e45494366a03321f05beeafaf99bdb" +checksum = "ef643f8defef7061c395bb3721b6a80d39c1baaa8ee2e42edf2917fa05584e7f" dependencies = [ "ambient-authority", "rand 0.8.5", @@ -278,9 +278,9 @@ dependencies = [ [[package]] name = "cap-std" -version = "0.25.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dc3bec032b93533630adb5cd0e05f4eac5475bf3e9edafc562ccbc44fd5db06" +checksum = "95624bb0abba6b6ff6fad2e02a7d3945d093d064ac5a3477a308c29fbe3bfd49" dependencies = [ "cap-primitives", "io-extras", @@ -291,9 +291,9 @@ dependencies = [ [[package]] name = "cap-tempfile" -version = "0.25.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e19d4852b4185065d0744d225fb961a79165f5a6dedfee9175457fbfd1a9bbb" +checksum = "d4297811bca678650ed68e938ba631218d8d7a326659a59170a3a53c4af51c99" dependencies = [ "cap-std", "rand 0.8.5", @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "cap-time-ext" -version = "0.25.0" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0486425152c6e3e45528baa8edff1c37e82056cb2105a82836989679b6509326" +checksum = "46a2d284862edf6e431e9ad4e109c02855157904cebaceae6f042b124a1a21e2" dependencies = [ "cap-primitives", "once_cell", @@ -335,12 +335,9 @@ dependencies = [ [[package]] name = "cast" -version = "0.2.7" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a" -dependencies = [ - "rustc_version", -] +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" @@ -359,9 +356,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if", "cipher", @@ -371,9 +368,9 @@ dependencies = [ [[package]] name = "chacha20poly1305" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", @@ -404,9 +401,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.8" +version = "3.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83" +checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" dependencies = [ "atty", "bitflags", @@ -421,9 +418,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.7" +version = "3.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" +checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" dependencies = [ "heck", "proc-macro-error", @@ -473,14 +470,13 @@ dependencies = [ [[package]] name = "console" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" dependencies = [ "encode_unicode", "libc", "once_cell", - "regex", "terminal_size", "unicode-width", "winapi", @@ -702,7 +698,7 @@ dependencies = [ name = "cranelift-serde" version = "0.88.0" dependencies = [ - "clap 3.2.8", + "clap 3.2.16", "cranelift-codegen", "cranelift-reader", "serde_json", @@ -715,7 +711,7 @@ dependencies = [ "anyhow", "capstone", "cfg-if", - "clap 3.2.8", + "clap 3.2.16", "cranelift", "cranelift-codegen", "cranelift-entity", @@ -770,9 +766,9 @@ dependencies = [ [[package]] name = "criterion" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", @@ -796,9 +792,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ "cast", "itertools", @@ -806,9 +802,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if", "crossbeam-utils", @@ -816,9 +812,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -827,9 +823,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ "autocfg 1.1.0", "cfg-if", @@ -841,9 +837,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if", "once_cell", @@ -944,9 +940,9 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98e23c06c035dac87bd802d98f368df73a7f2cb05a66ffbd1f377e821fac4af9" +checksum = "c9a577516173adb681466d517d39bd468293bc2c2a16439375ef0f35bba45f3d" dependencies = [ "proc-macro2", "quote", @@ -973,13 +969,23 @@ dependencies = [ ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "dirs" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" dependencies = [ - "cfg-if", - "dirs-sys-next", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", ] [[package]] @@ -1019,9 +1025,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.4.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5c4b5e5959dc2c2b89918d8e2cc40fcdd623cef026ed09d2f0ee05199dc8e4" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ "signature", ] @@ -1056,9 +1062,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "elliptic-curve" @@ -1149,9 +1155,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1188,14 +1194,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if", "libc", "redox_syscall", - "winapi", + "windows-sys", ] [[package]] @@ -1212,9 +1218,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fs-set-times" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "344a9d25719061ed11379a5ff2f7222486df7d7211f3c228a1d78fa387706576" +checksum = "a267b6a9304912e018610d53fe07115d8b530b160e85db4d2d3a59f3ddde1aec" dependencies = [ "io-lifetimes", "rustix", @@ -1242,9 +1248,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -1263,13 +1269,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -1284,9 +1290,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", "indexmap", @@ -1318,9 +1324,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] @@ -1342,9 +1348,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.0" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab7905ea95c6d9af62940f9d7dd9596d54c334ae2c15300c482051292d5637f" +checksum = "897cd85af6387be149f55acf168e41be176a02de7872403aaab184afc2f327e6" dependencies = [ "libc", ] @@ -1454,7 +1460,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d508111813f9af3afd2f92758f77e4ed2cc9371b642112c6a48d22eb73105c5" dependencies = [ - "hermit-abi 0.2.0", + "hermit-abi 0.2.5", "io-lifetimes", "rustix", "windows-sys", @@ -1480,7 +1486,7 @@ dependencies = [ name = "islec" version = "0.1.0" dependencies = [ - "clap 3.2.8", + "clap 3.2.16", "cranelift-isle", "env_logger 0.9.0", "miette", @@ -1503,9 +1509,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "ittapi" @@ -1538,9 +1544,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] @@ -1574,9 +1580,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" [[package]] name = "libfuzzer-sys" @@ -1601,9 +1607,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" +checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" [[package]] name = "linux-raw-sys" @@ -1697,9 +1703,9 @@ checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" [[package]] name = "miette" -version = "5.1.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ec753a43fd71bb5f28751c9ec17fbe89d6d26ca8282d1e1f82f5ac3dbd5581e" +checksum = "8e2c9d50e919ffdc4d2d83b83972a13e8ba86ba8245a205bee9e314d593c15a8" dependencies = [ "atty", "backtrace", @@ -1717,9 +1723,9 @@ dependencies = [ [[package]] name = "miette-derive" -version = "5.1.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdfc33ea15c5446600f91d319299dd40301614afff7143cdfa9bf4c09da3ca64" +checksum = "3c8d10c73bcc9f0ab5c918521dab23d178062a56e6b328eb37106d497280bd94" dependencies = [ "proc-macro2", "quote", @@ -1728,43 +1734,23 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.5.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", -] - -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", + "windows-sys", ] [[package]] @@ -1899,9 +1885,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "oorandom" @@ -1959,9 +1945,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.0.0" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" [[package]] name = "owo-colors" @@ -2013,9 +1999,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" [[package]] name = "pem-rfc7468" @@ -2058,9 +2044,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" dependencies = [ "num-traits", "plotters-backend", @@ -2071,15 +2057,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" +checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" dependencies = [ "plotters-backend", ] @@ -2131,15 +2117,15 @@ checksum = "0127cbc0239f585139a56effd7867921eae3425a000a72dde2b0a156062346b2" dependencies = [ "cc", "dunce", - "getrandom 0.2.6", + "getrandom 0.2.7", "libc", ] [[package]] name = "pqcrypto-kyber" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a17989a978f7d7c1496e38806ad9ff11f36eb8e419c562eafddbbf176af4a8a" +checksum = "fe9d9695c19e525d5366c913562a331fbeef9a2ad801d9a9ded61a0e4c2fe0fb" dependencies = [ "cc", "glob", @@ -2190,11 +2176,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.37" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2219,9 +2205,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871372391786ccec00d3c5d3d6608905b3d4db263639cfe075d3b60a736d115a" +checksum = "f446d0a6efba22928558c4fb4ce0b3fd6c89b0061343e390bf01a703742b8125" dependencies = [ "cc", ] @@ -2240,9 +2226,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -2306,7 +2292,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -2335,9 +2321,9 @@ checksum = "04d0088f16afb86d12c7f239d8de4637fa68ecc99a3db227e1ab58a294713e60" [[package]] name = "rayon" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd249e82c21598a9a426a4e00dd7adc1d640b22445ec8545feef801d1a74c221" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg 1.1.0", "crossbeam-deque", @@ -2347,9 +2333,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -2359,9 +2345,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -2372,7 +2358,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "redox_syscall", "thiserror", ] @@ -2392,9 +2378,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.5" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -2409,9 +2395,9 @@ checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" @@ -2461,25 +2447,16 @@ version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "rustix" -version = "0.35.6" +version = "0.35.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef258c11e17f5c01979a10543a30a4e12faef6aab217a74266e747eefa3aed88" +checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787" dependencies = [ "bitflags", "errno", "io-lifetimes", - "itoa 1.0.1", + "itoa 1.0.3", "libc", "linux-raw-sys", "once_cell", @@ -2500,9 +2477,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "same-file" @@ -2519,17 +2496,11 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "semver" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" - [[package]] name = "serde" -version = "1.0.137" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" dependencies = [ "serde_derive", ] @@ -2546,9 +2517,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" dependencies = [ "proc-macro2", "quote", @@ -2557,11 +2528,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.80" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f972498cf015f7c0746cac89ebe1d6ef10c293b94175a243a2d9442c163d9944" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ - "itoa 1.0.1", + "itoa 1.0.3", "ryu", "serde", ] @@ -2590,11 +2561,11 @@ dependencies = [ [[package]] name = "shellexpand" -version = "2.1.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" dependencies = [ - "dirs-next", + "dirs", ] [[package]] @@ -2621,9 +2592,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3" +checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803" [[package]] name = "slice-group-by" @@ -2633,9 +2604,9 @@ checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "smawk" @@ -2737,13 +2708,13 @@ checksum = "7c68d531d83ec6c531150584c42a4290911964d5f0d79132b193b67252a23b71" [[package]] name = "syn" -version = "1.0.92" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2776,9 +2747,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" @@ -2854,18 +2825,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" dependencies = [ "proc-macro2", "quote", @@ -2893,10 +2864,11 @@ dependencies = [ [[package]] name = "tokio" -version = "1.18.1" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ + "autocfg 1.1.0", "bytes", "libc", "memchr", @@ -2911,9 +2883,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -2931,9 +2903,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if", "log", @@ -2944,9 +2916,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -2955,9 +2927,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", "valuable", @@ -2976,9 +2948,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.11" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ "ansi_term", "sharded-slab", @@ -2994,6 +2966,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + [[package]] name = "unicode-linebreak" version = "0.1.2" @@ -3027,11 +3005,11 @@ dependencies = [ [[package]] name = "uuid" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -3085,12 +3063,6 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3184,9 +3156,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3194,13 +3166,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -3209,9 +3181,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3219,9 +3191,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", @@ -3232,9 +3204,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" [[package]] name = "wasm-encoder" @@ -3318,7 +3290,7 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.88.0" -source = "git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2#b70721d912152e5e238bd7014e920d80946a8a6f" +source = "git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2#c93f911d4bcc738c32051a21505df7b4abf05d5d" dependencies = [ "indexmap", ] @@ -3442,7 +3414,7 @@ version = "0.41.0" dependencies = [ "anyhow", "async-trait", - "clap 3.2.8", + "clap 3.2.16", "component-macro-test", "component-test-util", "criterion", @@ -3482,7 +3454,7 @@ name = "wasmtime-cli-flags" version = "0.41.0" dependencies = [ "anyhow", - "clap 3.2.8", + "clap 3.2.16", "file-per-thread-logger", "pretty_env_logger", "rayon", @@ -3528,7 +3500,7 @@ version = "0.41.0" dependencies = [ "anyhow", "atty", - "clap 3.2.8", + "clap 3.2.16", "cranelift-entity", "env_logger 0.9.0", "gimli", @@ -3763,9 +3735,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -3964,18 +3936,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.11.1+zstd.1.5.2" +version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a16b8414fde0414e90c612eba70985577451c4c504b99885ebed24762cb81a" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "5.0.1+zstd.1.5.2" +version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c12659121420dd6365c5c3de4901f97145b79651fb1d25814020ed2ed0585ae" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" dependencies = [ "libc", "zstd-sys", diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs index 8ca0eeaf5ecb..b8641df62124 100644 --- a/cranelift/wasm/src/sections_translator.rs +++ b/cranelift/wasm/src/sections_translator.rs @@ -304,7 +304,7 @@ pub fn parse_element_section<'data>( match kind { ElementKind::Active { table_index, - init_expr, + offset_expr: init_expr, } => { let mut init_expr_reader = init_expr.get_binary_reader(); let (base, offset) = match init_expr_reader.read_operator()? { @@ -354,7 +354,7 @@ pub fn parse_data_section<'data>( match kind { DataKind::Active { memory_index, - init_expr, + offset_expr: init_expr, } => { let mut init_expr_reader = init_expr.get_binary_reader(); let (base, offset) = match init_expr_reader.read_operator()? { diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index 7429a348ab93..9b5186648e61 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -429,7 +429,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { match kind { ElementKind::Active { table_index, - init_expr, + offset_expr: init_expr, } => { let table_index = TableIndex::from_u32(table_index); let mut init_expr_reader = init_expr.get_binary_reader(); @@ -547,7 +547,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { match kind { DataKind::Active { memory_index, - init_expr, + offset_expr: init_expr, } => { let range = mk_range(&mut self.result.total_data)?; let memory_index = MemoryIndex::from_u32(memory_index); From 931feebbf269211aa9346526eaba2276f4f9c8f4 Mon Sep 17 00:00:00 2001 From: cosine Date: Tue, 16 Aug 2022 13:16:50 -0400 Subject: [PATCH 11/81] Add function refs feature flag; support testing --- build.rs | 1 + cranelift/codegen/src/verifier/mod.rs | 5 ++++- crates/cli-flags/src/lib.rs | 16 ++++++++++++++++ crates/wasmtime/src/config.rs | 23 +++++++++++++++++++++++ tests/all/externals.rs | 15 ++++++++------- 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/build.rs b/build.rs index d44b8c0c0e2d..cc1fc1e40648 100644 --- a/build.rs +++ b/build.rs @@ -38,6 +38,7 @@ fn main() -> anyhow::Result<()> { // out. if spec_tests > 0 { test_directory_module(out, "tests/spec_testsuite/proposals/memory64", strategy)?; + test_directory_module(out, "tests/spec_testsuite/proposals/function-references", strategy)?; } else { println!( "cargo:warning=The spec testsuite is disabled. To enable, run `git submodule \ diff --git a/cranelift/codegen/src/verifier/mod.rs b/cranelift/codegen/src/verifier/mod.rs index 34eed7f51a14..6602c9666d95 100644 --- a/cranelift/codegen/src/verifier/mod.rs +++ b/cranelift/codegen/src/verifier/mod.rs @@ -1236,7 +1236,10 @@ impl<'a> Verifier<'a> { errors.report(( inst, self.context(inst), - format!("has an invalid controlling type {}", ctrl_type), + format!( + "has an invalid controlling type {} (allowed set is {:?})", + ctrl_type, value_typeset + ), )); } diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index 49ad0ce06a9f..386f6fbe498a 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -41,6 +41,10 @@ pub const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ ("memory64", "enables support for 64-bit memories"), #[cfg(feature = "component-model")] ("component-model", "enables support for the component model"), + ( + "function-references", + "enables support for typed function references", + ), ]; pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[ @@ -337,6 +341,7 @@ impl CommonOptions { memory64, #[cfg(feature = "component-model")] component_model, + function_references, } = self.wasm_features.unwrap_or_default(); if let Some(enable) = simd { @@ -348,6 +353,9 @@ impl CommonOptions { if let Some(enable) = reference_types { config.wasm_reference_types(enable); } + if let Some(enable) = function_references { + config.wasm_function_references(enable); + } if let Some(enable) = multi_value { config.wasm_multi_value(enable); } @@ -399,6 +407,7 @@ pub struct WasmFeatures { pub memory64: Option, #[cfg(feature = "component-model")] pub component_model: Option, + pub function_references: Option, } fn parse_wasm_features(features: &str) -> Result { @@ -449,6 +458,7 @@ fn parse_wasm_features(features: &str) -> Result { memory64: all.or(values["memory64"]), #[cfg(feature = "component-model")] component_model: all.or(values["component-model"]), + function_references: all.or(values["function-references"]), }) } @@ -551,6 +561,7 @@ mod test { threads, multi_memory, memory64, + function_references, } = options.wasm_features.unwrap(); assert_eq!(reference_types, Some(true)); @@ -560,6 +571,7 @@ mod test { assert_eq!(threads, Some(true)); assert_eq!(multi_memory, Some(true)); assert_eq!(memory64, Some(true)); + assert_eq!(function_references, Some(true)); Ok(()) } @@ -576,6 +588,7 @@ mod test { threads, multi_memory, memory64, + function_references, } = options.wasm_features.unwrap(); assert_eq!(reference_types, Some(false)); @@ -585,6 +598,7 @@ mod test { assert_eq!(threads, Some(false)); assert_eq!(multi_memory, Some(false)); assert_eq!(memory64, Some(false)); + assert_eq!(function_references, Some(false)); Ok(()) } @@ -604,6 +618,7 @@ mod test { threads, multi_memory, memory64, + function_references, } = options.wasm_features.unwrap(); assert_eq!(reference_types, Some(false)); @@ -613,6 +628,7 @@ mod test { assert_eq!(threads, None); assert_eq!(multi_memory, Some(true)); assert_eq!(memory64, Some(true)); + assert_eq!(function_references, None); Ok(()) } diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 2b3cf1202c07..bc51def168c1 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -106,6 +106,7 @@ pub struct Config { pub(crate) memory_init_cow: bool, pub(crate) memory_guaranteed_dense_image_size: u64, pub(crate) force_memory_init_memfd: bool, + pub(crate) function_references: bool, } /// User-provided configuration for the compiler. @@ -189,6 +190,7 @@ impl Config { memory_init_cow: true, memory_guaranteed_dense_image_size: 16 << 20, force_memory_init_memfd: false, + function_references: false, }; #[cfg(compiler)] { @@ -196,6 +198,7 @@ impl Config { ret.cranelift_opt_level(OptLevel::Speed); } ret.wasm_reference_types(true); + ret.wasm_function_references(true); ret.wasm_multi_value(true); ret.wasm_bulk_memory(true); ret.wasm_simd(true); @@ -611,6 +614,22 @@ impl Config { self } + /// Configures whether the [WebAssembly function references proposal][proposal] + /// will be enabled for compilation. + /// + /// This feature gates non-nullable reference types, function reference + /// types, call_ref, ref.func, and non-nullable reference related instructions. + /// + /// Note that the function references proposal depends on the reference types proposal. + /// + /// This feature is `false` by default. + /// + /// [proposal]: https://github.com/WebAssembly/function-references + pub fn wasm_function_references(&mut self, enable: bool) -> &mut Self { + self.features.function_references = enable; + self + } + /// Configures whether the WebAssembly SIMD proposal will be /// enabled for compilation. /// @@ -1485,6 +1504,10 @@ impl fmt::Debug for Config { .field("parse_wasm_debuginfo", &self.tunables.parse_wasm_debuginfo) .field("wasm_threads", &self.features.threads) .field("wasm_reference_types", &self.features.reference_types) + .field( + "wasm_function_references", + &self.features.function_references, + ) .field("wasm_bulk_memory", &self.features.bulk_memory) .field("wasm_simd", &self.features.simd) .field("wasm_multi_value", &self.features.multi_value) diff --git a/tests/all/externals.rs b/tests/all/externals.rs index 7f4d1455de0a..0dab42aad068 100644 --- a/tests/all/externals.rs +++ b/tests/all/externals.rs @@ -1,7 +1,13 @@ use wasmtime::*; -const EXTERN_REF : RefType = RefType { nullable: true, heap_type: HeapType::Extern }; -const FUNC_REF : RefType = RefType { nullable: true, heap_type: HeapType::Func }; +const EXTERN_REF: RefType = RefType { + nullable: true, + heap_type: HeapType::Extern, +}; +const FUNC_REF: RefType = RefType { + nullable: true, + heap_type: HeapType::Func, +}; #[test] fn bad_globals() { @@ -24,11 +30,6 @@ fn bad_globals() { fn bad_tables() { let mut store = Store::<()>::default(); - // TODO(dhil) fixme: this test is not meaningful since the refactoring of the ValType. - // i32 not supported yet - // let ty = TableType::new(ValType::I32, 0, Some(1)); - // assert!(Table::new(&mut store, ty.clone(), Val::I32(0)).is_err()); - // mismatched initializer let ty = TableType::new(FUNC_REF, 0, Some(1)); assert!(Table::new(&mut store, ty.clone(), Val::I32(0)).is_err()); From 4897c592df37f93de52337ffd8f9dd0181b48de1 Mon Sep 17 00:00:00 2001 From: cosine Date: Wed, 16 Nov 2022 22:28:10 -0500 Subject: [PATCH 12/81] Provide function references support in helpers - Always support Index in blocktypes - Support Index as table type by pretending to be Func - Etc --- cranelift/wasm/src/translation_utils.rs | 44 ++++--------------------- crates/cranelift/src/func_environ.rs | 4 +-- crates/runtime/src/instance.rs | 4 +-- crates/runtime/src/table.rs | 6 ++-- tests/all/wast.rs | 2 ++ 5 files changed, 17 insertions(+), 43 deletions(-) diff --git a/cranelift/wasm/src/translation_utils.rs b/cranelift/wasm/src/translation_utils.rs index 2c56f60f4722..d618dfe90834 100644 --- a/cranelift/wasm/src/translation_utils.rs +++ b/cranelift/wasm/src/translation_utils.rs @@ -59,16 +59,6 @@ pub fn tabletype_to_type( } } -/// TODO(dhil): Temporary workaround, should be available from wasmparser/readers/core/types.rs -const FUNC_REF: wasmparser::RefType = wasmparser::RefType { - nullable: true, - heap_type: wasmparser::HeapType::Func, -}; -const EXTERN_REF: wasmparser::RefType = wasmparser::RefType { - nullable: true, - heap_type: wasmparser::HeapType::Extern, -}; - /// Get the parameter and result types for the given Wasm blocktype. pub fn blocktype_params_results<'a, T>( validator: &'a FuncValidator, @@ -83,32 +73,20 @@ where return Ok(match ty { wasmparser::BlockType::Empty => { let params: &'static [wasmparser::ValType] = &[]; - let results: &'static [wasmparser::ValType] = &[]; + // If we care about not allocating, surely we can type munge more. + // But, it is midnight + let results: std::vec::Vec = vec![]; ( itertools::Either::Left(params.iter().copied()), - itertools::Either::Left(results.iter().copied()), + itertools::Either::Left(results.into_iter()), ) } wasmparser::BlockType::Type(ty) => { let params: &'static [wasmparser::ValType] = &[]; - let results: &'static [wasmparser::ValType] = match ty { - wasmparser::ValType::I32 => &[wasmparser::ValType::I32], - wasmparser::ValType::I64 => &[wasmparser::ValType::I64], - wasmparser::ValType::F32 => &[wasmparser::ValType::F32], - wasmparser::ValType::F64 => &[wasmparser::ValType::F64], - wasmparser::ValType::V128 => &[wasmparser::ValType::V128], - wasmparser::ValType::Ref(rt) => { - match rt.heap_type { - wasmparser::HeapType::Extern => &[wasmparser::ValType::Ref(EXTERN_REF)], - wasmparser::HeapType::Func => &[wasmparser::ValType::Ref(FUNC_REF)], - _ => todo!("Implement blocktype_params_results for HeapType::Bot/Index"), // TODO(dhil) fixme: I have a feeling this one is going to be somewhat painful. - } - } - wasmparser::ValType::Bot => &[wasmparser::ValType::Bot], - }; + let results: std::vec::Vec = vec![ty.clone()]; ( itertools::Either::Left(params.iter().copied()), - itertools::Either::Left(results.iter().copied()), + itertools::Either::Left(results.into_iter()), ) } wasmparser::BlockType::FuncType(ty_index) => { @@ -146,15 +124,7 @@ pub fn block_with_params( builder.append_block_param(block, ir::types::F64); } wasmparser::ValType::Ref(rt) => { - match rt.heap_type { - wasmparser::HeapType::Func | wasmparser::HeapType::Extern => { - builder.append_block_param( - block, - environ.reference_type(rt.heap_type.try_into()?), - ); - } // TODO(dhil) fixme: verify that this is indeed the correct thing to do. - _ => todo!("Implement block_with_params for HeapType::Bot/Index"), // TODO(dhil) fixme - } + builder.append_block_param(block, environ.reference_type(rt.heap_type.try_into()?)); } wasmparser::ValType::V128 => { builder.append_block_param(block, ir::types::I8X16); diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 2f13ecfd3442..e28e73784324 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -939,7 +939,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let plan = &self.module.table_plans[table_index]; match plan.table.wasm_ty.heap_type { - WasmHeapType::Func => match plan.style { + WasmHeapType::Func | WasmHeapType::Index(_) => match plan.style { TableStyle::CallerChecksSignature => { Ok(self.get_or_init_funcref_table_elem(builder, table_index, table, index)) } @@ -1077,7 +1077,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let plan = &self.module.table_plans[table_index]; match plan.table.wasm_ty.heap_type { - WasmHeapType::Func => match plan.style { + WasmHeapType::Func | WasmHeapType::Index(_) => match plan.style { TableStyle::CallerChecksSignature => { let table_entry_addr = builder.ins().table_addr(pointer_type, table, index, 0); // Set the "initialized bit". See doc-comment on diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 0b6da1b944ce..80c74750edf2 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -30,7 +30,7 @@ use wasmtime_environ::{ packed_option::ReservedValue, DataIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex, GlobalInit, HostPtr, MemoryIndex, Module, PrimaryMap, SignatureIndex, TableIndex, - TableInitialization, TrapCode, VMOffsets, WasmType, WASM_EXTERN_REF, WASM_FUNC_REF, + TableInitialization, TrapCode, VMOffsets, WasmRefType, WasmType, WASM_EXTERN_REF, }; mod allocator; @@ -1006,7 +1006,7 @@ impl Instance { } GlobalInit::RefNullConst => match global.wasm_ty { // `VMGlobalDefinition::new()` already zeroed out the bits - WasmType::Ref(WASM_EXTERN_REF) | WasmType::Ref(WASM_FUNC_REF) => {} + WasmType::Ref(WasmRefType { nullable: true, .. }) => {} ty => panic!("unsupported reference type for global: {:?}", ty), }, GlobalInit::Import => panic!("locally-defined global initialized as import"), diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index f76b6bb7b7b0..dc0a981b6a21 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -8,7 +8,9 @@ use anyhow::{bail, format_err, Error, Result}; use std::convert::{TryFrom, TryInto}; use std::ops::Range; use std::ptr; -use wasmtime_environ::{TablePlan, TrapCode, WasmRefType, WasmHeapType, FUNCREF_INIT_BIT, FUNCREF_MASK}; +use wasmtime_environ::{ + TablePlan, TrapCode, WasmHeapType, WasmRefType, FUNCREF_INIT_BIT, FUNCREF_MASK, +}; /// An element going into or coming out of a table. /// @@ -167,7 +169,7 @@ fn wasm_to_table_type(rt: WasmRefType) -> Result { match rt.heap_type { WasmHeapType::Func => Ok(TableElementType::Func), WasmHeapType::Extern => Ok(TableElementType::Extern), - WasmHeapType::Index(_) => todo!("Implement WasmHeapType::Index for wasm_to_table_type"), + WasmHeapType::Index(_) => Ok(TableElementType::Func), ht => bail!("invalid table element type {:?}", ht), } } diff --git a/tests/all/wast.rs b/tests/all/wast.rs index e59b290e79eb..09ed62a69783 100644 --- a/tests/all/wast.rs +++ b/tests/all/wast.rs @@ -25,12 +25,14 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()> let memory64 = feature_found(wast, "memory64"); let multi_memory = feature_found(wast, "multi-memory"); let threads = feature_found(wast, "threads"); + let function_references = feature_found(wast, "function-references"); let mut cfg = Config::new(); cfg.wasm_simd(simd) .wasm_multi_memory(multi_memory) .wasm_threads(threads) .wasm_memory64(memory64) + .wasm_function_references(function_references) .cranelift_debug_verifier(true); cfg.wasm_component_model(feature_found(wast, "component-model")); From fda8182a3c318ed456e6dcdd7c60ec2812c9bc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Tue, 16 Aug 2022 17:18:31 +0100 Subject: [PATCH 13/81] Implement ref.as_non_null --- cranelift/codegen/src/ir/trapcode.rs | 5 +++++ cranelift/wasm/src/code_translator.rs | 11 +++++++++-- crates/cranelift/src/compiler.rs | 1 + crates/environ/src/trap_encoding.rs | 5 +++++ crates/wasmtime/src/trap.rs | 5 +++++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/cranelift/codegen/src/ir/trapcode.rs b/cranelift/codegen/src/ir/trapcode.rs index 3114114f6dc6..0ef55a81d7b6 100644 --- a/cranelift/codegen/src/ir/trapcode.rs +++ b/cranelift/codegen/src/ir/trapcode.rs @@ -49,6 +49,9 @@ pub enum TrapCode { /// This trap is resumable. Interrupt, + /// A reference that should not be null was null + NullReference, + /// A user-defined trap code. User(u16), } @@ -68,6 +71,7 @@ impl Display for TrapCode { BadConversionToInteger => "bad_toint", UnreachableCodeReached => "unreachable", Interrupt => "interrupt", + NullReference => "null_reference", User(x) => return write!(f, "user{}", x), }; f.write_str(identifier) @@ -91,6 +95,7 @@ impl FromStr for TrapCode { "bad_toint" => Ok(BadConversionToInteger), "unreachable" => Ok(UnreachableCodeReached), "interrupt" => Ok(Interrupt), + "null_reference" => Ok(NullReference), _ if s.starts_with("user") => s[4..].parse().map(User).map_err(|_| ()), _ => Err(()), } diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index 122ea15b7e58..ddc4ec98b5a2 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -2025,8 +2025,15 @@ pub fn translate_operator( Operator::BrOnNull { .. } | Operator::BrOnNonNull { .. } | Operator::CallRef - | Operator::ReturnCallRef - | Operator::RefAsNonNull => todo!("Implement Operator::[BrOnNull,BrOnNonNull,CallRef] for translate_operator"), // TODO(dhil) fixme + | Operator::ReturnCallRef => { + todo!("Implement Operator::[BrOnNull,BrOnNonNull,CallRef] for translate_operator") + } // TODO(dhil) fixme + Operator::RefAsNonNull => { + let r = state.pop1(); + let is_null = environ.translate_ref_is_null(builder.cursor(), r)?; + builder.ins().trapnz(is_null, ir::TrapCode::NullReference); + state.push1(r); + } }; Ok(()) } diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index addb9177bb3c..3b07a6a92080 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -899,6 +899,7 @@ fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation { ir::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger, ir::TrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached, ir::TrapCode::Interrupt => TrapCode::Interrupt, + ir::TrapCode::NullReference => TrapCode::NullReference, ir::TrapCode::User(ALWAYS_TRAP_CODE) => TrapCode::AlwaysTrapAdapter, // these should never be emitted by wasmtime-cranelift diff --git a/crates/environ/src/trap_encoding.rs b/crates/environ/src/trap_encoding.rs index 1a56bb2618a1..0c688047e9a9 100644 --- a/crates/environ/src/trap_encoding.rs +++ b/crates/environ/src/trap_encoding.rs @@ -1,5 +1,6 @@ use object::write::{Object, StandardSegment}; use object::{Bytes, LittleEndian, SectionKind, U32Bytes}; + use std::convert::TryFrom; use std::ops::Range; @@ -99,6 +100,9 @@ pub enum TrapCode { /// This trap is resumable. Interrupt, + /// A reference was null + NullReference, + /// Used for the component model when functions are lifted/lowered in a way /// that generates a function that always traps. AlwaysTrapAdapter, @@ -209,6 +213,7 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option { BadConversionToInteger UnreachableCodeReached Interrupt + NullReference AlwaysTrapAdapter } diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 1d7e0095b20a..f3b0531dee45 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -88,6 +88,9 @@ pub enum TrapCode { /// Execution has potentially run too long and may be interrupted. Interrupt, + /// Okay why is this defined three times i am losing my mind + NullReference, + /// When the `component-model` feature is enabled this trap represents a /// function that was `canon lift`'d, then `canon lower`'d, then called. /// This combination of creation of a function in the component model @@ -111,6 +114,7 @@ impl TrapCode { EnvTrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger, EnvTrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached, EnvTrapCode::Interrupt => TrapCode::Interrupt, + EnvTrapCode::NullReference => TrapCode::NullReference, EnvTrapCode::AlwaysTrapAdapter => TrapCode::AlwaysTrapAdapter, } } @@ -131,6 +135,7 @@ impl fmt::Display for TrapCode { BadConversionToInteger => "invalid conversion to integer", UnreachableCodeReached => "wasm `unreachable` instruction executed", Interrupt => "interrupt", + NullReference => "null reference", AlwaysTrapAdapter => "degenerate component adapter called", }; write!(f, "{}", desc) From 19593044b8ee3b0469a0f228c4ed623bc3211914 Mon Sep 17 00:00:00 2001 From: Luna Phipps-Costin Date: Tue, 16 Aug 2022 13:54:46 -0400 Subject: [PATCH 14/81] Add br_on_null --- cranelift/wasm/src/code_translator.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index ddc4ec98b5a2..9c4ec6065470 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -2022,12 +2022,21 @@ pub fn translate_operator( // TODO(dhil) fixme: merge into the above list. // Function references instructions - Operator::BrOnNull { .. } - | Operator::BrOnNonNull { .. } - | Operator::CallRef - | Operator::ReturnCallRef => { + Operator::BrOnNonNull { .. } | Operator::CallRef | Operator::ReturnCallRef => { todo!("Implement Operator::[BrOnNull,BrOnNonNull,CallRef] for translate_operator") } // TODO(dhil) fixme + Operator::BrOnNull { relative_depth } => { + let r = state.pop1(); + let (br_destination, inputs) = translate_br_if_args(*relative_depth, state); + let is_null = environ.translate_ref_is_null(builder.cursor(), r)?; + canonicalise_then_brnz(builder, is_null, br_destination, inputs); + + let next_block = builder.create_block(); + canonicalise_then_jump(builder, next_block, &[]); + builder.seal_block(next_block); // The only predecessor is the current block. + builder.switch_to_block(next_block); + state.push1(r); + } Operator::RefAsNonNull => { let r = state.pop1(); let is_null = environ.translate_ref_is_null(builder.cursor(), r)?; From e14fde523d6b8637b70718e44e291dd3ca666f01 Mon Sep 17 00:00:00 2001 From: cosine Date: Sun, 13 Nov 2022 20:14:46 -0500 Subject: [PATCH 15/81] Update Cargo.lock to use wasm-tools with peek This will ultimately be reverted when we refer to wasm-tools#function-references, which doesn't have peek, but does have type annotations on CallRef --- Cargo.lock | 158 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 94 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1904d6b3f1fe..55561d76858d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,9 +89,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.59" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" +checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" [[package]] name = "arbitrary" @@ -219,9 +219,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byteorder" @@ -401,9 +401,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.16" +version = "3.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" dependencies = [ "atty", "bitflags", @@ -418,9 +418,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.15" +version = "3.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" +checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" dependencies = [ "heck", "proc-macro-error", @@ -499,9 +499,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" dependencies = [ "libc", ] @@ -698,7 +698,7 @@ dependencies = [ name = "cranelift-serde" version = "0.88.0" dependencies = [ - "clap 3.2.16", + "clap 3.2.17", "cranelift-codegen", "cranelift-reader", "serde_json", @@ -711,7 +711,7 @@ dependencies = [ "anyhow", "capstone", "cfg-if", - "clap 3.2.16", + "clap 3.2.17", "cranelift", "cranelift-codegen", "cranelift-entity", @@ -1062,9 +1062,9 @@ dependencies = [ [[package]] name = "either" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" @@ -1440,9 +1440,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb" +checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" dependencies = [ "libc", "windows-sys", @@ -1486,7 +1486,7 @@ dependencies = [ name = "islec" version = "0.1.0" dependencies = [ - "clap 3.2.16", + "clap 3.2.17", "cranelift-isle", "env_logger 0.9.0", "miette", @@ -1580,9 +1580,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.127" +version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "libfuzzer-sys" @@ -1607,9 +1607,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" +checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" [[package]] name = "linux-raw-sys" @@ -1703,9 +1703,9 @@ checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" [[package]] name = "miette" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2c9d50e919ffdc4d2d83b83972a13e8ba86ba8245a205bee9e314d593c15a8" +checksum = "a28d6092d7e94a90bb9ea8e6c26c99d5d112d49dda2afdb4f7ea8cf09e1a5a6d" dependencies = [ "atty", "backtrace", @@ -1723,9 +1723,9 @@ dependencies = [ [[package]] name = "miette-derive" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8d10c73bcc9f0ab5c918521dab23d178062a56e6b328eb37106d497280bd94" +checksum = "4f2485ed7d1fe80704928e3eb86387439609bd0c6bb96db8208daa364cfd1e09" dependencies = [ "proc-macro2", "quote", @@ -1885,9 +1885,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" [[package]] name = "oorandom" @@ -1945,15 +1945,15 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.2.0" +version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" [[package]] name = "owo-colors" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "p256" @@ -2044,9 +2044,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" +checksum = "716b4eeb6c4a1d3ecc956f75b43ec2e8e8ba80026413e70a3f41fd3313d3492b" dependencies = [ "num-traits", "plotters-backend", @@ -2063,9 +2063,9 @@ checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" dependencies = [ "plotters-backend", ] @@ -2449,9 +2449,9 @@ checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustix" -version = "0.35.7" +version = "0.35.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787" +checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" dependencies = [ "bitflags", "errno", @@ -2498,9 +2498,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.142" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] @@ -2517,9 +2517,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.142" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", @@ -2528,9 +2528,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.83" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa 1.0.3", "ryu", @@ -3217,32 +3217,41 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d443c5a7daae71697d97ec12ad70b4fe8766d3a0f4db16158ac8b781365892f7" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-mutate" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e6de18ed96f27d3942041e5ae02177aff18e4425196a3d4b1f14145d027f71" +checksum = "f04ad5c8a18bf9d8d07ad9df8dea5e8ff701ab3472583a79350c3ab5b4766705" dependencies = [ "egg", "log", "rand 0.8.5", "thiserror", - "wasm-encoder", - "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.16.0", + "wasmparser 0.89.1", ] [[package]] name = "wasm-smith" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54f72dd89c036847831ef4d3b8f7fd8618d87509422728f12b0937f96d6dd04" +checksum = "3daf8042376731e1873eae92dd609e1d0781105ffc3ffbc452f7bab719c887e2" dependencies = [ "arbitrary", "flagset", "indexmap", "leb128", - "wasm-encoder", - "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-encoder 0.16.0", + "wasmparser 0.89.1", ] [[package]] @@ -3290,19 +3299,28 @@ dependencies = [ [[package]] name = "wasmparser" version = "0.88.0" -source = "git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2#c93f911d4bcc738c32051a21505df7b4abf05d5d" +source = "git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2#27948fa2f43b0e206353532be14155cfcb1508b4" +dependencies = [ + "indexmap", +] + +[[package]] +name = "wasmparser" +version = "0.89.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5d3e08b13876f96dd55608d03cd4883a0545884932d5adf11925876c96daef" dependencies = [ "indexmap", ] [[package]] name = "wasmprinter" -version = "0.2.38" +version = "0.2.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f2786f19a25211ddfa331e28b7579a6d6880f5f4b18d21253cd90274aa4c21" +checksum = "aa9e5ee2f56cc8a5da489558114e8c118e5a8416d96aefe63dcf1b5b05b858c6" dependencies = [ "anyhow", - "wasmparser 0.88.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.89.1", ] [[package]] @@ -3414,7 +3432,7 @@ version = "0.41.0" dependencies = [ "anyhow", "async-trait", - "clap 3.2.16", + "clap 3.2.17", "component-macro-test", "component-test-util", "criterion", @@ -3454,7 +3472,7 @@ name = "wasmtime-cli-flags" version = "0.41.0" dependencies = [ "anyhow", - "clap 3.2.16", + "clap 3.2.17", "file-per-thread-logger", "pretty_env_logger", "rayon", @@ -3500,7 +3518,7 @@ version = "0.41.0" dependencies = [ "anyhow", "atty", - "clap 3.2.16", + "clap 3.2.17", "cranelift-entity", "env_logger 0.9.0", "gimli", @@ -3510,7 +3528,7 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasm-encoder", + "wasm-encoder 0.15.0", "wasmparser 0.88.0 (git+https://github.com/effect-handlers/wasm-tools?branch=func-ref-2)", "wasmprinter", "wasmtime-component-util", @@ -3581,7 +3599,7 @@ dependencies = [ "target-lexicon", "tempfile", "v8", - "wasm-encoder", + "wasm-encoder 0.15.0", "wasm-mutate", "wasm-smith", "wasm-spec-interpreter", @@ -3721,16 +3739,28 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder", + "wasm-encoder 0.15.0", +] + +[[package]] +name = "wast" +version = "46.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0ab19660e3ea6891bba69167b9be40fad00fb1fe3dd39c5eebcee15607131b" +dependencies = [ + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.16.0", ] [[package]] name = "wat" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d4bc4724b4f02a482c8cab053dac5ef26410f264c06ce914958f9a42813556" +checksum = "8f775282def4d5bffd94d60d6ecd57bfe6faa46171cdbf8d32bd5458842b1e3e" dependencies = [ - "wast 45.0.0", + "wast 46.0.0", ] [[package]] From 6e317608e0a0938002b4e2cfb19714fcf961e5ea Mon Sep 17 00:00:00 2001 From: Luna Phipps-Costin Date: Thu, 18 Aug 2022 10:03:04 -0400 Subject: [PATCH 16/81] Add call_ref --- cranelift/wasm/src/code_translator.rs | 37 ++++++++++++++++++-- cranelift/wasm/src/environ/dummy.rs | 10 ++++++ cranelift/wasm/src/environ/spec.rs | 17 +++++++++ cranelift/wasm/src/func_translator.rs | 3 +- crates/cranelift/src/func_environ.rs | 50 +++++++++++++++++++++++++++ crates/wast/src/wast.rs | 2 ++ 6 files changed, 116 insertions(+), 3 deletions(-) diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index 9c4ec6065470..e84ce91617cc 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -94,7 +94,7 @@ use smallvec::SmallVec; use std::cmp; use std::convert::TryFrom; use std::vec::Vec; -use wasmparser::{FuncValidator, MemoryImmediate, Operator, WasmModuleResources}; +use wasmparser::{FuncValidator, MemoryImmediate, Operator, ValType, WasmModuleResources}; // Clippy warns about "align: _" but its important to document that the flags field is ignored #[cfg_attr( @@ -109,6 +109,7 @@ pub fn translate_operator( builder: &mut FunctionBuilder, state: &mut FuncTranslationState, environ: &mut FE, + ty: Option, ) -> WasmResult<()> { if !state.reachable { translate_unreachable_operator(validator, &op, builder, state, environ)?; @@ -2022,7 +2023,7 @@ pub fn translate_operator( // TODO(dhil) fixme: merge into the above list. // Function references instructions - Operator::BrOnNonNull { .. } | Operator::CallRef | Operator::ReturnCallRef => { + Operator::BrOnNonNull { .. } | Operator::ReturnCallRef => { todo!("Implement Operator::[BrOnNull,BrOnNonNull,CallRef] for translate_operator") } // TODO(dhil) fixme Operator::BrOnNull { relative_depth } => { @@ -2037,6 +2038,38 @@ pub fn translate_operator( builder.switch_to_block(next_block); state.push1(r); } + Operator::CallRef => { + // Get function signature + let index = match ty { + None => panic!("expected Some val type"), + Some(wasmparser::ValType::Ref(wasmparser::RefType { + heap_type: wasmparser::HeapType::Index(type_idx), + .. + })) => type_idx, + _ => panic!("unexpected val type"), + }; + // `index` is the index of the function's signature and `table_index` is the index of + // the table to search the function in. + let (sigref, num_args) = state.get_indirect_sig(builder.func, index, environ)?; + //let table = state.get_or_create_table(builder.func, *table_index, environ)?; + let callee = state.pop1(); + + // Bitcast any vector arguments to their default type, I8X16, before calling. + let args = state.peekn_mut(num_args); + bitcast_wasm_params(environ, sigref, args, builder); + + let call = + environ.translate_call_ref(builder, sigref, callee, state.peekn(num_args))?; + + let inst_results = builder.inst_results(call); + debug_assert_eq!( + inst_results.len(), + builder.func.dfg.signatures[sigref].returns.len(), + "translate_call_ref results should match the call signature" + ); + state.popn(num_args); + state.pushn(inst_results); + } Operator::RefAsNonNull => { let r = state.pop1(); let is_null = environ.translate_ref_is_null(builder.cursor(), r)?; diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs index 27529954de9b..a8bb65cfbdd4 100644 --- a/cranelift/wasm/src/environ/dummy.rs +++ b/cranelift/wasm/src/environ/dummy.rs @@ -449,6 +449,16 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ Ok(pos.ins().Call(ir::Opcode::Call, INVALID, callee, args).0) } + fn translate_call_ref( + &mut self, + _builder: &mut FunctionBuilder, + _sig_ref: ir::SigRef, + _callee: ir::Value, + _call_args: &[ir::Value], + ) -> WasmResult { + todo!("Implement dummy translate_call_ref") + } + fn translate_memory_grow( &mut self, mut pos: FuncCursor, diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index 8fa4b2bf3e30..bd307f311f75 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -194,6 +194,23 @@ pub trait FuncEnvironment: TargetEnvironment { Ok(pos.ins().call(callee, call_args)) } + /// Translate a `call_ref` WebAssembly instruction at `pos`. + /// + /// Insert instructions at `pos` for an indirect call to the + /// function `callee`. The `callee` value will have type `Ref`. TODO + /// + /// The signature `sig_ref` was previously created by `make_indirect_sig()`. + /// + /// Return the call instruction whose results are the WebAssembly return values. + #[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))] + fn translate_call_ref( + &mut self, + builder: &mut FunctionBuilder, + sig_ref: ir::SigRef, + callee: ir::Value, + call_args: &[ir::Value], + ) -> WasmResult; + /// Translate a `memory.grow` WebAssembly instruction. /// /// The `index` provided identifies the linear memory to grow, and `heap` is the heap reference diff --git a/cranelift/wasm/src/func_translator.rs b/cranelift/wasm/src/func_translator.rs index b96571d664c9..da1c9589c651 100644 --- a/cranelift/wasm/src/func_translator.rs +++ b/cranelift/wasm/src/func_translator.rs @@ -233,12 +233,13 @@ fn parse_function_body( environ.before_translate_function(builder, state)?; while !reader.eof() { + let ty = validator.peek(); let pos = reader.original_position(); builder.set_srcloc(cur_srcloc(&reader)); let op = reader.read_operator()?; validator.op(pos, &op)?; environ.before_translate_operator(&op, builder, state)?; - translate_operator(validator, &op, builder, state, environ)?; + translate_operator(validator, &op, builder, state, environ, ty)?; environ.after_translate_operator(&op, builder, state)?; } environ.after_translate_function(builder, state)?; diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index e28e73784324..6b15edb4a5a6 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -1682,6 +1682,56 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args)) } + // At this time, this looks a lot like translate_call_indirect. But, it + // will soon change if an unchecked indirect call is added to cranelift, so + // when it breaks, just do that instead of factoring it with call_indirect + fn translate_call_ref( + &mut self, + builder: &mut FunctionBuilder, + sig_ref: ir::SigRef, + callee: ir::Value, + call_args: &[ir::Value], + ) -> WasmResult { + let pointer_type = self.pointer_type(); + + // Check for whether the callee is null, and trap if so. + builder.ins().trapz(callee, ir::TrapCode::NullReference); + + // Dereference callee pointer to get the function address. + let mem_flags = ir::MemFlags::trusted(); + let func_addr = builder.ins().load( + pointer_type, + mem_flags, + callee, + i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_func_ptr()), + ); + + + let mut real_call_args = Vec::with_capacity(call_args.len() + 2); + let caller_vmctx = builder + .func + .special_param(ArgumentPurpose::VMContext) + .unwrap(); + + // First append the callee vmctx address. + let vmctx = builder.ins().load( + pointer_type, + mem_flags, + callee, + i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_vmctx()), + ); + real_call_args.push(vmctx); + real_call_args.push(caller_vmctx); + + // Then append the regular call arguments. + real_call_args.extend_from_slice(call_args); + + Ok(builder + .ins() + .call_indirect(sig_ref, func_addr, &real_call_args)) + } + + fn translate_memory_grow( &mut self, mut pos: FuncCursor<'_>, diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index d9f885acff28..72c906a04736 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -328,6 +328,8 @@ impl WastContext { // specifies which element is uninitialized, but our traps don't // shepherd that information out. || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element")) + // function references call_ref + || (expected.contains("null function") && actual.contains("null reference")) { return Ok(()); } From aa71b968640a987ad1d5d1fd3602b0ef136e16cc Mon Sep 17 00:00:00 2001 From: cosine Date: Wed, 16 Nov 2022 22:31:58 -0500 Subject: [PATCH 17/81] Support typed function references in ref.null --- crates/cranelift/src/func_environ.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 6b15edb4a5a6..b4c38dc05843 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -10,7 +10,8 @@ use cranelift_frontend::FunctionBuilder; use cranelift_frontend::Variable; use cranelift_wasm::{ self, FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, MemoryIndex, TableIndex, - TargetEnvironment, TypeIndex, WasmError, WasmResult, WasmType, WasmRefType, WasmHeapType, WASM_EXTERN_REF, + TargetEnvironment, TypeIndex, WasmError, WasmHeapType, WasmRefType, WasmResult, WasmType, + WASM_EXTERN_REF, }; use std::convert::TryFrom; use std::mem; @@ -1269,13 +1270,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m ht: WasmHeapType, ) -> WasmResult { Ok(match ht { - WasmHeapType::Func => pos.ins().iconst(self.pointer_type(), 0), + WasmHeapType::Func | WasmHeapType::Index(_) => pos.ins().iconst(self.pointer_type(), 0), WasmHeapType::Extern => pos.ins().null(self.reference_type(ht)), - _ => { - return Err(WasmError::Unsupported( - "`ref.null T` that is not a `funcref` or an `externref`".into(), - )); - } + WasmHeapType::Bot => panic!("goes away in refactor"), }) } @@ -1481,7 +1478,10 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m // `GlobalVariable` for which `cranelift-wasm` supports custom access // translation. match self.module.globals[index].wasm_ty { - WasmType::Ref(WasmRefType { heap_type: WasmHeapType::Extern, .. }) => Ok(GlobalVariable::Custom), + WasmType::Ref(WasmRefType { + heap_type: WasmHeapType::Extern, + .. + }) => Ok(GlobalVariable::Custom), _ => { let (gv, offset) = self.get_global_location(func, index); Ok(GlobalVariable::Memory { @@ -1706,7 +1706,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_func_ptr()), ); - let mut real_call_args = Vec::with_capacity(call_args.len() + 2); let caller_vmctx = builder .func @@ -1731,7 +1730,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m .call_indirect(sig_ref, func_addr, &real_call_args)) } - fn translate_memory_grow( &mut self, mut pos: FuncCursor<'_>, From 1f411e639f6bb42320c2d297ddea61774dd54d65 Mon Sep 17 00:00:00 2001 From: cosine Date: Fri, 7 Oct 2022 23:57:17 -0400 Subject: [PATCH 18/81] Implement br_on_non_null --- cranelift/wasm/src/code_translator.rs | 31 ++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index e84ce91617cc..8304d82f7118 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -2023,9 +2023,12 @@ pub fn translate_operator( // TODO(dhil) fixme: merge into the above list. // Function references instructions - Operator::BrOnNonNull { .. } | Operator::ReturnCallRef => { - todo!("Implement Operator::[BrOnNull,BrOnNonNull,CallRef] for translate_operator") - } // TODO(dhil) fixme + Operator::ReturnCallRef => { + return Err(wasm_unsupported!( + "proposed tail-call operator for function references {:?}", + op + )); + } Operator::BrOnNull { relative_depth } => { let r = state.pop1(); let (br_destination, inputs) = translate_br_if_args(*relative_depth, state); @@ -2038,6 +2041,28 @@ pub fn translate_operator( builder.switch_to_block(next_block); state.push1(r); } + Operator::BrOnNonNull { relative_depth } => { + // We write this a bit differently from the spec to avoid an extra + // block/branch and the typed accounting thereof. Instead of the + // spec's approach, it's described as such: + // Peek the value val from the stack. + // If val is ref.null ht, then: pop the value val from the stack. + // Else: Execute the instruction (br relative_depth). + let is_null = environ.translate_ref_is_null(builder.cursor(), state.peek1())?; + let (br_destination, inputs) = translate_br_if_args(*relative_depth, state); + canonicalise_then_brz(builder, is_null, br_destination, inputs); + // In the null case, pop the ref + state.pop1(); + // It seems that we're required to create an unconditional jump for + // the non-br case, based on the example of BrIf, but i'm not sure why + let next_block = builder.create_block(); + canonicalise_then_jump(builder, next_block, &[]); + builder.seal_block(next_block); // The only predecessor is the current block. + + // The rest of the translation operates on our is null case, which is + // currently an empty block + builder.switch_to_block(next_block); + } Operator::CallRef => { // Get function signature let index = match ty { From a589b9948aca197fc8e6394ba3663e248f0edfb8 Mon Sep 17 00:00:00 2001 From: cosine Date: Thu, 17 Nov 2022 17:56:08 -0500 Subject: [PATCH 19/81] Remove extraneous flag; default func refs false --- crates/wasmtime/src/config.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index bc51def168c1..513a1d8ea706 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -106,7 +106,6 @@ pub struct Config { pub(crate) memory_init_cow: bool, pub(crate) memory_guaranteed_dense_image_size: u64, pub(crate) force_memory_init_memfd: bool, - pub(crate) function_references: bool, } /// User-provided configuration for the compiler. @@ -190,7 +189,6 @@ impl Config { memory_init_cow: true, memory_guaranteed_dense_image_size: 16 << 20, force_memory_init_memfd: false, - function_references: false, }; #[cfg(compiler)] { @@ -198,7 +196,6 @@ impl Config { ret.cranelift_opt_level(OptLevel::Speed); } ret.wasm_reference_types(true); - ret.wasm_function_references(true); ret.wasm_multi_value(true); ret.wasm_bulk_memory(true); ret.wasm_simd(true); From b6d81e26bb2acec1a48c134241293c27522f1287 Mon Sep 17 00:00:00 2001 From: cosine Date: Thu, 17 Nov 2022 18:06:25 -0500 Subject: [PATCH 20/81] Use IndirectCallToNull trap code for call_ref --- cranelift/wasm/src/code_translator.rs | 1 - crates/cranelift/src/func_environ.rs | 4 +++- crates/wast/src/wast.rs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index 8304d82f7118..f1594c9e59a1 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -2076,7 +2076,6 @@ pub fn translate_operator( // `index` is the index of the function's signature and `table_index` is the index of // the table to search the function in. let (sigref, num_args) = state.get_indirect_sig(builder.func, index, environ)?; - //let table = state.get_or_create_table(builder.func, *table_index, environ)?; let callee = state.pop1(); // Bitcast any vector arguments to their default type, I8X16, before calling. diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index b4c38dc05843..4bdf51ed4341 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -1695,7 +1695,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let pointer_type = self.pointer_type(); // Check for whether the callee is null, and trap if so. - builder.ins().trapz(callee, ir::TrapCode::NullReference); + builder + .ins() + .trapz(callee, ir::TrapCode::IndirectCallToNull); // Dereference callee pointer to get the function address. let mem_flags = ir::MemFlags::trusted(); diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 72c906a04736..a4990c2904ed 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -329,7 +329,7 @@ impl WastContext { // shepherd that information out. || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element")) // function references call_ref - || (expected.contains("null function") && actual.contains("null reference")) + || (expected.contains("null function") && actual.contains("uninitialized element")) { return Ok(()); } From 99fba53416e096873a95558e67be5952a52471ba Mon Sep 17 00:00:00 2001 From: cosine Date: Sat, 19 Nov 2022 19:31:06 -0500 Subject: [PATCH 21/81] Factor common call_indirect / call_ref into a fn --- crates/cranelift/src/func_environ.rs | 118 ++++++++++++--------------- 1 file changed, 51 insertions(+), 67 deletions(-) diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 4bdf51ed4341..9178cc951901 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -295,6 +295,52 @@ impl<'module_environment> FuncEnvironment<'module_environment> { (base, func_addr) } + /// This calls a function by reference without checking the signature. It + /// gets the function address, sets relevant flags, and passes the special + /// callee/caller vmctxs. It is used by both call_indirect (which checks the + /// signature) and call_ref (which doesn't). + fn call_function_unchecked( + &mut self, + builder: &mut FunctionBuilder, + sig_ref: ir::SigRef, + callee: ir::Value, + call_args: &[ir::Value], + ) -> WasmResult { + let pointer_type = self.pointer_type(); + + // Dereference callee pointer to get the function address. + let mem_flags = ir::MemFlags::trusted(); + let func_addr = builder.ins().load( + pointer_type, + mem_flags, + callee, + i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_func_ptr()), + ); + + let mut real_call_args = Vec::with_capacity(call_args.len() + 2); + let caller_vmctx = builder + .func + .special_param(ArgumentPurpose::VMContext) + .unwrap(); + + // First append the callee vmctx address. + let vmctx = builder.ins().load( + pointer_type, + mem_flags, + callee, + i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_vmctx()), + ); + real_call_args.push(vmctx); + real_call_args.push(caller_vmctx); + + // Then append the regular call arguments. + real_call_args.extend_from_slice(call_args); + + Ok(builder + .ins() + .call_indirect(sig_ref, func_addr, &real_call_args)) + } + /// Generate code to increment or decrement the given `externref`'s /// reference count. /// @@ -1553,15 +1599,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m .ins() .trapz(anyfunc_ptr, ir::TrapCode::IndirectCallToNull); - // Dereference anyfunc pointer to get the function address. - let mem_flags = ir::MemFlags::trusted(); - let func_addr = builder.ins().load( - pointer_type, - mem_flags, - anyfunc_ptr, - i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_func_ptr()), - ); - // If necessary, check the signature. match self.module.table_plans[table_index].style { TableStyle::CallerChecksSignature => { @@ -1606,28 +1643,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m } } - let mut real_call_args = Vec::with_capacity(call_args.len() + 2); - let caller_vmctx = builder - .func - .special_param(ArgumentPurpose::VMContext) - .unwrap(); - - // First append the callee vmctx address. - let vmctx = builder.ins().load( - pointer_type, - mem_flags, - anyfunc_ptr, - i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_vmctx()), - ); - real_call_args.push(vmctx); - real_call_args.push(caller_vmctx); - - // Then append the regular call arguments. - real_call_args.extend_from_slice(call_args); - - Ok(builder - .ins() - .call_indirect(sig_ref, func_addr, &real_call_args)) + self.call_function_unchecked(builder, sig_ref, anyfunc_ptr, call_args) } fn translate_call( @@ -1682,9 +1698,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args)) } - // At this time, this looks a lot like translate_call_indirect. But, it - // will soon change if an unchecked indirect call is added to cranelift, so - // when it breaks, just do that instead of factoring it with call_indirect fn translate_call_ref( &mut self, builder: &mut FunctionBuilder, @@ -1692,44 +1705,15 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m callee: ir::Value, call_args: &[ir::Value], ) -> WasmResult { - let pointer_type = self.pointer_type(); - // Check for whether the callee is null, and trap if so. + // This doesn't need to happen when the ref is non-nullable. But, it + // may not need to happen ever. So, leave it for now and let smart people + // figure that out builder .ins() .trapz(callee, ir::TrapCode::IndirectCallToNull); - // Dereference callee pointer to get the function address. - let mem_flags = ir::MemFlags::trusted(); - let func_addr = builder.ins().load( - pointer_type, - mem_flags, - callee, - i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_func_ptr()), - ); - - let mut real_call_args = Vec::with_capacity(call_args.len() + 2); - let caller_vmctx = builder - .func - .special_param(ArgumentPurpose::VMContext) - .unwrap(); - - // First append the callee vmctx address. - let vmctx = builder.ins().load( - pointer_type, - mem_flags, - callee, - i32::from(self.offsets.ptr.vmcaller_checked_anyfunc_vmctx()), - ); - real_call_args.push(vmctx); - real_call_args.push(caller_vmctx); - - // Then append the regular call arguments. - real_call_args.extend_from_slice(call_args); - - Ok(builder - .ins() - .call_indirect(sig_ref, func_addr, &real_call_args)) + self.call_function_unchecked(builder, sig_ref, callee, call_args) } fn translate_memory_grow( From c199f2d027ffa7a082b604e7e535789634a27ff5 Mon Sep 17 00:00:00 2001 From: cosine Date: Sat, 19 Nov 2022 19:35:16 -0500 Subject: [PATCH 22/81] Remove copypasta clippy attribute / format --- cranelift/wasm/src/environ/spec.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index bd307f311f75..5d5a4c5959b2 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -9,7 +9,7 @@ use crate::state::FuncTranslationState; use crate::{ DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, - Table, TableIndex, Tag, TagIndex, TypeIndex, WasmError, WasmFuncType, WasmResult, WasmHeapType, + Table, TableIndex, Tag, TagIndex, TypeIndex, WasmError, WasmFuncType, WasmHeapType, WasmResult, }; use core::convert::From; use cranelift_codegen::cursor::FuncCursor; @@ -197,12 +197,11 @@ pub trait FuncEnvironment: TargetEnvironment { /// Translate a `call_ref` WebAssembly instruction at `pos`. /// /// Insert instructions at `pos` for an indirect call to the - /// function `callee`. The `callee` value will have type `Ref`. TODO + /// function `callee`. The `callee` value will have type `Ref`. /// /// The signature `sig_ref` was previously created by `make_indirect_sig()`. /// /// Return the call instruction whose results are the WebAssembly return values. - #[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))] fn translate_call_ref( &mut self, builder: &mut FunctionBuilder, @@ -376,7 +375,11 @@ pub trait FuncEnvironment: TargetEnvironment { /// null sentinel is not a null reference type pointer for your type. If you /// override this method, then you should also override /// `translate_ref_is_null` as well. - fn translate_ref_null(&mut self, mut pos: FuncCursor, ty: WasmHeapType) -> WasmResult { + fn translate_ref_null( + &mut self, + mut pos: FuncCursor, + ty: WasmHeapType, + ) -> WasmResult { let _ = ty; Ok(pos.ins().null(self.reference_type(ty))) } From 8e33f186b0ecb7f5556c3d6c9bc8b7180666d1cb Mon Sep 17 00:00:00 2001 From: cosine Date: Mon, 5 Dec 2022 18:13:03 -0500 Subject: [PATCH 23/81] Add a some more tests for typed table instructions There certainly need to be many more, but this at least catches the bugs fixed in the next commit --- build.rs | 7 +- .../function-references/table_fill.wast | 163 +++++++++++++++ .../function-references/table_get.wast | 98 +++++++++ .../function-references/table_grow.wast | 196 ++++++++++++++++++ .../function-references/table_set.wast | 140 +++++++++++++ 5 files changed, 603 insertions(+), 1 deletion(-) create mode 100644 tests/misc_testsuite/function-references/table_fill.wast create mode 100644 tests/misc_testsuite/function-references/table_get.wast create mode 100644 tests/misc_testsuite/function-references/table_grow.wast create mode 100644 tests/misc_testsuite/function-references/table_set.wast diff --git a/build.rs b/build.rs index cc1fc1e40648..1248cab7dc20 100644 --- a/build.rs +++ b/build.rs @@ -29,6 +29,7 @@ fn main() -> anyhow::Result<()> { test_directory_module(out, "tests/misc_testsuite/threads", strategy)?; test_directory_module(out, "tests/misc_testsuite/memory64", strategy)?; test_directory_module(out, "tests/misc_testsuite/component-model", strategy)?; + test_directory_module(out, "tests/misc_testsuite/function-references", strategy)?; Ok(()) })?; @@ -38,7 +39,11 @@ fn main() -> anyhow::Result<()> { // out. if spec_tests > 0 { test_directory_module(out, "tests/spec_testsuite/proposals/memory64", strategy)?; - test_directory_module(out, "tests/spec_testsuite/proposals/function-references", strategy)?; + test_directory_module( + out, + "tests/spec_testsuite/proposals/function-references", + strategy, + )?; } else { println!( "cargo:warning=The spec testsuite is disabled. To enable, run `git submodule \ diff --git a/tests/misc_testsuite/function-references/table_fill.wast b/tests/misc_testsuite/function-references/table_fill.wast new file mode 100644 index 000000000000..ac20adf31a34 --- /dev/null +++ b/tests/misc_testsuite/function-references/table_fill.wast @@ -0,0 +1,163 @@ +(module + (table $t 10 externref) + + (func (export "fill") (param $i i32) (param $r externref) (param $n i32) + (table.fill $t (local.get $i) (local.get $r) (local.get $n)) + ) + + (func (export "get") (param $i i32) (result externref) + (table.get $t (local.get $i)) + ) +) + +(assert_return (invoke "get" (i32.const 1)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 2)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 3)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 4)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 5)) (ref.null extern)) + +(assert_return (invoke "fill" (i32.const 2) (ref.extern 1) (i32.const 3))) +(assert_return (invoke "get" (i32.const 1)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 2)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 3)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 5)) (ref.null extern)) + +(assert_return (invoke "fill" (i32.const 4) (ref.extern 2) (i32.const 2))) +(assert_return (invoke "get" (i32.const 3)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 2)) +(assert_return (invoke "get" (i32.const 5)) (ref.extern 2)) +(assert_return (invoke "get" (i32.const 6)) (ref.null extern)) + +(assert_return (invoke "fill" (i32.const 4) (ref.extern 3) (i32.const 0))) +(assert_return (invoke "get" (i32.const 3)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 2)) +(assert_return (invoke "get" (i32.const 5)) (ref.extern 2)) + +(assert_return (invoke "fill" (i32.const 8) (ref.extern 4) (i32.const 2))) +(assert_return (invoke "get" (i32.const 7)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 8)) (ref.extern 4)) +(assert_return (invoke "get" (i32.const 9)) (ref.extern 4)) + +(assert_return (invoke "fill" (i32.const 9) (ref.null extern) (i32.const 1))) +(assert_return (invoke "get" (i32.const 8)) (ref.extern 4)) +(assert_return (invoke "get" (i32.const 9)) (ref.null extern)) + +(assert_return (invoke "fill" (i32.const 10) (ref.extern 5) (i32.const 0))) +(assert_return (invoke "get" (i32.const 9)) (ref.null extern)) + +(assert_trap + (invoke "fill" (i32.const 8) (ref.extern 6) (i32.const 3)) + "out of bounds table access" +) +(assert_return (invoke "get" (i32.const 7)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 8)) (ref.extern 4)) +(assert_return (invoke "get" (i32.const 9)) (ref.null extern)) + +(assert_trap + (invoke "fill" (i32.const 11) (ref.null extern) (i32.const 0)) + "out of bounds table access" +) + +(assert_trap + (invoke "fill" (i32.const 11) (ref.null extern) (i32.const 10)) + "out of bounds table access" +) + + +;; Type errors + +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-value-length-empty-vs-i32-i32 + (table.fill $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-empty-vs-i32 + (table.fill $t (ref.null extern) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-value-empty-vs + (table.fill $t (i32.const 1) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-length-empty-vs-i32 + (table.fill $t (i32.const 1) (ref.null extern)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-index-f32-vs-i32 + (table.fill $t (f32.const 1) (ref.null extern) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 funcref) + (func $type-value-vs-funcref (param $r externref) + (table.fill $t (i32.const 1) (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $afunc (func)) + (table $t 0 (ref null $afunc)) + (func $type-funcref-vs-typed-func (param $r funcref) + (table.fill $t (i32.const 1) (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-length-f32-vs-i32 + (table.fill $t (i32.const 1) (ref.null extern) (f32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t1 1 externref) + (table $t2 1 funcref) + (func $type-value-externref-vs-funcref-multi (param $r externref) + (table.fill $t2 (i32.const 0) (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 1 externref) + (func $type-result-empty-vs-num (result i32) + (table.fill $t (i32.const 0) (ref.null extern) (i32.const 1)) + ) + ) + "type mismatch" +) diff --git a/tests/misc_testsuite/function-references/table_get.wast b/tests/misc_testsuite/function-references/table_get.wast new file mode 100644 index 000000000000..fa1dca988be0 --- /dev/null +++ b/tests/misc_testsuite/function-references/table_get.wast @@ -0,0 +1,98 @@ +(module + (type $res-i32 (func (result i32))) + (table $t2 2 externref) + (table $t3 3 funcref) + (table $t4 (ref null $res-i32) (elem (ref.func $returns-five))) + (elem (table $t3) (i32.const 1) func $returns-five) + (func $returns-five (result i32) (i32.const 5)) + + (func (export "init") (param $r externref) + (table.set $t2 (i32.const 1) (local.get $r)) + (table.set $t3 (i32.const 2) (table.get $t3 (i32.const 1))) + ) + + (func (export "get-externref") (param $i i32) (result externref) + (table.get $t2 (local.get $i)) + ) + (func $f3 (export "get-funcref") (param $i i32) (result funcref) + (table.get $t3 (local.get $i)) + ) + (func $f4 (export "get-typed-func") (param $i i32) (result (ref $res-i32)) + (ref.as_non_null (table.get $t4 (local.get $i))) + ) + + (func (export "is_null-funcref") (param $i i32) (result i32) + (ref.is_null (call $f3 (local.get $i))) + ) + (func (export "get-typed-and-call") (param $i i32) (result i32) (call_ref (call $f4 (local.get $i)))) +) + +(invoke "init" (ref.extern 1)) + +(assert_return (invoke "get-externref" (i32.const 0)) (ref.null extern)) +(assert_return (invoke "get-externref" (i32.const 1)) (ref.extern 1)) + +(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func)) +(assert_return (invoke "is_null-funcref" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "is_null-funcref" (i32.const 2)) (i32.const 0)) + +(assert_return (invoke "get-typed-and-call" (i32.const 0)) (i32.const 5)) + +(assert_trap (invoke "get-externref" (i32.const 2)) "out of bounds table access") +(assert_trap (invoke "get-funcref" (i32.const 3)) "out of bounds table access") +(assert_trap (invoke "get-typed-func" (i32.const 2)) "out of bounds table access") +(assert_trap (invoke "get-externref" (i32.const -1)) "out of bounds table access") +(assert_trap (invoke "get-funcref" (i32.const -1)) "out of bounds table access") +(assert_trap (invoke "get-typed-func" (i32.const -1)) "out of bounds table access") + + +;; Type errors + +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-empty-vs-i32 (result externref) + (table.get $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-f32-vs-i32 (result externref) + (table.get $t (f32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 10 externref) + (func $type-result-externref-vs-empty + (table.get $t (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-result-externref-vs-funcref (result funcref) + (table.get $t (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t1 1 funcref) + (table $t2 1 externref) + (func $type-result-externref-vs-funcref-multi (result funcref) + (table.get $t2 (i32.const 0)) + ) + ) + "type mismatch" +) diff --git a/tests/misc_testsuite/function-references/table_grow.wast b/tests/misc_testsuite/function-references/table_grow.wast new file mode 100644 index 000000000000..5dd9eefa1329 --- /dev/null +++ b/tests/misc_testsuite/function-references/table_grow.wast @@ -0,0 +1,196 @@ +(module + (table $t 0 externref) + + (func (export "get") (param $i i32) (result externref) (table.get $t (local.get $i))) + (func (export "set") (param $i i32) (param $r externref) (table.set $t (local.get $i) (local.get $r))) + + (func (export "grow") (param $sz i32) (param $init externref) (result i32) + (table.grow $t (local.get $init) (local.get $sz)) + ) + (func (export "size") (result i32) (table.size $t)) +) + +(assert_return (invoke "size") (i32.const 0)) +(assert_trap (invoke "set" (i32.const 0) (ref.extern 2)) "out of bounds table access") +(assert_trap (invoke "get" (i32.const 0)) "out of bounds table access") + +(assert_return (invoke "grow" (i32.const 1) (ref.null extern)) (i32.const 0)) +(assert_return (invoke "size") (i32.const 1)) +(assert_return (invoke "get" (i32.const 0)) (ref.null extern)) +(assert_return (invoke "set" (i32.const 0) (ref.extern 2))) +(assert_return (invoke "get" (i32.const 0)) (ref.extern 2)) +(assert_trap (invoke "set" (i32.const 1) (ref.extern 2)) "out of bounds table access") +(assert_trap (invoke "get" (i32.const 1)) "out of bounds table access") + +(assert_return (invoke "grow" (i32.const 4) (ref.extern 3)) (i32.const 1)) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "get" (i32.const 0)) (ref.extern 2)) +(assert_return (invoke "set" (i32.const 0) (ref.extern 2))) +(assert_return (invoke "get" (i32.const 0)) (ref.extern 2)) +(assert_return (invoke "get" (i32.const 1)) (ref.extern 3)) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 3)) +(assert_return (invoke "set" (i32.const 4) (ref.extern 4))) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 4)) +(assert_trap (invoke "set" (i32.const 5) (ref.extern 2)) "out of bounds table access") +(assert_trap (invoke "get" (i32.const 5)) "out of bounds table access") + + +;; Reject growing to size outside i32 value range +(module + (table $t 0x10 funcref) + (elem declare func $f) + (func $f (export "grow") (result i32) + (table.grow $t (ref.func $f) (i32.const 0xffff_fff0)) + ) +) + +(assert_return (invoke "grow") (i32.const -1)) + + +(module + (table $t 0 externref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null extern) (local.get 0)) + ) +) + +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 800)) (i32.const 3)) + +(module + (type $afunc (func)) + (table $t 0 (ref null $afunc)) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null $afunc) (local.get 0)) + ) +) + +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 800)) (i32.const 3)) + +(module + (table $t 0 10 externref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null extern) (local.get 0)) + ) +) + +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 2)) +(assert_return (invoke "grow" (i32.const 6)) (i32.const 4)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 10)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "grow" (i32.const 0x10000)) (i32.const -1)) + + +(module + (table $t 10 funcref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null func) (local.get 0)) + ) + (elem declare func 1) + (func (export "check-table-null") (param i32 i32) (result funcref) + (local funcref) + (local.set 2 (ref.func 1)) + (block + (loop + (local.set 2 (table.get $t (local.get 0))) + (br_if 1 (i32.eqz (ref.is_null (local.get 2)))) + (br_if 1 (i32.ge_u (local.get 0) (local.get 1))) + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + (br_if 0 (i32.le_u (local.get 0) (local.get 1))) + ) + ) + (local.get 2) + ) +) + +(assert_return (invoke "check-table-null" (i32.const 0) (i32.const 9)) (ref.null func)) +(assert_return (invoke "grow" (i32.const 10)) (i32.const 10)) +(assert_return (invoke "check-table-null" (i32.const 0) (i32.const 19)) (ref.null func)) + + +;; Type errors + +(assert_invalid + (module + (table $t 0 externref) + (func $type-init-size-empty-vs-i32-externref (result i32) + (table.grow $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-size-empty-vs-i32 (result i32) + (table.grow $t (ref.null extern)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-init-empty-vs-externref (result i32) + (table.grow $t (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-size-f32-vs-i32 (result i32) + (table.grow $t (ref.null extern) (f32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 funcref) + (func $type-init-externref-vs-funcref (param $r externref) (result i32) + (table.grow $t (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $afunc (func)) + (table $t 0 (ref null $afunc)) + (func $type-init-funcref-vs-typed-func (param $r funcref) (result i32) + (table.grow $t (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 1 externref) + (func $type-result-i32-vs-empty + (table.grow $t (ref.null extern) (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 1 externref) + (func $type-result-i32-vs-f32 (result f32) + (table.grow $t (ref.null extern) (i32.const 0)) + ) + ) + "type mismatch" +) diff --git a/tests/misc_testsuite/function-references/table_set.wast b/tests/misc_testsuite/function-references/table_set.wast new file mode 100644 index 000000000000..2c927127935e --- /dev/null +++ b/tests/misc_testsuite/function-references/table_set.wast @@ -0,0 +1,140 @@ +(module + (type $res-i32 (func (result i32))) + (table $t2 1 externref) + (table $t3 2 funcref) + (table $t4 1 (ref null $res-i32)) + (elem (table $t3) (i32.const 1) func $returns-five) + (func $returns-five (result i32) (i32.const 5)) + + (func (export "get-externref") (param $i i32) (result externref) + (table.get $t2 (local.get $i)) + ) + (func $f3 (export "get-funcref") (param $i i32) (result funcref) + (table.get $t3 (local.get $i)) + ) + (func $f4 (export "get-typed-func") (param $i i32) (result (ref null $res-i32)) + (table.get $t4 (local.get $i)) + ) + + (func (export "set-externref") (param $i i32) (param $r externref) + (table.set $t2 (local.get $i) (local.get $r)) + ) + (func (export "set-funcref") (param $i i32) (param $r funcref) + (table.set $t3 (local.get $i) (local.get $r)) + ) + (func (export "set-funcref-from") (param $i i32) (param $j i32) + (table.set $t3 (local.get $i) (table.get $t3 (local.get $j))) + ) + (func $f5 (export "set-typed-func") (param $i i32) (param $r (ref $res-i32)) + (table.set $t4 (local.get $i) (local.get $r)) + ) + + (func (export "is_null-funcref") (param $i i32) (result i32) + (ref.is_null (call $f3 (local.get $i))) + ) + (func (export "is_null-typed-func") (param $i i32) (result i32) + (ref.is_null (call $f4 (local.get $i))) + ) + (func (export "set-returns-five") (param $i i32) + (call $f5 (local.get $i) (ref.func $returns-five)) + ) + (func (export "get-typed-and-call") (param $i i32) (result i32) (call_ref (call $f4 (local.get $i)))) +) + +(assert_return (invoke "get-externref" (i32.const 0)) (ref.null extern)) +(assert_return (invoke "set-externref" (i32.const 0) (ref.extern 1))) +(assert_return (invoke "get-externref" (i32.const 0)) (ref.extern 1)) +(assert_return (invoke "set-externref" (i32.const 0) (ref.null extern))) +(assert_return (invoke "get-externref" (i32.const 0)) (ref.null extern)) + +(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func)) +(assert_return (invoke "set-funcref-from" (i32.const 0) (i32.const 1))) +(assert_return (invoke "is_null-funcref" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "set-funcref" (i32.const 0) (ref.null func))) +(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func)) + +(assert_return (invoke "is_null-typed-func" (i32.const 0)) (i32.const 1)) +(invoke "set-returns-five" (i32.const 0)) +(assert_return (invoke "get-typed-and-call" (i32.const 0)) (i32.const 5)) + +(assert_trap (invoke "set-externref" (i32.const 2) (ref.null extern)) "out of bounds table access") +(assert_trap (invoke "set-funcref" (i32.const 3) (ref.null func)) "out of bounds table access") +(assert_trap (invoke "set-returns-five" (i32.const 2)) "out of bounds table access") +(assert_trap (invoke "set-externref" (i32.const -1) (ref.null extern)) "out of bounds table access") +(assert_trap (invoke "set-funcref" (i32.const -1) (ref.null func)) "out of bounds table access") +(assert_trap (invoke "set-returns-five" (i32.const -1)) "out of bounds table access") + +(assert_trap (invoke "set-externref" (i32.const 2) (ref.extern 0)) "out of bounds table access") +(assert_trap (invoke "set-funcref-from" (i32.const 3) (i32.const 1)) "out of bounds table access") +(assert_trap (invoke "set-externref" (i32.const -1) (ref.extern 0)) "out of bounds table access") +(assert_trap (invoke "set-funcref-from" (i32.const -1) (i32.const 1)) "out of bounds table access") + + +;; Type errors + +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-value-empty-vs-i32-externref + (table.set $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-empty-vs-i32 + (table.set $t (ref.null extern)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-value-empty-vs-externref + (table.set $t (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-size-f32-vs-i32 + (table.set $t (f32.const 1) (ref.null extern)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 funcref) + (func $type-value-externref-vs-funcref (param $r externref) + (table.set $t (i32.const 1) (local.get $r)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t1 1 externref) + (table $t2 1 funcref) + (func $type-value-externref-vs-funcref-multi (param $r externref) + (table.set $t2 (i32.const 0) (local.get $r)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 10 externref) + (func $type-result-empty-vs-num (result i32) + (table.set $t (i32.const 0) (ref.null extern)) + ) + ) + "type mismatch" +) From 84823692fa018633539ba1d64d2fab036ae7a6ba Mon Sep 17 00:00:00 2001 From: cosine Date: Mon, 5 Dec 2022 19:12:38 -0500 Subject: [PATCH 24/81] Fix missing typed cases for table_grow, table_fill --- crates/cranelift/src/func_environ.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 9178cc951901..46aa4764176c 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -947,7 +947,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m ) -> WasmResult { let (func_idx, func_sig) = match self.module.table_plans[table_index].table.wasm_ty.heap_type { - WasmHeapType::Func => ( + WasmHeapType::Func | WasmHeapType::Index(_) => ( BuiltinFunctionIndex::table_grow_funcref(), self.builtin_function_signatures .table_grow_funcref(&mut pos.func), @@ -957,10 +957,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m self.builtin_function_signatures .table_grow_externref(&mut pos.func), ), - _ => return Err(WasmError::Unsupported( - "`table.grow` with a table element type that is not `funcref` or `externref`" - .into(), - )), + WasmHeapType::Bot => unreachable!("no bot"), }; let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); @@ -1281,7 +1278,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m ) -> WasmResult<()> { let (builtin_idx, builtin_sig) = match self.module.table_plans[table_index].table.wasm_ty.heap_type { - WasmHeapType::Func => ( + WasmHeapType::Func | WasmHeapType::Index(_) => ( BuiltinFunctionIndex::table_fill_funcref(), self.builtin_function_signatures .table_fill_funcref(&mut pos.func), @@ -1291,10 +1288,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m self.builtin_function_signatures .table_fill_externref(&mut pos.func), ), - _ => return Err(WasmError::Unsupported( - "`table.fill` with a table element type that is not `funcref` or `externref`" - .into(), - )), + WasmHeapType::Bot => unreachable!("no bot"), }; let (vmctx, builtin_addr) = From d30c76edbfbc3dabc269c5068464dd8e4a317d5a Mon Sep 17 00:00:00 2001 From: cosine Date: Mon, 5 Dec 2022 19:28:48 -0500 Subject: [PATCH 25/81] Document trap code; remove answered question --- cranelift/wasm/src/code_translator.rs | 2 -- crates/wasmtime/src/trap.rs | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index f1594c9e59a1..9018df0c89f8 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -2053,8 +2053,6 @@ pub fn translate_operator( canonicalise_then_brz(builder, is_null, br_destination, inputs); // In the null case, pop the ref state.pop1(); - // It seems that we're required to create an unconditional jump for - // the non-br case, based on the example of BrIf, but i'm not sure why let next_block = builder.create_block(); canonicalise_then_jump(builder, next_block, &[]); builder.seal_block(next_block); // The only predecessor is the current block. diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index f3b0531dee45..cd492de50380 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -88,7 +88,9 @@ pub enum TrapCode { /// Execution has potentially run too long and may be interrupted. Interrupt, - /// Okay why is this defined three times i am losing my mind + /// Used for ref.as_non_null; a reference which was asserted by the + /// program to be non-null was null. Not used for call_ref, which uses + /// IndirectCallToNull. NullReference, /// When the `component-model` feature is enabled this trap represents a From 20907e8ba018f67bdd002abd2fbbbc4e03a258c3 Mon Sep 17 00:00:00 2001 From: cosine Date: Mon, 5 Dec 2022 19:29:32 -0500 Subject: [PATCH 26/81] Mark wasm-tools to wasmtime reftype infallible --- cranelift/wasm/src/code_translator.rs | 3 +- cranelift/wasm/src/func_translator.rs | 3 +- cranelift/wasm/src/sections_translator.rs | 12 +++---- cranelift/wasm/src/translation_utils.rs | 29 ++-------------- crates/environ/src/module_environ.rs | 4 +-- crates/types/src/lib.rs | 40 ++++++++++------------- 6 files changed, 30 insertions(+), 61 deletions(-) diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index 9018df0c89f8..f2454eef3b32 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -79,7 +79,6 @@ use crate::translation_utils::{ }; use crate::wasm_unsupported; use crate::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TypeIndex, WasmResult}; -use core::convert::TryInto; use core::{i32, u32}; use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; use cranelift_codegen::ir::immediates::Offset32; @@ -1037,7 +1036,7 @@ pub fn translate_operator( translate_fcmp(FloatCC::LessThanOrEqual, builder, state) } Operator::RefNull { ty } => { - state.push1(environ.translate_ref_null(builder.cursor(), (*ty).try_into()?)?) + state.push1(environ.translate_ref_null(builder.cursor(), (*ty).into())?) } Operator::RefIsNull => { let value = state.pop1(); diff --git a/cranelift/wasm/src/func_translator.rs b/cranelift/wasm/src/func_translator.rs index da1c9589c651..6950f67369d7 100644 --- a/cranelift/wasm/src/func_translator.rs +++ b/cranelift/wasm/src/func_translator.rs @@ -9,7 +9,6 @@ use crate::environ::FuncEnvironment; use crate::state::FuncTranslationState; use crate::translation_utils::get_vmctx_value_label; use crate::WasmResult; -use core::convert::TryInto; use cranelift_codegen::entity::EntityRef; use cranelift_codegen::ir::{self, Block, InstBuilder, ValueLabel}; use cranelift_codegen::timing; @@ -202,7 +201,7 @@ fn declare_locals( let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into()); builder.ins().vconst(ir::types::I8X16, constant_handle) } - Ref(rt) => environ.translate_ref_null(builder.cursor(), rt.heap_type.try_into()?)?, + Ref(rt) => environ.translate_ref_null(builder.cursor(), rt.heap_type.into())?, Bot => panic!("ValType::Bot won't ever actually exist"), }; diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs index b8641df62124..fe56317e4971 100644 --- a/cranelift/wasm/src/sections_translator.rs +++ b/cranelift/wasm/src/sections_translator.rs @@ -45,12 +45,12 @@ fn tag(e: TagType) -> Tag { } } -fn table(ty: TableType) -> WasmResult
{ - Ok(Table { - wasm_ty: ty.element_type.try_into()?, +fn table(ty: TableType) -> Table { + Table { + wasm_ty: ty.element_type.into(), minimum: ty.initial, maximum: ty.maximum, - }) + } } fn global(ty: GlobalType, initializer: GlobalInit) -> WasmResult { @@ -112,7 +112,7 @@ pub fn parse_import_section<'data>( environ.declare_global_import(ty, import.module, import.name)?; } TypeRef::Table(ty) => { - let ty = table(ty)?; + let ty = table(ty); environ.declare_table_import(ty, import.module, import.name)?; } } @@ -151,7 +151,7 @@ pub fn parse_table_section( environ.reserve_tables(tables.get_count())?; for entry in tables { - let ty = table(entry?)?; + let ty = table(entry?); environ.declare_table(ty)?; } diff --git a/cranelift/wasm/src/translation_utils.rs b/cranelift/wasm/src/translation_utils.rs index d618dfe90834..120b3b7dd7a1 100644 --- a/cranelift/wasm/src/translation_utils.rs +++ b/cranelift/wasm/src/translation_utils.rs @@ -1,7 +1,6 @@ //! Helper functions and structures for the translation. use crate::environ::TargetEnvironment; use crate::WasmResult; -use core::convert::TryInto; use core::u32; use cranelift_codegen::ir; use cranelift_frontend::FunctionBuilder; @@ -30,31 +29,7 @@ pub fn type_to_type( wasmparser::ValType::F32 => Ok(ir::types::F32), wasmparser::ValType::F64 => Ok(ir::types::F64), wasmparser::ValType::V128 => Ok(ir::types::I8X16), - wasmparser::ValType::Ref(rt) => Ok(environ.reference_type(rt.heap_type.try_into()?)), - wasmparser::ValType::Bot => todo!("ValType::Bot will not exist in final wasm-tools"), - } -} - -/// Helper function translating wasmparser possible table types to Cranelift types when possible, -/// or None for Func tables. -pub fn tabletype_to_type( - ty: wasmparser::ValType, - environ: &PE, -) -> WasmResult> { - match ty { - wasmparser::ValType::I32 => Ok(Some(ir::types::I32)), - wasmparser::ValType::I64 => Ok(Some(ir::types::I64)), - wasmparser::ValType::F32 => Ok(Some(ir::types::F32)), - wasmparser::ValType::F64 => Ok(Some(ir::types::F64)), - wasmparser::ValType::V128 => Ok(Some(ir::types::I8X16)), - wasmparser::ValType::Ref(rt) => { - match rt.heap_type { - wasmparser::HeapType::Extern => { - Ok(Some(environ.reference_type(rt.heap_type.try_into()?))) - } - _ => Ok(None), // TODO(dhil) fixme: verify this is indeed the right thing to do. - } - } + wasmparser::ValType::Ref(rt) => Ok(environ.reference_type(rt.heap_type.into())), wasmparser::ValType::Bot => todo!("ValType::Bot will not exist in final wasm-tools"), } } @@ -124,7 +99,7 @@ pub fn block_with_params( builder.append_block_param(block, ir::types::F64); } wasmparser::ValType::Ref(rt) => { - builder.append_block_param(block, environ.reference_type(rt.heap_type.try_into()?)); + builder.append_block_param(block, environ.reference_type(rt.heap_type.into())); } wasmparser::ValType::V128 => { builder.append_block_param(block, ir::types::I8X16); diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index 9b5186648e61..95f820a23e41 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -249,7 +249,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { } TypeRef::Table(ty) => { self.result.module.num_imported_tables += 1; - EntityType::Table(ty.try_into()?) + EntityType::Table(ty.into()) } // doesn't get past validation @@ -279,7 +279,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { self.result.module.table_plans.reserve_exact(cnt); for entry in tables { - let table = entry?.try_into()?; + let table = entry?.into(); let plan = TablePlan::for_table(table, &self.tunables); self.result.module.table_plans.push(plan); } diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index d18f8d169f11..5b48bf095679 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -41,7 +41,7 @@ impl TryFrom for WasmType { F32 => Ok(WasmType::F32), F64 => Ok(WasmType::F64), V128 => Ok(WasmType::V128), - Ref(rt) => Ok(WasmType::Ref(WasmRefType::try_from(rt)?)), + Ref(rt) => Ok(WasmType::Ref(WasmRefType::from(rt))), Bot => Ok(WasmType::Bot), } } @@ -92,18 +92,17 @@ pub const WASM_FUNC_REF: WasmRefType = WasmRefType { heap_type: WasmHeapType::Func, }; -impl TryFrom for WasmRefType { - type Error = WasmError; - fn try_from( +impl From for WasmRefType { + fn from( wasmparser::RefType { nullable, heap_type, }: wasmparser::RefType, - ) -> Result { - Ok(WasmRefType { + ) -> Self { + WasmRefType { nullable, - heap_type: WasmHeapType::try_from(heap_type)?, - }) + heap_type: WasmHeapType::from(heap_type), + } } } @@ -149,15 +148,14 @@ pub enum WasmHeapType { Index(u32), } -impl TryFrom for WasmHeapType { - type Error = WasmError; - fn try_from(ht: wasmparser::HeapType) -> Result { +impl From for WasmHeapType { + fn from(ht: wasmparser::HeapType) -> Self { use wasmparser::HeapType::*; match ht { - Bot => Ok(WasmHeapType::Bot), - Func => Ok(WasmHeapType::Func), - Extern => Ok(WasmHeapType::Extern), - Index(i) => Ok(WasmHeapType::Index(i)), + Bot => WasmHeapType::Bot, + Func => WasmHeapType::Func, + Extern => WasmHeapType::Extern, + Index(i) => WasmHeapType::Index(i), } } } @@ -449,15 +447,13 @@ pub struct Table { pub maximum: Option, } -impl TryFrom for Table { - type Error = WasmError; - - fn try_from(ty: wasmparser::TableType) -> WasmResult
{ - Ok(Table { - wasm_ty: ty.element_type.try_into()?, +impl From for Table { + fn from(ty: wasmparser::TableType) -> Table { + Table { + wasm_ty: ty.element_type.into(), minimum: ty.initial, maximum: ty.maximum, - }) + } } } From 9d0482f65935e1ef4703a1342a355013c5b80782 Mon Sep 17 00:00:00 2001 From: cosine Date: Tue, 6 Dec 2022 11:14:59 -0500 Subject: [PATCH 27/81] Fix reversed conditional --- crates/environ/src/module.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 7c21e46953c8..a53c352bcefb 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -428,7 +428,12 @@ impl ModuleTranslation<'_> { // If this is not a funcref table, then we can't support a // pre-computed table of function indices. - if self.module.table_plans[segment.table_index].table.wasm_ty.heap_type == WasmHeapType::Func { + if self.module.table_plans[segment.table_index] + .table + .wasm_ty + .heap_type + != WasmHeapType::Func + { leftovers.push(segment.clone()); continue; } From 71a7e96ed50610cfa591c6f94f01e26c5a24fb32 Mon Sep 17 00:00:00 2001 From: cosine Date: Tue, 6 Dec 2022 11:20:43 -0500 Subject: [PATCH 28/81] Scope externref/funcref shorthands within WasmRefType --- crates/cranelift/src/func_environ.rs | 5 ++--- crates/runtime/src/instance.rs | 6 +++--- crates/types/src/lib.rs | 23 ++++++++++++----------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 46aa4764176c..33c88b598d4d 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -11,7 +11,6 @@ use cranelift_frontend::Variable; use cranelift_wasm::{ self, FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, MemoryIndex, TableIndex, TargetEnvironment, TypeIndex, WasmError, WasmHeapType, WasmRefType, WasmResult, WasmType, - WASM_EXTERN_REF, }; use std::convert::TryFrom; use std::mem; @@ -1359,7 +1358,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m ) -> WasmResult { debug_assert_eq!( self.module.globals[index].wasm_ty, - WasmType::Ref(WASM_EXTERN_REF), + WasmType::Ref(WasmRefType::EXTERNREF), "We only use GlobalVariable::Custom for externref" ); @@ -1387,7 +1386,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m ) -> WasmResult<()> { debug_assert_eq!( self.module.globals[index].wasm_ty, - WasmType::Ref(WASM_EXTERN_REF), + WasmType::Ref(WasmRefType::EXTERNREF), "We only use GlobalVariable::Custom for externref" ); diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 80c74750edf2..c64884c6201e 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -30,7 +30,7 @@ use wasmtime_environ::{ packed_option::ReservedValue, DataIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex, GlobalInit, HostPtr, MemoryIndex, Module, PrimaryMap, SignatureIndex, TableIndex, - TableInitialization, TrapCode, VMOffsets, WasmRefType, WasmType, WASM_EXTERN_REF, + TableInitialization, TrapCode, VMOffsets, WasmRefType, WasmType, }; mod allocator; @@ -994,7 +994,7 @@ impl Instance { // count as values move between globals, everything else is just // copy-able bits. match global.wasm_ty { - WasmType::Ref(WASM_EXTERN_REF) => { + WasmType::Ref(WasmRefType::EXTERNREF) => { *(*to).as_externref_mut() = from.as_externref().clone() } _ => ptr::copy_nonoverlapping(from, to, 1), @@ -1025,7 +1025,7 @@ impl Drop for Instance { }; match global.wasm_ty { // For now only externref globals need to get destroyed - WasmType::Ref(WASM_EXTERN_REF) => {} + WasmType::Ref(WasmRefType::EXTERNREF) => {} _ => continue, } unsafe { diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 5b48bf095679..d226ec13976f 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -82,15 +82,16 @@ pub struct WasmRefType { pub heap_type: WasmHeapType, } -pub const WASM_EXTERN_REF: WasmRefType = WasmRefType { - nullable: true, - heap_type: WasmHeapType::Extern, -}; - -pub const WASM_FUNC_REF: WasmRefType = WasmRefType { - nullable: true, - heap_type: WasmHeapType::Func, -}; +impl WasmRefType { + pub const EXTERNREF: WasmRefType = WasmRefType { + nullable: true, + heap_type: WasmHeapType::Extern, + }; + pub const FUNCREF: WasmRefType = WasmRefType { + nullable: true, + heap_type: WasmHeapType::Func, + }; +} impl From for WasmRefType { fn from( @@ -123,8 +124,8 @@ impl From for wasmparser::RefType { impl fmt::Display for WasmRefType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &WASM_EXTERN_REF => write!(f, "externref"), - &WASM_FUNC_REF => write!(f, "funcref"), + &Self::EXTERNREF => write!(f, "externref"), + &Self::FUNCREF => write!(f, "funcref"), WasmRefType { heap_type, nullable, From 6765ecbd30fa7e74320bbb536c8f099ba24eba1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Hillerstr=C3=B6m?= Date: Thu, 16 Feb 2023 18:25:17 +0100 Subject: [PATCH 29/81] Merge with upstream --- .gitattributes | 4 - .github/ISSUE_TEMPLATE/fuzzbug.md | 44 + .../binary-compatible-builds/action.yml | 2 +- .github/actions/github-release/action.yml | 2 +- .github/actions/github-release/main.js | 42 +- .../actions/github-release/package-lock.json | 571 +++ .github/actions/github-release/package.json | 4 +- .github/actions/install-rust/action.yml | 80 +- .github/actions/install-rust/main.js | 36 - .github/labeler.yml | 4 + .github/subscribe-to-label.json | 3 +- .github/workflows/build.yml | 105 + .github/workflows/cargo-audit.yml | 2 +- .github/workflows/main.yml | 175 +- .github/workflows/performance.yml | 131 + .github/workflows/publish-to-cratesio.yml | 2 +- .github/workflows/push-tag.yml | 12 +- .github/workflows/release-process.yml | 6 +- Cargo.lock | 1503 ++++---- Cargo.toml | 170 +- README.md | 53 +- RELEASES.md | 608 +++- benches/call.rs | 6 +- benches/instantiation.rs | 16 +- benches/thread_eager_init.rs | 15 +- benches/trap.rs | 76 +- benches/wasi.rs | 86 + benches/wasi/.gitignore | 1 + benches/wasi/get-current-time.wat | 22 + benches/wasi/open-file.wat | 53 + benches/wasi/read-arguments.wat | 42 + benches/wasi/read-dir.wat | 41 + benches/wasi/read-environment.wat | 45 + benches/wasi/read-file.wat | 78 + build.rs | 39 +- ci/build-src-tarball.sh | 20 + ci/docker/riscv64gc-linux/Dockerfile | 7 + ci/run-tests.sh | 1 + cranelift/Cargo.toml | 52 +- cranelift/bforest/Cargo.toml | 6 +- cranelift/codegen/Cargo.toml | 64 +- cranelift/codegen/build.rs | 139 +- cranelift/codegen/meta/Cargo.toml | 6 +- cranelift/codegen/meta/src/cdsl/formats.rs | 14 +- .../codegen/meta/src/cdsl/instructions.rs | 65 +- cranelift/codegen/meta/src/cdsl/mod.rs | 9 - cranelift/codegen/meta/src/cdsl/operands.rs | 15 +- cranelift/codegen/meta/src/cdsl/settings.rs | 13 +- cranelift/codegen/meta/src/cdsl/types.rs | 138 +- cranelift/codegen/meta/src/cdsl/typevar.rs | 206 +- cranelift/codegen/meta/src/gen_inst.rs | 600 +++- cranelift/codegen/meta/src/gen_settings.rs | 32 +- cranelift/codegen/meta/src/gen_types.rs | 5 - cranelift/codegen/meta/src/isa/arm64.rs | 11 +- cranelift/codegen/meta/src/isa/mod.rs | 7 +- cranelift/codegen/meta/src/isa/riscv64.rs | 28 + cranelift/codegen/meta/src/isa/x86.rs | 267 +- cranelift/codegen/meta/src/lib.rs | 3 +- cranelift/codegen/meta/src/shared/entities.rs | 38 +- cranelift/codegen/meta/src/shared/formats.rs | 72 +- .../codegen/meta/src/shared/immediates.rs | 17 +- .../codegen/meta/src/shared/instructions.rs | 1034 ++---- cranelift/codegen/meta/src/shared/settings.rs | 58 +- cranelift/codegen/meta/src/shared/types.rs | 99 - cranelift/codegen/shared/Cargo.toml | 4 +- cranelift/codegen/src/alias_analysis.rs | 258 +- cranelift/codegen/src/binemit/mod.rs | 38 +- cranelift/codegen/src/bitset.rs | 2 +- cranelift/codegen/src/cfg_printer.rs | 2 +- cranelift/codegen/src/context.rs | 202 +- cranelift/codegen/src/ctxhash.rs | 168 + cranelift/codegen/src/cursor.rs | 11 +- cranelift/codegen/src/data_value.rs | 106 +- cranelift/codegen/src/dce.rs | 7 +- cranelift/codegen/src/dominator_tree.rs | 49 +- cranelift/codegen/src/egraph.rs | 612 ++++ cranelift/codegen/src/egraph/cost.rs | 97 + cranelift/codegen/src/egraph/domtree.rs | 69 + cranelift/codegen/src/egraph/elaborate.rs | 679 ++++ cranelift/codegen/src/flowgraph.rs | 91 +- cranelift/codegen/src/incremental_cache.rs | 254 ++ cranelift/codegen/src/inst_predicates.rs | 142 +- cranelift/codegen/src/ir/builder.rs | 20 +- cranelift/codegen/src/ir/condcodes.rs | 94 +- cranelift/codegen/src/ir/constant.rs | 31 +- cranelift/codegen/src/ir/dfg.rs | 595 +++- cranelift/codegen/src/ir/dynamic_type.rs | 19 +- cranelift/codegen/src/ir/entities.rs | 52 +- cranelift/codegen/src/ir/extfunc.rs | 106 +- cranelift/codegen/src/ir/extname.rs | 290 +- cranelift/codegen/src/ir/function.rs | 345 +- cranelift/codegen/src/ir/globalvalue.rs | 6 +- cranelift/codegen/src/ir/heap.rs | 67 - cranelift/codegen/src/ir/immediates.rs | 78 +- cranelift/codegen/src/ir/instructions.rs | 360 +- cranelift/codegen/src/ir/jumptable.rs | 103 +- cranelift/codegen/src/ir/known_symbol.rs | 47 + cranelift/codegen/src/ir/layout.rs | 40 +- cranelift/codegen/src/ir/libcall.rs | 88 +- cranelift/codegen/src/ir/mod.rs | 27 +- cranelift/codegen/src/ir/progpoint.rs | 2 + cranelift/codegen/src/ir/sourceloc.rs | 53 +- cranelift/codegen/src/ir/stackslot.rs | 6 +- cranelift/codegen/src/ir/table.rs | 2 +- cranelift/codegen/src/ir/trapcode.rs | 41 +- cranelift/codegen/src/ir/types.rs | 209 +- cranelift/codegen/src/isa/aarch64/abi.rs | 274 +- cranelift/codegen/src/isa/aarch64/inst.isle | 1682 +++++++-- .../codegen/src/isa/aarch64/inst/args.rs | 378 +- .../codegen/src/isa/aarch64/inst/emit.rs | 816 +++-- .../src/isa/aarch64/inst/emit_tests.rs | 1158 ++++-- .../codegen/src/isa/aarch64/inst/imms.rs | 19 +- cranelift/codegen/src/isa/aarch64/inst/mod.rs | 945 +++-- .../codegen/src/isa/aarch64/inst/regs.rs | 6 + .../src/isa/aarch64/inst/unwind/systemv.rs | 19 +- cranelift/codegen/src/isa/aarch64/lower.isle | 1353 +++++-- cranelift/codegen/src/isa/aarch64/lower.rs | 1112 +----- .../codegen/src/isa/aarch64/lower/isle.rs | 453 ++- .../isa/aarch64/lower/isle/generated_code.rs | 4 +- .../src/isa/aarch64/lower_dynamic_neon.isle | 50 +- .../codegen/src/isa/aarch64/lower_inst.rs | 1925 ---------- cranelift/codegen/src/isa/aarch64/mod.rs | 175 +- cranelift/codegen/src/isa/call_conv.rs | 12 + cranelift/codegen/src/isa/mod.rs | 79 +- cranelift/codegen/src/isa/riscv64/abi.rs | 722 ++++ cranelift/codegen/src/isa/riscv64/inst.isle | 2297 ++++++++++++ .../codegen/src/isa/riscv64/inst/args.rs | 1969 +++++++++++ .../codegen/src/isa/riscv64/inst/emit.rs | 2842 +++++++++++++++ .../src/isa/riscv64/inst/emit_tests.rs | 2275 ++++++++++++ .../codegen/src/isa/riscv64/inst/imms.rs | 218 ++ cranelift/codegen/src/isa/riscv64/inst/mod.rs | 1726 +++++++++ .../codegen/src/isa/riscv64/inst/regs.rs | 220 ++ .../codegen/src/isa/riscv64/inst/unwind.rs | 2 + .../src/isa/riscv64/inst/unwind/systemv.rs | 172 + cranelift/codegen/src/isa/riscv64/lower.isle | 886 +++++ cranelift/codegen/src/isa/riscv64/lower.rs | 33 + .../codegen/src/isa/riscv64/lower/isle.rs | 465 +++ .../isa/riscv64/lower/isle/generated_code.rs | 9 + cranelift/codegen/src/isa/riscv64/mod.rs | 270 ++ cranelift/codegen/src/isa/riscv64/settings.rs | 8 + cranelift/codegen/src/isa/s390x/abi.rs | 105 +- cranelift/codegen/src/isa/s390x/inst.isle | 1479 ++++---- cranelift/codegen/src/isa/s390x/inst/args.rs | 4 +- cranelift/codegen/src/isa/s390x/inst/emit.rs | 538 ++- .../codegen/src/isa/s390x/inst/emit_tests.rs | 769 +++- cranelift/codegen/src/isa/s390x/inst/mod.rs | 1049 ++++-- cranelift/codegen/src/isa/s390x/inst/regs.rs | 75 + .../src/isa/s390x/inst/unwind/systemv.rs | 19 +- cranelift/codegen/src/isa/s390x/lower.isle | 1753 +++++----- cranelift/codegen/src/isa/s390x/lower.rs | 304 +- cranelift/codegen/src/isa/s390x/lower/isle.rs | 418 ++- .../isa/s390x/lower/isle/generated_code.rs | 2 +- cranelift/codegen/src/isa/s390x/mod.rs | 95 +- cranelift/codegen/src/isa/x64/abi.rs | 186 +- cranelift/codegen/src/isa/x64/encoding/rex.rs | 32 +- cranelift/codegen/src/isa/x64/inst.isle | 1190 +++++-- cranelift/codegen/src/isa/x64/inst/args.rs | 181 +- cranelift/codegen/src/isa/x64/inst/emit.rs | 514 ++- .../codegen/src/isa/x64/inst/emit_tests.rs | 648 +++- cranelift/codegen/src/isa/x64/inst/mod.rs | 846 ++--- .../src/isa/x64/inst/unwind/systemv.rs | 19 +- cranelift/codegen/src/isa/x64/lower.isle | 1800 +++++++--- cranelift/codegen/src/isa/x64/lower.rs | 2328 +------------ cranelift/codegen/src/isa/x64/lower/isle.rs | 702 ++-- .../src/isa/x64/lower/isle/generated_code.rs | 7 +- cranelift/codegen/src/isa/x64/mod.rs | 221 +- cranelift/codegen/src/isle_prelude.rs | 734 ++++ .../codegen/src/legalizer/globalvalue.rs | 6 + cranelift/codegen/src/legalizer/heap.rs | 259 -- cranelift/codegen/src/legalizer/mod.rs | 313 +- cranelift/codegen/src/legalizer/table.rs | 12 +- cranelift/codegen/src/lib.rs | 13 +- cranelift/codegen/src/licm.rs | 10 +- cranelift/codegen/src/loop_analysis.rs | 118 +- cranelift/codegen/src/machinst/abi.rs | 2451 ++++++++++++- cranelift/codegen/src/machinst/abi_impl.rs | 1908 ---------- cranelift/codegen/src/machinst/blockorder.rs | 100 +- cranelift/codegen/src/machinst/buffer.rs | 151 +- cranelift/codegen/src/machinst/compile.rs | 27 +- cranelift/codegen/src/machinst/helpers.rs | 12 +- cranelift/codegen/src/machinst/inst_common.rs | 20 - cranelift/codegen/src/machinst/isle.rs | 871 ++--- cranelift/codegen/src/machinst/lower.rs | 811 ++--- cranelift/codegen/src/machinst/mod.rs | 200 +- cranelift/codegen/src/machinst/reg.rs | 127 +- cranelift/codegen/src/machinst/vcode.rs | 266 +- cranelift/codegen/src/nan_canonicalization.rs | 18 +- cranelift/codegen/src/opts.rs | 131 + cranelift/codegen/src/opts/algebraic.isle | 475 +++ cranelift/codegen/src/opts/cprop.isle | 173 + cranelift/codegen/src/opts/generated_code.rs | 11 + cranelift/codegen/src/prelude.isle | 844 +---- cranelift/codegen/src/prelude_lower.isle | 724 ++++ cranelift/codegen/src/prelude_opt.isle | 34 + cranelift/codegen/src/remove_constant_phis.rs | 229 +- cranelift/codegen/src/scoped_hash_map.rs | 190 +- cranelift/codegen/src/settings.rs | 22 +- cranelift/codegen/src/simple_gvn.rs | 15 +- cranelift/codegen/src/simple_preopt.rs | 344 +- cranelift/codegen/src/souper_harvest.rs | 54 +- cranelift/codegen/src/timing.rs | 3 +- cranelift/codegen/src/unionfind.rs | 74 + cranelift/codegen/src/unreachable_code.rs | 18 +- cranelift/codegen/src/verifier/flags.rs | 161 - cranelift/codegen/src/verifier/mod.rs | 462 +-- cranelift/codegen/src/write.rs | 150 +- cranelift/docs/heap.dot | 8 - cranelift/docs/heap.svg | 26 - cranelift/docs/index.md | 2 +- cranelift/docs/ir.md | 222 +- cranelift/docs/isle-integration.md | 15 +- cranelift/docs/testing.md | 171 +- cranelift/entity/Cargo.toml | 4 +- cranelift/entity/src/list.rs | 40 +- cranelift/entity/src/map.rs | 2 +- cranelift/entity/src/set.rs | 16 +- cranelift/filetests/Cargo.toml | 36 +- cranelift/filetests/README.md | 36 + .../filetests/filetests/alias/extends.clif | 5 +- .../filetests/filetests/alias/fence.clif | 7 +- .../filetests/alias/multiple-blocks.clif | 7 +- .../filetests/alias/partial-redundancy.clif | 10 +- .../filetests/alias/simple-alias.clif | 16 +- cranelift/filetests/filetests/cfg/loop.clif | 14 +- .../filetests/filetests/cfg/traps_early.clif | 6 +- .../filetests/filetests/cfg/unused_node.clif | 18 +- cranelift/filetests/filetests/dce/basic.clif | 6 +- .../filetests/filetests/domtree/basic.clif | 11 +- .../filetests/filetests/domtree/loops.clif | 59 +- .../filetests/filetests/domtree/loops2.clif | 53 +- .../filetests/domtree/tall-tree.clif | 33 +- .../filetests/domtree/wide-tree.clif | 43 +- .../filetests/filetests/egraph/algebraic.clif | 359 ++ .../filetests/egraph/alias_analysis.clif | 22 + .../filetests/filetests/egraph/basic-gvn.clif | 28 + .../filetests/filetests/egraph/cprop.clif | 233 ++ .../filetests/filetests/egraph/i128-opts.clif | 13 + .../filetests/filetests/egraph/isplit.clif | 29 + .../filetests/egraph/issue-5405.clif | 16 + .../filetests/egraph/issue-5417.clif | 15 + .../filetests/egraph/issue-5437.clif | 39 + .../filetests/egraph/issue-5716.clif | 40 + .../filetests/filetests/egraph/licm.clif | 38 + .../filetests/filetests/egraph/misc.clif | 21 + .../filetests/filetests/egraph/mul-pow-2.clif | 34 + .../filetests/egraph/multivalue.clif | 37 + .../filetests/egraph/not_a_load.clif | 40 + .../filetests/filetests/egraph/remat.clif | 33 + .../filetests/filetests/egraph/select.clif | 155 + .../filetests/filetests/egraph/vselect.clif | 154 + .../filetests/isa/aarch64/amodes.clif | 314 +- .../filetests/isa/aarch64/arithmetic.clif | 430 ++- .../filetests/isa/aarch64/atomic-cas.clif | 55 + .../filetests/isa/aarch64/atomic-rmw-lse.clif | 420 ++- .../filetests/isa/aarch64/atomic-rmw.clif | 1064 +++++- .../filetests/isa/aarch64/atomic_load.clif | 54 + .../filetests/isa/aarch64/atomic_store.clif | 54 + .../filetests/isa/aarch64/basic1.clif | 6 + .../filetests/isa/aarch64/bitcast.clif | 67 + .../filetests/isa/aarch64/bitops.clif | 880 ++++- .../isa/aarch64/bitopts-optimized.clif | 56 + .../filetests/isa/aarch64/bmask.clif | 494 +++ .../filetests/isa/aarch64/bswap.clif | 52 + .../filetests/filetests/isa/aarch64/bti.clif | 186 + .../filetests/isa/aarch64/call-indirect.clif | 10 + .../filetests/isa/aarch64/call-pauth.clif | 27 +- .../filetests/filetests/isa/aarch64/call.clif | 718 +++- .../filetests/isa/aarch64/compare_zero.clif | 425 ++- .../filetests/isa/aarch64/condbr.clif | 567 ++- .../filetests/isa/aarch64/condops.clif | 1239 ++++++- .../filetests/isa/aarch64/constants.clif | 201 +- .../isa/aarch64/dynamic-simd-narrow.clif | 217 +- .../isa/aarch64/dynamic-simd-neon.clif | 175 +- .../isa/aarch64/dynamic-simd-widen.clif | 67 +- .../filetests/isa/aarch64/dynamic-slot.clif | 93 +- .../filetests/isa/aarch64/extend-op.clif | 215 +- .../filetests/isa/aarch64/fcvt-small.clif | 164 +- .../filetests/filetests/isa/aarch64/fcvt.clif | 895 +++++ .../filetests/isa/aarch64/floating-point.clif | 907 +++-- .../filetests/isa/aarch64/fp_sp_pc-pauth.clif | 86 + .../filetests/isa/aarch64/fp_sp_pc.clif | 32 +- .../filetests/isa/aarch64/heap_addr.clif | 53 - .../filetests/filetests/isa/aarch64/iabs.clif | 85 +- .../filetests/isa/aarch64/icmp-const.clif | 175 + .../isa/aarch64/iconst-icmp-small.clif | 24 +- .../isa/aarch64/inline-probestack.clif | 124 + .../filetests/isa/aarch64/jumptable.clif | 49 +- .../filetests/filetests/isa/aarch64/leaf.clif | 5 + .../leaf_with_preserve_frame_pointers.clif | 9 + .../filetests/isa/aarch64/multivalue-ret.clif | 7 + .../isa/aarch64/narrow-arithmetic.clif | 30 + .../filetests/isa/aarch64/pinned-reg.clif | 12 +- .../filetests/isa/aarch64/prologue.clif | 231 +- .../filetests/isa/aarch64/reduce.clif | 20 + .../filetests/isa/aarch64/reftypes.clif | 95 +- .../filetests/isa/aarch64/select.clif | 43 + .../filetests/isa/aarch64/shift-op.clif | 12 + .../filetests/isa/aarch64/shift-rotate.clif | 466 ++- .../isa/aarch64/simd-arithmetic.clif | 130 + .../isa/aarch64/simd-bitwise-compile.clif | 358 ++ .../isa/aarch64/simd-comparison-legalize.clif | 70 + .../filetests/isa/aarch64/simd-extmul.clif | 72 + .../isa/aarch64/simd-lane-access-compile.clif | 215 ++ .../isa/aarch64/simd-logical-compile.clif | 64 + .../filetests/isa/aarch64/simd-min-max.clif | 168 +- .../filetests/isa/aarch64/simd-narrow.clif | 195 +- .../isa/aarch64/simd-pairwise-add.clif | 140 +- .../filetests/isa/aarch64/simd-valltrue.clif | 134 +- .../filetests/filetests/isa/aarch64/simd.clif | 107 +- .../filetests/isa/aarch64/simd_load_zero.clif | 43 +- .../filetests/isa/aarch64/stack-limit.clif | 169 +- .../filetests/isa/aarch64/stack.clif | 659 +++- .../isa/aarch64/symbol-value-pic.clif | 24 + .../filetests/isa/aarch64/symbol-value.clif | 11 +- .../filetests/isa/aarch64/tls-elf-gd.clif | 36 +- .../filetests/isa/aarch64/traps.clif | 36 +- .../isa/aarch64/uadd_overflow_trap.clif | 129 + .../isa/aarch64/uextend-sextend.clif | 72 + .../filetests/isa/aarch64/vhigh_bits.clif | 157 + ...0_guard_no_spectre_i32_access_0_offset.wat | 72 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 76 + ...o_spectre_i32_access_0xffff0000_offset.wat | 80 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 70 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 76 + ...no_spectre_i8_access_0xffff0000_offset.wat | 80 + ..._guard_yes_spectre_i32_access_0_offset.wat | 72 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 76 + ...s_spectre_i32_access_0xffff0000_offset.wat | 80 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 70 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 76 + ...es_spectre_i8_access_0xffff0000_offset.wat | 80 + ...f_guard_no_spectre_i32_access_0_offset.wat | 72 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 76 + ...o_spectre_i32_access_0xffff0000_offset.wat | 80 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 70 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 76 + ...no_spectre_i8_access_0xffff0000_offset.wat | 80 + ..._guard_yes_spectre_i32_access_0_offset.wat | 72 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 76 + ...s_spectre_i32_access_0xffff0000_offset.wat | 80 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 70 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 76 + ...es_spectre_i8_access_0xffff0000_offset.wat | 80 + ...0_guard_no_spectre_i32_access_0_offset.wat | 70 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 74 + ...o_spectre_i32_access_0xffff0000_offset.wat | 78 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 68 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 74 + ...no_spectre_i8_access_0xffff0000_offset.wat | 78 + ..._guard_yes_spectre_i32_access_0_offset.wat | 70 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 74 + ...s_spectre_i32_access_0xffff0000_offset.wat | 78 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 68 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 74 + ...es_spectre_i8_access_0xffff0000_offset.wat | 78 + ...f_guard_no_spectre_i32_access_0_offset.wat | 70 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 74 + ...o_spectre_i32_access_0xffff0000_offset.wat | 78 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 68 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 74 + ...no_spectre_i8_access_0xffff0000_offset.wat | 78 + ..._guard_yes_spectre_i32_access_0_offset.wat | 70 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 74 + ...s_spectre_i32_access_0xffff0000_offset.wat | 78 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 68 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 74 + ...es_spectre_i8_access_0xffff0000_offset.wat | 78 + ...0_guard_no_spectre_i32_access_0_offset.wat | 68 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 72 + ...o_spectre_i32_access_0xffff0000_offset.wat | 46 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 68 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 72 + ...no_spectre_i8_access_0xffff0000_offset.wat | 46 + ..._guard_yes_spectre_i32_access_0_offset.wat | 68 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 72 + ...s_spectre_i32_access_0xffff0000_offset.wat | 46 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 68 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 72 + ...es_spectre_i8_access_0xffff0000_offset.wat | 46 + ...f_guard_no_spectre_i32_access_0_offset.wat | 54 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 56 + ...o_spectre_i32_access_0xffff0000_offset.wat | 46 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 54 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 56 + ...no_spectre_i8_access_0xffff0000_offset.wat | 46 + ..._guard_yes_spectre_i32_access_0_offset.wat | 54 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 56 + ...s_spectre_i32_access_0xffff0000_offset.wat | 46 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 54 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 56 + ...es_spectre_i8_access_0xffff0000_offset.wat | 46 + ...0_guard_no_spectre_i32_access_0_offset.wat | 66 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 70 + ...o_spectre_i32_access_0xffff0000_offset.wat | 46 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 66 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 70 + ...no_spectre_i8_access_0xffff0000_offset.wat | 46 + ..._guard_yes_spectre_i32_access_0_offset.wat | 66 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 70 + ...s_spectre_i32_access_0xffff0000_offset.wat | 46 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 66 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 70 + ...es_spectre_i8_access_0xffff0000_offset.wat | 46 + ...f_guard_no_spectre_i32_access_0_offset.wat | 66 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 70 + ...o_spectre_i32_access_0xffff0000_offset.wat | 46 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 66 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 70 + ...no_spectre_i8_access_0xffff0000_offset.wat | 46 + ..._guard_yes_spectre_i32_access_0_offset.wat | 66 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 70 + ...s_spectre_i32_access_0xffff0000_offset.wat | 46 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 66 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 70 + ...es_spectre_i8_access_0xffff0000_offset.wat | 46 + .../filetests/isa/riscv64/amodes.clif | 597 ++++ .../filetests/isa/riscv64/arithmetic.clif | 900 +++++ .../filetests/isa/riscv64/atomic-rmw.clif | 344 ++ .../filetests/isa/riscv64/atomic_load.clif | 62 + .../filetests/isa/riscv64/atomic_store.clif | 132 + .../isa/riscv64/bitops-optimized.clif | 70 + .../filetests/isa/riscv64/bitops.clif | 2000 +++++++++++ .../filetests/isa/riscv64/call-indirect.clif | 36 + .../filetests/filetests/isa/riscv64/call.clif | 812 +++++ .../filetests/isa/riscv64/condbr.clif | 745 ++++ .../filetests/isa/riscv64/condops.clif | 152 + .../filetests/isa/riscv64/constants.clif | 513 +++ .../filetests/isa/riscv64/extend-op.clif | 202 ++ .../filetests/filetests/isa/riscv64/fcmp.clif | 74 + .../filetests/isa/riscv64/fcvt-small.clif | 210 ++ .../filetests/isa/riscv64/float.clif | 1308 +++++++ .../filetests/isa/riscv64/i128-bmask.clif | 221 ++ .../filetests/isa/riscv64/iabs-zbb.clif | 84 + .../filetests/filetests/isa/riscv64/iabs.clif | 94 + .../isa/riscv64/iconst-icmp-small.clif | 39 + .../filetests/isa/riscv64/issue-5583.clif | 16 + .../filetests/isa/riscv64/multivalue-ret.clif | 24 + .../isa/riscv64/narrow-arithmetic.clif | 92 + .../filetests/isa/riscv64/prologue.clif | 441 +++ .../filetests/isa/riscv64/reduce.clif | 60 + .../filetests/isa/riscv64/reftypes.clif | 175 + .../filetests/isa/riscv64/shift-op.clif | 41 + .../filetests/isa/riscv64/shift-rotate.clif | 836 +++++ .../filetests/isa/riscv64/stack-limit.clif | 399 +++ .../filetests/isa/riscv64/stack.clif | 1087 ++++++ .../filetests/isa/riscv64/symbol-value.clif | 26 + .../filetests/isa/riscv64/traps.clif | 64 + .../isa/riscv64/uadd_overflow_trap.clif | 174 + .../isa/riscv64/uextend-sextend.clif | 205 ++ ...0_guard_no_spectre_i32_access_0_offset.wat | 74 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 82 + ...o_spectre_i32_access_0xffff0000_offset.wat | 84 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 72 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 82 + ...no_spectre_i8_access_0xffff0000_offset.wat | 84 + ..._guard_yes_spectre_i32_access_0_offset.wat | 70 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 78 + ...s_spectre_i32_access_0xffff0000_offset.wat | 80 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 68 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 78 + ...es_spectre_i8_access_0xffff0000_offset.wat | 80 + ...f_guard_no_spectre_i32_access_0_offset.wat | 74 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 82 + ...o_spectre_i32_access_0xffff0000_offset.wat | 84 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 72 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 82 + ...no_spectre_i8_access_0xffff0000_offset.wat | 84 + ..._guard_yes_spectre_i32_access_0_offset.wat | 70 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 78 + ...s_spectre_i32_access_0xffff0000_offset.wat | 80 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 68 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 78 + ...es_spectre_i8_access_0xffff0000_offset.wat | 80 + ...0_guard_no_spectre_i32_access_0_offset.wat | 72 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 80 + ...o_spectre_i32_access_0xffff0000_offset.wat | 82 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 70 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 80 + ...no_spectre_i8_access_0xffff0000_offset.wat | 82 + ..._guard_yes_spectre_i32_access_0_offset.wat | 68 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 76 + ...s_spectre_i32_access_0xffff0000_offset.wat | 78 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 66 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 76 + ...es_spectre_i8_access_0xffff0000_offset.wat | 78 + ...f_guard_no_spectre_i32_access_0_offset.wat | 72 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 80 + ...o_spectre_i32_access_0xffff0000_offset.wat | 82 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 70 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 80 + ...no_spectre_i8_access_0xffff0000_offset.wat | 82 + ..._guard_yes_spectre_i32_access_0_offset.wat | 68 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 76 + ...s_spectre_i32_access_0xffff0000_offset.wat | 78 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 66 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 76 + ...es_spectre_i8_access_0xffff0000_offset.wat | 78 + ...0_guard_no_spectre_i32_access_0_offset.wat | 72 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 76 + ...o_spectre_i32_access_0xffff0000_offset.wat | 46 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 72 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 76 + ...no_spectre_i8_access_0xffff0000_offset.wat | 46 + ..._guard_yes_spectre_i32_access_0_offset.wat | 68 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 72 + ...s_spectre_i32_access_0xffff0000_offset.wat | 46 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 68 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 72 + ...es_spectre_i8_access_0xffff0000_offset.wat | 46 + ...f_guard_no_spectre_i32_access_0_offset.wat | 58 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 62 + ...o_spectre_i32_access_0xffff0000_offset.wat | 46 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 58 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 62 + ...no_spectre_i8_access_0xffff0000_offset.wat | 46 + ..._guard_yes_spectre_i32_access_0_offset.wat | 58 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 62 + ...s_spectre_i32_access_0xffff0000_offset.wat | 46 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 58 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 62 + ...es_spectre_i8_access_0xffff0000_offset.wat | 46 + ...0_guard_no_spectre_i32_access_0_offset.wat | 70 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 74 + ...o_spectre_i32_access_0xffff0000_offset.wat | 46 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 70 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 74 + ...no_spectre_i8_access_0xffff0000_offset.wat | 46 + ..._guard_yes_spectre_i32_access_0_offset.wat | 66 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 70 + ...s_spectre_i32_access_0xffff0000_offset.wat | 46 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 66 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 70 + ...es_spectre_i8_access_0xffff0000_offset.wat | 46 + ...f_guard_no_spectre_i32_access_0_offset.wat | 70 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 74 + ...o_spectre_i32_access_0xffff0000_offset.wat | 46 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 70 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 74 + ...no_spectre_i8_access_0xffff0000_offset.wat | 46 + ..._guard_yes_spectre_i32_access_0_offset.wat | 66 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 70 + ...s_spectre_i32_access_0xffff0000_offset.wat | 46 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 66 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 70 + ...es_spectre_i8_access_0xffff0000_offset.wat | 46 + .../filetests/isa/s390x/arithmetic.clif | 1337 +++++-- .../isa/s390x/atomic_cas-little.clif | 100 +- .../filetests/isa/s390x/atomic_cas.clif | 70 +- .../isa/s390x/atomic_load-little.clif | 51 +- .../filetests/isa/s390x/atomic_load.clif | 48 +- .../isa/s390x/atomic_rmw-arch13.clif | 175 +- .../isa/s390x/atomic_rmw-little.clif | 1016 +++++- .../filetests/isa/s390x/atomic_rmw.clif | 870 ++++- .../isa/s390x/atomic_store-little.clif | 88 +- .../filetests/isa/s390x/atomic_store.clif | 83 +- .../filetests/isa/s390x/bitcast.clif | 121 + .../filetests/isa/s390x/bitops-arch13.clif | 37 +- .../filetests/isa/s390x/bitops-optimized.clif | 127 + .../filetests/filetests/isa/s390x/bitops.clif | 776 +++-- .../filetests/isa/s390x/bitwise-arch13.clif | 172 +- .../filetests/isa/s390x/bitwise.clif | 586 +++- .../filetests/filetests/isa/s390x/bswap.clif | 53 + .../filetests/filetests/isa/s390x/call.clif | 273 +- .../filetests/isa/s390x/concat-split.clif | 25 +- .../filetests/filetests/isa/s390x/condbr.clif | 36 +- .../filetests/isa/s390x/condops.clif | 52 +- .../filetests/isa/s390x/constants.clif | 84 +- .../filetests/isa/s390x/conversions.clif | 1542 ++++---- .../filetests/isa/s390x/div-traps.clif | 582 +++- .../filetests/filetests/isa/s390x/fence.clif | 6 + .../isa/s390x/floating-point-arch13.clif | 1097 ++++-- .../filetests/isa/s390x/floating-point.clif | 1516 ++++++-- .../filetests/isa/s390x/fp_sp_pc.clif | 36 + .../filetests/isa/s390x/fpmem-arch13.clif | 32 + .../filetests/filetests/isa/s390x/fpmem.clif | 68 +- .../filetests/isa/s390x/heap_addr.clif | 50 - .../filetests/isa/s390x/icmp-i128.clif | 196 +- .../filetests/filetests/isa/s390x/icmp.clif | 622 +++- .../filetests/isa/s390x/issue-5425.clif | 16 + .../filetests/isa/s390x/jumptable.clif | 38 +- .../filetests/filetests/isa/s390x/leaf.clif | 5 + .../leaf_with_preserve_frame_pointers.clif | 11 + .../filetests/isa/s390x/load-little.clif | 237 +- .../filetests/filetests/isa/s390x/load.clif | 180 +- .../filetests/filetests/isa/s390x/minmax.clif | 431 +++ .../filetests/isa/s390x/multivalue-ret.clif | 119 +- .../filetests/isa/s390x/reftypes.clif | 101 +- .../filetests/isa/s390x/saturating-ops.clif | 6 + .../filetests/isa/s390x/shift-rotate.clif | 1194 +++++-- .../filetests/isa/s390x/stack-limit.clif | 134 + .../filetests/filetests/isa/s390x/stack.clif | 66 +- .../filetests/isa/s390x/store-little.clif | 189 +- .../filetests/filetests/isa/s390x/store.clif | 192 +- .../filetests/isa/s390x/struct-arg.clif | 105 +- .../filetests/isa/s390x/symbols.clif | 34 + .../filetests/isa/s390x/tls_elf.clif | 47 + .../filetests/filetests/isa/s390x/traps.clif | 58 +- .../isa/s390x/uadd_overflow_trap.clif | 141 + .../filetests/isa/s390x/vec-abi.clif | 247 ++ .../filetests/isa/s390x/vec-arithmetic.clif | 949 ++++- .../filetests/isa/s390x/vec-bitcast.clif | 117 + .../filetests/isa/s390x/vec-bitops.clif | 24 + .../filetests/isa/s390x/vec-bitwise.clif | 232 +- .../isa/s390x/vec-constants-le-lane.clif | 400 +++ .../filetests/isa/s390x/vec-constants.clif | 197 +- .../isa/s390x/vec-conversions-le-lane.clif | 357 ++ .../filetests/isa/s390x/vec-conversions.clif | 195 +- .../filetests/isa/s390x/vec-fcmp.clif | 338 +- .../filetests/isa/s390x/vec-fp-arch13.clif | 71 +- .../filetests/filetests/isa/s390x/vec-fp.clif | 578 ++- .../filetests/isa/s390x/vec-icmp.clif | 420 ++- .../filetests/isa/s390x/vec-lane-arch13.clif | 622 +++- .../isa/s390x/vec-lane-le-lane-arch13.clif | 1308 +++++++ .../filetests/isa/s390x/vec-lane-le-lane.clif | 3101 +++++++++++++++++ .../filetests/isa/s390x/vec-lane.clif | 1623 +++++++-- .../filetests/isa/s390x/vec-logical.clif | 811 ++++- .../isa/s390x/vec-permute-le-lane.clif | 808 +++++ .../filetests/isa/s390x/vec-permute.clif | 433 ++- .../filetests/isa/s390x/vec-shift-rotate.clif | 260 +- .../filetests/isa/s390x/vecmem-arch13.clif | 380 +- .../isa/s390x/vecmem-le-lane-arch13.clif | 665 ++++ .../filetests/isa/s390x/vecmem-le-lane.clif | 823 +++++ .../filetests/filetests/isa/s390x/vecmem.clif | 547 ++- ...0_guard_no_spectre_i32_access_0_offset.wat | 76 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 81 + ...o_spectre_i32_access_0xffff0000_offset.wat | 97 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 76 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 81 + ...no_spectre_i8_access_0xffff0000_offset.wat | 97 + ..._guard_yes_spectre_i32_access_0_offset.wat | 88 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 88 + ...s_spectre_i32_access_0xffff0000_offset.wat | 93 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 76 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 88 + ...es_spectre_i8_access_0xffff0000_offset.wat | 93 + ...f_guard_no_spectre_i32_access_0_offset.wat | 76 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 81 + ...o_spectre_i32_access_0xffff0000_offset.wat | 97 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 76 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 81 + ...no_spectre_i8_access_0xffff0000_offset.wat | 97 + ..._guard_yes_spectre_i32_access_0_offset.wat | 88 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 88 + ...s_spectre_i32_access_0xffff0000_offset.wat | 93 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 76 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 88 + ...es_spectre_i8_access_0xffff0000_offset.wat | 93 + ...0_guard_no_spectre_i32_access_0_offset.wat | 74 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 78 + ...o_spectre_i32_access_0xffff0000_offset.wat | 86 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 72 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 78 + ...no_spectre_i8_access_0xffff0000_offset.wat | 86 + ..._guard_yes_spectre_i32_access_0_offset.wat | 86 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 89 + ...s_spectre_i32_access_0xffff0000_offset.wat | 89 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 76 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 89 + ...es_spectre_i8_access_0xffff0000_offset.wat | 89 + ...f_guard_no_spectre_i32_access_0_offset.wat | 74 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 78 + ...o_spectre_i32_access_0xffff0000_offset.wat | 86 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 72 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 78 + ...no_spectre_i8_access_0xffff0000_offset.wat | 86 + ..._guard_yes_spectre_i32_access_0_offset.wat | 86 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 89 + ...s_spectre_i32_access_0xffff0000_offset.wat | 89 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 76 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 89 + ...es_spectre_i8_access_0xffff0000_offset.wat | 89 + ...0_guard_no_spectre_i32_access_0_offset.wat | 72 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 74 + ...o_spectre_i32_access_0xffff0000_offset.wat | 50 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 72 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 74 + ...no_spectre_i8_access_0xffff0000_offset.wat | 50 + ..._guard_yes_spectre_i32_access_0_offset.wat | 69 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 70 + ...s_spectre_i32_access_0xffff0000_offset.wat | 50 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 69 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 70 + ...es_spectre_i8_access_0xffff0000_offset.wat | 50 + ...f_guard_no_spectre_i32_access_0_offset.wat | 62 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 66 + ...o_spectre_i32_access_0xffff0000_offset.wat | 50 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 62 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 66 + ...no_spectre_i8_access_0xffff0000_offset.wat | 50 + ..._guard_yes_spectre_i32_access_0_offset.wat | 62 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 66 + ...s_spectre_i32_access_0xffff0000_offset.wat | 50 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 62 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 66 + ...es_spectre_i8_access_0xffff0000_offset.wat | 50 + ...0_guard_no_spectre_i32_access_0_offset.wat | 68 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 70 + ...o_spectre_i32_access_0xffff0000_offset.wat | 50 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 68 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 70 + ...no_spectre_i8_access_0xffff0000_offset.wat | 50 + ..._guard_yes_spectre_i32_access_0_offset.wat | 73 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 68 + ...s_spectre_i32_access_0xffff0000_offset.wat | 50 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 73 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 68 + ...es_spectre_i8_access_0xffff0000_offset.wat | 50 + ...f_guard_no_spectre_i32_access_0_offset.wat | 68 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 70 + ...o_spectre_i32_access_0xffff0000_offset.wat | 50 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 68 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 70 + ...no_spectre_i8_access_0xffff0000_offset.wat | 50 + ..._guard_yes_spectre_i32_access_0_offset.wat | 73 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 68 + ...s_spectre_i32_access_0xffff0000_offset.wat | 50 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 73 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 68 + ...es_spectre_i8_access_0xffff0000_offset.wat | 50 + .../filetests/isa/x64/amode-opt.clif | 112 +- .../filetests/isa/x64/atomic-cas-bug.clif | 66 +- .../isa/x64/atomic_cas_const_addr.clif | 5 +- cranelift/filetests/filetests/isa/x64/b1.clif | 75 - .../filetests/isa/x64/band_not_bmi1.clif | 55 + .../filetests/filetests/isa/x64/basic.clif | 14 +- .../filetests/filetests/isa/x64/bextend.clif | 17 - .../filetests/filetests/isa/x64/bitcast.clif | 103 + .../filetests/filetests/isa/x64/bmask.clif | 800 +++++ .../filetests/filetests/isa/x64/branches.clif | 584 +++- .../filetests/filetests/isa/x64/bswap.clif | 84 + .../filetests/isa/x64/call-conv.clif | 445 ++- .../filetests/isa/x64/ceil-libcall.clif | 57 + .../filetests/filetests/isa/x64/ceil.clif | 103 + .../filetests/isa/x64/clz-lzcnt.clif | 22 + .../filetests/isa/x64/cmp-mem-bug.clif | 75 +- .../filetests/isa/x64/conditional-values.clif | 458 +++ .../filetests/filetests/isa/x64/ctz-bmi1.clif | 22 + .../filetests/isa/x64/div-checks.clif | 151 +- .../filetests/isa/x64/extractlane.clif | 153 + .../filetests/filetests/isa/x64/fabs.clif | 119 + .../filetests/filetests/isa/x64/fastcall.clif | 347 +- .../filetests/isa/x64/fcmp-mem-bug.clif | 113 +- .../filetests/isa/x64/fcopysign.clif | 77 + .../filetests/isa/x64/fcvt-simd.clif | 29 + .../filetests/filetests/isa/x64/fcvt.clif | 1118 ++++++ .../filetests/isa/x64/floating-point.clif | 41 +- .../filetests/isa/x64/floor-libcall.clif | 57 + .../filetests/filetests/isa/x64/floor.clif | 103 + .../filetests/filetests/isa/x64/fma-call.clif | 57 + .../filetests/filetests/isa/x64/fma-inst.clif | 53 + .../filetests/filetests/isa/x64/fneg.clif | 119 + .../filetests/filetests/isa/x64/fp_sp_pc.clif | 38 +- .../filetests/filetests/isa/x64/heap.clif | 36 - .../filetests/filetests/isa/x64/i128.clif | 1731 ++++++--- .../filetests/filetests/isa/x64/iabs.clif | 119 + .../filetests/isa/x64/immediates.clif | 41 +- .../isa/x64/inline-probestack-large.clif | 117 + .../filetests/isa/x64/inline-probestack.clif | 116 + .../filetests/filetests/isa/x64/ishl.clif | 1013 ++++++ .../filetests/filetests/isa/x64/leaf.clif | 11 + .../leaf_with_preserve_frame_pointers.clif | 11 + .../filetests/isa/x64/load-op-store.clif | 99 + .../filetests/filetests/isa/x64/load-op.clif | 199 +- .../filetests/isa/x64/move-elision.clif | 18 +- .../filetests/isa/x64/narrowing.clif | 146 + .../filetests/isa/x64/nearest-libcall.clif | 57 + .../filetests/filetests/isa/x64/nearest.clif | 103 + .../filetests/isa/x64/pinned-reg.clif | 42 +- .../filetests/isa/x64/popcnt-use-popcnt.clif | 22 + .../filetests/filetests/isa/x64/popcnt.clif | 240 +- .../filetests/isa/x64/probestack.clif | 15 + .../filetests/filetests/isa/x64/sdiv.clif | 119 + .../filetests/isa/x64/select-i128.clif | 46 +- .../filetests/filetests/isa/x64/select.clif | 67 + .../filetests/filetests/isa/x64/sextend.clif | 28 + .../filetests/isa/x64/shuffle-avx512.clif | 128 + .../filetests/isa/x64/simd-bitselect.clif | 251 ++ .../isa/x64/simd-bitwise-compile.clif | 397 ++- .../isa/x64/simd-comparison-legalize.clif | 81 +- .../filetests/isa/x64/simd-issue-3951.clif | 2 +- .../isa/x64/simd-lane-access-compile.clif | 247 +- .../isa/x64/simd-logical-compile.clif | 58 +- .../filetests/isa/x64/simd-pairwise-add.clif | 186 + .../filetests/isa/x64/simd-widen-mul.clif | 426 +++ .../filetests/filetests/isa/x64/smulhi.clif | 90 + .../filetests/isa/x64/sqmul_round_sat.clif | 37 + .../filetests/filetests/isa/x64/srem.clif | 159 + .../filetests/filetests/isa/x64/sshr.clif | 1034 ++++++ .../filetests/isa/x64/struct-arg.clif | 170 +- .../filetests/isa/x64/struct-ret.clif | 87 + .../filetests/filetests/isa/x64/symbols.clif | 22 + .../filetests/filetests/isa/x64/table.clif | 45 +- .../filetests/filetests/isa/x64/tls_coff.clif | 35 + .../filetests/filetests/isa/x64/tls_elf.clif | 14 +- .../filetests/filetests/isa/x64/traps.clif | 31 +- .../filetests/isa/x64/trunc-libcall.clif | 57 + .../filetests/filetests/isa/x64/trunc.clif | 103 + .../filetests/isa/x64/uadd_overflow_trap.clif | 187 + .../filetests/filetests/isa/x64/udiv.clif | 117 + .../filetests/filetests/isa/x64/udivrem.clif | 171 + .../filetests/isa/x64/uextend-elision.clif | 14 +- .../filetests/filetests/isa/x64/umax-bug.clif | 20 +- .../filetests/filetests/isa/x64/umulhi.clif | 90 + .../isa/x64/unused_jt_unreachable_block.clif | 22 - .../filetests/filetests/isa/x64/urem.clif | 125 + .../filetests/filetests/isa/x64/ushr.clif | 1014 ++++++ .../filetests/filetests/isa/x64/uunarrow.clif | 51 + .../filetests/isa/x64/vhigh_bits.clif | 134 + ...0_guard_no_spectre_i32_access_0_offset.wat | 84 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 84 + ...o_spectre_i32_access_0xffff0000_offset.wat | 90 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 82 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 84 + ...no_spectre_i8_access_0xffff0000_offset.wat | 90 + ..._guard_yes_spectre_i32_access_0_offset.wat | 84 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 84 + ...s_spectre_i32_access_0xffff0000_offset.wat | 86 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 80 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 84 + ...es_spectre_i8_access_0xffff0000_offset.wat | 86 + ...f_guard_no_spectre_i32_access_0_offset.wat | 84 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 84 + ...o_spectre_i32_access_0xffff0000_offset.wat | 90 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 82 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 84 + ...no_spectre_i8_access_0xffff0000_offset.wat | 90 + ..._guard_yes_spectre_i32_access_0_offset.wat | 84 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 84 + ...s_spectre_i32_access_0xffff0000_offset.wat | 86 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 80 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 84 + ...es_spectre_i8_access_0xffff0000_offset.wat | 86 + ...0_guard_no_spectre_i32_access_0_offset.wat | 82 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 82 + ...o_spectre_i32_access_0xffff0000_offset.wat | 88 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 80 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 82 + ...no_spectre_i8_access_0xffff0000_offset.wat | 88 + ..._guard_yes_spectre_i32_access_0_offset.wat | 80 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 83 + ...s_spectre_i32_access_0xffff0000_offset.wat | 86 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 78 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 83 + ...es_spectre_i8_access_0xffff0000_offset.wat | 86 + ...f_guard_no_spectre_i32_access_0_offset.wat | 82 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 82 + ...o_spectre_i32_access_0xffff0000_offset.wat | 88 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 80 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 82 + ...no_spectre_i8_access_0xffff0000_offset.wat | 88 + ..._guard_yes_spectre_i32_access_0_offset.wat | 80 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 83 + ...s_spectre_i32_access_0xffff0000_offset.wat | 86 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 78 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 83 + ...es_spectre_i8_access_0xffff0000_offset.wat | 86 + ...0_guard_no_spectre_i32_access_0_offset.wat | 78 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 78 + ...o_spectre_i32_access_0xffff0000_offset.wat | 54 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 78 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 78 + ...no_spectre_i8_access_0xffff0000_offset.wat | 54 + ..._guard_yes_spectre_i32_access_0_offset.wat | 76 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 78 + ...s_spectre_i32_access_0xffff0000_offset.wat | 54 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 76 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 78 + ...es_spectre_i8_access_0xffff0000_offset.wat | 54 + ...f_guard_no_spectre_i32_access_0_offset.wat | 68 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 68 + ...o_spectre_i32_access_0xffff0000_offset.wat | 54 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 68 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 68 + ...no_spectre_i8_access_0xffff0000_offset.wat | 54 + ..._guard_yes_spectre_i32_access_0_offset.wat | 68 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 68 + ...s_spectre_i32_access_0xffff0000_offset.wat | 54 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 68 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 68 + ...es_spectre_i8_access_0xffff0000_offset.wat | 54 + ...0_guard_no_spectre_i32_access_0_offset.wat | 76 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 76 + ...o_spectre_i32_access_0xffff0000_offset.wat | 54 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 76 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 76 + ...no_spectre_i8_access_0xffff0000_offset.wat | 54 + ..._guard_yes_spectre_i32_access_0_offset.wat | 74 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 76 + ...s_spectre_i32_access_0xffff0000_offset.wat | 54 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 74 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 76 + ...es_spectre_i8_access_0xffff0000_offset.wat | 54 + ...f_guard_no_spectre_i32_access_0_offset.wat | 76 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 76 + ...o_spectre_i32_access_0xffff0000_offset.wat | 54 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 76 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 76 + ...no_spectre_i8_access_0xffff0000_offset.wat | 54 + ..._guard_yes_spectre_i32_access_0_offset.wat | 74 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 76 + ...s_spectre_i32_access_0xffff0000_offset.wat | 54 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 74 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 76 + ...es_spectre_i8_access_0xffff0000_offset.wat | 54 + .../filetests/isa/x64/widen-high-bug.clif | 33 + .../filetests/filetests/isa/x64/widening.clif | 323 ++ .../legalizer/conditional-traps.clif | 38 + .../filetests/legalizer/isplit-bb.clif | 3 +- cranelift/filetests/filetests/licm/basic.clif | 6 +- .../filetests/filetests/licm/br-table.clif | 3 +- .../filetests/filetests/licm/complex.clif | 24 +- .../filetests/licm/critical-edge.clif | 12 +- .../filetests/filetests/licm/encoding.clif | 6 +- .../filetests/licm/load_readonly_notrap.clif | 12 +- .../filetests/licm/multiple-blocks.clif | 12 +- .../filetests/licm/nested_loops.clif | 12 +- .../filetests/filetests/licm/reject.clif | 32 +- .../filetests/licm/reject_load_notrap.clif | 30 +- .../filetests/licm/reject_load_readonly.clif | 12 +- .../filetests/licm/rewrite-jump-table.clif | 8 +- .../filetests/filetests/parser/branch.clif | 46 +- .../filetests/filetests/parser/call.clif | 22 +- .../filetests/filetests/parser/flags.clif | 50 +- .../filetests/filetests/parser/memory.clif | 33 +- .../filetests/filetests/parser/ternary.clif | 31 +- .../filetests/filetests/parser/tiny.clif | 52 +- .../filetests/filetests/preopt/branch.clif | 80 - .../filetests/preopt/constant_fold.clif | 20 - .../filetests/filetests/preopt/numerical.clif | 37 - .../filetests/filetests/runtests/alias.clif | 1 + .../filetests/runtests/arithmetic.clif | 1 + .../runtests/atomic-cas-subword-little.clif | 1 + .../filetests/runtests/atomic-cas.clif | 3 +- .../filetests/runtests/atomic-load-store.clif | 94 + .../filetests/runtests/atomic-rmw-little.clif | 1 + .../runtests/atomic-rmw-subword-big.clif | 8 +- .../runtests/atomic-rmw-subword-little.clif | 1 + .../filetests/filetests/runtests/bextend.clif | 88 - .../filetests/filetests/runtests/bint.clif | 340 -- .../filetests/runtests/bitcast-ref64.clif | 27 + .../filetests/runtests/bitcast-same-type.clif | 71 + .../filetests/filetests/runtests/bitcast.clif | 45 + .../filetests/filetests/runtests/bitops.clif | 7 +- .../filetests/filetests/runtests/bitrev.clif | 1 + .../filetests/filetests/runtests/bmask.clif | 176 +- .../filetests/filetests/runtests/bnot.clif | 69 + .../filetests/filetests/runtests/br.clif | 219 +- .../filetests/filetests/runtests/br_icmp.clif | 767 ---- .../filetests/runtests/br_icmp_overflow.clif | 217 -- .../filetests/runtests/br_table.clif | 24 +- .../filetests/filetests/runtests/breduce.clif | 89 - .../filetests/filetests/runtests/brif.clif | 194 ++ .../filetests/filetests/runtests/bswap.clif | 58 + .../filetests/filetests/runtests/call.clif | 89 + .../filetests/runtests/call_indirect.clif | 36 + .../filetests/runtests/call_libcall.clif | 26 + .../filetests/filetests/runtests/ceil.clif | 6 +- .../filetests/filetests/runtests/cls.clif | 1 + .../filetests/filetests/runtests/clz.clif | 1 + .../filetests/filetests/runtests/const.clif | 51 +- .../filetests/runtests/conversion.clif | 56 + .../filetests/runtests/conversions.clif | 86 - .../filetests/filetests/runtests/ctz.clif | 1 + .../filetests/runtests/div-checks.clif | 2 + .../filetests/filetests/runtests/extend.clif | 1 + .../filetests/filetests/runtests/fabs.clif | 1 + .../filetests/filetests/runtests/fadd.clif | 5 +- .../filetests/filetests/runtests/fcmp-eq.clif | 320 ++ .../filetests/filetests/runtests/fcmp-ge.clif | 320 ++ .../filetests/filetests/runtests/fcmp-gt.clif | 320 ++ .../filetests/filetests/runtests/fcmp-le.clif | 320 ++ .../filetests/filetests/runtests/fcmp-lt.clif | 320 ++ .../filetests/filetests/runtests/fcmp-ne.clif | 320 ++ .../filetests/runtests/fcmp-one.clif | 319 ++ .../filetests/runtests/fcmp-ord.clif | 319 ++ .../filetests/runtests/fcmp-ueq.clif | 319 ++ .../filetests/runtests/fcmp-uge.clif | 319 ++ .../filetests/runtests/fcmp-ugt.clif | 319 ++ .../filetests/runtests/fcmp-ule.clif | 319 ++ .../filetests/runtests/fcmp-ult.clif | 319 ++ .../filetests/runtests/fcmp-uno.clif | 320 ++ .../filetests/filetests/runtests/fcmp.clif | 62 - .../filetests/runtests/fcopysign.clif | 1 + .../filetests/runtests/fcvt-sat-small.clif | 132 + .../filetests/filetests/runtests/fdemote.clif | 88 + .../filetests/filetests/runtests/fdiv.clif | 5 +- .../filetests/filetests/runtests/fence.clif | 18 + .../filetests/runtests/fibonacci.clif | 32 +- .../filetests/runtests/float-bitops.clif | 63 + .../filetests/filetests/runtests/floor.clif | 6 +- .../filetests/runtests/fma-interpreter.clif | 25 - .../filetests/filetests/runtests/fma.clif | 21 +- .../filetests/runtests/fmax-pseudo.clif | 5 +- .../filetests/filetests/runtests/fmax.clif | 5 +- .../filetests/runtests/fmin-pseudo.clif | 5 +- .../filetests/filetests/runtests/fmin.clif | 5 +- .../filetests/filetests/runtests/fmul.clif | 5 +- .../filetests/filetests/runtests/fneg.clif | 1 + .../filetests/runtests/fpromote.clif | 96 + .../filetests/filetests/runtests/fsub.clif | 5 +- .../filetests/runtests/global_value.clif | 23 - .../filetests/filetests/runtests/heap.clif | 206 -- .../filetests/runtests/i128-arithmetic.clif | 11 + .../filetests/runtests/i128-bandnot.clif | 1 + .../filetests/runtests/i128-bextend.clif | 45 - .../filetests/runtests/i128-bint.clif | 86 - .../filetests/runtests/i128-bitops-count.clif | 1 + .../filetests/runtests/i128-bitops.clif | 2 + .../filetests/runtests/i128-bitrev.clif | 7 +- .../filetests/runtests/i128-bmask.clif | 87 +- .../filetests/runtests/i128-bnot.clif | 11 + .../filetests/runtests/i128-bornot.clif | 1 + .../filetests/filetests/runtests/i128-br.clif | 36 +- .../filetests/runtests/i128-breduce.clif | 41 - .../runtests/i128-bricmp-overflow.clif | 61 - .../filetests/runtests/i128-bricmp.clif | 248 -- .../filetests/runtests/i128-bswap.clif | 16 + .../filetests/runtests/i128-bxornot.clif | 1 + .../filetests/runtests/i128-call.clif | 24 + .../filetests/runtests/i128-cls.clif | 1 + .../filetests/runtests/i128-concat-split.clif | 2 + .../filetests/runtests/i128-const.clif | 13 - .../filetests/runtests/i128-conversion.clif | 52 + .../filetests/runtests/i128-extend.clif | 1 + .../filetests/runtests/i128-iabs.clif | 13 + .../filetests/runtests/i128-iaddcout.clif | 29 + .../runtests/i128-icmp-overflow.clif | 43 - .../filetests/runtests/i128-icmp.clif | 244 +- .../filetests/runtests/i128-ineg.clif | 19 + .../filetests/runtests/i128-ireduce.clif | 1 + .../filetests/runtests/i128-isubbout.clif | 30 + .../filetests/runtests/i128-load-store.clif | 83 +- .../filetests/runtests/i128-rotate.clif | 116 + .../filetests/runtests/i128-select.clif | 24 +- .../runtests/i128-shifts-small-types.clif | 85 - .../filetests/runtests/i128-shifts.clif | 80 +- .../filetests/filetests/runtests/iabs.clif | 15 +- .../filetests/runtests/iaddcarry.clif | 130 +- .../filetests/filetests/runtests/iaddcin.clif | 60 +- .../filetests/runtests/iaddcout-i16.clif | 29 + .../filetests/runtests/iaddcout-i32.clif | 29 + .../filetests/runtests/iaddcout-i64.clif | 28 + .../filetests/runtests/iaddcout-i8.clif | 29 + .../filetests/runtests/iaddcout.clif | 87 - .../filetests/runtests/icmp-eq-imm.clif | 65 +- .../filetests/filetests/runtests/icmp-eq.clif | 33 +- .../filetests/filetests/runtests/icmp-ne.clif | 33 +- .../filetests/runtests/icmp-nof.clif | 75 - .../filetests/filetests/runtests/icmp-of.clif | 75 - .../filetests/runtests/icmp-sge.clif | 57 +- .../filetests/runtests/icmp-sgt.clif | 57 +- .../filetests/runtests/icmp-sle.clif | 57 +- .../filetests/runtests/icmp-slt.clif | 57 +- .../filetests/runtests/icmp-uge.clif | 57 +- .../filetests/runtests/icmp-ugt.clif | 57 +- .../filetests/runtests/icmp-ule.clif | 57 +- .../filetests/runtests/icmp-ult.clif | 56 +- .../filetests/filetests/runtests/icmp.clif | 7 +- .../filetests/filetests/runtests/ineg.clif | 54 + .../filetests/runtests/inline-probestack.clif | 39 + .../filetests/runtests/integer-minmax.clif | 129 +- .../filetests/filetests/runtests/ireduce.clif | 1 + .../filetests/runtests/issue-5498.clif | 18 + .../filetests/runtests/issue-5690.clif | 29 + .../filetests/runtests/issue5497.clif | 11 + .../filetests/runtests/issue5523.clif | 15 + .../filetests/runtests/issue5524.clif | 11 + .../filetests/runtests/issue5525.clif | 12 + .../filetests/runtests/issue5526.clif | 98 + .../filetests/runtests/issue5528.clif | 20 + .../filetests/runtests/issue5569.clif | 394 +++ .../filetests/filetests/runtests/isubbin.clif | 64 +- .../filetests/runtests/isubborrow.clif | 128 +- .../filetests/runtests/isubbout.clif | 51 +- .../filetests/runtests/load-op-store.clif | 96 - .../filetests/filetests/runtests/nearest.clif | 6 +- .../runtests/or-and-y-with-not-y.clif | 34 + .../filetests/runtests/pinned-reg.clif | 13 + .../filetests/runtests/popcnt-interpret.clif | 8 + .../filetests/filetests/runtests/popcnt.clif | 7 +- .../runtests/ref64-invalid-null.clif | 30 +- .../filetests/runtests/return-call.clif | 68 + .../runtests/riscv64_issue_4996.clif | 25 + .../filetests/filetests/runtests/rotl.clif | 243 ++ .../filetests/filetests/runtests/rotr.clif | 244 ++ .../filetests/filetests/runtests/select.clif | 73 +- .../runtests/selectif-spectre-guard.clif | 326 ++ .../filetests/runtests/shift-right-left.clif | 74 + .../runtests/shifts-small-types.clif | 322 -- .../filetests/filetests/runtests/shifts.clif | 491 ++- .../filetests/runtests/simd-arithmetic.clif | 13 +- .../filetests/runtests/simd-avg-round.clif | 51 + .../runtests/simd-bitcast-aarch64.clif | 21 + .../filetests/runtests/simd-bitcast.clif | 35 + .../runtests/simd-bitselect-to-vselect.clif | 7 +- .../filetests/runtests/simd-bitselect.clif | 2 +- .../filetests/runtests/simd-bmask.clif | 24 +- .../filetests/runtests/simd-comparison.clif | 208 -- .../filetests/runtests/simd-conversion.clif | 37 + .../filetests/runtests/simd-fcmp.clif | 60 + .../runtests/simd-fcopysign-64bit.clif | 37 + .../filetests/runtests/simd-fcopysign.clif | 63 + .../filetests/runtests/simd-fma-64bit.clif | 14 +- .../filetests/runtests/simd-fma.clif | 20 +- .../runtests/simd-iaddpairwise-64bit.clif | 42 + .../filetests/runtests/simd-icmp-eq.clif | 44 +- .../filetests/runtests/simd-icmp-ne.clif | 46 +- .../filetests/runtests/simd-icmp-nof.clif | 45 - .../filetests/runtests/simd-icmp-of.clif | 45 - .../filetests/runtests/simd-icmp-sge.clif | 39 +- .../filetests/runtests/simd-icmp-sgt.clif | 53 +- .../filetests/runtests/simd-icmp-sle.clif | 40 +- .../filetests/runtests/simd-icmp-slt.clif | 39 +- .../runtests/simd-icmp-uge-i64x2.clif | 17 + .../filetests/runtests/simd-icmp-uge.clif | 34 +- .../runtests/simd-icmp-ugt-i64x2.clif | 17 + .../filetests/runtests/simd-icmp-ugt.clif | 33 +- .../runtests/simd-icmp-ule-i64x2.clif | 17 + .../filetests/runtests/simd-icmp-ule.clif | 35 +- .../runtests/simd-icmp-ult-i64x2.clif | 17 + .../filetests/runtests/simd-icmp-ult.clif | 48 +- .../filetests/runtests/simd-lane-access.clif | 30 +- .../filetests/runtests/simd-logical.clif | 44 +- .../runtests/simd-min-max-aarch64.clif | 16 +- .../filetests/runtests/simd-min-max.clif | 40 +- .../filetests/runtests/simd-shuffle.clif | 8 + .../filetests/runtests/simd-splat.clif | 3 - .../filetests/runtests/simd-swizzle.clif | 20 - .../filetests/runtests/simd-ushr.clif | 2 +- .../runtests/simd-valltrue-64bit.clif | 48 +- .../filetests/runtests/simd-valltrue.clif | 40 +- .../runtests/simd-vanytrue-64bit.clif | 48 +- .../filetests/runtests/simd-vanytrue.clif | 40 +- .../filetests/runtests/simd-vconst-64bit.clif | 39 + .../filetests/runtests/simd-vconst.clif | 6 +- .../filetests/runtests/simd-vselect.clif | 56 +- .../filetests/runtests/simd_compare_zero.clif | 74 +- .../filetests/runtests/smulhi-aarch64.clif | 1 + .../filetests/filetests/runtests/smulhi.clif | 2 + .../filetests/runtests/spill-reload.clif | 1 + .../filetests/filetests/runtests/sqrt.clif | 5 +- .../filetests/filetests/runtests/srem.clif | 13 +- .../filetests/runtests/stack-addr-32.clif | 28 +- .../filetests/runtests/stack-addr-64.clif | 18 +- .../filetests/filetests/runtests/stack.clif | 11 +- .../filetests/runtests/table_addr.clif | 143 - .../filetests/filetests/runtests/trunc.clif | 6 +- .../runtests/uadd_overflow_trap.clif | 68 + .../filetests/filetests/runtests/umulhi.clif | 1 + .../filetests/filetests/runtests/urem.clif | 1 + .../runtests/x64-xmm-mem-align-bug.clif | 17 + .../filetests/filetests/simple_gvn/basic.clif | 3 +- .../simple_gvn/idempotent-trapping.clif | 68 + .../filetests/simple_gvn/readonly.clif | 7 +- .../filetests/simple_gvn/reject.clif | 18 +- .../filetests/simple_gvn/scopes.clif | 6 +- .../filetests/simple_preopt/bitselect.clif | 51 - .../filetests/simple_preopt/branch.clif | 45 +- .../filetests/simple_preopt/i128.clif | 28 + ...ing_instructions_and_cfg_predecessors.clif | 11 +- .../filetests/simple_preopt/sign_extend.clif | 4 +- .../filetests/simple_preopt/simplify32.clif | 4 +- .../filetests/simple_preopt/simplify64.clif | 22 +- .../verifier/argument-extension.clif | 26 + .../filetests/verifier/bad_layout.clif | 2 +- .../filetests/filetests/verifier/bitcast.clif | 41 +- .../filetests/verifier/cold_entry.clif | 6 + .../filetests/filetests/verifier/heap.clif | 45 - .../filetests/verifier/jump_table.clif | 8 +- .../filetests/verifier/return-call.clif | 50 + .../filetests/verifier/simd-lane-index.clif | 14 +- .../filetests/verifier/type_check.clif | 40 +- .../filetests/wasm/basic-wat-test.wat | 45 + .../filetests/filetests/wasm/control.clif | 13 +- .../duplicate-loads-dynamic-memory-egraph.wat | 92 + .../wasm/duplicate-loads-dynamic-memory.wat | 116 + .../duplicate-loads-static-memory-egraph.wat | 74 + .../wasm/duplicate-loads-static-memory.wat | 86 + .../filetests/wasm/f32-compares.clif | 12 +- .../filetests/filetests/wasm/f32-load.wat | 22 + .../filetests/wasm/f32-memory64.clif | 27 - .../filetests/filetests/wasm/f32-store.wat | 25 + .../filetests/wasm/f64-compares.clif | 12 +- .../filetests/filetests/wasm/f64-load.wat | 24 + .../filetests/wasm/f64-memory64.clif | 27 - .../filetests/filetests/wasm/f64-store.wat | 25 + .../filetests/wasm/i32-compares.clif | 22 +- .../filetests/filetests/wasm/i32-load.wat | 24 + .../filetests/filetests/wasm/i32-load16-s.wat | 24 + .../filetests/filetests/wasm/i32-load16-u.wat | 24 + .../filetests/filetests/wasm/i32-load8-s.wat | 24 + .../filetests/filetests/wasm/i32-load8-u.wat | 24 + .../filetests/wasm/i32-memory64.clif | 88 - .../filetests/filetests/wasm/i32-not-x64.wat | 46 + .../filetests/filetests/wasm/i32-store.wat | 25 + .../filetests/filetests/wasm/i32-store16.wat | 25 + .../filetests/filetests/wasm/i32-store8.wat | 25 + .../filetests/wasm/i64-compares.clif | 22 +- .../filetests/filetests/wasm/i64-load.wat | 24 + .../filetests/filetests/wasm/i64-load16-s.wat | 24 + .../filetests/filetests/wasm/i64-load16-u.wat | 24 + .../filetests/filetests/wasm/i64-load8-s.wat | 24 + .../filetests/filetests/wasm/i64-load8-u.wat | 24 + .../filetests/wasm/i64-memory64.clif | 117 - .../filetests/filetests/wasm/i64-store.wat | 25 + .../filetests/filetests/wasm/i64-store16.wat | 25 + .../filetests/filetests/wasm/i64-store32.wat | 25 + .../filetests/filetests/wasm/i64-store8.wat | 25 + .../filetests/filetests/wasm/issue-5696.wat | 20 + ...0_guard_no_spectre_i32_access_0_offset.wat | 80 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 82 + ...o_spectre_i32_access_0xffff0000_offset.wat | 84 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 78 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 82 + ...no_spectre_i8_access_0xffff0000_offset.wat | 84 + ..._guard_yes_spectre_i32_access_0_offset.wat | 82 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 84 + ...s_spectre_i32_access_0xffff0000_offset.wat | 86 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 80 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 84 + ...es_spectre_i8_access_0xffff0000_offset.wat | 86 + ...f_guard_no_spectre_i32_access_0_offset.wat | 80 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 82 + ...o_spectre_i32_access_0xffff0000_offset.wat | 84 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 78 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 82 + ...no_spectre_i8_access_0xffff0000_offset.wat | 84 + ..._guard_yes_spectre_i32_access_0_offset.wat | 82 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 84 + ...s_spectre_i32_access_0xffff0000_offset.wat | 86 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 80 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 84 + ...es_spectre_i8_access_0xffff0000_offset.wat | 86 + ...0_guard_no_spectre_i32_access_0_offset.wat | 78 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 80 + ...o_spectre_i32_access_0xffff0000_offset.wat | 82 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 76 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 80 + ...no_spectre_i8_access_0xffff0000_offset.wat | 82 + ..._guard_yes_spectre_i32_access_0_offset.wat | 80 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 82 + ...s_spectre_i32_access_0xffff0000_offset.wat | 84 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 78 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 82 + ...es_spectre_i8_access_0xffff0000_offset.wat | 84 + ...f_guard_no_spectre_i32_access_0_offset.wat | 78 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 80 + ...o_spectre_i32_access_0xffff0000_offset.wat | 82 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 76 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 80 + ...no_spectre_i8_access_0xffff0000_offset.wat | 82 + ..._guard_yes_spectre_i32_access_0_offset.wat | 80 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 82 + ...s_spectre_i32_access_0xffff0000_offset.wat | 84 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 78 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 82 + ...es_spectre_i8_access_0xffff0000_offset.wat | 84 + ...0_guard_no_spectre_i32_access_0_offset.wat | 72 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 74 + ...o_spectre_i32_access_0xffff0000_offset.wat | 56 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 72 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 74 + ...no_spectre_i8_access_0xffff0000_offset.wat | 56 + ..._guard_yes_spectre_i32_access_0_offset.wat | 76 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 78 + ...s_spectre_i32_access_0xffff0000_offset.wat | 56 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 76 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 78 + ...es_spectre_i8_access_0xffff0000_offset.wat | 56 + ...f_guard_no_spectre_i32_access_0_offset.wat | 68 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 70 + ...o_spectre_i32_access_0xffff0000_offset.wat | 56 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 68 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 70 + ...no_spectre_i8_access_0xffff0000_offset.wat | 56 + ..._guard_yes_spectre_i32_access_0_offset.wat | 68 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 70 + ...s_spectre_i32_access_0xffff0000_offset.wat | 56 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 68 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 70 + ...es_spectre_i8_access_0xffff0000_offset.wat | 56 + ...0_guard_no_spectre_i32_access_0_offset.wat | 70 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 72 + ...o_spectre_i32_access_0xffff0000_offset.wat | 54 + ..._0_guard_no_spectre_i8_access_0_offset.wat | 70 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 72 + ...no_spectre_i8_access_0xffff0000_offset.wat | 54 + ..._guard_yes_spectre_i32_access_0_offset.wat | 74 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 76 + ...s_spectre_i32_access_0xffff0000_offset.wat | 54 + ...0_guard_yes_spectre_i8_access_0_offset.wat | 74 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 76 + ...es_spectre_i8_access_0xffff0000_offset.wat | 54 + ...f_guard_no_spectre_i32_access_0_offset.wat | 70 + ...rd_no_spectre_i32_access_0x1000_offset.wat | 72 + ...o_spectre_i32_access_0xffff0000_offset.wat | 54 + ...ff_guard_no_spectre_i8_access_0_offset.wat | 70 + ...ard_no_spectre_i8_access_0x1000_offset.wat | 72 + ...no_spectre_i8_access_0xffff0000_offset.wat | 54 + ..._guard_yes_spectre_i32_access_0_offset.wat | 74 + ...d_yes_spectre_i32_access_0x1000_offset.wat | 76 + ...s_spectre_i32_access_0xffff0000_offset.wat | 54 + ...f_guard_yes_spectre_i8_access_0_offset.wat | 74 + ...rd_yes_spectre_i8_access_0x1000_offset.wat | 76 + ...es_spectre_i8_access_0xffff0000_offset.wat | 54 + .../wasm/load-store/make-load-store-tests.sh | 134 + .../filetests/wasm/multi-val-mixed.clif | 776 ++--- cranelift/filetests/filetests/wasm/r32.clif | 3 +- cranelift/filetests/filetests/wasm/r64.clif | 3 +- cranelift/filetests/src/function_runner.rs | 511 +-- cranelift/filetests/src/lib.rs | 8 +- cranelift/filetests/src/runner.rs | 4 +- cranelift/filetests/src/runone.rs | 83 +- .../filetests/src/runtest_environment.rs | 50 +- cranelift/filetests/src/subtest.rs | 48 +- cranelift/filetests/src/test_compile.rs | 36 +- cranelift/filetests/src/test_interpret.rs | 130 +- cranelift/filetests/src/test_licm.rs | 1 + cranelift/filetests/src/test_optimize.rs | 47 + cranelift/filetests/src/test_preopt.rs | 48 - cranelift/filetests/src/test_run.rs | 167 +- cranelift/filetests/src/test_unwind.rs | 4 +- cranelift/filetests/src/test_wasm.rs | 134 + cranelift/filetests/src/test_wasm/config.rs | 223 ++ cranelift/filetests/src/test_wasm/env.rs | 616 ++++ cranelift/frontend/Cargo.toml | 17 +- cranelift/frontend/src/frontend.rs | 472 +-- cranelift/frontend/src/lib.rs | 14 +- cranelift/frontend/src/ssa.rs | 831 ++--- cranelift/frontend/src/switch.rs | 411 +-- cranelift/frontend/src/variable.rs | 21 +- cranelift/fuzzgen/Cargo.toml | 11 +- cranelift/fuzzgen/src/config.rs | 64 +- cranelift/fuzzgen/src/function_generator.rs | 1763 ++++++++-- cranelift/fuzzgen/src/lib.rs | 286 +- cranelift/fuzzgen/src/passes/fcvt.rs | 98 + cranelift/fuzzgen/src/passes/int_divz.rs | 78 + cranelift/fuzzgen/src/passes/mod.rs | 5 + cranelift/interpreter/Cargo.toml | 20 +- cranelift/interpreter/src/environment.rs | 6 +- cranelift/interpreter/src/frame.rs | 6 +- cranelift/interpreter/src/instruction.rs | 2 +- cranelift/interpreter/src/interpreter.rs | 285 +- cranelift/interpreter/src/state.rs | 64 +- cranelift/interpreter/src/step.rs | 660 ++-- cranelift/interpreter/src/value.rs | 167 +- cranelift/isle/README.md | 2 +- cranelift/isle/fuzz/Cargo.toml | 6 +- cranelift/isle/isle/Cargo.toml | 10 +- .../isle/isle_examples/fail/extra_parens.isle | 6 + .../fail/multi_internal_etor.isle | 4 + .../isle/isle_examples/fail/multi_prio.isle | 4 + .../isle/isle/isle_examples/link/borrows.isle | 2 +- .../isle/isle/isle_examples/link/iflets.isle | 10 +- .../isle_examples/link/multi_constructor.isle | 15 + .../link/multi_constructor_main.rs | 71 + .../isle_examples/link/multi_extractor.isle | 14 + .../link/multi_extractor_main.rs | 50 + .../isle/isle/isle_examples/pass/test3.isle | 2 +- .../isle/isle_examples/pass/tutorial.isle | 6 +- .../isle/isle/isle_examples/run/iconst.isle | 8 +- .../isle_examples/run/let_shadowing_main.rs | 24 +- cranelift/isle/isle/src/ast.rs | 14 +- cranelift/isle/isle/src/codegen.rs | 1154 +++--- cranelift/isle/isle/src/compile.rs | 22 +- cranelift/isle/isle/src/error.rs | 356 +- cranelift/isle/isle/src/error_miette.rs | 65 - cranelift/isle/isle/src/ir.rs | 666 ---- cranelift/isle/isle/src/lexer.rs | 45 +- cranelift/isle/isle/src/lib.rs | 183 +- cranelift/isle/isle/src/overlap.rs | 137 + cranelift/isle/isle/src/parser.rs | 206 +- cranelift/isle/isle/src/sema.rs | 1082 ++++-- cranelift/isle/isle/src/serialize.rs | 846 +++++ cranelift/isle/isle/src/trie.rs | 370 -- cranelift/isle/isle/src/trie_again.rs | 683 ++++ cranelift/isle/isle/tests/run_tests.rs | 20 +- cranelift/isle/islec/Cargo.toml | 11 +- cranelift/isle/islec/src/main.rs | 33 +- cranelift/jit/Cargo.toml | 27 +- cranelift/jit/examples/jit-minimal.rs | 11 +- cranelift/jit/src/backend.rs | 222 +- cranelift/jit/src/compiled_blob.rs | 16 +- cranelift/jit/src/memory.rs | 136 +- cranelift/jit/tests/basic.rs | 9 +- cranelift/module/Cargo.toml | 10 +- cranelift/module/src/data_context.rs | 34 +- cranelift/module/src/lib.rs | 17 +- cranelift/module/src/module.rs | 177 +- cranelift/native/Cargo.toml | 11 +- cranelift/native/src/lib.rs | 57 +- cranelift/object/Cargo.toml | 20 +- cranelift/object/src/backend.rs | 202 +- cranelift/object/tests/basic.rs | 7 +- cranelift/preopt/Cargo.toml | 26 - cranelift/preopt/README.md | 1 - cranelift/preopt/src/constant_folding.rs | 257 -- cranelift/preopt/src/lib.rs | 46 - cranelift/reader/Cargo.toml | 11 +- cranelift/reader/src/heap_command.rs | 71 - cranelift/reader/src/isaspec.rs | 4 +- cranelift/reader/src/lexer.rs | 22 +- cranelift/reader/src/lib.rs | 92 +- cranelift/reader/src/parser.rs | 723 +--- cranelift/reader/src/run_command.rs | 22 +- cranelift/reader/src/sourcemap.rs | 23 +- cranelift/serde/Cargo.toml | 10 +- cranelift/src/bugpoint.rs | 160 +- cranelift/src/clif-util.rs | 0 cranelift/src/compile.rs | 109 +- cranelift/src/disasm.rs | 72 +- cranelift/src/interpret.rs | 6 +- cranelift/src/run.rs | 20 +- cranelift/src/souper_harvest.rs | 80 +- cranelift/src/utils.rs | 88 - cranelift/src/wasm.rs | 3 +- cranelift/tests/bugpoint_consts.clif | 4 +- cranelift/tests/bugpoint_consts_expected.clif | 16 +- cranelift/tests/bugpoint_test.clif | 294 +- cranelift/tests/bugpoint_test_expected.clif | 2 +- cranelift/tests/filetests.rs | 7 +- cranelift/umbrella/Cargo.toml | 8 +- cranelift/wasm/Cargo.toml | 25 +- cranelift/wasm/src/code_translator.rs | 819 +++-- .../wasm/src/code_translator/bounds_checks.rs | 413 +++ cranelift/wasm/src/environ/dummy.rs | 86 +- cranelift/wasm/src/environ/mod.rs | 4 +- cranelift/wasm/src/environ/spec.rs | 52 +- cranelift/wasm/src/func_translator.rs | 22 +- cranelift/wasm/src/heap.rs | 99 + cranelift/wasm/src/lib.rs | 8 +- cranelift/wasm/src/module_translator.rs | 17 +- cranelift/wasm/src/sections_translator.rs | 115 +- .../src/{state/func_state.rs => state.rs} | 23 +- cranelift/wasm/src/state/mod.rs | 14 - cranelift/wasm/src/state/module_state.rs | 75 - cranelift/wasm/src/translation_utils.rs | 27 - crates/asm-macros/Cargo.toml | 4 +- crates/asm-macros/src/lib.rs | 86 +- crates/bench-api/Cargo.toml | 29 +- crates/bench-api/src/lib.rs | 96 +- crates/c-api/CMakeLists.txt | 95 +- crates/c-api/Cargo.toml | 25 +- crates/c-api/include/wasi.h | 24 + crates/c-api/include/wasmtime.h | 17 + crates/c-api/include/wasmtime/config.h | 8 + crates/c-api/include/wasmtime/error.h | 18 + crates/c-api/include/wasmtime/func.h | 5 +- crates/c-api/include/wasmtime/linker.h | 2 + crates/c-api/include/wasmtime/store.h | 38 + crates/c-api/include/wasmtime/trap.h | 11 +- crates/c-api/include/wasmtime/val.h | 28 +- crates/c-api/macros/Cargo.toml | 4 +- crates/c-api/src/config.rs | 6 + crates/c-api/src/error.rs | 24 +- crates/c-api/src/func.rs | 71 +- crates/c-api/src/instance.rs | 13 +- crates/c-api/src/linker.rs | 5 +- crates/c-api/src/store.rs | 38 +- crates/c-api/src/trap.rs | 138 +- crates/c-api/src/vec.rs | 27 +- crates/c-api/src/wasi.rs | 158 +- crates/cache/Cargo.toml | 20 +- crates/cache/src/lib.rs | 3 +- crates/cli-flags/Cargo.toml | 14 +- crates/cli-flags/src/lib.rs | 112 +- crates/component-macro/Cargo.toml | 21 +- crates/component-macro/src/bindgen.rs | 213 ++ crates/component-macro/src/component.rs | 1199 +++++++ crates/component-macro/src/lib.rs | 1260 +------ .../component-macro/test-helpers/Cargo.toml | 13 + .../component-macro/test-helpers/src/lib.rs | 23 + crates/component-macro/tests/codegen.rs | 28 + crates/component-macro/tests/codegen/char.wit | 11 + .../tests/codegen/conventions.wit | 38 + .../tests/codegen/direct-import.wit | 3 + .../component-macro/tests/codegen/empty.wit | 1 + .../component-macro/tests/codegen/flags.wit | 53 + .../component-macro/tests/codegen/floats.wit | 11 + .../tests/codegen/function-new.wit | 3 + .../tests/codegen/integers.wit | 38 + .../component-macro/tests/codegen/lists.wit | 83 + .../tests/codegen/many-arguments.wit | 50 + .../tests/codegen/multi-return.wit | 12 + .../component-macro/tests/codegen/records.wit | 59 + .../tests/codegen/share-types.wit | 19 + .../tests/codegen/simple-functions.wit | 15 + .../tests/codegen/simple-lists.wit | 11 + .../tests/codegen/simple-wasi.wit | 15 + .../tests/codegen/small-anonymous.wit | 13 + .../tests/codegen/smoke-default.wit | 3 + .../tests/codegen/smoke-export.wit | 5 + .../component-macro/tests/codegen/smoke.wit | 5 + .../component-macro/tests/codegen/strings.wit | 10 + .../component-macro/tests/codegen/unions.wit | 64 + .../tests/codegen/use-paths.wit | 27 + .../tests/codegen/variants.wit | 145 + .../tests/codegen/worlds-with-types.wit | 14 + crates/component-util/Cargo.toml | 6 +- crates/component-util/src/lib.rs | 80 +- crates/cranelift/Cargo.toml | 33 +- crates/cranelift/src/builder.rs | 13 +- crates/cranelift/src/compiler.rs | 559 +-- crates/cranelift/src/compiler/component.rs | 455 ++- .../src/debug/transform/address_transform.rs | 15 +- .../src/debug/transform/expression.rs | 5 +- crates/cranelift/src/func_environ.rs | 221 +- crates/cranelift/src/lib.rs | 14 +- crates/cranelift/src/obj.rs | 159 +- crates/environ/Cargo.toml | 36 +- crates/environ/examples/factc.rs | 1 - crates/environ/fuzz/Cargo.toml | 13 +- .../fuzz/fuzz_targets/fact-valid-module.rs | 245 +- crates/environ/src/address_map.rs | 31 +- crates/environ/src/builtin.rs | 6 +- crates/environ/src/compilation.rs | 135 +- crates/environ/src/component.rs | 22 + crates/environ/src/component/compiler.rs | 59 +- crates/environ/src/component/dfg.rs | 52 + crates/environ/src/component/info.rs | 56 +- crates/environ/src/component/translate.rs | 104 +- .../environ/src/component/translate/adapt.rs | 60 +- .../environ/src/component/translate/inline.rs | 71 +- crates/environ/src/component/types.rs | 1174 ++++++- .../src/component/vmcomponent_offsets.rs | 59 +- crates/environ/src/fact.rs | 456 ++- crates/environ/src/fact/signature.rs | 261 +- crates/environ/src/fact/trampoline.rs | 2209 +++++++++--- crates/environ/src/fact/transcode.rs | 146 + crates/environ/src/fact/traps.rs | 4 + crates/environ/src/module.rs | 4 +- crates/environ/src/module_environ.rs | 139 +- crates/environ/src/obj.rs | 182 +- crates/environ/src/trap_encoding.rs | 110 +- crates/environ/src/vmoffsets.rs | 98 +- crates/fiber/Cargo.toml | 12 +- crates/fiber/src/lib.rs | 2 + crates/fiber/src/unix.rs | 6 +- crates/fiber/src/unix/aarch64.rs | 154 +- crates/fiber/src/unix/riscv64.rs | 158 + crates/fiber/src/unix/x86_64.rs | 5 +- crates/fuzzing/Cargo.toml | 40 +- crates/fuzzing/src/generators.rs | 10 +- .../src/generators/codegen_settings.rs | 52 + .../fuzzing/src/generators/component_types.rs | 85 +- crates/fuzzing/src/generators/config.rs | 242 +- .../instance_allocation_strategy.rs | 43 +- .../fuzzing/src/generators/instance_limits.rs | 49 - crates/fuzzing/src/generators/module.rs | 71 + .../fuzzing/src/generators/module_config.rs | 38 - .../fuzzing/src/generators/pooling_config.rs | 67 + .../src/generators/single_inst_module.rs | 652 +++- crates/fuzzing/src/generators/stacks.rs | 6 +- crates/fuzzing/src/generators/table_ops.rs | 6 +- crates/fuzzing/src/generators/value.rs | 309 ++ crates/fuzzing/src/oracles.rs | 699 ++-- crates/fuzzing/src/oracles/diff_spec.rs | 144 + crates/fuzzing/src/oracles/diff_v8.rs | 323 ++ crates/fuzzing/src/oracles/diff_wasmi.rs | 198 ++ crates/fuzzing/src/oracles/diff_wasmtime.rs | 224 ++ crates/fuzzing/src/oracles/dummy.rs | 7 +- crates/fuzzing/src/oracles/engine.rs | 227 ++ crates/fuzzing/src/oracles/stacks.rs | 20 +- crates/fuzzing/src/oracles/v8.rs | 336 -- .../fuzzing/wasm-spec-interpreter/Cargo.toml | 8 +- crates/fuzzing/wasm-spec-interpreter/build.rs | 2 +- .../wasm-spec-interpreter/ocaml/interpret.ml | 117 +- .../fuzzing/wasm-spec-interpreter/src/lib.rs | 22 +- .../wasm-spec-interpreter/src/with_library.rs | 318 +- .../src/without_library.rs | 36 +- .../wasm-spec-interpreter/tests/memory.wat | 12 + .../wasm-spec-interpreter/tests/shr_s.wat | 9 + crates/jit-debug/Cargo.toml | 12 +- crates/jit-icache-coherence/Cargo.toml | 31 + crates/jit-icache-coherence/src/lib.rs | 105 + crates/jit-icache-coherence/src/libc.rs | 149 + crates/jit-icache-coherence/src/win.rs | 45 + crates/jit/Cargo.toml | 33 +- crates/jit/src/code_memory.rs | 345 +- crates/jit/src/debug.rs | 1 + crates/jit/src/instantiate.rs | 784 ++--- crates/jit/src/lib.rs | 3 +- crates/jit/src/profiling/jitdump_linux.rs | 3 +- crates/jit/src/profiling/vtune.rs | 3 +- crates/jit/src/unwind.rs | 5 +- crates/jit/src/unwind/systemv.rs | 6 +- crates/jit/src/unwind/winx32.rs | 20 - crates/jit/src/unwind/winx64.rs | 8 +- crates/misc/component-fuzz-util/Cargo.toml | 8 +- crates/misc/component-fuzz-util/src/lib.rs | 689 ++-- crates/misc/component-macro-test/Cargo.toml | 4 +- crates/misc/component-test-util/Cargo.toml | 10 +- crates/misc/component-test-util/src/lib.rs | 41 +- crates/runtime/Cargo.toml | 41 +- crates/runtime/build.rs | 9 - crates/runtime/src/component.rs | 71 +- crates/runtime/src/component/transcode.rs | 451 +++ crates/runtime/src/cow.rs | 760 ++-- crates/runtime/src/cow_disabled.rs | 73 - crates/runtime/src/export.rs | 6 +- crates/runtime/src/instance.rs | 174 +- crates/runtime/src/instance/allocator.rs | 415 +-- .../runtime/src/instance/allocator/pooling.rs | 1534 ++++---- .../allocator/pooling/index_allocator.rs | 837 ++--- .../src/instance/allocator/pooling/unix.rs | 36 +- .../src/instance/allocator/pooling/windows.rs | 8 - crates/runtime/src/lib.rs | 54 +- crates/runtime/src/libcalls.rs | 235 +- crates/runtime/src/memory.rs | 298 +- crates/runtime/src/mmap.rs | 86 +- crates/runtime/src/mmap_vec.rs | 104 +- crates/runtime/src/parking_spot.rs | 443 +++ crates/runtime/src/table.rs | 32 +- crates/runtime/src/trampolines.rs | 3 + crates/runtime/src/trampolines/aarch64.rs | 5 +- crates/runtime/src/trampolines/riscv64.rs | 120 + crates/runtime/src/trampolines/x86_64.rs | 111 +- crates/runtime/src/traphandlers.rs | 341 +- crates/runtime/src/traphandlers/backtrace.rs | 24 +- .../src/traphandlers/backtrace/riscv64.rs | 21 + crates/runtime/src/traphandlers/macos.rs | 2 +- crates/runtime/src/traphandlers/unix.rs | 46 +- crates/runtime/src/traphandlers/windows.rs | 8 +- crates/runtime/src/vmcontext.rs | 60 +- .../src/vmcontext/vm_host_func_context.rs | 8 +- crates/test-programs/Cargo.toml | 22 +- crates/test-programs/tests/wasm_tests/main.rs | 4 +- .../tests/wasm_tests/runtime/cap_std_sync.rs | 2 +- .../tests/wasm_tests/runtime/tokio.rs | 2 +- crates/test-programs/wasi-tests/Cargo.lock | 6 +- crates/test-programs/wasi-tests/Cargo.toml | 7 +- .../wasi-tests/src/bin/close_preopen.rs | 10 +- .../wasi-tests/src/bin/dangling_symlink.rs | 6 +- .../wasi-tests/src/bin/directory_seek.rs | 4 +- .../wasi-tests/src/bin/fd_filestat_get.rs | 1 - .../wasi-tests/src/bin/file_seek_tell.rs | 3 +- .../wasi-tests/src/bin/interesting_paths.rs | 15 +- .../wasi-tests/src/bin/nofollow_errors.rs | 15 +- .../wasi-tests/src/bin/path_filestat.rs | 9 +- .../wasi-tests/src/bin/path_link.rs | 21 +- .../src/bin/path_open_create_existing.rs | 3 +- .../src/bin/path_open_dirfd_not_dir.rs | 3 +- .../wasi-tests/src/bin/path_open_missing.rs | 3 +- .../src/bin/path_open_read_without_rights.rs | 6 +- .../wasi-tests/src/bin/path_rename.rs | 22 +- .../bin/path_rename_file_trailing_slashes.rs | 15 +- .../src/bin/path_symlink_trailing_slashes.rs | 15 +- .../wasi-tests/src/bin/poll_oneoff_files.rs | 29 +- .../wasi-tests/src/bin/poll_oneoff_stdio.rs | 18 +- .../wasi-tests/src/bin/readlink.rs | 2 +- .../bin/remove_directory_trailing_slashes.rs | 6 +- .../src/bin/remove_nonempty_directory.rs | 3 +- .../wasi-tests/src/bin/renumber.rs | 4 +- .../wasi-tests/src/bin/symlink_loop.rs | 3 +- .../wasi-tests/src/bin/truncation_rights.rs | 5 +- .../src/bin/unlink_file_trailing_slashes.rs | 9 +- crates/test-programs/wasi-tests/src/lib.rs | 6 +- crates/types/Cargo.toml | 12 +- crates/types/src/lib.rs | 22 +- crates/wasi-common/Cargo.toml | 28 +- crates/wasi-common/README.md | 9 +- crates/wasi-common/cap-std-sync/Cargo.toml | 39 +- crates/wasi-common/cap-std-sync/src/dir.rs | 34 +- crates/wasi-common/cap-std-sync/src/file.rs | 61 +- crates/wasi-common/cap-std-sync/src/lib.rs | 15 +- crates/wasi-common/cap-std-sync/src/net.rs | 146 +- .../cap-std-sync/src/sched/unix.rs | 4 +- .../cap-std-sync/src/sched/windows.rs | 25 +- crates/wasi-common/cap-std-sync/src/stdio.rs | 37 +- crates/wasi-common/src/ctx.rs | 85 +- crates/wasi-common/src/dir.rs | 193 +- crates/wasi-common/src/error.rs | 158 +- crates/wasi-common/src/file.rs | 80 +- crates/wasi-common/src/lib.rs | 2 +- crates/wasi-common/src/pipe.rs | 10 +- crates/wasi-common/src/snapshots/preview_0.rs | 340 +- crates/wasi-common/src/snapshots/preview_1.rs | 739 ++-- .../src/snapshots/preview_1/error.rs | 255 ++ crates/wasi-common/src/table.rs | 88 +- crates/wasi-common/tokio/Cargo.toml | 25 +- crates/wasi-common/tokio/src/file.rs | 46 +- crates/wasi-common/tokio/src/lib.rs | 10 +- crates/wasi-common/tokio/src/sched/unix.rs | 15 +- crates/wasi-common/tokio/tests/poll_oneoff.rs | 2 +- crates/wasi-crypto/Cargo.toml | 12 +- .../wiggle_interfaces/asymmetric_common.rs | 40 +- .../src/wiggle_interfaces/common.rs | 37 +- .../src/wiggle_interfaces/key_exchange.rs | 3 +- .../src/wiggle_interfaces/signatures.rs | 17 +- .../src/wiggle_interfaces/symmetric.rs | 98 +- crates/wasi-nn/Cargo.toml | 14 +- .../classification-example/Cargo.toml | 2 +- crates/wasi-nn/src/api.rs | 6 +- crates/wasi-nn/src/ctx.rs | 19 +- crates/wasi-nn/src/impl.rs | 19 +- crates/wasi-nn/src/openvino.rs | 24 +- crates/wasi-nn/src/witx.rs | 3 +- crates/wasi-threads/Cargo.toml | 23 + crates/wasi-threads/README.md | 12 + crates/wasi-threads/src/lib.rs | 159 + crates/wasi/Cargo.toml | 20 +- crates/wasi/src/lib.rs | 54 +- crates/wasmtime/Cargo.toml | 62 +- crates/wasmtime/src/code.rs | 103 + crates/wasmtime/src/component/component.rs | 658 ++-- crates/wasmtime/src/component/func.rs | 289 +- crates/wasmtime/src/component/func/host.rs | 302 +- crates/wasmtime/src/component/func/options.rs | 16 +- crates/wasmtime/src/component/func/typed.rs | 585 +++- crates/wasmtime/src/component/instance.rs | 134 +- crates/wasmtime/src/component/linker.rs | 97 +- crates/wasmtime/src/component/matching.rs | 39 +- crates/wasmtime/src/component/mod.rs | 272 +- crates/wasmtime/src/component/storage.rs | 43 + crates/wasmtime/src/component/types.rs | 339 +- crates/wasmtime/src/component/values.rs | 587 ++-- crates/wasmtime/src/config.rs | 492 ++- crates/wasmtime/src/engine.rs | 133 +- crates/wasmtime/src/engine/serialization.rs | 603 ++++ crates/wasmtime/src/externals.rs | 27 +- crates/wasmtime/src/func.rs | 239 +- crates/wasmtime/src/func/typed.rs | 30 +- crates/wasmtime/src/instance.rs | 90 +- crates/wasmtime/src/lib.rs | 21 +- crates/wasmtime/src/linker.rs | 265 +- crates/wasmtime/src/memory.rs | 131 +- crates/wasmtime/src/module.rs | 515 +-- crates/wasmtime/src/module/registry.rs | 302 +- crates/wasmtime/src/module/serialization.rs | 746 ---- crates/wasmtime/src/store.rs | 88 +- crates/wasmtime/src/trampoline.rs | 6 +- crates/wasmtime/src/trampoline/func.rs | 35 +- crates/wasmtime/src/trampoline/global.rs | 4 +- crates/wasmtime/src/trampoline/memory.rs | 103 +- crates/wasmtime/src/trap.rs | 709 ++-- crates/wasmtime/src/types/matching.rs | 68 +- crates/wast/Cargo.toml | 14 +- crates/wast/src/component.rs | 65 +- crates/wast/src/spectest.rs | 32 +- crates/wast/src/wast.rs | 82 +- crates/wiggle/Cargo.toml | 29 +- crates/wiggle/generate/Cargo.toml | 8 +- .../wiggle/generate/src/codegen_settings.rs | 66 +- crates/wiggle/generate/src/config.rs | 192 +- crates/wiggle/generate/src/funcs.rs | 156 +- crates/wiggle/generate/src/lib.rs | 50 +- crates/wiggle/generate/src/module_trait.rs | 35 +- crates/wiggle/generate/src/names.rs | 324 +- crates/wiggle/generate/src/types/error.rs | 53 + crates/wiggle/generate/src/types/flags.rs | 43 +- crates/wiggle/generate/src/types/handle.rs | 34 +- crates/wiggle/generate/src/types/mod.rs | 59 +- crates/wiggle/generate/src/types/record.rs | 77 +- crates/wiggle/generate/src/types/variant.rs | 74 +- crates/wiggle/generate/src/wasmtime.rs | 66 +- crates/wiggle/macro/Cargo.toml | 7 +- crates/wiggle/macro/src/lib.rs | 25 +- crates/wiggle/src/guest_type.rs | 159 +- crates/wiggle/src/lib.rs | 693 ++-- crates/wiggle/src/wasmtime.rs | 61 +- crates/wiggle/test-helpers/Cargo.toml | 13 +- .../wiggle/test-helpers/examples/tracing.rs | 18 +- crates/wiggle/test-helpers/src/lib.rs | 13 +- crates/wiggle/tests/atoms.rs | 10 +- crates/wiggle/tests/atoms_async.rs | 10 +- crates/wiggle/tests/errors.rs | 60 +- crates/wiggle/tests/flags.rs | 5 +- crates/wiggle/tests/handles.rs | 13 +- crates/wiggle/tests/ints.rs | 5 +- crates/wiggle/tests/lists.rs | 32 +- crates/wiggle/tests/pointers.rs | 5 +- crates/wiggle/tests/records.rs | 38 +- crates/wiggle/tests/strings.rs | 35 +- crates/wiggle/tests/tracing.rs | 16 + crates/wiggle/tests/variant.rs | 10 +- crates/wiggle/tests/wasi.rs | 7 +- crates/wiggle/tests/wasmtime_sync.rs | 2 +- crates/winch/Cargo.toml | 21 + {cranelift/preopt => crates/winch}/LICENSE | 1 - crates/winch/src/builder.rs | 72 + crates/winch/src/compiler.rs | 91 + crates/winch/src/lib.rs | 3 + crates/wit-bindgen/Cargo.toml | 14 + crates/wit-bindgen/src/lib.rs | 1368 ++++++++ crates/wit-bindgen/src/rust.rs | 427 +++ crates/wit-bindgen/src/source.rs | 130 + crates/wit-bindgen/src/types.rs | 178 + deny.toml | 7 +- docs/SUMMARY.md | 2 + docs/contributing-architecture.md | 2 +- docs/contributing-building.md | 112 - docs/contributing-cross-compiling.md | 100 + docs/examples-markdown.md | 10 +- docs/lang-elixir.md | 43 + docs/lang-python.md | 8 +- docs/lang-ruby.md | 63 + docs/lang-rust.md | 6 +- docs/lang.md | 2 + docs/stability-release.md | 1 + docs/stability-tiers.md | 2 + docs/stability-wasm-proposals-support.md | 2 +- docs/wasm-rust.md | 117 +- docs/wasm-wat.md | 2 +- examples/epochs.rs | 2 +- examples/externref.rs | 2 +- examples/fib-debug/main.rs | 2 +- examples/fuel.c | 5 + examples/fuel.rs | 5 +- examples/gcd.rs | 2 +- examples/hello.rs | 2 +- examples/interrupt.rs | 6 +- examples/linking.rs | 2 +- examples/memory.rs | 8 +- examples/multi.rs | 3 +- examples/multimemory.rs | 12 +- examples/serialize.rs | 2 +- examples/threads.rs | 2 +- examples/tokio/main.rs | 2 +- examples/wasi/main.c | 1 + examples/wasi/main.rs | 11 +- fuzz/Cargo.toml | 61 +- fuzz/README.md | 18 +- fuzz/build.rs | 38 +- fuzz/fuzz_targets/component_api.rs | 2 +- fuzz/fuzz_targets/cranelift-fuzzgen.rs | 195 +- fuzz/fuzz_targets/cranelift-icache.rs | 127 + fuzz/fuzz_targets/differential.rs | 276 +- fuzz/fuzz_targets/differential_spec.rs | 47 - fuzz/fuzz_targets/differential_v8.rs | 50 - fuzz/fuzz_targets/differential_wasmi.rs | 20 - fuzz/fuzz_targets/instantiate-many.rs | 8 +- fuzz/fuzz_targets/instantiate.rs | 68 +- scripts/publish.rs | 112 +- src/commands/compile.rs | 22 +- src/commands/run.rs | 173 +- src/commands/settings.rs | 171 +- src/commands/wast.rs | 2 +- supply-chain/audits.toml | 1389 +++++++- supply-chain/config.toml | 305 +- supply-chain/imports.lock | 447 ++- tests/all/async_functions.rs | 60 +- tests/all/call_hook.rs | 78 +- tests/all/cli_tests.rs | 177 +- tests/all/cli_tests/threads.wat | 62 + tests/all/component_model.rs | 12 +- tests/all/component_model/aot.rs | 99 + tests/all/component_model/async.rs | 88 + tests/all/component_model/bindgen.rs | 115 + tests/all/component_model/bindgen/results.rs | 635 ++++ tests/all/component_model/dynamic.rs | 141 +- tests/all/component_model/func.rs | 690 ++-- tests/all/component_model/import.rs | 266 +- tests/all/component_model/macros.rs | 117 +- tests/all/component_model/nested.rs | 15 +- tests/all/component_model/post_return.rs | 41 +- tests/all/component_model/strings.rs | 578 +++ tests/all/custom_signal_handler.rs | 22 +- tests/all/externals.rs | 4 +- tests/all/fuel.rs | 63 +- tests/all/func.rs | 164 +- tests/all/gc.rs | 8 +- tests/all/host_funcs.rs | 73 +- tests/all/iloop.rs | 40 +- tests/all/import_calling_export.rs | 7 +- tests/all/import_indexes.rs | 2 +- tests/all/instance.rs | 14 +- tests/all/limits.rs | 53 +- tests/all/linker.rs | 73 +- tests/all/main.rs | 1 + tests/all/memory.rs | 119 +- tests/all/module.rs | 54 +- tests/all/module_serialize.rs | 16 +- tests/all/pooling_allocator.rs | 417 ++- tests/all/relocs.rs | 6 +- tests/all/stack_overflow.rs | 45 +- tests/all/threads.rs | 27 +- tests/all/traps.rs | 664 +++- tests/all/wait_notify.rs | 120 + tests/all/wast.rs | 54 +- tests/host_segfault.rs | 61 +- .../component-model/adapter.wast | 12 +- .../misc_testsuite/component-model/fused.wast | 282 +- .../component-model/import.wast | 15 + .../component-model/instance.wast | 8 +- .../component-model/linking.wast | 4 +- .../component-model/nested.wast | 12 +- .../component-model/simple.wast | 8 +- .../component-model/strings.wast | 108 + .../misc_testsuite/component-model/types.wast | 245 +- tests/misc_testsuite/issue4840.wast | 16 + tests/misc_testsuite/issue4857.wast | 10 + tests/misc_testsuite/issue4890.wast | 12 + tests/misc_testsuite/simd/issue4807.wast | 8 + .../threads/atomics_notify.wast | 18 + .../threads/atomics_wait_address.wast | 97 +- .../threads/load-store-alignment.wast | 48 +- tests/spec_testsuite | 2 +- winch/Cargo.toml | 33 + winch/codegen/Cargo.toml | 28 + winch/codegen/LICENSE | 219 ++ winch/codegen/src/abi/local.rs | 68 + winch/codegen/src/abi/mod.rs | 149 + winch/codegen/src/codegen.rs | 232 ++ winch/codegen/src/frame/mod.rs | 144 + winch/codegen/src/isa/aarch64/abi.rs | 210 ++ winch/codegen/src/isa/aarch64/address.rs | 144 + winch/codegen/src/isa/aarch64/asm.rs | 244 ++ winch/codegen/src/isa/aarch64/masm.rs | 188 + winch/codegen/src/isa/aarch64/mod.rs | 91 + winch/codegen/src/isa/aarch64/regs.rs | 137 + winch/codegen/src/isa/mod.rs | 122 + winch/codegen/src/isa/reg.rs | 51 + winch/codegen/src/isa/x64/abi.rs | 236 ++ winch/codegen/src/isa/x64/address.rs | 17 + winch/codegen/src/isa/x64/asm.rs | 343 ++ winch/codegen/src/isa/x64/masm.rs | 194 ++ winch/codegen/src/isa/x64/mod.rs | 97 + winch/codegen/src/isa/x64/regs.rs | 144 + winch/codegen/src/lib.rs | 18 + winch/codegen/src/masm.rs | 152 + winch/codegen/src/regalloc.rs | 145 + winch/codegen/src/regset.rs | 88 + winch/codegen/src/stack.rs | 207 ++ winch/codegen/src/visitor.rs | 144 + winch/docs/testing.md | 63 + winch/filetests/Cargo.toml | 22 + winch/filetests/build.rs | 4 + .../filetests/aarch64/i32_add/const.wat | 17 + .../filetests/aarch64/i32_add/locals.wat | 39 + .../filetests/aarch64/i32_add/max.wat | 16 + .../filetests/aarch64/i32_add/max_one.wat | 18 + .../filetests/aarch64/i32_add/mixed.wat | 17 + .../filetests/aarch64/i32_add/params.wat | 24 + .../filetests/aarch64/i32_add/signed.wat | 18 + .../aarch64/i32_add/unsigned_with_zero.wat | 17 + .../filetests/aarch64/i64_add/const.wat | 17 + .../filetests/aarch64/i64_add/locals.wat | 40 + .../filetests/aarch64/i64_add/max.wat | 17 + .../filetests/aarch64/i64_add/max_one.wat | 18 + .../filetests/aarch64/i64_add/mixed.wat | 17 + .../filetests/aarch64/i64_add/params.wat | 24 + .../filetests/aarch64/i64_add/signed.wat | 18 + .../aarch64/i64_add/unsigned_with_zero.wat | 17 + .../filetests/filetests/x64/i32_add/const.wat | 15 + .../filetests/x64/i32_add/locals.wat | 33 + winch/filetests/filetests/x64/i32_add/max.wat | 14 + .../filetests/x64/i32_add/max_one.wat | 15 + .../filetests/filetests/x64/i32_add/mixed.wat | 15 + .../filetests/x64/i32_add/params.wat | 21 + .../filetests/x64/i32_add/signed.wat | 15 + .../x64/i32_add/unsigned_with_zero.wat | 15 + .../filetests/filetests/x64/i32_mul/const.wat | 15 + .../filetests/x64/i32_mul/locals.wat | 33 + winch/filetests/filetests/x64/i32_mul/max.wat | 14 + .../filetests/x64/i32_mul/max_one.wat | 15 + .../filetests/filetests/x64/i32_mul/mixed.wat | 15 + .../filetests/x64/i32_mul/params.wat | 21 + .../filetests/x64/i32_mul/signed.wat | 15 + .../x64/i32_mul/unsigned_with_zero.wat | 15 + .../filetests/filetests/x64/i32_sub/const.wat | 15 + .../filetests/x64/i32_sub/locals.wat | 33 + winch/filetests/filetests/x64/i32_sub/max.wat | 14 + .../filetests/x64/i32_sub/max_one.wat | 15 + .../filetests/filetests/x64/i32_sub/mixed.wat | 15 + .../filetests/x64/i32_sub/params.wat | 21 + .../filetests/x64/i32_sub/signed.wat | 15 + .../x64/i32_sub/unsigned_with_zero.wat | 15 + .../filetests/filetests/x64/i64_add/const.wat | 15 + .../filetests/x64/i64_add/locals.wat | 35 + winch/filetests/filetests/x64/i64_add/max.wat | 16 + .../filetests/x64/i64_add/max_one.wat | 16 + .../filetests/filetests/x64/i64_add/mixed.wat | 15 + .../filetests/x64/i64_add/params.wat | 21 + .../filetests/x64/i64_add/signed.wat | 15 + .../x64/i64_add/unsigned_with_zero.wat | 15 + .../filetests/filetests/x64/i64_mul/const.wat | 15 + .../filetests/x64/i64_mul/locals.wat | 35 + winch/filetests/filetests/x64/i64_mul/max.wat | 15 + .../filetests/x64/i64_mul/max_one.wat | 16 + .../filetests/filetests/x64/i64_mul/mixed.wat | 15 + .../filetests/x64/i64_mul/params.wat | 21 + .../filetests/x64/i64_mul/signed.wat | 15 + .../x64/i64_mul/unsigned_with_zero.wat | 15 + .../filetests/filetests/x64/i64_sub/const.wat | 15 + .../filetests/x64/i64_sub/locals.wat | 35 + winch/filetests/filetests/x64/i64_sub/max.wat | 15 + .../filetests/x64/i64_sub/max_one.wat | 16 + .../filetests/filetests/x64/i64_sub/mixed.wat | 15 + .../filetests/x64/i64_sub/params.wat | 21 + .../filetests/x64/i64_sub/signed.wat | 15 + .../x64/i64_sub/unsigned_with_zero.wat | 15 + winch/filetests/src/disasm.rs | 71 + winch/filetests/src/lib.rs | 165 + winch/src/compile.rs | 73 + winch/src/filetests.rs | 25 + winch/src/main.rs | 21 + winch/test-macros/Cargo.toml | 18 + winch/test-macros/src/lib.rs | 82 + 1998 files changed, 211676 insertions(+), 58884 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/fuzzbug.md create mode 100644 .github/actions/github-release/package-lock.json delete mode 100644 .github/actions/install-rust/main.js create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/performance.yml create mode 100644 benches/wasi.rs create mode 100644 benches/wasi/.gitignore create mode 100644 benches/wasi/get-current-time.wat create mode 100644 benches/wasi/open-file.wat create mode 100644 benches/wasi/read-arguments.wat create mode 100644 benches/wasi/read-dir.wat create mode 100644 benches/wasi/read-environment.wat create mode 100644 benches/wasi/read-file.wat create mode 100755 ci/build-src-tarball.sh create mode 100644 ci/docker/riscv64gc-linux/Dockerfile create mode 100644 cranelift/codegen/meta/src/isa/riscv64.rs mode change 100644 => 100755 cranelift/codegen/meta/src/shared/instructions.rs create mode 100644 cranelift/codegen/src/ctxhash.rs create mode 100644 cranelift/codegen/src/egraph.rs create mode 100644 cranelift/codegen/src/egraph/cost.rs create mode 100644 cranelift/codegen/src/egraph/domtree.rs create mode 100644 cranelift/codegen/src/egraph/elaborate.rs create mode 100644 cranelift/codegen/src/incremental_cache.rs delete mode 100644 cranelift/codegen/src/ir/heap.rs create mode 100644 cranelift/codegen/src/ir/known_symbol.rs delete mode 100644 cranelift/codegen/src/isa/aarch64/lower_inst.rs create mode 100644 cranelift/codegen/src/isa/riscv64/abi.rs create mode 100644 cranelift/codegen/src/isa/riscv64/inst.isle create mode 100644 cranelift/codegen/src/isa/riscv64/inst/args.rs create mode 100644 cranelift/codegen/src/isa/riscv64/inst/emit.rs create mode 100644 cranelift/codegen/src/isa/riscv64/inst/emit_tests.rs create mode 100644 cranelift/codegen/src/isa/riscv64/inst/imms.rs create mode 100644 cranelift/codegen/src/isa/riscv64/inst/mod.rs create mode 100644 cranelift/codegen/src/isa/riscv64/inst/regs.rs create mode 100644 cranelift/codegen/src/isa/riscv64/inst/unwind.rs create mode 100644 cranelift/codegen/src/isa/riscv64/inst/unwind/systemv.rs create mode 100644 cranelift/codegen/src/isa/riscv64/lower.isle create mode 100644 cranelift/codegen/src/isa/riscv64/lower.rs create mode 100644 cranelift/codegen/src/isa/riscv64/lower/isle.rs create mode 100644 cranelift/codegen/src/isa/riscv64/lower/isle/generated_code.rs create mode 100644 cranelift/codegen/src/isa/riscv64/mod.rs create mode 100644 cranelift/codegen/src/isa/riscv64/settings.rs create mode 100644 cranelift/codegen/src/isle_prelude.rs delete mode 100644 cranelift/codegen/src/legalizer/heap.rs delete mode 100644 cranelift/codegen/src/machinst/abi_impl.rs create mode 100644 cranelift/codegen/src/opts.rs create mode 100644 cranelift/codegen/src/opts/algebraic.isle create mode 100644 cranelift/codegen/src/opts/cprop.isle create mode 100644 cranelift/codegen/src/opts/generated_code.rs create mode 100644 cranelift/codegen/src/prelude_lower.isle create mode 100644 cranelift/codegen/src/prelude_opt.isle create mode 100644 cranelift/codegen/src/unionfind.rs delete mode 100644 cranelift/codegen/src/verifier/flags.rs delete mode 100644 cranelift/docs/heap.dot delete mode 100644 cranelift/docs/heap.svg create mode 100644 cranelift/filetests/README.md create mode 100644 cranelift/filetests/filetests/egraph/algebraic.clif create mode 100644 cranelift/filetests/filetests/egraph/alias_analysis.clif create mode 100644 cranelift/filetests/filetests/egraph/basic-gvn.clif create mode 100644 cranelift/filetests/filetests/egraph/cprop.clif create mode 100644 cranelift/filetests/filetests/egraph/i128-opts.clif create mode 100644 cranelift/filetests/filetests/egraph/isplit.clif create mode 100644 cranelift/filetests/filetests/egraph/issue-5405.clif create mode 100644 cranelift/filetests/filetests/egraph/issue-5417.clif create mode 100644 cranelift/filetests/filetests/egraph/issue-5437.clif create mode 100644 cranelift/filetests/filetests/egraph/issue-5716.clif create mode 100644 cranelift/filetests/filetests/egraph/licm.clif create mode 100644 cranelift/filetests/filetests/egraph/misc.clif create mode 100644 cranelift/filetests/filetests/egraph/mul-pow-2.clif create mode 100644 cranelift/filetests/filetests/egraph/multivalue.clif create mode 100644 cranelift/filetests/filetests/egraph/not_a_load.clif create mode 100644 cranelift/filetests/filetests/egraph/remat.clif create mode 100644 cranelift/filetests/filetests/egraph/select.clif create mode 100644 cranelift/filetests/filetests/egraph/vselect.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/atomic-cas.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/bitcast.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/bitopts-optimized.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/bmask.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/bswap.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/bti.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/fcvt.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/fp_sp_pc-pauth.clif delete mode 100644 cranelift/filetests/filetests/isa/aarch64/heap_addr.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/icmp-const.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/inline-probestack.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/select.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/simd-arithmetic.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/simd-bitwise-compile.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/simd-comparison-legalize.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/simd-lane-access-compile.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/simd-logical-compile.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/symbol-value-pic.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/uadd_overflow_trap.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/vhigh_bits.clif create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/aarch64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/amodes.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/arithmetic.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/atomic-rmw.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/atomic_load.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/atomic_store.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/bitops-optimized.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/bitops.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/call-indirect.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/call.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/condbr.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/condops.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/constants.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/extend-op.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/fcmp.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/fcvt-small.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/float.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/i128-bmask.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/iabs-zbb.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/iabs.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/iconst-icmp-small.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/issue-5583.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/multivalue-ret.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/narrow-arithmetic.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/prologue.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/reduce.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/reftypes.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/shift-op.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/shift-rotate.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/stack-limit.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/stack.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/symbol-value.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/traps.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/uadd_overflow_trap.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/uextend-sextend.clif create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/riscv64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/bitcast.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/bitops-optimized.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/bswap.clif delete mode 100644 cranelift/filetests/filetests/isa/s390x/heap_addr.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/issue-5425.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/minmax.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/tls_elf.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/uadd_overflow_trap.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/vec-abi.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/vec-bitcast.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/vec-constants-le-lane.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/vec-conversions-le-lane.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/vec-lane-le-lane-arch13.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/vec-lane-le-lane.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/vec-permute-le-lane.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/vecmem-le-lane-arch13.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/vecmem-le-lane.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/s390x/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat delete mode 100644 cranelift/filetests/filetests/isa/x64/b1.clif create mode 100644 cranelift/filetests/filetests/isa/x64/band_not_bmi1.clif delete mode 100644 cranelift/filetests/filetests/isa/x64/bextend.clif create mode 100644 cranelift/filetests/filetests/isa/x64/bitcast.clif create mode 100644 cranelift/filetests/filetests/isa/x64/bmask.clif create mode 100644 cranelift/filetests/filetests/isa/x64/bswap.clif create mode 100644 cranelift/filetests/filetests/isa/x64/ceil-libcall.clif create mode 100644 cranelift/filetests/filetests/isa/x64/ceil.clif create mode 100644 cranelift/filetests/filetests/isa/x64/conditional-values.clif create mode 100644 cranelift/filetests/filetests/isa/x64/extractlane.clif create mode 100644 cranelift/filetests/filetests/isa/x64/fabs.clif create mode 100644 cranelift/filetests/filetests/isa/x64/fcopysign.clif create mode 100644 cranelift/filetests/filetests/isa/x64/fcvt-simd.clif create mode 100644 cranelift/filetests/filetests/isa/x64/fcvt.clif create mode 100644 cranelift/filetests/filetests/isa/x64/floor-libcall.clif create mode 100644 cranelift/filetests/filetests/isa/x64/floor.clif create mode 100644 cranelift/filetests/filetests/isa/x64/fma-call.clif create mode 100644 cranelift/filetests/filetests/isa/x64/fma-inst.clif create mode 100644 cranelift/filetests/filetests/isa/x64/fneg.clif delete mode 100644 cranelift/filetests/filetests/isa/x64/heap.clif create mode 100644 cranelift/filetests/filetests/isa/x64/iabs.clif create mode 100644 cranelift/filetests/filetests/isa/x64/inline-probestack-large.clif create mode 100644 cranelift/filetests/filetests/isa/x64/inline-probestack.clif create mode 100644 cranelift/filetests/filetests/isa/x64/ishl.clif create mode 100644 cranelift/filetests/filetests/isa/x64/narrowing.clif create mode 100644 cranelift/filetests/filetests/isa/x64/nearest-libcall.clif create mode 100644 cranelift/filetests/filetests/isa/x64/nearest.clif create mode 100644 cranelift/filetests/filetests/isa/x64/sdiv.clif create mode 100644 cranelift/filetests/filetests/isa/x64/select.clif create mode 100644 cranelift/filetests/filetests/isa/x64/sextend.clif create mode 100644 cranelift/filetests/filetests/isa/x64/shuffle-avx512.clif create mode 100644 cranelift/filetests/filetests/isa/x64/simd-bitselect.clif create mode 100644 cranelift/filetests/filetests/isa/x64/simd-pairwise-add.clif create mode 100644 cranelift/filetests/filetests/isa/x64/simd-widen-mul.clif create mode 100644 cranelift/filetests/filetests/isa/x64/smulhi.clif create mode 100644 cranelift/filetests/filetests/isa/x64/sqmul_round_sat.clif create mode 100644 cranelift/filetests/filetests/isa/x64/srem.clif create mode 100644 cranelift/filetests/filetests/isa/x64/sshr.clif create mode 100644 cranelift/filetests/filetests/isa/x64/tls_coff.clif create mode 100644 cranelift/filetests/filetests/isa/x64/trunc-libcall.clif create mode 100644 cranelift/filetests/filetests/isa/x64/trunc.clif create mode 100644 cranelift/filetests/filetests/isa/x64/uadd_overflow_trap.clif create mode 100644 cranelift/filetests/filetests/isa/x64/udiv.clif create mode 100644 cranelift/filetests/filetests/isa/x64/udivrem.clif create mode 100644 cranelift/filetests/filetests/isa/x64/umulhi.clif delete mode 100644 cranelift/filetests/filetests/isa/x64/unused_jt_unreachable_block.clif create mode 100644 cranelift/filetests/filetests/isa/x64/urem.clif create mode 100644 cranelift/filetests/filetests/isa/x64/ushr.clif create mode 100644 cranelift/filetests/filetests/isa/x64/uunarrow.clif create mode 100644 cranelift/filetests/filetests/isa/x64/vhigh_bits.clif create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/wasm/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/isa/x64/widen-high-bug.clif create mode 100644 cranelift/filetests/filetests/isa/x64/widening.clif create mode 100644 cranelift/filetests/filetests/legalizer/conditional-traps.clif delete mode 100644 cranelift/filetests/filetests/preopt/branch.clif delete mode 100644 cranelift/filetests/filetests/preopt/constant_fold.clif delete mode 100644 cranelift/filetests/filetests/preopt/numerical.clif create mode 100644 cranelift/filetests/filetests/runtests/atomic-load-store.clif delete mode 100644 cranelift/filetests/filetests/runtests/bextend.clif delete mode 100644 cranelift/filetests/filetests/runtests/bint.clif create mode 100644 cranelift/filetests/filetests/runtests/bitcast-ref64.clif create mode 100644 cranelift/filetests/filetests/runtests/bitcast-same-type.clif create mode 100644 cranelift/filetests/filetests/runtests/bitcast.clif create mode 100644 cranelift/filetests/filetests/runtests/bnot.clif delete mode 100644 cranelift/filetests/filetests/runtests/br_icmp.clif delete mode 100644 cranelift/filetests/filetests/runtests/br_icmp_overflow.clif delete mode 100644 cranelift/filetests/filetests/runtests/breduce.clif create mode 100644 cranelift/filetests/filetests/runtests/brif.clif create mode 100644 cranelift/filetests/filetests/runtests/bswap.clif create mode 100644 cranelift/filetests/filetests/runtests/call.clif create mode 100644 cranelift/filetests/filetests/runtests/call_indirect.clif create mode 100644 cranelift/filetests/filetests/runtests/call_libcall.clif create mode 100644 cranelift/filetests/filetests/runtests/conversion.clif delete mode 100644 cranelift/filetests/filetests/runtests/conversions.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-eq.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-ge.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-gt.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-le.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-lt.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-ne.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-one.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-ord.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-ueq.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-uge.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-ugt.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-ule.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-ult.clif create mode 100644 cranelift/filetests/filetests/runtests/fcmp-uno.clif delete mode 100644 cranelift/filetests/filetests/runtests/fcmp.clif create mode 100644 cranelift/filetests/filetests/runtests/fcvt-sat-small.clif create mode 100644 cranelift/filetests/filetests/runtests/fdemote.clif create mode 100644 cranelift/filetests/filetests/runtests/fence.clif create mode 100644 cranelift/filetests/filetests/runtests/float-bitops.clif delete mode 100644 cranelift/filetests/filetests/runtests/fma-interpreter.clif create mode 100644 cranelift/filetests/filetests/runtests/fpromote.clif delete mode 100644 cranelift/filetests/filetests/runtests/global_value.clif delete mode 100644 cranelift/filetests/filetests/runtests/heap.clif delete mode 100644 cranelift/filetests/filetests/runtests/i128-bextend.clif delete mode 100644 cranelift/filetests/filetests/runtests/i128-bint.clif create mode 100644 cranelift/filetests/filetests/runtests/i128-bnot.clif delete mode 100644 cranelift/filetests/filetests/runtests/i128-breduce.clif delete mode 100644 cranelift/filetests/filetests/runtests/i128-bricmp-overflow.clif delete mode 100644 cranelift/filetests/filetests/runtests/i128-bricmp.clif create mode 100644 cranelift/filetests/filetests/runtests/i128-bswap.clif create mode 100644 cranelift/filetests/filetests/runtests/i128-call.clif delete mode 100644 cranelift/filetests/filetests/runtests/i128-const.clif create mode 100644 cranelift/filetests/filetests/runtests/i128-conversion.clif create mode 100644 cranelift/filetests/filetests/runtests/i128-iabs.clif create mode 100644 cranelift/filetests/filetests/runtests/i128-iaddcout.clif delete mode 100644 cranelift/filetests/filetests/runtests/i128-icmp-overflow.clif create mode 100644 cranelift/filetests/filetests/runtests/i128-ineg.clif create mode 100644 cranelift/filetests/filetests/runtests/i128-isubbout.clif delete mode 100644 cranelift/filetests/filetests/runtests/i128-shifts-small-types.clif create mode 100644 cranelift/filetests/filetests/runtests/iaddcout-i16.clif create mode 100644 cranelift/filetests/filetests/runtests/iaddcout-i32.clif create mode 100644 cranelift/filetests/filetests/runtests/iaddcout-i64.clif create mode 100644 cranelift/filetests/filetests/runtests/iaddcout-i8.clif delete mode 100644 cranelift/filetests/filetests/runtests/iaddcout.clif delete mode 100644 cranelift/filetests/filetests/runtests/icmp-nof.clif delete mode 100644 cranelift/filetests/filetests/runtests/icmp-of.clif create mode 100644 cranelift/filetests/filetests/runtests/ineg.clif create mode 100644 cranelift/filetests/filetests/runtests/inline-probestack.clif create mode 100644 cranelift/filetests/filetests/runtests/issue-5498.clif create mode 100644 cranelift/filetests/filetests/runtests/issue-5690.clif create mode 100644 cranelift/filetests/filetests/runtests/issue5497.clif create mode 100644 cranelift/filetests/filetests/runtests/issue5523.clif create mode 100644 cranelift/filetests/filetests/runtests/issue5524.clif create mode 100644 cranelift/filetests/filetests/runtests/issue5525.clif create mode 100644 cranelift/filetests/filetests/runtests/issue5526.clif create mode 100644 cranelift/filetests/filetests/runtests/issue5528.clif create mode 100644 cranelift/filetests/filetests/runtests/issue5569.clif delete mode 100644 cranelift/filetests/filetests/runtests/load-op-store.clif create mode 100644 cranelift/filetests/filetests/runtests/or-and-y-with-not-y.clif create mode 100644 cranelift/filetests/filetests/runtests/pinned-reg.clif create mode 100644 cranelift/filetests/filetests/runtests/return-call.clif create mode 100644 cranelift/filetests/filetests/runtests/riscv64_issue_4996.clif create mode 100644 cranelift/filetests/filetests/runtests/rotl.clif create mode 100644 cranelift/filetests/filetests/runtests/rotr.clif create mode 100644 cranelift/filetests/filetests/runtests/selectif-spectre-guard.clif create mode 100644 cranelift/filetests/filetests/runtests/shift-right-left.clif delete mode 100644 cranelift/filetests/filetests/runtests/shifts-small-types.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-avg-round.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-bitcast-aarch64.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-bitcast.clif delete mode 100644 cranelift/filetests/filetests/runtests/simd-comparison.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-fcmp.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-fcopysign-64bit.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-fcopysign.clif delete mode 100644 cranelift/filetests/filetests/runtests/simd-icmp-nof.clif delete mode 100644 cranelift/filetests/filetests/runtests/simd-icmp-of.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-icmp-uge-i64x2.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-icmp-ugt-i64x2.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-icmp-ule-i64x2.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-icmp-ult-i64x2.clif create mode 100644 cranelift/filetests/filetests/runtests/simd-vconst-64bit.clif delete mode 100644 cranelift/filetests/filetests/runtests/table_addr.clif create mode 100644 cranelift/filetests/filetests/runtests/uadd_overflow_trap.clif create mode 100644 cranelift/filetests/filetests/runtests/x64-xmm-mem-align-bug.clif create mode 100644 cranelift/filetests/filetests/simple_gvn/idempotent-trapping.clif delete mode 100644 cranelift/filetests/filetests/simple_preopt/bitselect.clif create mode 100644 cranelift/filetests/filetests/simple_preopt/i128.clif create mode 100644 cranelift/filetests/filetests/verifier/argument-extension.clif create mode 100644 cranelift/filetests/filetests/verifier/cold_entry.clif delete mode 100644 cranelift/filetests/filetests/verifier/heap.clif create mode 100644 cranelift/filetests/filetests/verifier/return-call.clif create mode 100644 cranelift/filetests/filetests/wasm/basic-wat-test.wat create mode 100644 cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory-egraph.wat create mode 100644 cranelift/filetests/filetests/wasm/duplicate-loads-dynamic-memory.wat create mode 100644 cranelift/filetests/filetests/wasm/duplicate-loads-static-memory-egraph.wat create mode 100644 cranelift/filetests/filetests/wasm/duplicate-loads-static-memory.wat create mode 100644 cranelift/filetests/filetests/wasm/f32-load.wat delete mode 100644 cranelift/filetests/filetests/wasm/f32-memory64.clif create mode 100644 cranelift/filetests/filetests/wasm/f32-store.wat create mode 100644 cranelift/filetests/filetests/wasm/f64-load.wat delete mode 100644 cranelift/filetests/filetests/wasm/f64-memory64.clif create mode 100644 cranelift/filetests/filetests/wasm/f64-store.wat create mode 100644 cranelift/filetests/filetests/wasm/i32-load.wat create mode 100644 cranelift/filetests/filetests/wasm/i32-load16-s.wat create mode 100644 cranelift/filetests/filetests/wasm/i32-load16-u.wat create mode 100644 cranelift/filetests/filetests/wasm/i32-load8-s.wat create mode 100644 cranelift/filetests/filetests/wasm/i32-load8-u.wat delete mode 100644 cranelift/filetests/filetests/wasm/i32-memory64.clif create mode 100644 cranelift/filetests/filetests/wasm/i32-not-x64.wat create mode 100644 cranelift/filetests/filetests/wasm/i32-store.wat create mode 100644 cranelift/filetests/filetests/wasm/i32-store16.wat create mode 100644 cranelift/filetests/filetests/wasm/i32-store8.wat create mode 100644 cranelift/filetests/filetests/wasm/i64-load.wat create mode 100644 cranelift/filetests/filetests/wasm/i64-load16-s.wat create mode 100644 cranelift/filetests/filetests/wasm/i64-load16-u.wat create mode 100644 cranelift/filetests/filetests/wasm/i64-load8-s.wat create mode 100644 cranelift/filetests/filetests/wasm/i64-load8-u.wat delete mode 100644 cranelift/filetests/filetests/wasm/i64-memory64.clif create mode 100644 cranelift/filetests/filetests/wasm/i64-store.wat create mode 100644 cranelift/filetests/filetests/wasm/i64-store16.wat create mode 100644 cranelift/filetests/filetests/wasm/i64-store32.wat create mode 100644 cranelift/filetests/filetests/wasm/i64-store8.wat create mode 100644 cranelift/filetests/filetests/wasm/issue-5696.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_dynamic_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i32_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_no_spectre_i8_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i32_access_0xffff0000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0x1000_offset.wat create mode 100644 cranelift/filetests/filetests/wasm/load-store/load_store_static_kind_i64_index_0xffffffff_guard_yes_spectre_i8_access_0xffff0000_offset.wat create mode 100755 cranelift/filetests/filetests/wasm/load-store/make-load-store-tests.sh create mode 100644 cranelift/filetests/src/test_optimize.rs delete mode 100644 cranelift/filetests/src/test_preopt.rs 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 create mode 100644 cranelift/fuzzgen/src/passes/fcvt.rs create mode 100644 cranelift/fuzzgen/src/passes/int_divz.rs create mode 100644 cranelift/fuzzgen/src/passes/mod.rs create mode 100644 cranelift/isle/isle/isle_examples/fail/extra_parens.isle create mode 100644 cranelift/isle/isle/isle_examples/fail/multi_internal_etor.isle create mode 100644 cranelift/isle/isle/isle_examples/fail/multi_prio.isle create mode 100644 cranelift/isle/isle/isle_examples/link/multi_constructor.isle create mode 100644 cranelift/isle/isle/isle_examples/link/multi_constructor_main.rs create mode 100644 cranelift/isle/isle/isle_examples/link/multi_extractor.isle create mode 100644 cranelift/isle/isle/isle_examples/link/multi_extractor_main.rs delete mode 100644 cranelift/isle/isle/src/error_miette.rs delete mode 100644 cranelift/isle/isle/src/ir.rs create mode 100644 cranelift/isle/isle/src/overlap.rs create mode 100644 cranelift/isle/isle/src/serialize.rs delete mode 100644 cranelift/isle/isle/src/trie.rs create mode 100644 cranelift/isle/isle/src/trie_again.rs delete mode 100644 cranelift/preopt/Cargo.toml delete mode 100644 cranelift/preopt/README.md delete mode 100644 cranelift/preopt/src/constant_folding.rs delete mode 100644 cranelift/preopt/src/lib.rs delete mode 100644 cranelift/reader/src/heap_command.rs mode change 100755 => 100644 cranelift/src/clif-util.rs create mode 100644 cranelift/wasm/src/code_translator/bounds_checks.rs create mode 100644 cranelift/wasm/src/heap.rs rename cranelift/wasm/src/{state/func_state.rs => state.rs} (96%) delete mode 100644 cranelift/wasm/src/state/mod.rs delete mode 100644 cranelift/wasm/src/state/module_state.rs create mode 100644 crates/component-macro/src/bindgen.rs create mode 100644 crates/component-macro/src/component.rs create mode 100644 crates/component-macro/test-helpers/Cargo.toml create mode 100644 crates/component-macro/test-helpers/src/lib.rs create mode 100644 crates/component-macro/tests/codegen.rs create mode 100644 crates/component-macro/tests/codegen/char.wit create mode 100644 crates/component-macro/tests/codegen/conventions.wit create mode 100644 crates/component-macro/tests/codegen/direct-import.wit create mode 100644 crates/component-macro/tests/codegen/empty.wit create mode 100644 crates/component-macro/tests/codegen/flags.wit create mode 100644 crates/component-macro/tests/codegen/floats.wit create mode 100644 crates/component-macro/tests/codegen/function-new.wit create mode 100644 crates/component-macro/tests/codegen/integers.wit create mode 100644 crates/component-macro/tests/codegen/lists.wit create mode 100644 crates/component-macro/tests/codegen/many-arguments.wit create mode 100644 crates/component-macro/tests/codegen/multi-return.wit create mode 100644 crates/component-macro/tests/codegen/records.wit create mode 100644 crates/component-macro/tests/codegen/share-types.wit create mode 100644 crates/component-macro/tests/codegen/simple-functions.wit create mode 100644 crates/component-macro/tests/codegen/simple-lists.wit create mode 100644 crates/component-macro/tests/codegen/simple-wasi.wit create mode 100644 crates/component-macro/tests/codegen/small-anonymous.wit create mode 100644 crates/component-macro/tests/codegen/smoke-default.wit create mode 100644 crates/component-macro/tests/codegen/smoke-export.wit create mode 100644 crates/component-macro/tests/codegen/smoke.wit create mode 100644 crates/component-macro/tests/codegen/strings.wit create mode 100644 crates/component-macro/tests/codegen/unions.wit create mode 100644 crates/component-macro/tests/codegen/use-paths.wit create mode 100644 crates/component-macro/tests/codegen/variants.wit create mode 100644 crates/component-macro/tests/codegen/worlds-with-types.wit create mode 100644 crates/environ/src/fact/transcode.rs create mode 100644 crates/fiber/src/unix/riscv64.rs delete mode 100644 crates/fuzzing/src/generators/instance_limits.rs create mode 100644 crates/fuzzing/src/generators/module.rs delete mode 100644 crates/fuzzing/src/generators/module_config.rs create mode 100644 crates/fuzzing/src/generators/pooling_config.rs create mode 100644 crates/fuzzing/src/generators/value.rs create mode 100644 crates/fuzzing/src/oracles/diff_spec.rs create mode 100644 crates/fuzzing/src/oracles/diff_v8.rs create mode 100644 crates/fuzzing/src/oracles/diff_wasmi.rs create mode 100644 crates/fuzzing/src/oracles/diff_wasmtime.rs create mode 100644 crates/fuzzing/src/oracles/engine.rs delete mode 100644 crates/fuzzing/src/oracles/v8.rs create mode 100644 crates/fuzzing/wasm-spec-interpreter/tests/memory.wat create mode 100644 crates/fuzzing/wasm-spec-interpreter/tests/shr_s.wat create mode 100644 crates/jit-icache-coherence/Cargo.toml create mode 100644 crates/jit-icache-coherence/src/lib.rs create mode 100644 crates/jit-icache-coherence/src/libc.rs create mode 100644 crates/jit-icache-coherence/src/win.rs delete mode 100644 crates/jit/src/unwind/winx32.rs create mode 100644 crates/runtime/src/component/transcode.rs delete mode 100644 crates/runtime/src/cow_disabled.rs create mode 100644 crates/runtime/src/parking_spot.rs create mode 100644 crates/runtime/src/trampolines/riscv64.rs create mode 100644 crates/runtime/src/traphandlers/backtrace/riscv64.rs create mode 100644 crates/wasi-common/src/snapshots/preview_1/error.rs create mode 100644 crates/wasi-threads/Cargo.toml create mode 100644 crates/wasi-threads/README.md create mode 100644 crates/wasi-threads/src/lib.rs create mode 100644 crates/wasmtime/src/code.rs create mode 100644 crates/wasmtime/src/component/storage.rs create mode 100644 crates/wasmtime/src/engine/serialization.rs delete mode 100644 crates/wasmtime/src/module/serialization.rs create mode 100644 crates/wiggle/generate/src/types/error.rs create mode 100644 crates/wiggle/tests/tracing.rs create mode 100644 crates/winch/Cargo.toml rename {cranelift/preopt => crates/winch}/LICENSE (99%) create mode 100644 crates/winch/src/builder.rs create mode 100644 crates/winch/src/compiler.rs create mode 100644 crates/winch/src/lib.rs create mode 100644 crates/wit-bindgen/Cargo.toml create mode 100644 crates/wit-bindgen/src/lib.rs create mode 100644 crates/wit-bindgen/src/rust.rs create mode 100644 crates/wit-bindgen/src/source.rs create mode 100644 crates/wit-bindgen/src/types.rs create mode 100644 docs/contributing-cross-compiling.md create mode 100644 docs/lang-elixir.md create mode 100644 docs/lang-ruby.md create mode 100644 fuzz/fuzz_targets/cranelift-icache.rs delete mode 100644 fuzz/fuzz_targets/differential_spec.rs delete mode 100644 fuzz/fuzz_targets/differential_v8.rs delete mode 100644 fuzz/fuzz_targets/differential_wasmi.rs create mode 100644 tests/all/cli_tests/threads.wat create mode 100644 tests/all/component_model/aot.rs create mode 100644 tests/all/component_model/async.rs create mode 100644 tests/all/component_model/bindgen.rs create mode 100644 tests/all/component_model/bindgen/results.rs create mode 100644 tests/all/component_model/strings.rs create mode 100644 tests/all/wait_notify.rs create mode 100644 tests/misc_testsuite/component-model/strings.wast create mode 100644 tests/misc_testsuite/issue4840.wast create mode 100644 tests/misc_testsuite/issue4857.wast create mode 100644 tests/misc_testsuite/issue4890.wast create mode 100644 tests/misc_testsuite/simd/issue4807.wast create mode 100644 tests/misc_testsuite/threads/atomics_notify.wast create mode 100644 winch/Cargo.toml create mode 100644 winch/codegen/Cargo.toml create mode 100644 winch/codegen/LICENSE create mode 100644 winch/codegen/src/abi/local.rs create mode 100644 winch/codegen/src/abi/mod.rs create mode 100644 winch/codegen/src/codegen.rs create mode 100644 winch/codegen/src/frame/mod.rs create mode 100644 winch/codegen/src/isa/aarch64/abi.rs create mode 100644 winch/codegen/src/isa/aarch64/address.rs create mode 100644 winch/codegen/src/isa/aarch64/asm.rs create mode 100644 winch/codegen/src/isa/aarch64/masm.rs create mode 100644 winch/codegen/src/isa/aarch64/mod.rs create mode 100644 winch/codegen/src/isa/aarch64/regs.rs create mode 100644 winch/codegen/src/isa/mod.rs create mode 100644 winch/codegen/src/isa/reg.rs create mode 100644 winch/codegen/src/isa/x64/abi.rs create mode 100644 winch/codegen/src/isa/x64/address.rs create mode 100644 winch/codegen/src/isa/x64/asm.rs create mode 100644 winch/codegen/src/isa/x64/masm.rs create mode 100644 winch/codegen/src/isa/x64/mod.rs create mode 100644 winch/codegen/src/isa/x64/regs.rs create mode 100644 winch/codegen/src/lib.rs create mode 100644 winch/codegen/src/masm.rs create mode 100644 winch/codegen/src/regalloc.rs create mode 100644 winch/codegen/src/regset.rs create mode 100644 winch/codegen/src/stack.rs create mode 100644 winch/codegen/src/visitor.rs create mode 100644 winch/docs/testing.md create mode 100644 winch/filetests/Cargo.toml create mode 100644 winch/filetests/build.rs create mode 100644 winch/filetests/filetests/aarch64/i32_add/const.wat create mode 100644 winch/filetests/filetests/aarch64/i32_add/locals.wat create mode 100644 winch/filetests/filetests/aarch64/i32_add/max.wat create mode 100644 winch/filetests/filetests/aarch64/i32_add/max_one.wat create mode 100644 winch/filetests/filetests/aarch64/i32_add/mixed.wat create mode 100644 winch/filetests/filetests/aarch64/i32_add/params.wat create mode 100644 winch/filetests/filetests/aarch64/i32_add/signed.wat create mode 100644 winch/filetests/filetests/aarch64/i32_add/unsigned_with_zero.wat create mode 100644 winch/filetests/filetests/aarch64/i64_add/const.wat create mode 100644 winch/filetests/filetests/aarch64/i64_add/locals.wat create mode 100644 winch/filetests/filetests/aarch64/i64_add/max.wat create mode 100644 winch/filetests/filetests/aarch64/i64_add/max_one.wat create mode 100644 winch/filetests/filetests/aarch64/i64_add/mixed.wat create mode 100644 winch/filetests/filetests/aarch64/i64_add/params.wat create mode 100644 winch/filetests/filetests/aarch64/i64_add/signed.wat create mode 100644 winch/filetests/filetests/aarch64/i64_add/unsigned_with_zero.wat create mode 100644 winch/filetests/filetests/x64/i32_add/const.wat create mode 100644 winch/filetests/filetests/x64/i32_add/locals.wat create mode 100644 winch/filetests/filetests/x64/i32_add/max.wat create mode 100644 winch/filetests/filetests/x64/i32_add/max_one.wat create mode 100644 winch/filetests/filetests/x64/i32_add/mixed.wat create mode 100644 winch/filetests/filetests/x64/i32_add/params.wat create mode 100644 winch/filetests/filetests/x64/i32_add/signed.wat create mode 100644 winch/filetests/filetests/x64/i32_add/unsigned_with_zero.wat create mode 100644 winch/filetests/filetests/x64/i32_mul/const.wat create mode 100644 winch/filetests/filetests/x64/i32_mul/locals.wat create mode 100644 winch/filetests/filetests/x64/i32_mul/max.wat create mode 100644 winch/filetests/filetests/x64/i32_mul/max_one.wat create mode 100644 winch/filetests/filetests/x64/i32_mul/mixed.wat create mode 100644 winch/filetests/filetests/x64/i32_mul/params.wat create mode 100644 winch/filetests/filetests/x64/i32_mul/signed.wat create mode 100644 winch/filetests/filetests/x64/i32_mul/unsigned_with_zero.wat create mode 100644 winch/filetests/filetests/x64/i32_sub/const.wat create mode 100644 winch/filetests/filetests/x64/i32_sub/locals.wat create mode 100644 winch/filetests/filetests/x64/i32_sub/max.wat create mode 100644 winch/filetests/filetests/x64/i32_sub/max_one.wat create mode 100644 winch/filetests/filetests/x64/i32_sub/mixed.wat create mode 100644 winch/filetests/filetests/x64/i32_sub/params.wat create mode 100644 winch/filetests/filetests/x64/i32_sub/signed.wat create mode 100644 winch/filetests/filetests/x64/i32_sub/unsigned_with_zero.wat create mode 100644 winch/filetests/filetests/x64/i64_add/const.wat create mode 100644 winch/filetests/filetests/x64/i64_add/locals.wat create mode 100644 winch/filetests/filetests/x64/i64_add/max.wat create mode 100644 winch/filetests/filetests/x64/i64_add/max_one.wat create mode 100644 winch/filetests/filetests/x64/i64_add/mixed.wat create mode 100644 winch/filetests/filetests/x64/i64_add/params.wat create mode 100644 winch/filetests/filetests/x64/i64_add/signed.wat create mode 100644 winch/filetests/filetests/x64/i64_add/unsigned_with_zero.wat create mode 100644 winch/filetests/filetests/x64/i64_mul/const.wat create mode 100644 winch/filetests/filetests/x64/i64_mul/locals.wat create mode 100644 winch/filetests/filetests/x64/i64_mul/max.wat create mode 100644 winch/filetests/filetests/x64/i64_mul/max_one.wat create mode 100644 winch/filetests/filetests/x64/i64_mul/mixed.wat create mode 100644 winch/filetests/filetests/x64/i64_mul/params.wat create mode 100644 winch/filetests/filetests/x64/i64_mul/signed.wat create mode 100644 winch/filetests/filetests/x64/i64_mul/unsigned_with_zero.wat create mode 100644 winch/filetests/filetests/x64/i64_sub/const.wat create mode 100644 winch/filetests/filetests/x64/i64_sub/locals.wat create mode 100644 winch/filetests/filetests/x64/i64_sub/max.wat create mode 100644 winch/filetests/filetests/x64/i64_sub/max_one.wat create mode 100644 winch/filetests/filetests/x64/i64_sub/mixed.wat create mode 100644 winch/filetests/filetests/x64/i64_sub/params.wat create mode 100644 winch/filetests/filetests/x64/i64_sub/signed.wat create mode 100644 winch/filetests/filetests/x64/i64_sub/unsigned_with_zero.wat create mode 100644 winch/filetests/src/disasm.rs create mode 100644 winch/filetests/src/lib.rs create mode 100644 winch/src/compile.rs create mode 100644 winch/src/filetests.rs create mode 100644 winch/src/main.rs create mode 100644 winch/test-macros/Cargo.toml create mode 100644 winch/test-macros/src/lib.rs diff --git a/.gitattributes b/.gitattributes index abbcc8367205..1f16d2f34820 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,7 +8,3 @@ # ISLE should use lisp syntax highlighting. *.isle linguist-language=lisp - -# Tell GitHub this is generated code, and doesn't need to be shown in diffs by -# default. -cranelift/codegen/src/isa/*/lower/isle/generated_code.rs linguist-generated diff --git a/.github/ISSUE_TEMPLATE/fuzzbug.md b/.github/ISSUE_TEMPLATE/fuzzbug.md new file mode 100644 index 000000000000..52cd30bf6144 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/fuzzbug.md @@ -0,0 +1,44 @@ +--- +name: Fuzz Bug Report +about: Report a fuzz bug in Wasmtime or Cranelift +title: ' fuzzbug: ' +labels: bug, fuzz-bug +assignees: '' +--- + +Thanks for filing an issue! Please fill out the TODOs below, and change `` in the title to the corresponding fuzzing target. + + + +
+Test case input + + + +``` +TODO_paste_the_base64_encoded_input_here +``` + +
+ +
+`cargo +nightly fuzz fmt` output + + + +``` +TODO_paste_cargo_fuzz_fmt_output_here +``` + +
+ +
+Stack trace or other relevant details + + + +``` +TODO_paste_the_report_here +``` + +
diff --git a/.github/actions/binary-compatible-builds/action.yml b/.github/actions/binary-compatible-builds/action.yml index c2950d99b02b..b5a190c4a06f 100644 --- a/.github/actions/binary-compatible-builds/action.yml +++ b/.github/actions/binary-compatible-builds/action.yml @@ -2,7 +2,7 @@ name: 'Set up a CentOS 6 container to build releases in' description: 'Set up a CentOS 6 container to build releases in' runs: - using: node12 + using: node16 main: 'main.js' inputs: name: diff --git a/.github/actions/github-release/action.yml b/.github/actions/github-release/action.yml index 3225a91dbbe2..17c4715b9c12 100644 --- a/.github/actions/github-release/action.yml +++ b/.github/actions/github-release/action.yml @@ -8,5 +8,5 @@ inputs: description: '' required: true runs: - using: 'node12' + using: 'node16' main: 'main.js' diff --git a/.github/actions/github-release/main.js b/.github/actions/github-release/main.js index 82374c99484b..c91f7862a1b0 100644 --- a/.github/actions/github-release/main.js +++ b/.github/actions/github-release/main.js @@ -25,7 +25,7 @@ async function runOnce() { core.info(`name: ${name}`); core.info(`token: ${token}`); - const octokit = new github.GitHub(token); + const octokit = github.getOctokit(token); // For the `dev` release we may need to update the tag to point to the new // commit on this branch. All other names should already have tags associated @@ -43,20 +43,10 @@ async function runOnce() { if (tag === null || tag.data.object.sha !== sha) { core.info(`updating existing tag or creating new one`); - // Delete the previous release for this tag, if any - try { - core.info(`fetching release for ${name}`); - const release = await octokit.repos.getReleaseByTag({ owner, repo, tag: name }); - core.info(`deleting release ${release.data.id}`); - await octokit.repos.deleteRelease({ owner, repo, release_id: release.data.id }); - } catch (e) { - // ignore, there may not have been a release - console.log("ERROR: ", JSON.stringify(e, null, 2)); - } try { core.info(`updating dev tag`); - await octokit.git.updateRef({ + await octokit.rest.git.updateRef({ owner, repo, ref: 'tags/dev', @@ -80,6 +70,13 @@ async function runOnce() { // tag by this point. } } + + console.log("double-checking tag is correct"); + tag = await octokit.request("GET /repos/:owner/:repo/git/refs/tags/:name", { owner, repo, name }); + if (tag.data.object.sha !== sha) { + console.log("tag: ", JSON.stringify(tag.data, null, 2)); + throw new Error("tag didn't work"); + } } else { core.info(`existing tag works`); } @@ -91,12 +88,12 @@ async function runOnce() { let release = null; try { core.info(`fetching release`); - release = await octokit.repos.getReleaseByTag({ owner, repo, tag: name }); + release = await octokit.rest.repos.getReleaseByTag({ owner, repo, tag: name }); } catch (e) { console.log("ERROR: ", JSON.stringify(e, null, 2)); core.info(`creating a release`); try { - release = await octokit.repos.createRelease({ + release = await octokit.rest.repos.createRelease({ owner, repo, tag_name: name, @@ -105,7 +102,7 @@ async function runOnce() { } catch(e) { console.log("ERROR: ", JSON.stringify(e, null, 2)); core.info(`fetching one more time`); - release = await octokit.repos.getReleaseByTag({ owner, repo, tag: name }); + release = await octokit.rest.repos.getReleaseByTag({ owner, repo, tag: name }); } } console.log("found release: ", JSON.stringify(release.data, null, 2)); @@ -113,11 +110,22 @@ async function runOnce() { // Upload all the relevant assets for this release as just general blobs. for (const file of glob.sync(files)) { const size = fs.statSync(file).size; + const name = path.basename(file); + for (const asset of release.data.assets) { + if (asset.name !== name) + continue; + console.log(`deleting prior asset ${asset.id}`); + await octokit.rest.repos.deleteReleaseAsset({ + owner, + repo, + asset_id: asset.id, + }); + } core.info(`upload ${file}`); - await octokit.repos.uploadReleaseAsset({ + await octokit.rest.repos.uploadReleaseAsset({ data: fs.createReadStream(file), headers: { 'content-length': size, 'content-type': 'application/octet-stream' }, - name: path.basename(file), + name, url: release.data.upload_url, }); } diff --git a/.github/actions/github-release/package-lock.json b/.github/actions/github-release/package-lock.json new file mode 100644 index 000000000000..0bfb4329948d --- /dev/null +++ b/.github/actions/github-release/package-lock.json @@ -0,0 +1,571 @@ +{ + "name": "wasmtime-github-release", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "wasmtime-github-release", + "version": "0.0.0", + "dependencies": { + "@actions/core": "^1.9.1", + "@actions/github": "^5.1.0", + "glob": "^7.1.5" + } + }, + "node_modules/@actions/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz", + "integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==", + "dependencies": { + "@actions/http-client": "^2.0.1", + "uuid": "^8.3.2" + } + }, + "node_modules/@actions/github": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.0.tgz", + "integrity": "sha512-tuI80F7JQIhg77ZTTgUAPpVD7ZnP9oHSPN8xw7LOwtA4vEMbAjWJNbmLBfV7xua7r016GyjzWLuec5cs8f/a8A==", + "dependencies": { + "@actions/http-client": "^2.0.1", + "@octokit/core": "^3.6.0", + "@octokit/plugin-paginate-rest": "^2.17.0", + "@octokit/plugin-rest-endpoint-methods": "^5.13.0" + } + }, + "node_modules/@actions/http-client": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz", + "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==", + "dependencies": { + "tunnel": "^0.0.6" + } + }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "dependencies": { + "@octokit/types": "^6.40.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", + "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "dependencies": { + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "dependencies": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + }, + "dependencies": { + "@actions/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz", + "integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==", + "requires": { + "@actions/http-client": "^2.0.1", + "uuid": "^8.3.2" + } + }, + "@actions/github": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.0.tgz", + "integrity": "sha512-tuI80F7JQIhg77ZTTgUAPpVD7ZnP9oHSPN8xw7LOwtA4vEMbAjWJNbmLBfV7xua7r016GyjzWLuec5cs8f/a8A==", + "requires": { + "@actions/http-client": "^2.0.1", + "@octokit/core": "^3.6.0", + "@octokit/plugin-paginate-rest": "^2.17.0", + "@octokit/plugin-rest-endpoint-methods": "^5.13.0" + } + }, + "@actions/http-client": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz", + "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==", + "requires": { + "tunnel": "^0.0.6" + } + }, + "@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "requires": { + "@octokit/types": "^6.0.3" + } + }, + "@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "requires": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "requires": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "requires": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==" + }, + "@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "requires": { + "@octokit/types": "^6.40.0" + } + }, + "@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", + "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "requires": { + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" + } + }, + "@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "requires": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "requires": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "requires": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/.github/actions/github-release/package.json b/.github/actions/github-release/package.json index abfc55f6ff2b..80ab88253ad6 100644 --- a/.github/actions/github-release/package.json +++ b/.github/actions/github-release/package.json @@ -3,8 +3,8 @@ "version": "0.0.0", "main": "main.js", "dependencies": { - "@actions/core": "^1.0.0", - "@actions/github": "^1.0.0", + "@actions/core": "^1.9.1", + "@actions/github": "^5.1.0", "glob": "^7.1.5" } } diff --git a/.github/actions/install-rust/action.yml b/.github/actions/install-rust/action.yml index 7a196591840d..d8016f78e939 100644 --- a/.github/actions/install-rust/action.yml +++ b/.github/actions/install-rust/action.yml @@ -1,12 +1,86 @@ name: 'Install Rust toolchain' -description: 'Install both `rustup` and a Rust toolchain' +description: 'Install a rust toolchain and cache the crates index' inputs: toolchain: description: 'Default toolchan to install' required: false default: 'stable' + lockfiles: + description: 'Path glob for Cargo.lock files to use as cache keys' + required: false + default: '**/Cargo.lock' runs: - using: node12 - main: 'main.js' + using: composite + steps: + + - name: Install Rust + shell: bash + run: | + rustup set profile minimal + rustup update "${{ inputs.toolchain }}" --no-self-update + rustup default "${{ inputs.toolchain }}" + + # Save disk space by avoiding incremental compilation. Also turn down + # debuginfo from 2 to 1 to help save disk space. + cat >> "$GITHUB_ENV" <> "$GITHUB_ENV" + fi + + if [[ "${{ runner.os }}" = "macOS" ]]; then + cat >> "$GITHUB_ENV" <> $GITHUB_ENV + + - name: Cache Cargo registry index + uses: actions/cache@v3 + with: + path: ~/.cargo/registry/index/ + key: cargo-registry-${{ env.CARGO_REGISTRY_CACHE_KEY }} + # Any older registry-index cache is still valid. It's a git clone, so + # cargo only has to pull down the changes since the index was cached. + restore-keys: cargo-registry- + + - name: Cache crate sources for dependencies + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: cargo-crates-${{ inputs.lockfiles }}-${{ hashFiles(inputs.lockfiles) }} + # If Cargo.lock has changed, we probably will need to get the source + # code for some crates we don't already have. But any crates we have + # source cached for are still valid. The only problem is nothing + # removes old crate sources from the cache, so using `restore-keys` + # this way may use more of our GitHub cache quota than we'd like. + # + # Also, scope this cache by which Cargo.lock we're building from. + # Otherwise, whichever job writes the cache first will get its + # dependencies cached, and that cache will be used as the basis for the + # next job, even though odds are pretty good the cache is useless. + restore-keys: cargo-crates-${{ inputs.lockfiles }}- + +# TODO: on cache miss, after cargo has updated the registry index, run `git gc` diff --git a/.github/actions/install-rust/main.js b/.github/actions/install-rust/main.js deleted file mode 100644 index b144d70aea43..000000000000 --- a/.github/actions/install-rust/main.js +++ /dev/null @@ -1,36 +0,0 @@ -const child_process = require('child_process'); -const toolchain = process.env.INPUT_TOOLCHAIN; -const fs = require('fs'); - -function set_env(name, val) { - fs.appendFileSync(process.env['GITHUB_ENV'], `${name}=${val}\n`) -} - -// Needed for now to get 1.24.2 which fixes a bug in 1.24.1 that causes issues -// on Windows. -if (process.platform === 'win32') { - child_process.execFileSync('rustup', ['self', 'update']); -} - -child_process.execFileSync('rustup', ['set', 'profile', 'minimal']); -child_process.execFileSync('rustup', ['update', toolchain, '--no-self-update']); -child_process.execFileSync('rustup', ['default', toolchain]); - -// Deny warnings on CI to keep our code warning-free as it lands in-tree. Don't -// do this on nightly though since there's a fair amount of warning churn there. -if (!toolchain.startsWith('nightly')) { - set_env("RUSTFLAGS", "-D warnings"); -} - -// Save disk space by avoiding incremental compilation, and also we don't use -// any caching so incremental wouldn't help anyway. -set_env("CARGO_INCREMENTAL", "0"); - -// Turn down debuginfo from 2 to 1 to help save disk space -set_env("CARGO_PROFILE_DEV_DEBUG", "1"); -set_env("CARGO_PROFILE_TEST_DEBUG", "1"); - -if (process.platform === 'darwin') { - set_env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "unpacked"); - set_env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "unpacked"); -} diff --git a/.github/labeler.yml b/.github/labeler.yml index 19599af1c24d..f7b27da7d867 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -77,3 +77,7 @@ "wasmtime:ref-types": - "crates/wasmtime/src/ref.rs" - "crates/runtime/src/externref.rs" + +"winch": + - "winch/**" + - "crates/winch/**" diff --git a/.github/subscribe-to-label.json b/.github/subscribe-to-label.json index 184ea9ae54e3..08b1ee0d3c95 100644 --- a/.github/subscribe-to-label.json +++ b/.github/subscribe-to-label.json @@ -2,5 +2,6 @@ "cfallin": ["isle"], "fitzgen": ["fuzzing", "isle", "wasmtime:ref-types"], "peterhuene": ["wasmtime:api", "wasmtime:c-api"], - "kubkon": ["wasi"] + "kubkon": ["wasi"], + "saulecabrera": ["winch"] } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000000..6ea2885e8f57 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,105 @@ + +name: Build +on: + push: + branches: + - main + tags: + - 'v*' + pull_request: + branches: + - 'release-*' + +defaults: + run: + shell: bash + +# Cancel any in-flight jobs for the same PR/branch so there's only one active +# at a time +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + # Perform release builds of `wasmtime` and `libwasmtime.so`. Builds on + # Windows/Mac/Linux, and artifacts are uploaded after the build is finished. + # Note that we also run tests here to test exactly what we're deploying. + build: + name: Build wasmtime + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - build: x86_64-linux + os: ubuntu-latest + - build: x86_64-macos + os: macos-latest + - build: aarch64-macos + os: macos-latest + target: aarch64-apple-darwin + - build: x86_64-windows + os: windows-latest + - build: x86_64-mingw + os: windows-latest + target: x86_64-pc-windows-gnu + - build: aarch64-linux + os: ubuntu-latest + target: aarch64-unknown-linux-gnu + - build: s390x-linux + os: ubuntu-latest + target: s390x-unknown-linux-gnu + - build: riscv64gc-linux + os: ubuntu-latest + target: riscv64gc-unknown-linux-gnu + steps: + - uses: actions/checkout@v3 + with: + submodules: true + - uses: ./.github/actions/install-rust + # Note that the usage of this nightly toolchain is temporary until it + # rides to stable. After this nightly version becomes stable (Rust 1.69.0) + # then this should switch back to using stable by deleting the `with` and + # `toolchain` options. + with: + toolchain: nightly-2023-01-31 + # On one builder produce the source tarball since there's no need to produce + # it everywhere + - run: ./ci/build-src-tarball.sh + if: matrix.build == 'x86_64-linux' + - uses: ./.github/actions/binary-compatible-builds + with: + name: ${{ matrix.build }} + - run: | + echo CARGO_BUILD_TARGET=${{ matrix.target }} >> $GITHUB_ENV + rustup target add ${{ matrix.target }} + if: matrix.target != '' + + # Build `wasmtime` and executables. Note that we include `all-arch` so our + # release artifacts can be used to compile `.cwasm`s for other targets. + - run: $CENTOS cargo build --release --bin wasmtime --features all-arch + + # Build `libwasmtime.so` + - run: $CENTOS cargo build --release --manifest-path crates/c-api/Cargo.toml + + # Assemble release artifats appropriate for this platform, then upload them + # unconditionally to this workflow's files so we have a copy of them. + - run: ./ci/build-tarballs.sh "${{ matrix.build }}" "${{ matrix.target }}" + - uses: actions/upload-artifact@v3 + with: + name: bins-${{ matrix.build }} + path: dist + + # ... and if this was an actual push (tag or `main`) then we publish a + # new release. This'll automatically publish a tag release or update `dev` + # with this `sha`. Note that `continue-on-error` is set here so if this hits + # a bug we can go back and fetch and upload the release ourselves. + - run: cd .github/actions/github-release && npm install --production + - name: Publish Release + uses: ./.github/actions/github-release + # We only publish for main or a version tag, not `release-*` branches + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) && github.repository == 'bytecodealliance/wasmtime' + with: + files: "dist/*" + token: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true + diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml index 4c24d366c84a..951fe2ca6780 100644 --- a/.github/workflows/cargo-audit.yml +++ b/.github/workflows/cargo-audit.yml @@ -6,7 +6,7 @@ jobs: security_audit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - uses: actions-rs/audit-check@v1 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f24170844a5e..b7d82078d9c9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ jobs: name: Rustfmt runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/actions/install-rust @@ -34,7 +34,7 @@ jobs: name: Cargo deny runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/actions/install-rust @@ -50,13 +50,13 @@ jobs: name: Cargo vet runs-on: ubuntu-latest env: - CARGO_VET_VERSION: 0.2.0 + CARGO_VET_VERSION: 0.3.1 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/actions/install-rust - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ${{ runner.tool_cache }}/cargo-vet key: cargo-vet-bin-${{ env.CARGO_VET_VERSION }} @@ -68,25 +68,24 @@ jobs: name: Doc build runs-on: ubuntu-latest env: - CARGO_MDBOOK_VERSION: 0.4.8 + CARGO_MDBOOK_VERSION: 0.4.21 RUSTDOCFLAGS: -Dbroken_intra_doc_links --cfg nightlydoc OPENVINO_SKIP_LINKING: 1 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/actions/install-rust with: - toolchain: nightly-2022-04-27 + toolchain: nightly-2022-12-15 # Build C API documentation - - run: sudo apt-get update -y && sudo apt-get install -y libclang1-9 libclang-cpp9 - - run: curl -L https://www.doxygen.nl/files/doxygen-1.9.3.linux.bin.tar.gz | tar xzf - + - run: curl -L https://sourceforge.net/projects/doxygen/files/rel-1.9.3/doxygen-1.9.3.linux.bin.tar.gz/download | tar xzf - - run: echo "`pwd`/doxygen-1.9.3/bin" >> $GITHUB_PATH - run: cd crates/c-api && doxygen doxygen.conf # install mdbook, build the docs, and test the docs - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ${{ runner.tool_cache }}/mdbook key: cargo-mdbook-bin-${{ env.CARGO_MDBOOK_VERSION }} @@ -113,7 +112,7 @@ jobs: mv crates/c-api/html gh-pages/c-api mv target/doc gh-pages/api tar czf gh-pages.tar.gz gh-pages - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: gh-pages path: gh-pages.tar.gz @@ -136,8 +135,10 @@ jobs: checks: name: Check runs-on: ubuntu-latest + env: + CARGO_NDK_VERSION: 2.12.2 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/actions/install-rust @@ -173,36 +174,58 @@ jobs: env: CARGO_PROFILE_DEV_DEBUG_ASSERTIONS: false - # Check whether `crates/wasi-common` cross-compiles to the following targets: - # * wasm32-unknown-emscripten - # * armv7-unknown-linux-gnueabihf - - run: | - rustup target add wasm32-unknown-emscripten - rustup target add armv7-unknown-linux-gnueabihf - sudo apt-get update && sudo apt-get install -y gcc-arm-linux-gnueabihf - - run: cargo check --target wasm32-unknown-emscripten -p wasi-common - - run: cargo check --target armv7-unknown-linux-gnueabihf -p wasi-common + # Check whether `wasmtime` cross-compiles to x86_64-unknown-freebsd + # TODO: We aren't building with default features since the `ittapi` crate fails to compile on freebsd. + - run: rustup target add x86_64-unknown-freebsd + - run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache --target x86_64-unknown-freebsd + + # Check whether `wasmtime` cross-compiles to aarch64-linux-android + - run: rustup target add aarch64-linux-android + - name: Setup Android SDK + uses: android-actions/setup-android@v2 + - uses: actions/cache@v3 + with: + path: ${{ runner.tool_cache }}/cargo-ndk + key: cargo-ndk-bin-${{ env.CARGO_NDK_VERSION }} + - run: echo "${{ runner.tool_cache }}/cargo-ndk/bin" >> $GITHUB_PATH + - run: cargo install --root ${{ runner.tool_cache }}/cargo-ndk --version ${{ env.CARGO_NDK_VERSION }} cargo-ndk + - run: cargo ndk -t arm64-v8a check -p wasmtime + + # Check whether `wasmtime` cross-compiles to aarch64-pc-windows-msvc + # We don't build nor test it because it lacks trap handling. + # Tracking issue: https://github.com/bytecodealliance/wasmtime/issues/4992 + checks_winarm64: + name: Check Windows ARM64 + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: true + - uses: ./.github/actions/install-rust + - run: rustup target add aarch64-pc-windows-msvc + - run: cargo check -p wasmtime --target aarch64-pc-windows-msvc fuzz_targets: name: Fuzz Targets runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true # Note that building with fuzzers requires nightly since it uses unstable # flags to rustc. - uses: ./.github/actions/install-rust with: - toolchain: nightly-2022-04-27 + toolchain: nightly-2022-12-15 - run: cargo install cargo-fuzz --vers "^0.11" - # Install OCaml packages necessary for 'differential_spec' fuzz target. + # Install the OCaml packages necessary for fuzz targets that use the + # `wasm-spec-interpreter`. - run: sudo apt install -y ocaml-nox ocamlbuild ocaml-findlib libzarith-ocaml-dev - run: cargo fetch working-directory: ./fuzz - - run: cargo fuzz build --dev + - run: cargo fuzz build --dev -s none # Check that the ISLE fuzz targets build too. - - run: cargo fuzz build --dev --fuzz-dir ./cranelift/isle/fuzz + - run: cargo fuzz build --dev -s none --fuzz-dir ./cranelift/isle/fuzz # Perform all tests (debug mode) for `wasmtime`. This runs stable/beta/nightly # channels of Rust as well as macOS/Linux/Windows. @@ -232,8 +255,14 @@ jobs: gcc: s390x-linux-gnu-gcc qemu: qemu-s390x -L /usr/s390x-linux-gnu qemu_target: s390x-linux-user + - os: ubuntu-latest + target: riscv64gc-unknown-linux-gnu + gcc_package: gcc-riscv64-linux-gnu + gcc: riscv64-linux-gnu-gcc + qemu: qemu-riscv64 -L /usr/riscv64-linux-gnu + qemu_target: riscv64-linux-user steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/actions/install-rust @@ -250,7 +279,7 @@ jobs: - run: cargo fetch --locked - run: cargo fetch --locked --manifest-path crates/test-programs/wasi-tests/Cargo.toml - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ${{ runner.tool_cache }}/qemu key: qemu-${{ matrix.target }}-${{ env.QEMU_BUILD_VERSION }}-patchmadvise2 @@ -324,9 +353,9 @@ jobs: # Build and test the wasi-nn module. test_wasi_nn: name: Test wasi-nn module - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 # TODO: remove pin when fixed (#5408) steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - uses: ./.github/actions/install-rust @@ -341,12 +370,11 @@ jobs: name: Test wasi-crypto module runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true + - run: rustup update stable && rustup default stable - run: rustup target add wasm32-wasi - - name: Install Rust - run: rustup update stable && rustup default stable - run: ./ci/run-wasi-crypto-example.sh env: RUST_BACKTRACE: 1 @@ -355,12 +383,11 @@ jobs: name: Run benchmarks runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true + - uses: ./.github/actions/install-rust - run: rustup target add wasm32-wasi - - name: Install Rust - run: rustup update stable && rustup default stable - run: cargo test --benches --release # Verify that cranelift's code generation is deterministic @@ -368,86 +395,18 @@ jobs: name: Meta deterministic check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - - name: Install Rust - run: rustup update stable && rustup default stable + - uses: ./.github/actions/install-rust - run: cd cranelift/codegen && cargo build --features all-arch - run: ci/ensure_deterministic_build.sh - # Perform release builds of `wasmtime` and `libwasmtime.so`. Builds on - # Windows/Mac/Linux, and artifacts are uploaded after the build is finished. - # Note that we also run tests here to test exactly what we're deploying. - build: - name: Build wasmtime - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - build: x86_64-linux - os: ubuntu-latest - - build: x86_64-macos - os: macos-latest - - build: aarch64-macos - os: macos-latest - target: aarch64-apple-darwin - - build: x86_64-windows - os: windows-latest - - build: x86_64-mingw - os: windows-latest - target: x86_64-pc-windows-gnu - - build: aarch64-linux - os: ubuntu-latest - target: aarch64-unknown-linux-gnu - - build: s390x-linux - os: ubuntu-latest - target: s390x-unknown-linux-gnu - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - uses: ./.github/actions/install-rust - - uses: ./.github/actions/binary-compatible-builds - with: - name: ${{ matrix.build }} - - run: | - echo CARGO_BUILD_TARGET=${{ matrix.target }} >> $GITHUB_ENV - rustup target add ${{ matrix.target }} - if: matrix.target != '' - - # Build `wasmtime` and executables - - run: $CENTOS cargo build --release --bin wasmtime - - # Build `libwasmtime.so` - - run: $CENTOS cargo build --release --manifest-path crates/c-api/Cargo.toml - - # Assemble release artifats appropriate for this platform, then upload them - # unconditionally to this workflow's files so we have a copy of them. - - run: ./ci/build-tarballs.sh "${{ matrix.build }}" "${{ matrix.target }}" - - uses: actions/upload-artifact@v1 - with: - name: bins-${{ matrix.build }} - path: dist - - # ... and if this was an actual push (tag or `main`) then we publish a - # new release. This'll automatically publish a tag release or update `dev` - # with this `sha`. Note that `continue-on-error` is set here so if this hits - # a bug we can go back and fetch and upload the release ourselves. - - run: cd .github/actions/github-release && npm install --production - - name: Publish Release - uses: ./.github/actions/github-release - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')) && github.repository == 'bytecodealliance/wasmtime' - with: - files: "dist/*" - token: ${{ secrets.GITHUB_TOKEN }} - continue-on-error: true - verify-publish: if: github.repository == 'bytecodealliance/wasmtime' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - run: rustup update stable && rustup default stable diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml new file mode 100644 index 000000000000..54308bfc5689 --- /dev/null +++ b/.github/workflows/performance.yml @@ -0,0 +1,131 @@ +# This is a workflow triggered by PR or triggered manually +# Runs quick performance tests and reports the comparison against HEAD +# Test should take less than 10 minutes to run on current self-hosted devices +name: "Performance Testing" + +# Controls when the action will run. +# This workflow runs when manually triggered by keywords used in the start of a review comment +# Currently that phrase is /bench_x64. /bench_aarch64 and /bench_all are TODOs. +on: + issue_comment: + types: [created] + push: + +# Env variables +env: + SG_COMMIT: 2ab01ac + GITHUB_CONTEXT: ${{ toJson(github) }} + +jobs: + Wasmtime_Repo_On_PR_Comment: + name: Benchmark x64 on PR comment Wasmtime repo + runs-on: ubuntu-latest + if: | + (github.event_name == 'issue_comment') && + (github.event.issue.pull_request.url) && + (contains(github.event.comment.body, '/bench_x64')) && + (('abrown' == github.event.comment.user.login) + || ('afonso360' == github.event.comment.user.login) + || ('akirilov-arm' == github.event.comment.user.login) + || ('alexcrichton' == github.event.comment.user.login) + || ('bbouvier' == github.event.comment.user.login) + || ('bjorn3' == github.event.comment.user.login) + || ('cfallin' == github.event.comment.user.login) + || ('fitzgen' == github.event.comment.user.login) + || ('jlb6740' == github.event.comment.user.login) + || ('sparker-arm' == github.event.comment.user.login) + || ('uweigand' == github.event.comment.user.login)) + steps: + - run: echo "$GITHUB_CONTEXT" + - run: | + # Create and Push Branch + git clone https://wasmtime-publish:${{secrets.PERSONAL_ACCESS_TOKEN}}@github.com/bytecodealliance/wasmtime-sightglass-benchmarking.git + cd wasmtime-sightglass-benchmarking + git remote add wasmtime ${{ github.event.repository.clone_url }} + git fetch wasmtime refs/pull/*/merge:refs/remotes/wasmtime/pull/*/merge + export issue_pr_url=${{ github.event.issue.pull_request.url }} + export issue_commits_url=${{ github.event.issue.comments_url }} + export issue_ref_name=$(curl -sSL $issue_pr_url | jq -r '.head.ref' | head -n 1) + export issue_number=$(curl -sSL $issue_pr_url | jq -r '.number' | head -n 1) + export issue_merge_commit_sha=$(curl -sSL $issue_pr_url | jq -r '.merge_commit_sha' | head -n 1) + git submodule update --init --recursive + git checkout wasmtime/pull/${issue_number}/merge -b pull/${issue_number}/merge/${issue_merge_commit_sha} + git config user.name $(curl -sSL $issue_commits_url | jq -r '.[].commit.committer.name' | tail -n 1) + git config user.email $(curl -sSL $issue_commits_url | jq -r '.[].commit.committer.email' | tail -n 1) + git log -n 1 + git commit --allow-empty -m "${issue_commits_url}" + git push origin --force pull/${issue_number}/merge/${issue_merge_commit_sha} + git log -n 1 + + Performance_Repo_On_Push: + name: Benchmark x64 on push Performance repo + runs-on: [self-hosted, linux, x64] + if: (github.event_name == 'push') && (github.repository == 'bytecodealliance/wasmtime-sightglass-benchmarking') + steps: + - run: echo "$GITHUB_CONTEXT" + - run: echo "${{ github.event.head_commit.message }}" + - name: "Build sightglass commit '${{ env.SG_COMMIT }}'" + run: | + cd ../ && ls -l && rm -rf ./sightglass + git clone https://github.com/bytecodealliance/sightglass.git && cd ./sightglass + git checkout ${{env.SG_COMMIT}} + cargo build --release + + - name: Checkout patch from bytecodealliance/wasmtime (pushed and triggering on this perf repo) + uses: actions/checkout@v3 + with: + submodules: true + path: wasmtime_commit + + - run: rustup update nightly && rustup default nightly + + - name: Build patch from bytecodealliance/wasmtime (pushed and triggering on this perf repo) + working-directory: ./wasmtime_commit + run: | + cargo --version + cargo build --release -p wasmtime-bench-api + cp target/release/libwasmtime_bench_api.so /tmp/wasmtime_commit.so + + - name: Checkout main from bytecodealliance/wasmtime + uses: actions/checkout@v3 + with: + ref: 'main' + repository: 'bytecodealliance/wasmtime' + submodules: true + path: wasmtime_main + + - name: Build main from bytecodealliance/wasmtime + working-directory: ./wasmtime_main + run: | + cargo build --release -p wasmtime-bench-api + cp target/release/libwasmtime_bench_api.so /tmp/wasmtime_main.so + + - name: Run performance tests + working-directory: ../sightglass + run: | + cargo run -- \ + benchmark \ + --processes 5 \ + --iterations-per-process 5 \ + --engine /tmp/wasmtime_main.so \ + --engine /tmp/wasmtime_commit.so \ + --output-file /tmp/results.txt + + - name: Print Results + run: cat /tmp/results.txt + + - id: get-comment-body + name: Create Results Body + run: | + body="$(cat /tmp/results.txt)" + body="${body//'%'/'%25'}" + body="${body//$'\n'/'%0A'}" + body="${body//$'\r'/'%0D'}" + echo "::set-output name=body::$body" + + - name: Publish Results + run: | + curl -X POST -H "Accept: application/vnd.github.v3+json" \ + -H "Authorization: token ${{ secrets.WASMTIME_PUBLISHING_TOKEN }}" \ + ${{ github.event.head_commit.message }} \ + -d '{"body": ${{ toJSON(steps.get-comment-body.outputs.body) }}}' diff --git a/.github/workflows/publish-to-cratesio.yml b/.github/workflows/publish-to-cratesio.yml index 7fe1991f4e34..6d05b66778a4 100644 --- a/.github/workflows/publish-to-cratesio.yml +++ b/.github/workflows/publish-to-cratesio.yml @@ -14,7 +14,7 @@ jobs: if: github.repository == 'bytecodealliance/wasmtime' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - run: rustup update stable && rustup default stable diff --git a/.github/workflows/push-tag.yml b/.github/workflows/push-tag.yml index 97c67608fbd7..9fb730b0e31b 100644 --- a/.github/workflows/push-tag.yml +++ b/.github/workflows/push-tag.yml @@ -17,23 +17,23 @@ jobs: if: github.repository == 'bytecodealliance/wasmtime' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true fetch-depth: 0 - name: Test if tag is needed run: | git log ${{ github.event.before }}...${{ github.event.after }} | tee main.log - version=$(grep 'version =' Cargo.toml | head -n 1 | sed 's/.*"\(.*\)"/\1/') + version=$(grep '^version =' Cargo.toml | head -n 1 | sed 's/.*"\(.*\)"/\1/') echo "version: $version" - echo "::set-output name=version::$version" - echo "::set-output name=sha::$(git rev-parse HEAD)" + echo "version=$version" >> $GITHUB_OUTPUT + echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT if grep -q "automatically-tag-and-release-this-commit" main.log; then echo push-tag - echo "::set-output name=push_tag::yes" + echo "push_tag=yes" >> $GITHUB_OUTPUT else echo no-push-tag - echo "::set-output name=push_tag::no" + echo "push_tag=no" >> $GITHUB_OUTPUT fi id: tag - name: Push the tag diff --git a/.github/workflows/release-process.yml b/.github/workflows/release-process.yml index 7caeeb8b5e20..fcbc8382f80c 100644 --- a/.github/workflows/release-process.yml +++ b/.github/workflows/release-process.yml @@ -38,7 +38,7 @@ jobs: name: Run the release process runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: true - name: Setup @@ -117,7 +117,7 @@ jobs: rustc ci/update-release-date.rs -o /tmp/update-release-date /tmp/update-release-date $(date +'%Y-%m-%d') - git commit -a -F-<]` instead of the + prior raw slice return. + [#5240](https://github.com/bytecodealliance/wasmtime/pull/5240) + +* Creation of a `WasiCtx` will no longer unconditionally acquire randomness from + the OS, instead using the `rand::thread_rng()` function in Rust which is only + periodically reseeded with randomness from the OS. + [#5244](https://github.com/bytecodealliance/wasmtime/pull/5244) + +* Codegen of dynamically-bounds-checked wasm memory accesses has been improved. + [#5190](https://github.com/bytecodealliance/wasmtime/pull/5190) + +* Wasmtime will now emit inline stack probes in generated functions for x86\_64, + aarch64, and riscv64 architectures. This guarantees a process abort if an + engine was misconfigured to give wasm too much stack instead of optionally + allowing wasm to skip the guard page. + [#5350](https://github.com/bytecodealliance/wasmtime/pull/5350) + [#5353](https://github.com/bytecodealliance/wasmtime/pull/5353) + +### Fixed + +* Dropping a `Module` will now release kernel resources in-use by the pooling + allocator when enabled instead of waiting for a new instance to be + re-instantiated into prior slots. + [#5321](https://github.com/bytecodealliance/wasmtime/pull/5321) + +-------------------------------------------------------------------------------- + +## 3.0.1 + +Released 2022-12-01. + +### Fixed + +* The instruction cache is now flushed for AArch64 Android. + [#5331](https://github.com/bytecodealliance/wasmtime/pull/5331) + +* Building for FreeBSD and Android has been fixed. + [#5323](https://github.com/bytecodealliance/wasmtime/pull/5323) + +-------------------------------------------------------------------------------- + +## 3.0.0 + +Released 2022-11-21 + +### Added + +* New `WasiCtx::{push_file, push_dir}` methods exist for embedders to add their + own objects. + [#5027](https://github.com/bytecodealliance/wasmtime/pull/5027) + +* Wasmtime's `component-model` support now supports `async` host functions and + embedding in the same manner as core wasm. + [#5055](https://github.com/bytecodealliance/wasmtime/pull/5055) + +* The `wasmtime` CLI executable now supports a `--max-wasm-stack` flag. + [#5156](https://github.com/bytecodealliance/wasmtime/pull/5156) + +* AOT compilation support has been implemented for components (aka the + `component-model` feature of the Wasmtime crate). + [#5160](https://github.com/bytecodealliance/wasmtime/pull/5160) + +* A new `wasi_config_set_stdin_bytes` function is available in the C API to set + the stdin of a WASI-using module from an in-memory slice. + [#5179](https://github.com/bytecodealliance/wasmtime/pull/5179) + +* When using the pooling allocator there are now options to reset memory with + `memset` instead of `madvisev` on Linux to keep pages resident in memory to + reduce page faults when reusing linear memory slots. + [#5207](https://github.com/bytecodealliance/wasmtime/pull/5207) + +### Changed + +* Consuming 0 fuel with 0 fuel left is now considered to succeed. Additionally a + store may not consume its last unit of fuel. + [#5013](https://github.com/bytecodealliance/wasmtime/pull/5013) + +* A number of variants in the `wasi_common::ErrorKind` enum have been removed. + [#5015](https://github.com/bytecodealliance/wasmtime/pull/5015) + +* Methods on `WasiDir` now error-by-default instead of requiring a definition by + default. + [#5019](https://github.com/bytecodealliance/wasmtime/pull/5019) + +* Bindings generated by the `wiggle` crate now always depend on the `wasmtime` + crate meaning crates like `wasi-common` no longer compile for platforms such + as `wasm32-unknown-emscripten`. + [#5137](https://github.com/bytecodealliance/wasmtime/pull/5137) + +* Error handling in the `wasmtime` crate's API has been changed to primarily + work with `anyhow::Error` for custom errors. The `Trap` type has been replaced + with a simple `enum Trap { ... }` and backtrace information is now stored as a + `WasmBacktrace` type inserted as context into an `anyhow::Error`. + Host-functions are expected to return `anyhow::Result` instead of the prior + `Trap` error return from before. Additionally the old `Trap::i32_exit` + constructor is now a concrete `wasi_commont::I32Exit` type which can be tested + for with a `downcast_ref` on the error returned from Wasmtime. + [#5149](https://github.com/bytecodealliance/wasmtime/pull/5149) + +* Configuration of the pooling allocator is now done through a builder-style + `PoolingAllocationConfig` API instead of the prior enum-variant API. + [#5205](https://github.com/bytecodealliance/wasmtime/pull/5205) + +### Fixed + +* The instruction cache is now properly flushed for AArch64 on Windows. + [#4997](https://github.com/bytecodealliance/wasmtime/pull/4997) + +* Backtrace capturing with many sequences of wasm->host calls on the stack no + longer exhibit quadratic capturing behavior. + [#5049](https://github.com/bytecodealliance/wasmtime/pull/5049) + +-------------------------------------------------------------------------------- + +## 2.0.2 + +Released 2022-11-10. + +### Fixed + +* [CVE-2022-39392] - modules may perform out-of-bounds reads/writes when the + pooling allocator was configured with `memory_pages: 0`. + +* [CVE-2022-39393] - data can be leaked between instances when using the pooling + allocator. + +* [CVE-2022-39394] - An incorrect Rust signature for the C API + `wasmtime_trap_code` function could lead to an out-of-bounds write of three + zero bytes. + +[CVE-2022-39392]: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-44mr-8vmm-wjhg +[CVE-2022-39393]: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-wh6w-3828-g9qf +[CVE-2022-39394]: https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-h84q-m8rr-3v9q + +-------------------------------------------------------------------------------- + +## 2.0.1 + +Released 2022-10-27. + +### Fixed + +* A compilation error when building only the `wasmtime` crate on Windows with + only the default features enabled has been fixed. + [#5134](https://github.com/bytecodealliance/wasmtime/pull/5134) + +### Changed + +* The `rayon` dependency added to `cranelift-isle` in 2.0.0 has been removed to + improve the compile time of the `cranelift-codegen` crate. + [#5101](https://github.com/bytecodealliance/wasmtime/pull/5101) + +-------------------------------------------------------------------------------- + +## 2.0.0 + +Released 2022-10-20 + +### Added + +* Cranelift has gained support for forward-edge CFI on the AArch64 backend. + [#3693](https://github.com/bytecodealliance/wasmtime/pull/3693) + +* A `--disable-parallel-compilation` CLI flag is now implemented for `wasmtime`. + [#4911](https://github.com/bytecodealliance/wasmtime/pull/4911) + +* [Tier 3] support has been added for for RISC-V 64 with a new backend in + Cranelift for this architecture. + [#4271](https://github.com/bytecodealliance/wasmtime/pull/4271) + +* Basic [tier 3] support for Windows ARM64 has been added but features such as + traps don't work at this time. + [#4990](https://github.com/bytecodealliance/wasmtime/pull/4990) + +### Changed + +* The implementation of the `random_get` function in `wasi-common` is now faster + by using a userspace CSPRNG rather than the OS for randomness. + [#4917](https://github.com/bytecodealliance/wasmtime/pull/4917) + +* The AArch64 backend has completed its transition to ISLE. + [#4851](https://github.com/bytecodealliance/wasmtime/pull/4851) + [#4866](https://github.com/bytecodealliance/wasmtime/pull/4866) + [#4898](https://github.com/bytecodealliance/wasmtime/pull/4898) + [#4884](https://github.com/bytecodealliance/wasmtime/pull/4884) + [#4820](https://github.com/bytecodealliance/wasmtime/pull/4820) + [#4913](https://github.com/bytecodealliance/wasmtime/pull/4913) + [#4942](https://github.com/bytecodealliance/wasmtime/pull/4942) + [#4943](https://github.com/bytecodealliance/wasmtime/pull/4943) + +* The size of the `sigaltstack` allocated per-thread for signal handling has + been increased from 16k to 64k. + [#4964](https://github.com/bytecodealliance/wasmtime/pull/4964) + + +[Tier 3]: https://docs.wasmtime.dev/stability-tiers.html + +-------------------------------------------------------------------------------- + +## 1.0.2 + +Released 2022-11-10. + +### Fixed + +* [CVE-2022-39392] - modules may perform out-of-bounds reads/writes when the + pooling allocator was configured with `memory_pages: 0`. + +* [CVE-2022-39393] - data can be leaked between instances when using the pooling + allocator. + +* [CVE-2022-39394] - An incorrect Rust signature for the C API + `wasmtime_trap_code` function could lead to an out-of-bounds write of three + zero bytes. + +-------------------------------------------------------------------------------- + +## 1.0.1 + +Released 2022-09-26 + +This is a patch release that incorporates a fix for a miscompilation of an +atomic-CAS operator on aarch64. The instruction is not usable from Wasmtime +with default settings, but may be used if the Wasm atomics extension is +enabled. The bug may also be reachable via other uses of Cranelift. Thanks to +@bjorn3 for reporting and debugging this issue! + +### Fixed + +* Fixed a miscompilation of `atomic_cas` on aarch64. The output register was + swapped with a temporary register in the register-allocator constraints. + [#4959](https://github.com/bytecodealliance/wasmtime/pull/4959) + [#4960](https://github.com/bytecodealliance/wasmtime/pull/4960) + +-------------------------------------------------------------------------------- + +## 1.0.0 + +Released 2022-09-20 + +This release marks the official 1.0 release of Wasmtime and represents the +culmination of the work amongst over 300 contributors. Wasmtime has been +battle-tested in production through multiple embeddings for quite some time now +and we're confident in releasing a 1.0 version to signify the stability and +quality of the Wasmtime engine. + +More information about Wasmtime's 1.0 release is on the [Bytecode Alliance's +blog][ba-blog] with separate posts on [Wasmtime's performance +features][ba-perf], [Wasmtime's security story][ba-security], and [the 1.0 +release announcement][ba-1.0]. + +As a reminder the 2.0 release of Wasmtime is scheduled for one month from now on +October 20th. For more information see the [RFC on Wasmtime's 1.0 +release][rfc-1.0]. + +[ba-blog]: https://bytecodealliance.org/articles/ +[ba-perf]: https://bytecodealliance.org/articles/wasmtime-10-performance +[ba-security]: https://bytecodealliance.org/articles/security-and-correctness-in-wasmtime +[ba-1.0]: https://bytecodealliance.org/articles/wasmtime-1-0-fast-safe-and-now-production-ready.md +[rfc-1.0]: https://github.com/bytecodealliance/rfcs/blob/main/accepted/wasmtime-one-dot-oh.md + +### Added + +* An incremental compilation cache for Cranelift has been added which can be + enabled with `Config::enable_incremental_compilation`, and this option is + disabled by default for now. The incremental compilation cache has been + measured to improve compile times for cold uncached modules as well due to + some wasm modules having similar-enough functions internally. + [#4551](https://github.com/bytecodealliance/wasmtime/pull/4551) + +* Source tarballs are now available as part of Wasmtime's release artifacts. + [#4294](https://github.com/bytecodealliance/wasmtime/pull/4294) + +* WASI APIs that specify the REALTIME clock are now supported. + [#4777](https://github.com/bytecodealliance/wasmtime/pull/4777) + +* WASI's socket functions are now fully implemented. + [#4776](https://github.com/bytecodealliance/wasmtime/pull/4776) + +* The native call stack for async-executed wasm functions are no longer + automatically reset to zero after the stack is returned to the pool when using + the pooling allocator. A `Config::async_stack_zeroing` option has been added + to restore the old behavior of zero-on-return-to-pool. + [#4813](https://github.com/bytecodealliance/wasmtime/pull/4813) + +* Inline stack probing has been implemented for the Cranelift x64 backend. + [#4747](https://github.com/bytecodealliance/wasmtime/pull/4747) + +### Changed + +* Generating of native unwind information has moved from a + `Config::wasm_backtrace` option to a new `Config::native_unwind_info` option + and is enabled by default. + [#4643](https://github.com/bytecodealliance/wasmtime/pull/4643) + +* The `memory-init-cow` feature is now enabled by default in the C API. + [#4690](https://github.com/bytecodealliance/wasmtime/pull/4690) + +* Back-edge CFI is now enabled by default on AArch64 macOS. + [#4720](https://github.com/bytecodealliance/wasmtime/pull/4720) + +* WASI calls will no longer return NOTCAPABLE in preparation for the removal of + the rights system from WASI. + [#4666](https://github.com/bytecodealliance/wasmtime/pull/4666) + +### Internal + +This section of the release notes shouldn't affect external users since no +public-facing APIs are affected, but serves as a place to document larger +changes internally within Wasmtime. + +* Differential fuzzing has been refactored and improved into one fuzzing target + which can execute against any of Wasmtime itself (configured differently), + wasmi, V8, or the spec interpreter. Fuzzing now executes each exported + function with fuzz-generated inputs and the contents of all of memory and each + exported global is compared after each execution. Additionally more + interesting shapes of modules are also possible to generate. + [#4515](https://github.com/bytecodealliance/wasmtime/pull/4515) + [#4735](https://github.com/bytecodealliance/wasmtime/pull/4735) + [#4737](https://github.com/bytecodealliance/wasmtime/pull/4737) + [#4739](https://github.com/bytecodealliance/wasmtime/pull/4739) + [#4774](https://github.com/bytecodealliance/wasmtime/pull/4774) + [#4773](https://github.com/bytecodealliance/wasmtime/pull/4773) + [#4845](https://github.com/bytecodealliance/wasmtime/pull/4845) + [#4672](https://github.com/bytecodealliance/wasmtime/pull/4672) + [#4674](https://github.com/bytecodealliance/wasmtime/pull/4674) + +* The x64 backend for Cranelift has been fully migrated to ISLE. + [#4619](https://github.com/bytecodealliance/wasmtime/pull/4619) + [#4625](https://github.com/bytecodealliance/wasmtime/pull/4625) + [#4645](https://github.com/bytecodealliance/wasmtime/pull/4645) + [#4650](https://github.com/bytecodealliance/wasmtime/pull/4650) + [#4684](https://github.com/bytecodealliance/wasmtime/pull/4684) + [#4704](https://github.com/bytecodealliance/wasmtime/pull/4704) + [#4718](https://github.com/bytecodealliance/wasmtime/pull/4718) + [#4726](https://github.com/bytecodealliance/wasmtime/pull/4726) + [#4722](https://github.com/bytecodealliance/wasmtime/pull/4722) + [#4729](https://github.com/bytecodealliance/wasmtime/pull/4729) + [#4730](https://github.com/bytecodealliance/wasmtime/pull/4730) + [#4741](https://github.com/bytecodealliance/wasmtime/pull/4741) + [#4763](https://github.com/bytecodealliance/wasmtime/pull/4763) + [#4772](https://github.com/bytecodealliance/wasmtime/pull/4772) + [#4780](https://github.com/bytecodealliance/wasmtime/pull/4780) + [#4787](https://github.com/bytecodealliance/wasmtime/pull/4787) + [#4793](https://github.com/bytecodealliance/wasmtime/pull/4793) + [#4809](https://github.com/bytecodealliance/wasmtime/pull/4809) + +* The AArch64 backend for Cranelift has seen significant progress in being + ported to ISLE. + [#4608](https://github.com/bytecodealliance/wasmtime/pull/4608) + [#4639](https://github.com/bytecodealliance/wasmtime/pull/4639) + [#4634](https://github.com/bytecodealliance/wasmtime/pull/4634) + [#4748](https://github.com/bytecodealliance/wasmtime/pull/4748) + [#4750](https://github.com/bytecodealliance/wasmtime/pull/4750) + [#4751](https://github.com/bytecodealliance/wasmtime/pull/4751) + [#4753](https://github.com/bytecodealliance/wasmtime/pull/4753) + [#4788](https://github.com/bytecodealliance/wasmtime/pull/4788) + [#4796](https://github.com/bytecodealliance/wasmtime/pull/4796) + [#4785](https://github.com/bytecodealliance/wasmtime/pull/4785) + [#4819](https://github.com/bytecodealliance/wasmtime/pull/4819) + [#4821](https://github.com/bytecodealliance/wasmtime/pull/4821) + [#4832](https://github.com/bytecodealliance/wasmtime/pull/4832) + +* The s390x backend has seen improvements and additions to fully support the + Cranelift backend for rustc. + [#4682](https://github.com/bytecodealliance/wasmtime/pull/4682) + [#4702](https://github.com/bytecodealliance/wasmtime/pull/4702) + [#4616](https://github.com/bytecodealliance/wasmtime/pull/4616) + [#4680](https://github.com/bytecodealliance/wasmtime/pull/4680) + +* Significant improvements have been made to Cranelift-based fuzzing with more + supported features and more instructions being fuzzed. + [#4589](https://github.com/bytecodealliance/wasmtime/pull/4589) + [#4591](https://github.com/bytecodealliance/wasmtime/pull/4591) + [#4665](https://github.com/bytecodealliance/wasmtime/pull/4665) + [#4670](https://github.com/bytecodealliance/wasmtime/pull/4670) + [#4590](https://github.com/bytecodealliance/wasmtime/pull/4590) + [#4375](https://github.com/bytecodealliance/wasmtime/pull/4375) + [#4519](https://github.com/bytecodealliance/wasmtime/pull/4519) + [#4696](https://github.com/bytecodealliance/wasmtime/pull/4696) + [#4700](https://github.com/bytecodealliance/wasmtime/pull/4700) + [#4703](https://github.com/bytecodealliance/wasmtime/pull/4703) + [#4602](https://github.com/bytecodealliance/wasmtime/pull/4602) + [#4713](https://github.com/bytecodealliance/wasmtime/pull/4713) + [#4738](https://github.com/bytecodealliance/wasmtime/pull/4738) + [#4667](https://github.com/bytecodealliance/wasmtime/pull/4667) + [#4782](https://github.com/bytecodealliance/wasmtime/pull/4782) + [#4783](https://github.com/bytecodealliance/wasmtime/pull/4783) + [#4800](https://github.com/bytecodealliance/wasmtime/pull/4800) + +* Optimization work on cranelift has continued across various dimensions for + some modest compile-time improvements. + [#4621](https://github.com/bytecodealliance/wasmtime/pull/4621) + [#4701](https://github.com/bytecodealliance/wasmtime/pull/4701) + [#4697](https://github.com/bytecodealliance/wasmtime/pull/4697) + [#4711](https://github.com/bytecodealliance/wasmtime/pull/4711) + [#4710](https://github.com/bytecodealliance/wasmtime/pull/4710) + [#4829](https://github.com/bytecodealliance/wasmtime/pull/4829) + +-------------------------------------------------------------------------------- + +## 0.40.0 + +Released 2022-08-20 + +This was a relatively quiet release in terms of user-facing features where most +of the work was around the internals of Wasmtime and Cranelift. Improvements +internally have been made along the lines of: + +* Many more instructions are now implemented with ISLE instead of handwritten + lowerings. +* Many improvements to the cranelift-based fuzzing. +* Many platform improvements for s390x including full SIMD support, running + `rustc_codegen_cranelift` with features like `i128`, supporting more + ABIs, etc. +* Much more of the component model has been implemented and is now fuzzed. + +Finally this release is currently scheduled to be the last `0.*` release of +Wasmtime. The upcoming release of Wasmtime on September 20 is planned to be +Wasmtime's 1.0 release. More information about what 1.0 means for Wasmtime is +available in the [1.0 RFC] + +[1.0 RFC]: https://github.com/bytecodealliance/rfcs/blob/main/accepted/wasmtime-one-dot-oh.md + +### Added + +* Stack walking has been reimplemented with frame pointers rather than with + native unwind information. This means that backtraces are feasible to capture + in performance-critical environments and in general stack walking is much + faster than before. + [#4431](https://github.com/bytecodealliance/wasmtime/pull/4431) + +* The WebAssembly `simd` proposal is now fully implemented for the s390x + backend. + [#4427](https://github.com/bytecodealliance/wasmtime/pull/4427) + +* Support for AArch64 has been added in the experimental native debuginfo + support that Wasmtime has. + [#4468](https://github.com/bytecodealliance/wasmtime/pull/4468) + +* Support building the C API of Wasmtime with CMake has been added. + [#4369](https://github.com/bytecodealliance/wasmtime/pull/4369) + +* Clarification was added to Wasmtime's documentation about "tiers of support" + for various features. + [#4479](https://github.com/bytecodealliance/wasmtime/pull/4479) + +### Fixed + +* Support for `filestat_get` has been improved for stdio streams in WASI. + [#4531](https://github.com/bytecodealliance/wasmtime/pull/4531) + +* Enabling the `vtune` feature no longer breaks builds on AArch64. + [#4533](https://github.com/bytecodealliance/wasmtime/pull/4533) + -------------------------------------------------------------------------------- ## 0.39.1 diff --git a/benches/call.rs b/benches/call.rs index f1e5790b5deb..9ec51d35d24e 100644 --- a/benches/call.rs +++ b/benches/call.rs @@ -117,7 +117,7 @@ fn bench_host_to_wasm( // below. c.bench_function(&format!("host-to-wasm - typed - {}", name), |b| { let typed = instance - .get_typed_func::(&mut *store, name) + .get_typed_func::(&mut *store, name) .unwrap(); b.iter(|| { let results = if is_async.use_async() { @@ -370,7 +370,7 @@ fn wasm_to_host(c: &mut Criterion) { ) { group.bench_function(&format!("wasm-to-host - {} - nop", desc), |b| { let run = instance - .get_typed_func::(&mut *store, "run-nop") + .get_typed_func::(&mut *store, "run-nop") .unwrap(); b.iter_custom(|iters| { let start = Instant::now(); @@ -386,7 +386,7 @@ fn wasm_to_host(c: &mut Criterion) { &format!("wasm-to-host - {} - nop-params-and-results", desc), |b| { let run = instance - .get_typed_func::(&mut *store, "run-nop-params-and-results") + .get_typed_func::(&mut *store, "run-nop-params-and-results") .unwrap(); b.iter_custom(|iters| { let start = Instant::now(); diff --git a/benches/instantiation.rs b/benches/instantiation.rs index 356dbeb5a2e4..33a1e2f2ba47 100644 --- a/benches/instantiation.rs +++ b/benches/instantiation.rs @@ -46,7 +46,7 @@ fn bench_sequential(c: &mut Criterion, path: &Path) { let mut linker = Linker::new(&engine); wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap(); let pre = linker - .instantiate_pre(&mut store(&engine), &module) + .instantiate_pre(&module) .expect("failed to pre-instantiate"); (engine, pre) }); @@ -77,7 +77,7 @@ fn bench_parallel(c: &mut Criterion, path: &Path) { wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap(); let pre = Arc::new( linker - .instantiate_pre(&mut store(&engine), &module) + .instantiate_pre(&module) .expect("failed to pre-instantiate"), ); (engine, pre) @@ -202,13 +202,11 @@ fn bench_instantiation(c: &mut Criterion) { fn strategies() -> impl Iterator { [ InstanceAllocationStrategy::OnDemand, - InstanceAllocationStrategy::Pooling { - strategy: Default::default(), - instance_limits: InstanceLimits { - memory_pages: 10_000, - ..Default::default() - }, - }, + InstanceAllocationStrategy::Pooling({ + let mut config = PoolingAllocationConfig::default(); + config.instance_memory_pages(10_000); + config + }), ] .into_iter() } diff --git a/benches/thread_eager_init.rs b/benches/thread_eager_init.rs index dbd5617a6f58..8572a335c406 100644 --- a/benches/thread_eager_init.rs +++ b/benches/thread_eager_init.rs @@ -4,7 +4,7 @@ use std::time::{Duration, Instant}; use wasmtime::*; fn measure_execution_time(c: &mut Criterion) { - // Baseline performance: a single measurment covers both initializing + // Baseline performance: a single measurement covers both initializing // thread local resources and executing the first call. // // The other two bench functions should sum to this duration. @@ -55,7 +55,7 @@ fn duration_of_call(engine: &Engine, module: &Module) -> Duration { let mut store = Store::new(engine, ()); let inst = Instance::new(&mut store, module, &[]).expect("instantiate"); let f = inst.get_func(&mut store, "f").expect("get f"); - let f = f.typed::<(), (), _>(&store).expect("type f"); + let f = f.typed::<(), ()>(&store).expect("type f"); let call = Instant::now(); f.call(&mut store, ()).expect("call f"); @@ -91,15 +91,10 @@ fn test_setup() -> (Engine, Module) { // We only expect to create one Instance at a time, with a single memory. let pool_count = 10; + let mut pool = PoolingAllocationConfig::default(); + pool.instance_count(pool_count).instance_memory_pages(1); let mut config = Config::new(); - config.allocation_strategy(InstanceAllocationStrategy::Pooling { - strategy: PoolingAllocationStrategy::NextAvailable, - instance_limits: InstanceLimits { - count: pool_count, - memory_pages: 1, - ..Default::default() - }, - }); + config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool)); let engine = Engine::new(&config).unwrap(); // The module has a memory (shouldn't matter) and a single function which is a no-op. diff --git a/benches/trap.rs b/benches/trap.rs index 00a23759c011..979f30676445 100644 --- a/benches/trap.rs +++ b/benches/trap.rs @@ -9,6 +9,7 @@ fn bench_traps(c: &mut Criterion) { bench_multi_threaded_traps(c); bench_many_modules_registered_traps(c); bench_many_stack_frames_traps(c); + bench_host_wasm_frames_traps(c); } fn bench_multi_threaded_traps(c: &mut Criterion) { @@ -37,9 +38,8 @@ fn bench_multi_threaded_traps(c: &mut Criterion) { move || { let mut store = Store::new(&engine, ()); let instance = Instance::new(&mut store, &module, &[]).unwrap(); - let f = instance - .get_typed_func::<(), (), _>(&mut store, "") - .unwrap(); + let f = + instance.get_typed_func::<(), ()>(&mut store, "").unwrap(); // Notify the parent thread that we are // doing background work now. @@ -66,9 +66,7 @@ fn bench_multi_threaded_traps(c: &mut Criterion) { let mut store = Store::new(&engine, ()); let instance = Instance::new(&mut store, &module, &[]).unwrap(); - let f = instance - .get_typed_func::<(), (), _>(&mut store, "") - .unwrap(); + let f = instance.get_typed_func::<(), ()>(&mut store, "").unwrap(); // Measure how long it takes to do `iters` worth of traps // while there is a bunch of background work going on. @@ -110,9 +108,7 @@ fn bench_many_modules_registered_traps(c: &mut Criterion) { b.iter_custom(|iters| { let mut store = Store::new(&engine, ()); let instance = Instance::new(&mut store, modules.last().unwrap(), &[]).unwrap(); - let f = instance - .get_typed_func::<(), (), _>(&mut store, "") - .unwrap(); + let f = instance.get_typed_func::<(), ()>(&mut store, "").unwrap(); let start = std::time::Instant::now(); for _ in 0..iters { @@ -142,13 +138,71 @@ fn bench_many_stack_frames_traps(c: &mut Criterion) { b.iter_custom(|iters| { let mut store = Store::new(&engine, ()); let instance = Instance::new(&mut store, &module, &[]).unwrap(); + let f = instance.get_typed_func::<(), ()>(&mut store, "").unwrap(); + + let start = std::time::Instant::now(); + for _ in 0..iters { + assert!(f.call(&mut store, ()).is_err()); + } + start.elapsed() + }); + }, + ); + } + + group.finish() +} + +fn bench_host_wasm_frames_traps(c: &mut Criterion) { + let mut group = c.benchmark_group("host-wasm-frames-traps"); + + let wat = r#" + (module + (import "" "" (func $host_func (param i32))) + (func (export "f") (param i32) + local.get 0 + i32.eqz + if + unreachable + end + + local.get 0 + i32.const 1 + i32.sub + call $host_func + ) + ) + "#; + + let engine = Engine::default(); + let module = Module::new(&engine, wat).unwrap(); + + for num_stack_frames in vec![20, 40, 60, 80, 100, 120, 140, 160, 180, 200] { + group.throughput(Throughput::Elements(num_stack_frames)); + group.bench_with_input( + BenchmarkId::from_parameter(num_stack_frames), + &num_stack_frames, + |b, &num_stack_frames| { + b.iter_custom(|iters| { + let mut store = Store::new(&engine, ()); + let host_func = Func::new( + &mut store, + FuncType::new(vec![ValType::I32], vec![]), + |mut caller, args, _results| { + let f = caller.get_export("f").unwrap(); + let f = f.into_func().unwrap(); + f.call(caller, args, &mut [])?; + Ok(()) + }, + ); + let instance = Instance::new(&mut store, &module, &[host_func.into()]).unwrap(); let f = instance - .get_typed_func::<(), (), _>(&mut store, "") + .get_typed_func::<(i32,), ()>(&mut store, "f") .unwrap(); let start = std::time::Instant::now(); for _ in 0..iters { - assert!(f.call(&mut store, ()).is_err()); + assert!(f.call(&mut store, (num_stack_frames as i32,)).is_err()); } start.elapsed() }); diff --git a/benches/wasi.rs b/benches/wasi.rs new file mode 100644 index 000000000000..ae8a7c8c171d --- /dev/null +++ b/benches/wasi.rs @@ -0,0 +1,86 @@ +//! Measure some common WASI call scenarios. + +use criterion::{criterion_group, criterion_main, Criterion}; +use std::{fs::File, path::Path, time::Instant}; +use wasmtime::{Engine, Linker, Module, Store, TypedFunc}; +use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; + +criterion_group!(benches, bench_wasi); +criterion_main!(benches); + +fn bench_wasi(c: &mut Criterion) { + let _ = env_logger::try_init(); + + // Build a zero-filled test file if it does not yet exist. + let test_file = Path::new("benches/wasi/test.bin"); + if !test_file.is_file() { + let file = File::create(test_file).unwrap(); + file.set_len(4096).unwrap(); + } + + // Benchmark each `*.wat` file in the `wasi` directory. + for file in std::fs::read_dir("benches/wasi").unwrap() { + let path = file.unwrap().path(); + if path.extension().map(|e| e == "wat").unwrap_or(false) { + let wat = std::fs::read(&path).unwrap(); + let (mut store, run_fn) = instantiate(&wat); + let bench_name = format!("wasi/{}", path.file_name().unwrap().to_string_lossy()); + // To avoid overhead, the module itself must iterate the expected + // number of times in a specially-crafted `run` function (see + // `instantiate` for details). + c.bench_function(&bench_name, move |b| { + b.iter_custom(|iters| { + let start = Instant::now(); + let result = run_fn.call(&mut store, iters).unwrap(); + assert_eq!(iters, result); + start.elapsed() + }) + }); + } + } +} + +/// Compile and instantiate the Wasm module, returning the exported `run` +/// function. This function expects `run` to: +/// - have a single `u64` parameter indicating the number of loop iterations to +/// execute +/// - execute the body of the function for that number of loop iterations +/// - return a single `u64` indicating how many loop iterations were executed +/// (to double-check) +fn instantiate(wat: &[u8]) -> (Store, TypedFunc) { + let engine = Engine::default(); + let wasi = wasi_context(); + let mut store = Store::new(&engine, wasi); + let module = Module::new(&engine, wat).unwrap(); + let mut linker = Linker::new(&engine); + wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap(); + let instance = linker.instantiate(&mut store, &module).unwrap(); + let run = instance.get_typed_func(&mut store, "run").unwrap(); + (store, run) +} + +/// Build a WASI context with some actual data to retrieve. +fn wasi_context() -> WasiCtx { + let wasi = WasiCtxBuilder::new(); + wasi.envs(&[ + ("a".to_string(), "b".to_string()), + ("b".to_string(), "c".to_string()), + ("c".to_string(), "d".to_string()), + ]) + .unwrap() + .args(&[ + "exe".to_string(), + "--flag1".to_string(), + "--flag2".to_string(), + "--flag3".to_string(), + "--flag4".to_string(), + ]) + .unwrap() + .preopened_dir( + wasmtime_wasi::Dir::open_ambient_dir("benches/wasi", wasmtime_wasi::ambient_authority()) + .unwrap(), + "/", + ) + .unwrap() + .build() +} diff --git a/benches/wasi/.gitignore b/benches/wasi/.gitignore new file mode 100644 index 000000000000..a0c61d0c3ae4 --- /dev/null +++ b/benches/wasi/.gitignore @@ -0,0 +1 @@ +test.bin diff --git a/benches/wasi/get-current-time.wat b/benches/wasi/get-current-time.wat new file mode 100644 index 000000000000..8ac6ceb0758e --- /dev/null +++ b/benches/wasi/get-current-time.wat @@ -0,0 +1,22 @@ +(module + (import "wasi_snapshot_preview1" "clock_time_get" + (func $__wasi_clock_time_get (param i32 i64 i32) (result i32))) + (func (export "run") (param $iters i64) (result i64) + (local $i i64) + (local.set $i (i64.const 0)) + (loop $cont + ;; Retrieve the current time with the following parameters: + ;; - $clockid: here we use the enum value for $realtime + ;; - $precision: the maximum lag, which we set to 0 here + ;; - the address at which to write the u64 $timestamp + ;; Returns an error code. + (call $__wasi_clock_time_get (i32.const 1) (i64.const 0) (i32.const 0)) + (drop) + ;; Continue looping until $i reaches $iters. + (local.set $i (i64.add (local.get $i) (i64.const 1))) + (br_if $cont (i64.lt_u (local.get $i) (local.get $iters))) + ) + (local.get $i) + ) + (memory (export "memory") 1) +) diff --git a/benches/wasi/open-file.wat b/benches/wasi/open-file.wat new file mode 100644 index 000000000000..390562ee81c4 --- /dev/null +++ b/benches/wasi/open-file.wat @@ -0,0 +1,53 @@ +;; Repeatedly open and close `test.bin`. +(module + (import "wasi_snapshot_preview1" "path_open" + (func $__wasi_path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "fd_read" + (func $__wasi_fd_read (param i32 i32 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "fd_close" + (func $__wasi_fd_close (param i32) (result i32))) + (func (export "run") (param $iters i64) (result i64) + (local $i i64) + (local.set $i (i64.const 0)) + (loop $cont + ;; Open the file `test.bin` under the same directory as this WAT + ;; file; this assumes some prior set up of the preopens in + ;; `wasi.rs`. See https://github.com/WebAssembly/WASI/blob/d8da230b/phases/snapshot/witx/wasi_snapshot_preview1.witx#L346. + (call $__wasi_path_open + ;; The fd of the preopen under which to search for the file; + ;; the first three are the `std*` ones. + (i32.const 3) + ;; The lookup flags (i.e., whether to follow symlinks). + (i32.const 0) + ;; The path to the file under the initial fd. + (i32.const 0) + (i32.const 8) + ;; The open flags; in this case we will only attempt to read but + ;; this may attempt to create the file if it does not exist, see + ;; https://github.com/WebAssembly/WASI/blob/d8da230b/phases/snapshot/witxtypenames.witx#L444). + (i32.const 0) + ;; The base rights and the inheriting rights: here we only set + ;; the bits for the FD_READ and FD_READDIR capabilities. + (i64.const 0x2002) + (i64.const 0x2002) + ;; The file descriptor flags (e.g., whether to append, sync, + ;; etc.); see https://github.com/WebAssembly/WASI/blob/d8da230b/phases/snapshot/witx/typenames.witx#L385 + (i32.const 0) + ;; The address at which to store the opened fd (if the call + ;; succeeds) + (i32.const 16)) + (if (then unreachable)) + + ;; Close the open file handle we stored at offset 16. + (call $__wasi_fd_close (i32.load (i32.const 16))) + (if (then unreachable)) + + ;; Continue looping until $i reaches $iters. + (local.set $i (i64.add (local.get $i) (i64.const 1))) + (br_if $cont (i64.lt_u (local.get $i) (local.get $iters))) + ) + (local.get $i) + ) + (data (i32.const 0) "test.bin") + (memory (export "memory") 1) +) diff --git a/benches/wasi/read-arguments.wat b/benches/wasi/read-arguments.wat new file mode 100644 index 000000000000..c6f1d4a33dbe --- /dev/null +++ b/benches/wasi/read-arguments.wat @@ -0,0 +1,42 @@ +(module + (import "wasi_snapshot_preview1" "args_get" + (func $__wasi_args_get (param i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "args_sizes_get" + (func $__wasi_args_sizes_get (param i32 i32) (result i32))) + (func (export "run") (param $iters i64) (result i64) + (local $i i64) + (local.set $i (i64.const 0)) + (loop $cont + ;; Read the current argument list by: + ;; 1) retrieving the argument sizes and then + ;; 2) retrieving the argument data itself. + + ;; Retrieve the sizes of the arguments with parameters: + ;; - the address at which to write the number of arguments + ;; - the address at which to write the size of the argument buffer + ;; Returns an error code. + (call $__wasi_args_sizes_get (i32.const 0) (i32.const 4)) + (drop) + + ;; Read the arguments with parameters: + ;; - the address at which to write the array of argument pointers + ;; (i.e., one pointer per argument); here we overwrite the size + ;; written at address 0 + ;; - the address at which to write the buffer of argument strings + ;; (pointed to by the items written to the first address); we + ;; calculate where to start the buffer based on the size of the + ;; pointer list (i.e., number of arguments * 4 bytes per pointer) + ;; Returns an error code. + (call $__wasi_args_get + (i32.const 0) + (i32.mul (i32.load (i32.const 0)) (i32.const 4))) + (drop) + + ;; Continue looping until $i reaches $iters. + (local.set $i (i64.add (local.get $i) (i64.const 1))) + (br_if $cont (i64.lt_u (local.get $i) (local.get $iters))) + ) + (local.get $i) + ) + (memory (export "memory") 1) +) diff --git a/benches/wasi/read-dir.wat b/benches/wasi/read-dir.wat new file mode 100644 index 000000000000..6d9004a712c7 --- /dev/null +++ b/benches/wasi/read-dir.wat @@ -0,0 +1,41 @@ +;; Read the directory entries of the preopened directory. +(module + (import "wasi_snapshot_preview1" "fd_readdir" + (func $__wasi_fd_readdir (param i32 i32 i32 i64 i32) (result i32))) + (func (export "run") (param $iters i64) (result i64) + (local $i i64) + (local.set $i (i64.const 0)) + + (if (i32.ne (i32.load (i32.const 0)) (i32.const 0)) + (then unreachable)) + + (loop $cont + ;; Read the file into the sole iovec buffer. + (call $__wasi_fd_readdir + ;; The fd of the preopened directory; the first three are the + ;; `std*` ones. + (i32.const 3) + ;; The buffer address at which to store the entries and the + ;; length of the buffer. + (i32.const 16) + (i32.const 4096) + ;; The location at which to start reading entries in the + ;; directory; here we start at the first entry. + (i64.const 0) + ;; The address at which to store the number of bytes read. + (i32.const 8)) + (drop) + + ;; Check that we indeed read at least 380 bytes of directory + ;; entries. + (if (i32.lt_u (i32.load (i32.const 8)) (i32.const 300)) + (then unreachable)) + + ;; Continue looping until $i reaches $iters. + (local.set $i (i64.add (local.get $i) (i64.const 1))) + (br_if $cont (i64.lt_u (local.get $i) (local.get $iters))) + ) + (local.get $i) + ) + (memory (export "memory") 1) +) diff --git a/benches/wasi/read-environment.wat b/benches/wasi/read-environment.wat new file mode 100644 index 000000000000..50f50b22751f --- /dev/null +++ b/benches/wasi/read-environment.wat @@ -0,0 +1,45 @@ +(module + (import "wasi_snapshot_preview1" "environ_get" + (func $__wasi_environ_get (param i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "environ_sizes_get" + (func $__wasi_environ_sizes_get (param i32 i32) (result i32))) + (func (export "run") (param $iters i64) (result i64) + (local $i i64) + (local.set $i (i64.const 0)) + (loop $cont + ;; Read the current environment key-value pairs by: + ;; 1) retrieving the environment sizes and then + ;; 2) retrieving the environment data itself. + + ;; Retrieve the sizes of the environment with parameters: + ;; - the address at which to write the number of environment + ;; variables + ;; - the address at which to write the size of the environment + ;; buffer + ;; Returns an error code. + (call $__wasi_environ_sizes_get (i32.const 0) (i32.const 4)) + (drop) + + ;; Read the environment with parameters: + ;; - the address at which to write the array of environment pointers + ;; (i.e., one pointer per key-value pair); here we overwrite + ;; the size written at address 0 + ;; - the address at which to write the buffer of key-value pairs + ;; (pointed to by the items written to the first address); we + ;; calculate where to start the buffer based on the size of the + ;; pointer list (i.e., number of key-value pairs * 4 bytes per + ;; pointer) + ;; Returns an error code. + (call $__wasi_environ_get + (i32.const 0) + (i32.mul (i32.load (i32.const 0)) (i32.const 4))) + (drop) + + ;; Continue looping until $i reaches $iters. + (local.set $i (i64.add (local.get $i) (i64.const 1))) + (br_if $cont (i64.lt_u (local.get $i) (local.get $iters))) + ) + (local.get $i) + ) + (memory (export "memory") 1) +) diff --git a/benches/wasi/read-file.wat b/benches/wasi/read-file.wat new file mode 100644 index 000000000000..9e3d2bb5be2b --- /dev/null +++ b/benches/wasi/read-file.wat @@ -0,0 +1,78 @@ +;; Repeatedly read the contents of `test.bin`. +(module + (import "wasi_snapshot_preview1" "path_open" + (func $__wasi_path_open (param i32 i32 i32 i32 i32 i64 i64 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "fd_read" + (func $__wasi_fd_read (param i32 i32 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "fd_close" + (func $__wasi_fd_close (param i32) (result i32))) + (func (export "run") (param $iters i64) (result i64) + (local $i i64) + (local.set $i (i64.const 0)) + + ;; Set up the iovec list; the memory usage for this module should be: + ;; - offset 0 => file name + ;; - offset 16 => the opened file descriptor + ;; - offset 24 => the number of read bytes + ;; - offset 32 => the iovec list + ;; - offset 48 => the first (and only) iovec buffer + (i32.store (i32.const 32) (i32.const 48)) + (i32.store (i32.const 36) (i32.const 4096)) + + (loop $cont + ;; Open the file `test.bin` under the same directory as this WAT + ;; file; this assumes some prior set up of the preopens in + ;; `wasi.rs`. See https://github.com/WebAssembly/WASI/blob/d8da230b/phases/snapshot/witx/wasi_snapshot_preview1.witx#L346. + (call $__wasi_path_open + ;; The fd of the preopen under which to search for the file; + ;; the first three are the `std*` ones. + (i32.const 3) + ;; The lookup flags (i.e., whether to follow symlinks). + (i32.const 0) + ;; The path to the file under the initial fd. + (i32.const 0) + (i32.const 8) + ;; The open flags; in this case we will only attempt to read but + ;; this may attempt to create the file if it does not exist, see + ;; https://github.com/WebAssembly/WASI/blob/d8da230b/phases/snapshot/witxtypenames.witx#L444). + (i32.const 0) + ;; The base rights and the inheriting rights: here we only set + ;; the bits for the FD_READ and FD_READDIR capabilities. + (i64.const 0x2002) + (i64.const 0x2002) + ;; The file descriptor flags (e.g., whether to append, sync, + ;; etc.); see https://github.com/WebAssembly/WASI/blob/d8da230b/phases/snapshot/witx/typenames.witx#L385 + (i32.const 0) + ;; The address at which to store the opened fd (if the call + ;; succeeds) + (i32.const 16)) + (if (then unreachable)) + + ;; Read the file into the sole iovec buffer. + (call $__wasi_fd_read + ;; The now-open fd stored at offset 16. + (i32.load (i32.const 16)) + ;; The address and size of the list of iovecs; here we only use + ;; a list of a single iovec set up outside the loop. + (i32.const 32) + (i32.const 1) + ;; The address at which to store the number of bytes read. + (i32.const 24)) + (if (then unreachable)) + ;; Check that we indeed read 4096 bytes. + (if (i32.ne (i32.load (i32.const 24)) (i32.const 4096)) + (then unreachable)) + + ;; Close the open file handle we stored at offset 16. + (call $__wasi_fd_close (i32.load (i32.const 16))) + (if (then unreachable)) + + ;; Continue looping until $i reaches $iters. + (local.set $i (i64.add (local.get $i) (i64.const 1))) + (br_if $cont (i64.lt_u (local.get $i) (local.get $iters))) + ) + (local.get $i) + ) + (data (i32.const 0) "test.bin") + (memory (export "memory") 1) +) diff --git a/build.rs b/build.rs index 1248cab7dc20..f9998f0847cf 100644 --- a/build.rs +++ b/build.rs @@ -12,6 +12,7 @@ use std::process::Command; fn main() -> anyhow::Result<()> { println!("cargo:rerun-if-changed=build.rs"); + let out_dir = PathBuf::from( env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"), ); @@ -44,6 +45,12 @@ fn main() -> anyhow::Result<()> { "tests/spec_testsuite/proposals/function-references", strategy, )?; + test_directory_module( + out, + "tests/spec_testsuite/proposals/multi-memory", + strategy, + )?; + test_directory_module(out, "tests/spec_testsuite/proposals/threads", strategy)?; } else { println!( "cargo:warning=The spec testsuite is disabled. To enable, run `git submodule \ @@ -63,7 +70,6 @@ fn main() -> anyhow::Result<()> { drop(Command::new("rustfmt").arg(&output).status()); Ok(()) } - fn test_directory_module( out: &mut String, path: impl AsRef, @@ -92,7 +98,7 @@ fn test_directory( return None; } // Ignore files starting with `.`, which could be editor temporary files - if p.file_stem()?.to_str()?.starts_with(".") { + if p.file_stem()?.to_str()?.starts_with('.') { return None; } Some(p) @@ -117,8 +123,7 @@ fn extract_name(path: impl AsRef) -> String { .expect("filename should have a stem") .to_str() .expect("filename should be representable as a string") - .replace("-", "_") - .replace("/", "_") + .replace(['-', '/'], "_") } fn with_test_module( @@ -164,7 +169,7 @@ fn write_testsuite_tests( " crate::wast::run_wast(r#\"{}\"#, crate::wast::Strategy::{}, {}).unwrap();", path.display(), strategy, - pooling + pooling, )?; writeln!(out, "}}")?; writeln!(out)?; @@ -173,19 +178,19 @@ fn write_testsuite_tests( /// Ignore tests that aren't supported yet. fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool { - match strategy { - "Cranelift" => match (testsuite, testname) { + assert_eq!(strategy, "Cranelift"); + match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() { + "s390x" => { // FIXME: These tests fail under qemu due to a qemu bug. - (_, "simd_f32x4_pmin_pmax") if platform_is_s390x() => return true, - (_, "simd_f64x2_pmin_pmax") if platform_is_s390x() => return true, - _ => {} - }, - _ => panic!("unrecognized strategy"), - } + testname == "simd_f32x4_pmin_pmax" || testname == "simd_f64x2_pmin_pmax" + } - false -} + // Currently the simd wasm proposal is not implemented in the riscv64 + // backend so skip all tests which could use simd. + "riscv64" => { + testsuite == "simd" || testname.contains("simd") || testname.contains("memory_multi") + } -fn platform_is_s390x() -> bool { - env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "s390x" + _ => false, + } } diff --git a/ci/build-src-tarball.sh b/ci/build-src-tarball.sh new file mode 100755 index 000000000000..ad7e5c916c58 --- /dev/null +++ b/ci/build-src-tarball.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -ex + +# Determine the name of the tarball +tag=dev +if [[ $GITHUB_REF == refs/tags/v* ]]; then + tag=${GITHUB_REF#refs/tags/} +fi +pkgname=wasmtime-$tag-src + +# Vendor all crates.io dependencies since this is supposed to be an +# offline-only-compatible tarball +mkdir .cargo +cargo vendor > .cargo/config.toml + +# Create the tarball from the destination +tar -czf /tmp/$pkgname.tar.gz --transform "s/^\./$pkgname/S" --exclude=.git . +mkdir -p dist +mv /tmp/$pkgname.tar.gz dist/ diff --git a/ci/docker/riscv64gc-linux/Dockerfile b/ci/docker/riscv64gc-linux/Dockerfile new file mode 100644 index 000000000000..522867a67cb7 --- /dev/null +++ b/ci/docker/riscv64gc-linux/Dockerfile @@ -0,0 +1,7 @@ +FROM ubuntu:22.04 + +RUN apt-get update -y && apt-get install -y gcc gcc-riscv64-linux-gnu ca-certificates + +ENV PATH=$PATH:/rust/bin +ENV CARGO_BUILD_TARGET=riscv64gc-unknown-linux-gnu +ENV CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc diff --git a/ci/run-tests.sh b/ci/run-tests.sh index f81b155f2d4a..db187b779e40 100755 --- a/ci/run-tests.sh +++ b/ci/run-tests.sh @@ -2,6 +2,7 @@ cargo test \ --features "test-programs/test_programs" \ + --features wasi-threads \ --workspace \ --exclude 'wasmtime-wasi-*' \ --exclude wasi-crypto \ diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index d4b728392f2f..7603fd261118 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -1,46 +1,54 @@ [package] name = "cranelift-tools" authors = ["The Cranelift Project Developers"] -version = "0.73.0" +version = "0.0.0" description = "Binaries for testing the Cranelift libraries" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/docs/index.md" repository = "https://github.com/bytecodealliance/wasmtime" publish = false -edition = "2021" +edition.workspace = true [[bin]] name = "clif-util" path = "src/clif-util.rs" +[[test]] +name = "filetests" +path = "tests/filetests.rs" +harness = false + [dependencies] cfg-if = "1.0" -cranelift-codegen = { path = "codegen", version = "0.88.0" } -cranelift-entity = { path = "entity", version = "0.88.0" } -cranelift-interpreter = { path = "interpreter", version = "0.88.0" } -cranelift-reader = { path = "reader", version = "0.88.0" } -cranelift-frontend = { path = "frontend", version = "0.88.0" } -cranelift-wasm = { path = "wasm", version = "0.88.0", optional = true } -cranelift-native = { path = "native", version = "0.88.0" } -cranelift-filetests = { path = "filetests", version = "0.73.0" } -cranelift-module = { path = "module", version = "0.88.0" } -cranelift-object = { path = "object", version = "0.88.0" } -cranelift-jit = { path = "jit", version = "0.88.0" } -cranelift-preopt = { path = "preopt", version = "0.88.0" } -cranelift = { path = "umbrella", version = "0.88.0" } +cranelift-codegen = { workspace = true, features = ["disas"] } +cranelift-entity = { workspace = true } +cranelift-interpreter = { workspace = true } +cranelift-reader = { workspace = true } +cranelift-frontend = { workspace = true } +cranelift-wasm = { workspace = true, optional = true } +cranelift-native = { workspace = true } +cranelift-filetests = { workspace = true } +cranelift-module = { workspace = true } +cranelift-object = { workspace = true } +cranelift-jit = { workspace = true } +cranelift = { workspace = true } filecheck = "0.5.0" -log = "0.4.8" +log = { workspace = true } termcolor = "1.1.2" -capstone = { version = "0.9.0", optional = true } -wat = { version = "1.0.47", optional = true } -target-lexicon = { version = "0.12", features = ["std"] } +capstone = { workspace = true, optional = true } +wat = { workspace = true, optional = true } +target-lexicon = { workspace = true, features = ["std"] } pretty_env_logger = "0.4.0" rayon = { version = "1", optional = true } indicatif = "0.13.0" -thiserror = "1.0.15" +thiserror = { workspace = true } walkdir = "2.2" -anyhow = "1.0.32" -clap = { version = "3.2.0", features = ["derive"] } +anyhow = { workspace = true } +clap = { workspace = true } +similar = { workspace = true } +toml = { workspace = true } +serde = { workspace = true } +fxhash = "0.2.1" [features] default = ["disas", "wasm", "cranelift-codegen/all-arch", "cranelift-codegen/trace-log", "souper-harvest"] diff --git a/cranelift/bforest/Cargo.toml b/cranelift/bforest/Cargo.toml index eae0dc75bfc6..4cda4486b434 100644 --- a/cranelift/bforest/Cargo.toml +++ b/cranelift/bforest/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-bforest" -version = "0.88.0" +version = "0.94.0" description = "A forest of B+-trees" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift-bforest" @@ -9,10 +9,10 @@ repository = "https://github.com/bytecodealliance/wasmtime" categories = ["no-std"] readme = "README.md" keywords = ["btree", "forest", "set", "map"] -edition = "2021" +edition.workspace = true [dependencies] -cranelift-entity = { path = "../entity", version = "0.88.0", default-features = false } +cranelift-entity = { workspace = true } [badges] maintenance = { status = "experimental" } diff --git a/cranelift/codegen/Cargo.toml b/cranelift/codegen/Cargo.toml index dbc919cf6d44..7c8f43945aad 100644 --- a/cranelift/codegen/Cargo.toml +++ b/cranelift/codegen/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-codegen" -version = "0.88.0" +version = "0.94.0" description = "Low-level code generator library" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift-codegen" @@ -10,21 +10,26 @@ categories = ["no-std"] readme = "README.md" keywords = ["compile", "compiler", "jit"] build = "build.rs" -edition = "2021" +edition.workspace = true [dependencies] -cranelift-codegen-shared = { path = "./shared", version = "0.88.0" } -cranelift-entity = { path = "../entity", version = "0.88.0" } -cranelift-bforest = { path = "../bforest", version = "0.88.0" } -hashbrown = { version = "0.12", optional = true } -target-lexicon = "0.12" -log = { version = "0.4.6", default-features = false } +arrayvec = "0.7" +anyhow = { workspace = true, optional = true } +bumpalo = "3" +capstone = { workspace = true, optional = true } +cranelift-codegen-shared = { path = "./shared", version = "0.94.0" } +cranelift-entity = { workspace = true } +cranelift-bforest = { workspace = true } +hashbrown = { workspace = true, features = ["raw"] } +target-lexicon = { workspace = true } +log = { workspace = true } serde = { version = "1.0.94", features = ["derive"], optional = true } bincode = { version = "1.2.1", optional = true } -gimli = { version = "0.26.0", default-features = false, features = ["write"], optional = true } -smallvec = { version = "1.6.1" } -regalloc2 = { version = "0.3.2", features = ["checker"] } +gimli = { workspace = true, features = ["write"], optional = true } +smallvec = { workspace = true } +regalloc2 = { version = "0.6.1", features = ["checker"] } souper-ir = { version = "2.1.0", optional = true } +sha2 = { version = "0.10.2", optional = true } # It is a goal of the cranelift-codegen crate to have minimal external dependencies. # Please don't add any unless they are essential to the task of creating binary # machine code. Integration tests that need external dependencies can be @@ -32,23 +37,29 @@ souper-ir = { version = "2.1.0", optional = true } [dev-dependencies] criterion = "0.3" +similar = "2.1.0" [build-dependencies] -cranelift-codegen-meta = { path = "meta", version = "0.88.0" } -cranelift-isle = { path = "../isle/isle", version = "=0.88.0" } -miette = { version = "5.1.0", features = ["fancy"], optional = true } +cranelift-codegen-meta = { path = "meta", version = "0.94.0" } +cranelift-isle = { path = "../isle/isle", version = "=0.94.0" } [features] -default = ["std", "unwind"] +default = ["std", "unwind", "trace-log"] # The "std" feature enables use of libstd. The "core" feature enables use # of some minimal std-like replacement libraries. At least one of these two # features need to be enabled. std = [] -# The "core" features enables use of "hashbrown" since core doesn't have -# a HashMap implementation, and a workaround for Cargo #4866. -core = ["hashbrown"] +# The "core" feature used to enable a hashmap workaround, but is now +# deprecated (we (i) always use hashbrown, and (ii) don't support a +# no_std build anymore). The feature remains for backward +# compatibility as a no-op. +core = [] + +# Enable the `to_capstone` method on TargetIsa, for constructing a Capstone +# context, and the `disassemble` method on `MachBufferFinalized`. +disas = ["anyhow", "capstone"] # This enables some additional functions useful for writing tests, but which # can significantly increase the size of the library. @@ -65,7 +76,7 @@ unwind = ["gimli"] x86 = [] arm64 = [] s390x = [] - +riscv64 = [] # Stub feature that does nothing, for Cargo-features compatibility: the new # backend is the default now. experimental_x64 = [] @@ -74,7 +85,8 @@ experimental_x64 = [] all-arch = [ "x86", "arm64", - "s390x" + "s390x", + "riscv64" ] # For dependent crates that want to serialize some parts of cranelift @@ -82,13 +94,21 @@ enable-serde = [ "serde", "cranelift-entity/enable-serde", "regalloc2/enable-serde", + "smallvec/serde" +] + +# Enable the incremental compilation cache for hot-reload use cases. +incremental-cache = [ + "enable-serde", + "bincode", + "sha2" ] # Enable support for the Souper harvester. souper-harvest = ["souper-ir", "souper-ir/stringify"] -# Provide fancy Miette-produced errors for ISLE. -isle-errors = ["miette", "cranelift-isle/miette-errors"] +# Report any ISLE errors in pretty-printed style. +isle-errors = ["cranelift-isle/fancy-errors"] # Put ISLE generated files in isle_generated_code/, for easier # inspection, rather than inside of target/. diff --git a/cranelift/codegen/build.rs b/cranelift/codegen/build.rs index 4960b0c68c02..e98bc3df520a 100644 --- a/cranelift/codegen/build.rs +++ b/cranelift/codegen/build.rs @@ -15,6 +15,7 @@ // current directory is used to find the sources. use cranelift_codegen_meta as meta; +use cranelift_isle::error::Errors; use std::env; use std::io::Read; @@ -177,9 +178,19 @@ fn get_isle_compilations( ) -> Result { let cur_dir = std::env::current_dir()?; - let clif_isle = out_dir.join("clif.isle"); + // Preludes. + let clif_lower_isle = out_dir.join("clif_lower.isle"); + let clif_opt_isle = out_dir.join("clif_opt.isle"); let prelude_isle = make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("prelude.isle")); + let prelude_opt_isle = + make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("prelude_opt.isle")); + let prelude_lower_isle = + make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("prelude_lower.isle")); + + // Directory for mid-end optimizations. + let src_opts = make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("opts")); + // Directories for lowering backends. let src_isa_x64 = make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("x64")); let src_isa_aarch64 = @@ -187,6 +198,8 @@ fn get_isle_compilations( let src_isa_s390x = make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("s390x")); + let src_isa_risc_v = + make_isle_source_path_relative(&cur_dir, crate_dir.join("src").join("isa").join("riscv64")); // This is a set of ISLE compilation units. // // The format of each entry is: @@ -202,37 +215,62 @@ fn get_isle_compilations( // `cranelift/codegen/src/isa/*/lower/isle/generated_code.rs`! Ok(IsleCompilations { items: vec![ + // The mid-end optimization rules. + IsleCompilation { + output: out_dir.join("isle_opt.rs"), + inputs: vec![ + prelude_isle.clone(), + prelude_opt_isle.clone(), + src_opts.join("algebraic.isle"), + src_opts.join("cprop.isle"), + ], + untracked_inputs: vec![clif_opt_isle.clone()], + }, // The x86-64 instruction selector. IsleCompilation { output: out_dir.join("isle_x64.rs"), inputs: vec![ prelude_isle.clone(), + prelude_lower_isle.clone(), src_isa_x64.join("inst.isle"), src_isa_x64.join("lower.isle"), ], - untracked_inputs: vec![clif_isle.clone()], + untracked_inputs: vec![clif_lower_isle.clone()], }, // The aarch64 instruction selector. IsleCompilation { output: out_dir.join("isle_aarch64.rs"), inputs: vec![ prelude_isle.clone(), + prelude_lower_isle.clone(), src_isa_aarch64.join("inst.isle"), src_isa_aarch64.join("inst_neon.isle"), src_isa_aarch64.join("lower.isle"), src_isa_aarch64.join("lower_dynamic_neon.isle"), ], - untracked_inputs: vec![clif_isle.clone()], + untracked_inputs: vec![clif_lower_isle.clone()], }, // The s390x instruction selector. IsleCompilation { output: out_dir.join("isle_s390x.rs"), inputs: vec![ prelude_isle.clone(), + prelude_lower_isle.clone(), src_isa_s390x.join("inst.isle"), src_isa_s390x.join("lower.isle"), ], - untracked_inputs: vec![clif_isle.clone()], + untracked_inputs: vec![clif_lower_isle.clone()], + }, + // The risc-v instruction selector. + IsleCompilation { + output: out_dir.join("isle_riscv64.rs"), + inputs: vec![ + prelude_isle.clone(), + prelude_lower_isle.clone(), + src_isa_risc_v.join("inst.isle"), + src_isa_risc_v.join("lower.isle"), + ], + untracked_inputs: vec![clif_lower_isle.clone()], }, ], }) @@ -251,13 +289,16 @@ fn build_isle( } if let Err(e) = run_compilation(compilation) { - eprintln!("Error building ISLE files: {:?}", e); - let mut source = e.source(); - while let Some(e) = source { - eprintln!("{:?}", e); - source = e.source(); - } had_error = true; + eprintln!("Error building ISLE files:"); + eprintln!("{:?}", e); + #[cfg(not(feature = "isle-errors"))] + { + eprintln!("To see a more detailed error report, run: "); + eprintln!(); + eprintln!(" $ cargo check -p cranelift-codegen --features isle-errors"); + eprintln!(); + } } } @@ -274,21 +315,16 @@ fn build_isle( /// /// NB: This must happen *after* the `cranelift-codegen-meta` functions, since /// it consumes files generated by them. -fn run_compilation( - compilation: &IsleCompilation, -) -> Result<(), Box> { +fn run_compilation(compilation: &IsleCompilation) -> Result<(), Errors> { use cranelift_isle as isle; eprintln!("Rebuilding {}", compilation.output.display()); - let code = (|| { - let lexer = isle::lexer::Lexer::from_files( - compilation - .inputs - .iter() - .chain(compilation.untracked_inputs.iter()), - )?; - let defs = isle::parser::parse(lexer)?; + let code = { + let file_paths = compilation + .inputs + .iter() + .chain(compilation.untracked_inputs.iter()); let mut options = isle::codegen::CodegenOptions::default(); // Because we include!() the generated ISLE source, we cannot @@ -298,62 +334,8 @@ fn run_compilation( // https://github.com/rust-lang/rust/issues/47995.) options.exclude_global_allow_pragmas = true; - isle::compile::compile(&defs, &options) - })() - .map_err(|e| { - // Make sure to include the source snippets location info along with - // the error messages. - - #[cfg(feature = "isle-errors")] - { - let report = miette::Report::new(e); - return DebugReport(report); - - struct DebugReport(miette::Report); - - impl std::fmt::Display for DebugReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - self.0.handler().debug(&*self.0, f) - } - } - - impl std::fmt::Debug for DebugReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Display::fmt(self, f) - } - } - - impl std::error::Error for DebugReport {} - } - #[cfg(not(feature = "isle-errors"))] - { - return DebugReport(format!("{}", e)); - - struct DebugReport(String); - - impl std::fmt::Display for DebugReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - writeln!(f, "ISLE errors:\n\n{}\n", self.0)?; - writeln!(f, "To see a more detailed error report, run: ")?; - writeln!(f, "")?; - writeln!( - f, - " $ cargo check -p cranelift-codegen --features isle-errors" - )?; - writeln!(f, "")?; - Ok(()) - } - } - - impl std::fmt::Debug for DebugReport { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Display::fmt(self, f) - } - } - - impl std::error::Error for DebugReport {} - } - })?; + isle::compile::from_files(file_paths, &options)? + }; let code = rustfmt(&code).unwrap_or_else(|e| { println!( @@ -367,7 +349,8 @@ fn run_compilation( "Writing ISLE-generated Rust code to {}", compilation.output.display() ); - std::fs::write(&compilation.output, code)?; + std::fs::write(&compilation.output, code) + .map_err(|e| Errors::from_io(e, "failed writing output"))?; Ok(()) } diff --git a/cranelift/codegen/meta/Cargo.toml b/cranelift/codegen/meta/Cargo.toml index 93bc62aab8fe..2af8dcd3d5d2 100644 --- a/cranelift/codegen/meta/Cargo.toml +++ b/cranelift/codegen/meta/Cargo.toml @@ -1,19 +1,19 @@ [package] name = "cranelift-codegen-meta" authors = ["The Cranelift Project Developers"] -version = "0.88.0" +version = "0.94.0" description = "Metaprogram for cranelift-codegen code generator library" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" readme = "README.md" -edition = "2021" +edition.workspace = true # FIXME(rust-lang/cargo#9300): uncomment once that lands # [package.metadata.docs.rs] # rustdoc-args = [ "--document-private-items" ] [dependencies] -cranelift-codegen-shared = { path = "../shared", version = "0.88.0" } +cranelift-codegen-shared = { path = "../shared", version = "0.94.0" } [badges] maintenance = { status = "experimental" } diff --git a/cranelift/codegen/meta/src/cdsl/formats.rs b/cranelift/codegen/meta/src/cdsl/formats.rs index 876fb7702f3f..10d804a98aa3 100644 --- a/cranelift/codegen/meta/src/cdsl/formats.rs +++ b/cranelift/codegen/meta/src/cdsl/formats.rs @@ -38,6 +38,8 @@ pub(crate) struct InstructionFormat { pub imm_fields: Vec, + pub num_block_operands: usize, + /// Index of the value input operand that is used to infer the controlling type variable. By /// default, this is `0`, the first `value` operand. The index is relative to the values only, /// ignoring immediate operands. @@ -49,6 +51,7 @@ pub(crate) struct InstructionFormat { pub(crate) struct FormatStructure { pub num_value_operands: usize, pub has_value_list: bool, + pub num_block_operands: usize, /// Tuples of (Rust field name / Rust type) for each immediate field. pub imm_field_names: Vec<(&'static str, &'static str)>, } @@ -62,8 +65,8 @@ impl fmt::Display for InstructionFormat { .collect::>() .join(", "); fmt.write_fmt(format_args!( - "{}(imms=({}), vals={})", - self.name, imm_args, self.num_value_operands + "{}(imms=({}), vals={}, blocks={})", + self.name, imm_args, self.num_value_operands, self.num_block_operands, ))?; Ok(()) } @@ -75,6 +78,7 @@ impl InstructionFormat { FormatStructure { num_value_operands: self.num_value_operands, has_value_list: self.has_value_list, + num_block_operands: self.num_block_operands, imm_field_names: self .imm_fields .iter() @@ -92,6 +96,7 @@ impl InstructionFormatBuilder { name, num_value_operands: 0, has_value_list: false, + num_block_operands: 0, imm_fields: Vec::new(), typevar_operand: None, }) @@ -107,6 +112,11 @@ impl InstructionFormatBuilder { self } + pub fn block(mut self) -> Self { + self.0.num_block_operands += 1; + self + } + pub fn imm(mut self, operand_kind: &OperandKind) -> Self { let field = FormatField { kind: operand_kind.clone(), diff --git a/cranelift/codegen/meta/src/cdsl/instructions.rs b/cranelift/codegen/meta/src/cdsl/instructions.rs index bb6af7f6922d..da5657ae8578 100644 --- a/cranelift/codegen/meta/src/cdsl/instructions.rs +++ b/cranelift/codegen/meta/src/cdsl/instructions.rs @@ -73,8 +73,8 @@ pub(crate) struct InstructionContent { pub can_trap: bool, /// Does this instruction have other side effects besides can_* flags? pub other_side_effects: bool, - /// Does this instruction write to CPU flags? - pub writes_cpu_flags: bool, + /// Despite having other side effects, is this instruction okay to GVN? + pub side_effects_idempotent: bool, } impl InstructionContent { @@ -135,6 +135,7 @@ pub(crate) struct InstructionBuilder { can_store: bool, can_trap: bool, other_side_effects: bool, + side_effects_idempotent: bool, } impl InstructionBuilder { @@ -154,6 +155,7 @@ impl InstructionBuilder { can_store: false, can_trap: false, other_side_effects: false, + side_effects_idempotent: false, } } @@ -169,47 +171,59 @@ impl InstructionBuilder { self } - #[allow(clippy::wrong_self_convention)] - pub fn is_terminator(mut self, val: bool) -> Self { - self.is_terminator = val; + /// Mark this instruction as a block terminator. + pub fn terminates_block(mut self) -> Self { + self.is_terminator = true; self } - #[allow(clippy::wrong_self_convention)] - pub fn is_branch(mut self, val: bool) -> Self { - self.is_branch = val; - self + /// Mark this instruction as a branch instruction. This also implies that the instruction is a + /// block terminator. + pub fn branches(mut self) -> Self { + self.is_branch = true; + self.terminates_block() } - #[allow(clippy::wrong_self_convention)] - pub fn is_call(mut self, val: bool) -> Self { - self.is_call = val; + /// Mark this instruction as a call instruction. + pub fn call(mut self) -> Self { + self.is_call = true; self } - #[allow(clippy::wrong_self_convention)] - pub fn is_return(mut self, val: bool) -> Self { - self.is_return = val; + /// Mark this instruction as a return instruction. This also implies that the instruction is a + /// block terminator. + pub fn returns(mut self) -> Self { + self.is_return = true; + self.terminates_block() + } + + /// Mark this instruction as one that can load from memory. + pub fn can_load(mut self) -> Self { + self.can_load = true; self } - pub fn can_load(mut self, val: bool) -> Self { - self.can_load = val; + /// Mark this instruction as one that can store to memory. + pub fn can_store(mut self) -> Self { + self.can_store = true; self } - pub fn can_store(mut self, val: bool) -> Self { - self.can_store = val; + /// Mark this instruction as possibly trapping. + pub fn can_trap(mut self) -> Self { + self.can_trap = true; self } - pub fn can_trap(mut self, val: bool) -> Self { - self.can_trap = val; + /// Mark this instruction as one that has side-effects. + pub fn other_side_effects(mut self) -> Self { + self.other_side_effects = true; self } - pub fn other_side_effects(mut self, val: bool) -> Self { - self.other_side_effects = val; + /// Mark this instruction as one whose side-effects may be de-duplicated. + pub fn side_effects_idempotent(mut self) -> Self { + self.side_effects_idempotent = true; self } @@ -240,9 +254,6 @@ impl InstructionBuilder { let polymorphic_info = verify_polymorphic(&operands_in, &operands_out, &self.format, &value_opnums); - // Infer from output operands whether an instruction clobbers CPU flags or not. - let writes_cpu_flags = operands_out.iter().any(|op| op.is_cpu_flags()); - let camel_name = camel_case(&self.name); Rc::new(InstructionContent { @@ -264,7 +275,7 @@ impl InstructionBuilder { can_store: self.can_store, can_trap: self.can_trap, other_side_effects: self.other_side_effects, - writes_cpu_flags, + side_effects_idempotent: self.side_effects_idempotent, }) } } diff --git a/cranelift/codegen/meta/src/cdsl/mod.rs b/cranelift/codegen/meta/src/cdsl/mod.rs index fa5f62562870..565783ad1680 100644 --- a/cranelift/codegen/meta/src/cdsl/mod.rs +++ b/cranelift/codegen/meta/src/cdsl/mod.rs @@ -17,15 +17,6 @@ macro_rules! predicate { ($a:ident && $($b:tt)*) => { PredicateNode::And(Box::new($a.into()), Box::new(predicate!($($b)*))) }; - (!$a:ident && $($b:tt)*) => { - PredicateNode::And( - Box::new(PredicateNode::Not(Box::new($a.into()))), - Box::new(predicate!($($b)*)) - ) - }; - (!$a:ident) => { - PredicateNode::Not(Box::new($a.into())) - }; ($a:ident) => { $a.into() }; diff --git a/cranelift/codegen/meta/src/cdsl/operands.rs b/cranelift/codegen/meta/src/cdsl/operands.rs index c278617b85fd..15c10fe4e7e9 100644 --- a/cranelift/codegen/meta/src/cdsl/operands.rs +++ b/cranelift/codegen/meta/src/cdsl/operands.rs @@ -87,17 +87,6 @@ impl Operand { _ => false, } } - - pub fn is_cpu_flags(&self) -> bool { - match &self.kind.fields { - OperandKindFields::TypeVar(type_var) - if type_var.name == "iflags" || type_var.name == "fflags" => - { - true - } - _ => false, - } - } } pub type EnumValues = HashMap<&'static str, &'static str>; @@ -163,6 +152,10 @@ impl OperandKind { | OperandKindFields::VariableArgs => unreachable!(), } } + + pub(crate) fn is_block(&self) -> bool { + self.rust_type == "ir::BlockCall" + } } impl Into for &TypeVar { diff --git a/cranelift/codegen/meta/src/cdsl/settings.rs b/cranelift/codegen/meta/src/cdsl/settings.rs index c4e76b760f00..358c0879eb6e 100644 --- a/cranelift/codegen/meta/src/cdsl/settings.rs +++ b/cranelift/codegen/meta/src/cdsl/settings.rs @@ -66,7 +66,7 @@ impl Setting { } } -#[derive(Hash, PartialEq, Eq)] +#[derive(Hash, PartialEq, Eq, Copy, Clone)] pub(crate) struct PresetIndex(usize); #[derive(Hash, PartialEq, Eq)] @@ -110,6 +110,15 @@ impl Preset { } layout } + + pub fn setting_names<'a>( + &'a self, + group: &'a SettingGroup, + ) -> impl Iterator + 'a { + self.values + .iter() + .map(|bool_index| group.settings[bool_index.0].name) + } } pub(crate) struct SettingGroup { @@ -172,7 +181,6 @@ struct ProtoSetting { pub(crate) enum PredicateNode { OwnedBool(BoolSettingIndex), SharedBool(&'static str, &'static str), - Not(Box), And(Box, Box), } @@ -202,7 +210,6 @@ impl PredicateNode { PredicateNode::And(ref lhs, ref rhs) => { format!("{} && {}", lhs.render(group), rhs.render(group)) } - PredicateNode::Not(ref node) => format!("!({})", node.render(group)), } } } diff --git a/cranelift/codegen/meta/src/cdsl/types.rs b/cranelift/codegen/meta/src/cdsl/types.rs index 1c2ca3f1cc56..661ed2c957fe 100644 --- a/cranelift/codegen/meta/src/cdsl/types.rs +++ b/cranelift/codegen/meta/src/cdsl/types.rs @@ -18,7 +18,6 @@ static RUST_NAME_PREFIX: &str = "ir::types::"; pub(crate) enum ValueType { Lane(LaneType), Reference(ReferenceType), - Special(SpecialType), Vector(VectorType), DynamicVector(DynamicVectorType), } @@ -29,11 +28,6 @@ impl ValueType { LaneTypeIterator::new() } - /// Iterate through all of the special types (neither lanes nor vectors). - pub fn all_special_types() -> SpecialTypeIterator { - SpecialTypeIterator::new() - } - pub fn all_reference_types() -> ReferenceTypeIterator { ReferenceTypeIterator::new() } @@ -43,7 +37,6 @@ impl ValueType { match *self { ValueType::Lane(l) => l.doc(), ValueType::Reference(r) => r.doc(), - ValueType::Special(s) => s.doc(), ValueType::Vector(ref v) => v.doc(), ValueType::DynamicVector(ref v) => v.doc(), } @@ -54,7 +47,6 @@ impl ValueType { match *self { ValueType::Lane(l) => l.lane_bits(), ValueType::Reference(r) => r.lane_bits(), - ValueType::Special(s) => s.lane_bits(), ValueType::Vector(ref v) => v.lane_bits(), ValueType::DynamicVector(ref v) => v.lane_bits(), } @@ -78,7 +70,6 @@ impl ValueType { match *self { ValueType::Lane(l) => l.number(), ValueType::Reference(r) => r.number(), - ValueType::Special(s) => s.number(), ValueType::Vector(ref v) => v.number(), ValueType::DynamicVector(ref v) => v.number(), } @@ -100,7 +91,6 @@ impl fmt::Display for ValueType { match *self { ValueType::Lane(l) => l.fmt(f), ValueType::Reference(r) => r.fmt(f), - ValueType::Special(s) => s.fmt(f), ValueType::Vector(ref v) => v.fmt(f), ValueType::DynamicVector(ref v) => v.fmt(f), } @@ -121,13 +111,6 @@ impl From for ValueType { } } -/// Create a ValueType from a given special type. -impl From for ValueType { - fn from(spec: SpecialType) -> Self { - ValueType::Special(spec) - } -} - /// Create a ValueType from a given vector type. impl From for ValueType { fn from(vector: VectorType) -> Self { @@ -145,7 +128,6 @@ impl From for ValueType { /// A concrete scalar type that can appear as a vector lane too. #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub(crate) enum LaneType { - Bool(shared_types::Bool), Float(shared_types::Float), Int(shared_types::Int), } @@ -154,7 +136,6 @@ impl LaneType { /// Return a string containing the documentation comment for this lane type. pub fn doc(self) -> String { match self { - LaneType::Bool(_) => format!("A boolean type with {} bits.", self.lane_bits()), LaneType::Float(shared_types::Float::F32) => String::from( "A 32-bit floating point type represented in the IEEE 754-2008 *binary32* interchange format. This corresponds to the :c:type:`float` @@ -178,7 +159,6 @@ impl LaneType { /// Return the number of bits in a lane. pub fn lane_bits(self) -> u64 { match self { - LaneType::Bool(ref b) => *b as u64, LaneType::Float(ref f) => *f as u64, LaneType::Int(ref i) => *i as u64, } @@ -188,12 +168,6 @@ impl LaneType { pub fn number(self) -> u16 { constants::LANE_BASE + match self { - LaneType::Bool(shared_types::Bool::B1) => 0, - LaneType::Bool(shared_types::Bool::B8) => 1, - LaneType::Bool(shared_types::Bool::B16) => 2, - LaneType::Bool(shared_types::Bool::B32) => 3, - LaneType::Bool(shared_types::Bool::B64) => 4, - LaneType::Bool(shared_types::Bool::B128) => 5, LaneType::Int(shared_types::Int::I8) => 6, LaneType::Int(shared_types::Int::I16) => 7, LaneType::Int(shared_types::Int::I32) => 8, @@ -204,18 +178,6 @@ impl LaneType { } } - pub fn bool_from_bits(num_bits: u16) -> LaneType { - LaneType::Bool(match num_bits { - 1 => shared_types::Bool::B1, - 8 => shared_types::Bool::B8, - 16 => shared_types::Bool::B16, - 32 => shared_types::Bool::B32, - 64 => shared_types::Bool::B64, - 128 => shared_types::Bool::B128, - _ => unreachable!("unxpected num bits for bool"), - }) - } - pub fn int_from_bits(num_bits: u16) -> LaneType { LaneType::Int(match num_bits { 8 => shared_types::Int::I8, @@ -251,7 +213,6 @@ impl LaneType { impl fmt::Display for LaneType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - LaneType::Bool(_) => write!(f, "b{}", self.lane_bits()), LaneType::Float(_) => write!(f, "f{}", self.lane_bits()), LaneType::Int(_) => write!(f, "i{}", self.lane_bits()), } @@ -265,7 +226,6 @@ impl fmt::Debug for LaneType { f, "{}", match *self { - LaneType::Bool(_) => format!("BoolType({})", inner_msg), LaneType::Float(_) => format!("FloatType({})", inner_msg), LaneType::Int(_) => format!("IntType({})", inner_msg), } @@ -273,13 +233,6 @@ impl fmt::Debug for LaneType { } } -/// Create a LaneType from a given bool variant. -impl From for LaneType { - fn from(b: shared_types::Bool) -> Self { - LaneType::Bool(b) - } -} - /// Create a LaneType from a given float variant. impl From for LaneType { fn from(f: shared_types::Float) -> Self { @@ -296,7 +249,6 @@ impl From for LaneType { /// An iterator for different lane types. pub(crate) struct LaneTypeIterator { - bool_iter: shared_types::BoolIterator, int_iter: shared_types::IntIterator, float_iter: shared_types::FloatIterator, } @@ -305,7 +257,6 @@ impl LaneTypeIterator { /// Create a new lane type iterator. fn new() -> Self { Self { - bool_iter: shared_types::BoolIterator::new(), int_iter: shared_types::IntIterator::new(), float_iter: shared_types::FloatIterator::new(), } @@ -315,9 +266,7 @@ impl LaneTypeIterator { impl Iterator for LaneTypeIterator { type Item = LaneType; fn next(&mut self) -> Option { - if let Some(b) = self.bool_iter.next() { - Some(LaneType::from(b)) - } else if let Some(i) = self.int_iter.next() { + if let Some(i) = self.int_iter.next() { Some(LaneType::from(i)) } else if let Some(f) = self.float_iter.next() { Some(LaneType::from(f)) @@ -470,91 +419,6 @@ impl fmt::Debug for DynamicVectorType { } } -/// A concrete scalar type that is neither a vector nor a lane type. -/// -/// Special types cannot be used to form vectors. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub(crate) enum SpecialType { - Flag(shared_types::Flag), -} - -impl SpecialType { - /// Return a string containing the documentation comment for this special type. - pub fn doc(self) -> String { - match self { - SpecialType::Flag(shared_types::Flag::IFlags) => String::from( - "CPU flags representing the result of an integer comparison. These flags - can be tested with an :type:`intcc` condition code.", - ), - SpecialType::Flag(shared_types::Flag::FFlags) => String::from( - "CPU flags representing the result of a floating point comparison. These - flags can be tested with a :type:`floatcc` condition code.", - ), - } - } - - /// Return the number of bits in a lane. - pub fn lane_bits(self) -> u64 { - match self { - SpecialType::Flag(_) => 0, - } - } - - /// Find the unique number associated with this special type. - pub fn number(self) -> u16 { - match self { - SpecialType::Flag(shared_types::Flag::IFlags) => 1, - SpecialType::Flag(shared_types::Flag::FFlags) => 2, - } - } -} - -impl fmt::Display for SpecialType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - SpecialType::Flag(shared_types::Flag::IFlags) => write!(f, "iflags"), - SpecialType::Flag(shared_types::Flag::FFlags) => write!(f, "fflags"), - } - } -} - -impl fmt::Debug for SpecialType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "{}", - match *self { - SpecialType::Flag(_) => format!("FlagsType({})", self), - } - ) - } -} - -impl From for SpecialType { - fn from(f: shared_types::Flag) -> Self { - SpecialType::Flag(f) - } -} - -pub(crate) struct SpecialTypeIterator { - flag_iter: shared_types::FlagIterator, -} - -impl SpecialTypeIterator { - fn new() -> Self { - Self { - flag_iter: shared_types::FlagIterator::new(), - } - } -} - -impl Iterator for SpecialTypeIterator { - type Item = SpecialType; - fn next(&mut self) -> Option { - self.flag_iter.next().map(SpecialType::from) - } -} - /// Reference type is scalar type, but not lane type. #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct ReferenceType(pub shared_types::Reference); diff --git a/cranelift/codegen/meta/src/cdsl/typevar.rs b/cranelift/codegen/meta/src/cdsl/typevar.rs index 63c14f861a91..f5875be2ea03 100644 --- a/cranelift/codegen/meta/src/cdsl/typevar.rs +++ b/cranelift/codegen/meta/src/cdsl/typevar.rs @@ -6,7 +6,7 @@ use std::iter::FromIterator; use std::ops; use std::rc::Rc; -use crate::cdsl::types::{LaneType, ReferenceType, SpecialType, ValueType}; +use crate::cdsl::types::{LaneType, ReferenceType, ValueType}; const MAX_LANES: u16 = 256; const MAX_BITS: u16 = 128; @@ -57,9 +57,6 @@ impl TypeVar { let mut builder = TypeSetBuilder::new(); let (scalar_type, num_lanes) = match value_type { - ValueType::Special(special_type) => { - return TypeVar::new(name, doc, builder.specials(vec![special_type]).build()); - } ValueType::Reference(ReferenceType(reference_type)) => { let bits = reference_type as RangeBound; return TypeVar::new(name, doc, builder.refs(bits..bits).build()); @@ -90,10 +87,6 @@ impl TypeVar { let bits = float_type as RangeBound; builder.floats(bits..bits) } - LaneType::Bool(bool_type) => { - let bits = bool_type as RangeBound; - builder.bools(bits..bits) - } }; TypeVar::new(name, doc, builder.build()) } @@ -160,7 +153,6 @@ impl TypeVar { let ts = self.get_typeset(); // Safety checks to avoid over/underflows. - assert!(ts.specials.is_empty(), "can't derive from special types"); match derived_func { DerivedFunc::HalfWidth => { assert!( @@ -171,10 +163,6 @@ impl TypeVar { ts.floats.is_empty() || *ts.floats.iter().min().unwrap() > 32, "can't halve all float types" ); - assert!( - ts.bools.is_empty() || *ts.bools.iter().min().unwrap() > 8, - "can't halve all boolean types" - ); } DerivedFunc::DoubleWidth => { assert!( @@ -185,22 +173,6 @@ impl TypeVar { ts.floats.is_empty() || *ts.floats.iter().max().unwrap() < MAX_FLOAT_BITS, "can't double all float types" ); - assert!( - ts.bools.is_empty() || *ts.bools.iter().max().unwrap() < MAX_BITS, - "can't double all boolean types" - ); - } - DerivedFunc::HalfVector => { - assert!( - *ts.lanes.iter().min().unwrap() > 1, - "can't halve a scalar type" - ); - } - DerivedFunc::DoubleVector => { - assert!( - *ts.lanes.iter().max().unwrap() < MAX_LANES, - "can't double 256 lanes" - ); } DerivedFunc::SplitLanes => { assert!( @@ -211,10 +183,6 @@ impl TypeVar { ts.floats.is_empty() || *ts.floats.iter().min().unwrap() > 32, "can't halve all float types" ); - assert!( - ts.bools.is_empty() || *ts.bools.iter().min().unwrap() > 8, - "can't halve all boolean types" - ); assert!( *ts.lanes.iter().max().unwrap() < MAX_LANES, "can't double 256 lanes" @@ -229,10 +197,6 @@ impl TypeVar { ts.floats.is_empty() || *ts.floats.iter().max().unwrap() < MAX_FLOAT_BITS, "can't double all float types" ); - assert!( - ts.bools.is_empty() || *ts.bools.iter().max().unwrap() < MAX_BITS, - "can't double all boolean types" - ); assert!( *ts.lanes.iter().min().unwrap() > 1, "can't halve a scalar type" @@ -268,12 +232,6 @@ impl TypeVar { pub fn double_width(&self) -> TypeVar { self.derived(DerivedFunc::DoubleWidth) } - pub fn half_vector(&self) -> TypeVar { - self.derived(DerivedFunc::HalfVector) - } - pub fn double_vector(&self) -> TypeVar { - self.derived(DerivedFunc::DoubleVector) - } pub fn split_lanes(&self) -> TypeVar { self.derived(DerivedFunc::SplitLanes) } @@ -341,8 +299,6 @@ pub(crate) enum DerivedFunc { AsBool, HalfWidth, DoubleWidth, - HalfVector, - DoubleVector, SplitLanes, MergeLanes, DynamicToVector, @@ -355,8 +311,6 @@ impl DerivedFunc { DerivedFunc::AsBool => "as_bool", DerivedFunc::HalfWidth => "half_width", DerivedFunc::DoubleWidth => "double_width", - DerivedFunc::HalfVector => "half_vector", - DerivedFunc::DoubleVector => "double_vector", DerivedFunc::SplitLanes => "split_lanes", DerivedFunc::MergeLanes => "merge_lanes", DerivedFunc::DynamicToVector => "dynamic_to_vector", @@ -384,9 +338,6 @@ pub(crate) struct TypeVarParent { /// - The permitted range of boolean types. /// /// The ranges are inclusive from smallest bit-width to largest bit-width. -/// -/// Finally, a type set can contain special types (derived from `SpecialType`) -/// which can't appear as lane types. type RangeBound = u16; type Range = ops::Range; @@ -404,9 +355,7 @@ pub(crate) struct TypeSet { pub dynamic_lanes: NumSet, pub ints: NumSet, pub floats: NumSet, - pub bools: NumSet, pub refs: NumSet, - pub specials: Vec, } impl TypeSet { @@ -415,28 +364,21 @@ impl TypeSet { dynamic_lanes: NumSet, ints: NumSet, floats: NumSet, - bools: NumSet, refs: NumSet, - specials: Vec, ) -> Self { Self { lanes, dynamic_lanes, ints, floats, - bools, refs, - specials, } } /// Return the number of concrete types represented by this typeset. pub fn size(&self) -> usize { - self.lanes.len() - * (self.ints.len() + self.floats.len() + self.bools.len() + self.refs.len()) - + self.dynamic_lanes.len() - * (self.ints.len() + self.floats.len() + self.bools.len() + self.refs.len()) - + self.specials.len() + self.lanes.len() * (self.ints.len() + self.floats.len() + self.refs.len()) + + self.dynamic_lanes.len() * (self.ints.len() + self.floats.len() + self.refs.len()) } /// Return the image of self across the derived function func. @@ -446,8 +388,6 @@ impl TypeSet { DerivedFunc::AsBool => self.as_bool(), DerivedFunc::HalfWidth => self.half_width(), DerivedFunc::DoubleWidth => self.double_width(), - DerivedFunc::HalfVector => self.half_vector(), - DerivedFunc::DoubleVector => self.double_vector(), DerivedFunc::SplitLanes => self.half_width().double_vector(), DerivedFunc::MergeLanes => self.double_width().half_vector(), DerivedFunc::DynamicToVector => self.dynamic_to_vector(), @@ -467,13 +407,6 @@ impl TypeSet { copy.ints = NumSet::new(); copy.floats = NumSet::new(); copy.refs = NumSet::new(); - if !(&self.lanes - &num_set![1]).is_empty() { - copy.bools = &self.ints | &self.floats; - copy.bools = ©.bools | &self.bools; - } - if self.lanes.contains(&1) { - copy.bools.insert(1); - } copy } @@ -482,8 +415,6 @@ impl TypeSet { let mut copy = self.clone(); copy.ints = NumSet::from_iter(self.ints.iter().filter(|&&x| x > 8).map(|&x| x / 2)); copy.floats = NumSet::from_iter(self.floats.iter().filter(|&&x| x > 32).map(|&x| x / 2)); - copy.bools = NumSet::from_iter(self.bools.iter().filter(|&&x| x > 8).map(|&x| x / 2)); - copy.specials = Vec::new(); copy } @@ -497,14 +428,6 @@ impl TypeSet { .filter(|&&x| x < MAX_FLOAT_BITS) .map(|&x| x * 2), ); - copy.bools = NumSet::from_iter( - self.bools - .iter() - .filter(|&&x| x < MAX_BITS) - .map(|&x| x * 2) - .filter(|x| legal_bool(*x)), - ); - copy.specials = Vec::new(); copy } @@ -512,7 +435,6 @@ impl TypeSet { fn half_vector(&self) -> TypeSet { let mut copy = self.clone(); copy.lanes = NumSet::from_iter(self.lanes.iter().filter(|&&x| x > 1).map(|&x| x / 2)); - copy.specials = Vec::new(); copy } @@ -525,7 +447,6 @@ impl TypeSet { .filter(|&&x| x < MAX_LANES) .map(|&x| x * 2), ); - copy.specials = Vec::new(); copy } @@ -537,7 +458,6 @@ impl TypeSet { .filter(|&&x| x < MAX_LANES) .map(|&x| x), ); - copy.specials = Vec::new(); copy.dynamic_lanes = NumSet::new(); copy } @@ -551,9 +471,6 @@ impl TypeSet { for &bits in &self.floats { ret.push(LaneType::float_from_bits(bits).by(num_lanes)); } - for &bits in &self.bools { - ret.push(LaneType::bool_from_bits(bits).by(num_lanes)); - } for &bits in &self.refs { ret.push(ReferenceType::ref_from_bits(bits).into()); } @@ -565,12 +482,6 @@ impl TypeSet { for &bits in &self.floats { ret.push(LaneType::float_from_bits(bits).to_dynamic(num_lanes)); } - for &bits in &self.bools { - ret.push(LaneType::bool_from_bits(bits).to_dynamic(num_lanes)); - } - } - for &special in &self.specials { - ret.push(special.into()); } ret } @@ -612,24 +523,12 @@ impl fmt::Debug for TypeSet { Vec::from_iter(self.floats.iter().map(|x| x.to_string())).join(", ") )); } - if !self.bools.is_empty() { - subsets.push(format!( - "bools={{{}}}", - Vec::from_iter(self.bools.iter().map(|x| x.to_string())).join(", ") - )); - } if !self.refs.is_empty() { subsets.push(format!( "refs={{{}}}", Vec::from_iter(self.refs.iter().map(|x| x.to_string())).join(", ") )); } - if !self.specials.is_empty() { - subsets.push(format!( - "specials={{{}}}", - Vec::from_iter(self.specials.iter().map(|x| x.to_string())).join(", ") - )); - } write!(fmt, "{})", subsets.join(", "))?; Ok(()) @@ -639,12 +538,10 @@ impl fmt::Debug for TypeSet { pub(crate) struct TypeSetBuilder { ints: Interval, floats: Interval, - bools: Interval, refs: Interval, includes_scalars: bool, simd_lanes: Interval, dynamic_simd_lanes: Interval, - specials: Vec, } impl TypeSetBuilder { @@ -652,12 +549,10 @@ impl TypeSetBuilder { Self { ints: Interval::None, floats: Interval::None, - bools: Interval::None, refs: Interval::None, includes_scalars: true, simd_lanes: Interval::None, dynamic_simd_lanes: Interval::None, - specials: Vec::new(), } } @@ -671,11 +566,6 @@ impl TypeSetBuilder { self.floats = interval.into(); self } - pub fn bools(mut self, interval: impl Into) -> Self { - assert!(self.bools == Interval::None); - self.bools = interval.into(); - self - } pub fn refs(mut self, interval: impl Into) -> Self { assert!(self.refs == Interval::None); self.refs = interval.into(); @@ -695,28 +585,16 @@ impl TypeSetBuilder { self.dynamic_simd_lanes = interval.into(); self } - pub fn specials(mut self, specials: Vec) -> Self { - assert!(self.specials.is_empty()); - self.specials = specials; - self - } pub fn build(self) -> TypeSet { let min_lanes = if self.includes_scalars { 1 } else { 2 }; - let bools = range_to_set(self.bools.to_range(1..MAX_BITS, None)) - .into_iter() - .filter(|x| legal_bool(*x)) - .collect(); - TypeSet::new( range_to_set(self.simd_lanes.to_range(min_lanes..MAX_LANES, Some(1))), range_to_set(self.dynamic_simd_lanes.to_range(2..MAX_LANES, None)), range_to_set(self.ints.to_range(8..MAX_BITS, None)), range_to_set(self.floats.to_range(32..64, None)), - bools, range_to_set(self.refs.to_range(32..64, None)), - self.specials, ) } } @@ -760,11 +638,6 @@ impl Into for Range { } } -fn legal_bool(bits: RangeBound) -> bool { - // Only allow legal bit widths for bool types. - bits == 1 || (bits >= 8 && bits <= MAX_BITS && bits.is_power_of_two()) -} - /// Generates a set with all the powers of two included in the range. fn range_to_set(range: Option) -> NumSet { let mut set = NumSet::new(); @@ -791,22 +664,11 @@ fn test_typevar_builder() { assert_eq!(type_set.lanes, num_set![1]); assert!(type_set.floats.is_empty()); assert_eq!(type_set.ints, num_set![8, 16, 32, 64, 128]); - assert!(type_set.bools.is_empty()); - assert!(type_set.specials.is_empty()); - - let type_set = TypeSetBuilder::new().bools(Interval::All).build(); - assert_eq!(type_set.lanes, num_set![1]); - assert!(type_set.floats.is_empty()); - assert!(type_set.ints.is_empty()); - assert_eq!(type_set.bools, num_set![1, 8, 16, 32, 64, 128]); - assert!(type_set.specials.is_empty()); let type_set = TypeSetBuilder::new().floats(Interval::All).build(); assert_eq!(type_set.lanes, num_set![1]); assert_eq!(type_set.floats, num_set![32, 64]); assert!(type_set.ints.is_empty()); - assert!(type_set.bools.is_empty()); - assert!(type_set.specials.is_empty()); let type_set = TypeSetBuilder::new() .floats(Interval::All) @@ -816,8 +678,6 @@ fn test_typevar_builder() { assert_eq!(type_set.lanes, num_set![2, 4, 8, 16, 32, 64, 128, 256]); assert_eq!(type_set.floats, num_set![32, 64]); assert!(type_set.ints.is_empty()); - assert!(type_set.bools.is_empty()); - assert!(type_set.specials.is_empty()); let type_set = TypeSetBuilder::new() .floats(Interval::All) @@ -827,8 +687,6 @@ fn test_typevar_builder() { assert_eq!(type_set.lanes, num_set![1, 2, 4, 8, 16, 32, 64, 128, 256]); assert_eq!(type_set.floats, num_set![32, 64]); assert!(type_set.ints.is_empty()); - assert!(type_set.bools.is_empty()); - assert!(type_set.specials.is_empty()); let type_set = TypeSetBuilder::new() .floats(Interval::All) @@ -839,12 +697,9 @@ fn test_typevar_builder() { assert_eq!(type_set.floats, num_set![32, 64]); assert!(type_set.dynamic_lanes.is_empty()); assert!(type_set.ints.is_empty()); - assert!(type_set.bools.is_empty()); - assert!(type_set.specials.is_empty()); let type_set = TypeSetBuilder::new() .ints(Interval::All) - .bools(Interval::All) .floats(Interval::All) .dynamic_simd_lanes(Interval::All) .includes_scalars(false) @@ -854,10 +709,8 @@ fn test_typevar_builder() { num_set![2, 4, 8, 16, 32, 64, 128, 256] ); assert_eq!(type_set.ints, num_set![8, 16, 32, 64, 128]); - assert_eq!(type_set.bools, num_set![1, 8, 16, 32, 64, 128]); assert_eq!(type_set.floats, num_set![32, 64]); assert_eq!(type_set.lanes, num_set![1]); - assert!(type_set.specials.is_empty()); let type_set = TypeSetBuilder::new() .floats(Interval::All) @@ -871,15 +724,11 @@ fn test_typevar_builder() { assert_eq!(type_set.floats, num_set![32, 64]); assert_eq!(type_set.lanes, num_set![1]); assert!(type_set.ints.is_empty()); - assert!(type_set.bools.is_empty()); - assert!(type_set.specials.is_empty()); let type_set = TypeSetBuilder::new().ints(16..64).build(); assert_eq!(type_set.lanes, num_set![1]); assert_eq!(type_set.ints, num_set![16, 32, 64]); assert!(type_set.floats.is_empty()); - assert!(type_set.bools.is_empty()); - assert!(type_set.specials.is_empty()); } #[test] @@ -897,17 +746,6 @@ fn test_dynamic_to_vector() { .ints(Interval::All) .build() ); - assert_eq!( - TypeSetBuilder::new() - .dynamic_simd_lanes(Interval::All) - .bools(Interval::All) - .build() - .dynamic_to_vector(), - TypeSetBuilder::new() - .simd_lanes(2..128) - .bools(Interval::All) - .build() - ); assert_eq!( TypeSetBuilder::new() .dynamic_simd_lanes(Interval::All) @@ -944,20 +782,6 @@ fn test_as_bool() { a.lane_of(), TypeSetBuilder::new().ints(8..8).floats(32..32).build() ); - - // Test as_bool with disjoint intervals. - let mut a_as_bool = TypeSetBuilder::new().simd_lanes(2..8).build(); - a_as_bool.bools = num_set![8, 32]; - assert_eq!(a.as_bool(), a_as_bool); - - let b = TypeSetBuilder::new() - .simd_lanes(1..8) - .ints(8..8) - .floats(32..32) - .build(); - let mut b_as_bool = TypeSetBuilder::new().simd_lanes(1..8).build(); - b_as_bool.bools = num_set![1, 8, 32]; - assert_eq!(b.as_bool(), b_as_bool); } #[test] @@ -1002,14 +826,6 @@ fn test_forward_images() { TypeSetBuilder::new().floats(32..64).build().half_width(), TypeSetBuilder::new().floats(32..32).build() ); - assert_eq!( - TypeSetBuilder::new().bools(1..8).build().half_width(), - empty_set - ); - assert_eq!( - TypeSetBuilder::new().bools(1..32).build().half_width(), - TypeSetBuilder::new().bools(8..16).build() - ); // Double width. assert_eq!( @@ -1028,14 +844,6 @@ fn test_forward_images() { TypeSetBuilder::new().floats(32..64).build().double_width(), TypeSetBuilder::new().floats(64..64).build() ); - assert_eq!( - TypeSetBuilder::new().bools(1..16).build().double_width(), - TypeSetBuilder::new().bools(16..32).build() - ); - assert_eq!( - TypeSetBuilder::new().bools(32..64).build().double_width(), - TypeSetBuilder::new().bools(64..128).build() - ); } #[test] @@ -1069,10 +877,6 @@ fn test_typeset_singleton() { TypeSetBuilder::new().floats(64..64).build().get_singleton(), ValueType::Lane(shared_types::Float::F64.into()) ); - assert_eq!( - TypeSetBuilder::new().bools(1..1).build().get_singleton(), - ValueType::Lane(shared_types::Bool::B1.into()) - ); assert_eq!( TypeSetBuilder::new() .simd_lanes(4..4) @@ -1110,8 +914,6 @@ fn test_typevar_singleton() { assert_eq!(typevar.name, "i32"); assert_eq!(typevar.type_set.ints, num_set![32]); assert!(typevar.type_set.floats.is_empty()); - assert!(typevar.type_set.bools.is_empty()); - assert!(typevar.type_set.specials.is_empty()); assert_eq!(typevar.type_set.lanes, num_set![1]); // Test f32x4. @@ -1123,6 +925,4 @@ fn test_typevar_singleton() { assert!(typevar.type_set.ints.is_empty()); assert_eq!(typevar.type_set.floats, num_set![32]); assert_eq!(typevar.type_set.lanes, num_set![4]); - assert!(typevar.type_set.bools.is_empty()); - assert!(typevar.type_set.specials.is_empty()); } diff --git a/cranelift/codegen/meta/src/gen_inst.rs b/cranelift/codegen/meta/src/gen_inst.rs index eb2a6dfd20df..db43caef6235 100644 --- a/cranelift/codegen/meta/src/gen_inst.rs +++ b/cranelift/codegen/meta/src/gen_inst.rs @@ -66,10 +66,10 @@ fn gen_formats(formats: &[&InstructionFormat], fmt: &mut Formatter) { /// 16 bytes on 64-bit architectures. If more space is needed to represent an instruction, use a /// `ValueList` to store the additional information out of line. fn gen_instruction_data(formats: &[&InstructionFormat], fmt: &mut Formatter) { - fmt.line("#[derive(Clone, Debug)]"); + fmt.line("#[derive(Copy, Clone, Debug, PartialEq, Hash)]"); fmt.line(r#"#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]"#); fmt.line("#[allow(missing_docs)]"); - fmt.line("pub enum InstructionData {"); + fmtln!(fmt, "pub enum InstructionData {"); fmt.indent(|fmt| { for format in formats { fmtln!(fmt, "{} {{", format.name); @@ -82,6 +82,18 @@ fn gen_instruction_data(formats: &[&InstructionFormat], fmt: &mut Formatter) { } else if format.num_value_operands > 0 { fmtln!(fmt, "args: [Value; {}],", format.num_value_operands); } + + match format.num_block_operands { + 0 => (), + 1 => fmt.line("destination: ir::BlockCall,"), + 2 => fmtln!( + fmt, + "blocks: [ir::BlockCall; {}],", + format.num_block_operands + ), + n => panic!("Too many block operands in instruction: {}", n), + } + for field in &format.imm_fields { fmtln!(fmt, "{}: {},", field.member, field.kind.rust_type); } @@ -158,8 +170,6 @@ fn gen_arguments_method(formats: &[&InstructionFormat], fmt: &mut Formatter, is_ /// - `pub fn opcode(&self) -> Opcode` /// - `pub fn arguments(&self, &pool) -> &[Value]` /// - `pub fn arguments_mut(&mut self, &pool) -> &mut [Value]` -/// - `pub fn take_value_list(&mut self) -> Option` -/// - `pub fn put_value_list(&mut self, args: ir::ValueList>` /// - `pub fn eq(&self, &other: Self, &pool) -> bool` /// - `pub fn hash(&self, state: &mut H, &pool)` fn gen_instruction_data_impl(formats: &[&InstructionFormat], fmt: &mut Formatter) { @@ -213,65 +223,17 @@ fn gen_instruction_data_impl(formats: &[&InstructionFormat], fmt: &mut Formatter gen_arguments_method(formats, fmt, true); fmt.empty_line(); - fmt.doc_comment(r#" - Take out the value list with all the value arguments and return - it. - - This leaves the value list in the instruction empty. Use - `put_value_list` to put the value list back. - "#); - fmt.line("pub fn take_value_list(&mut self) -> Option {"); - fmt.indent(|fmt| { - let mut m = Match::new("*self"); - - for format in formats { - if format.has_value_list { - m.arm(format!("Self::{}", format.name), - vec!["ref mut args", ".."], - "Some(args.take())".to_string()); - } - } - - m.arm_no_fields("_", "None"); - - fmt.add_match(m); - }); - fmt.line("}"); - fmt.empty_line(); - - fmt.doc_comment(r#" - Put back a value list. - - After removing a value list with `take_value_list()`, use this - method to put it back. It is required that this instruction has - a format that accepts a value list, and that the existing value - list is empty. This avoids leaking list pool memory. - "#); - fmt.line("pub fn put_value_list(&mut self, vlist: ir::ValueList) {"); - fmt.indent(|fmt| { - fmt.line("let args = match *self {"); - fmt.indent(|fmt| { - for format in formats { - if format.has_value_list { - fmtln!(fmt, "Self::{} {{ ref mut args, .. }} => args,", format.name); - } - } - fmt.line("_ => panic!(\"No value list: {:?}\", self),"); - }); - fmt.line("};"); - fmt.line("debug_assert!(args.is_empty(), \"Value list already in use\");"); - fmt.line("*args = vlist;"); - }); - fmt.line("}"); - fmt.empty_line(); - fmt.doc_comment(r#" Compare two `InstructionData` for equality. This operation requires a reference to a `ValueListPool` to determine if the contents of any `ValueLists` are equal. + + This operation takes a closure that is allowed to map each + argument value to some other value before the instructions + are compared. This allows various forms of canonicalization. "#); - fmt.line("pub fn eq(&self, other: &Self, pool: &ir::ValueListPool) -> bool {"); + fmt.line("pub fn eq Value>(&self, other: &Self, pool: &ir::ValueListPool, mapper: F) -> bool {"); fmt.indent(|fmt| { fmt.line("if ::core::mem::discriminant(self) != ::core::mem::discriminant(other) {"); fmt.indent(|fmt| { @@ -287,17 +249,29 @@ fn gen_instruction_data_impl(formats: &[&InstructionFormat], fmt: &mut Formatter let args_eq = if format.has_value_list { members.push("args"); - Some("args1.as_slice(pool) == args2.as_slice(pool)") + Some("args1.as_slice(pool).iter().zip(args2.as_slice(pool).iter()).all(|(a, b)| mapper(*a) == mapper(*b))") } else if format.num_value_operands == 1 { members.push("arg"); - Some("arg1 == arg2") + Some("mapper(*arg1) == mapper(*arg2)") } else if format.num_value_operands > 0 { members.push("args"); - Some("args1 == args2") + Some("args1.iter().zip(args2.iter()).all(|(a, b)| mapper(*a) == mapper(*b))") } else { None }; + let blocks_eq = match format.num_block_operands { + 0 => None, + 1 => { + members.push("destination"); + Some("destination1 == destination2") + }, + _ => { + members.push("blocks"); + Some("blocks1.iter().zip(blocks2.iter()).all(|(a, b)| a.block(pool) == b.block(pool))") + } + }; + for field in &format.imm_fields { members.push(field.member); } @@ -313,6 +287,9 @@ fn gen_instruction_data_impl(formats: &[&InstructionFormat], fmt: &mut Formatter if let Some(args_eq) = args_eq { fmtln!(fmt, "&& {}", args_eq); } + if let Some(blocks_eq) = blocks_eq { + fmtln!(fmt, "&& {}", blocks_eq); + } }); fmtln!(fmt, "}"); } @@ -328,8 +305,12 @@ fn gen_instruction_data_impl(formats: &[&InstructionFormat], fmt: &mut Formatter This operation requires a reference to a `ValueListPool` to hash the contents of any `ValueLists`. + + This operation takes a closure that is allowed to map each + argument value to some other value before it is hashed. This + allows various forms of canonicalization. "#); - fmt.line("pub fn hash(&self, state: &mut H, pool: &ir::ValueListPool) {"); + fmt.line("pub fn hash Value>(&self, state: &mut H, pool: &ir::ValueListPool, mapper: F) {"); fmt.indent(|fmt| { fmt.line("match *self {"); fmt.indent(|fmt| { @@ -337,17 +318,29 @@ fn gen_instruction_data_impl(formats: &[&InstructionFormat], fmt: &mut Formatter let name = format!("Self::{}", format.name); let mut members = vec!["opcode"]; - let args = if format.has_value_list { + let (args, len) = if format.has_value_list { members.push("ref args"); - "args.as_slice(pool)" + ("args.as_slice(pool)", "args.len(pool)") } else if format.num_value_operands == 1 { members.push("ref arg"); - "arg" - } else if format.num_value_operands > 0{ + ("std::slice::from_ref(arg)", "1") + } else if format.num_value_operands > 0 { members.push("ref args"); - "args" + ("args", "args.len()") } else { - "&()" + ("&[]", "0") + }; + + let blocks = match format.num_block_operands { + 0 => None, + 1 => { + members.push("ref destination"); + Some(("std::slice::from_ref(destination)", "1")) + } + _ => { + members.push("ref blocks"); + Some(("blocks", "blocks.len()")) + } }; for field in &format.imm_fields { @@ -362,7 +355,105 @@ fn gen_instruction_data_impl(formats: &[&InstructionFormat], fmt: &mut Formatter for field in &format.imm_fields { fmtln!(fmt, "::core::hash::Hash::hash(&{}, state);", field.member); } - fmtln!(fmt, "::core::hash::Hash::hash({}, state);", args); + fmtln!(fmt, "::core::hash::Hash::hash(&{}, state);", len); + fmtln!(fmt, "for &arg in {} {{", args); + fmt.indent(|fmt| { + fmtln!(fmt, "let arg = mapper(arg);"); + fmtln!(fmt, "::core::hash::Hash::hash(&arg, state);"); + }); + fmtln!(fmt, "}"); + + if let Some((blocks, len)) = blocks { + fmtln!(fmt, "::core::hash::Hash::hash(&{}, state);", len); + fmtln!(fmt, "for &block in {} {{", blocks); + fmt.indent(|fmt| { + fmtln!(fmt, "::core::hash::Hash::hash(&block.block(pool), state);"); + fmtln!(fmt, "for &arg in block.args_slice(pool) {"); + fmt.indent(|fmt| { + fmtln!(fmt, "let arg = mapper(arg);"); + fmtln!(fmt, "::core::hash::Hash::hash(&arg, state);"); + }); + fmtln!(fmt, "}"); + }); + fmtln!(fmt, "}"); + } + }); + fmtln!(fmt, "}"); + } + }); + fmt.line("}"); + }); + fmt.line("}"); + + fmt.empty_line(); + + fmt.doc_comment(r#" + Deep-clone an `InstructionData`, including any referenced lists. + + This operation requires a reference to a `ValueListPool` to + clone the `ValueLists`. + "#); + fmt.line("pub fn deep_clone(&self, pool: &mut ir::ValueListPool) -> Self {"); + fmt.indent(|fmt| { + fmt.line("match *self {"); + fmt.indent(|fmt| { + for format in formats { + let name = format!("Self::{}", format.name); + let mut members = vec!["opcode"]; + + if format.has_value_list { + members.push("ref args"); + } else if format.num_value_operands == 1 { + members.push("arg"); + } else if format.num_value_operands > 0 { + members.push("args"); + } + + match format.num_block_operands { + 0 => {} + 1 => { + members.push("destination"); + } + _ => { + members.push("blocks"); + } + }; + + for field in &format.imm_fields { + members.push(field.member); + } + let members = members.join(", "); + + fmtln!(fmt, "{}{{{}}} => {{", name, members ); // beware the moustaches + fmt.indent(|fmt| { + fmtln!(fmt, "Self::{} {{", format.name); + fmt.indent(|fmt| { + fmtln!(fmt, "opcode,"); + + if format.has_value_list { + fmtln!(fmt, "args: args.deep_clone(pool),"); + } else if format.num_value_operands == 1 { + fmtln!(fmt, "arg,"); + } else if format.num_value_operands > 0 { + fmtln!(fmt, "args,"); + } + + match format.num_block_operands { + 0 => {} + 1 => { + fmtln!(fmt, "destination: destination.deep_clone(pool),"); + } + 2 => { + fmtln!(fmt, "blocks: [blocks[0].deep_clone(pool), blocks[1].deep_clone(pool)],"); + } + _ => panic!("Too many block targets in instruction"), + } + + for field in &format.imm_fields { + fmtln!(fmt, "{},", field.member); + } + }); + fmtln!(fmt, "}"); }); fmtln!(fmt, "}"); } @@ -405,7 +496,7 @@ fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) { All instructions from all supported ISAs are present. "#, ); - fmt.line("#[repr(u16)]"); + fmt.line("#[repr(u8)]"); fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]"); fmt.line( r#"#[cfg_attr( @@ -507,11 +598,11 @@ fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) { ); gen_bool_accessor( all_inst, - |inst| inst.writes_cpu_flags, - "writes_cpu_flags", - "Does this instruction write to CPU flags?", + |inst| inst.side_effects_idempotent, + "side_effects_idempotent", + "Despite having side effects, is this instruction okay to GVN?", fmt, - ); + ) }); fmt.line("}"); fmt.empty_line(); @@ -572,24 +663,6 @@ fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) { fmt.empty_line(); } -fn gen_try_from(all_inst: &AllInstructions, fmt: &mut Formatter) { - fmt.line("impl core::convert::TryFrom for Opcode {"); - fmt.indent(|fmt| { - fmt.line("type Error = ();"); - fmt.line("#[inline]"); - fmt.line("fn try_from(x: u16) -> Result {"); - fmt.indent(|fmt| { - fmtln!(fmt, "if 0 < x && x <= {} {{", all_inst.len()); - fmt.indent(|fmt| fmt.line("Ok(unsafe { core::mem::transmute(x) })")); - fmt.line("} else {"); - fmt.indent(|fmt| fmt.line("Err(())")); - fmt.line("}"); - }); - fmt.line("}"); - }); - fmt.line("}"); -} - /// Get the value type constraint for an SSA value operand, where /// `ctrl_typevar` is the controlling type variable. /// @@ -656,12 +729,6 @@ fn typeset_to_string(ts: &TypeSet) -> String { if !ts.floats.is_empty() { result += &format!(", floats={}", iterable_to_string(&ts.floats)); } - if !ts.bools.is_empty() { - result += &format!(", bools={}", iterable_to_string(&ts.bools)); - } - if !ts.specials.is_empty() { - result += &format!(", specials=[{}]", iterable_to_string(&ts.specials)); - } if !ts.refs.is_empty() { result += &format!(", refs={}", iterable_to_string(&ts.refs)); } @@ -691,7 +758,6 @@ pub(crate) fn gen_typesets_table(type_sets: &UniqueTable, fmt: &mut For gen_bitset(&ts.dynamic_lanes, "dynamic_lanes", 16, fmt); gen_bitset(&ts.ints, "ints", 8, fmt); gen_bitset(&ts.floats, "floats", 8, fmt); - gen_bitset(&ts.bools, "bools", 8, fmt); gen_bitset(&ts.refs, "refs", 8, fmt); }); fmt.line("},"); @@ -839,6 +905,19 @@ fn gen_member_inits(format: &InstructionFormat, fmt: &mut Formatter) { } fmtln!(fmt, "args: [{}],", args.join(", ")); } + + // Block operands + match format.num_block_operands { + 0 => (), + 1 => fmt.line("destination: block0"), + n => { + let mut blocks = Vec::new(); + for i in 0..n { + blocks.push(format!("block{}", i)); + } + fmtln!(fmt, "blocks: [{}],", blocks.join(", ")); + } + } } /// Emit a method for creating and inserting an instruction format. @@ -858,6 +937,9 @@ fn gen_format_constructor(format: &InstructionFormat, fmt: &mut Formatter) { args.push(format!("{}: {}", f.member, f.kind.rust_type)); } + // Then the block operands. + args.extend((0..format.num_block_operands).map(|i| format!("block{}: ir::BlockCall", i))); + // Then the value operands. if format.has_value_list { // Take all value arguments as a finished value list. The value lists @@ -902,6 +984,9 @@ fn gen_format_constructor(format: &InstructionFormat, fmt: &mut Formatter) { fmtln!(fmt, "data.sign_extend_immediates(ctrl_typevar);"); } + // Assert that this opcode belongs to this format + fmtln!(fmt, "debug_assert_eq!(opcode.format(), InstructionFormat::from(&data), \"Wrong InstructionFormat for Opcode: {}\", opcode);"); + fmt.line("self.build(data, ctrl_typevar)"); }); fmtln!(fmt, "}"); @@ -913,12 +998,7 @@ fn gen_format_constructor(format: &InstructionFormat, fmt: &mut Formatter) { /// instruction reference itself for instructions that don't have results. fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Formatter) { // Construct method arguments. - let mut args = vec![if format.has_value_list { - "mut self" - } else { - "self" - } - .to_string()]; + let mut args = vec![String::new()]; let mut args_doc = Vec::new(); let mut rets_doc = Vec::new(); @@ -937,17 +1017,39 @@ fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Fo let mut tmpl_types = Vec::new(); let mut into_args = Vec::new(); + let mut block_args = Vec::new(); for op in &inst.operands_in { - let t = if op.is_immediate() { - let t = format!("T{}", tmpl_types.len() + 1); - tmpl_types.push(format!("{}: Into<{}>", t, op.kind.rust_type)); - into_args.push(op.name); - t + if op.kind.is_block() { + args.push(format!("{}_label: {}", op.name, "ir::Block")); + args_doc.push(format!( + "- {}_label: {}", + op.name, "Destination basic block" + )); + + args.push(format!("{}_args: {}", op.name, "&[Value]")); + args_doc.push(format!("- {}_args: {}", op.name, "Block arguments")); + + block_args.push(op); } else { - op.kind.rust_type.to_string() - }; - args.push(format!("{}: {}", op.name, t)); - args_doc.push(format!("- {}: {}", op.name, op.doc())); + let t = if op.is_immediate() { + let t = format!("T{}", tmpl_types.len() + 1); + tmpl_types.push(format!("{}: Into<{}>", t, op.kind.rust_type)); + into_args.push(op.name); + t + } else { + op.kind.rust_type.to_string() + }; + args.push(format!("{}: {}", op.name, t)); + args_doc.push(format!("- {}: {}", op.name, op.doc())); + } + } + + // We need to mutate `self` if this instruction accepts a value list, or will construct + // BlockCall values. + if format.has_value_list || !block_args.is_empty() { + args[0].push_str("mut self"); + } else { + args[0].push_str("self"); } for op in &inst.operands_out { @@ -996,10 +1098,19 @@ fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Fo fmtln!(fmt, "fn {} {{", proto); fmt.indent(|fmt| { // Convert all of the `Into<>` arguments. - for arg in &into_args { + for arg in into_args { fmtln!(fmt, "let {} = {}.into();", arg, arg); } + // Convert block references + for op in block_args { + fmtln!( + fmt, + "let {0} = self.data_flow_graph_mut().block_call({0}_label, {0}_args);", + op.name + ); + } + // Arguments for instruction constructor. let first_arg = format!("Opcode::{}", inst.camel_name); let mut args = vec![first_arg.as_str()]; @@ -1085,7 +1196,21 @@ fn gen_inst_builder(inst: &Instruction, format: &InstructionFormat, fmt: &mut Fo fmtln!(fmt, "}") } -fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: &mut Formatter) { +/// Which ISLE target are we generating code for? +#[derive(Clone, Copy, PartialEq, Eq)] +enum IsleTarget { + /// Generating code for instruction selection and lowering. + Lower, + /// Generating code for CLIF to CLIF optimizations. + Opt, +} + +fn gen_common_isle( + formats: &[&InstructionFormat], + instructions: &AllInstructions, + fmt: &mut Formatter, + isle_target: IsleTarget, +) { use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Write; @@ -1174,6 +1299,41 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: fmt.empty_line(); } + // Generate all of the block arrays we need for `InstructionData` as well as + // the constructors and extractors for them. + fmt.line(";;;; Block Arrays ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"); + fmt.empty_line(); + let block_array_arities: BTreeSet<_> = formats + .iter() + .filter(|f| f.num_block_operands > 1) + .map(|f| f.num_block_operands) + .collect(); + for n in block_array_arities { + fmtln!(fmt, ";; ISLE representation of `[BlockCall; {}]`.", n); + fmtln!(fmt, "(type BlockArray{} extern (enum))", n); + fmt.empty_line(); + + fmtln!( + fmt, + "(decl block_array_{0} ({1}) BlockArray{0})", + n, + (0..n).map(|_| "BlockCall").collect::>().join(" ") + ); + + fmtln!( + fmt, + "(extern constructor block_array_{0} pack_block_array_{0})", + n + ); + + fmtln!( + fmt, + "(extern extractor infallible block_array_{0} unpack_block_array_{0})", + n + ); + fmt.empty_line(); + } + // Generate the extern type declaration for `Opcode`. fmt.line(";;;; `Opcode` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"); fmt.empty_line(); @@ -1191,9 +1351,12 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: fmt.empty_line(); // Generate the extern type declaration for `InstructionData`. - fmt.line(";;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"); + fmtln!( + fmt, + ";;;; `InstructionData` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;", + ); fmt.empty_line(); - fmt.line("(type InstructionData extern"); + fmtln!(fmt, "(type InstructionData extern"); fmt.indent(|fmt| { fmt.line("(enum"); fmt.indent(|fmt| { @@ -1206,6 +1369,13 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: } else if format.num_value_operands > 1 { write!(&mut s, " (args ValueArray{})", format.num_value_operands).unwrap(); } + + match format.num_block_operands { + 0 => (), + 1 => write!(&mut s, " (destination BlockCall)").unwrap(), + n => write!(&mut s, " (blocks BlockArray{})", n).unwrap(), + } + for field in &format.imm_fields { write!( &mut s, @@ -1225,16 +1395,28 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: fmt.empty_line(); // Generate the helper extractors for each opcode's full instruction. - // - // TODO: if/when we port our peephole optimization passes to ISLE we will - // want helper constructors as well. - fmt.line(";;;; Extracting Opcode, Operands, and Immediates from `InstructionData` ;;;;;;;;"); + fmtln!( + fmt, + ";;;; Extracting Opcode, Operands, and Immediates from `InstructionData` ;;;;;;;;", + ); fmt.empty_line(); + let ret_ty = match isle_target { + IsleTarget::Lower => "Inst", + IsleTarget::Opt => "Value", + }; for inst in instructions { + if isle_target == IsleTarget::Opt && inst.format.has_value_list { + continue; + } + fmtln!( fmt, - "(decl {} ({}) Inst)", + "(decl {} ({}{}) {})", inst.name, + match isle_target { + IsleTarget::Lower => "", + IsleTarget::Opt => "Type ", + }, inst.operands_in .iter() .map(|o| { @@ -1246,23 +1428,34 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: } }) .collect::>() - .join(" ") + .join(" "), + ret_ty ); fmtln!(fmt, "(extractor"); fmt.indent(|fmt| { fmtln!( fmt, - "({} {})", + "({} {}{})", inst.name, + match isle_target { + IsleTarget::Lower => "", + IsleTarget::Opt => "ty ", + }, inst.operands_in .iter() .map(|o| { o.name }) .collect::>() .join(" ") ); + let mut s = format!( - "(inst_data (InstructionData.{} (Opcode.{})", - inst.format.name, inst.camel_name + "(inst_data{} (InstructionData.{} (Opcode.{})", + match isle_target { + IsleTarget::Lower => "", + IsleTarget::Opt => " ty", + }, + inst.format.name, + inst.camel_name ); // Value and varargs operands. @@ -1324,21 +1517,156 @@ fn gen_isle(formats: &[&InstructionFormat], instructions: &AllInstructions, fmt: let imm_operands: Vec<_> = inst .operands_in .iter() - .filter(|o| !o.is_value() && !o.is_varargs()) + .filter(|o| !o.is_value() && !o.is_varargs() && !o.kind.is_block()) .collect(); - assert_eq!(imm_operands.len(), inst.format.imm_fields.len()); + assert_eq!(imm_operands.len(), inst.format.imm_fields.len(),); for op in imm_operands { write!(&mut s, " {}", op.name).unwrap(); } + // Blocks. + let block_operands: Vec<_> = inst + .operands_in + .iter() + .filter(|o| o.kind.is_block()) + .collect(); + assert_eq!(block_operands.len(), inst.format.num_block_operands); + assert!(block_operands.len() <= 2); + + if !block_operands.is_empty() { + if block_operands.len() == 1 { + write!(&mut s, " {}", block_operands[0].name).unwrap(); + } else { + let blocks: Vec<_> = block_operands.iter().map(|o| o.name).collect(); + let blocks = blocks.join(" "); + write!( + &mut s, + " (block_array_{} {})", + inst.format.num_block_operands, blocks, + ) + .unwrap(); + } + } + s.push_str("))"); fmt.line(&s); }); fmt.line(")"); + + // Generate a constructor if this is the mid-end prelude. + if isle_target == IsleTarget::Opt { + fmtln!( + fmt, + "(rule ({} ty {})", + inst.name, + inst.operands_in + .iter() + .map(|o| o.name) + .collect::>() + .join(" ") + ); + fmt.indent(|fmt| { + let mut s = format!( + "(make_inst ty (InstructionData.{} (Opcode.{})", + inst.format.name, inst.camel_name + ); + + // Handle values. Note that we skip generating + // constructors for any instructions with variadic + // value lists. This is fine for the mid-end because + // in practice only calls and branches (for branch + // args) use this functionality, and neither can + // really be optimized or rewritten in the mid-end + // (currently). + // + // As a consequence, we only have to handle the + // one-`Value` case, in which the `Value` is directly + // in the `InstructionData`, and the multiple-`Value` + // case, in which the `Value`s are in a + // statically-sized array (e.g. `[Value; 2]` for a + // binary op). + assert!(!inst.format.has_value_list); + if inst.format.num_value_operands == 1 { + write!( + &mut s, + " {}", + inst.operands_in.iter().find(|o| o.is_value()).unwrap().name + ) + .unwrap(); + } else if inst.format.num_value_operands > 1 { + // As above, get all bindings together, and pass + // to a sub-term; here we use a constructor to + // build the value array. + let values = inst + .operands_in + .iter() + .filter(|o| o.is_value()) + .map(|o| o.name) + .collect::>(); + assert_eq!(values.len(), inst.format.num_value_operands); + let values = values.join(" "); + write!( + &mut s, + " (value_array_{}_ctor {})", + inst.format.num_value_operands, values + ) + .unwrap(); + } + + if inst.format.num_block_operands > 0 { + let blocks: Vec<_> = inst + .operands_in + .iter() + .filter(|o| o.kind.is_block()) + .map(|o| o.name) + .collect(); + if inst.format.num_block_operands == 1 { + write!(&mut s, " {}", blocks.first().unwrap(),).unwrap(); + } else { + write!( + &mut s, + " (block_array_{} {})", + inst.format.num_block_operands, + blocks.join(" ") + ) + .unwrap(); + } + } + + // Immediates (non-value args). + for o in inst + .operands_in + .iter() + .filter(|o| !o.is_value() && !o.is_varargs() && !o.kind.is_block()) + { + write!(&mut s, " {}", o.name).unwrap(); + } + s.push_str("))"); + fmt.line(&s); + }); + fmt.line(")"); + } + fmt.empty_line(); } } +fn gen_opt_isle( + formats: &[&InstructionFormat], + instructions: &AllInstructions, + fmt: &mut Formatter, +) { + gen_common_isle(formats, instructions, fmt, IsleTarget::Opt); +} + +fn gen_lower_isle( + formats: &[&InstructionFormat], + instructions: &AllInstructions, + fmt: &mut Formatter, +) { + gen_common_isle(formats, instructions, fmt, IsleTarget::Lower); +} + /// Generate an `enum` immediate in ISLE. fn gen_isle_enum(name: &str, mut variants: Vec<&str>, fmt: &mut Formatter) { variants.sort(); @@ -1403,7 +1731,8 @@ pub(crate) fn generate( all_inst: &AllInstructions, opcode_filename: &str, inst_builder_filename: &str, - isle_filename: &str, + isle_opt_filename: &str, + isle_lower_filename: &str, out_dir: &str, isle_dir: &str, ) -> Result<(), error::Error> { @@ -1417,14 +1746,17 @@ pub(crate) fn generate( gen_opcodes(all_inst, &mut fmt); fmt.empty_line(); gen_type_constraints(all_inst, &mut fmt); - fmt.empty_line(); - gen_try_from(all_inst, &mut fmt); fmt.update_file(opcode_filename, out_dir)?; - // ISLE DSL. + // ISLE DSL: mid-end ("opt") generated bindings. + let mut fmt = Formatter::new(); + gen_opt_isle(&formats, all_inst, &mut fmt); + fmt.update_file(isle_opt_filename, isle_dir)?; + + // ISLE DSL: lowering generated bindings. let mut fmt = Formatter::new(); - gen_isle(&formats, all_inst, &mut fmt); - fmt.update_file(isle_filename, isle_dir)?; + gen_lower_isle(&formats, all_inst, &mut fmt); + fmt.update_file(isle_lower_filename, isle_dir)?; // Instruction builder. let mut fmt = Formatter::new(); diff --git a/cranelift/codegen/meta/src/gen_settings.rs b/cranelift/codegen/meta/src/gen_settings.rs index 8218876ae3da..01a4f731a772 100644 --- a/cranelift/codegen/meta/src/gen_settings.rs +++ b/cranelift/codegen/meta/src/gen_settings.rs @@ -98,6 +98,26 @@ fn gen_iterator(group: &SettingGroup, fmt: &mut Formatter) { fmtln!(fmt, "}"); } +/// Generates a `all()` function with all options for this enum +fn gen_enum_all(name: &str, values: &[&'static str], fmt: &mut Formatter) { + fmtln!( + fmt, + "/// Returns a slice with all possible [{}] values.", + name + ); + fmtln!(fmt, "pub fn all() -> &'static [{}] {{", name); + fmt.indent(|fmt| { + fmtln!(fmt, "&["); + fmt.indent(|fmt| { + for v in values.iter() { + fmtln!(fmt, "Self::{},", camel_case(v)); + } + }); + fmtln!(fmt, "]"); + }); + fmtln!(fmt, "}"); +} + /// Emit Display and FromStr implementations for enum settings. fn gen_to_and_from_str(name: &str, values: &[&'static str], fmt: &mut Formatter) { fmtln!(fmt, "impl fmt::Display for {} {{", name); @@ -158,6 +178,12 @@ fn gen_enum_types(group: &SettingGroup, fmt: &mut Formatter) { }); fmtln!(fmt, "}"); + fmtln!(fmt, "impl {} {{", name); + fmt.indent(|fmt| { + gen_enum_all(&name, values, fmt); + }); + fmtln!(fmt, "}"); + gen_to_and_from_str(&name, values, fmt); } } @@ -370,7 +396,11 @@ fn gen_descriptors(group: &SettingGroup, fmt: &mut Formatter) { ); fmt.indent(|fmt| { for preset in &group.presets { - fmt.comment(preset.name); + fmt.comment(format!( + "{}: {}", + preset.name, + preset.setting_names(&group).collect::>().join(", ") + )); for (mask, value) in preset.layout(&group) { fmtln!(fmt, "(0b{:08b}, 0b{:08b}),", mask, value); } diff --git a/cranelift/codegen/meta/src/gen_types.rs b/cranelift/codegen/meta/src/gen_types.rs index 0d27070df75c..f83638fd7f0d 100644 --- a/cranelift/codegen/meta/src/gen_types.rs +++ b/cranelift/codegen/meta/src/gen_types.rs @@ -48,11 +48,6 @@ fn emit_dynamic_vectors(bits: u64, fmt: &mut srcgen::Formatter) { /// Emit types using the given formatter object. fn emit_types(fmt: &mut srcgen::Formatter) { - // Emit all of the special types, such as types for CPU flags. - for spec in cdsl_types::ValueType::all_special_types().map(cdsl_types::ValueType::from) { - emit_type(&spec, fmt); - } - // Emit all of the lane types, such integers, floats, and booleans. for ty in cdsl_types::ValueType::all_lane_types().map(cdsl_types::ValueType::from) { emit_type(&ty, fmt); diff --git a/cranelift/codegen/meta/src/isa/arm64.rs b/cranelift/codegen/meta/src/isa/arm64.rs index 7fc17738bb27..9e1aac536422 100644 --- a/cranelift/codegen/meta/src/isa/arm64.rs +++ b/cranelift/codegen/meta/src/isa/arm64.rs @@ -5,13 +5,13 @@ use crate::shared::Definitions as SharedDefinitions; fn define_settings(_shared: &SettingGroup) -> SettingGroup { let mut setting = SettingGroupBuilder::new("arm64"); - let has_lse = setting.add_bool( + + setting.add_bool( "has_lse", "Has Large System Extensions (FEAT_LSE) support.", "", false, ); - setting.add_bool( "has_pauth", "Has Pointer authentication (FEAT_PAuth) support; enables the use of \ @@ -44,8 +44,13 @@ fn define_settings(_shared: &SettingGroup) -> SettingGroup { "", false, ); + setting.add_bool( + "use_bti", + "Use Branch Target Identification (FEAT_BTI) instructions.", + "", + false, + ); - setting.add_predicate("use_lse", predicate!(has_lse)); setting.build() } diff --git a/cranelift/codegen/meta/src/isa/mod.rs b/cranelift/codegen/meta/src/isa/mod.rs index 6411932b16ad..4d77f9268ddf 100644 --- a/cranelift/codegen/meta/src/isa/mod.rs +++ b/cranelift/codegen/meta/src/isa/mod.rs @@ -4,6 +4,7 @@ use crate::shared::Definitions as SharedDefinitions; use std::fmt; mod arm64; +mod riscv64; mod s390x; pub(crate) mod x86; @@ -13,6 +14,7 @@ pub enum Isa { X86, Arm64, S390x, + Riscv64, } impl Isa { @@ -30,13 +32,14 @@ impl Isa { "aarch64" => Some(Isa::Arm64), "s390x" => Some(Isa::S390x), x if ["x86_64", "i386", "i586", "i686"].contains(&x) => Some(Isa::X86), + "riscv64" | "riscv64gc" | "riscv64imac" => Some(Isa::Riscv64), _ => None, } } /// Returns all supported isa targets. pub fn all() -> &'static [Isa] { - &[Isa::X86, Isa::Arm64, Isa::S390x] + &[Isa::X86, Isa::Arm64, Isa::S390x, Isa::Riscv64] } } @@ -47,6 +50,7 @@ impl fmt::Display for Isa { Isa::X86 => write!(f, "x86"), Isa::Arm64 => write!(f, "arm64"), Isa::S390x => write!(f, "s390x"), + Isa::Riscv64 => write!(f, "riscv64"), } } } @@ -57,6 +61,7 @@ pub(crate) fn define(isas: &[Isa], shared_defs: &mut SharedDefinitions) -> Vec x86::define(shared_defs), Isa::Arm64 => arm64::define(shared_defs), Isa::S390x => s390x::define(shared_defs), + Isa::Riscv64 => riscv64::define(shared_defs), }) .collect() } diff --git a/cranelift/codegen/meta/src/isa/riscv64.rs b/cranelift/codegen/meta/src/isa/riscv64.rs new file mode 100644 index 000000000000..3b1cc6254836 --- /dev/null +++ b/cranelift/codegen/meta/src/isa/riscv64.rs @@ -0,0 +1,28 @@ +use crate::cdsl::isa::TargetIsa; +use crate::cdsl::settings::{SettingGroup, SettingGroupBuilder}; + +use crate::shared::Definitions as SharedDefinitions; + +fn define_settings(_shared: &SettingGroup) -> SettingGroup { + let mut setting = SettingGroupBuilder::new("riscv64"); + + let _has_m = setting.add_bool("has_m", "has extension M?", "", false); + let _has_a = setting.add_bool("has_a", "has extension A?", "", false); + let _has_f = setting.add_bool("has_f", "has extension F?", "", false); + let _has_d = setting.add_bool("has_d", "has extension D?", "", false); + let _has_v = setting.add_bool("has_v", "has extension V?", "", false); + let _has_b = setting.add_bool("has_b", "has extension B?", "", false); + let _has_c = setting.add_bool("has_c", "has extension C?", "", false); + let _has_zbkb = setting.add_bool("has_zbkb", "has extension zbkb?", "", false); + let _has_zbb = setting.add_bool("has_zbb", "has extension zbb?", "", false); + + let _has_zicsr = setting.add_bool("has_zicsr", "has extension zicsr?", "", false); + let _has_zifencei = setting.add_bool("has_zifencei", "has extension zifencei?", "", false); + + setting.build() +} + +pub(crate) fn define(shared_defs: &mut SharedDefinitions) -> TargetIsa { + let settings = define_settings(&shared_defs.settings); + TargetIsa::new("riscv64", settings) +} diff --git a/cranelift/codegen/meta/src/isa/x86.rs b/cranelift/codegen/meta/src/isa/x86.rs index e1acdfca2064..47c7ff1aeb79 100644 --- a/cranelift/codegen/meta/src/isa/x86.rs +++ b/cranelift/codegen/meta/src/isa/x86.rs @@ -164,61 +164,278 @@ fn define_settings(shared: &SettingGroup) -> SettingGroup { settings.add_predicate("use_bmi1", predicate!(has_bmi1)); settings.add_predicate("use_lzcnt", predicate!(has_lzcnt)); - // Some shared boolean values are used in x86 instruction predicates, so we need to group them - // in the same TargetIsa, for compatibility with code generated by meta-python. - // TODO Once all the meta generation code has been migrated from Python to Rust, we can put it - // back in the shared SettingGroup, and use it in x86 instruction predicates. - - let is_pic = shared.get_bool("is_pic"); - settings.add_predicate("is_pic", predicate!(is_pic)); - settings.add_predicate("not_is_pic", predicate!(!is_pic)); + let sse3 = settings.add_preset("sse3", "SSE3 and earlier.", preset!(has_sse3)); + let ssse3 = settings.add_preset("ssse3", "SSSE3 and earlier.", preset!(sse3 && has_ssse3)); + let sse41 = settings.add_preset("sse41", "SSE4.1 and earlier.", preset!(ssse3 && has_sse41)); + let sse42 = settings.add_preset("sse42", "SSE4.2 and earlier.", preset!(sse41 && has_sse42)); // Presets corresponding to x86 CPUs. - + // Features and architecture names are from LLVM's x86 presets: + // https://github.com/llvm/llvm-project/blob/d4493dd1ed58ac3f1eab0c4ca6e363e2b15bfd1c/llvm/lib/Target/X86/X86.td#L1300-L1643 settings.add_preset( "baseline", "A baseline preset with no extensions enabled.", preset!(), ); + + // Intel CPUs + + // Netburst + settings.add_preset("nocona", "Nocona microarchitecture.", preset!(sse3)); + + // Intel Core 2 Solo/Duo + settings.add_preset("core2", "Core 2 microarchitecture.", preset!(sse3)); + settings.add_preset("penryn", "Penryn microarchitecture.", preset!(sse41)); + + // Intel Atom CPUs + let atom = settings.add_preset("atom", "Atom microarchitecture.", preset!(ssse3)); + settings.add_preset("bonnell", "Bonnell microarchitecture.", preset!(atom)); + let silvermont = settings.add_preset( + "silvermont", + "Silvermont microarchitecture.", + preset!(atom && sse42 && has_popcnt), + ); + settings.add_preset("slm", "Silvermont microarchitecture.", preset!(silvermont)); + let goldmont = settings.add_preset( + "goldmont", + "Goldmont microarchitecture.", + preset!(silvermont), + ); + settings.add_preset( + "goldmont-plus", + "Goldmont Plus microarchitecture.", + preset!(goldmont), + ); + let tremont = settings.add_preset("tremont", "Tremont microarchitecture.", preset!(goldmont)); + + let alderlake = settings.add_preset( + "alderlake", + "Alderlake microarchitecture.", + preset!(tremont && has_bmi1 && has_bmi2 && has_lzcnt && has_fma), + ); + let sierra_forest = settings.add_preset( + "sierraforest", + "Sierra Forest microarchitecture.", + preset!(alderlake), + ); + settings.add_preset( + "grandridge", + "Grandridge microarchitecture.", + preset!(sierra_forest), + ); let nehalem = settings.add_preset( "nehalem", "Nehalem microarchitecture.", - preset!(has_sse3 && has_ssse3 && has_sse41 && has_sse42 && has_popcnt), + preset!(sse42 && has_popcnt), + ); + settings.add_preset("corei7", "Core i7 microarchitecture.", preset!(nehalem)); + let westmere = settings.add_preset("westmere", "Westmere microarchitecture.", preset!(nehalem)); + let sandy_bridge = settings.add_preset( + "sandybridge", + "Sandy Bridge microarchitecture.", + preset!(westmere && has_avx), + ); + settings.add_preset( + "corei7-avx", + "Core i7 AVX microarchitecture.", + preset!(sandy_bridge), + ); + let ivy_bridge = settings.add_preset( + "ivybridge", + "Ivy Bridge microarchitecture.", + preset!(sandy_bridge), + ); + settings.add_preset( + "core-avx-i", + "Intel Core CPU with 64-bit extensions.", + preset!(ivy_bridge), ); let haswell = settings.add_preset( "haswell", "Haswell microarchitecture.", - preset!(nehalem && has_bmi1 && has_bmi2 && has_lzcnt), + preset!(ivy_bridge && has_avx2 && has_bmi1 && has_bmi2 && has_fma && has_lzcnt), + ); + settings.add_preset( + "core-avx2", + "Intel Core CPU with AVX2 extensions.", + preset!(haswell), ); let broadwell = settings.add_preset( "broadwell", "Broadwell microarchitecture.", - preset!(haswell && has_fma), + preset!(haswell), ); let skylake = settings.add_preset("skylake", "Skylake microarchitecture.", preset!(broadwell)); + let knights_landing = settings.add_preset( + "knl", + "Knights Landing microarchitecture.", + preset!(has_popcnt && has_avx512f && has_fma && has_bmi1 && has_bmi2 && has_lzcnt), + ); + settings.add_preset( + "knm", + "Knights Mill microarchitecture.", + preset!(knights_landing), + ); + let skylake_avx512 = settings.add_preset( + "skylake-avx512", + "Skylake AVX512 microarchitecture.", + preset!(broadwell && has_avx512f && has_avx512dq && has_avx512vl), + ); + settings.add_preset( + "skx", + "Skylake AVX512 microarchitecture.", + preset!(skylake_avx512), + ); + let cascadelake = settings.add_preset( + "cascadelake", + "Cascade Lake microarchitecture.", + preset!(skylake_avx512), + ); + settings.add_preset( + "cooperlake", + "Cooper Lake microarchitecture.", + preset!(cascadelake), + ); let cannonlake = settings.add_preset( "cannonlake", "Canon Lake microarchitecture.", - preset!(skylake), + preset!(skylake && has_avx512f && has_avx512dq && has_avx512vl && has_avx512vbmi), ); + let icelake_client = settings.add_preset( + "icelake-client", + "Ice Lake microarchitecture.", + preset!(cannonlake && has_avx512bitalg), + ); + // LLVM doesn't use the name "icelake" but Cranelift did in the past; alias it settings.add_preset( "icelake", - "Ice Lake microarchitecture.", - preset!(cannonlake), + "Ice Lake microarchitecture", + preset!(icelake_client), + ); + let icelake_server = settings.add_preset( + "icelake-server", + "Ice Lake (server) microarchitecture.", + preset!(icelake_client), + ); + settings.add_preset( + "tigerlake", + "Tiger Lake microarchitecture.", + preset!(icelake_client), + ); + let sapphire_rapids = settings.add_preset( + "sapphirerapids", + "Saphire Rapids microarchitecture.", + preset!(icelake_server), + ); + settings.add_preset( + "raptorlake", + "Raptor Lake microarchitecture.", + preset!(alderlake), + ); + settings.add_preset( + "meteorlake", + "Meteor Lake microarchitecture.", + preset!(alderlake), + ); + settings.add_preset( + "graniterapids", + "Granite Rapids microarchitecture.", + preset!(sapphire_rapids), ); + + // AMD CPUs + + settings.add_preset("opteron", "Opteron microarchitecture.", preset!()); + settings.add_preset("k8", "K8 Hammer microarchitecture.", preset!()); + settings.add_preset("athlon64", "Athlon64 microarchitecture.", preset!()); + settings.add_preset("athlon-fx", "Athlon FX microarchitecture.", preset!()); settings.add_preset( + "opteron-sse3", + "Opteron microarchitecture with support for SSE3 instructions.", + preset!(sse3), + ); + settings.add_preset( + "k8-sse3", + "K8 Hammer microarchitecture with support for SSE3 instructions.", + preset!(sse3), + ); + settings.add_preset( + "athlon64-sse3", + "Athlon 64 microarchitecture with support for SSE3 instructions.", + preset!(sse3), + ); + let barcelona = settings.add_preset( + "barcelona", + "Barcelona microarchitecture.", + preset!(has_popcnt && has_lzcnt), + ); + settings.add_preset( + "amdfam10", + "AMD Family 10h microarchitecture", + preset!(barcelona), + ); + + let btver1 = settings.add_preset( + "btver1", + "Bobcat microarchitecture.", + preset!(ssse3 && has_lzcnt && has_popcnt), + ); + settings.add_preset( + "btver2", + "Jaguar microarchitecture.", + preset!(btver1 && has_avx && has_bmi1), + ); + + let bdver1 = settings.add_preset( + "bdver1", + "Bulldozer microarchitecture", + preset!(has_lzcnt && has_popcnt && ssse3), + ); + let bdver2 = settings.add_preset( + "bdver2", + "Piledriver microarchitecture.", + preset!(bdver1 && has_bmi1), + ); + let bdver3 = settings.add_preset("bdver3", "Steamroller microarchitecture.", preset!(bdver2)); + settings.add_preset( + "bdver4", + "Excavator microarchitecture.", + preset!(bdver3 && has_avx2 && has_bmi2), + ); + + let znver1 = settings.add_preset( "znver1", "Zen (first generation) microarchitecture.", - preset!( - has_sse3 - && has_ssse3 - && has_sse41 - && has_sse42 - && has_popcnt - && has_bmi1 - && has_bmi2 - && has_lzcnt - ), + preset!(sse42 && has_popcnt && has_bmi1 && has_bmi2 && has_lzcnt && has_fma), + ); + let znver2 = settings.add_preset( + "znver2", + "Zen (second generation) microarchitecture.", + preset!(znver1), + ); + settings.add_preset( + "znver3", + "Zen (third generation) microarchitecture.", + preset!(znver2), + ); + + // Generic + + settings.add_preset("x86-64", "Generic x86-64 microarchitecture.", preset!()); + let x86_64_v2 = settings.add_preset( + "x86-64-v2", + "Generic x86-64 (V2) microarchitecture.", + preset!(sse42 && has_popcnt), + ); + let x86_64_v3 = settings.add_preset( + "x84_64_v3", + "Generic x86_64 (V3) microarchitecture.", + preset!(x86_64_v2 && has_bmi1 && has_bmi2 && has_fma && has_lzcnt && has_avx2), + ); + settings.add_preset( + "x86_64_v4", + "Generic x86_64 (V4) microarchitecture.", + preset!(x86_64_v3 && has_avx512dq && has_avx512vl), ); settings.build() diff --git a/cranelift/codegen/meta/src/lib.rs b/cranelift/codegen/meta/src/lib.rs index 8b525acabf89..764283927f6e 100644 --- a/cranelift/codegen/meta/src/lib.rs +++ b/cranelift/codegen/meta/src/lib.rs @@ -47,7 +47,8 @@ pub fn generate(isas: &[isa::Isa], out_dir: &str, isle_dir: &str) -> Result<(), &shared_defs.all_instructions, "opcodes.rs", "inst_builder.rs", - "clif.isle", + "clif_opt.isle", + "clif_lower.isle", &out_dir, isle_dir, )?; diff --git a/cranelift/codegen/meta/src/shared/entities.rs b/cranelift/codegen/meta/src/shared/entities.rs index f612d3507d0b..374e61f4167b 100644 --- a/cranelift/codegen/meta/src/shared/entities.rs +++ b/cranelift/codegen/meta/src/shared/entities.rs @@ -11,9 +11,17 @@ fn new(format_field_name: &'static str, rust_type: &'static str, doc: &'static s } pub(crate) struct EntityRefs { - /// A reference to a basic block in the same function. - /// This is primarliy used in control flow instructions. - pub(crate) block: OperandKind, + /// A reference to a basic block in the same function, with its arguments provided. + /// This is primarily used in control flow instructions. + pub(crate) block_call: OperandKind, + + /// A reference to a basic block in the same function, with its arguments provided. + /// This is primarily used in control flow instructions. + pub(crate) block_then: OperandKind, + + /// A reference to a basic block in the same function, with its arguments provided. + /// This is primarily used in control flow instructions. + pub(crate) block_else: OperandKind, /// A reference to a stack slot declared in the function preamble. pub(crate) stack_slot: OperandKind, @@ -35,9 +43,6 @@ pub(crate) struct EntityRefs { /// A reference to a jump table declared in the function preamble. pub(crate) jump_table: OperandKind, - /// A reference to a heap declared in the function preamble. - pub(crate) heap: OperandKind, - /// A reference to a table declared in the function preamble. pub(crate) table: OperandKind, @@ -48,11 +53,24 @@ pub(crate) struct EntityRefs { impl EntityRefs { pub fn new() -> Self { Self { - block: new( + block_call: new( "destination", - "ir::Block", - "a basic block in the same function.", + "ir::BlockCall", + "a basic block in the same function, with its arguments provided.", + ), + + block_then: new( + "block_then", + "ir::BlockCall", + "a basic block in the same function, with its arguments provided.", ), + + block_else: new( + "block_else", + "ir::BlockCall", + "a basic block in the same function, with its arguments provided.", + ), + stack_slot: new("stack_slot", "ir::StackSlot", "A stack slot"), dynamic_stack_slot: new( @@ -69,8 +87,6 @@ impl EntityRefs { jump_table: new("table", "ir::JumpTable", "A jump table."), - heap: new("heap", "ir::Heap", "A heap."), - table: new("table", "ir::Table", "A table."), varargs: OperandKind::new( diff --git a/cranelift/codegen/meta/src/shared/formats.rs b/cranelift/codegen/meta/src/shared/formats.rs index 84c2a39af735..35ea2e7f8ff4 100644 --- a/cranelift/codegen/meta/src/shared/formats.rs +++ b/cranelift/codegen/meta/src/shared/formats.rs @@ -8,24 +8,16 @@ pub(crate) struct Formats { pub(crate) binary: Rc, pub(crate) binary_imm8: Rc, pub(crate) binary_imm64: Rc, - pub(crate) branch: Rc, - pub(crate) branch_float: Rc, - pub(crate) branch_icmp: Rc, - pub(crate) branch_int: Rc, pub(crate) branch_table: Rc, + pub(crate) brif: Rc, pub(crate) call: Rc, pub(crate) call_indirect: Rc, pub(crate) cond_trap: Rc, pub(crate) float_compare: Rc, - pub(crate) float_cond: Rc, - pub(crate) float_cond_trap: Rc, pub(crate) func_addr: Rc, - pub(crate) heap_addr: Rc, pub(crate) int_compare: Rc, pub(crate) int_compare_imm: Rc, - pub(crate) int_cond: Rc, - pub(crate) int_cond_trap: Rc, - pub(crate) int_select: Rc, + pub(crate) int_add_trap: Rc, pub(crate) jump: Rc, pub(crate) load: Rc, pub(crate) load_no_offset: Rc, @@ -43,7 +35,6 @@ pub(crate) struct Formats { pub(crate) ternary_imm8: Rc, pub(crate) trap: Rc, pub(crate) unary: Rc, - pub(crate) unary_bool: Rc, pub(crate) unary_const: Rc, pub(crate) unary_global_value: Rc, pub(crate) unary_ieee32: Rc, @@ -62,8 +53,6 @@ impl Formats { unary_ieee64: Builder::new("UnaryIeee64").imm(&imm.ieee64).build(), - unary_bool: Builder::new("UnaryBool").imm(&imm.boolean).build(), - unary_const: Builder::new("UnaryConst").imm(&imm.pool_constant).build(), unary_global_value: Builder::new("UnaryGlobalValue") @@ -116,56 +105,18 @@ impl Formats { .imm(&imm.imm64) .build(), - int_cond: Builder::new("IntCond").imm(&imm.intcc).value().build(), - float_compare: Builder::new("FloatCompare") .imm(&imm.floatcc) .value() .value() .build(), - float_cond: Builder::new("FloatCond").imm(&imm.floatcc).value().build(), - - int_select: Builder::new("IntSelect") - .imm(&imm.intcc) - .value() - .value() - .value() - .build(), - - jump: Builder::new("Jump").imm(&entities.block).varargs().build(), - - branch: Builder::new("Branch") - .value() - .imm(&entities.block) - .varargs() - .build(), - - branch_int: Builder::new("BranchInt") - .imm(&imm.intcc) - .value() - .imm(&entities.block) - .varargs() - .build(), - - branch_float: Builder::new("BranchFloat") - .imm(&imm.floatcc) - .value() - .imm(&entities.block) - .varargs() - .build(), + jump: Builder::new("Jump").block().build(), - branch_icmp: Builder::new("BranchIcmp") - .imm(&imm.intcc) - .value() - .value() - .imm(&entities.block) - .varargs() - .build(), + brif: Builder::new("Brif").value().block().block().build(), branch_table: Builder::new("BranchTable") .value() - .imm(&entities.block) .imm(&entities.jump_table) .build(), @@ -241,13 +192,6 @@ impl Formats { .imm(&entities.dynamic_stack_slot) .build(), - // Accessing a WebAssembly heap. - heap_addr: Builder::new("HeapAddr") - .imm(&entities.heap) - .value() - .imm(&imm.uimm32) - .build(), - // Accessing a WebAssembly table. table_addr: Builder::new("TableAddr") .imm(&entities.table) @@ -259,14 +203,8 @@ impl Formats { cond_trap: Builder::new("CondTrap").value().imm(&imm.trapcode).build(), - int_cond_trap: Builder::new("IntCondTrap") - .imm(&imm.intcc) + int_add_trap: Builder::new("IntAddTrap") .value() - .imm(&imm.trapcode) - .build(), - - float_cond_trap: Builder::new("FloatCondTrap") - .imm(&imm.floatcc) .value() .imm(&imm.trapcode) .build(), diff --git a/cranelift/codegen/meta/src/shared/immediates.rs b/cranelift/codegen/meta/src/shared/immediates.rs index 4808ce559327..9f908c93da48 100644 --- a/cranelift/codegen/meta/src/shared/immediates.rs +++ b/cranelift/codegen/meta/src/shared/immediates.rs @@ -14,9 +14,6 @@ pub(crate) struct Immediates { /// counts on shift instructions. pub uimm8: OperandKind, - /// An unsigned 32-bit immediate integer operand. - pub uimm32: OperandKind, - /// An unsigned 128-bit immediate integer operand. /// /// This operand is used to pass entire 128-bit vectors as immediates to instructions like @@ -44,11 +41,6 @@ pub(crate) struct Immediates { /// IEEE 754-2008 binary64 interchange format. pub ieee64: OperandKind, - /// An immediate boolean operand. - /// - /// This type of immediate boolean can interact with SSA values with any BoolType type. - pub boolean: OperandKind, - /// A condition code for comparing integer values. /// /// This enumerated operand kind is used for the `icmp` instruction and corresponds to the @@ -112,11 +104,6 @@ impl Immediates { "ir::immediates::Uimm8", "An 8-bit immediate unsigned integer.", ), - uimm32: new_imm( - "imm", - "ir::immediates::Uimm32", - "A 32-bit immediate unsigned integer.", - ), uimm128: new_imm( "imm", "ir::Immediate", @@ -142,7 +129,6 @@ impl Immediates { "ir::immediates::Ieee64", "A 64-bit immediate floating point number.", ), - boolean: new_imm("imm", "bool", "An immediate boolean."), intcc: { let mut intcc_values = HashMap::new(); intcc_values.insert("eq", "Equal"); @@ -155,8 +141,6 @@ impl Immediates { intcc_values.insert("ugt", "UnsignedGreaterThan"); intcc_values.insert("ule", "UnsignedLessThanOrEqual"); intcc_values.insert("ult", "UnsignedLessThan"); - intcc_values.insert("of", "Overflow"); - intcc_values.insert("nof", "NotOverflow"); new_enum( "cond", "ir::condcodes::IntCC", @@ -190,6 +174,7 @@ impl Immediates { }, memflags: new_imm("flags", "ir::MemFlags", "Memory operation flags"), + trapcode: { let mut trapcode_values = HashMap::new(); trapcode_values.insert("stk_ovf", "StackOverflow"); diff --git a/cranelift/codegen/meta/src/shared/instructions.rs b/cranelift/codegen/meta/src/shared/instructions.rs old mode 100644 new mode 100755 index c3b7467aa162..575caa4437a5 --- a/cranelift/codegen/meta/src/shared/instructions.rs +++ b/cranelift/codegen/meta/src/shared/instructions.rs @@ -17,8 +17,8 @@ fn define_control_flow( imm: &Immediates, entities: &EntityRefs, ) { - let block = &Operand::new("block", &entities.block).with_doc("Destination basic block"); - let args = &Operand::new("args", &entities.varargs).with_doc("block arguments"); + let block_call = &Operand::new("block_call", &entities.block_call) + .with_doc("Destination basic block, with its arguments provided"); ig.push( Inst::new( @@ -32,127 +32,33 @@ fn define_control_flow( "#, &formats.jump, ) - .operands_in(vec![block, args]) - .is_terminator(true) - .is_branch(true), + .operands_in(vec![block_call]) + .branches(), ); - let Testable = &TypeVar::new( - "Testable", - "A scalar boolean or integer type", - TypeSetBuilder::new() - .ints(Interval::All) - .bools(Interval::All) - .build(), - ); - - { - let c = &Operand::new("c", Testable).with_doc("Controlling value to test"); - - ig.push( - Inst::new( - "brz", - r#" - Branch when zero. - - If ``c`` is a `b1` value, take the branch when ``c`` is false. If - ``c`` is an integer value, take the branch when ``c = 0``. - "#, - &formats.branch, - ) - .operands_in(vec![c, block, args]) - .is_branch(true), - ); - - ig.push( - Inst::new( - "brnz", - r#" - Branch when non-zero. - - If ``c`` is a `b1` value, take the branch when ``c`` is true. If - ``c`` is an integer value, take the branch when ``c != 0``. - "#, - &formats.branch, - ) - .operands_in(vec![c, block, args]) - .is_branch(true), - ); - } - - let iB = &TypeVar::new( - "iB", - "A scalar integer type", + let ScalarTruthy = &TypeVar::new( + "ScalarTruthy", + "A scalar truthy type", TypeSetBuilder::new().ints(Interval::All).build(), ); - let iflags: &TypeVar = &ValueType::Special(types::Flag::IFlags.into()).into(); - let fflags: &TypeVar = &ValueType::Special(types::Flag::FFlags.into()).into(); { - let Cond = &Operand::new("Cond", &imm.intcc); - let x = &Operand::new("x", iB); - let y = &Operand::new("y", iB); - - ig.push( - Inst::new( - "br_icmp", - r#" - Compare scalar integers and branch. - - Compare ``x`` and ``y`` in the same way as the `icmp` instruction - and take the branch if the condition is true: - - ```text - br_icmp ugt v1, v2, block4(v5, v6) - ``` - - is semantically equivalent to: - - ```text - v10 = icmp ugt, v1, v2 - brnz v10, block4(v5, v6) - ``` - - Some RISC architectures like MIPS and RISC-V provide instructions that - implement all or some of the condition codes. The instruction can also - be used to represent *macro-op fusion* on architectures like Intel's. - "#, - &formats.branch_icmp, - ) - .operands_in(vec![Cond, x, y, block, args]) - .is_branch(true), - ); - - let f = &Operand::new("f", iflags); + let c = &Operand::new("c", ScalarTruthy).with_doc("Controlling value to test"); + let block_then = &Operand::new("block_then", &entities.block_then).with_doc("Then block"); + let block_else = &Operand::new("block_else", &entities.block_else).with_doc("Else block"); ig.push( Inst::new( "brif", r#" - Branch when condition is true in integer CPU flags. - "#, - &formats.branch_int, - ) - .operands_in(vec![Cond, f, block, args]) - .is_branch(true), - ); - } - - { - let Cond = &Operand::new("Cond", &imm.floatcc); - - let f = &Operand::new("f", fflags); + Conditional branch when cond is non-zero. - ig.push( - Inst::new( - "brff", - r#" - Branch when condition is true in floating point CPU flags. + Take the ``then`` branch when ``c != 0``, and the ``else`` branch otherwise. "#, - &formats.branch_float, + &formats.brif, ) - .operands_in(vec![Cond, f, block, args]) - .is_branch(true), + .operands_in(vec![c, block_then, block_else]) + .branches(), ); } @@ -186,9 +92,8 @@ fn define_control_flow( "#, &formats.branch_table, ) - .operands_in(vec![x, block, JT]) - .is_terminator(true) - .is_branch(true), + .operands_in(vec![x, JT]) + .branches(), ); } @@ -206,9 +111,9 @@ fn define_control_flow( "#, &formats.nullary, ) - .other_side_effects(true) - .can_load(true) - .can_store(true), + .other_side_effects() + .can_load() + .can_store(), ); { @@ -222,11 +127,11 @@ fn define_control_flow( &formats.trap, ) .operands_in(vec![code]) - .can_trap(true) - .is_terminator(true), + .can_trap() + .terminates_block(), ); - let c = &Operand::new("c", Testable).with_doc("Controlling value to test"); + let c = &Operand::new("c", ScalarTruthy).with_doc("Controlling value to test"); ig.push( Inst::new( "trapz", @@ -238,7 +143,7 @@ fn define_control_flow( &formats.cond_trap, ) .operands_in(vec![c, code]) - .can_trap(true), + .can_trap(), ); ig.push( @@ -252,10 +157,10 @@ fn define_control_flow( &formats.trap, ) .operands_in(vec![code]) - .can_trap(true), + .can_trap(), ); - let c = &Operand::new("c", Testable).with_doc("Controlling value to test"); + let c = &Operand::new("c", ScalarTruthy).with_doc("Controlling value to test"); ig.push( Inst::new( "trapnz", @@ -267,7 +172,7 @@ fn define_control_flow( &formats.cond_trap, ) .operands_in(vec![c, code]) - .can_trap(true), + .can_trap(), ); ig.push( @@ -281,36 +186,7 @@ fn define_control_flow( &formats.cond_trap, ) .operands_in(vec![c, code]) - .can_trap(true), - ); - - let Cond = &Operand::new("Cond", &imm.intcc); - let f = &Operand::new("f", iflags); - ig.push( - Inst::new( - "trapif", - r#" - Trap when condition is true in integer CPU flags. - "#, - &formats.int_cond_trap, - ) - .operands_in(vec![Cond, f, code]) - .can_trap(true), - ); - - let Cond = &Operand::new("Cond", &imm.floatcc); - let f = &Operand::new("f", fflags); - let code = &Operand::new("code", &imm.trapcode); - ig.push( - Inst::new( - "trapff", - r#" - Trap when condition is true in floating point CPU flags. - "#, - &formats.float_cond_trap, - ) - .operands_in(vec![Cond, f, code]) - .can_trap(true), + .can_trap(), ); } @@ -328,8 +204,7 @@ fn define_control_flow( &formats.multiary, ) .operands_in(vec![rvals]) - .is_return(true) - .is_terminator(true), + .returns(), ); let FN = &Operand::new("FN", &entities.func_ref) @@ -349,7 +224,7 @@ fn define_control_flow( ) .operands_in(vec![FN, args]) .operands_out(vec![rvals]) - .is_call(true), + .call(), ); let SIG = &Operand::new("SIG", &entities.sig_ref).with_doc("function signature"); @@ -374,7 +249,52 @@ fn define_control_flow( ) .operands_in(vec![SIG, callee, args]) .operands_out(vec![rvals]) - .is_call(true), + .call(), + ); + + ig.push( + Inst::new( + "return_call", + r#" + Direct tail call. + + Tail call a function which has been declared in the preamble. The + argument types must match the function's signature, the caller and + callee calling conventions must be the same, and must be a calling + convention that supports tail calls. + + This instruction is a block terminator. + "#, + &formats.call, + ) + .operands_in(vec![FN, args]) + .returns() + .call(), + ); + + ig.push( + Inst::new( + "return_call_indirect", + r#" + Indirect tail call. + + Call the function pointed to by `callee` with the given arguments. The + argument types must match the function's signature, the caller and + callee calling conventions must be the same, and must be a calling + convention that supports tail calls. + + This instruction is a block terminator. + + Note that this is different from WebAssembly's ``tail_call_indirect``; + the callee is a native address, rather than a table index. For + WebAssembly, `table_addr` and `load` are used to obtain a native address + from a table. + "#, + &formats.call_indirect, + ) + .operands_in(vec![SIG, callee, args]) + .returns() + .call(), ); let FN = &Operand::new("FN", &entities.func_ref) @@ -412,7 +332,6 @@ fn define_simd_lane_access( TypeSetBuilder::new() .ints(Interval::All) .floats(Interval::All) - .bools(Interval::All) .simd_lanes(Interval::All) .dynamic_simd_lanes(Interval::All) .includes_scalars(false) @@ -527,7 +446,7 @@ fn define_simd_arithmetic( ig.push( Inst::new( - "imin", + "smin", r#" Signed integer minimum. "#, @@ -551,7 +470,7 @@ fn define_simd_arithmetic( ig.push( Inst::new( - "imax", + "smax", r#" Signed integer maximum. "#, @@ -592,6 +511,8 @@ fn define_simd_arithmetic( "avg_round", r#" Unsigned average with rounding: `a := (x + y + 1) // 2` + + The addition does not lose any information (such as from overflow). "#, &formats.binary, ) @@ -680,10 +601,7 @@ pub(crate) fn define( define_simd_arithmetic(&mut ig, formats, imm, entities); // Operand kind shorthands. - let iflags: &TypeVar = &ValueType::Special(types::Flag::IFlags.into()).into(); - let fflags: &TypeVar = &ValueType::Special(types::Flag::FFlags.into()).into(); - - let b1: &TypeVar = &ValueType::from(LaneType::from(types::Bool::B1)).into(); + let i8: &TypeVar = &ValueType::from(LaneType::from(types::Int::I8)).into(); let f32_: &TypeVar = &ValueType::from(LaneType::from(types::Float::F32)).into(); let f64_: &TypeVar = &ValueType::from(LaneType::from(types::Float::F64)).into(); @@ -698,19 +616,20 @@ pub(crate) fn define( .build(), ); - let Bool = &TypeVar::new( - "Bool", - "A scalar or vector boolean type", + let NarrowInt = &TypeVar::new( + "NarrowInt", + "An integer type with lanes type to `i64`", TypeSetBuilder::new() - .bools(Interval::All) + .ints(8..64) .simd_lanes(Interval::All) + .dynamic_simd_lanes(Interval::All) .build(), ); - let ScalarBool = &TypeVar::new( - "ScalarBool", - "A scalar boolean type", - TypeSetBuilder::new().bools(Interval::All).build(), + let ScalarTruthy = &TypeVar::new( + "ScalarTruthy", + "A scalar truthy type", + TypeSetBuilder::new().ints(Interval::All).build(), ); let iB = &TypeVar::new( @@ -719,6 +638,12 @@ pub(crate) fn define( TypeSetBuilder::new().ints(Interval::All).build(), ); + let iSwappable = &TypeVar::new( + "iSwappable", + "A multi byte scalar integer type", + TypeSetBuilder::new().ints(16..128).build(), + ); + let iAddr = &TypeVar::new( "iAddr", "An integer address type", @@ -731,41 +656,28 @@ pub(crate) fn define( TypeSetBuilder::new().refs(Interval::All).build(), ); - let Testable = &TypeVar::new( - "Testable", - "A scalar boolean or integer type", - TypeSetBuilder::new() - .ints(Interval::All) - .bools(Interval::All) - .build(), - ); - let TxN = &TypeVar::new( "TxN", "A SIMD vector type", TypeSetBuilder::new() .ints(Interval::All) .floats(Interval::All) - .bools(Interval::All) .simd_lanes(Interval::All) .includes_scalars(false) .build(), ); let Any = &TypeVar::new( "Any", - "Any integer, float, boolean, or reference scalar or vector type", + "Any integer, float, or reference scalar or vector type", TypeSetBuilder::new() .ints(Interval::All) .floats(Interval::All) - .bools(Interval::All) .refs(Interval::All) .simd_lanes(Interval::All) .includes_scalars(true) .build(), ); - let AnyTo = &TypeVar::copy_from(Any, "AnyTo".to_string()); - let Mem = &TypeVar::new( "Mem", "Any type that can be stored in memory", @@ -803,7 +715,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -818,7 +730,7 @@ pub(crate) fn define( &formats.store, ) .operands_in(vec![MemFlags, x, p, Offset]) - .can_store(true), + .can_store(), ); let iExt8 = &TypeVar::new( @@ -841,7 +753,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -856,7 +768,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -870,7 +782,7 @@ pub(crate) fn define( &formats.store, ) .operands_in(vec![MemFlags, x, p, Offset]) - .can_store(true), + .can_store(), ); let iExt16 = &TypeVar::new( @@ -893,7 +805,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -908,7 +820,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -922,7 +834,7 @@ pub(crate) fn define( &formats.store, ) .operands_in(vec![MemFlags, x, p, Offset]) - .can_store(true), + .can_store(), ); let iExt32 = &TypeVar::new( @@ -945,7 +857,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -960,7 +872,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -974,7 +886,7 @@ pub(crate) fn define( &formats.store, ) .operands_in(vec![MemFlags, x, p, Offset]) - .can_store(true), + .can_store(), ); let I16x8 = &TypeVar::new( @@ -999,7 +911,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -1013,7 +925,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); let I32x4 = &TypeVar::new( @@ -1038,7 +950,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -1052,7 +964,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); let I64x2 = &TypeVar::new( @@ -1077,7 +989,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -1091,7 +1003,7 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); let x = &Operand::new("x", Mem).with_doc("Value to be stored"); @@ -1116,7 +1028,7 @@ pub(crate) fn define( ) .operands_in(vec![SS, Offset]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -1135,7 +1047,7 @@ pub(crate) fn define( &formats.stack_store, ) .operands_in(vec![x, SS, Offset]) - .can_store(true), + .can_store(), ); ig.push( @@ -1167,7 +1079,7 @@ pub(crate) fn define( ) .operands_in(vec![DSS]) .operands_out(vec![a]) - .can_load(true), + .can_load(), ); ig.push( @@ -1182,7 +1094,7 @@ pub(crate) fn define( &formats.dynamic_stack_store, ) .operands_in(vec![x, DSS]) - .can_store(true), + .can_store(), ); let GV = &Operand::new("GV", &entities.global_value); @@ -1236,36 +1148,6 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let HeapOffset = &TypeVar::new( - "HeapOffset", - "An unsigned heap offset", - TypeSetBuilder::new().ints(32..64).build(), - ); - - let H = &Operand::new("H", &entities.heap); - let p = &Operand::new("p", HeapOffset); - let Size = &Operand::new("Size", &imm.uimm32).with_doc("Size in bytes"); - - ig.push( - Inst::new( - "heap_addr", - r#" - Bounds check and compute absolute address of heap memory. - - Verify that the offset range ``p .. p + Size - 1`` is in bounds for the - heap H, and generate an absolute address that is safe to dereference. - - 1. If ``p + Size`` is not greater than the heap bound, return an - absolute address corresponding to a byte offset of ``p`` from the - heap's base address. - 2. If ``p + Size`` is greater than the heap bound, generate a trap. - "#, - &formats.heap_addr, - ) - .operands_in(vec![H, p, Size]) - .operands_out(vec![addr]), - ); - // Note this instruction is marked as having other side-effects, so GVN won't try to hoist it, // which would result in it being subject to spilling. While not hoisting would generally hurt // performance, since a computed value used many times may need to be regenerated before each @@ -1281,7 +1163,7 @@ pub(crate) fn define( &formats.nullary, ) .operands_out(vec![addr]) - .other_side_effects(true), + .other_side_effects(), ); ig.push( @@ -1293,7 +1175,7 @@ pub(crate) fn define( &formats.unary, ) .operands_in(vec![addr]) - .other_side_effects(true), + .other_side_effects(), ); ig.push( @@ -1366,7 +1248,7 @@ pub(crate) fn define( ); let N = &Operand::new("N", &imm.imm64); - let a = &Operand::new("a", Int).with_doc("A constant integer scalar or vector value"); + let a = &Operand::new("a", NarrowInt).with_doc("A constant integer scalar or vector value"); ig.push( Inst::new( @@ -1417,24 +1299,6 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let N = &Operand::new("N", &imm.boolean); - let a = &Operand::new("a", Bool).with_doc("A constant boolean scalar or vector value"); - - ig.push( - Inst::new( - "bconst", - r#" - Boolean constant. - - Create a scalar boolean SSA value with an immediate constant value, or - a boolean vector where all the lanes have the same value. - "#, - &formats.unary_bool, - ) - .operands_in(vec![N]) - .operands_out(vec![a]), - ); - let N = &Operand::new("N", &imm.pool_constant) .with_doc("The 16 immediate bytes of a 128-bit vector"); let a = &Operand::new("a", TxN).with_doc("A constant vector value"); @@ -1453,21 +1317,6 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let constant = - &Operand::new("constant", &imm.pool_constant).with_doc("A constant in the constant pool"); - let address = &Operand::new("address", iAddr); - ig.push( - Inst::new( - "const_addr", - r#" - Calculate the base address of a value in the constant pool. - "#, - &formats.unary_const, - ) - .operands_in(vec![constant]) - .operands_out(vec![address]), - ); - let mask = &Operand::new("mask", &imm.uimm128) .with_doc("The 16 immediate bytes used for selecting the elements to shuffle"); let Tx16 = &TypeVar::new( @@ -1476,7 +1325,6 @@ pub(crate) fn define( lane counts and widths", TypeSetBuilder::new() .ints(8..8) - .bools(8..8) .simd_lanes(16..16) .includes_scalars(false) .build(), @@ -1526,7 +1374,7 @@ pub(crate) fn define( &formats.nullary, )); - let c = &Operand::new("c", Testable).with_doc("Controlling value to test"); + let c = &Operand::new("c", ScalarTruthy).with_doc("Controlling value to test"); let x = &Operand::new("x", Any).with_doc("Value to use when `c` is true"); let y = &Operand::new("y", Any).with_doc("Value to use when `c` is false"); let a = &Operand::new("a", Any); @@ -1546,28 +1394,13 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let cc = &Operand::new("cc", &imm.intcc).with_doc("Controlling condition code"); - let flags = &Operand::new("flags", iflags).with_doc("The machine's flag register"); - ig.push( Inst::new( - "selectif", - r#" - Conditional select, dependent on integer condition codes. - "#, - &formats.int_select, - ) - .operands_in(vec![cc, flags, x, y]) - .operands_out(vec![a]), - ); - - ig.push( - Inst::new( - "selectif_spectre_guard", + "select_spectre_guard", r#" Conditional select intended for Spectre guards. - This operation is semantically equivalent to a selectif instruction. + This operation is semantically equivalent to a select instruction. However, it is guaranteed to not be removed or otherwise altered by any optimization pass, and is guaranteed to result in a conditional-move instruction, not a branch-based lowering. As such, it is suitable @@ -1582,11 +1415,14 @@ pub(crate) fn define( speculative path, this ensures that no Spectre vulnerability will exist. "#, - &formats.int_select, + &formats.ternary, ) - .operands_in(vec![cc, flags, x, y]) + .operands_in(vec![c, x, y]) .operands_out(vec![a]) - .other_side_effects(true), + .other_side_effects() + // We can de-duplicate spectre selects since the side effect is + // idempotent. + .side_effects_idempotent(), ); let c = &Operand::new("c", Any).with_doc("Controlling value to test"); @@ -1606,82 +1442,6 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let x = &Operand::new("x", Any); - - ig.push( - Inst::new( - "copy", - r#" - Register-register copy. - - This instruction copies its input, preserving the value type. - - A pure SSA-form program does not need to copy values, but this - instruction is useful for representing intermediate stages during - instruction transformations, and the register allocator needs a way of - representing register copies. - "#, - &formats.unary, - ) - .operands_in(vec![x]) - .operands_out(vec![a]), - ); - - let x = &Operand::new("x", TxN).with_doc("Vector to split"); - let lo = &Operand::new("lo", &TxN.half_vector()).with_doc("Low-numbered lanes of `x`"); - let hi = &Operand::new("hi", &TxN.half_vector()).with_doc("High-numbered lanes of `x`"); - - ig.push( - Inst::new( - "vsplit", - r#" - Split a vector into two halves. - - Split the vector `x` into two separate values, each containing half of - the lanes from ``x``. The result may be two scalars if ``x`` only had - two lanes. - "#, - &formats.unary, - ) - .operands_in(vec![x]) - .operands_out(vec![lo, hi]), - ); - - let Any128 = &TypeVar::new( - "Any128", - "Any scalar or vector type with as most 128 lanes", - TypeSetBuilder::new() - .ints(Interval::All) - .floats(Interval::All) - .bools(Interval::All) - .simd_lanes(1..128) - .includes_scalars(true) - .build(), - ); - - let x = &Operand::new("x", Any128).with_doc("Low-numbered lanes"); - let y = &Operand::new("y", Any128).with_doc("High-numbered lanes"); - let a = &Operand::new("a", &Any128.double_vector()).with_doc("Concatenation of `x` and `y`"); - - ig.push( - Inst::new( - "vconcat", - r#" - Vector concatenation. - - Return a vector formed by concatenating ``x`` and ``y``. The resulting - vector type has twice as many lanes as each of the inputs. The lanes of - ``x`` appear as the low-numbered lanes, and the lanes of ``y`` become - the high-numbered lanes of ``a``. - - It is possible to form a vector by concatenating two scalars. - "#, - &formats.binary, - ) - .operands_in(vec![x, y]) - .operands_out(vec![a]), - ); - let c = &Operand::new("c", &TxN.as_bool()).with_doc("Controlling vector"); let x = &Operand::new("x", TxN).with_doc("Value to use where `c` is true"); let y = &Operand::new("y", TxN).with_doc("Value to use where `c` is false"); @@ -1693,7 +1453,7 @@ pub(crate) fn define( r#" Vector lane select. - Select lanes from ``x`` or ``y`` controlled by the lanes of the boolean + Select lanes from ``x`` or ``y`` controlled by the lanes of the truthy vector ``c``. "#, &formats.ternary, @@ -1702,7 +1462,7 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let s = &Operand::new("s", b1); + let s = &Operand::new("s", i8); ig.push( Inst::new( @@ -1772,15 +1532,9 @@ pub(crate) fn define( | sge | uge | Greater than or equal | | sgt | ugt | Greater than | | sle | ule | Less than or equal | - | of | * | Overflow | - | nof | * | No Overflow | - - \* The unsigned version of overflow condition for add has ISA-specific semantics and thus - has been kept as a method on the TargetIsa trait as - [unsigned_add_overflow_condition][crate::isa::TargetIsa::unsigned_add_overflow_condition]. - When this instruction compares integer vectors, it returns a boolean - vector of lane-wise comparisons. + When this instruction compares integer vectors, it returns a vector of + lane-wise comparisons. "#, &formats.int_compare, ) @@ -1788,7 +1542,7 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let a = &Operand::new("a", b1); + let a = &Operand::new("a", i8); let x = &Operand::new("x", iB); let Y = &Operand::new("Y", &imm.imm64); @@ -1799,7 +1553,7 @@ pub(crate) fn define( Compare scalar integer to a constant. This is the same as the `icmp` instruction, except one operand is - an immediate constant. + a sign extended 64 bit immediate constant. This instruction can only compare scalars. Use `icmp` for lane-wise vector comparisons. @@ -1810,40 +1564,6 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let f = &Operand::new("f", iflags); - let x = &Operand::new("x", iB); - let y = &Operand::new("y", iB); - - ig.push( - Inst::new( - "ifcmp", - r#" - Compare scalar integers and return flags. - - Compare two scalar integer values and return integer CPU flags - representing the result. - "#, - &formats.binary, - ) - .operands_in(vec![x, y]) - .operands_out(vec![f]), - ); - - ig.push( - Inst::new( - "ifcmp_imm", - r#" - Compare scalar integer to a constant and return flags. - - Like `icmp_imm`, but returns integer CPU flags instead of testing - a specific condition code. - "#, - &formats.binary_imm64, - ) - .operands_in(vec![x, Y]) - .operands_out(vec![f]), - ); - let a = &Operand::new("a", Int); let x = &Operand::new("x", Int); let y = &Operand::new("y", Int); @@ -1996,7 +1716,8 @@ pub(crate) fn define( ) .operands_in(vec![x, y]) .operands_out(vec![a]) - .can_trap(true), + .can_trap() + .side_effects_idempotent(), ); ig.push( @@ -2014,7 +1735,8 @@ pub(crate) fn define( ) .operands_in(vec![x, y]) .operands_out(vec![a]) - .can_trap(true), + .can_trap() + .side_effects_idempotent(), ); ig.push( @@ -2029,7 +1751,8 @@ pub(crate) fn define( ) .operands_in(vec![x, y]) .operands_out(vec![a]) - .can_trap(true), + .can_trap() + .side_effects_idempotent(), ); ig.push( @@ -2044,7 +1767,8 @@ pub(crate) fn define( ) .operands_in(vec![x, y]) .operands_out(vec![a]) - .can_trap(true), + .can_trap() + .side_effects_idempotent(), ); } @@ -2058,7 +1782,7 @@ pub(crate) fn define( r#" Add immediate integer. - Same as `iadd`, but one operand is an immediate constant. + Same as `iadd`, but one operand is a sign extended 64 bit immediate constant. Polymorphic over all scalar integer types, but does not support vector types. @@ -2075,6 +1799,8 @@ pub(crate) fn define( r#" Integer multiplication by immediate constant. + Same as `imul`, but one operand is a sign extended 64 bit immediate constant. + Polymorphic over all scalar integer types, but does not support vector types. "#, @@ -2090,6 +1816,8 @@ pub(crate) fn define( r#" Unsigned integer division by an immediate constant. + Same as `udiv`, but one operand is a zero extended 64 bit immediate constant. + This operation traps if the divisor is zero. "#, &formats.binary_imm64, @@ -2104,6 +1832,8 @@ pub(crate) fn define( r#" Signed integer division by an immediate constant. + Same as `sdiv`, but one operand is a sign extended 64 bit immediate constant. + This operation traps if the divisor is zero, or if the result is not representable in `B` bits two's complement. This only happens when `x = -2^{B-1}, Y = -1`. @@ -2120,6 +1850,8 @@ pub(crate) fn define( r#" Unsigned integer remainder with immediate divisor. + Same as `urem`, but one operand is a zero extended 64 bit immediate constant. + This operation traps if the divisor is zero. "#, &formats.binary_imm64, @@ -2134,6 +1866,8 @@ pub(crate) fn define( r#" Signed integer remainder with immediate divisor. + Same as `srem`, but one operand is a sign extended 64 bit immediate constant. + This operation traps if the divisor is zero. "#, &formats.binary_imm64, @@ -2148,6 +1882,8 @@ pub(crate) fn define( r#" Immediate reverse wrapping subtraction: `a := Y - x \pmod{2^B}`. + The immediate operand is a sign extended 64 bit constant. + Also works as integer negation when `Y = 0`. Use `iadd_imm` with a negative immediate operand for the reverse immediate subtraction. @@ -2165,15 +1901,10 @@ pub(crate) fn define( let x = &Operand::new("x", iB); let y = &Operand::new("y", iB); - let c_in = &Operand::new("c_in", b1).with_doc("Input carry flag"); - let c_out = &Operand::new("c_out", b1).with_doc("Output carry flag"); - let b_in = &Operand::new("b_in", b1).with_doc("Input borrow flag"); - let b_out = &Operand::new("b_out", b1).with_doc("Output borrow flag"); - - let c_if_in = &Operand::new("c_in", iflags); - let c_if_out = &Operand::new("c_out", iflags); - let b_if_in = &Operand::new("b_in", iflags); - let b_if_out = &Operand::new("b_out", iflags); + let c_in = &Operand::new("c_in", i8).with_doc("Input carry flag"); + let c_out = &Operand::new("c_out", i8).with_doc("Output carry flag"); + let b_in = &Operand::new("b_in", i8).with_doc("Input borrow flag"); + let b_out = &Operand::new("b_out", i8).with_doc("Output borrow flag"); ig.push( Inst::new( @@ -2196,27 +1927,6 @@ pub(crate) fn define( .operands_out(vec![a]), ); - ig.push( - Inst::new( - "iadd_ifcin", - r#" - Add integers with carry in. - - Same as `iadd` with an additional carry flag input. Computes: - - ```text - a = x + y + c_{in} \pmod 2^B - ``` - - Polymorphic over all scalar integer types, but does not support vector - types. - "#, - &formats.ternary, - ) - .operands_in(vec![x, y, c_if_in]) - .operands_out(vec![a]), - ); - ig.push( Inst::new( "iadd_cout", @@ -2239,28 +1949,6 @@ pub(crate) fn define( .operands_out(vec![a, c_out]), ); - ig.push( - Inst::new( - "iadd_ifcout", - r#" - Add integers with carry out. - - Same as `iadd` with an additional carry flag output. - - ```text - a &= x + y \pmod 2^B \\ - c_{out} &= x+y >= 2^B - ``` - - Polymorphic over all scalar integer types, but does not support vector - types. - "#, - &formats.binary, - ) - .operands_in(vec![x, y]) - .operands_out(vec![a, c_if_out]), - ); - ig.push( Inst::new( "iadd_carry", @@ -2283,27 +1971,34 @@ pub(crate) fn define( .operands_out(vec![a, c_out]), ); - ig.push( - Inst::new( - "iadd_ifcarry", - r#" - Add integers with carry in and out. + { + let code = &Operand::new("code", &imm.trapcode); - Same as `iadd` with an additional carry flag input and output. + let i32_64 = &TypeVar::new( + "i32_64", + "A 32 or 64-bit scalar integer type", + TypeSetBuilder::new().ints(32..64).build(), + ); - ```text - a &= x + y + c_{in} \pmod 2^B \\ - c_{out} &= x + y + c_{in} >= 2^B - ``` + let a = &Operand::new("a", i32_64); + let x = &Operand::new("x", i32_64); + let y = &Operand::new("y", i32_64); + ig.push( + Inst::new( + "uadd_overflow_trap", + r#" + Unsigned addition of x and y, trapping if the result overflows. - Polymorphic over all scalar integer types, but does not support vector - types. - "#, - &formats.ternary, - ) - .operands_in(vec![x, y, c_if_in]) - .operands_out(vec![a, c_if_out]), - ); + Accepts 32 or 64-bit integers, and does not support vector types. + "#, + &formats.int_add_trap, + ) + .operands_in(vec![x, y, code]) + .operands_out(vec![a]) + .can_trap() + .side_effects_idempotent(), + ); + } ig.push( Inst::new( @@ -2326,27 +2021,6 @@ pub(crate) fn define( .operands_out(vec![a]), ); - ig.push( - Inst::new( - "isub_ifbin", - r#" - Subtract integers with borrow in. - - Same as `isub` with an additional borrow flag input. Computes: - - ```text - a = x - (y + b_{in}) \pmod 2^B - ``` - - Polymorphic over all scalar integer types, but does not support vector - types. - "#, - &formats.ternary, - ) - .operands_in(vec![x, y, b_if_in]) - .operands_out(vec![a]), - ); - ig.push( Inst::new( "isub_bout", @@ -2369,28 +2043,6 @@ pub(crate) fn define( .operands_out(vec![a, b_out]), ); - ig.push( - Inst::new( - "isub_ifbout", - r#" - Subtract integers with borrow out. - - Same as `isub` with an additional borrow flag output. - - ```text - a &= x - y \pmod 2^B \\ - b_{out} &= x < y - ``` - - Polymorphic over all scalar integer types, but does not support vector - types. - "#, - &formats.binary, - ) - .operands_in(vec![x, y]) - .operands_out(vec![a, b_if_out]), - ); - ig.push( Inst::new( "isub_borrow", @@ -2413,35 +2065,12 @@ pub(crate) fn define( .operands_out(vec![a, b_out]), ); - ig.push( - Inst::new( - "isub_ifborrow", - r#" - Subtract integers with borrow in and out. - - Same as `isub` with an additional borrow flag input and output. - - ```text - a &= x - (y + b_{in}) \pmod 2^B \\ - b_{out} &= x < y + b_{in} - ``` - - Polymorphic over all scalar integer types, but does not support vector - types. - "#, - &formats.ternary, - ) - .operands_in(vec![x, y, b_if_in]) - .operands_out(vec![a, b_if_out]), - ); - let bits = &TypeVar::new( "bits", - "Any integer, float, or boolean scalar or vector type", + "Any integer, float, or vector type", TypeSetBuilder::new() .ints(Interval::All) .floats(Interval::All) - .bools(Interval::All) .simd_lanes(Interval::All) .includes_scalars(true) .build(), @@ -2550,7 +2179,7 @@ pub(crate) fn define( r#" Bitwise and with immediate. - Same as `band`, but one operand is an immediate constant. + Same as `band`, but one operand is a zero extended 64 bit immediate constant. Polymorphic over all scalar integer types, but does not support vector types. @@ -2567,7 +2196,7 @@ pub(crate) fn define( r#" Bitwise or with immediate. - Same as `bor`, but one operand is an immediate constant. + Same as `bor`, but one operand is a zero extended 64 bit immediate constant. Polymorphic over all scalar integer types, but does not support vector types. @@ -2584,7 +2213,7 @@ pub(crate) fn define( r#" Bitwise xor with immediate. - Same as `bxor`, but one operand is an immediate constant. + Same as `bxor`, but one operand is a zero extended 64 bit immediate constant. Polymorphic over all scalar integer types, but does not support vector types. @@ -2633,6 +2262,8 @@ pub(crate) fn define( "rotl_imm", r#" Rotate left by immediate. + + Same as `rotl`, but one operand is a zero extended 64 bit immediate constant. "#, &formats.binary_imm64, ) @@ -2645,6 +2276,8 @@ pub(crate) fn define( "rotr_imm", r#" Rotate right by immediate. + + Same as `rotr`, but one operand is a zero extended 64 bit immediate constant. "#, &formats.binary_imm64, ) @@ -2820,6 +2453,23 @@ pub(crate) fn define( .operands_out(vec![a]), ); + let x = &Operand::new("x", iSwappable); + let a = &Operand::new("a", iSwappable); + + ig.push( + Inst::new( + "bswap", + r#" + Reverse the byte order of an integer. + + Reverses the bytes in ``x``. + "#, + &formats.unary, + ) + .operands_in(vec![x]) + .operands_out(vec![a]), + ); + let x = &Operand::new("x", Int); let a = &Operand::new("a", Int); @@ -2919,7 +2569,7 @@ pub(crate) fn define( floating point comparisons of the same name. When this instruction compares floating point vectors, it returns a - boolean vector with the results of lane-wise comparisons. + vector with the results of lane-wise comparisons. "#, &formats.float_compare, ) @@ -2927,23 +2577,6 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let f = &Operand::new("f", fflags); - - ig.push( - Inst::new( - "ffcmp", - r#" - Floating point comparison returning flags. - - Compares two numbers like `fcmp`, but returns floating point CPU - flags instead of testing a specific condition. - "#, - &formats.binary, - ) - .operands_in(vec![x, y]) - .operands_out(vec![f]), - ); - let x = &Operand::new("x", Float); let y = &Operand::new("y", Float); let z = &Operand::new("z", Float); @@ -3198,7 +2831,7 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let a = &Operand::new("a", b1); + let a = &Operand::new("a", i8); let x = &Operand::new("x", Ref); ig.push( @@ -3216,7 +2849,7 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let a = &Operand::new("a", b1); + let a = &Operand::new("a", i8); let x = &Operand::new("x", Ref); ig.push( @@ -3234,45 +2867,9 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let Cond = &Operand::new("Cond", &imm.intcc); - let f = &Operand::new("f", iflags); - let a = &Operand::new("a", b1); - - ig.push( - Inst::new( - "trueif", - r#" - Test integer CPU flags for a specific condition. - - Check the CPU flags in ``f`` against the ``Cond`` condition code and - return true when the condition code is satisfied. - "#, - &formats.int_cond, - ) - .operands_in(vec![Cond, f]) - .operands_out(vec![a]), - ); - - let Cond = &Operand::new("Cond", &imm.floatcc); - let f = &Operand::new("f", fflags); - - ig.push( - Inst::new( - "trueff", - r#" - Test floating point CPU flags for a specific condition. - - Check the CPU flags in ``f`` against the ``Cond`` condition code and - return true when the condition code is satisfied. - "#, - &formats.float_cond, - ) - .operands_in(vec![Cond, f]) - .operands_out(vec![a]), - ); - let x = &Operand::new("x", Mem); let a = &Operand::new("a", MemTo).with_doc("Bits of `x` reinterpreted"); + let MemFlags = &Operand::new("MemFlags", &imm.memflags); ig.push( Inst::new( @@ -3282,34 +2879,16 @@ pub(crate) fn define( The input and output types must be storable to memory and of the same size. A bitcast is equivalent to storing one type and loading the other - type from the same address. - "#, - &formats.unary, - ) - .operands_in(vec![x]) - .operands_out(vec![a]), - ); - - let x = &Operand::new("x", Any); - let a = &Operand::new("a", AnyTo).with_doc("Bits of `x` reinterpreted"); - - ig.push( - Inst::new( - "raw_bitcast", - r#" - Cast the bits in `x` as a different type of the same bit width. + type from the same address, both using the specified MemFlags. - This instruction does not change the data's representation but allows - data in registers to be used as different types, e.g. an i32x4 as a - b8x16. The only constraint on the result `a` is that it can be - `raw_bitcast` back to the original type. Also, in a raw_bitcast between - vector types with the same number of lanes, the value of each result - lane is a raw_bitcast of the corresponding operand lane. TODO there is - currently no mechanism for enforcing the bit width constraint. + Note that this operation only supports the `big` or `little` MemFlags. + The specified byte order only affects the result in the case where + input and output types differ in lane count/size. In this case, the + operation is only valid if a byte order specifier is provided. "#, - &formats.unary, + &formats.load_no_offset, ) - .operands_in(vec![x]) + .operands_in(vec![MemFlags, x]) .operands_out(vec![a]), ); @@ -3329,80 +2908,11 @@ pub(crate) fn define( .operands_out(vec![a]), ); - let Bool = &TypeVar::new( - "Bool", - "A scalar boolean type", - TypeSetBuilder::new().bools(Interval::All).build(), - ); - - let BoolTo = &TypeVar::new( - "BoolTo", - "A smaller boolean type", - TypeSetBuilder::new().bools(Interval::All).build(), - ); - - let x = &Operand::new("x", Bool); - let a = &Operand::new("a", BoolTo); - - ig.push( - Inst::new( - "breduce", - r#" - Convert `x` to a smaller boolean type by discarding the most significant bits. - "#, - &formats.unary, - ) - .operands_in(vec![x]) - .operands_out(vec![a]), - ); - - let BoolTo = &TypeVar::new( - "BoolTo", - "A larger boolean type", - TypeSetBuilder::new().bools(Interval::All).build(), - ); - let x = &Operand::new("x", Bool); - let a = &Operand::new("a", BoolTo); - - ig.push( - Inst::new( - "bextend", - r#" - Convert `x` to a larger boolean type - "#, - &formats.unary, - ) - .operands_in(vec![x]) - .operands_out(vec![a]), - ); - - let IntTo = &TypeVar::new( - "IntTo", - "A scalar integer type", - TypeSetBuilder::new().ints(Interval::All).build(), - ); - let x = &Operand::new("x", ScalarBool); - let a = &Operand::new("a", IntTo); - - ig.push( - Inst::new( - "bint", - r#" - Convert `x` to an integer. - - True maps to 1 and false maps to 0. - "#, - &formats.unary, - ) - .operands_in(vec![x]) - .operands_out(vec![a]), - ); - - let Bool = &TypeVar::new( - "Bool", - "A scalar or vector boolean type", + let Truthy = &TypeVar::new( + "Truthy", + "A scalar or vector whose values are truthy", TypeSetBuilder::new() - .bools(Interval::All) + .ints(Interval::All) .simd_lanes(Interval::All) .build(), ); @@ -3414,7 +2924,7 @@ pub(crate) fn define( .simd_lanes(Interval::All) .build(), ); - let x = &Operand::new("x", Bool); + let x = &Operand::new("x", Truthy); let a = &Operand::new("a", IntTo); ig.push( @@ -3733,8 +3243,7 @@ pub(crate) fn define( - `f32` and `f64`. This may change in the future. The result type must have the same number of vector lanes as the input, - and the result lanes must not have fewer bits than the input lanes. If - the input and output types are the same, this is a no-op. + and the result lanes must not have fewer bits than the input lanes. "#, &formats.unary, ) @@ -3755,8 +3264,7 @@ pub(crate) fn define( - `f32` and `f64`. This may change in the future. The result type must have the same number of vector lanes as the input, - and the result lanes must not have more bits than the input lanes. If - the input and output types are the same, this is a no-op. + and the result lanes must not have more bits than the input lanes. "#, &formats.unary, ) @@ -3827,59 +3335,67 @@ pub(crate) fn define( .operands_out(vec![x]), ); - let x = &Operand::new("x", Float); + let FloatScalar = &TypeVar::new( + "FloatScalar", + "A scalar only floating point number", + TypeSetBuilder::new().floats(Interval::All).build(), + ); + let x = &Operand::new("x", FloatScalar); let a = &Operand::new("a", IntTo); ig.push( Inst::new( "fcvt_to_uint", r#" - Convert floating point to unsigned integer. + Converts floating point scalars to unsigned integer. - Each lane in `x` is converted to an unsigned integer by rounding - towards zero. If `x` is NaN or if the unsigned integral value cannot be - represented in the result type, this instruction traps. + Only operates on `x` if it is a scalar. If `x` is NaN or if + the unsigned integral value cannot be represented in the result + type, this instruction traps. - The result type must have the same number of vector lanes as the input. "#, &formats.unary, ) .operands_in(vec![x]) .operands_out(vec![a]) - .can_trap(true), + .can_trap() + .side_effects_idempotent(), ); ig.push( Inst::new( - "fcvt_to_uint_sat", + "fcvt_to_sint", r#" - Convert floating point to unsigned integer as fcvt_to_uint does, but - saturates the input instead of trapping. NaN and negative values are - converted to 0. + Converts floating point scalars to signed integer. + + Only operates on `x` if it is a scalar. If `x` is NaN or if + the unsigned integral value cannot be represented in the result + type, this instruction traps. + "#, &formats.unary, ) .operands_in(vec![x]) - .operands_out(vec![a]), + .operands_out(vec![a]) + .can_trap() + .side_effects_idempotent(), ); + let x = &Operand::new("x", Float); + let a = &Operand::new("a", IntTo); + ig.push( Inst::new( - "fcvt_to_sint", + "fcvt_to_uint_sat", r#" - Convert floating point to signed integer. - - Each lane in `x` is converted to a signed integer by rounding towards - zero. If `x` is NaN or if the signed integral value cannot be - represented in the result type, this instruction traps. - - The result type must have the same number of vector lanes as the input. + Convert floating point to unsigned integer as fcvt_to_uint does, but + saturates the input instead of trapping. NaN and negative values are + converted to 0. "#, &formats.unary, ) .operands_in(vec![x]) - .operands_out(vec![a]) - .can_trap(true), + .operands_out(vec![a]), ); ig.push( @@ -3990,15 +3506,6 @@ pub(crate) fn define( .operands_out(vec![lo, hi]), ); - let NarrowInt = &TypeVar::new( - "NarrowInt", - "An integer type with lanes type to `i64`", - TypeSetBuilder::new() - .ints(8..64) - .simd_lanes(Interval::All) - .build(), - ); - let lo = &Operand::new("lo", NarrowInt); let hi = &Operand::new("hi", NarrowInt); let a = &Operand::new("a", &NarrowInt.double_width()) @@ -4047,9 +3554,9 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, AtomicRmwOp, p, x]) .operands_out(vec![a]) - .can_load(true) - .can_store(true) - .other_side_effects(true), + .can_load() + .can_store() + .other_side_effects(), ); ig.push( @@ -4069,9 +3576,9 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p, e, x]) .operands_out(vec![a]) - .can_load(true) - .can_store(true) - .other_side_effects(true), + .can_load() + .can_store() + .other_side_effects(), ); ig.push( @@ -4089,8 +3596,8 @@ pub(crate) fn define( ) .operands_in(vec![MemFlags, p]) .operands_out(vec![a]) - .can_load(true) - .other_side_effects(true), + .can_load() + .other_side_effects(), ); ig.push( @@ -4107,8 +3614,8 @@ pub(crate) fn define( &formats.store_no_offset, ) .operands_in(vec![MemFlags, x, p]) - .can_store(true) - .other_side_effects(true), + .can_store() + .other_side_effects(), ); ig.push( @@ -4121,7 +3628,7 @@ pub(crate) fn define( "#, &formats.nullary, ) - .other_side_effects(true), + .other_side_effects(), ); let TxN = &TypeVar::new( @@ -4130,7 +3637,6 @@ pub(crate) fn define( TypeSetBuilder::new() .ints(Interval::All) .floats(Interval::All) - .bools(Interval::All) .dynamic_simd_lanes(Interval::All) .build(), ); diff --git a/cranelift/codegen/meta/src/shared/settings.rs b/cranelift/codegen/meta/src/shared/settings.rs index cf4473a1cd33..2db1f1303439 100644 --- a/cranelift/codegen/meta/src/shared/settings.rs +++ b/cranelift/codegen/meta/src/shared/settings.rs @@ -53,6 +53,19 @@ pub(crate) fn define() -> SettingGroup { true, ); + settings.add_bool( + "use_egraphs", + "Enable egraph-based optimization.", + r#" + This enables an optimization phase that converts CLIF to an egraph (equivalence graph) + representation, performs various rewrites, and then converts it back. This should result in + better optimization, but the traditional optimization pass structure is also still + available by setting this to `false`. The `false` setting will eventually be + deprecated and removed. + "#, + true, + ); + settings.add_bool( "enable_verifier", "Run the Cranelift IR verifier at strategic times during compilation.", @@ -128,21 +141,6 @@ pub(crate) fn define() -> SettingGroup { false, ); - settings.add_bool( - "use_pinned_reg_as_heap_base", - "Use the pinned register as the heap base.", - r#" - Enabling this requires the enable_pinned_reg setting to be set to true. It enables a custom - legalization of the `heap_addr` instruction so it will use the pinned register as the heap - base, instead of fetching it from a global value. - - Warning! Enabling this means that the pinned register *must* be maintained to contain the - heap base address at all times, during the lifetime of a function. Using the pinned - register for other purposes when this is set is very likely to cause crashes. - "#, - false, - ); - settings.add_bool( "enable_simd", "Enable the use of SIMD instructions.", @@ -262,7 +260,7 @@ pub(crate) fn define() -> SettingGroup { "enable_probestack", "Enable the use of stack probes for supported calling conventions.", "", - true, + false, ); settings.add_bool( @@ -284,6 +282,18 @@ pub(crate) fn define() -> SettingGroup { 12, ); + settings.add_enum( + "probestack_strategy", + "Controls what kinds of stack probes are emitted.", + r#" + Supported strategies: + + - `outline`: Always emits stack probes as calls to a probe stack function. + - `inline`: Always emits inline stack probes. + "#, + vec!["outline", "inline"], + ); + // Jump table options. settings.add_bool( @@ -327,5 +337,21 @@ pub(crate) fn define() -> SettingGroup { true, ); + settings.add_bool( + "enable_incremental_compilation_cache_checks", + "Enable additional checks for debugging the incremental compilation cache.", + r#" + Enables additional checks that are useful during development of the incremental + compilation cache. This should be mostly useful for Cranelift hackers, as well as for + helping to debug false incremental cache positives for embedders. + + This option is disabled by default and requires enabling the "incremental-cache" Cargo + feature in cranelift-codegen. + "#, + false, + ); + + // When adding new settings please check if they can also be added + // in cranelift/fuzzgen/src/lib.rs for fuzzing. settings.build() } diff --git a/cranelift/codegen/meta/src/shared/types.rs b/cranelift/codegen/meta/src/shared/types.rs index 631e5433e953..33efd108014b 100644 --- a/cranelift/codegen/meta/src/shared/types.rs +++ b/cranelift/codegen/meta/src/shared/types.rs @@ -1,49 +1,5 @@ //! This module predefines all the Cranelift scalar types. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub(crate) enum Bool { - /// 1-bit bool. - B1 = 1, - /// 8-bit bool. - B8 = 8, - /// 16-bit bool. - B16 = 16, - /// 32-bit bool. - B32 = 32, - /// 64-bit bool. - B64 = 64, - /// 128-bit bool. - B128 = 128, -} - -/// This provides an iterator through all of the supported bool variants. -pub(crate) struct BoolIterator { - index: u8, -} - -impl BoolIterator { - pub fn new() -> Self { - Self { index: 0 } - } -} - -impl Iterator for BoolIterator { - type Item = Bool; - fn next(&mut self) -> Option { - let res = match self.index { - 0 => Some(Bool::B1), - 1 => Some(Bool::B8), - 2 => Some(Bool::B16), - 3 => Some(Bool::B32), - 4 => Some(Bool::B64), - 5 => Some(Bool::B128), - _ => return None, - }; - self.index += 1; - res - } -} - #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub(crate) enum Int { /// 8-bit int. @@ -116,41 +72,6 @@ impl Iterator for FloatIterator { } } -/// A type representing CPU flags. -/// -/// Flags can't be stored in memory. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] -pub(crate) enum Flag { - /// CPU flags from an integer comparison. - IFlags, - /// CPU flags from a floating point comparison. - FFlags, -} - -/// Iterator through the variants of the Flag enum. -pub(crate) struct FlagIterator { - index: u8, -} - -impl FlagIterator { - pub fn new() -> Self { - Self { index: 0 } - } -} - -impl Iterator for FlagIterator { - type Item = Flag; - fn next(&mut self) -> Option { - let res = match self.index { - 0 => Some(Flag::IFlags), - 1 => Some(Flag::FFlags), - _ => return None, - }; - self.index += 1; - res - } -} - #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub(crate) enum Reference { /// 32-bit reference. @@ -187,18 +108,6 @@ impl Iterator for ReferenceIterator { mod iter_tests { use super::*; - #[test] - fn bool_iter_works() { - let mut bool_iter = BoolIterator::new(); - assert_eq!(bool_iter.next(), Some(Bool::B1)); - assert_eq!(bool_iter.next(), Some(Bool::B8)); - assert_eq!(bool_iter.next(), Some(Bool::B16)); - assert_eq!(bool_iter.next(), Some(Bool::B32)); - assert_eq!(bool_iter.next(), Some(Bool::B64)); - assert_eq!(bool_iter.next(), Some(Bool::B128)); - assert_eq!(bool_iter.next(), None); - } - #[test] fn int_iter_works() { let mut int_iter = IntIterator::new(); @@ -218,14 +127,6 @@ mod iter_tests { assert_eq!(float_iter.next(), None); } - #[test] - fn flag_iter_works() { - let mut flag_iter = FlagIterator::new(); - assert_eq!(flag_iter.next(), Some(Flag::IFlags)); - assert_eq!(flag_iter.next(), Some(Flag::FFlags)); - assert_eq!(flag_iter.next(), None); - } - #[test] fn reference_iter_works() { let mut reference_iter = ReferenceIterator::new(); diff --git a/cranelift/codegen/shared/Cargo.toml b/cranelift/codegen/shared/Cargo.toml index 936799cabeb9..3faadbb46f29 100644 --- a/cranelift/codegen/shared/Cargo.toml +++ b/cranelift/codegen/shared/Cargo.toml @@ -1,12 +1,12 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift-codegen-shared" -version = "0.88.0" +version = "0.94.0" description = "For code shared between cranelift-codegen-meta and cranelift-codegen" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" readme = "README.md" -edition = "2021" +edition.workspace = true [dependencies] # Since this is a shared dependency of several packages, please strive to keep this dependency-free diff --git a/cranelift/codegen/src/alias_analysis.rs b/cranelift/codegen/src/alias_analysis.rs index 53d3ba60cfc6..f3b6339f9938 100644 --- a/cranelift/codegen/src/alias_analysis.rs +++ b/cranelift/codegen/src/alias_analysis.rs @@ -76,7 +76,7 @@ use cranelift_entity::{packed_option::PackedOption, EntityRef}; /// For a given program point, the vector of last-store instruction /// indices for each disjoint category of abstract state. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -struct LastStores { +pub struct LastStores { heap: PackedOption, table: PackedOption, vmctx: PackedOption, @@ -85,14 +85,14 @@ struct LastStores { impl LastStores { fn update(&mut self, func: &Function, inst: Inst) { - let opcode = func.dfg[inst].opcode(); + let opcode = func.dfg.insts[inst].opcode(); if has_memory_fence_semantics(opcode) { self.heap = inst.into(); self.table = inst.into(); self.vmctx = inst.into(); self.other = inst.into(); } else if opcode.can_store() { - if let Some(memflags) = func.dfg[inst].memflags() { + if let Some(memflags) = func.dfg.insts[inst].memflags() { if memflags.heap() { self.heap = inst.into(); } else if memflags.table() { @@ -112,7 +112,7 @@ impl LastStores { } fn get_last_store(&self, func: &Function, inst: Inst) -> PackedOption { - if let Some(memflags) = func.dfg[inst].memflags() { + if let Some(memflags) = func.dfg.insts[inst].memflags() { if memflags.heap() { self.heap } else if memflags.table() { @@ -122,7 +122,9 @@ impl LastStores { } else { self.other } - } else if func.dfg[inst].opcode().can_load() || func.dfg[inst].opcode().can_store() { + } else if func.dfg.insts[inst].opcode().can_load() + || func.dfg.insts[inst].opcode().can_store() + { inst.into() } else { PackedOption::default() @@ -179,9 +181,6 @@ struct MemoryLoc { /// An alias-analysis pass. pub struct AliasAnalysis<'a> { - /// The function we're analyzing. - func: &'a mut Function, - /// The domtree for the function. domtree: &'a DominatorTree, @@ -198,23 +197,22 @@ pub struct AliasAnalysis<'a> { impl<'a> AliasAnalysis<'a> { /// Perform an alias analysis pass. - pub fn new(func: &'a mut Function, domtree: &'a DominatorTree) -> AliasAnalysis<'a> { + pub fn new(func: &Function, domtree: &'a DominatorTree) -> AliasAnalysis<'a> { trace!("alias analysis: input is:\n{:?}", func); let mut analysis = AliasAnalysis { - func, domtree, block_input: FxHashMap::default(), mem_values: FxHashMap::default(), }; - analysis.compute_block_input_states(); + analysis.compute_block_input_states(func); analysis } - fn compute_block_input_states(&mut self) { + fn compute_block_input_states(&mut self, func: &Function) { let mut queue = vec![]; let mut queue_set = FxHashSet::default(); - let entry = self.func.layout.entry_block().unwrap(); + let entry = func.layout.entry_block().unwrap(); queue.push(entry); queue_set.insert(entry); @@ -232,19 +230,13 @@ impl<'a> AliasAnalysis<'a> { state ); - for inst in self.func.layout.block_insts(block) { - state.update(self.func, inst); + for inst in func.layout.block_insts(block) { + state.update(func, inst); trace!("after inst{}: state is {:?}", inst.index(), state); } - visit_block_succs(self.func, block, |_inst, succ| { - let succ_first_inst = self - .func - .layout - .block_insts(succ) - .into_iter() - .next() - .unwrap(); + visit_block_succs(func, block, |_inst, succ, _from_table| { + let succ_first_inst = func.layout.block_insts(succ).into_iter().next().unwrap(); let updated = match self.block_input.get_mut(&succ) { Some(succ_state) => { let old = succ_state.clone(); @@ -264,117 +256,145 @@ impl<'a> AliasAnalysis<'a> { } } - /// Make a pass and update known-redundant loads to aliased - /// values. We interleave the updates with the memory-location - /// tracking because resolving some aliases may expose others - /// (e.g. in cases of double-indirection with two separate chains - /// of loads). - pub fn compute_and_update_aliases(&mut self) { - let mut pos = FuncCursor::new(self.func); + /// Get the starting state for a block. + pub fn block_starting_state(&self, block: Block) -> LastStores { + self.block_input + .get(&block) + .cloned() + .unwrap_or_else(|| LastStores::default()) + } - while let Some(block) = pos.next_block() { - let mut state = self - .block_input - .get(&block) - .cloned() - .unwrap_or_else(|| LastStores::default()); + /// Process one instruction. Meant to be invoked in program order + /// within a block, and ideally in RPO or at least some domtree + /// preorder for maximal reuse. + /// + /// Returns `true` if instruction was removed. + pub fn process_inst( + &mut self, + func: &mut Function, + state: &mut LastStores, + inst: Inst, + ) -> Option { + trace!( + "alias analysis: scanning at inst{} with state {:?} ({:?})", + inst.index(), + state, + func.dfg.insts[inst], + ); - while let Some(inst) = pos.next_inst() { + let replacing_value = if let Some((address, offset, ty)) = inst_addr_offset_type(func, inst) + { + let address = func.dfg.resolve_aliases(address); + let opcode = func.dfg.insts[inst].opcode(); + + if opcode.can_store() { + let store_data = inst_store_data(func, inst).unwrap(); + let store_data = func.dfg.resolve_aliases(store_data); + let mem_loc = MemoryLoc { + last_store: inst.into(), + address, + offset, + ty, + extending_opcode: get_ext_opcode(opcode), + }; trace!( - "alias analysis: scanning at inst{} with state {:?} ({:?})", + "alias analysis: at inst{}: store with data v{} at loc {:?}", inst.index(), - state, - pos.func.dfg[inst], + store_data.index(), + mem_loc ); + self.mem_values.insert(mem_loc, (inst, store_data)); - if let Some((address, offset, ty)) = inst_addr_offset_type(pos.func, inst) { - let address = pos.func.dfg.resolve_aliases(address); - let opcode = pos.func.dfg[inst].opcode(); + None + } else if opcode.can_load() { + let last_store = state.get_last_store(func, inst); + let load_result = func.dfg.inst_results(inst)[0]; + let mem_loc = MemoryLoc { + last_store, + address, + offset, + ty, + extending_opcode: get_ext_opcode(opcode), + }; + trace!( + "alias analysis: at inst{}: load with last_store inst{} at loc {:?}", + inst.index(), + last_store.map(|inst| inst.index()).unwrap_or(usize::MAX), + mem_loc + ); - if opcode.can_store() { - let store_data = inst_store_data(pos.func, inst).unwrap(); - let store_data = pos.func.dfg.resolve_aliases(store_data); - let mem_loc = MemoryLoc { - last_store: inst.into(), - address, - offset, - ty, - extending_opcode: get_ext_opcode(opcode), - }; + // Is there a Value already known to be stored + // at this specific memory location? If so, + // we can alias the load result to this + // already-known Value. + // + // Check if the definition dominates this + // location; it might not, if it comes from a + // load (stores will always dominate though if + // their `last_store` survives through + // meet-points to this use-site). + let aliased = + if let Some((def_inst, value)) = self.mem_values.get(&mem_loc).cloned() { trace!( - "alias analysis: at inst{}: store with data v{} at loc {:?}", - inst.index(), - store_data.index(), - mem_loc + " -> sees known value v{} from inst{}", + value.index(), + def_inst.index() ); - self.mem_values.insert(mem_loc, (inst, store_data)); - } else if opcode.can_load() { - let last_store = state.get_last_store(pos.func, inst); - let load_result = pos.func.dfg.inst_results(inst)[0]; - let mem_loc = MemoryLoc { - last_store, - address, - offset, - ty, - extending_opcode: get_ext_opcode(opcode), - }; - trace!( - "alias analysis: at inst{}: load with last_store inst{} at loc {:?}", - inst.index(), - last_store.map(|inst| inst.index()).unwrap_or(usize::MAX), - mem_loc - ); - - // Is there a Value already known to be stored - // at this specific memory location? If so, - // we can alias the load result to this - // already-known Value. - // - // Check if the definition dominates this - // location; it might not, if it comes from a - // load (stores will always dominate though if - // their `last_store` survives through - // meet-points to this use-site). - let aliased = if let Some((def_inst, value)) = - self.mem_values.get(&mem_loc).cloned() - { - trace!( - " -> sees known value v{} from inst{}", - value.index(), - def_inst.index() - ); - if self.domtree.dominates(def_inst, inst, &pos.func.layout) { - trace!( - " -> dominates; value equiv from v{} to v{} inserted", - load_result.index(), - value.index() - ); - - pos.func.dfg.detach_results(inst); - pos.func.dfg.change_to_alias(load_result, value); - pos.remove_inst_and_step_back(); - true - } else { - false - } - } else { - false - }; - - // Otherwise, we can keep *this* load around - // as a new equivalent value. - if !aliased { + if self.domtree.dominates(def_inst, inst, &func.layout) { trace!( - " -> inserting load result v{} at loc {:?}", + " -> dominates; value equiv from v{} to v{} inserted", load_result.index(), - mem_loc + value.index() ); - self.mem_values.insert(mem_loc, (inst, load_result)); + Some(value) + } else { + None } - } + } else { + None + }; + + // Otherwise, we can keep *this* load around + // as a new equivalent value. + if aliased.is_none() { + trace!( + " -> inserting load result v{} at loc {:?}", + load_result.index(), + mem_loc + ); + self.mem_values.insert(mem_loc, (inst, load_result)); } - state.update(pos.func, inst); + aliased + } else { + None + } + } else { + None + }; + + state.update(func, inst); + + replacing_value + } + + /// Make a pass and update known-redundant loads to aliased + /// values. We interleave the updates with the memory-location + /// tracking because resolving some aliases may expose others + /// (e.g. in cases of double-indirection with two separate chains + /// of loads). + pub fn compute_and_update_aliases(&mut self, func: &mut Function) { + let mut pos = FuncCursor::new(func); + + while let Some(block) = pos.next_block() { + let mut state = self.block_starting_state(block); + while let Some(inst) = pos.next_inst() { + if let Some(replaced_result) = self.process_inst(pos.func, &mut state, inst) { + let result = pos.func.dfg.inst_results(inst)[0]; + pos.func.dfg.detach_results(inst); + pos.func.dfg.change_to_alias(result, replaced_result); + pos.remove_inst_and_step_back(); + } } } } diff --git a/cranelift/codegen/src/binemit/mod.rs b/cranelift/codegen/src/binemit/mod.rs index 750eaa2a215f..33bc8f641472 100644 --- a/cranelift/codegen/src/binemit/mod.rs +++ b/cranelift/codegen/src/binemit/mod.rs @@ -35,6 +35,10 @@ pub enum Reloc { X86CallPLTRel4, /// x86 GOT PC-relative 4-byte X86GOTPCRel4, + /// The 32-bit offset of the target from the beginning of its section. + /// Equivalent to `IMAGE_REL_AMD64_SECREL`. + /// See: [PE Format](https://docs.microsoft.com/en-us/windows/win32/debug/pe-format) + X86SecRel, /// Arm32 call target Arm32Call, /// Arm64 call target. Encoded as bottom 26 bits of instruction. This @@ -43,6 +47,8 @@ pub enum Reloc { Arm64Call, /// s390x PC-relative 4-byte offset S390xPCRel32Dbl, + /// s390x PC-relative 4-byte offset to PLT + S390xPLTRel32Dbl, /// Elf x86_64 32 bit signed PC relative offset to two GOT entries for GD symbol. ElfX86_64TlsGd, @@ -59,6 +65,29 @@ pub enum Reloc { /// Set the add immediate field to the low 12 bits of the final address. Does not check for overflow. /// This is equivalent to `R_AARCH64_TLSGD_ADD_LO12_NC` in the [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#relocations-for-thread-local-storage) Aarch64TlsGdAddLo12Nc, + + /// AArch64 GOT Page + /// Set the immediate value of an ADRP to bits 32:12 of X; check that –232 <= X < 232 + /// This is equivalent to `R_AARCH64_ADR_GOT_PAGE` (311) in the [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#static-aarch64-relocations) + Aarch64AdrGotPage21, + + /// AArch64 GOT Low bits + + /// Set the LD/ST immediate field to bits 11:3 of X. No overflow check; check that X&7 = 0 + /// This is equivalent to `R_AARCH64_LD64_GOT_LO12_NC` (312) in the [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#static-aarch64-relocations) + Aarch64Ld64GotLo12Nc, + + /// procedure call. + /// call symbol + /// expands to the following assembly and relocation: + /// auipc ra, 0 + /// jalr ra, ra, 0 + RiscvCall, + + /// s390x TLS GD64 - 64-bit offset of tls_index for GD symbol in GOT + S390xTlsGd64, + /// s390x TLS GDCall - marker to enable optimization of TLS calls + S390xTlsGdCall, } impl fmt::Display for Reloc { @@ -69,16 +98,23 @@ impl fmt::Display for Reloc { Self::Abs4 => write!(f, "Abs4"), Self::Abs8 => write!(f, "Abs8"), Self::S390xPCRel32Dbl => write!(f, "PCRel32Dbl"), + Self::S390xPLTRel32Dbl => write!(f, "PLTRel32Dbl"), Self::X86PCRel4 => write!(f, "PCRel4"), Self::X86CallPCRel4 => write!(f, "CallPCRel4"), Self::X86CallPLTRel4 => write!(f, "CallPLTRel4"), Self::X86GOTPCRel4 => write!(f, "GOTPCRel4"), + Self::X86SecRel => write!(f, "SecRel"), Self::Arm32Call | Self::Arm64Call => write!(f, "Call"), + Self::RiscvCall => write!(f, "RiscvCall"), Self::ElfX86_64TlsGd => write!(f, "ElfX86_64TlsGd"), Self::MachOX86_64Tlv => write!(f, "MachOX86_64Tlv"), Self::Aarch64TlsGdAdrPage21 => write!(f, "Aarch64TlsGdAdrPage21"), Self::Aarch64TlsGdAddLo12Nc => write!(f, "Aarch64TlsGdAddLo12Nc"), + Self::Aarch64AdrGotPage21 => write!(f, "Aarch64AdrGotPage21"), + Self::Aarch64Ld64GotLo12Nc => write!(f, "Aarch64AdrGotLo12Nc"), + Self::S390xTlsGd64 => write!(f, "TlsGd64"), + Self::S390xTlsGdCall => write!(f, "TlsGdCall"), } } } @@ -88,7 +124,7 @@ impl fmt::Display for Reloc { /// The code starts at offset 0 and is followed optionally by relocatable jump tables and copyable /// (raw binary) read-only data. Any padding between sections is always part of the section that /// precedes the boundary between the sections. -#[derive(PartialEq)] +#[derive(Debug, PartialEq)] pub struct CodeInfo { /// Number of bytes in total. pub total_size: CodeOffset, diff --git a/cranelift/codegen/src/bitset.rs b/cranelift/codegen/src/bitset.rs index d271a866178e..c09a58777f7c 100644 --- a/cranelift/codegen/src/bitset.rs +++ b/cranelift/codegen/src/bitset.rs @@ -4,7 +4,7 @@ //! T is intended to be a primitive unsigned type. Currently it can be any type between u8 and u32 //! //! If you would like to add support for larger bitsets in the future, you need to change the trait -//! bound Into and the u32 in the implementation of `max_bits()`. +//! bound `Into` and the `u32` in the implementation of `max_bits()`. use core::convert::{From, Into}; use core::mem::size_of; diff --git a/cranelift/codegen/src/cfg_printer.rs b/cranelift/codegen/src/cfg_printer.rs index 843b66f2774e..7b906d19e78c 100644 --- a/cranelift/codegen/src/cfg_printer.rs +++ b/cranelift/codegen/src/cfg_printer.rs @@ -53,7 +53,7 @@ impl<'a> CFGPrinter<'a> { write!(w, " {} [shape=record, label=\"{{", block)?; crate::write::write_block_header(w, self.func, block, 4)?; // Add all outgoing branch instructions to the label. - for inst in self.func.layout.block_likely_branches(block) { + if let Some(inst) = self.func.layout.last_inst(block) { write!(w, " | <{}>", inst)?; PlainWriter.write_instruction(w, self.func, &aliases, inst, 0)?; } diff --git a/cranelift/codegen/src/context.rs b/cranelift/codegen/src/context.rs index fb7b8bb37d56..62154558d039 100644 --- a/cranelift/codegen/src/context.rs +++ b/cranelift/codegen/src/context.rs @@ -12,19 +12,21 @@ use crate::alias_analysis::AliasAnalysis; use crate::dce::do_dce; use crate::dominator_tree::DominatorTree; +use crate::egraph::EgraphPass; use crate::flowgraph::ControlFlowGraph; use crate::ir::Function; use crate::isa::TargetIsa; use crate::legalizer::simple_legalize; use crate::licm::do_licm; use crate::loop_analysis::LoopAnalysis; -use crate::machinst::CompiledCode; +use crate::machinst::{CompiledCode, CompiledCodeStencil}; use crate::nan_canonicalization::do_nan_canonicalization; use crate::remove_constant_phis::do_remove_constant_phis; use crate::result::{CodegenResult, CompileResult}; use crate::settings::{FlagsOrIsa, OptLevel}; use crate::simple_gvn::do_simple_gvn; use crate::simple_preopt::do_preopt; +use crate::trace; use crate::unreachable_code::eliminate_unreachable_code; use crate::verifier::{verify_context, VerifierErrors, VerifierResult}; use crate::{timing, CompileError}; @@ -50,7 +52,7 @@ pub struct Context { pub loop_analysis: LoopAnalysis, /// Result of MachBackend compilation, if computed. - compiled_code: Option, + pub(crate) compiled_code: Option, /// Flag: do we want a disassembly with the CompiledCode? pub want_disasm: bool, @@ -104,119 +106,141 @@ impl Context { /// Compile the function, and emit machine code into a `Vec`. /// - /// Run the function through all the passes necessary to generate code for the target ISA - /// represented by `isa`, as well as the final step of emitting machine code into a - /// `Vec`. The machine code is not relocated. Instead, any relocations can be obtained - /// from `compiled_code()`. + /// Run the function through all the passes necessary to generate + /// code for the target ISA represented by `isa`, as well as the + /// final step of emitting machine code into a `Vec`. The + /// machine code is not relocated. Instead, any relocations can be + /// obtained from `compiled_code()`. /// - /// This function calls `compile` and `emit_to_memory`, taking care to resize `mem` as - /// needed, so it provides a safe interface. + /// Performs any optimizations that are enabled, unless + /// `optimize()` was already invoked. /// - /// Returns information about the function's code and read-only data. + /// This function calls `compile`, taking care to resize `mem` as + /// needed. + /// + /// Returns information about the function's code and read-only + /// data. pub fn compile_and_emit( &mut self, isa: &dyn TargetIsa, mem: &mut Vec, ) -> CompileResult<&CompiledCode> { let compiled_code = self.compile(isa)?; - let code_info = compiled_code.code_info(); - let old_len = mem.len(); - mem.resize(old_len + code_info.total_size as usize, 0); - mem[old_len..].copy_from_slice(compiled_code.code_buffer()); + mem.extend_from_slice(compiled_code.code_buffer()); Ok(compiled_code) } - /// Compile the function. + /// Internally compiles the function into a stencil. /// - /// Run the function through all the passes necessary to generate code for the target ISA - /// represented by `isa`. This does not include the final step of emitting machine code into a - /// code sink. - /// - /// Returns information about the function's code and read-only data. - pub fn compile(&mut self, isa: &dyn TargetIsa) -> CompileResult<&CompiledCode> { + /// Public only for testing and fuzzing purposes. + pub fn compile_stencil(&mut self, isa: &dyn TargetIsa) -> CodegenResult { let _tt = timing::compile(); - let mut inner = || { - self.verify_if(isa)?; + self.verify_if(isa)?; - let opt_level = isa.flags().opt_level(); - log::trace!( - "Compiling (opt level {:?}):\n{}", - opt_level, - self.func.display() - ); + self.optimize(isa)?; - self.compute_cfg(); - if opt_level != OptLevel::None { - self.preopt(isa)?; - } - if isa.flags().enable_nan_canonicalization() { - self.canonicalize_nans(isa)?; - } + isa.compile_function(&self.func, self.want_disasm) + } - self.legalize(isa)?; - if opt_level != OptLevel::None { - self.compute_domtree(); - self.compute_loop_analysis(); - self.licm(isa)?; - self.simple_gvn(isa)?; - } + /// Optimize the function, performing all compilation steps up to + /// but not including machine-code lowering and register + /// allocation. + /// + /// Public only for testing purposes. + pub fn optimize(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> { + log::debug!( + "Number of CLIF instructions to optimize: {}", + self.func.dfg.num_insts() + ); + log::debug!( + "Number of CLIF blocks to optimize: {}", + self.func.dfg.num_blocks() + ); + + let opt_level = isa.flags().opt_level(); + crate::trace!( + "Optimizing (opt level {:?}):\n{}", + opt_level, + self.func.display() + ); + self.compute_cfg(); + if !isa.flags().use_egraphs() && opt_level != OptLevel::None { + self.preopt(isa)?; + } + if isa.flags().enable_nan_canonicalization() { + self.canonicalize_nans(isa)?; + } + + self.legalize(isa)?; + + if !isa.flags().use_egraphs() && opt_level != OptLevel::None { self.compute_domtree(); - self.eliminate_unreachable_code(isa)?; - if opt_level != OptLevel::None { - self.dce(isa)?; - } + self.compute_loop_analysis(); + self.licm(isa)?; + self.simple_gvn(isa)?; + } + + self.compute_domtree(); + self.eliminate_unreachable_code(isa)?; + + if opt_level != OptLevel::None { + self.dce(isa)?; + } - self.remove_constant_phis(isa)?; + self.remove_constant_phis(isa)?; - if opt_level != OptLevel::None && isa.flags().enable_alias_analysis() { - self.replace_redundant_loads()?; - self.simple_gvn(isa)?; + if opt_level != OptLevel::None { + if isa.flags().use_egraphs() { + self.egraph_pass()?; + } else if isa.flags().enable_alias_analysis() { + for _ in 0..2 { + self.replace_redundant_loads()?; + self.simple_gvn(isa)?; + } } + } - let result = isa.compile_function(&self.func, self.want_disasm)?; - self.compiled_code = Some(result); - Ok(()) - }; - - inner() - .map(|_| self.compiled_code.as_ref().unwrap()) - .map_err(|error| CompileError { - inner: error, - func: &self.func, - }) + Ok(()) + } + + /// Compile the function. + /// + /// Run the function through all the passes necessary to generate code for the target ISA + /// represented by `isa`. This does not include the final step of emitting machine code into a + /// code sink. + /// + /// Returns information about the function's code and read-only data. + pub fn compile(&mut self, isa: &dyn TargetIsa) -> CompileResult<&CompiledCode> { + let _tt = timing::compile(); + let stencil = self.compile_stencil(isa).map_err(|error| CompileError { + inner: error, + func: &self.func, + })?; + Ok(self + .compiled_code + .insert(stencil.apply_params(&self.func.params))) } /// If available, return information about the code layout in the /// final machine code: the offsets (in bytes) of each basic-block /// start, and all basic-block edges. + #[deprecated = "use CompiledCode::get_code_bb_layout"] pub fn get_code_bb_layout(&self) -> Option<(Vec, Vec<(usize, usize)>)> { - if let Some(result) = self.compiled_code.as_ref() { - Some(( - result.bb_starts.iter().map(|&off| off as usize).collect(), - result - .bb_edges - .iter() - .map(|&(from, to)| (from as usize, to as usize)) - .collect(), - )) - } else { - None - } + self.compiled_code().map(CompiledCode::get_code_bb_layout) } /// Creates unwind information for the function. /// /// Returns `None` if the function has no unwind information. #[cfg(feature = "unwind")] + #[deprecated = "use CompiledCode::create_unwind_info"] pub fn create_unwind_info( &self, isa: &dyn TargetIsa, ) -> CodegenResult> { - let unwind_info_kind = isa.unwind_info_kind(); - let result = self.compiled_code.as_ref().unwrap(); - isa.emit_unwind_info(result, unwind_info_kind) + self.compiled_code().unwrap().create_unwind_info(isa) } /// Run the verifier on the function. @@ -261,7 +285,7 @@ impl Context { /// Perform pre-legalization rewrites on the function. pub fn preopt(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> { - do_preopt(&mut self.func, &mut self.cfg, isa); + do_preopt(&mut self.func, isa); self.verify_if(isa)?; Ok(()) } @@ -338,8 +362,8 @@ impl Context { /// by a store instruction to the same instruction (so-called /// "store-to-load forwarding"). pub fn replace_redundant_loads(&mut self) -> CodegenResult<()> { - let mut analysis = AliasAnalysis::new(&mut self.func, &self.domtree); - analysis.compute_and_update_aliases(); + let mut analysis = AliasAnalysis::new(&self.func, &self.domtree); + analysis.compute_and_update_aliases(&mut self.func); Ok(()) } @@ -352,4 +376,24 @@ impl Context { do_souper_harvest(&self.func, out); Ok(()) } + + /// Run optimizations via the egraph infrastructure. + pub fn egraph_pass(&mut self) -> CodegenResult<()> { + trace!( + "About to optimize with egraph phase:\n{}", + self.func.display() + ); + self.compute_loop_analysis(); + let mut alias_analysis = AliasAnalysis::new(&self.func, &self.domtree); + let mut pass = EgraphPass::new( + &mut self.func, + &self.domtree, + &self.loop_analysis, + &mut alias_analysis, + ); + pass.run(); + log::info!("egraph stats: {:?}", pass.stats); + trace!("After egraph optimization:\n{}", self.func.display()); + Ok(()) + } } diff --git a/cranelift/codegen/src/ctxhash.rs b/cranelift/codegen/src/ctxhash.rs new file mode 100644 index 000000000000..e172d46c127a --- /dev/null +++ b/cranelift/codegen/src/ctxhash.rs @@ -0,0 +1,168 @@ +//! A hashmap with "external hashing": nodes are hashed or compared for +//! equality only with some external context provided on lookup/insert. +//! This allows very memory-efficient data structures where +//! node-internal data references some other storage (e.g., offsets into +//! an array or pool of shared data). + +use hashbrown::raw::RawTable; +use std::hash::{Hash, Hasher}; + +/// Trait that allows for equality comparison given some external +/// context. +/// +/// Note that this trait is implemented by the *context*, rather than +/// the item type, for somewhat complex lifetime reasons (lack of GATs +/// to allow `for<'ctx> Ctx<'ctx>`-like associated types in traits on +/// the value type). +pub trait CtxEq { + /// Determine whether `a` and `b` are equal, given the context in + /// `self` and the union-find data structure `uf`. + fn ctx_eq(&self, a: &V1, b: &V2) -> bool; +} + +/// Trait that allows for hashing given some external context. +pub trait CtxHash: CtxEq { + /// Compute the hash of `value`, given the context in `self` and + /// the union-find data structure `uf`. + fn ctx_hash(&self, state: &mut H, value: &Value); +} + +/// A null-comparator context type for underlying value types that +/// already have `Eq` and `Hash`. +#[derive(Default)] +pub struct NullCtx; + +impl CtxEq for NullCtx { + fn ctx_eq(&self, a: &V, b: &V) -> bool { + a.eq(b) + } +} +impl CtxHash for NullCtx { + fn ctx_hash(&self, state: &mut H, value: &V) { + value.hash(state); + } +} + +/// A bucket in the hash table. +/// +/// Some performance-related design notes: we cache the hashcode for +/// speed, as this often buys a few percent speed in +/// interning-table-heavy workloads. We only keep the low 32 bits of +/// the hashcode, for memory efficiency: in common use, `K` and `V` +/// are often 32 bits also, and a 12-byte bucket is measurably better +/// than a 16-byte bucket. +struct BucketData { + hash: u32, + k: K, + v: V, +} + +/// A HashMap that takes external context for all operations. +pub struct CtxHashMap { + raw: RawTable>, +} + +impl CtxHashMap { + /// Create an empty hashmap with pre-allocated space for the given + /// capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self { + raw: RawTable::with_capacity(capacity), + } + } +} + +fn compute_hash(ctx: &Ctx, k: &K) -> u32 +where + Ctx: CtxHash, +{ + let mut hasher = crate::fx::FxHasher::default(); + ctx.ctx_hash(&mut hasher, k); + hasher.finish() as u32 +} + +impl CtxHashMap { + /// Insert a new key-value pair, returning the old value associated + /// with this key (if any). + pub fn insert(&mut self, k: K, v: V, ctx: &Ctx) -> Option + where + Ctx: CtxEq + CtxHash, + { + let hash = compute_hash(ctx, &k); + match self.raw.find(hash as u64, |bucket| { + hash == bucket.hash && ctx.ctx_eq(&bucket.k, &k) + }) { + Some(bucket) => { + let data = unsafe { bucket.as_mut() }; + Some(std::mem::replace(&mut data.v, v)) + } + None => { + let data = BucketData { hash, k, v }; + self.raw + .insert_entry(hash as u64, data, |bucket| bucket.hash as u64); + None + } + } + } + + /// Look up a key, returning a borrow of the value if present. + pub fn get<'a, Q, Ctx>(&'a self, k: &Q, ctx: &Ctx) -> Option<&'a V> + where + Ctx: CtxEq + CtxHash + CtxHash, + { + let hash = compute_hash(ctx, k); + self.raw + .find(hash as u64, |bucket| { + hash == bucket.hash && ctx.ctx_eq(&bucket.k, k) + }) + .map(|bucket| { + let data = unsafe { bucket.as_ref() }; + &data.v + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::hash::Hash; + + #[derive(Clone, Copy, Debug)] + struct Key { + index: u32, + } + struct Ctx { + vals: &'static [&'static str], + } + impl CtxEq for Ctx { + fn ctx_eq(&self, a: &Key, b: &Key) -> bool { + self.vals[a.index as usize].eq(self.vals[b.index as usize]) + } + } + impl CtxHash for Ctx { + fn ctx_hash(&self, state: &mut H, value: &Key) { + self.vals[value.index as usize].hash(state); + } + } + + #[test] + fn test_basic() { + let ctx = Ctx { + vals: &["a", "b", "a"], + }; + + let k0 = Key { index: 0 }; + let k1 = Key { index: 1 }; + let k2 = Key { index: 2 }; + + assert!(ctx.ctx_eq(&k0, &k2)); + assert!(!ctx.ctx_eq(&k0, &k1)); + assert!(!ctx.ctx_eq(&k2, &k1)); + + let mut map: CtxHashMap = CtxHashMap::with_capacity(4); + assert_eq!(map.insert(k0, 42, &ctx), None); + assert_eq!(map.insert(k2, 84, &ctx), Some(42)); + assert_eq!(map.get(&k1, &ctx), None); + assert_eq!(*map.get(&k0, &ctx).unwrap(), 84); + } +} diff --git a/cranelift/codegen/src/cursor.rs b/cranelift/codegen/src/cursor.rs index 2dc8ce7a2bed..3de1b2166de7 100644 --- a/cranelift/codegen/src/cursor.rs +++ b/cranelift/codegen/src/cursor.rs @@ -589,7 +589,7 @@ impl<'f> FuncCursor<'f> { /// Use the source location of `inst` for future instructions. pub fn use_srcloc(&mut self, inst: ir::Inst) { - self.srcloc = self.func.srclocs[inst]; + self.srcloc = self.func.srcloc(inst); } /// Create an instruction builder that inserts an instruction at the current position. @@ -612,6 +612,7 @@ impl<'f> Cursor for FuncCursor<'f> { } fn set_srcloc(&mut self, srcloc: ir::SourceLoc) { + self.func.params.ensure_base_srcloc(srcloc); self.srcloc = srcloc; } @@ -640,9 +641,9 @@ impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut FuncCursor<'f> { if let CursorPosition::At(_) = self.position() { if let Some(curr) = self.current_inst() { if let Some(prev) = self.layout().prev_inst(curr) { - let prev_op = self.data_flow_graph()[prev].opcode(); - let inst_op = self.data_flow_graph()[inst].opcode(); - let curr_op = self.data_flow_graph()[curr].opcode(); + let prev_op = self.data_flow_graph().insts[prev].opcode(); + let inst_op = self.data_flow_graph().insts[inst].opcode(); + let curr_op = self.data_flow_graph().insts[curr].opcode(); if prev_op.is_branch() && !prev_op.is_terminator() && !inst_op.is_terminator() @@ -658,7 +659,7 @@ impl<'c, 'f> ir::InstInserterBase<'c> for &'c mut FuncCursor<'f> { } self.insert_inst(inst); if !self.srcloc.is_default() { - self.func.srclocs[inst] = self.srcloc; + self.func.set_srcloc(inst, self.srcloc); } &mut self.func.dfg } diff --git a/cranelift/codegen/src/data_value.rs b/cranelift/codegen/src/data_value.rs index 6abc29987b05..2da55fc9c7e6 100644 --- a/cranelift/codegen/src/data_value.rs +++ b/cranelift/codegen/src/data_value.rs @@ -10,9 +10,8 @@ use core::fmt::{self, Display, Formatter}; /// /// [Value]: crate::ir::Value #[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq, PartialOrd)] +#[derive(Clone, Debug, PartialOrd)] pub enum DataValue { - B(bool), I8(i8), I16(i16), I32(i32), @@ -29,6 +28,42 @@ pub enum DataValue { V64([u8; 8]), } +impl PartialEq for DataValue { + fn eq(&self, other: &Self) -> bool { + use DataValue::*; + match (self, other) { + (I8(l), I8(r)) => l == r, + (I8(_), _) => false, + (I16(l), I16(r)) => l == r, + (I16(_), _) => false, + (I32(l), I32(r)) => l == r, + (I32(_), _) => false, + (I64(l), I64(r)) => l == r, + (I64(_), _) => false, + (I128(l), I128(r)) => l == r, + (I128(_), _) => false, + (U8(l), U8(r)) => l == r, + (U8(_), _) => false, + (U16(l), U16(r)) => l == r, + (U16(_), _) => false, + (U32(l), U32(r)) => l == r, + (U32(_), _) => false, + (U64(l), U64(r)) => l == r, + (U64(_), _) => false, + (U128(l), U128(r)) => l == r, + (U128(_), _) => false, + (F32(l), F32(r)) => l.as_f32() == r.as_f32(), + (F32(_), _) => false, + (F64(l), F64(r)) => l.as_f64() == r.as_f64(), + (F64(_), _) => false, + (V128(l), V128(r)) => l == r, + (V128(_), _) => false, + (V64(l), V64(r)) => l == r, + (V64(_), _) => false, + } + } +} + impl DataValue { /// Try to cast an immediate integer (a wrapped `i64` on most Cranelift instructions) to the /// given Cranelift [Type]. @@ -46,7 +81,6 @@ impl DataValue { /// Return the Cranelift IR [Type] for this [DataValue]. pub fn ty(&self) -> Type { match self { - DataValue::B(_) => types::B8, // A default type. DataValue::I8(_) | DataValue::U8(_) => types::I8, DataValue::I16(_) | DataValue::U16(_) => types::I16, DataValue::I32(_) | DataValue::U32(_) => types::I32, @@ -67,14 +101,6 @@ impl DataValue { } } - /// Return true if the value is a bool (i.e. `DataValue::B`). - pub fn is_bool(&self) -> bool { - match self { - DataValue::B(_) => true, - _ => false, - } - } - /// Write a [DataValue] to a slice. /// /// # Panics: @@ -82,8 +108,6 @@ impl DataValue { /// Panics if the slice does not have enough space to accommodate the [DataValue] pub fn write_to_slice(&self, dst: &mut [u8]) { match self { - DataValue::B(true) => dst[..16].copy_from_slice(&[u8::MAX; 16][..]), - DataValue::B(false) => dst[..16].copy_from_slice(&[0; 16][..]), DataValue::I8(i) => dst[..1].copy_from_slice(&i.to_ne_bytes()[..]), DataValue::I16(i) => dst[..2].copy_from_slice(&i.to_ne_bytes()[..]), DataValue::I32(i) => dst[..4].copy_from_slice(&i.to_ne_bytes()[..]), @@ -91,8 +115,8 @@ impl DataValue { DataValue::I128(i) => dst[..16].copy_from_slice(&i.to_ne_bytes()[..]), DataValue::F32(f) => dst[..4].copy_from_slice(&f.bits().to_ne_bytes()[..]), DataValue::F64(f) => dst[..8].copy_from_slice(&f.bits().to_ne_bytes()[..]), - DataValue::V128(v) => dst[..16].copy_from_slice(&u128::from_le_bytes(*v).to_ne_bytes()), - DataValue::V64(v) => dst[..8].copy_from_slice(&u64::from_le_bytes(*v).to_ne_bytes()), + DataValue::V128(v) => dst[..16].copy_from_slice(&v[..]), + DataValue::V64(v) => dst[..8].copy_from_slice(&v[..]), _ => unimplemented!(), }; } @@ -115,20 +139,11 @@ impl DataValue { types::F64 => DataValue::F64(Ieee64::with_bits(u64::from_ne_bytes( src[..8].try_into().unwrap(), ))), - _ if ty.is_bool() => { - // Only `ty.bytes()` are guaranteed to be written - // so we can only test the first n bytes of `src` - - let size = ty.bytes() as usize; - DataValue::B(src[..size].iter().any(|&i| i != 0)) - } _ if ty.is_vector() => { if ty.bytes() == 16 { - DataValue::V128( - u128::from_ne_bytes(src[..16].try_into().unwrap()).to_le_bytes(), - ) + DataValue::V128(src[..16].try_into().unwrap()) } else if ty.bytes() == 8 { - DataValue::V64(u64::from_ne_bytes(src[..8].try_into().unwrap()).to_le_bytes()) + DataValue::V64(src[..8].try_into().unwrap()) } else { unimplemented!() } @@ -139,13 +154,7 @@ impl DataValue { /// Write a [DataValue] to a memory location. pub unsafe fn write_value_to(&self, p: *mut u128) { - // Since `DataValue` does not have type info for bools we always - // write out a full 16 byte slot. - let size = match self.ty() { - ty if ty.is_bool() => 16, - ty => ty.bytes() as usize, - }; - + let size = self.ty().bytes() as usize; self.write_to_slice(std::slice::from_raw_parts_mut(p as *mut u8, size)); } @@ -156,6 +165,25 @@ impl DataValue { ty, ) } + + /// Performs a bitwise comparison over the contents of [DataValue]. + /// + /// Returns true if all bits are equal. + /// + /// This behaviour is different from PartialEq for NaN floats. + pub fn bitwise_eq(&self, other: &DataValue) -> bool { + match (self, other) { + // We need to bit compare the floats to ensure that we produce the correct values + // on NaN's. The test suite expects to assert the precise bit pattern on NaN's or + // works around it in the tests themselves. + (DataValue::F32(a), DataValue::F32(b)) => a.bits() == b.bits(), + (DataValue::F64(a), DataValue::F64(b)) => a.bits() == b.bits(), + + // We don't need to worry about F32x4 / F64x2 Since we compare V128 which is already the + // raw bytes anyway + (a, b) => a == b, + } + } } /// Record failures to cast [DataValue]. @@ -215,7 +243,6 @@ macro_rules! build_conversion_impl { } }; } -build_conversion_impl!(bool, B, B8); build_conversion_impl!(i8, I8, I8); build_conversion_impl!(i16, I16, I16); build_conversion_impl!(i32, I32, I32); @@ -239,7 +266,6 @@ impl From for DataValue { impl Display for DataValue { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { - DataValue::B(dv) => write!(f, "{}", dv), DataValue::I8(dv) => write!(f, "{}", dv), DataValue::I16(dv) => write!(f, "{}", dv), DataValue::I32(dv) => write!(f, "{}", dv), @@ -299,16 +325,6 @@ mod test { #[test] fn type_conversions() { - assert_eq!(DataValue::B(true).ty(), types::B8); - assert_eq!( - TryInto::::try_into(DataValue::B(false)).unwrap(), - false - ); - assert_eq!( - TryInto::::try_into(DataValue::B(false)).unwrap_err(), - DataValueCastFailure::TryInto(types::B8, types::I32) - ); - assert_eq!(DataValue::V128([0; 16]).ty(), types::I8X16); assert_eq!( TryInto::<[u8; 16]>::try_into(DataValue::V128([0; 16])).unwrap(), diff --git a/cranelift/codegen/src/dce.rs b/cranelift/codegen/src/dce.rs index e3e855806da8..2b52f92bcf3a 100644 --- a/cranelift/codegen/src/dce.rs +++ b/cranelift/codegen/src/dce.rs @@ -11,7 +11,7 @@ use crate::ir::Function; use crate::timing; /// Perform DCE on `func`. -pub fn do_dce(func: &mut Function, domtree: &mut DominatorTree) { +pub fn do_dce(func: &mut Function, domtree: &DominatorTree) { let _tt = timing::dce(); debug_assert!(domtree.is_valid()); @@ -23,10 +23,11 @@ pub fn do_dce(func: &mut Function, domtree: &mut DominatorTree) { if has_side_effect(pos.func, inst) || any_inst_results_used(inst, &live, &pos.func.dfg) { - for arg in pos.func.dfg.inst_args(inst) { - let v = pos.func.dfg.resolve_aliases(*arg); + for arg in pos.func.dfg.inst_values(inst) { + let v = pos.func.dfg.resolve_aliases(arg); live[v.index()] = true; } + continue; } } diff --git a/cranelift/codegen/src/dominator_tree.rs b/cranelift/codegen/src/dominator_tree.rs index 5077354f7a6f..23ccb1783a7e 100644 --- a/cranelift/codegen/src/dominator_tree.rs +++ b/cranelift/codegen/src/dominator_tree.rs @@ -2,7 +2,7 @@ use crate::entity::SecondaryMap; use crate::flowgraph::{BlockPredecessor, ControlFlowGraph}; -use crate::ir::instructions::BranchInfo; +use crate::inst_predicates; use crate::ir::{Block, ExpandedProgramPoint, Function, Inst, Layout, ProgramOrder, Value}; use crate::packed_option::PackedOption; use crate::timing; @@ -351,20 +351,7 @@ impl DominatorTree { /// post-order. Split-invariant means that if a block is split in two, we get the same /// post-order except for the insertion of the new block header at the split point. fn push_successors(&mut self, func: &Function, block: Block) { - for inst in func.layout.block_likely_branches(block) { - match func.dfg.analyze_branch(inst) { - BranchInfo::SingleDest(succ, _) => self.push_if_unseen(succ), - BranchInfo::Table(jt, dest) => { - for succ in func.jump_tables[jt].iter() { - self.push_if_unseen(*succ); - } - if let Some(dest) = dest { - self.push_if_unseen(dest); - } - } - BranchInfo::NotABranch => {} - } - } + inst_predicates::visit_block_succs(func, block, |_, succ, _| self.push_if_unseen(succ)) } /// Push `block` onto `self.stack` if it has not already been seen. @@ -649,11 +636,14 @@ mod tests { let v0 = func.dfg.append_block_param(block0, I32); let block1 = func.dfg.make_block(); let block2 = func.dfg.make_block(); + let trap_block = func.dfg.make_block(); let mut cur = FuncCursor::new(&mut func); cur.insert_block(block0); - cur.ins().brnz(v0, block2, &[]); + cur.ins().brif(v0, block2, &[], trap_block, &[]); + + cur.insert_block(trap_block); cur.ins().trap(TrapCode::User(0)); cur.insert_block(block1); @@ -670,13 +660,13 @@ mod tests { // Fall-through-first, prune-at-source DFT: // // block0 { - // brnz block2 { + // brif block2 { // trap // block2 { // return // } block2 // } block0 - assert_eq!(dt.cfg_postorder(), &[block2, block0]); + assert_eq!(dt.cfg_postorder(), &[trap_block, block2, block0]); let v2_def = cur.func.dfg.value_def(v2).unwrap_inst(); assert!(!dt.dominates(v2_def, block0, &cur.func.layout)); @@ -710,8 +700,7 @@ mod tests { let jmp_block3_block1 = cur.ins().jump(block1, &[]); cur.insert_block(block1); - let br_block1_block0 = cur.ins().brnz(cond, block0, &[]); - let jmp_block1_block2 = cur.ins().jump(block2, &[]); + let br_block1_block0_block2 = cur.ins().brif(cond, block0, &[], block2, &[]); cur.insert_block(block2); cur.ins().jump(block0, &[]); @@ -726,7 +715,7 @@ mod tests { // block3 { // block3:jump block1 { // block1 { - // block1:brnz block0 { + // block1:brif block0 { // block1:jump block2 { // block2 { // block2:jump block0 (seen) @@ -734,7 +723,7 @@ mod tests { // } block1:jump block2 // block0 { // } block0 - // } block1:brnz block0 + // } block1:brif block0 // } block1 // } block3:jump block1 // } block3 @@ -744,12 +733,16 @@ mod tests { assert_eq!(cur.func.layout.entry_block().unwrap(), block3); assert_eq!(dt.idom(block3), None); assert_eq!(dt.idom(block1).unwrap(), jmp_block3_block1); - assert_eq!(dt.idom(block2).unwrap(), jmp_block1_block2); - assert_eq!(dt.idom(block0).unwrap(), br_block1_block0); + assert_eq!(dt.idom(block2).unwrap(), br_block1_block0_block2); + assert_eq!(dt.idom(block0).unwrap(), br_block1_block0_block2); - assert!(dt.dominates(br_block1_block0, br_block1_block0, &cur.func.layout)); - assert!(!dt.dominates(br_block1_block0, jmp_block3_block1, &cur.func.layout)); - assert!(dt.dominates(jmp_block3_block1, br_block1_block0, &cur.func.layout)); + assert!(dt.dominates( + br_block1_block0_block2, + br_block1_block0_block2, + &cur.func.layout + )); + assert!(!dt.dominates(br_block1_block0_block2, jmp_block3_block1, &cur.func.layout)); + assert!(dt.dominates(jmp_block3_block1, br_block1_block0_block2, &cur.func.layout)); assert_eq!( dt.rpo_cmp(block3, block3, &cur.func.layout), @@ -761,7 +754,7 @@ mod tests { Ordering::Less ); assert_eq!( - dt.rpo_cmp(jmp_block3_block1, jmp_block1_block2, &cur.func.layout), + dt.rpo_cmp(jmp_block3_block1, br_block1_block0_block2, &cur.func.layout), Ordering::Less ); } diff --git a/cranelift/codegen/src/egraph.rs b/cranelift/codegen/src/egraph.rs new file mode 100644 index 000000000000..02bfeb013cdd --- /dev/null +++ b/cranelift/codegen/src/egraph.rs @@ -0,0 +1,612 @@ +//! Support for egraphs represented in the DataFlowGraph. + +use crate::alias_analysis::{AliasAnalysis, LastStores}; +use crate::ctxhash::{CtxEq, CtxHash, CtxHashMap}; +use crate::cursor::{Cursor, CursorPosition, FuncCursor}; +use crate::dominator_tree::DominatorTree; +use crate::egraph::domtree::DomTreeWithChildren; +use crate::egraph::elaborate::Elaborator; +use crate::fx::FxHashSet; +use crate::inst_predicates::{is_mergeable_for_egraph, is_pure_for_egraph}; +use crate::ir::{ + DataFlowGraph, Function, Inst, InstructionData, Type, Value, ValueDef, ValueListPool, +}; +use crate::loop_analysis::LoopAnalysis; +use crate::opts::generated_code::ContextIter; +use crate::opts::IsleContext; +use crate::trace; +use crate::unionfind::UnionFind; +use cranelift_entity::packed_option::ReservedValue; +use cranelift_entity::SecondaryMap; +use std::hash::Hasher; + +mod cost; +mod domtree; +mod elaborate; + +/// Pass over a Function that does the whole aegraph thing. +/// +/// - Removes non-skeleton nodes from the Layout. +/// - Performs a GVN-and-rule-application pass over all Values +/// reachable from the skeleton, potentially creating new Union +/// nodes (i.e., an aegraph) so that some values have multiple +/// representations. +/// - Does "extraction" on the aegraph: selects the best value out of +/// the tree-of-Union nodes for each used value. +/// - Does "scoped elaboration" on the aegraph: chooses one or more +/// locations for pure nodes to become instructions again in the +/// layout, as forced by the skeleton. +/// +/// At the beginning and end of this pass, the CLIF should be in a +/// state that passes the verifier and, additionally, has no Union +/// nodes. During the pass, Union nodes may exist, and instructions in +/// the layout may refer to results of instructions that are not +/// placed in the layout. +pub struct EgraphPass<'a> { + /// The function we're operating on. + func: &'a mut Function, + /// Dominator tree, used for elaboration pass. + domtree: &'a DominatorTree, + /// Alias analysis, used during optimization. + alias_analysis: &'a mut AliasAnalysis<'a>, + /// "Domtree with children": like `domtree`, but with an explicit + /// list of children, rather than just parent pointers. + domtree_children: DomTreeWithChildren, + /// Loop analysis results, used for built-in LICM during + /// elaboration. + loop_analysis: &'a LoopAnalysis, + /// Which canonical Values do we want to rematerialize in each + /// block where they're used? + /// + /// (A canonical Value is the *oldest* Value in an eclass, + /// i.e. tree of union value-nodes). + remat_values: FxHashSet, + /// Stats collected while we run this pass. + pub(crate) stats: Stats, + /// Union-find that maps all members of a Union tree (eclass) back + /// to the *oldest* (lowest-numbered) `Value`. + eclasses: UnionFind, +} + +/// Context passed through node insertion and optimization. +pub(crate) struct OptimizeCtx<'opt, 'analysis> +where + 'analysis: 'opt, +{ + // Borrowed from EgraphPass: + pub(crate) func: &'opt mut Function, + pub(crate) value_to_opt_value: &'opt mut SecondaryMap, + pub(crate) gvn_map: &'opt mut CtxHashMap<(Type, InstructionData), Value>, + pub(crate) eclasses: &'opt mut UnionFind, + pub(crate) remat_values: &'opt mut FxHashSet, + pub(crate) stats: &'opt mut Stats, + pub(crate) alias_analysis: &'opt mut AliasAnalysis<'analysis>, + pub(crate) alias_analysis_state: &'opt mut LastStores, + // Held locally during optimization of one node (recursively): + pub(crate) rewrite_depth: usize, + pub(crate) subsume_values: FxHashSet, +} + +/// For passing to `insert_pure_enode`. Sometimes the enode already +/// exists as an Inst (from the original CLIF), and sometimes we're in +/// the middle of creating it and want to avoid inserting it if +/// possible until we know we need it. +pub(crate) enum NewOrExistingInst { + New(InstructionData, Type), + Existing(Inst), +} + +impl NewOrExistingInst { + fn get_inst_key<'a>(&'a self, dfg: &'a DataFlowGraph) -> (Type, InstructionData) { + match self { + NewOrExistingInst::New(data, ty) => (*ty, *data), + NewOrExistingInst::Existing(inst) => { + let ty = dfg.ctrl_typevar(*inst); + (ty, dfg.insts[*inst].clone()) + } + } + } +} + +impl<'opt, 'analysis> OptimizeCtx<'opt, 'analysis> +where + 'analysis: 'opt, +{ + /// Optimization of a single instruction. + /// + /// This does a few things: + /// - Looks up the instruction in the GVN deduplication map. If we + /// already have the same instruction somewhere else, with the + /// same args, then we can alias the original instruction's + /// results and omit this instruction entirely. + /// - Note that we do this canonicalization based on the + /// instruction with its arguments as *canonical* eclass IDs, + /// that is, the oldest (smallest index) `Value` reachable in + /// the tree-of-unions (whole eclass). This ensures that we + /// properly canonicalize newer nodes that use newer "versions" + /// of a value that are still equal to the older versions. + /// - If the instruction is "new" (not deduplicated), then apply + /// optimization rules: + /// - All of the mid-end rules written in ISLE. + /// - Store-to-load forwarding. + /// - Update the value-to-opt-value map, and update the eclass + /// union-find, if we rewrote the value to different form(s). + pub(crate) fn insert_pure_enode(&mut self, inst: NewOrExistingInst) -> Value { + // Create the external context for looking up and updating the + // GVN map. This is necessary so that instructions themselves + // do not have to carry all the references or data for a full + // `Eq` or `Hash` impl. + let gvn_context = GVNContext { + union_find: self.eclasses, + value_lists: &self.func.dfg.value_lists, + }; + + self.stats.pure_inst += 1; + if let NewOrExistingInst::New(..) = inst { + self.stats.new_inst += 1; + } + + // Does this instruction already exist? If so, add entries to + // the value-map to rewrite uses of its results to the results + // of the original (existing) instruction. If not, optimize + // the new instruction. + if let Some(&orig_result) = self + .gvn_map + .get(&inst.get_inst_key(&self.func.dfg), &gvn_context) + { + self.stats.pure_inst_deduped += 1; + if let NewOrExistingInst::Existing(inst) = inst { + debug_assert_eq!(self.func.dfg.inst_results(inst).len(), 1); + let result = self.func.dfg.first_result(inst); + self.value_to_opt_value[result] = orig_result; + self.eclasses.union(result, orig_result); + self.stats.union += 1; + result + } else { + orig_result + } + } else { + // Now actually insert the InstructionData and attach + // result value (exactly one). + let (inst, result, ty) = match inst { + NewOrExistingInst::New(data, typevar) => { + let inst = self.func.dfg.make_inst(data); + // TODO: reuse return value? + self.func.dfg.make_inst_results(inst, typevar); + let result = self.func.dfg.first_result(inst); + // Add to eclass unionfind. + self.eclasses.add(result); + // New inst. We need to do the analysis of its result. + (inst, result, typevar) + } + NewOrExistingInst::Existing(inst) => { + let result = self.func.dfg.first_result(inst); + let ty = self.func.dfg.ctrl_typevar(inst); + (inst, result, ty) + } + }; + + let opt_value = self.optimize_pure_enode(inst); + let gvn_context = GVNContext { + union_find: self.eclasses, + value_lists: &self.func.dfg.value_lists, + }; + self.gvn_map.insert( + (ty, self.func.dfg.insts[inst].clone()), + opt_value, + &gvn_context, + ); + self.value_to_opt_value[result] = opt_value; + opt_value + } + } + + /// Optimizes an enode by applying any matching mid-end rewrite + /// rules (or store-to-load forwarding, which is a special case), + /// unioning together all possible optimized (or rewritten) forms + /// of this expression into an eclass and returning the `Value` + /// that represents that eclass. + fn optimize_pure_enode(&mut self, inst: Inst) -> Value { + // A pure node always has exactly one result. + let orig_value = self.func.dfg.first_result(inst); + + let mut isle_ctx = IsleContext { ctx: self }; + + // Limit rewrite depth. When we apply optimization rules, they + // may create new nodes (values) and those are, recursively, + // optimized eagerly as soon as they are created. So we may + // have more than one ISLE invocation on the stack. (This is + // necessary so that as the toplevel builds the + // right-hand-side expression bottom-up, it uses the "latest" + // optimized values for all the constituent parts.) To avoid + // infinite or problematic recursion, we bound the rewrite + // depth to a small constant here. + const REWRITE_LIMIT: usize = 5; + if isle_ctx.ctx.rewrite_depth > REWRITE_LIMIT { + isle_ctx.ctx.stats.rewrite_depth_limit += 1; + return orig_value; + } + isle_ctx.ctx.rewrite_depth += 1; + + // Invoke the ISLE toplevel constructor, getting all new + // values produced as equivalents to this value. + trace!("Calling into ISLE with original value {}", orig_value); + isle_ctx.ctx.stats.rewrite_rule_invoked += 1; + let mut optimized_values = + crate::opts::generated_code::constructor_simplify(&mut isle_ctx, orig_value); + + // Create a union of all new values with the original (or + // maybe just one new value marked as "subsuming" the + // original, if present.) + let mut union_value = orig_value; + while let Some(optimized_value) = optimized_values.next(&mut isle_ctx) { + trace!( + "Returned from ISLE for {}, got {:?}", + orig_value, + optimized_value + ); + if optimized_value == orig_value { + trace!(" -> same as orig value; skipping"); + continue; + } + if isle_ctx.ctx.subsume_values.contains(&optimized_value) { + // Merge in the unionfind so canonicalization + // still works, but take *only* the subsuming + // value, and break now. + isle_ctx.ctx.eclasses.union(optimized_value, union_value); + union_value = optimized_value; + break; + } + + let old_union_value = union_value; + union_value = isle_ctx + .ctx + .func + .dfg + .union(old_union_value, optimized_value); + isle_ctx.ctx.stats.union += 1; + trace!(" -> union: now {}", union_value); + isle_ctx.ctx.eclasses.add(union_value); + isle_ctx + .ctx + .eclasses + .union(old_union_value, optimized_value); + isle_ctx.ctx.eclasses.union(old_union_value, union_value); + } + + isle_ctx.ctx.rewrite_depth -= 1; + + union_value + } + + /// Optimize a "skeleton" instruction, possibly removing + /// it. Returns `true` if the instruction should be removed from + /// the layout. + fn optimize_skeleton_inst(&mut self, inst: Inst) -> bool { + self.stats.skeleton_inst += 1; + + // First, can we try to deduplicate? We need to keep some copy + // of the instruction around because it's side-effecting, but + // we may be able to reuse an earlier instance of it. + if is_mergeable_for_egraph(self.func, inst) { + let result = self.func.dfg.inst_results(inst)[0]; + trace!(" -> mergeable side-effecting op {}", inst); + let inst = NewOrExistingInst::Existing(inst); + let gvn_context = GVNContext { + union_find: self.eclasses, + value_lists: &self.func.dfg.value_lists, + }; + + // Does this instruction already exist? If so, add entries to + // the value-map to rewrite uses of its results to the results + // of the original (existing) instruction. If not, optimize + // the new instruction. + let key = inst.get_inst_key(&self.func.dfg); + if let Some(&orig_result) = self.gvn_map.get(&key, &gvn_context) { + // Hit in GVN map -- reuse value. + self.value_to_opt_value[result] = orig_result; + self.eclasses.union(orig_result, result); + trace!(" -> merges result {} to {}", result, orig_result); + true + } else { + // Otherwise, insert it into the value-map. + self.value_to_opt_value[result] = result; + self.gvn_map.insert(key, result, &gvn_context); + trace!(" -> inserts as new (no GVN)"); + false + } + } + // Otherwise, if a load or store, process it with the alias + // analysis to see if we can optimize it (rewrite in terms of + // an earlier load or stored value). + else if let Some(new_result) = + self.alias_analysis + .process_inst(self.func, self.alias_analysis_state, inst) + { + self.stats.alias_analysis_removed += 1; + let result = self.func.dfg.first_result(inst); + trace!( + " -> inst {} has result {} replaced with {}", + inst, + result, + new_result + ); + self.value_to_opt_value[result] = new_result; + true + } + // Otherwise, generic side-effecting op -- always keep it, and + // set its results to identity-map to original values. + else { + // Set all results to identity-map to themselves + // in the value-to-opt-value map. + for &result in self.func.dfg.inst_results(inst) { + self.value_to_opt_value[result] = result; + self.eclasses.add(result); + } + false + } + } +} + +impl<'a> EgraphPass<'a> { + /// Create a new EgraphPass. + pub fn new( + func: &'a mut Function, + domtree: &'a DominatorTree, + loop_analysis: &'a LoopAnalysis, + alias_analysis: &'a mut AliasAnalysis<'a>, + ) -> Self { + let num_values = func.dfg.num_values(); + let domtree_children = DomTreeWithChildren::new(func, domtree); + Self { + func, + domtree, + domtree_children, + loop_analysis, + alias_analysis, + stats: Stats::default(), + eclasses: UnionFind::with_capacity(num_values), + remat_values: FxHashSet::default(), + } + } + + /// Run the process. + pub fn run(&mut self) { + self.remove_pure_and_optimize(); + + trace!("egraph built:\n{}\n", self.func.display()); + if cfg!(feature = "trace-log") { + for (value, def) in self.func.dfg.values_and_defs() { + trace!(" -> {} = {:?}", value, def); + match def { + ValueDef::Result(i, 0) => { + trace!(" -> {} = {:?}", i, self.func.dfg.insts[i]); + } + _ => {} + } + } + } + trace!("stats: {:?}", self.stats); + self.elaborate(); + } + + /// Remove pure nodes from the `Layout` of the function, ensuring + /// that only the "side-effect skeleton" remains, and also + /// optimize the pure nodes. This is the first step of + /// egraph-based processing and turns the pure CFG-based CLIF into + /// a CFG skeleton with a sea of (optimized) nodes tying it + /// together. + /// + /// As we walk through the code, we eagerly apply optimization + /// rules; at any given point we have a "latest version" of an + /// eclass of possible representations for a `Value` in the + /// original program, which is itself a `Value` at the root of a + /// union-tree. We keep a map from the original values to these + /// optimized values. When we encounter any instruction (pure or + /// side-effecting skeleton) we rewrite its arguments to capture + /// the "latest" optimized forms of these values. (We need to do + /// this as part of this pass, and not later using a finished map, + /// because the eclass can continue to be updated and we need to + /// only refer to its subset that exists at this stage, to + /// maintain acyclicity.) + fn remove_pure_and_optimize(&mut self) { + let mut cursor = FuncCursor::new(self.func); + let mut value_to_opt_value: SecondaryMap = + SecondaryMap::with_default(Value::reserved_value()); + let mut gvn_map: CtxHashMap<(Type, InstructionData), Value> = + CtxHashMap::with_capacity(cursor.func.dfg.num_values()); + + // In domtree preorder, visit blocks. (TODO: factor out an + // iterator from this and elaborator.) + let root = self.domtree_children.root(); + let mut block_stack = vec![root]; + while let Some(block) = block_stack.pop() { + // We popped this block; push children + // immediately, then process this block. + block_stack.extend(self.domtree_children.children(block)); + + trace!("Processing block {}", block); + cursor.set_position(CursorPosition::Before(block)); + + let mut alias_analysis_state = self.alias_analysis.block_starting_state(block); + + for ¶m in cursor.func.dfg.block_params(block) { + trace!("creating initial singleton eclass for blockparam {}", param); + self.eclasses.add(param); + value_to_opt_value[param] = param; + } + while let Some(inst) = cursor.next_inst() { + trace!("Processing inst {}", inst); + + // While we're passing over all insts, create initial + // singleton eclasses for all result and blockparam + // values. Also do initial analysis of all inst + // results. + for &result in cursor.func.dfg.inst_results(inst) { + trace!("creating initial singleton eclass for {}", result); + self.eclasses.add(result); + } + + // Rewrite args of *all* instructions using the + // value-to-opt-value map. + cursor.func.dfg.resolve_aliases_in_arguments(inst); + cursor.func.dfg.map_inst_values(inst, |_, arg| { + let new_value = value_to_opt_value[arg]; + trace!("rewriting arg {} of inst {} to {}", arg, inst, new_value); + debug_assert_ne!(new_value, Value::reserved_value()); + new_value + }); + + // Build a context for optimization, with borrows of + // state. We can't invoke a method on `self` because + // we've borrowed `self.func` mutably (as + // `cursor.func`) so we pull apart the pieces instead + // here. + let mut ctx = OptimizeCtx { + func: cursor.func, + value_to_opt_value: &mut value_to_opt_value, + gvn_map: &mut gvn_map, + eclasses: &mut self.eclasses, + rewrite_depth: 0, + subsume_values: FxHashSet::default(), + remat_values: &mut self.remat_values, + stats: &mut self.stats, + alias_analysis: self.alias_analysis, + alias_analysis_state: &mut alias_analysis_state, + }; + + if is_pure_for_egraph(ctx.func, inst) { + // Insert into GVN map and optimize any new nodes + // inserted (recursively performing this work for + // any nodes the optimization rules produce). + let inst = NewOrExistingInst::Existing(inst); + ctx.insert_pure_enode(inst); + // We've now rewritten all uses, or will when we + // see them, and the instruction exists as a pure + // enode in the eclass, so we can remove it. + cursor.remove_inst_and_step_back(); + } else { + if ctx.optimize_skeleton_inst(inst) { + cursor.remove_inst_and_step_back(); + } + } + } + } + } + + /// Scoped elaboration: compute a final ordering of op computation + /// for each block and update the given Func body. After this + /// runs, the function body is back into the state where every + /// Inst with an used result is placed in the layout (possibly + /// duplicated, if our code-motion logic decides this is the best + /// option). + /// + /// This works in concert with the domtree. We do a preorder + /// traversal of the domtree, tracking a scoped map from Id to + /// (new) Value. The map's scopes correspond to levels in the + /// domtree. + /// + /// At each block, we iterate forward over the side-effecting + /// eclasses, and recursively generate their arg eclasses, then + /// emit the ops themselves. + /// + /// To use an eclass in a given block, we first look it up in the + /// scoped map, and get the Value if already present. If not, we + /// need to generate it. We emit the extracted enode for this + /// eclass after recursively generating its args. Eclasses are + /// thus computed "as late as possible", but then memoized into + /// the Id-to-Value map and available to all dominated blocks and + /// for the rest of this block. (This subsumes GVN.) + fn elaborate(&mut self) { + let mut elaborator = Elaborator::new( + self.func, + self.domtree, + &self.domtree_children, + self.loop_analysis, + &mut self.remat_values, + &mut self.eclasses, + &mut self.stats, + ); + elaborator.elaborate(); + + self.check_post_egraph(); + } + + #[cfg(debug_assertions)] + fn check_post_egraph(&self) { + // Verify that no union nodes are reachable from inst args, + // and that all inst args' defining instructions are in the + // layout. + for block in self.func.layout.blocks() { + for inst in self.func.layout.block_insts(block) { + self.func + .dfg + .inst_values(inst) + .for_each(|arg| match self.func.dfg.value_def(arg) { + ValueDef::Result(i, _) => { + debug_assert!(self.func.layout.inst_block(i).is_some()); + } + ValueDef::Union(..) => { + panic!("egraph union node {} still reachable at {}!", arg, inst); + } + _ => {} + }) + } + } + } + + #[cfg(not(debug_assertions))] + fn check_post_egraph(&self) {} +} + +/// Implementation of external-context equality and hashing on +/// InstructionData. This allows us to deduplicate instructions given +/// some context that lets us see its value lists and the mapping from +/// any value to "canonical value" (in an eclass). +struct GVNContext<'a> { + value_lists: &'a ValueListPool, + union_find: &'a UnionFind, +} + +impl<'a> CtxEq<(Type, InstructionData), (Type, InstructionData)> for GVNContext<'a> { + fn ctx_eq( + &self, + (a_ty, a_inst): &(Type, InstructionData), + (b_ty, b_inst): &(Type, InstructionData), + ) -> bool { + a_ty == b_ty + && a_inst.eq(b_inst, self.value_lists, |value| { + self.union_find.find(value) + }) + } +} + +impl<'a> CtxHash<(Type, InstructionData)> for GVNContext<'a> { + fn ctx_hash(&self, state: &mut H, (ty, inst): &(Type, InstructionData)) { + std::hash::Hash::hash(&ty, state); + inst.hash(state, self.value_lists, |value| self.union_find.find(value)); + } +} + +/// Statistics collected during egraph-based processing. +#[derive(Clone, Debug, Default)] +pub(crate) struct Stats { + pub(crate) pure_inst: u64, + pub(crate) pure_inst_deduped: u64, + pub(crate) skeleton_inst: u64, + pub(crate) alias_analysis_removed: u64, + pub(crate) new_inst: u64, + pub(crate) union: u64, + pub(crate) subsume: u64, + pub(crate) remat: u64, + pub(crate) rewrite_rule_invoked: u64, + pub(crate) rewrite_depth_limit: u64, + pub(crate) elaborate_visit_node: u64, + pub(crate) elaborate_memoize_hit: u64, + pub(crate) elaborate_memoize_miss: u64, + pub(crate) elaborate_memoize_miss_remat: u64, + pub(crate) elaborate_licm_hoist: u64, + pub(crate) elaborate_func: u64, + pub(crate) elaborate_func_pre_insts: u64, + pub(crate) elaborate_func_post_insts: u64, +} diff --git a/cranelift/codegen/src/egraph/cost.rs b/cranelift/codegen/src/egraph/cost.rs new file mode 100644 index 000000000000..9cfb0894ca74 --- /dev/null +++ b/cranelift/codegen/src/egraph/cost.rs @@ -0,0 +1,97 @@ +//! Cost functions for egraph representation. + +use crate::ir::Opcode; + +/// A cost of computing some value in the program. +/// +/// Costs are measured in an arbitrary union that we represent in a +/// `u32`. The ordering is meant to be meaningful, but the value of a +/// single unit is arbitrary (and "not to scale"). We use a collection +/// of heuristics to try to make this approximation at least usable. +/// +/// We start by defining costs for each opcode (see `pure_op_cost` +/// below). The cost of computing some value, initially, is the cost +/// of its opcode, plus the cost of computing its inputs. +/// +/// We then adjust the cost according to loop nests: for each +/// loop-nest level, we multiply by 1024. Because we only have 32 +/// bits, we limit this scaling to a loop-level of two (i.e., multiply +/// by 2^20 ~= 1M). +/// +/// Arithmetic on costs is always saturating: we don't want to wrap +/// around and return to a tiny cost when adding the costs of two very +/// expensive operations. It is better to approximate and lose some +/// precision than to lose the ordering by wrapping. +/// +/// Finally, we reserve the highest value, `u32::MAX`, as a sentinel +/// that means "infinite". This is separate from the finite costs and +/// not reachable by doing arithmetic on them (even when overflowing) +/// -- we saturate just *below* infinity. (This is done by the +/// `finite()` method.) An infinite cost is used to represent a value +/// that cannot be computed, or otherwise serve as a sentinel when +/// performing search for the lowest-cost representation of a value. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct Cost(u32); +impl Cost { + pub(crate) fn at_level(&self, loop_level: usize) -> Cost { + let loop_level = std::cmp::min(2, loop_level); + let multiplier = 1u32 << ((10 * loop_level) as u32); + Cost(self.0.saturating_mul(multiplier)).finite() + } + + pub(crate) fn infinity() -> Cost { + // 2^32 - 1 is, uh, pretty close to infinite... (we use `Cost` + // only for heuristics and always saturate so this suffices!) + Cost(u32::MAX) + } + + pub(crate) fn zero() -> Cost { + Cost(0) + } + + /// Clamp this cost at a "finite" value. Can be used in + /// conjunction with saturating ops to avoid saturating into + /// `infinity()`. + fn finite(self) -> Cost { + Cost(std::cmp::min(u32::MAX - 1, self.0)) + } +} + +impl std::default::Default for Cost { + fn default() -> Cost { + Cost::zero() + } +} + +impl std::ops::Add for Cost { + type Output = Cost; + fn add(self, other: Cost) -> Cost { + Cost(self.0.saturating_add(other.0)).finite() + } +} + +/// Return the cost of a *pure* opcode. Caller is responsible for +/// checking that the opcode came from an instruction that satisfies +/// `inst_predicates::is_pure_for_egraph()`. +pub(crate) fn pure_op_cost(op: Opcode) -> Cost { + match op { + // Constants. + Opcode::Iconst | Opcode::F32const | Opcode::F64const => Cost(0), + // Extends/reduces. + Opcode::Uextend | Opcode::Sextend | Opcode::Ireduce | Opcode::Iconcat | Opcode::Isplit => { + Cost(1) + } + // "Simple" arithmetic. + Opcode::Iadd + | Opcode::Isub + | Opcode::Band + | Opcode::Bor + | Opcode::Bxor + | Opcode::Bnot + | Opcode::Ishl + | Opcode::Ushr + | Opcode::Sshr => Cost(2), + // Everything else (pure.) + _ => Cost(3), + } +} diff --git a/cranelift/codegen/src/egraph/domtree.rs b/cranelift/codegen/src/egraph/domtree.rs new file mode 100644 index 000000000000..f0af89e2a244 --- /dev/null +++ b/cranelift/codegen/src/egraph/domtree.rs @@ -0,0 +1,69 @@ +//! Extended domtree with various traversal support. + +use crate::dominator_tree::DominatorTree; +use crate::ir::{Block, Function}; +use cranelift_entity::{packed_option::PackedOption, SecondaryMap}; + +#[derive(Clone, Debug)] +pub(crate) struct DomTreeWithChildren { + nodes: SecondaryMap, + root: Block, +} + +#[derive(Clone, Copy, Debug, Default)] +struct DomTreeNode { + children: PackedOption, + next: PackedOption, +} + +impl DomTreeWithChildren { + pub(crate) fn new(func: &Function, domtree: &DominatorTree) -> DomTreeWithChildren { + let mut nodes: SecondaryMap = + SecondaryMap::with_capacity(func.dfg.num_blocks()); + + for block in func.layout.blocks() { + let idom_inst = match domtree.idom(block) { + Some(idom_inst) => idom_inst, + None => continue, + }; + let idom = func + .layout + .inst_block(idom_inst) + .expect("Dominating instruction should be part of a block"); + + nodes[block].next = nodes[idom].children; + nodes[idom].children = block.into(); + } + + let root = func.layout.entry_block().unwrap(); + + Self { nodes, root } + } + + pub(crate) fn root(&self) -> Block { + self.root + } + + pub(crate) fn children<'a>(&'a self, block: Block) -> DomTreeChildIter<'a> { + let block = self.nodes[block].children; + DomTreeChildIter { + domtree: self, + block, + } + } +} + +pub(crate) struct DomTreeChildIter<'a> { + domtree: &'a DomTreeWithChildren, + block: PackedOption, +} + +impl<'a> Iterator for DomTreeChildIter<'a> { + type Item = Block; + fn next(&mut self) -> Option { + self.block.expand().map(|block| { + self.block = self.domtree.nodes[block].next; + block + }) + } +} diff --git a/cranelift/codegen/src/egraph/elaborate.rs b/cranelift/codegen/src/egraph/elaborate.rs new file mode 100644 index 000000000000..d52927ffeaa9 --- /dev/null +++ b/cranelift/codegen/src/egraph/elaborate.rs @@ -0,0 +1,679 @@ +//! Elaboration phase: lowers EGraph back to sequences of operations +//! in CFG nodes. + +use super::cost::{pure_op_cost, Cost}; +use super::domtree::DomTreeWithChildren; +use super::Stats; +use crate::dominator_tree::DominatorTree; +use crate::fx::FxHashSet; +use crate::ir::{Block, Function, Inst, Value, ValueDef}; +use crate::loop_analysis::{Loop, LoopAnalysis, LoopLevel}; +use crate::scoped_hash_map::ScopedHashMap; +use crate::trace; +use crate::unionfind::UnionFind; +use alloc::vec::Vec; +use cranelift_entity::{packed_option::ReservedValue, SecondaryMap}; +use smallvec::{smallvec, SmallVec}; + +pub(crate) struct Elaborator<'a> { + func: &'a mut Function, + domtree: &'a DominatorTree, + domtree_children: &'a DomTreeWithChildren, + loop_analysis: &'a LoopAnalysis, + eclasses: &'a mut UnionFind, + /// Map from Value that is produced by a pure Inst (and was thus + /// not in the side-effecting skeleton) to the value produced by + /// an elaborated inst (placed in the layout) to whose results we + /// refer in the final code. + /// + /// The first time we use some result of an instruction during + /// elaboration, we can place it and insert an identity map (inst + /// results to that same inst's results) in this scoped + /// map. Within that block and its dom-tree children, that mapping + /// is visible and we can continue to use it. This allows us to + /// avoid cloning the instruction. However, if we pop that scope + /// and use it somewhere else as well, we will need to + /// duplicate. We detect this case by checking, when a value that + /// we want is not present in this map, whether the producing inst + /// is already placed in the Layout. If so, we duplicate, and + /// insert non-identity mappings from the original inst's results + /// to the cloned inst's results. + value_to_elaborated_value: ScopedHashMap, + /// Map from Value to the best (lowest-cost) Value in its eclass + /// (tree of union value-nodes). + value_to_best_value: SecondaryMap, + /// Stack of blocks and loops in current elaboration path. + loop_stack: SmallVec<[LoopStackEntry; 8]>, + /// The current block into which we are elaborating. + cur_block: Block, + /// Values that opt rules have indicated should be rematerialized + /// in every block they are used (e.g., immediates or other + /// "cheap-to-compute" ops). + remat_values: &'a FxHashSet, + /// Explicitly-unrolled value elaboration stack. + elab_stack: Vec, + /// Results from the elab stack. + elab_result_stack: Vec, + /// Explicitly-unrolled block elaboration stack. + block_stack: Vec, + /// Stats for various events during egraph processing, to help + /// with optimization of this infrastructure. + stats: &'a mut Stats, +} + +#[derive(Clone, Copy, Debug)] +struct ElaboratedValue { + in_block: Block, + value: Value, +} + +#[derive(Clone, Debug)] +struct LoopStackEntry { + /// The loop identifier. + lp: Loop, + /// The hoist point: a block that immediately dominates this + /// loop. May not be an immediate predecessor, but will be a valid + /// point to place all loop-invariant ops: they must depend only + /// on inputs that dominate the loop, so are available at (the end + /// of) this block. + hoist_block: Block, + /// The depth in the scope map. + scope_depth: u32, +} + +#[derive(Clone, Debug)] +enum ElabStackEntry { + /// Next action is to resolve this value into an elaborated inst + /// (placed into the layout) that produces the value, and + /// recursively elaborate the insts that produce its args. + /// + /// Any inserted ops should be inserted before `before`, which is + /// the instruction demanding this value. + Start { value: Value, before: Inst }, + /// Args have been pushed; waiting for results. + PendingInst { + inst: Inst, + result_idx: usize, + num_args: usize, + remat: bool, + before: Inst, + }, +} + +#[derive(Clone, Debug)] +enum BlockStackEntry { + Elaborate { block: Block, idom: Option }, + Pop, +} + +impl<'a> Elaborator<'a> { + pub(crate) fn new( + func: &'a mut Function, + domtree: &'a DominatorTree, + domtree_children: &'a DomTreeWithChildren, + loop_analysis: &'a LoopAnalysis, + remat_values: &'a FxHashSet, + eclasses: &'a mut UnionFind, + stats: &'a mut Stats, + ) -> Self { + let num_values = func.dfg.num_values(); + let mut value_to_best_value = + SecondaryMap::with_default((Cost::infinity(), Value::reserved_value())); + value_to_best_value.resize(num_values); + Self { + func, + domtree, + domtree_children, + loop_analysis, + eclasses, + value_to_elaborated_value: ScopedHashMap::with_capacity(num_values), + value_to_best_value, + loop_stack: smallvec![], + cur_block: Block::reserved_value(), + remat_values, + elab_stack: vec![], + elab_result_stack: vec![], + block_stack: vec![], + stats, + } + } + + fn start_block(&mut self, idom: Option, block: Block) { + trace!( + "start_block: block {:?} with idom {:?} at loop depth {:?} scope depth {}", + block, + idom, + self.loop_stack.len(), + self.value_to_elaborated_value.depth() + ); + + // Pop any loop levels we're no longer in. + while let Some(inner_loop) = self.loop_stack.last() { + if self.loop_analysis.is_in_loop(block, inner_loop.lp) { + break; + } + self.loop_stack.pop(); + } + + // Note that if the *entry* block is a loop header, we will + // not make note of the loop here because it will not have an + // immediate dominator. We must disallow this case because we + // will skip adding the `LoopStackEntry` here but our + // `LoopAnalysis` will otherwise still make note of this loop + // and loop depths will not match. + if let Some(idom) = idom { + if let Some(lp) = self.loop_analysis.is_loop_header(block) { + self.loop_stack.push(LoopStackEntry { + lp, + // Any code hoisted out of this loop will have code + // placed in `idom`, and will have def mappings + // inserted in to the scoped hashmap at that block's + // level. + hoist_block: idom, + scope_depth: (self.value_to_elaborated_value.depth() - 1) as u32, + }); + trace!( + " -> loop header, pushing; depth now {}", + self.loop_stack.len() + ); + } + } else { + debug_assert!( + self.loop_analysis.is_loop_header(block).is_none(), + "Entry block (domtree root) cannot be a loop header!" + ); + } + + trace!("block {}: loop stack is {:?}", block, self.loop_stack); + + self.cur_block = block; + } + + fn compute_best_values(&mut self) { + let best = &mut self.value_to_best_value; + for (value, def) in self.func.dfg.values_and_defs() { + trace!("computing best for value {:?} def {:?}", value, def); + match def { + ValueDef::Union(x, y) => { + // Pick the best of the two options based on + // min-cost. This works because each element of `best` + // is a `(cost, value)` tuple; `cost` comes first so + // the natural comparison works based on cost, and + // breaks ties based on value number. + trace!(" -> best of {:?} and {:?}", best[x], best[y]); + best[value] = std::cmp::min(best[x], best[y]); + trace!(" -> {:?}", best[value]); + } + ValueDef::Param(_, _) => { + best[value] = (Cost::zero(), value); + } + // If the Inst is inserted into the layout (which is, + // at this point, only the side-effecting skeleton), + // then it must be computed and thus we give it zero + // cost. + ValueDef::Result(inst, _) if self.func.layout.inst_block(inst).is_some() => { + best[value] = (Cost::zero(), value); + } + ValueDef::Result(inst, _) => { + trace!(" -> value {}: result, computing cost", value); + let inst_data = &self.func.dfg.insts[inst]; + let loop_level = self + .func + .layout + .inst_block(inst) + .map(|block| self.loop_analysis.loop_level(block)) + .unwrap_or(LoopLevel::root()); + // N.B.: at this point we know that the opcode is + // pure, so `pure_op_cost`'s precondition is + // satisfied. + let cost = self.func.dfg.inst_values(inst).fold( + pure_op_cost(inst_data.opcode()).at_level(loop_level.level()), + |cost, value| cost + best[value].0, + ); + best[value] = (cost, value); + } + }; + debug_assert_ne!(best[value].0, Cost::infinity()); + debug_assert_ne!(best[value].1, Value::reserved_value()); + trace!("best for eclass {:?}: {:?}", value, best[value]); + } + } + + /// Elaborate use of an eclass, inserting any needed new + /// instructions before the given inst `before`. Should only be + /// given values corresponding to results of instructions or + /// blockparams. + fn elaborate_eclass_use(&mut self, value: Value, before: Inst) -> ElaboratedValue { + debug_assert_ne!(value, Value::reserved_value()); + + // Kick off the process by requesting this result + // value. + self.elab_stack + .push(ElabStackEntry::Start { value, before }); + + // Now run the explicit-stack recursion until we reach + // the root. + self.process_elab_stack(); + debug_assert_eq!(self.elab_result_stack.len(), 1); + self.elab_result_stack.pop().unwrap() + } + + fn process_elab_stack(&mut self) { + while let Some(entry) = self.elab_stack.last() { + match entry { + &ElabStackEntry::Start { value, before } => { + // We always replace the Start entry, so pop it now. + self.elab_stack.pop(); + + debug_assert_ne!(value, Value::reserved_value()); + let value = self.func.dfg.resolve_aliases(value); + + self.stats.elaborate_visit_node += 1; + let canonical_value = self.eclasses.find_and_update(value); + debug_assert_ne!(canonical_value, Value::reserved_value()); + trace!( + "elaborate: value {} canonical {} before {}", + value, + canonical_value, + before + ); + + // Get the best option; we use `value` (latest + // value) here so we have a full view of the + // eclass. + trace!("looking up best value for {}", value); + let (_, best_value) = self.value_to_best_value[value]; + debug_assert_ne!(best_value, Value::reserved_value()); + trace!("elaborate: value {} -> best {}", value, best_value); + + let remat = if let Some(elab_val) = + self.value_to_elaborated_value.get(&canonical_value) + { + // Value is available. Look at the defined + // block, and determine whether this node kind + // allows rematerialization if the value comes + // from another block. If so, ignore the hit + // and recompute below. + let remat = elab_val.in_block != self.cur_block + && self.remat_values.contains(&best_value); + if !remat { + trace!("elaborate: value {} -> {:?}", value, elab_val); + self.stats.elaborate_memoize_hit += 1; + self.elab_result_stack.push(*elab_val); + continue; + } + trace!("elaborate: value {} -> remat", canonical_value); + self.stats.elaborate_memoize_miss_remat += 1; + // The op is pure at this point, so it is always valid to + // remove from this map. + self.value_to_elaborated_value.remove(&canonical_value); + true + } else { + // Value not available; but still look up + // whether it's been flagged for remat because + // this affects placement. + let remat = self.remat_values.contains(&best_value); + trace!(" -> not present in map; remat = {}", remat); + remat + }; + self.stats.elaborate_memoize_miss += 1; + + // Now resolve the value to its definition to see + // how we can compute it. + let (inst, result_idx) = match self.func.dfg.value_def(best_value) { + ValueDef::Result(inst, result_idx) => { + trace!( + " -> value {} is result {} of {}", + best_value, + result_idx, + inst + ); + (inst, result_idx) + } + ValueDef::Param(_, _) => { + // We don't need to do anything to compute + // this value; just push its result on the + // result stack (blockparams are already + // available). + trace!(" -> value {} is a blockparam", best_value); + self.elab_result_stack.push(ElaboratedValue { + in_block: self.cur_block, + value: best_value, + }); + continue; + } + ValueDef::Union(_, _) => { + panic!("Should never have a Union value as the best value"); + } + }; + + trace!( + " -> result {} of inst {:?}", + result_idx, + self.func.dfg.insts[inst] + ); + + // We're going to need to use this instruction + // result, placing the instruction into the + // layout. First, enqueue all args to be + // elaborated. Push state to receive the results + // and later elab this inst. + let num_args = self.func.dfg.inst_values(inst).count(); + self.elab_stack.push(ElabStackEntry::PendingInst { + inst, + result_idx, + num_args, + remat, + before, + }); + + // Push args in reverse order so we process the + // first arg first. + for arg in self.func.dfg.inst_values(inst).rev() { + debug_assert_ne!(arg, Value::reserved_value()); + self.elab_stack + .push(ElabStackEntry::Start { value: arg, before }); + } + } + + &ElabStackEntry::PendingInst { + inst, + result_idx, + num_args, + remat, + before, + } => { + self.elab_stack.pop(); + + trace!( + "PendingInst: {} result {} args {} remat {} before {}", + inst, + result_idx, + num_args, + remat, + before + ); + + // We should have all args resolved at this + // point. Grab them and drain them out, removing + // them. + let arg_idx = self.elab_result_stack.len() - num_args; + let arg_values = &self.elab_result_stack[arg_idx..]; + + // Compute max loop depth. + let loop_hoist_level = arg_values + .iter() + .map(|&value| { + // Find the outermost loop level at which + // the value's defining block *is not* a + // member. This is the loop-nest level + // whose hoist-block we hoist to. + let hoist_level = self + .loop_stack + .iter() + .position(|loop_entry| { + !self.loop_analysis.is_in_loop(value.in_block, loop_entry.lp) + }) + .unwrap_or(self.loop_stack.len()); + trace!( + " -> arg: elab_value {:?} hoist level {:?}", + value, + hoist_level + ); + hoist_level + }) + .max() + .unwrap_or(self.loop_stack.len()); + trace!( + " -> loop hoist level: {:?}; cur loop depth: {:?}, loop_stack: {:?}", + loop_hoist_level, + self.loop_stack.len(), + self.loop_stack, + ); + + // We know that this is a pure inst, because + // non-pure roots have already been placed in the + // value-to-elab'd-value map and are never subject + // to remat, so they will not reach this stage of + // processing. + // + // We now must determine the location at which we + // place the instruction. This is the current + // block *unless* we hoist above a loop when all + // args are loop-invariant (and this op is pure). + let (scope_depth, before, insert_block) = + if loop_hoist_level == self.loop_stack.len() || remat { + // Depends on some value at the current + // loop depth, or remat forces it here: + // place it at the current location. + ( + self.value_to_elaborated_value.depth(), + before, + self.func.layout.inst_block(before).unwrap(), + ) + } else { + // Does not depend on any args at current + // loop depth: hoist out of loop. + self.stats.elaborate_licm_hoist += 1; + let data = &self.loop_stack[loop_hoist_level]; + // `data.hoist_block` should dominate `before`'s block. + let before_block = self.func.layout.inst_block(before).unwrap(); + debug_assert!(self.domtree.dominates( + data.hoist_block, + before_block, + &self.func.layout + )); + // Determine the instruction at which we + // insert in `data.hoist_block`. + let before = self.func.layout.last_inst(data.hoist_block).unwrap(); + (data.scope_depth as usize, before, data.hoist_block) + }; + + trace!( + " -> decided to place: before {} insert_block {}", + before, + insert_block + ); + + // Now we need to place `inst` at the computed + // location (just before `before`). Note that + // `inst` may already have been placed somewhere + // else, because a pure node may be elaborated at + // more than one place. In this case, we need to + // duplicate the instruction (and return the + // `Value`s for that duplicated instance + // instead). + trace!("need inst {} before {}", inst, before); + let inst = if self.func.layout.inst_block(inst).is_some() { + // Clone the inst! + let new_inst = self.func.dfg.clone_inst(inst); + trace!( + " -> inst {} already has a location; cloned to {}", + inst, + new_inst + ); + // Create mappings in the + // value-to-elab'd-value map from original + // results to cloned results. + for (&result, &new_result) in self + .func + .dfg + .inst_results(inst) + .iter() + .zip(self.func.dfg.inst_results(new_inst).iter()) + { + let elab_value = ElaboratedValue { + value: new_result, + in_block: insert_block, + }; + let canonical_result = self.eclasses.find_and_update(result); + self.value_to_elaborated_value.insert_if_absent_with_depth( + canonical_result, + elab_value, + scope_depth, + ); + + self.eclasses.add(new_result); + self.eclasses.union(result, new_result); + self.value_to_best_value[new_result] = self.value_to_best_value[result]; + + trace!( + " -> cloned inst has new result {} for orig {}", + new_result, + result + ); + } + new_inst + } else { + trace!(" -> no location; using original inst"); + // Create identity mappings from result values + // to themselves in this scope, since we're + // using the original inst. + for &result in self.func.dfg.inst_results(inst) { + let elab_value = ElaboratedValue { + value: result, + in_block: insert_block, + }; + let canonical_result = self.eclasses.find_and_update(result); + self.value_to_elaborated_value.insert_if_absent_with_depth( + canonical_result, + elab_value, + scope_depth, + ); + trace!(" -> inserting identity mapping for {}", result); + } + inst + }; + // Place the inst just before `before`. + self.func.layout.insert_inst(inst, before); + + // Update the inst's arguments. + self.func + .dfg + .overwrite_inst_values(inst, arg_values.into_iter().map(|ev| ev.value)); + + // Now that we've consumed the arg values, pop + // them off the stack. + self.elab_result_stack.truncate(arg_idx); + + // Push the requested result index of the + // instruction onto the elab-results stack. + self.elab_result_stack.push(ElaboratedValue { + in_block: insert_block, + value: self.func.dfg.inst_results(inst)[result_idx], + }); + } + } + } + } + + fn elaborate_block(&mut self, elab_values: &mut Vec, idom: Option, block: Block) { + trace!("elaborate_block: block {}", block); + self.start_block(idom, block); + + // Iterate over the side-effecting skeleton using the linked + // list in Layout. We will insert instructions that are + // elaborated *before* `inst`, so we can always use its + // next-link to continue the iteration. + let mut next_inst = self.func.layout.first_inst(block); + let mut first_branch = None; + while let Some(inst) = next_inst { + trace!( + "elaborating inst {} with results {:?}", + inst, + self.func.dfg.inst_results(inst) + ); + // Record the first branch we see in the block; all + // elaboration for args of *any* branch must be inserted + // before the *first* branch, because the branch group + // must remain contiguous at the end of the block. + if self.func.dfg.insts[inst].opcode().is_branch() && first_branch == None { + first_branch = Some(inst); + } + + // Determine where elaboration inserts insts. + let before = first_branch.unwrap_or(inst); + trace!(" -> inserting before {}", before); + + elab_values.extend(self.func.dfg.inst_values(inst)); + for arg in elab_values.iter_mut() { + trace!(" -> arg {}", *arg); + // Elaborate the arg, placing any newly-inserted insts + // before `before`. Get the updated value, which may + // be different than the original. + let new_arg = self.elaborate_eclass_use(*arg, before); + trace!(" -> rewrote arg to {:?}", new_arg); + *arg = new_arg.value; + } + self.func + .dfg + .overwrite_inst_values(inst, elab_values.drain(..)); + + // We need to put the results of this instruction in the + // map now. + for &result in self.func.dfg.inst_results(inst) { + trace!(" -> result {}", result); + let canonical_result = self.eclasses.find_and_update(result); + self.value_to_elaborated_value.insert_if_absent( + canonical_result, + ElaboratedValue { + in_block: block, + value: result, + }, + ); + } + + next_inst = self.func.layout.next_inst(inst); + } + } + + fn elaborate_domtree(&mut self, domtree: &DomTreeWithChildren) { + let root = domtree.root(); + self.block_stack.push(BlockStackEntry::Elaborate { + block: root, + idom: None, + }); + + // A temporary workspace for elaborate_block, allocated here to maximize the use of the + // allocation. + let mut elab_values = Vec::new(); + + while let Some(top) = self.block_stack.pop() { + match top { + BlockStackEntry::Elaborate { block, idom } => { + self.block_stack.push(BlockStackEntry::Pop); + self.value_to_elaborated_value.increment_depth(); + + self.elaborate_block(&mut elab_values, idom, block); + + // Push children. We are doing a preorder + // traversal so we do this after processing this + // block above. + let block_stack_end = self.block_stack.len(); + for child in domtree.children(block) { + self.block_stack.push(BlockStackEntry::Elaborate { + block: child, + idom: Some(block), + }); + } + // Reverse what we just pushed so we elaborate in + // original block order. (The domtree iter is a + // single-ended iter over a singly-linked list so + // we can't `.rev()` above.) + self.block_stack[block_stack_end..].reverse(); + } + BlockStackEntry::Pop => { + self.value_to_elaborated_value.decrement_depth(); + } + } + } + } + + pub(crate) fn elaborate(&mut self) { + self.stats.elaborate_func += 1; + self.stats.elaborate_func_pre_insts += self.func.dfg.num_insts() as u64; + self.compute_best_values(); + self.elaborate_domtree(&self.domtree_children); + self.stats.elaborate_func_post_insts += self.func.dfg.num_insts() as u64; + } +} diff --git a/cranelift/codegen/src/flowgraph.rs b/cranelift/codegen/src/flowgraph.rs index 9c6ccbaea542..fa62d3caebda 100644 --- a/cranelift/codegen/src/flowgraph.rs +++ b/cranelift/codegen/src/flowgraph.rs @@ -11,21 +11,18 @@ //! //! ... //! -//! brz vx, Block1 ; end of basic block +//! brif vx, Block1, Block2 ; end of basic block //! -//! ... ; beginning of basic block -//! -//! ... -//! -//! jmp Block2 ; end of basic block +//! Block1: +//! jump block3 //! ``` //! -//! Here `Block1` and `Block2` would each have a single predecessor denoted as `(Block0, brz)` -//! and `(Block0, jmp Block2)` respectively. +//! Here `Block1` and `Block2` would each have a single predecessor denoted as `(Block0, brif)`, +//! while `Block3` would have a single predecessor denoted as `(Block1, jump block3)`. use crate::bforest; use crate::entity::SecondaryMap; -use crate::ir::instructions::BranchInfo; +use crate::inst_predicates; use crate::ir::{Block, Function, Inst}; use crate::timing; use core::mem; @@ -120,22 +117,9 @@ impl ControlFlowGraph { } fn compute_block(&mut self, func: &Function, block: Block) { - for inst in func.layout.block_likely_branches(block) { - match func.dfg.analyze_branch(inst) { - BranchInfo::SingleDest(dest, _) => { - self.add_edge(block, inst, dest); - } - BranchInfo::Table(jt, dest) => { - if let Some(dest) = dest { - self.add_edge(block, inst, dest); - } - for dest in func.jump_tables[jt].iter() { - self.add_edge(block, inst, *dest); - } - } - BranchInfo::NotABranch => {} - } - } + inst_predicates::visit_block_succs(func, block, |inst, dest, _| { + self.add_edge(block, inst, dest); + }); } fn invalidate_block_successors(&mut self, block: Block) { @@ -250,21 +234,17 @@ mod tests { let block1 = func.dfg.make_block(); let block2 = func.dfg.make_block(); - let br_block0_block2; - let br_block1_block1; - let jmp_block0_block1; - let jmp_block1_block2; + let br_block0_block2_block1; + let br_block1_block1_block2; { let mut cur = FuncCursor::new(&mut func); cur.insert_block(block0); - br_block0_block2 = cur.ins().brnz(cond, block2, &[]); - jmp_block0_block1 = cur.ins().jump(block1, &[]); + br_block0_block2_block1 = cur.ins().brif(cond, block2, &[], block1, &[]); cur.insert_block(block1); - br_block1_block1 = cur.ins().brnz(cond, block1, &[]); - jmp_block1_block2 = cur.ins().jump(block2, &[]); + br_block1_block1_block2 = cur.ins().brif(cond, block1, &[], block2, &[]); cur.insert_block(block2); } @@ -285,19 +265,23 @@ mod tests { assert_eq!(block2_predecessors.len(), 2); assert_eq!( - block1_predecessors.contains(&BlockPredecessor::new(block0, jmp_block0_block1)), + block1_predecessors + .contains(&BlockPredecessor::new(block0, br_block0_block2_block1)), true ); assert_eq!( - block1_predecessors.contains(&BlockPredecessor::new(block1, br_block1_block1)), + block1_predecessors + .contains(&BlockPredecessor::new(block1, br_block1_block1_block2)), true ); assert_eq!( - block2_predecessors.contains(&BlockPredecessor::new(block0, br_block0_block2)), + block2_predecessors + .contains(&BlockPredecessor::new(block0, br_block0_block2_block1)), true ); assert_eq!( - block2_predecessors.contains(&BlockPredecessor::new(block1, jmp_block1_block2)), + block2_predecessors + .contains(&BlockPredecessor::new(block1, br_block1_block1_block2)), true ); @@ -306,11 +290,22 @@ mod tests { assert_eq!(block2_successors, []); } - // Change some instructions and recompute block0 - func.dfg.replace(br_block0_block2).brnz(cond, block1, &[]); - func.dfg.replace(jmp_block0_block1).return_(&[]); + // Add a new block to hold a return instruction + let ret_block = func.dfg.make_block(); + + { + let mut cur = FuncCursor::new(&mut func); + cur.insert_block(ret_block); + cur.ins().return_(&[]); + } + + // Change some instructions and recompute block0 and ret_block + func.dfg + .replace(br_block0_block2_block1) + .brif(cond, block1, &[], ret_block, &[]); cfg.recompute_block(&mut func, block0); - let br_block0_block1 = br_block0_block2; + cfg.recompute_block(&mut func, ret_block); + let br_block0_block1_ret_block = br_block0_block2_block1; { let block0_predecessors = cfg.pred_iter(block0).collect::>(); @@ -326,23 +321,27 @@ mod tests { assert_eq!(block2_predecessors.len(), 1); assert_eq!( - block1_predecessors.contains(&BlockPredecessor::new(block0, br_block0_block1)), + block1_predecessors + .contains(&BlockPredecessor::new(block0, br_block0_block1_ret_block)), true ); assert_eq!( - block1_predecessors.contains(&BlockPredecessor::new(block1, br_block1_block1)), + block1_predecessors + .contains(&BlockPredecessor::new(block1, br_block1_block1_block2)), true ); assert_eq!( - block2_predecessors.contains(&BlockPredecessor::new(block0, br_block0_block2)), + block2_predecessors + .contains(&BlockPredecessor::new(block0, br_block0_block1_ret_block)), false ); assert_eq!( - block2_predecessors.contains(&BlockPredecessor::new(block1, jmp_block1_block2)), + block2_predecessors + .contains(&BlockPredecessor::new(block1, br_block1_block1_block2)), true ); - assert_eq!(block0_successors.collect::>(), [block1]); + assert_eq!(block0_successors.collect::>(), [block1, ret_block]); assert_eq!(block1_successors.collect::>(), [block1, block2]); assert_eq!(block2_successors.collect::>(), []); } diff --git a/cranelift/codegen/src/incremental_cache.rs b/cranelift/codegen/src/incremental_cache.rs new file mode 100644 index 000000000000..61702bd77623 --- /dev/null +++ b/cranelift/codegen/src/incremental_cache.rs @@ -0,0 +1,254 @@ +//! This module provides a set of primitives that allow implementing an incremental cache on top of +//! Cranelift, making it possible to reuse previous compiled artifacts for functions that have been +//! compiled previously. +//! +//! This set of operation is experimental and can be enabled using the Cargo feature +//! `incremental-cache`. +//! +//! This can bring speedups in different cases: change-code-and-immediately-recompile iterations +//! get faster, modules sharing lots of code can reuse each other's artifacts, etc. +//! +//! The three main primitives are the following: +//! - `compute_cache_key` is used to compute the cache key associated to a `Function`. This is +//! basically the content of the function, modulo a few things the caching system is resilient to. +//! - `serialize_compiled` is used to serialize the result of a compilation, so it can be reused +//! later on by... +//! - `try_finish_recompile`, which reads binary blobs serialized with `serialize_compiled`, +//! re-creating the compilation artifact from those. +//! +//! The `CacheStore` trait and `Context::compile_with_cache` method are provided as +//! high-level, easy-to-use facilities to make use of that cache, and show an example of how to use +//! the above three primitives to form a full incremental caching system. + +use core::fmt; + +use crate::alloc::string::String; +use crate::alloc::vec::Vec; +use crate::ir::function::{FunctionStencil, VersionMarker}; +use crate::ir::Function; +use crate::machinst::{CompiledCode, CompiledCodeStencil}; +use crate::result::CompileResult; +use crate::{isa::TargetIsa, timing}; +use crate::{trace, CompileError, Context}; +use alloc::borrow::{Cow, ToOwned as _}; +use alloc::string::ToString as _; + +impl Context { + /// Compile the function, as in `compile`, but tries to reuse compiled artifacts from former + /// compilations using the provided cache store. + pub fn compile_with_cache( + &mut self, + isa: &dyn TargetIsa, + cache_store: &mut dyn CacheKvStore, + ) -> CompileResult<(&CompiledCode, bool)> { + let cache_key_hash = { + let _tt = timing::try_incremental_cache(); + + let cache_key_hash = compute_cache_key(isa, &mut self.func); + + if let Some(blob) = cache_store.get(&cache_key_hash.0) { + match try_finish_recompile(&self.func, &blob) { + Ok(compiled_code) => { + let info = compiled_code.code_info(); + + if isa.flags().enable_incremental_compilation_cache_checks() { + let actual_result = self.compile(isa)?; + assert_eq!(*actual_result, compiled_code); + assert_eq!(actual_result.code_info(), info); + // no need to set `compiled_code` here, it's set by `compile()`. + return Ok((actual_result, true)); + } + + let compiled_code = self.compiled_code.insert(compiled_code); + return Ok((compiled_code, true)); + } + Err(err) => { + trace!("error when finishing recompilation: {err}"); + } + } + } + + cache_key_hash + }; + + let stencil = self.compile_stencil(isa).map_err(|err| CompileError { + inner: err, + func: &self.func, + })?; + + let stencil = { + let _tt = timing::store_incremental_cache(); + let (stencil, res) = serialize_compiled(stencil); + if let Ok(blob) = res { + cache_store.insert(&cache_key_hash.0, blob); + } + stencil + }; + + let compiled_code = self + .compiled_code + .insert(stencil.apply_params(&self.func.params)); + + Ok((compiled_code, false)) + } +} + +/// Backing storage for an incremental compilation cache, when enabled. +pub trait CacheKvStore { + /// Given a cache key hash, retrieves the associated opaque serialized data. + fn get(&self, key: &[u8]) -> Option>; + + /// Given a new cache key and a serialized blob obtained from `serialize_compiled`, stores it + /// in the cache store. + fn insert(&mut self, key: &[u8], val: Vec); +} + +/// Hashed `CachedKey`, to use as an identifier when looking up whether a function has already been +/// compiled or not. +#[derive(Clone, Hash, PartialEq, Eq)] +pub struct CacheKeyHash([u8; 32]); + +impl std::fmt::Display for CacheKeyHash { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "CacheKeyHash:{:?}", self.0) + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +struct CachedFunc { + stencil: CompiledCodeStencil, + version_marker: VersionMarker, +} + +/// Key for caching a single function's compilation. +/// +/// If two functions get the same `CacheKey`, then we can reuse the compiled artifacts, modulo some +/// fixups. +/// +/// Note: the key will be invalidated across different versions of cranelift, as the +/// `FunctionStencil` contains a `VersionMarker` itself. +#[derive(Hash)] +struct CacheKey<'a> { + stencil: &'a FunctionStencil, + parameters: CompileParameters, +} + +#[derive(Clone, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +struct CompileParameters { + isa: String, + triple: String, + flags: String, + isa_flags: Vec, +} + +impl CompileParameters { + fn from_isa(isa: &dyn TargetIsa) -> Self { + Self { + isa: isa.name().to_owned(), + triple: isa.triple().to_string(), + flags: isa.flags().to_string(), + isa_flags: isa + .isa_flags() + .into_iter() + .map(|v| v.value_string()) + .collect(), + } + } +} + +impl<'a> CacheKey<'a> { + /// Creates a new cache store key for a function. + /// + /// This is a bit expensive to compute, so it should be cached and reused as much as possible. + fn new(isa: &dyn TargetIsa, f: &'a mut Function) -> Self { + // Make sure the blocks and instructions are sequenced the same way as we might + // have serialized them earlier. This is the symmetric of what's done in + // `try_load`. + f.stencil.layout.full_renumber(); + CacheKey { + stencil: &f.stencil, + parameters: CompileParameters::from_isa(isa), + } + } +} + +/// Compute a cache key, and hash it on your behalf. +/// +/// Since computing the `CacheKey` is a bit expensive, it should be done as least as possible. +pub fn compute_cache_key(isa: &dyn TargetIsa, func: &mut Function) -> CacheKeyHash { + use core::hash::{Hash as _, Hasher}; + use sha2::Digest as _; + + struct Sha256Hasher(sha2::Sha256); + + impl Hasher for Sha256Hasher { + fn finish(&self) -> u64 { + panic!("Sha256Hasher doesn't support finish!"); + } + fn write(&mut self, bytes: &[u8]) { + self.0.update(bytes); + } + } + + let cache_key = CacheKey::new(isa, func); + + let mut hasher = Sha256Hasher(sha2::Sha256::new()); + cache_key.hash(&mut hasher); + let hash: [u8; 32] = hasher.0.finalize().into(); + + CacheKeyHash(hash) +} + +/// Given a function that's been successfully compiled, serialize it to a blob that the caller may +/// store somewhere for future use by `try_finish_recompile`. +/// +/// As this function requires ownership on the `CompiledCodeStencil`, it gives it back at the end +/// of the function call. The value is left untouched. +pub fn serialize_compiled( + result: CompiledCodeStencil, +) -> (CompiledCodeStencil, Result, bincode::Error>) { + let cached = CachedFunc { + stencil: result, + version_marker: VersionMarker, + }; + let result = bincode::serialize(&cached); + (cached.stencil, result) +} + +/// An error returned when recompiling failed. +#[derive(Debug)] +pub enum RecompileError { + /// The version embedded in the cache entry isn't the same as cranelift's current version. + VersionMismatch, + /// An error occurred while deserializing the cache entry. + Deserialize(bincode::Error), +} + +impl fmt::Display for RecompileError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RecompileError::VersionMismatch => write!(f, "cranelift version mismatch",), + RecompileError::Deserialize(err) => { + write!(f, "bincode failed during deserialization: {err}") + } + } + } +} + +/// Given a function that's been precompiled and its entry in the caching storage, try to shortcut +/// compilation of the given function. +/// +/// Precondition: the bytes must have retrieved from a cache store entry which hash value +/// is strictly the same as the `Function`'s computed hash retrieved from `compute_cache_key`. +pub fn try_finish_recompile(func: &Function, bytes: &[u8]) -> Result { + match bincode::deserialize::(bytes) { + Ok(result) => { + if result.version_marker != func.stencil.version_marker { + Err(RecompileError::VersionMismatch) + } else { + Ok(result.stencil.apply_params(&func.params)) + } + } + Err(err) => Err(RecompileError::Deserialize(err)), + } +} diff --git a/cranelift/codegen/src/inst_predicates.rs b/cranelift/codegen/src/inst_predicates.rs index 8d36742979ce..e6bc6e666452 100644 --- a/cranelift/codegen/src/inst_predicates.rs +++ b/cranelift/codegen/src/inst_predicates.rs @@ -1,8 +1,6 @@ //! Instruction predicates/properties, shared by various analyses. use crate::ir::immediates::Offset32; -use crate::ir::instructions::BranchInfo; -use crate::ir::{Block, DataFlowGraph, Function, Inst, InstructionData, Opcode, Type, Value}; -use crate::machinst::ty_bits; +use crate::ir::{self, Block, DataFlowGraph, Function, Inst, InstructionData, Opcode, Type, Value}; use cranelift_entity::EntityRef; /// Preserve instructions with used result values. @@ -11,6 +9,7 @@ pub fn any_inst_results_used(inst: Inst, live: &[bool], dfg: &DataFlowGraph) -> } /// Test whether the given opcode is unsafe to even consider as side-effect-free. +#[inline(always)] fn trivially_has_side_effects(opcode: Opcode) -> bool { opcode.is_call() || opcode.is_branch() @@ -24,6 +23,7 @@ fn trivially_has_side_effects(opcode: Opcode) -> bool { /// Load instructions without the `notrap` flag are defined to trap when /// operating on inaccessible memory, so we can't treat them as side-effect-free even if the loaded /// value is unused. +#[inline(always)] fn is_load_with_defined_trapping(opcode: Opcode, data: &InstructionData) -> bool { if !opcode.can_load() { return false; @@ -37,23 +37,72 @@ fn is_load_with_defined_trapping(opcode: Opcode, data: &InstructionData) -> bool /// Does the given instruction have any side-effect that would preclude it from being removed when /// its value is unused? +#[inline(always)] pub fn has_side_effect(func: &Function, inst: Inst) -> bool { - let data = &func.dfg[inst]; + let data = &func.dfg.insts[inst]; let opcode = data.opcode(); trivially_has_side_effects(opcode) || is_load_with_defined_trapping(opcode, data) } +/// Does the given instruction behave as a "pure" node with respect to +/// aegraph semantics? +/// +/// - Actual pure nodes (arithmetic, etc) +/// - Loads with the `readonly` flag set +pub fn is_pure_for_egraph(func: &Function, inst: Inst) -> bool { + let is_readonly_load = match func.dfg.insts[inst] { + InstructionData::Load { + opcode: Opcode::Load, + flags, + .. + } => flags.readonly() && flags.notrap(), + _ => false, + }; + // Multi-value results do not play nicely with much of the egraph + // infrastructure. They are in practice used only for multi-return + // calls and some other odd instructions (e.g. iadd_cout) which, + // for now, we can afford to leave in place as opaque + // side-effecting ops. So if more than one result, then the inst + // is "not pure". Similarly, ops with zero results can be used + // only for their side-effects, so are never pure. (Or if they + // are, we can always trivially eliminate them with no effect.) + let has_one_result = func.dfg.inst_results(inst).len() == 1; + + let op = func.dfg.insts[inst].opcode(); + + has_one_result && (is_readonly_load || (!op.can_load() && !trivially_has_side_effects(op))) +} + +/// Can the given instruction be merged into another copy of itself? +/// These instructions may have side-effects, but as long as we retain +/// the first instance of the instruction, the second and further +/// instances are redundant if they would produce the same trap or +/// result. +pub fn is_mergeable_for_egraph(func: &Function, inst: Inst) -> bool { + let op = func.dfg.insts[inst].opcode(); + // We can only merge one-result operators due to the way that GVN + // is structured in the egraph implementation. + let has_one_result = func.dfg.inst_results(inst).len() == 1; + has_one_result + // Loads/stores are handled by alias analysis and not + // otherwise mergeable. + && !op.can_load() + && !op.can_store() + // Can only have idempotent side-effects. + && (!has_side_effect(func, inst) || op.side_effects_idempotent()) +} + /// Does the given instruction have any side-effect as per [has_side_effect], or else is a load, /// but not the get_pinned_reg opcode? pub fn has_lowering_side_effect(func: &Function, inst: Inst) -> bool { - let op = func.dfg[inst].opcode(); + let op = func.dfg.insts[inst].opcode(); op != Opcode::GetPinnedReg && (has_side_effect(func, inst) || op.can_load()) } -/// Is the given instruction a constant value (`iconst`, `fconst`, `bconst`) that can be +/// Is the given instruction a constant value (`iconst`, `fconst`) that can be /// represented in 64 bits? pub fn is_constant_64bit(func: &Function, inst: Inst) -> Option { - let data = &func.dfg[inst]; + let data = &func.dfg.insts[inst]; if data.opcode() == Opcode::Null { return Some(0); } @@ -61,28 +110,13 @@ pub fn is_constant_64bit(func: &Function, inst: Inst) -> Option { &InstructionData::UnaryImm { imm, .. } => Some(imm.bits() as u64), &InstructionData::UnaryIeee32 { imm, .. } => Some(imm.bits() as u64), &InstructionData::UnaryIeee64 { imm, .. } => Some(imm.bits()), - &InstructionData::UnaryBool { imm, .. } => { - let imm = if imm { - let bits = ty_bits(func.dfg.value_type(func.dfg.inst_results(inst)[0])); - - if bits < 64 { - (1u64 << bits) - 1 - } else { - u64::MAX - } - } else { - 0 - }; - - Some(imm) - } _ => None, } } /// Get the address, offset, and access type from the given instruction, if any. pub fn inst_addr_offset_type(func: &Function, inst: Inst) -> Option<(Value, Offset32, Type)> { - let data = &func.dfg[inst]; + let data = &func.dfg.insts[inst]; match data { InstructionData::Load { arg, offset, .. } => { let ty = func.dfg.value_type(func.dfg.inst_results(inst)[0]); @@ -106,7 +140,7 @@ pub fn inst_addr_offset_type(func: &Function, inst: Inst) -> Option<(Value, Offs /// Get the store data, if any, from an instruction. pub fn inst_store_data(func: &Function, inst: Inst) -> Option { - let data = &func.dfg[inst]; + let data = &func.dfg.insts[inst]; match data { InstructionData::Store { args, .. } | InstructionData::StoreNoOffset { args, .. } => { Some(args[0]) @@ -123,34 +157,54 @@ pub fn has_memory_fence_semantics(op: Opcode) -> bool { | Opcode::AtomicCas | Opcode::AtomicLoad | Opcode::AtomicStore - | Opcode::Fence => true, + | Opcode::Fence + | Opcode::Debugtrap => true, Opcode::Call | Opcode::CallIndirect => true, + op if op.can_trap() => true, _ => false, } } -/// Visit all successors of a block with a given visitor closure. -pub(crate) fn visit_block_succs(f: &Function, block: Block, mut visit: F) { - for inst in f.layout.block_likely_branches(block) { - if f.dfg[inst].opcode().is_branch() { - visit_branch_targets(f, inst, &mut visit); - } - } -} +/// Visit all successors of a block with a given visitor closure. The closure +/// arguments are the branch instruction that is used to reach the successor, +/// the successor block itself, and a flag indicating whether the block is +/// branched to via a table entry. +pub(crate) fn visit_block_succs( + f: &Function, + block: Block, + mut visit: F, +) { + if let Some(inst) = f.layout.last_inst(block) { + match &f.dfg.insts[inst] { + ir::InstructionData::Jump { + destination: dest, .. + } => { + visit(inst, dest.block(&f.dfg.value_lists), false); + } -fn visit_branch_targets(f: &Function, inst: Inst, visit: &mut F) { - match f.dfg[inst].analyze_branch(&f.dfg.value_lists) { - BranchInfo::NotABranch => {} - BranchInfo::SingleDest(dest, _) => { - visit(inst, dest); - } - BranchInfo::Table(table, maybe_dest) => { - if let Some(dest) = maybe_dest { - visit(inst, dest); + ir::InstructionData::Brif { + blocks: [block_then, block_else], + .. + } => { + visit(inst, block_then.block(&f.dfg.value_lists), false); + visit(inst, block_else.block(&f.dfg.value_lists), false); } - for &dest in f.jump_tables[table].as_slice() { - visit(inst, dest); + + ir::InstructionData::BranchTable { table, .. } => { + let table = &f.stencil.dfg.jump_tables[*table]; + + // The default block is reached via a direct conditional branch, + // so it is not part of the table. We visit the default block first + // explicitly, as some callers of visit_block_succs depend on that + // ordering. + visit(inst, table.default_block(), false); + + for &dest in table.as_slice() { + visit(inst, dest, true); + } } + + inst => debug_assert!(!inst.opcode().is_branch()), } } } diff --git a/cranelift/codegen/src/ir/builder.rs b/cranelift/codegen/src/ir/builder.rs index 3191f9dae159..e4c434cbf3d6 100644 --- a/cranelift/codegen/src/ir/builder.rs +++ b/cranelift/codegen/src/ir/builder.rs @@ -4,6 +4,7 @@ //! function. Many of its methods are generated from the meta language instruction definitions. use crate::ir; +use crate::ir::instructions::InstructionFormat; use crate::ir::types; use crate::ir::{DataFlowGraph, InstructionData}; use crate::ir::{Inst, Opcode, Type, Value}; @@ -200,7 +201,7 @@ impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { fn build(self, data: InstructionData, ctrl_typevar: Type) -> (Inst, &'f mut DataFlowGraph) { // Splat the new instruction on top of the old one. - self.dfg[self.inst] = data; + self.dfg.insts[self.inst] = data; if !self.dfg.has_results(self.inst) { // The old result values were either detached or non-existent. @@ -217,7 +218,7 @@ mod tests { use crate::cursor::{Cursor, FuncCursor}; use crate::ir::condcodes::*; use crate::ir::types::*; - use crate::ir::{Function, InstBuilder, ValueDef}; + use crate::ir::{Function, InstBuilder, Opcode, TrapCode, ValueDef}; #[test] fn types() { @@ -237,7 +238,7 @@ mod tests { // Formula. let cmp = pos.ins().icmp(IntCC::Equal, arg0, v0); - assert_eq!(pos.func.dfg.value_type(cmp), B1); + assert_eq!(pos.func.dfg.value_type(cmp), I8); } #[test] @@ -262,4 +263,17 @@ mod tests { assert!(iadd != iconst); assert_eq!(pos.func.dfg.value_def(v0), ValueDef::Result(iconst, 0)); } + + #[test] + #[should_panic] + fn panics_when_inserting_wrong_opcode() { + let mut func = Function::new(); + let block0 = func.dfg.make_block(); + let mut pos = FuncCursor::new(&mut func); + pos.insert_block(block0); + + // We are trying to create a Opcode::Return with the InstData::Trap, which is obviously wrong + pos.ins() + .Trap(Opcode::Return, I32, TrapCode::BadConversionToInteger); + } } diff --git a/cranelift/codegen/src/ir/condcodes.rs b/cranelift/codegen/src/ir/condcodes.rs index 00e9717ca0f8..7059ce6c92b4 100644 --- a/cranelift/codegen/src/ir/condcodes.rs +++ b/cranelift/codegen/src/ir/condcodes.rs @@ -55,10 +55,6 @@ pub enum IntCC { UnsignedGreaterThan, /// Unsigned `<=`. UnsignedLessThanOrEqual, - /// Signed Overflow. - Overflow, - /// Signed No Overflow. - NotOverflow, } impl CondCode for IntCC { @@ -75,8 +71,6 @@ impl CondCode for IntCC { UnsignedGreaterThanOrEqual => UnsignedLessThan, UnsignedGreaterThan => UnsignedLessThanOrEqual, UnsignedLessThanOrEqual => UnsignedGreaterThan, - Overflow => NotOverflow, - NotOverflow => Overflow, } } @@ -93,13 +87,27 @@ impl CondCode for IntCC { UnsignedGreaterThanOrEqual => UnsignedLessThanOrEqual, UnsignedLessThan => UnsignedGreaterThan, UnsignedLessThanOrEqual => UnsignedGreaterThanOrEqual, - Overflow => Overflow, - NotOverflow => NotOverflow, } } } impl IntCC { + /// Returns a slice with all possible [IntCC] values. + pub fn all() -> &'static [IntCC] { + &[ + IntCC::Equal, + IntCC::NotEqual, + IntCC::SignedLessThan, + IntCC::SignedGreaterThanOrEqual, + IntCC::SignedGreaterThan, + IntCC::SignedLessThanOrEqual, + IntCC::UnsignedLessThan, + IntCC::UnsignedGreaterThanOrEqual, + IntCC::UnsignedGreaterThan, + IntCC::UnsignedLessThanOrEqual, + ] + } + /// Get the corresponding IntCC with the equal component removed. /// For conditions without a zero component, this is a no-op. pub fn without_equal(self) -> Self { @@ -140,8 +148,6 @@ impl IntCC { UnsignedGreaterThanOrEqual => "uge", UnsignedLessThan => "ult", UnsignedLessThanOrEqual => "ule", - Overflow => "of", - NotOverflow => "nof", } } } @@ -168,8 +174,6 @@ impl FromStr for IntCC { "ugt" => Ok(UnsignedGreaterThan), "ule" => Ok(UnsignedLessThanOrEqual), "ult" => Ok(UnsignedLessThan), - "of" => Ok(Overflow), - "nof" => Ok(NotOverflow), _ => Err(()), } } @@ -227,6 +231,28 @@ pub enum FloatCC { UnorderedOrGreaterThanOrEqual, } +impl FloatCC { + /// Returns a slice with all possible [FloatCC] values. + pub fn all() -> &'static [FloatCC] { + &[ + FloatCC::Ordered, + FloatCC::Unordered, + FloatCC::Equal, + FloatCC::NotEqual, + FloatCC::OrderedNotEqual, + FloatCC::UnorderedOrEqual, + FloatCC::LessThan, + FloatCC::LessThanOrEqual, + FloatCC::GreaterThan, + FloatCC::GreaterThanOrEqual, + FloatCC::UnorderedOrLessThan, + FloatCC::UnorderedOrLessThanOrEqual, + FloatCC::UnorderedOrGreaterThan, + FloatCC::UnorderedOrGreaterThanOrEqual, + ] + } +} + impl CondCode for FloatCC { fn inverse(self) -> Self { use self::FloatCC::*; @@ -320,24 +346,9 @@ mod tests { use super::*; use std::string::ToString; - static INT_ALL: [IntCC; 12] = [ - IntCC::Equal, - IntCC::NotEqual, - IntCC::SignedLessThan, - IntCC::SignedGreaterThanOrEqual, - IntCC::SignedGreaterThan, - IntCC::SignedLessThanOrEqual, - IntCC::UnsignedLessThan, - IntCC::UnsignedGreaterThanOrEqual, - IntCC::UnsignedGreaterThan, - IntCC::UnsignedLessThanOrEqual, - IntCC::Overflow, - IntCC::NotOverflow, - ]; - #[test] fn int_inverse() { - for r in &INT_ALL { + for r in IntCC::all() { let cc = *r; let inv = cc.inverse(); assert!(cc != inv); @@ -347,7 +358,7 @@ mod tests { #[test] fn int_reverse() { - for r in &INT_ALL { + for r in IntCC::all() { let cc = *r; let rev = cc.reverse(); assert_eq!(rev.reverse(), cc); @@ -356,33 +367,16 @@ mod tests { #[test] fn int_display() { - for r in &INT_ALL { + for r in IntCC::all() { let cc = *r; assert_eq!(cc.to_string().parse(), Ok(cc)); } assert_eq!("bogus".parse::(), Err(())); } - static FLOAT_ALL: [FloatCC; 14] = [ - FloatCC::Ordered, - FloatCC::Unordered, - FloatCC::Equal, - FloatCC::NotEqual, - FloatCC::OrderedNotEqual, - FloatCC::UnorderedOrEqual, - FloatCC::LessThan, - FloatCC::LessThanOrEqual, - FloatCC::GreaterThan, - FloatCC::GreaterThanOrEqual, - FloatCC::UnorderedOrLessThan, - FloatCC::UnorderedOrLessThanOrEqual, - FloatCC::UnorderedOrGreaterThan, - FloatCC::UnorderedOrGreaterThanOrEqual, - ]; - #[test] fn float_inverse() { - for r in &FLOAT_ALL { + for r in FloatCC::all() { let cc = *r; let inv = cc.inverse(); assert!(cc != inv); @@ -392,7 +386,7 @@ mod tests { #[test] fn float_reverse() { - for r in &FLOAT_ALL { + for r in FloatCC::all() { let cc = *r; let rev = cc.reverse(); assert_eq!(rev.reverse(), cc); @@ -401,7 +395,7 @@ mod tests { #[test] fn float_display() { - for r in &FLOAT_ALL { + for r in FloatCC::all() { let cc = *r; assert_eq!(cc.to_string().parse(), Ok(cc)); } diff --git a/cranelift/codegen/src/ir/constant.rs b/cranelift/codegen/src/ir/constant.rs index 3cd88d554618..1c540c0c37f7 100644 --- a/cranelift/codegen/src/ir/constant.rs +++ b/cranelift/codegen/src/ir/constant.rs @@ -10,7 +10,6 @@ use crate::ir::immediates::{IntoBytes, V128Imm}; use crate::ir::Constant; -use crate::HashMap; use alloc::collections::BTreeMap; use alloc::vec::Vec; use core::fmt; @@ -27,7 +26,7 @@ use serde::{Deserialize, Serialize}; /// WebAssembly values, which are [little-endian by design]. /// /// [little-endian by design]: https://github.com/WebAssembly/design/blob/master/Portability.md -#[derive(Clone, Hash, Eq, PartialEq, Debug, Default)] +#[derive(Clone, Hash, Eq, PartialEq, Debug, Default, PartialOrd, Ord)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct ConstantData(Vec); @@ -169,16 +168,20 @@ impl FromStr for ConstantData { /// Maintains the mapping between a constant handle (i.e. [`Constant`](crate::ir::Constant)) and /// its constant data (i.e. [`ConstantData`](crate::ir::ConstantData)). -#[derive(Clone)] +#[derive(Clone, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct ConstantPool { /// This mapping maintains the insertion order as long as Constants are created with /// sequentially increasing integers. + /// + /// It is important that, by construction, no entry in that list gets removed. If that ever + /// need to happen, don't forget to update the `Constant` generation scheme. handles_to_values: BTreeMap, - /// This mapping is unordered (no need for lexicographic ordering) but allows us to map - /// constant data back to handles. - values_to_handles: HashMap, + /// Mapping of hashed `ConstantData` to the index into the other hashmap. + /// + /// This allows for deduplication of entries into the `handles_to_values` mapping. + values_to_handles: BTreeMap, } impl ConstantPool { @@ -186,7 +189,7 @@ impl ConstantPool { pub fn new() -> Self { Self { handles_to_values: BTreeMap::new(), - values_to_handles: HashMap::new(), + values_to_handles: BTreeMap::new(), } } @@ -200,13 +203,13 @@ impl ConstantPool { /// data is inserted that is a duplicate of previous constant data, the existing handle will be /// returned. pub fn insert(&mut self, constant_value: ConstantData) -> Constant { - if self.values_to_handles.contains_key(&constant_value) { - *self.values_to_handles.get(&constant_value).unwrap() - } else { - let constant_handle = Constant::new(self.len()); - self.set(constant_handle, constant_value); - constant_handle + if let Some(cst) = self.values_to_handles.get(&constant_value) { + return *cst; } + + let constant_handle = Constant::new(self.len()); + self.set(constant_handle, constant_value); + constant_handle } /// Retrieve the constant data given a handle. @@ -250,7 +253,7 @@ impl ConstantPool { /// Return the combined size of all of the constant values in the pool. pub fn byte_size(&self) -> usize { - self.values_to_handles.keys().map(|c| c.len()).sum() + self.handles_to_values.values().map(|c| c.len()).sum() } } diff --git a/cranelift/codegen/src/ir/dfg.rs b/cranelift/codegen/src/ir/dfg.rs index 65b97cbb7156..6cf83706f013 100644 --- a/cranelift/codegen/src/ir/dfg.rs +++ b/cranelift/codegen/src/ir/dfg.rs @@ -4,24 +4,84 @@ use crate::entity::{self, PrimaryMap, SecondaryMap}; use crate::ir; use crate::ir::builder::ReplaceBuilder; use crate::ir::dynamic_type::{DynamicTypeData, DynamicTypes}; -use crate::ir::extfunc::ExtFuncData; -use crate::ir::instructions::{BranchInfo, CallInfo, InstructionData}; -use crate::ir::{types, ConstantData, ConstantPool, Immediate}; +use crate::ir::instructions::{CallInfo, InstructionData}; use crate::ir::{ - Block, DynamicType, FuncRef, Inst, SigRef, Signature, SourceLoc, Type, Value, + types, Block, BlockCall, ConstantData, ConstantPool, DynamicType, ExtFuncData, FuncRef, + Immediate, Inst, JumpTables, RelSourceLoc, SigRef, Signature, Type, Value, ValueLabelAssignments, ValueList, ValueListPool, }; use crate::packed_option::ReservedValue; use crate::write::write_operands; -use crate::HashMap; use core::fmt; use core::iter; use core::mem; use core::ops::{Index, IndexMut}; use core::u16; +use alloc::collections::BTreeMap; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; +use smallvec::SmallVec; + +/// Storage for instructions within the DFG. +#[derive(Clone, PartialEq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct Insts(PrimaryMap); + +/// Allow immutable access to instructions via indexing. +impl Index for Insts { + type Output = InstructionData; + + fn index(&self, inst: Inst) -> &InstructionData { + self.0.index(inst) + } +} + +/// Allow mutable access to instructions via indexing. +impl IndexMut for Insts { + fn index_mut(&mut self, inst: Inst) -> &mut InstructionData { + self.0.index_mut(inst) + } +} + +/// Storage for basic blocks within the DFG. +#[derive(Clone, PartialEq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct Blocks(PrimaryMap); + +impl Blocks { + /// Create a new basic block. + pub fn add(&mut self) -> Block { + self.0.push(BlockData::new()) + } + + /// Get the total number of basic blocks created in this function, whether they are + /// currently inserted in the layout or not. + /// + /// This is intended for use with `SecondaryMap::with_capacity`. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if the given block reference is valid. + pub fn is_valid(&self, block: Block) -> bool { + self.0.is_valid(block) + } +} + +impl Index for Blocks { + type Output = BlockData; + + fn index(&self, block: Block) -> &BlockData { + &self.0[block] + } +} + +impl IndexMut for Blocks { + fn index_mut(&mut self, block: Block) -> &mut BlockData { + &mut self.0[block] + } +} /// A data flow graph defines all instructions and basic blocks in a function as well as /// the data flow dependencies between them. The DFG also tracks values which can be either @@ -30,13 +90,13 @@ use serde::{Deserialize, Serialize}; /// The layout of blocks in the function and of instructions in each block is recorded by the /// `Layout` data structure which forms the other half of the function representation. /// -#[derive(Clone)] +#[derive(Clone, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct DataFlowGraph { /// Data about all of the instructions in the function, including opcodes and operands. /// The instructions in this map are not in program order. That is tracked by `Layout`, along /// with the block containing each instruction. - insts: PrimaryMap, + pub insts: Insts, /// List of result values for each instruction. /// @@ -48,7 +108,7 @@ pub struct DataFlowGraph { /// /// This map is not in program order. That is handled by `Layout`, and so is the sequence of /// instructions contained in each block. - blocks: PrimaryMap, + pub blocks: Blocks, /// Dynamic types created. pub dynamic_types: DynamicTypes, @@ -76,22 +136,25 @@ pub struct DataFlowGraph { pub ext_funcs: PrimaryMap, /// Saves Value labels. - pub values_labels: Option>, + pub values_labels: Option>, /// Constants used within the function pub constants: ConstantPool, /// Stores large immediates that otherwise will not fit on InstructionData pub immediates: PrimaryMap, + + /// Jump tables used in this function. + pub jump_tables: JumpTables, } impl DataFlowGraph { /// Create a new empty `DataFlowGraph`. pub fn new() -> Self { Self { - insts: PrimaryMap::new(), + insts: Insts(PrimaryMap::new()), results: SecondaryMap::new(), - blocks: PrimaryMap::new(), + blocks: Blocks(PrimaryMap::new()), dynamic_types: DynamicTypes::new(), value_lists: ValueListPool::new(), values: PrimaryMap::new(), @@ -101,14 +164,15 @@ impl DataFlowGraph { values_labels: None, constants: ConstantPool::new(), immediates: PrimaryMap::new(), + jump_tables: JumpTables::new(), } } /// Clear everything. pub fn clear(&mut self) { - self.insts.clear(); + self.insts.0.clear(); self.results.clear(); - self.blocks.clear(); + self.blocks.0.clear(); self.dynamic_types.clear(); self.value_lists.clear(); self.values.clear(); @@ -118,6 +182,7 @@ impl DataFlowGraph { self.values_labels = None; self.constants.clear(); self.immediates.clear(); + self.jump_tables.clear(); } /// Get the total number of instructions created in this function, whether they are currently @@ -125,12 +190,12 @@ impl DataFlowGraph { /// /// This is intended for use with `SecondaryMap::with_capacity`. pub fn num_insts(&self) -> usize { - self.insts.len() + self.insts.0.len() } /// Returns `true` if the given instruction reference is valid. pub fn inst_is_valid(&self, inst: Inst) -> bool { - self.insts.is_valid(inst) + self.insts.0.is_valid(inst) } /// Get the total number of basic blocks created in this function, whether they are @@ -146,21 +211,31 @@ impl DataFlowGraph { self.blocks.is_valid(block) } + /// Make a BlockCall, bundling together the block and its arguments. + pub fn block_call(&mut self, block: Block, args: &[Value]) -> BlockCall { + BlockCall::new(block, args, &mut self.value_lists) + } + /// Get the total number of values. pub fn num_values(&self) -> usize { self.values.len() } + /// Get an iterator over all values and their definitions. + pub fn values_and_defs(&self) -> impl Iterator + '_ { + self.values().map(|value| (value, self.value_def(value))) + } + /// Starts collection of debug information. pub fn collect_debug_info(&mut self) { if self.values_labels.is_none() { - self.values_labels = Some(HashMap::new()); + self.values_labels = Some(Default::default()); } } /// Inserts a `ValueLabelAssignments::Alias` for `to_alias` if debug info /// collection is enabled. - pub fn add_value_label_alias(&mut self, to_alias: Value, from: SourceLoc, value: Value) { + pub fn add_value_label_alias(&mut self, to_alias: Value, from: RelSourceLoc, value: Value) { if let Some(values_labels) = self.values_labels.as_mut() { values_labels.insert(to_alias, ir::ValueLabelAssignments::Alias { from, value }); } @@ -270,6 +345,7 @@ impl DataFlowGraph { // detect alias loops without overrunning the stack. self.value_def(self.resolve_aliases(original)) } + ValueData::Union { x, y, .. } => ValueDef::Union(x, y), } } @@ -285,6 +361,7 @@ impl DataFlowGraph { Inst { inst, num, .. } => Some(&v) == self.inst_results(inst).get(num as usize), Param { block, num, .. } => Some(&v) == self.block_params(block).get(num as usize), Alias { .. } => false, + Union { .. } => false, } } @@ -300,12 +377,7 @@ impl DataFlowGraph { /// For each argument of inst which is defined by an alias, replace the /// alias with the aliased value. pub fn resolve_aliases_in_arguments(&mut self, inst: Inst) { - for arg in self.insts[inst].arguments_mut(&mut self.value_lists) { - let resolved = resolve_aliases(&self.values, *arg); - if resolved != *arg { - *arg = resolved; - } - } + self.map_inst_values(inst, |dfg, arg| resolve_aliases(&dfg.values, arg)); } /// Turn a value into an alias of another. @@ -394,6 +466,8 @@ pub enum ValueDef { Result(Inst, usize), /// Value is the n'th parameter to a block. Param(Block, usize), + /// Value is a union of two other values. + Union(Value, Value), } impl ValueDef { @@ -430,12 +504,13 @@ impl ValueDef { pub fn num(self) -> usize { match self { Self::Result(_, n) | Self::Param(_, n) => n, + Self::Union(_, _) => 0, } } } /// Internal table storage for extended values. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] enum ValueData { /// Value is defined by an instruction. @@ -448,6 +523,11 @@ enum ValueData { /// An alias value can't be linked as an instruction result or block parameter. It is used as a /// placeholder when the original instruction or block has been rewritten or modified. Alias { ty: Type, original: Value }, + + /// Union is a "fork" in representation: the value can be + /// represented as either of the values named here. This is used + /// for aegraph (acyclic egraph) representation in the DFG. + Union { ty: Type, x: Value, y: Value }, } /// Bit-packed version of ValueData, for efficiency. @@ -455,40 +535,71 @@ enum ValueData { /// Layout: /// /// ```plain -/// | tag:2 | type:14 | num:16 | index:32 | +/// | tag:2 | type:14 | x:24 | y:24 | +/// +/// Inst 00 ty inst output inst index +/// Param 01 ty blockparam num block index +/// Alias 10 ty 0 value index +/// Union 11 ty first value second value /// ``` -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] struct ValueDataPacked(u64); +/// Encodes a value in 0..2^32 into 0..2^n, where n is less than 32 +/// (and is implied by `mask`), by translating 2^32-1 (0xffffffff) +/// into 2^n-1 and panic'ing on 2^n..2^32-1. +fn encode_narrow_field(x: u32, bits: u8) -> u32 { + if x == 0xffff_ffff { + (1 << bits) - 1 + } else { + debug_assert!(x < (1 << bits)); + x + } +} + +/// The inverse of the above `encode_narrow_field`: unpacks 2^n-1 into +/// 2^32-1. +fn decode_narrow_field(x: u32, bits: u8) -> u32 { + if x == (1 << bits) - 1 { + 0xffff_ffff + } else { + x + } +} + impl ValueDataPacked { - const INDEX_SHIFT: u64 = 0; - const INDEX_BITS: u64 = 32; - const NUM_SHIFT: u64 = Self::INDEX_SHIFT + Self::INDEX_BITS; - const NUM_BITS: u64 = 16; - const TYPE_SHIFT: u64 = Self::NUM_SHIFT + Self::NUM_BITS; - const TYPE_BITS: u64 = 14; - const TAG_SHIFT: u64 = Self::TYPE_SHIFT + Self::TYPE_BITS; - const TAG_BITS: u64 = 2; - - const TAG_INST: u64 = 1; - const TAG_PARAM: u64 = 2; - const TAG_ALIAS: u64 = 3; - - fn make(tag: u64, ty: Type, num: u16, index: u32) -> ValueDataPacked { + const Y_SHIFT: u8 = 0; + const Y_BITS: u8 = 24; + const X_SHIFT: u8 = Self::Y_SHIFT + Self::Y_BITS; + const X_BITS: u8 = 24; + const TYPE_SHIFT: u8 = Self::X_SHIFT + Self::X_BITS; + const TYPE_BITS: u8 = 14; + const TAG_SHIFT: u8 = Self::TYPE_SHIFT + Self::TYPE_BITS; + const TAG_BITS: u8 = 2; + + const TAG_INST: u64 = 0; + const TAG_PARAM: u64 = 1; + const TAG_ALIAS: u64 = 2; + const TAG_UNION: u64 = 3; + + fn make(tag: u64, ty: Type, x: u32, y: u32) -> ValueDataPacked { debug_assert!(tag < (1 << Self::TAG_BITS)); debug_assert!(ty.repr() < (1 << Self::TYPE_BITS)); + let x = encode_narrow_field(x, Self::X_BITS); + let y = encode_narrow_field(y, Self::Y_BITS); + ValueDataPacked( (tag << Self::TAG_SHIFT) | ((ty.repr() as u64) << Self::TYPE_SHIFT) - | ((num as u64) << Self::NUM_SHIFT) - | ((index as u64) << Self::INDEX_SHIFT), + | ((x as u64) << Self::X_SHIFT) + | ((y as u64) << Self::Y_SHIFT), ) } #[inline(always)] - fn field(self, shift: u64, bits: u64) -> u64 { + fn field(self, shift: u8, bits: u8) -> u64 { (self.0 >> shift) & ((1 << bits) - 1) } @@ -500,7 +611,7 @@ impl ValueDataPacked { #[inline(always)] fn set_type(&mut self, ty: Type) { - self.0 &= !((1 << Self::TYPE_BITS) - 1) << Self::TYPE_SHIFT; + self.0 &= !(((1 << Self::TYPE_BITS) - 1) << Self::TYPE_SHIFT); self.0 |= (ty.repr() as u64) << Self::TYPE_SHIFT; } } @@ -509,14 +620,17 @@ impl From for ValueDataPacked { fn from(data: ValueData) -> Self { match data { ValueData::Inst { ty, num, inst } => { - Self::make(Self::TAG_INST, ty, num, inst.as_bits()) + Self::make(Self::TAG_INST, ty, num.into(), inst.as_bits()) } ValueData::Param { ty, num, block } => { - Self::make(Self::TAG_PARAM, ty, num, block.as_bits()) + Self::make(Self::TAG_PARAM, ty, num.into(), block.as_bits()) } ValueData::Alias { ty, original } => { Self::make(Self::TAG_ALIAS, ty, 0, original.as_bits()) } + ValueData::Union { ty, x, y } => { + Self::make(Self::TAG_ALIAS, ty, x.as_bits(), y.as_bits()) + } } } } @@ -524,25 +638,33 @@ impl From for ValueDataPacked { impl From for ValueData { fn from(data: ValueDataPacked) -> Self { let tag = data.field(ValueDataPacked::TAG_SHIFT, ValueDataPacked::TAG_BITS); - let ty = data.field(ValueDataPacked::TYPE_SHIFT, ValueDataPacked::TYPE_BITS) as u16; - let num = data.field(ValueDataPacked::NUM_SHIFT, ValueDataPacked::NUM_BITS) as u16; - let index = data.field(ValueDataPacked::INDEX_SHIFT, ValueDataPacked::INDEX_BITS) as u32; + let ty = u16::try_from(data.field(ValueDataPacked::TYPE_SHIFT, ValueDataPacked::TYPE_BITS)) + .expect("Mask should ensure result fits in a u16"); + let x = u32::try_from(data.field(ValueDataPacked::X_SHIFT, ValueDataPacked::X_BITS)) + .expect("Mask should ensure result fits in a u32"); + let y = u32::try_from(data.field(ValueDataPacked::Y_SHIFT, ValueDataPacked::Y_BITS)) + .expect("Mask should ensure result fits in a u32"); let ty = Type::from_repr(ty); match tag { ValueDataPacked::TAG_INST => ValueData::Inst { ty, - num, - inst: Inst::from_bits(index), + num: u16::try_from(x).expect("Inst result num should fit in u16"), + inst: Inst::from_bits(decode_narrow_field(y, ValueDataPacked::Y_BITS)), }, ValueDataPacked::TAG_PARAM => ValueData::Param { ty, - num, - block: Block::from_bits(index), + num: u16::try_from(x).expect("Blockparam index should fit in u16"), + block: Block::from_bits(decode_narrow_field(y, ValueDataPacked::Y_BITS)), }, ValueDataPacked::TAG_ALIAS => ValueData::Alias { ty, - original: Value::from_bits(index), + original: Value::from_bits(decode_narrow_field(y, ValueDataPacked::Y_BITS)), + }, + ValueDataPacked::TAG_UNION => ValueData::Union { + ty, + x: Value::from_bits(decode_narrow_field(x, ValueDataPacked::X_BITS)), + y: Value::from_bits(decode_narrow_field(y, ValueDataPacked::Y_BITS)), }, _ => panic!("Invalid tag {} in ValueDataPacked 0x{:x}", tag, data.0), } @@ -554,12 +676,15 @@ impl From for ValueData { impl DataFlowGraph { /// Create a new instruction. /// - /// The type of the first result is indicated by `data.ty`. If the instruction produces - /// multiple results, also call `make_inst_results` to allocate value table entries. + /// The type of the first result is indicated by `data.ty`. If the + /// instruction produces multiple results, also call + /// `make_inst_results` to allocate value table entries. (It is + /// always safe to call `make_inst_results`, regardless of how + /// many results the instruction has.) pub fn make_inst(&mut self, data: InstructionData) -> Inst { let n = self.num_insts() + 1; self.results.resize(n); - self.insts.push(data) + self.insts.0.push(data) } /// Declares a dynamic vector type @@ -572,6 +697,74 @@ impl DataFlowGraph { DisplayInst(self, inst) } + /// Returns an object that displays the given `value`'s defining instruction. + /// + /// Panics if the value is not defined by an instruction (i.e. it is a basic + /// block argument). + pub fn display_value_inst(&self, value: Value) -> DisplayInst<'_> { + match self.value_def(value) { + ir::ValueDef::Result(inst, _) => self.display_inst(inst), + ir::ValueDef::Param(_, _) => panic!("value is not defined by an instruction"), + ir::ValueDef::Union(_, _) => panic!("value is a union of two other values"), + } + } + + /// Construct a read-only visitor context for the values of this instruction. + pub fn inst_values<'dfg>( + &'dfg self, + inst: Inst, + ) -> impl DoubleEndedIterator + 'dfg { + self.inst_args(inst) + .iter() + .chain( + self.insts[inst] + .branch_destination() + .into_iter() + .flat_map(|branch| branch.args_slice(&self.value_lists).iter()), + ) + .copied() + } + + /// Map a function over the values of the instruction. + pub fn map_inst_values(&mut self, inst: Inst, mut body: F) + where + F: FnMut(&mut DataFlowGraph, Value) -> Value, + { + for i in 0..self.inst_args(inst).len() { + let arg = self.inst_args(inst)[i]; + self.inst_args_mut(inst)[i] = body(self, arg); + } + + for block_ix in 0..self.insts[inst].branch_destination().len() { + // We aren't changing the size of the args list, so we won't need to write the branch + // back to the instruction. + let mut block = self.insts[inst].branch_destination()[block_ix]; + for i in 0..block.args_slice(&self.value_lists).len() { + let arg = block.args_slice(&self.value_lists)[i]; + block.args_slice_mut(&mut self.value_lists)[i] = body(self, arg); + } + } + } + + /// Overwrite the instruction's value references with values from the iterator. + /// NOTE: the iterator provided is expected to yield at least as many values as the instruction + /// currently has. + pub fn overwrite_inst_values(&mut self, inst: Inst, mut values: I) + where + I: Iterator, + { + for arg in self.inst_args_mut(inst) { + *arg = values.next().unwrap(); + } + + for block_ix in 0..self.insts[inst].branch_destination().len() { + let mut block = self.insts[inst].branch_destination()[block_ix]; + for arg in block.args_slice_mut(&mut self.value_lists) { + *arg = values.next().unwrap(); + } + } + } + /// Get all value arguments on `inst` as a slice. pub fn inst_args(&self, inst: Inst) -> &[Value] { self.insts[inst].arguments(&self.value_lists) @@ -584,7 +777,7 @@ impl DataFlowGraph { /// Get the fixed value arguments on `inst` as a slice. pub fn inst_fixed_args(&self, inst: Inst) -> &[Value] { - let num_fixed_args = self[inst] + let num_fixed_args = self.insts[inst] .opcode() .constraints() .num_fixed_value_arguments(); @@ -593,7 +786,7 @@ impl DataFlowGraph { /// Get the fixed value arguments on `inst` as a mutable slice. pub fn inst_fixed_args_mut(&mut self, inst: Inst) -> &mut [Value] { - let num_fixed_args = self[inst] + let num_fixed_args = self.insts[inst] .opcode() .constraints() .num_fixed_value_arguments(); @@ -602,7 +795,7 @@ impl DataFlowGraph { /// Get the variable value arguments on `inst` as a slice. pub fn inst_variable_args(&self, inst: Inst) -> &[Value] { - let num_fixed_args = self[inst] + let num_fixed_args = self.insts[inst] .opcode() .constraints() .num_fixed_value_arguments(); @@ -611,7 +804,7 @@ impl DataFlowGraph { /// Get the variable value arguments on `inst` as a mutable slice. pub fn inst_variable_args_mut(&mut self, inst: Inst) -> &mut [Value] { - let num_fixed_args = self[inst] + let num_fixed_args = self.insts[inst] .opcode() .constraints() .num_fixed_value_arguments(); @@ -648,43 +841,22 @@ impl DataFlowGraph { where I: Iterator>, { - let mut reuse = reuse.fuse(); - self.results[inst].clear(&mut self.value_lists); - // Get the call signature if this is a function call. - if let Some(sig) = self.call_signature(inst) { - // Create result values corresponding to the call return types. - debug_assert_eq!( - self.insts[inst].opcode().constraints().num_fixed_results(), - 0 - ); - let num_results = self.signatures[sig].returns.len(); - for res_idx in 0..num_results { - let ty = self.signatures[sig].returns[res_idx].value_type; - if let Some(Some(v)) = reuse.next() { - debug_assert_eq!(self.value_type(v), ty, "Reused {} is wrong type", ty); - self.attach_result(inst, v); - } else { - self.append_result(inst, ty); - } - } - num_results - } else { - // Create result values corresponding to the opcode's constraints. - let constraints = self.insts[inst].opcode().constraints(); - let num_results = constraints.num_fixed_results(); - for res_idx in 0..num_results { - let ty = constraints.result_type(res_idx, ctrl_typevar); - if let Some(Some(v)) = reuse.next() { - debug_assert_eq!(self.value_type(v), ty, "Reused {} is wrong type", ty); - self.attach_result(inst, v); - } else { - self.append_result(inst, ty); - } + let mut reuse = reuse.fuse(); + let result_tys: SmallVec<[_; 16]> = self.inst_result_types(inst, ctrl_typevar).collect(); + let num_results = result_tys.len(); + + for ty in result_tys { + if let Some(Some(v)) = reuse.next() { + debug_assert_eq!(self.value_type(v), ty, "Reused {} is wrong type", ty); + self.attach_result(inst, v); + } else { + self.append_result(inst, ty); } - num_results } + + num_results } /// Create a `ReplaceBuilder` that will replace `inst` with a new instruction in place. @@ -773,15 +945,21 @@ impl DataFlowGraph { }) } - /// Append a new value argument to an instruction. - /// - /// Panics if the instruction doesn't support arguments. - pub fn append_inst_arg(&mut self, inst: Inst, new_arg: Value) { - let mut branch_values = self.insts[inst] - .take_value_list() - .expect("the instruction doesn't have value arguments"); - branch_values.push(new_arg, &mut self.value_lists); - self.insts[inst].put_value_list(branch_values) + /// Clone an instruction, attaching new result `Value`s and + /// returning them. + pub fn clone_inst(&mut self, inst: Inst) -> Inst { + // First, add a clone of the InstructionData. + let inst_data = self.insts[inst].clone(); + // If the `inst_data` has a reference to a ValueList, clone it + // as well, because we can't share these (otherwise mutating + // one would affect the other). + let inst_data = inst_data.deep_clone(&mut self.value_lists); + let new_inst = self.make_inst(inst_data); + // Get the controlling type variable. + let ctrl_typevar = self.ctrl_typevar(inst); + // Create new result values. + self.make_inst_results(new_inst, ctrl_typevar); + new_inst } /// Get the first result of an instruction. @@ -808,6 +986,14 @@ impl DataFlowGraph { self.results[inst] } + /// Create a union of two values. + pub fn union(&mut self, x: Value, y: Value) -> Value { + // Get the type. + let ty = self.value_type(x); + debug_assert_eq!(ty, self.value_type(y)); + self.make_value(ValueData::Union { ty, x, y }) + } + /// Get the call signature of a direct or indirect call instruction. /// Returns `None` if `inst` is not a call instruction. pub fn call_signature(&self, inst: Inst) -> Option { @@ -818,9 +1004,82 @@ impl DataFlowGraph { } } - /// Check if `inst` is a branch. - pub fn analyze_branch(&self, inst: Inst) -> BranchInfo { - self.insts[inst].analyze_branch(&self.value_lists) + /// Like `call_signature` but returns none for tail call instructions. + fn non_tail_call_signature(&self, inst: Inst) -> Option { + let sig = self.call_signature(inst)?; + match self.insts[inst].opcode() { + ir::Opcode::ReturnCall | ir::Opcode::ReturnCallIndirect => None, + _ => Some(sig), + } + } + + // Only for use by the verifier. Everyone else should just use + // `dfg.inst_results(inst).len()`. + pub(crate) fn num_expected_results_for_verifier(&self, inst: Inst) -> usize { + match self.non_tail_call_signature(inst) { + Some(sig) => self.signatures[sig].returns.len(), + None => { + let constraints = self.insts[inst].opcode().constraints(); + constraints.num_fixed_results() + } + } + } + + /// Get the result types of the given instruction. + pub fn inst_result_types<'a>( + &'a self, + inst: Inst, + ctrl_typevar: Type, + ) -> impl iter::ExactSizeIterator + 'a { + return match self.non_tail_call_signature(inst) { + Some(sig) => InstResultTypes::Signature(self, sig, 0), + None => { + let constraints = self.insts[inst].opcode().constraints(); + InstResultTypes::Constraints(constraints, ctrl_typevar, 0) + } + }; + + enum InstResultTypes<'a> { + Signature(&'a DataFlowGraph, SigRef, usize), + Constraints(ir::instructions::OpcodeConstraints, Type, usize), + } + + impl Iterator for InstResultTypes<'_> { + type Item = Type; + + fn next(&mut self) -> Option { + match self { + InstResultTypes::Signature(dfg, sig, i) => { + let param = dfg.signatures[*sig].returns.get(*i)?; + *i += 1; + Some(param.value_type) + } + InstResultTypes::Constraints(constraints, ctrl_ty, i) => { + if *i < constraints.num_fixed_results() { + let ty = constraints.result_type(*i, *ctrl_ty); + *i += 1; + Some(ty) + } else { + None + } + } + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = match self { + InstResultTypes::Signature(dfg, sig, i) => { + dfg.signatures[*sig].returns.len() - *i + } + InstResultTypes::Constraints(constraints, _, i) => { + constraints.num_fixed_results() - *i + } + }; + (len, Some(len)) + } + } + + impl ExactSizeIterator for InstResultTypes<'_> {} } /// Compute the type of an instruction result from opcode constraints and call signatures. @@ -836,25 +1095,12 @@ impl DataFlowGraph { result_idx: usize, ctrl_typevar: Type, ) -> Option { - let constraints = self.insts[inst].opcode().constraints(); - let num_fixed_results = constraints.num_fixed_results(); - - if result_idx < num_fixed_results { - return Some(constraints.result_type(result_idx, ctrl_typevar)); - } - - // Not a fixed result, try to extract a return type from the call signature. - self.call_signature(inst).and_then(|sigref| { - self.signatures[sigref] - .returns - .get(result_idx - num_fixed_results) - .map(|&arg| arg.value_type) - }) + self.inst_result_types(inst, ctrl_typevar).nth(result_idx) } /// Get the controlling type variable, or `INVALID` if `inst` isn't polymorphic. pub fn ctrl_typevar(&self, inst: Inst) -> Type { - let constraints = self[inst].opcode().constraints(); + let constraints = self.insts[inst].opcode().constraints(); if !constraints.is_polymorphic() { types::INVALID @@ -862,9 +1108,14 @@ impl DataFlowGraph { // Not all instruction formats have a designated operand, but in that case // `requires_typevar_operand()` should never be true. self.value_type( - self[inst] + self.insts[inst] .typevar_operand(&self.value_lists) - .expect("Instruction format doesn't have a designated operand, bad opcode."), + .unwrap_or_else(|| { + panic!( + "Instruction format for {:?} doesn't have a designated operand", + self.insts[inst] + ) + }), ) } else { self.value_type(self.first_result(inst)) @@ -872,37 +1123,21 @@ impl DataFlowGraph { } } -/// Allow immutable access to instructions via indexing. -impl Index for DataFlowGraph { - type Output = InstructionData; - - fn index(&self, inst: Inst) -> &InstructionData { - &self.insts[inst] - } -} - -/// Allow mutable access to instructions via indexing. -impl IndexMut for DataFlowGraph { - fn index_mut(&mut self, inst: Inst) -> &mut InstructionData { - &mut self.insts[inst] - } -} - /// basic blocks. impl DataFlowGraph { /// Create a new basic block. pub fn make_block(&mut self) -> Block { - self.blocks.push(BlockData::new()) + self.blocks.add() } /// Get the number of parameters on `block`. pub fn num_block_params(&self, block: Block) -> usize { - self.blocks[block].params.len(&self.value_lists) + self.blocks[block].params(&self.value_lists).len() } /// Get the parameters on `block`. pub fn block_params(&self, block: Block) -> &[Value] { - self.blocks[block].params.as_slice(&self.value_lists) + self.blocks[block].params(&self.value_lists) } /// Get the types of the parameters on `block`. @@ -1056,9 +1291,9 @@ impl DataFlowGraph { /// Parameters on a basic block are values that dominate everything in the block. All /// branches to this block must provide matching arguments, and the arguments to the entry block must /// match the function arguments. -#[derive(Clone)] +#[derive(Clone, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] -struct BlockData { +pub struct BlockData { /// List of parameters to this block. params: ValueList, } @@ -1069,6 +1304,11 @@ impl BlockData { params: ValueList::new(), } } + + /// Get the parameters on `block`. + pub fn params<'a>(&self, pool: &'a ValueListPool) -> &'a [Value] { + self.params.as_slice(pool) + } } /// Object that can display an instruction. @@ -1089,9 +1329,9 @@ impl<'a> fmt::Display for DisplayInst<'a> { let typevar = dfg.ctrl_typevar(inst); if typevar.is_invalid() { - write!(f, "{}", dfg[inst].opcode())?; + write!(f, "{}", dfg.insts[inst].opcode())?; } else { - write!(f, "{}.{}", dfg[inst].opcode(), typevar)?; + write!(f, "{}.{}", dfg.insts[inst].opcode(), typevar)?; } write_operands(f, dfg, inst) } @@ -1135,29 +1375,15 @@ impl DataFlowGraph { ctrl_typevar: Type, reuse: &[Value], ) -> usize { - // Get the call signature if this is a function call. - if let Some(sig) = self.call_signature(inst) { - assert_eq!( - self.insts[inst].opcode().constraints().num_fixed_results(), - 0 - ); - for res_idx in 0..self.signatures[sig].returns.len() { - let ty = self.signatures[sig].returns[res_idx].value_type; - if let Some(v) = reuse.get(res_idx) { - self.set_value_type_for_parser(*v, ty); - } + let mut reuse_iter = reuse.iter().copied(); + let result_tys: SmallVec<[_; 16]> = self.inst_result_types(inst, ctrl_typevar).collect(); + for ty in result_tys { + if ty.is_dynamic_vector() { + self.check_dynamic_type(ty) + .unwrap_or_else(|| panic!("Use of undeclared dynamic type: {}", ty)); } - } else { - let constraints = self.insts[inst].opcode().constraints(); - for res_idx in 0..constraints.num_fixed_results() { - let ty = constraints.result_type(res_idx, ctrl_typevar); - if ty.is_dynamic_vector() { - self.check_dynamic_type(ty) - .unwrap_or_else(|| panic!("Use of undeclared dynamic type: {}", ty)); - } - if let Some(v) = reuse.get(res_idx) { - self.set_value_type_for_parser(*v, ty); - } + if let Some(v) = reuse_iter.next() { + self.set_value_type_for_parser(v, ty); } } @@ -1278,7 +1504,7 @@ mod tests { // Immutable reference resolution. { let immdfg = &dfg; - let ins = &immdfg[inst]; + let ins = &immdfg.insts[inst]; assert_eq!(ins.opcode(), Opcode::Iconst); } @@ -1408,6 +1634,7 @@ mod tests { #[test] fn aliases() { + use crate::ir::condcodes::IntCC; use crate::ir::InstBuilder; let mut func = Function::new(); @@ -1422,7 +1649,7 @@ mod tests { assert_eq!(pos.func.dfg.resolve_aliases(v1), v1); let arg0 = pos.func.dfg.append_block_param(block0, types::I32); - let (s, c) = pos.ins().iadd_ifcout(v1, arg0); + let (s, c) = pos.ins().iadd_cout(v1, arg0); let iadd = match pos.func.dfg.value_def(s) { ValueDef::Result(i, 0) => i, _ => panic!(), @@ -1432,17 +1659,33 @@ mod tests { pos.func.dfg.clear_results(iadd); pos.func.dfg.attach_result(iadd, s); - // Replace `iadd_ifcout` with a normal `iadd` and an `ifcmp`. + // Replace `iadd_cout` with a normal `iadd` and an `icmp`. pos.func.dfg.replace(iadd).iadd(v1, arg0); - let c2 = pos.ins().ifcmp(s, v1); + let c2 = pos.ins().icmp(IntCC::Equal, s, v1); pos.func.dfg.change_to_alias(c, c2); assert_eq!(pos.func.dfg.resolve_aliases(c2), c2); assert_eq!(pos.func.dfg.resolve_aliases(c), c2); + } + + #[test] + fn cloning() { + use crate::ir::InstBuilder; - // Make a copy of the alias. - let c3 = pos.ins().copy(c); - // This does not see through copies. - assert_eq!(pos.func.dfg.resolve_aliases(c3), c3); + let mut func = Function::new(); + let mut sig = Signature::new(crate::isa::CallConv::SystemV); + sig.params.push(ir::AbiParam::new(types::I32)); + let sig = func.import_signature(sig); + let block0 = func.dfg.make_block(); + let mut pos = FuncCursor::new(&mut func); + pos.insert_block(block0); + let v1 = pos.ins().iconst(types::I32, 0); + let v2 = pos.ins().iconst(types::I32, 1); + let call_inst = pos.ins().call_indirect(sig, v1, &[v1]); + let func = pos.func; + + let call_inst_dup = func.dfg.clone_inst(call_inst); + func.dfg.inst_args_mut(call_inst)[0] = v2; + assert_eq!(v1, func.dfg.inst_args(call_inst_dup)[0]); } } diff --git a/cranelift/codegen/src/ir/dynamic_type.rs b/cranelift/codegen/src/ir/dynamic_type.rs index 85589cef678a..f1ae30982114 100644 --- a/cranelift/codegen/src/ir/dynamic_type.rs +++ b/cranelift/codegen/src/ir/dynamic_type.rs @@ -1,6 +1,7 @@ //! Dynamic IR types use crate::ir::entities::DynamicType; +use crate::ir::types::*; use crate::ir::GlobalValue; use crate::ir::PrimaryMap; use crate::ir::Type; @@ -9,7 +10,7 @@ use crate::ir::Type; use serde::{Deserialize, Serialize}; /// A dynamic type object which has a base vector type and a scaling factor. -#[derive(Clone)] +#[derive(Clone, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct DynamicTypeData { /// Base vector type, this is the minimum size of the type. @@ -36,3 +37,19 @@ impl DynamicTypeData { /// All allocated dynamic types. pub type DynamicTypes = PrimaryMap; + +/// Convert a dynamic-vector type to a fixed-vector type. +pub fn dynamic_to_fixed(ty: Type) -> Type { + match ty { + I8X8XN => I8X8, + I8X16XN => I8X16, + I16X4XN => I16X4, + I16X8XN => I16X8, + I32X2XN => I32X2, + I32X4XN => I32X4, + I64X2XN => I64X2, + F32X4XN => F32X4, + F64X2XN => F64X2, + _ => unreachable!("unhandled type: {}", ty), + } +} diff --git a/cranelift/codegen/src/ir/entities.rs b/cranelift/codegen/src/ir/entities.rs index 2be7014685c9..51c7633207c7 100644 --- a/cranelift/codegen/src/ir/entities.rs +++ b/cranelift/codegen/src/ir/entities.rs @@ -58,7 +58,6 @@ impl Block { /// - [`iconst`](super::InstBuilder::iconst) for integer constants /// - [`f32const`](super::InstBuilder::f32const) for 32-bit float constants /// - [`f64const`](super::InstBuilder::f64const) for 64-bit float constants -/// - [`bconst`](super::InstBuilder::bconst) for boolean constants /// - [`vconst`](super::InstBuilder::vconst) for vector constants /// - [`null`](super::InstBuilder::null) for null reference constants /// @@ -88,12 +87,10 @@ impl Value { /// /// Most usage of `Inst` is internal. `Inst`ructions are returned by /// [`InstBuilder`](super::InstBuilder) instructions that do not return a -/// [`Value`], such as control flow and trap instructions. -/// -/// If you look around the API, you can find many inventive uses for `Inst`, -/// such as [annotating specific instructions with a comment][inst_comment] -/// or [performing reflection at compile time](super::DataFlowGraph::analyze_branch) -/// on the type of instruction. +/// [`Value`], such as control flow and trap instructions, as well as instructions that return a +/// variable (potentially zero!) number of values, like call or call-indirect instructions. To get +/// the `Value` of such instructions, use [`inst_results`](super::DataFlowGraph::inst_results) or +/// its analogue in `cranelift_frontend::FuncBuilder`. /// /// [inst_comment]: https://github.com/bjorn3/rustc_codegen_cranelift/blob/0f8814fd6da3d436a90549d4bb19b94034f2b19c/src/pretty_clif.rs /// @@ -328,6 +325,12 @@ impl FuncRef { } } +/// A reference to an `UserExternalName`, declared with `Function::declare_imported_user_function`. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct UserExternalNameRef(u32); +entity_impl!(UserExternalNameRef, "userextname"); + /// An opaque reference to a function [`Signature`](super::Signature). /// /// `SigRef`s are used to declare a function with @@ -360,32 +363,6 @@ impl SigRef { } } -/// An opaque reference to a [heap](https://en.wikipedia.org/wiki/Memory_management#DYNAMIC). -/// -/// Heaps are used to access dynamically allocated memory through -/// [`heap_addr`](super::InstBuilder::heap_addr). -/// -/// To create a heap, use [`FunctionBuilder::create_heap`](https://docs.rs/cranelift-frontend/*/cranelift_frontend/struct.FunctionBuilder.html#method.create_heap). -/// -/// While the order is stable, it is arbitrary. -#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] -pub struct Heap(u32); -entity_impl!(Heap, "heap"); - -impl Heap { - /// Create a new heap reference from its number. - /// - /// This method is for use by the parser. - pub fn with_number(n: u32) -> Option { - if n < u32::MAX { - Some(Self(n)) - } else { - None - } - } -} - /// An opaque reference to a [WebAssembly /// table](https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format#WebAssembly_tables). /// @@ -441,8 +418,6 @@ pub enum AnyEntity { FuncRef(FuncRef), /// A function call signature. SigRef(SigRef), - /// A heap. - Heap(Heap), /// A table. Table(Table), /// A function's stack limit @@ -464,7 +439,6 @@ impl fmt::Display for AnyEntity { Self::Constant(r) => r.fmt(f), Self::FuncRef(r) => r.fmt(f), Self::SigRef(r) => r.fmt(f), - Self::Heap(r) => r.fmt(f), Self::Table(r) => r.fmt(f), Self::StackLimit => write!(f, "stack_limit"), } @@ -543,12 +517,6 @@ impl From for AnyEntity { } } -impl From for AnyEntity { - fn from(r: Heap) -> Self { - Self::Heap(r) - } -} - impl From
for AnyEntity { fn from(r: Table) -> Self { Self::Table(r) diff --git a/cranelift/codegen/src/ir/extfunc.rs b/cranelift/codegen/src/ir/extfunc.rs index 8baa6bff84da..e3822c6e401c 100644 --- a/cranelift/codegen/src/ir/extfunc.rs +++ b/cranelift/codegen/src/ir/extfunc.rs @@ -14,6 +14,8 @@ use core::str::FromStr; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; +use super::function::FunctionParameters; + /// Function signature. /// /// The function signature describes the types of formal parameters and return values along with @@ -142,9 +144,6 @@ pub struct AbiParam { pub purpose: ArgumentPurpose, /// Method for extending argument to a full register. pub extension: ArgumentExtension, - - /// Was the argument converted to pointer during legalization? - pub legalized_to_pointer: bool, } impl AbiParam { @@ -154,7 +153,6 @@ impl AbiParam { value_type: vt, extension: ArgumentExtension::None, purpose: ArgumentPurpose::Normal, - legalized_to_pointer: false, } } @@ -164,7 +162,6 @@ impl AbiParam { value_type: vt, extension: ArgumentExtension::None, purpose, - legalized_to_pointer: false, } } @@ -190,9 +187,6 @@ impl AbiParam { impl fmt::Display for AbiParam { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.value_type)?; - if self.legalized_to_pointer { - write!(f, " ptr")?; - } match self.extension { ArgumentExtension::None => {} ArgumentExtension::Uext => write!(f, " uext")?, @@ -252,41 +246,12 @@ pub enum ArgumentPurpose { /// a `StructReturn` pointer argument to also return that pointer in a register. StructReturn, - /// The link register. - /// - /// Most RISC architectures implement calls by saving the return address in a designated - /// register rather than pushing it on the stack. This is represented with a `Link` argument. - /// - /// Similarly, some return instructions expect the return address in a register represented as - /// a `Link` return value. - Link, - - /// The frame pointer. - /// - /// This indicates the frame pointer register which has a special meaning in some ABIs. - /// - /// The frame pointer appears as an argument and as a return value since it is a callee-saved - /// register. - FramePointer, - - /// A callee-saved register. - /// - /// Some calling conventions have registers that must be saved by the callee. These registers - /// are represented as `CalleeSaved` arguments and return values. - CalleeSaved, - /// A VM context pointer. /// /// This is a pointer to a context struct containing details about the current sandbox. It is /// used as a base pointer for `vmctx` global values. VMContext, - /// A signature identifier. - /// - /// This is a special-purpose argument used to identify the calling convention expected by the - /// caller in an indirect call. The callee can verify that the expected signature ID matches. - SignatureId, - /// A stack limit pointer. /// /// This is a pointer to a stack limit. It is used to check the current stack pointer @@ -300,11 +265,7 @@ impl fmt::Display for ArgumentPurpose { Self::Normal => "normal", Self::StructArgument(size) => return write!(f, "sarg({})", size), Self::StructReturn => "sret", - Self::Link => "link", - Self::FramePointer => "fp", - Self::CalleeSaved => "csr", Self::VMContext => "vmctx", - Self::SignatureId => "sigid", Self::StackLimit => "stack_limit", }) } @@ -316,11 +277,7 @@ impl FromStr for ArgumentPurpose { match s { "normal" => Ok(Self::Normal), "sret" => Ok(Self::StructReturn), - "link" => Ok(Self::Link), - "fp" => Ok(Self::FramePointer), - "csr" => Ok(Self::CalleeSaved), "vmctx" => Ok(Self::VMContext), - "sigid" => Ok(Self::SignatureId), "stack_limit" => Ok(Self::StackLimit), _ if s.starts_with("sarg(") => { if !s.ends_with(")") { @@ -338,7 +295,7 @@ impl FromStr for ArgumentPurpose { /// An external function. /// /// Information about a function that can be called directly with a direct `call` instruction. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct ExtFuncData { /// Name of the external function. @@ -356,20 +313,11 @@ pub struct ExtFuncData { /// flag is best used when the target is known to be in the same unit of code generation, such /// as a Wasm module. /// - /// See the documentation for [`RelocDistance`](crate::machinst::RelocDistance) for more details. A - /// `colocated` flag value of `true` implies `RelocDistance::Near`. + /// See the documentation for `RelocDistance` for more details. A `colocated` flag value of + /// `true` implies `RelocDistance::Near`. pub colocated: bool, } -impl fmt::Display for ExtFuncData { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.colocated { - write!(f, "colocated ")?; - } - write!(f, "{} {}", self.name, self.signature) - } -} - impl ExtFuncData { /// Return an estimate of the distance to the referred-to function symbol. pub fn reloc_distance(&self) -> RelocDistance { @@ -379,12 +327,44 @@ impl ExtFuncData { RelocDistance::Far } } + + /// Returns a displayable version of the `ExtFuncData`, with or without extra context to + /// prettify the output. + pub fn display<'a>( + &'a self, + params: Option<&'a FunctionParameters>, + ) -> DisplayableExtFuncData<'a> { + DisplayableExtFuncData { + ext_func: self, + params, + } + } +} + +/// A displayable `ExtFuncData`, with extra context to prettify the output. +pub struct DisplayableExtFuncData<'a> { + ext_func: &'a ExtFuncData, + params: Option<&'a FunctionParameters>, +} + +impl<'a> fmt::Display for DisplayableExtFuncData<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.ext_func.colocated { + write!(f, "colocated ")?; + } + write!( + f, + "{} {}", + self.ext_func.name.display(self.params), + self.ext_func.signature + ) + } } #[cfg(test)] mod tests { use super::*; - use crate::ir::types::{B8, F32, I32}; + use crate::ir::types::{F32, I32, I8}; use alloc::string::ToString; #[test] @@ -396,8 +376,6 @@ mod tests { assert_eq!(t.sext().to_string(), "i32 sext"); t.purpose = ArgumentPurpose::StructReturn; assert_eq!(t.to_string(), "i32 uext sret"); - t.legalized_to_pointer = true; - assert_eq!(t.to_string(), "i32 ptr uext sret"); } #[test] @@ -405,11 +383,7 @@ mod tests { let all_purpose = [ (ArgumentPurpose::Normal, "normal"), (ArgumentPurpose::StructReturn, "sret"), - (ArgumentPurpose::Link, "link"), - (ArgumentPurpose::FramePointer, "fp"), - (ArgumentPurpose::CalleeSaved, "csr"), (ArgumentPurpose::VMContext, "vmctx"), - (ArgumentPurpose::SignatureId, "sigid"), (ArgumentPurpose::StackLimit, "stack_limit"), (ArgumentPurpose::StructArgument(42), "sarg(42)"), ]; @@ -441,7 +415,7 @@ mod tests { assert_eq!(sig.to_string(), "(i32) -> f32 windows_fastcall"); sig.params.push(AbiParam::new(I32.by(4).unwrap())); assert_eq!(sig.to_string(), "(i32, i32x4) -> f32 windows_fastcall"); - sig.returns.push(AbiParam::new(B8)); - assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, b8 windows_fastcall"); + sig.returns.push(AbiParam::new(I8)); + assert_eq!(sig.to_string(), "(i32, i32x4) -> f32, i8 windows_fastcall"); } } diff --git a/cranelift/codegen/src/ir/extname.rs b/cranelift/codegen/src/ir/extname.rs index 362cf8c67e98..00552bbd6949 100644 --- a/cranelift/codegen/src/ir/extname.rs +++ b/cranelift/codegen/src/ir/extname.rs @@ -4,15 +4,108 @@ //! function. The name of an external declaration doesn't have any meaning to //! Cranelift, which compiles functions independently. -use crate::ir::LibCall; -use core::cmp; +use crate::ir::{KnownSymbol, LibCall}; +use alloc::boxed::Box; use core::fmt::{self, Write}; use core::str::FromStr; +use cranelift_entity::EntityRef as _; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; -const TESTCASE_NAME_LENGTH: usize = 16; +use super::entities::UserExternalNameRef; +use super::function::FunctionParameters; + +/// An explicit name for a user-defined function, be it defined in code or in CLIF text. +/// +/// This is used both for naming a function (for debugging purposes) and for declaring external +/// functions. In the latter case, this becomes an `ExternalName`, which gets embedded in +/// relocations later, etc. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub enum UserFuncName { + /// A user-defined name, with semantics left to the user. + User(UserExternalName), + /// A name for a test case, mostly intended for Cranelift testing. + Testcase(TestcaseName), +} + +impl UserFuncName { + /// Creates a new external name from a sequence of bytes. Caller is expected + /// to guarantee bytes are only ascii alphanumeric or `_`. + pub fn testcase>(v: T) -> Self { + Self::Testcase(TestcaseName::new(v)) + } + + /// Create a new external name from a user-defined external function reference. + pub fn user(namespace: u32, index: u32) -> Self { + Self::User(UserExternalName::new(namespace, index)) + } +} + +impl Default for UserFuncName { + fn default() -> Self { + UserFuncName::User(UserExternalName::default()) + } +} + +impl fmt::Display for UserFuncName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UserFuncName::User(user) => user.fmt(f), + UserFuncName::Testcase(testcase) => testcase.fmt(f), + } + } +} + +/// An external name in a user-defined symbol table. +/// +/// Cranelift does not interpret these numbers in any way, so they can represent arbitrary values. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct UserExternalName { + /// Arbitrary. + pub namespace: u32, + /// Arbitrary. + pub index: u32, +} + +impl UserExternalName { + /// Creates a new [UserExternalName]. + pub fn new(namespace: u32, index: u32) -> Self { + Self { namespace, index } + } +} + +impl fmt::Display for UserExternalName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "u{}:{}", self.namespace, self.index) + } +} + +/// A name for a test case. +#[derive(Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct TestcaseName(Box<[u8]>); + +impl fmt::Display for TestcaseName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_char('%')?; + f.write_str(std::str::from_utf8(&self.0).unwrap()) + } +} + +impl fmt::Debug for TestcaseName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl TestcaseName { + pub(crate) fn new>(v: T) -> Self { + Self(v.as_ref().into()) + } +} /// The name of an external is either a reference to a user-defined symbol /// table, or a short sequence of ascii bytes so that test cases do not have @@ -25,27 +118,24 @@ const TESTCASE_NAME_LENGTH: usize = 16; /// External names can also serve as a primitive testing and debugging tool. /// In particular, many `.clif` test files use function names to identify /// functions. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum ExternalName { - /// A name in a user-defined symbol table. Cranelift does not interpret - /// these numbers in any way. - User { - /// Arbitrary. - namespace: u32, - /// Arbitrary. - index: u32, - }, + /// A reference to a name in a user-defined symbol table. + User(UserExternalNameRef), /// A test case function name of up to a hardcoded amount of ascii /// characters. This is not intended to be used outside test cases. - TestCase { - /// How many of the bytes in `ascii` are valid? - length: u8, - /// Ascii bytes of the name. - ascii: [u8; TESTCASE_NAME_LENGTH], - }, + TestCase(TestcaseName), /// A well-known runtime library function. LibCall(LibCall), + /// A well-known symbol. + KnownSymbol(KnownSymbol), +} + +impl Default for ExternalName { + fn default() -> Self { + Self::User(UserExternalNameRef::new(0)) + } } impl ExternalName { @@ -58,52 +148,56 @@ impl ExternalName { /// # use cranelift_codegen::ir::ExternalName; /// // Create `ExternalName` from a string. /// let name = ExternalName::testcase("hello"); - /// assert_eq!(name.to_string(), "%hello"); + /// assert_eq!(name.display(None).to_string(), "%hello"); /// ``` pub fn testcase>(v: T) -> Self { - let vec = v.as_ref(); - let len = cmp::min(vec.len(), TESTCASE_NAME_LENGTH); - let mut bytes = [0u8; TESTCASE_NAME_LENGTH]; - bytes[0..len].copy_from_slice(&vec[0..len]); - - Self::TestCase { - length: len as u8, - ascii: bytes, - } + Self::TestCase(TestcaseName::new(v)) } - /// Create a new external name from user-provided integer indices. + /// Create a new external name from a user-defined external function reference. /// /// # Examples /// ```rust - /// # use cranelift_codegen::ir::ExternalName; - /// // Create `ExternalName` from integer indices - /// let name = ExternalName::user(123, 456); - /// assert_eq!(name.to_string(), "u123:456"); + /// # use cranelift_codegen::ir::{ExternalName, UserExternalNameRef}; + /// let user_func_ref: UserExternalNameRef = Default::default(); // usually obtained with `Function::declare_imported_user_function()` + /// let name = ExternalName::user(user_func_ref); + /// assert_eq!(name.display(None).to_string(), "userextname0"); /// ``` - pub fn user(namespace: u32, index: u32) -> Self { - Self::User { namespace, index } + pub fn user(func_ref: UserExternalNameRef) -> Self { + Self::User(func_ref) } -} -impl Default for ExternalName { - fn default() -> Self { - Self::user(0, 0) + /// Returns a display for the current `ExternalName`, with extra context to prettify the + /// output. + pub fn display<'a>( + &'a self, + params: Option<&'a FunctionParameters>, + ) -> DisplayableExternalName<'a> { + DisplayableExternalName { name: self, params } } } -impl fmt::Display for ExternalName { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Self::User { namespace, index } => write!(f, "u{}:{}", namespace, index), - Self::TestCase { length, ascii } => { - f.write_char('%')?; - for byte in ascii.iter().take(length as usize) { - f.write_char(*byte as char)?; +/// An `ExternalName` that has enough context to be displayed. +pub struct DisplayableExternalName<'a> { + name: &'a ExternalName, + params: Option<&'a FunctionParameters>, +} + +impl<'a> fmt::Display for DisplayableExternalName<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.name { + ExternalName::User(func_ref) => { + if let Some(params) = self.params { + let name = ¶ms.user_named_funcs()[*func_ref]; + write!(f, "u{}:{}", name.namespace, name.index) + } else { + // Best effort. + write!(f, "{}", *func_ref) } - Ok(()) } - Self::LibCall(lc) => write!(f, "%{}", lc), + ExternalName::TestCase(testcase) => testcase.fmt(f), + ExternalName::LibCall(lc) => write!(f, "%{}", lc), + ExternalName::KnownSymbol(ks) => write!(f, "%{}", ks), } } } @@ -112,44 +206,106 @@ impl FromStr for ExternalName { type Err = (); fn from_str(s: &str) -> Result { - // Try to parse as a libcall name, otherwise it's a test case. - match s.parse() { - Ok(lc) => Ok(Self::LibCall(lc)), - Err(_) => Ok(Self::testcase(s.as_bytes())), + // Try to parse as a known symbol + if let Ok(ks) = s.parse() { + return Ok(Self::KnownSymbol(ks)); + } + + // Try to parse as a libcall name + if let Ok(lc) = s.parse() { + return Ok(Self::LibCall(lc)); } + + // Otherwise its a test case name + Ok(Self::testcase(s.as_bytes())) } } #[cfg(test)] mod tests { use super::ExternalName; - use crate::ir::LibCall; + use crate::ir::{ + entities::UserExternalNameRef, function::FunctionParameters, LibCall, UserExternalName, + }; use alloc::string::ToString; use core::u32; + use cranelift_entity::EntityRef as _; + + #[cfg(target_pointer_width = "64")] + #[test] + fn externalname_size() { + assert_eq!(core::mem::size_of::(), 24); + } #[test] fn display_testcase() { - assert_eq!(ExternalName::testcase("").to_string(), "%"); - assert_eq!(ExternalName::testcase("x").to_string(), "%x"); - assert_eq!(ExternalName::testcase("x_1").to_string(), "%x_1"); + assert_eq!(ExternalName::testcase("").display(None).to_string(), "%"); + assert_eq!(ExternalName::testcase("x").display(None).to_string(), "%x"); assert_eq!( - ExternalName::testcase("longname12345678").to_string(), - "%longname12345678" + ExternalName::testcase("x_1").display(None).to_string(), + "%x_1" ); - // Constructor will silently drop bytes beyond the 16th assert_eq!( - ExternalName::testcase("longname123456789").to_string(), + ExternalName::testcase("longname12345678") + .display(None) + .to_string(), "%longname12345678" ); + assert_eq!( + ExternalName::testcase("longname123456789") + .display(None) + .to_string(), + "%longname123456789" + ); } #[test] fn display_user() { - assert_eq!(ExternalName::user(0, 0).to_string(), "u0:0"); - assert_eq!(ExternalName::user(1, 1).to_string(), "u1:1"); assert_eq!( - ExternalName::user(u32::MAX, u32::MAX).to_string(), - "u4294967295:4294967295" + ExternalName::user(UserExternalNameRef::new(0)) + .display(None) + .to_string(), + "userextname0" + ); + assert_eq!( + ExternalName::user(UserExternalNameRef::new(1)) + .display(None) + .to_string(), + "userextname1" + ); + assert_eq!( + ExternalName::user(UserExternalNameRef::new((u32::MAX - 1) as _)) + .display(None) + .to_string(), + "userextname4294967294" + ); + + let mut func_params = FunctionParameters::new(); + + // ref 0 + func_params.ensure_user_func_name(UserExternalName { + namespace: 13, + index: 37, + }); + + // ref 1 + func_params.ensure_user_func_name(UserExternalName { + namespace: 2, + index: 4, + }); + + assert_eq!( + ExternalName::user(UserExternalNameRef::new(0)) + .display(Some(&func_params)) + .to_string(), + "u13:37" + ); + + assert_eq!( + ExternalName::user(UserExternalNameRef::new(1)) + .display(Some(&func_params)) + .to_string(), + "u2:4" ); } @@ -160,7 +316,9 @@ mod tests { Ok(ExternalName::LibCall(LibCall::FloorF32)) ); assert_eq!( - ExternalName::LibCall(LibCall::FloorF32).to_string(), + ExternalName::LibCall(LibCall::FloorF32) + .display(None) + .to_string(), "%FloorF32" ); } diff --git a/cranelift/codegen/src/ir/function.rs b/cranelift/codegen/src/ir/function.rs index 856c8f5f0d1b..3e0a00b17719 100644 --- a/cranelift/codegen/src/ir/function.rs +++ b/cranelift/codegen/src/ir/function.rs @@ -4,18 +4,16 @@ //! instructions. use crate::entity::{PrimaryMap, SecondaryMap}; -use crate::ir; -use crate::ir::JumpTables; use crate::ir::{ - instructions::BranchInfo, Block, DynamicStackSlot, DynamicStackSlotData, DynamicType, - ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Heap, HeapData, Inst, InstructionData, - JumpTable, JumpTableData, Opcode, SigRef, StackSlot, StackSlotData, Table, TableData, Type, + self, Block, DataFlowGraph, DynamicStackSlot, DynamicStackSlotData, DynamicStackSlots, + DynamicType, ExtFuncData, FuncRef, GlobalValue, GlobalValueData, Inst, InstructionData, + JumpTable, JumpTableData, Layout, Opcode, SigRef, Signature, SourceLocs, StackSlot, + StackSlotData, StackSlots, Table, TableData, Type, }; -use crate::ir::{DataFlowGraph, ExternalName, Layout, Signature}; -use crate::ir::{DynamicStackSlots, SourceLocs, StackSlots}; use crate::isa::CallConv; use crate::value_label::ValueLabelsRanges; use crate::write::write_function; +use crate::HashMap; #[cfg(feature = "enable-serde")] use alloc::string::String; use core::fmt; @@ -27,9 +25,13 @@ use serde::ser::Serializer; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; +use super::entities::UserExternalNameRef; +use super::extname::UserFuncName; +use super::{RelSourceLoc, SourceLoc, UserExternalName}; + /// A version marker used to ensure that serialized clif ir is never deserialized with a /// different version of Cranelift. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Hash)] pub struct VersionMarker; #[cfg(feature = "enable-serde")] @@ -60,21 +62,99 @@ impl<'de> Deserialize<'de> for VersionMarker { } } -/// -/// Functions can be cloned, but it is not a very fast operation. -/// The clone will have all the same entity numbers as the original. +/// Function parameters used when creating this function, and that will become applied after +/// compilation to materialize the final `CompiledCode`. #[derive(Clone)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] -pub struct Function { +pub struct FunctionParameters { + /// The first `SourceLoc` appearing in the function, serving as a base for every relative + /// source loc in the function. + base_srcloc: Option, + + /// External user-defined function references. + user_named_funcs: PrimaryMap, + + /// Inverted mapping of `user_named_funcs`, to deduplicate internally. + user_ext_name_to_ref: HashMap, +} + +impl FunctionParameters { + /// Creates a new `FunctionParameters` with the given name. + pub fn new() -> Self { + Self { + base_srcloc: None, + user_named_funcs: Default::default(), + user_ext_name_to_ref: Default::default(), + } + } + + /// Returns the base `SourceLoc`. + /// + /// If it was never explicitly set with `ensure_base_srcloc`, will return an invalid + /// `SourceLoc`. + pub fn base_srcloc(&self) -> SourceLoc { + self.base_srcloc.unwrap_or_default() + } + + /// Sets the base `SourceLoc`, if not set yet, and returns the base value. + pub fn ensure_base_srcloc(&mut self, srcloc: SourceLoc) -> SourceLoc { + match self.base_srcloc { + Some(val) => val, + None => { + self.base_srcloc = Some(srcloc); + srcloc + } + } + } + + /// Retrieve a `UserExternalNameRef` for the given name, or add a new one. + /// + /// This method internally deduplicates same `UserExternalName` so they map to the same + /// reference. + pub fn ensure_user_func_name(&mut self, name: UserExternalName) -> UserExternalNameRef { + if let Some(reff) = self.user_ext_name_to_ref.get(&name) { + *reff + } else { + let reff = self.user_named_funcs.push(name.clone()); + self.user_ext_name_to_ref.insert(name, reff); + reff + } + } + + /// Resets an already existing user function name to a new value. + pub fn reset_user_func_name(&mut self, index: UserExternalNameRef, name: UserExternalName) { + if let Some(prev_name) = self.user_named_funcs.get_mut(index) { + self.user_ext_name_to_ref.remove(prev_name); + *prev_name = name.clone(); + self.user_ext_name_to_ref.insert(name, index); + } + } + + /// Returns the internal mapping of `UserExternalNameRef` to `UserExternalName`. + pub fn user_named_funcs(&self) -> &PrimaryMap { + &self.user_named_funcs + } + + fn clear(&mut self) { + self.base_srcloc = None; + self.user_named_funcs.clear(); + self.user_ext_name_to_ref.clear(); + } +} + +/// Function fields needed when compiling a function. +/// +/// Additionally, these fields can be the same for two functions that would be compiled the same +/// way, and finalized by applying `FunctionParameters` onto their `CompiledCodeStencil`. +#[derive(Clone, PartialEq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct FunctionStencil { /// A version marker used to ensure that serialized clif ir is never deserialized with a /// different version of Cranelift. // Note: This must be the first field to ensure that Serde will deserialize it before // attempting to deserialize other fields that are potentially changed between versions. pub version_marker: VersionMarker, - /// Name of this function. Mostly used by `.clif` files. - pub name: ExternalName, - /// Signature of this function. pub signature: Signature, @@ -87,15 +167,9 @@ pub struct Function { /// Global values referenced. pub global_values: PrimaryMap, - /// Heaps referenced. - pub heaps: PrimaryMap, - /// Tables referenced. pub tables: PrimaryMap, - /// Jump tables used in this function. - pub jump_tables: JumpTables, - /// Data flow graph containing the primary definition of all instructions, blocks and values. pub dfg: DataFlowGraph, @@ -116,49 +190,22 @@ pub struct Function { pub stack_limit: Option, } -impl Function { - /// Create a function with the given name and signature. - pub fn with_name_signature(name: ExternalName, sig: Signature) -> Self { - Self { - version_marker: VersionMarker, - name, - signature: sig, - sized_stack_slots: StackSlots::new(), - dynamic_stack_slots: DynamicStackSlots::new(), - global_values: PrimaryMap::new(), - heaps: PrimaryMap::new(), - tables: PrimaryMap::new(), - jump_tables: PrimaryMap::new(), - dfg: DataFlowGraph::new(), - layout: Layout::new(), - srclocs: SecondaryMap::new(), - stack_limit: None, - } - } - - /// Clear all data structures in this function. - pub fn clear(&mut self) { +impl FunctionStencil { + fn clear(&mut self) { self.signature.clear(CallConv::Fast); self.sized_stack_slots.clear(); self.dynamic_stack_slots.clear(); self.global_values.clear(); - self.heaps.clear(); self.tables.clear(); - self.jump_tables.clear(); self.dfg.clear(); self.layout.clear(); self.srclocs.clear(); self.stack_limit = None; } - /// Create a new empty, anonymous function with a Fast calling convention. - pub fn new() -> Self { - Self::with_name_signature(ExternalName::default(), Signature::new(CallConv::Fast)) - } - /// Creates a jump table in the function, to be used by `br_table` instructions. pub fn create_jump_table(&mut self, data: JumpTableData) -> JumpTable { - self.jump_tables.push(data) + self.dfg.jump_tables.push(data) } /// Creates a sized stack slot in the function, to be used by `stack_load`, `stack_store` @@ -178,11 +225,6 @@ impl Function { self.dfg.signatures.push(signature) } - /// Declare an external function import. - pub fn import_function(&mut self, data: ExtFuncData) -> FuncRef { - self.dfg.ext_funcs.push(data) - } - /// Declares a global value accessible to the function. pub fn create_global_value(&mut self, data: GlobalValueData) -> GlobalValue { self.global_values.push(data) @@ -208,29 +250,11 @@ impl Function { .concrete() } - /// Declares a heap accessible to the function. - pub fn create_heap(&mut self, data: HeapData) -> Heap { - self.heaps.push(data) - } - /// Declares a table accessible to the function. pub fn create_table(&mut self, data: TableData) -> Table { self.tables.push(data) } - /// Return an object that can display this function with correct ISA-specific annotations. - pub fn display(&self) -> DisplayFunction<'_> { - DisplayFunction(self, Default::default()) - } - - /// Return an object that can display this function with correct ISA-specific annotations. - pub fn display_with<'a>( - &'a self, - annotations: DisplayFunctionAnnotations<'a>, - ) -> DisplayFunction<'a> { - DisplayFunction(self, annotations) - } - /// Find a presumed unique special-purpose function parameter value. /// /// Returns the value of the last `purpose` parameter, or `None` if no such parameter exists. @@ -246,51 +270,40 @@ impl Function { self.dfg.collect_debug_info(); } - /// Changes the destination of a jump or branch instruction. - /// Does nothing if called with a non-jump or non-branch instruction. - /// - /// Note that this method ignores multi-destination branches like `br_table`. - pub fn change_branch_destination(&mut self, inst: Inst, new_dest: Block) { - match self.dfg[inst].branch_destination_mut() { - None => (), - Some(inst_dest) => *inst_dest = new_dest, - } - } - /// Rewrite the branch destination to `new_dest` if the destination matches `old_dest`. /// Does nothing if called with a non-jump or non-branch instruction. - /// - /// Unlike [change_branch_destination](Function::change_branch_destination), this method rewrite the destinations of - /// multi-destination branches like `br_table`. pub fn rewrite_branch_destination(&mut self, inst: Inst, old_dest: Block, new_dest: Block) { - match self.dfg.analyze_branch(inst) { - BranchInfo::SingleDest(dest, ..) => { - if dest == old_dest { - self.change_branch_destination(inst, new_dest); + match &mut self.dfg.insts[inst] { + InstructionData::Jump { + destination: dest, .. + } => { + if dest.block(&self.dfg.value_lists) == old_dest { + dest.set_block(new_dest, &mut self.dfg.value_lists) + } + } + + InstructionData::Brif { + blocks: [block_then, block_else], + .. + } => { + if block_then.block(&self.dfg.value_lists) == old_dest { + block_then.set_block(new_dest, &mut self.dfg.value_lists); + } + + if block_else.block(&self.dfg.value_lists) == old_dest { + block_else.set_block(new_dest, &mut self.dfg.value_lists); } } - BranchInfo::Table(table, default_dest) => { - self.jump_tables[table].iter_mut().for_each(|entry| { + InstructionData::BranchTable { table, .. } => { + for entry in self.dfg.jump_tables[*table].all_branches_mut() { if *entry == old_dest { *entry = new_dest; } - }); - - if default_dest == Some(old_dest) { - match &mut self.dfg[inst] { - InstructionData::BranchTable { destination, .. } => { - *destination = new_dest; - } - _ => panic!( - "Unexpected instruction {} having default destination", - self.dfg.display_inst(inst) - ), - } } } - BranchInfo::NotABranch => {} + inst => debug_assert!(!inst.opcode().is_branch()), } } @@ -302,13 +315,13 @@ impl Function { let inst_iter = self.layout.block_insts(block); // Ignore all instructions prior to the first branch. - let mut inst_iter = inst_iter.skip_while(|&inst| !dfg[inst].opcode().is_branch()); + let mut inst_iter = inst_iter.skip_while(|&inst| !dfg.insts[inst].opcode().is_branch()); // A conditional branch is permitted in a basic block only when followed // by a terminal jump instruction. if let Some(_branch) = inst_iter.next() { if let Some(next) = inst_iter.next() { - match dfg[next].opcode() { + match dfg.insts[next].opcode() { Opcode::Jump => (), _ => return Err((next, "post-branch instruction not jump")), } @@ -346,7 +359,7 @@ impl Function { .zip(self.dfg.inst_results(src)) .all(|(a, b)| self.dfg.value_type(*a) == self.dfg.value_type(*b))); - self.dfg[dst] = self.dfg[src].clone(); + self.dfg.insts[dst] = self.dfg.insts[src]; self.layout.remove_inst(src); } @@ -356,6 +369,118 @@ impl Function { pub fn fixed_stack_size(&self) -> u32 { self.sized_stack_slots.values().map(|ss| ss.size).sum() } + + /// Returns the list of relative source locations for this function. + pub(crate) fn rel_srclocs(&self) -> &SecondaryMap { + &self.srclocs + } +} + +/// Functions can be cloned, but it is not a very fast operation. +/// The clone will have all the same entity numbers as the original. +#[derive(Clone)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct Function { + /// Name of this function. + /// + /// Mostly used by `.clif` files, only there for debugging / naming purposes. + pub name: UserFuncName, + + /// All the fields required for compiling a function, independently of details irrelevant to + /// compilation and that are stored in the `FunctionParameters` `params` field instead. + pub stencil: FunctionStencil, + + /// All the parameters that can be applied onto the function stencil, that is, that don't + /// matter when caching compilation artifacts. + pub params: FunctionParameters, +} + +impl core::ops::Deref for Function { + type Target = FunctionStencil; + + fn deref(&self) -> &Self::Target { + &self.stencil + } +} + +impl core::ops::DerefMut for Function { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.stencil + } +} + +impl Function { + /// Create a function with the given name and signature. + pub fn with_name_signature(name: UserFuncName, sig: Signature) -> Self { + Self { + name, + stencil: FunctionStencil { + version_marker: VersionMarker, + signature: sig, + sized_stack_slots: StackSlots::new(), + dynamic_stack_slots: DynamicStackSlots::new(), + global_values: PrimaryMap::new(), + tables: PrimaryMap::new(), + dfg: DataFlowGraph::new(), + layout: Layout::new(), + srclocs: SecondaryMap::new(), + stack_limit: None, + }, + params: FunctionParameters::new(), + } + } + + /// Clear all data structures in this function. + pub fn clear(&mut self) { + self.stencil.clear(); + self.params.clear(); + self.name = UserFuncName::default(); + } + + /// Create a new empty, anonymous function with a Fast calling convention. + pub fn new() -> Self { + Self::with_name_signature(Default::default(), Signature::new(CallConv::Fast)) + } + + /// Return an object that can display this function with correct ISA-specific annotations. + pub fn display(&self) -> DisplayFunction<'_> { + DisplayFunction(self, Default::default()) + } + + /// Return an object that can display this function with correct ISA-specific annotations. + pub fn display_with<'a>( + &'a self, + annotations: DisplayFunctionAnnotations<'a>, + ) -> DisplayFunction<'a> { + DisplayFunction(self, annotations) + } + + /// Sets an absolute source location for the given instruction. + /// + /// If no base source location has been set yet, records it at the same time. + pub fn set_srcloc(&mut self, inst: Inst, srcloc: SourceLoc) { + let base = self.params.ensure_base_srcloc(srcloc); + self.stencil.srclocs[inst] = RelSourceLoc::from_base_offset(base, srcloc); + } + + /// Returns an absolute source location for the given instruction. + pub fn srcloc(&self, inst: Inst) -> SourceLoc { + let base = self.params.base_srcloc(); + self.stencil.srclocs[inst].expand(base) + } + + /// Declare a user-defined external function import, to be referenced in `ExtFuncData::User` later. + pub fn declare_imported_user_function( + &mut self, + name: UserExternalName, + ) -> UserExternalNameRef { + self.params.ensure_user_func_name(name) + } + + /// Declare an external function import. + pub fn import_function(&mut self, data: ExtFuncData) -> FuncRef { + self.stencil.dfg.ext_funcs.push(data) + } } /// Additional annotations for function display. diff --git a/cranelift/codegen/src/ir/globalvalue.rs b/cranelift/codegen/src/ir/globalvalue.rs index 8ec39bf0a447..84094d716658 100644 --- a/cranelift/codegen/src/ir/globalvalue.rs +++ b/cranelift/codegen/src/ir/globalvalue.rs @@ -10,7 +10,7 @@ use core::fmt; use serde::{Deserialize, Serialize}; /// Information about a global value declaration. -#[derive(Clone)] +#[derive(Clone, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum GlobalValueData { /// Value is the address of the VM context struct. @@ -70,7 +70,7 @@ pub enum GlobalValueData { /// /// If `true`, some backends may use relocation forms that have limited range: for example, /// a +/- 2^27-byte range on AArch64. See the documentation for - /// [`RelocDistance`](crate::machinst::RelocDistance) for more details. + /// `RelocDistance` for more details. colocated: bool, /// Does this symbol refer to a thread local storage value? @@ -151,7 +151,7 @@ impl fmt::Display for GlobalValueData { "symbol {}{}{}", if colocated { "colocated " } else { "" }, if tls { "tls " } else { "" }, - name + name.display(None) )?; let offset_val: i64 = offset.into(); if offset_val > 0 { diff --git a/cranelift/codegen/src/ir/heap.rs b/cranelift/codegen/src/ir/heap.rs deleted file mode 100644 index 91aabccaa2e3..000000000000 --- a/cranelift/codegen/src/ir/heap.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! Heaps. - -use crate::ir::immediates::Uimm64; -use crate::ir::{GlobalValue, Type}; -use core::fmt; - -#[cfg(feature = "enable-serde")] -use serde::{Deserialize, Serialize}; - -/// Information about a heap declaration. -#[derive(Clone)] -#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] -pub struct HeapData { - /// The address of the start of the heap's storage. - pub base: GlobalValue, - - /// Guaranteed minimum heap size in bytes. Heap accesses before `min_size` don't need bounds - /// checking. - pub min_size: Uimm64, - - /// Size in bytes of the offset-guard pages following the heap. - pub offset_guard_size: Uimm64, - - /// Heap style, with additional style-specific info. - pub style: HeapStyle, - - /// The index type for the heap. - pub index_type: Type, -} - -/// Style of heap including style-specific information. -#[derive(Clone)] -#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] -pub enum HeapStyle { - /// A dynamic heap can be relocated to a different base address when it is grown. - Dynamic { - /// Global value providing the current bound of the heap in bytes. - bound_gv: GlobalValue, - }, - - /// A static heap has a fixed base address and a number of not-yet-allocated pages before the - /// offset-guard pages. - Static { - /// Heap bound in bytes. The offset-guard pages are allocated after the bound. - bound: Uimm64, - }, -} - -impl fmt::Display for HeapData { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match self.style { - HeapStyle::Dynamic { .. } => "dynamic", - HeapStyle::Static { .. } => "static", - })?; - - write!(f, " {}, min {}", self.base, self.min_size)?; - match self.style { - HeapStyle::Dynamic { bound_gv } => write!(f, ", bound {}", bound_gv)?, - HeapStyle::Static { bound } => write!(f, ", bound {}", bound)?, - } - write!( - f, - ", offset_guard {}, index_type {}", - self.offset_guard_size, self.index_type - ) - } -} diff --git a/cranelift/codegen/src/ir/immediates.rs b/cranelift/codegen/src/ir/immediates.rs index 3dba40645262..3b3f7032353b 100644 --- a/cranelift/codegen/src/ir/immediates.rs +++ b/cranelift/codegen/src/ir/immediates.rs @@ -8,7 +8,7 @@ use alloc::vec::Vec; use core::cmp::Ordering; use core::convert::TryFrom; use core::fmt::{self, Display, Formatter}; -use core::ops::{Add, Div, Mul, Neg, Sub}; +use core::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Sub}; use core::str::FromStr; use core::{i32, u32}; #[cfg(feature = "enable-serde")] @@ -472,6 +472,12 @@ impl FromStr for Offset32 { /// An IEEE binary32 immediate floating point value, represented as a u32 /// containing the bit pattern. /// +/// We specifically avoid using a f32 here since some architectures may silently alter floats. +/// See: +/// +/// The [PartialEq] and [Hash] implementations are over the underlying bit pattern, but +/// [PartialOrd] respects IEEE754 semantics. +/// /// All bit patterns are allowed. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] @@ -481,6 +487,12 @@ pub struct Ieee32(u32); /// An IEEE binary64 immediate floating point value, represented as a u64 /// containing the bit pattern. /// +/// We specifically avoid using a f64 here since some architectures may silently alter floats. +/// See: +/// +/// The [PartialEq] and [Hash] implementations are over the underlying bit pattern, but +/// [PartialOrd] respects IEEE754 semantics. +/// /// All bit patterns are allowed. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] @@ -909,6 +921,38 @@ impl Div for Ieee32 { } } +impl BitAnd for Ieee32 { + type Output = Ieee32; + + fn bitand(self, rhs: Self) -> Self::Output { + Self::with_bits(self.bits() & rhs.bits()) + } +} + +impl BitOr for Ieee32 { + type Output = Ieee32; + + fn bitor(self, rhs: Self) -> Self::Output { + Self::with_bits(self.bits() | rhs.bits()) + } +} + +impl BitXor for Ieee32 { + type Output = Ieee32; + + fn bitxor(self, rhs: Self) -> Self::Output { + Self::with_bits(self.bits() ^ rhs.bits()) + } +} + +impl Not for Ieee32 { + type Output = Ieee32; + + fn not(self) -> Self::Output { + Self::with_bits(!self.bits()) + } +} + impl Ieee64 { /// Create a new `Ieee64` containing the bits of `x`. pub fn with_bits(x: u64) -> Self { @@ -1101,6 +1145,38 @@ impl Div for Ieee64 { } } +impl BitAnd for Ieee64 { + type Output = Ieee64; + + fn bitand(self, rhs: Self) -> Self::Output { + Self::with_bits(self.bits() & rhs.bits()) + } +} + +impl BitOr for Ieee64 { + type Output = Ieee64; + + fn bitor(self, rhs: Self) -> Self::Output { + Self::with_bits(self.bits() | rhs.bits()) + } +} + +impl BitXor for Ieee64 { + type Output = Ieee64; + + fn bitxor(self, rhs: Self) -> Self::Output { + Self::with_bits(self.bits() ^ rhs.bits()) + } +} + +impl Not for Ieee64 { + type Output = Ieee64; + + fn not(self) -> Self::Output { + Self::with_bits(!self.bits()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/cranelift/codegen/src/ir/instructions.rs b/cranelift/codegen/src/ir/instructions.rs index 02a4d48e8705..9513949a6e51 100644 --- a/cranelift/codegen/src/ir/instructions.rs +++ b/cranelift/codegen/src/ir/instructions.rs @@ -7,9 +7,7 @@ //! directory. use alloc::vec::Vec; -use core::convert::{TryFrom, TryInto}; use core::fmt::{self, Display, Formatter}; -use core::num::NonZeroU32; use core::ops::{Deref, DerefMut}; use core::str::FromStr; @@ -17,13 +15,12 @@ use core::str::FromStr; use serde::{Deserialize, Serialize}; use crate::bitset::BitSet; -use crate::data_value::DataValue; use crate::entity; use crate::ir::{ self, condcodes::{FloatCC, IntCC}, trapcode::TrapCode, - types, Block, FuncRef, JumpTable, MemFlags, SigRef, StackSlot, Type, Value, + types, Block, FuncRef, MemFlags, SigRef, StackSlot, Type, Value, }; /// Some instructions use an external list of argument values because there is not enough space in @@ -34,6 +31,133 @@ pub type ValueList = entity::EntityList; /// Memory pool for holding value lists. See `ValueList`. pub type ValueListPool = entity::ListPool; +/// A pair of a Block and its arguments, stored in a single EntityList internally. +/// +/// NOTE: We don't expose either value_to_block or block_to_value outside of this module because +/// this operation is not generally safe. However, as the two share the same underlying layout, +/// they can be stored in the same value pool. +/// +/// BlockCall makes use of this shared layout by storing all of its contents (a block and its +/// argument) in a single EntityList. This is a bit better than introducing a new entity type for +/// the pair of a block name and the arguments entity list, as we don't pay any indirection penalty +/// to get to the argument values -- they're stored in-line with the block in the same list. +/// +/// The BlockCall::new function guarantees this layout by requiring a block argument that's written +/// in as the first element of the EntityList. Any subsequent entries are always assumed to be real +/// Values. +#[derive(Debug, Clone, Copy, PartialEq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct BlockCall { + /// The underlying storage for the BlockCall. The first element of the values EntityList is + /// guaranteed to always be a Block encoded as a Value via BlockCall::block_to_value. + /// Consequently, the values entity list is never empty. + values: entity::EntityList, +} + +impl BlockCall { + // NOTE: the only uses of this function should be internal to BlockCall. See the block comment + // on BlockCall for more context. + fn value_to_block(val: Value) -> Block { + Block::from_u32(val.as_u32()) + } + + // NOTE: the only uses of this function should be internal to BlockCall. See the block comment + // on BlockCall for more context. + fn block_to_value(block: Block) -> Value { + Value::from_u32(block.as_u32()) + } + + /// Construct a BlockCall with the given block and arguments. + pub fn new(block: Block, args: &[Value], pool: &mut ValueListPool) -> Self { + let mut values = ValueList::default(); + values.push(Self::block_to_value(block), pool); + values.extend(args.iter().copied(), pool); + Self { values } + } + + /// Return the block for this BlockCall. + pub fn block(&self, pool: &ValueListPool) -> Block { + let val = self.values.first(pool).unwrap(); + Self::value_to_block(val) + } + + /// Replace the block for this BlockCall. + pub fn set_block(&mut self, block: Block, pool: &mut ValueListPool) { + *self.values.get_mut(0, pool).unwrap() = Self::block_to_value(block); + } + + /// Append an argument to the block args. + pub fn append_argument(&mut self, arg: Value, pool: &mut ValueListPool) { + self.values.push(arg, pool); + } + + /// Return a slice for the arguments of this block. + pub fn args_slice<'a>(&self, pool: &'a ValueListPool) -> &'a [Value] { + &self.values.as_slice(pool)[1..] + } + + /// Return a slice for the arguments of this block. + pub fn args_slice_mut<'a>(&'a mut self, pool: &'a mut ValueListPool) -> &'a mut [Value] { + &mut self.values.as_mut_slice(pool)[1..] + } + + /// Remove the argument at ix from the argument list. + pub fn remove(&mut self, ix: usize, pool: &mut ValueListPool) { + self.values.remove(1 + ix, pool) + } + + /// Clear out the arguments list. + pub fn clear(&mut self, pool: &mut ValueListPool) { + self.values.truncate(1, pool) + } + + /// Appends multiple elements to the arguments. + pub fn extend(&mut self, elements: I, pool: &mut ValueListPool) + where + I: IntoIterator, + { + self.values.extend(elements, pool) + } + + /// Return a value that can display this block call. + pub fn display<'a>(&self, pool: &'a ValueListPool) -> DisplayBlockCall<'a> { + DisplayBlockCall { block: *self, pool } + } + + /// Deep-clone the underlying list in the same pool. The returned + /// list will have identical contents but changes to this list + /// will not change its contents or vice-versa. + pub fn deep_clone(&self, pool: &mut ValueListPool) -> Self { + Self { + values: self.values.deep_clone(pool), + } + } +} + +/// Wrapper for the context needed to display a [BlockCall] value. +pub struct DisplayBlockCall<'a> { + block: BlockCall, + pool: &'a ValueListPool, +} + +impl<'a> Display for DisplayBlockCall<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.block.block(&self.pool))?; + let args = self.block.args_slice(&self.pool); + if !args.is_empty() { + write!(f, "(")?; + for (ix, arg) in args.iter().enumerate() { + if ix > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg)?; + } + write!(f, ")")?; + } + Ok(()) + } +} + // Include code generated by `cranelift-codegen/meta/src/gen_inst.rs`. This file contains: // // - The `pub enum InstructionFormat` enum with all the instruction formats. @@ -78,24 +202,6 @@ impl Opcode { } } -impl TryFrom for Opcode { - type Error = (); - - #[inline] - fn try_from(x: NonZeroU32) -> Result { - let x: u16 = x.get().try_into().map_err(|_| ())?; - Self::try_from(x) - } -} - -impl From for NonZeroU32 { - #[inline] - fn from(op: Opcode) -> NonZeroU32 { - let x = op as u8; - NonZeroU32::new(x as u32).unwrap() - } -} - // This trait really belongs in cranelift-reader where it is used by the `.clif` file parser, but since // it critically depends on the `opcode_name()` function which is needed here anyway, it lives in // this module. This also saves us from running the build script twice to generate code for the two @@ -195,141 +301,46 @@ impl Default for VariableArgs { /// Avoid large matches on instruction formats by using the methods defined here to examine /// instructions. impl InstructionData { - /// Return information about the destination of a branch or jump instruction. + /// Get the destinations of this instruction, if it's a branch. /// - /// Any instruction that can transfer control to another block reveals its possible destinations - /// here. - pub fn analyze_branch<'a>(&'a self, pool: &'a ValueListPool) -> BranchInfo<'a> { - match *self { + /// `br_table` returns the empty slice. + pub fn branch_destination(&self) -> &[BlockCall] { + match self { Self::Jump { - destination, - ref args, - .. - } => BranchInfo::SingleDest(destination, args.as_slice(pool)), - Self::BranchInt { - destination, - ref args, - .. - } - | Self::BranchFloat { - destination, - ref args, - .. - } - | Self::Branch { - destination, - ref args, - .. - } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[1..]), - Self::BranchIcmp { - destination, - ref args, - .. - } => BranchInfo::SingleDest(destination, &args.as_slice(pool)[2..]), - Self::BranchTable { - table, destination, .. - } => BranchInfo::Table(table, Some(destination)), - _ => { - debug_assert!(!self.opcode().is_branch()); - BranchInfo::NotABranch - } - } - } - - /// Get the single destination of this branch instruction, if it is a single destination - /// branch or jump. - /// - /// Multi-destination branches like `br_table` return `None`. - pub fn branch_destination(&self) -> Option { - match *self { - Self::Jump { destination, .. } - | Self::Branch { destination, .. } - | Self::BranchInt { destination, .. } - | Self::BranchFloat { destination, .. } - | Self::BranchIcmp { destination, .. } => Some(destination), - Self::BranchTable { .. } => None, + ref destination, .. + } => std::slice::from_ref(destination), + Self::Brif { blocks, .. } => blocks, + Self::BranchTable { .. } => &[], _ => { debug_assert!(!self.opcode().is_branch()); - None + &[] } } } - /// Get a mutable reference to the single destination of this branch instruction, if it is a - /// single destination branch or jump. + /// Get a mutable slice of the destinations of this instruction, if it's a branch. /// - /// Multi-destination branches like `br_table` return `None`. - pub fn branch_destination_mut(&mut self) -> Option<&mut Block> { - match *self { + /// `br_table` returns the empty slice. + pub fn branch_destination_mut(&mut self) -> &mut [BlockCall] { + match self { Self::Jump { ref mut destination, .. - } - | Self::Branch { - ref mut destination, - .. - } - | Self::BranchInt { - ref mut destination, - .. - } - | Self::BranchFloat { - ref mut destination, - .. - } - | Self::BranchIcmp { - ref mut destination, - .. - } => Some(destination), - Self::BranchTable { .. } => None, + } => std::slice::from_mut(destination), + Self::Brif { blocks, .. } => blocks, + Self::BranchTable { .. } => &mut [], _ => { debug_assert!(!self.opcode().is_branch()); - None + &mut [] } } } - /// Return the value of an immediate if the instruction has one or `None` otherwise. Only - /// immediate values are considered, not global values, constant handles, condition codes, etc. - pub fn imm_value(&self) -> Option { - match self { - &InstructionData::UnaryBool { imm, .. } => Some(DataValue::from(imm)), - // 8-bit. - &InstructionData::BinaryImm8 { imm, .. } - | &InstructionData::TernaryImm8 { imm, .. } => Some(DataValue::from(imm as i8)), // Note the switch from unsigned to signed. - // 32-bit - &InstructionData::UnaryIeee32 { imm, .. } => Some(DataValue::from(imm)), - &InstructionData::HeapAddr { imm, .. } => { - let imm: u32 = imm.into(); - Some(DataValue::from(imm as i32)) // Note the switch from unsigned to signed. - } - &InstructionData::Load { offset, .. } - | &InstructionData::Store { offset, .. } - | &InstructionData::StackLoad { offset, .. } - | &InstructionData::StackStore { offset, .. } - | &InstructionData::TableAddr { offset, .. } => Some(DataValue::from(offset)), - // 64-bit. - &InstructionData::UnaryImm { imm, .. } - | &InstructionData::BinaryImm64 { imm, .. } - | &InstructionData::IntCompareImm { imm, .. } => Some(DataValue::from(imm.bits())), - &InstructionData::UnaryIeee64 { imm, .. } => Some(DataValue::from(imm)), - // 128-bit; though these immediates are present logically in the IR they are not - // included in the `InstructionData` for memory-size reasons. This case, returning - // `None`, is left here to alert users of this method that they should retrieve the - // value using the `DataFlowGraph`. - &InstructionData::Shuffle { imm: _, .. } => None, - _ => None, - } - } - /// If this is a trapping instruction, get its trap code. Otherwise, return /// `None`. pub fn trap_code(&self) -> Option { match *self { - Self::CondTrap { code, .. } - | Self::FloatCondTrap { code, .. } - | Self::IntCondTrap { code, .. } - | Self::Trap { code, .. } => Some(code), + Self::CondTrap { code, .. } | Self::Trap { code, .. } => Some(code), _ => None, } } @@ -338,12 +349,7 @@ impl InstructionData { /// condition. Otherwise, return `None`. pub fn cond_code(&self) -> Option { match self { - &InstructionData::IntCond { cond, .. } - | &InstructionData::BranchIcmp { cond, .. } - | &InstructionData::IntCompare { cond, .. } - | &InstructionData::IntCondTrap { cond, .. } - | &InstructionData::BranchInt { cond, .. } - | &InstructionData::IntSelect { cond, .. } + &InstructionData::IntCompare { cond, .. } | &InstructionData::IntCompareImm { cond, .. } => Some(cond), _ => None, } @@ -353,10 +359,7 @@ impl InstructionData { /// condition. Otherwise, return `None`. pub fn fp_cond_code(&self) -> Option { match self { - &InstructionData::BranchFloat { cond, .. } - | &InstructionData::FloatCompare { cond, .. } - | &InstructionData::FloatCond { cond, .. } - | &InstructionData::FloatCondTrap { cond, .. } => Some(cond), + &InstructionData::FloatCompare { cond, .. } => Some(cond), _ => None, } } @@ -365,10 +368,7 @@ impl InstructionData { /// trap code. Otherwise, return `None`. pub fn trap_code_mut(&mut self) -> Option<&mut TrapCode> { match self { - Self::CondTrap { code, .. } - | Self::FloatCondTrap { code, .. } - | Self::IntCondTrap { code, .. } - | Self::Trap { code, .. } => Some(code), + Self::CondTrap { code, .. } | Self::Trap { code, .. } => Some(code), _ => None, } } @@ -464,20 +464,6 @@ impl InstructionData { } } -/// Information about branch and jump instructions. -pub enum BranchInfo<'a> { - /// This is not a branch or jump instruction. - /// This instruction will not transfer control to another block in the function, but it may still - /// affect control flow by returning or trapping. - NotABranch, - - /// This is a branch or jump to a single destination block, possibly taking value arguments. - SingleDest(Block, &'a [Value]), - - /// This is a jump table branch which can have many destination blocks and maybe one default block. - Table(JumpTable, Option), -} - /// Information about call instructions. pub enum CallInfo<'a> { /// This is not a call instruction. @@ -629,8 +615,6 @@ pub struct ValueTypeSet { pub ints: BitSet8, /// Allowed float widths pub floats: BitSet8, - /// Allowed bool widths - pub bools: BitSet8, /// Allowed ref widths pub refs: BitSet8, /// Allowed dynamic vectors minimum lane sizes @@ -647,8 +631,6 @@ impl ValueTypeSet { self.ints.contains(l2b) } else if scalar.is_float() { self.floats.contains(l2b) - } else if scalar.is_bool() { - self.bools.contains(l2b) } else if scalar.is_ref() { self.refs.contains(l2b) } else { @@ -675,10 +657,8 @@ impl ValueTypeSet { types::I32 } else if self.floats.max().unwrap_or(0) > 5 { types::F32 - } else if self.bools.max().unwrap_or(0) > 5 { - types::B32 } else { - types::B1 + types::I8 }; t.by(1 << self.lanes.min().unwrap()).unwrap() } @@ -708,12 +688,6 @@ enum OperandConstraint { /// This operand is `ctrlType.double_width()`. DoubleWidth, - /// This operand is `ctrlType.half_vector()`. - HalfVector, - - /// This operand is `ctrlType.double_vector()`. - DoubleVector, - /// This operand is `ctrlType.split_lanes()`. SplitLanes, @@ -742,12 +716,6 @@ impl OperandConstraint { .double_width() .expect("invalid type for double_width"), ), - HalfVector => Bound( - ctrl_type - .half_vector() - .expect("invalid type for half_vector"), - ), - DoubleVector => Bound(ctrl_type.by(2).expect("invalid type for double_vector")), SplitLanes => { if ctrl_type.is_dynamic_vector() { Bound( @@ -809,6 +777,19 @@ mod tests { use super::*; use alloc::string::ToString; + #[test] + fn inst_data_is_copy() { + fn is_copy() {} + is_copy::(); + } + + #[test] + fn inst_data_size() { + // The size of `InstructionData` is performance sensitive, so make sure + // we don't regress it unintentionally. + assert_eq!(std::mem::size_of::(), 16); + } + #[test] fn opcodes() { use core::mem; @@ -901,7 +882,6 @@ mod tests { lanes: BitSet16::from_range(0, 8), ints: BitSet8::from_range(4, 7), floats: BitSet8::from_range(0, 0), - bools: BitSet8::from_range(3, 7), refs: BitSet8::from_range(5, 7), dynamic_lanes: BitSet16::from_range(0, 4), }; @@ -911,9 +891,6 @@ mod tests { assert!(vts.contains(I32X4)); assert!(vts.contains(I32X4XN)); assert!(!vts.contains(F32)); - assert!(!vts.contains(B1)); - assert!(vts.contains(B8)); - assert!(vts.contains(B64)); assert!(vts.contains(R32)); assert!(vts.contains(R64)); assert_eq!(vts.example().to_string(), "i32"); @@ -922,7 +899,6 @@ mod tests { lanes: BitSet16::from_range(0, 8), ints: BitSet8::from_range(0, 0), floats: BitSet8::from_range(5, 7), - bools: BitSet8::from_range(3, 7), refs: BitSet8::from_range(0, 0), dynamic_lanes: BitSet16::from_range(0, 8), }; @@ -932,7 +908,6 @@ mod tests { lanes: BitSet16::from_range(1, 8), ints: BitSet8::from_range(0, 0), floats: BitSet8::from_range(5, 7), - bools: BitSet8::from_range(3, 7), refs: BitSet8::from_range(0, 0), dynamic_lanes: BitSet16::from_range(0, 8), }; @@ -940,23 +915,18 @@ mod tests { let vts = ValueTypeSet { lanes: BitSet16::from_range(2, 8), - ints: BitSet8::from_range(0, 0), + ints: BitSet8::from_range(3, 7), floats: BitSet8::from_range(0, 0), - bools: BitSet8::from_range(3, 7), refs: BitSet8::from_range(0, 0), dynamic_lanes: BitSet16::from_range(0, 8), }; - assert!(!vts.contains(B32X2)); - assert!(vts.contains(B32X4)); - assert!(vts.contains(B16X4XN)); - assert_eq!(vts.example().to_string(), "b32x4"); + assert_eq!(vts.example().to_string(), "i32x4"); let vts = ValueTypeSet { // TypeSet(lanes=(1, 256), ints=(8, 64)) lanes: BitSet16::from_range(0, 9), ints: BitSet8::from_range(3, 7), floats: BitSet8::from_range(0, 0), - bools: BitSet8::from_range(0, 0), refs: BitSet8::from_range(0, 0), dynamic_lanes: BitSet16::from_range(0, 8), }; diff --git a/cranelift/codegen/src/ir/jumptable.rs b/cranelift/codegen/src/ir/jumptable.rs index bf05169d363b..4a847a15eb07 100644 --- a/cranelift/codegen/src/ir/jumptable.rs +++ b/cranelift/codegen/src/ir/jumptable.rs @@ -14,7 +14,12 @@ use serde::{Deserialize, Serialize}; /// Contents of a jump table. /// /// All jump tables use 0-based indexing and are densely populated. -#[derive(Clone)] +/// +/// The default block for the jump table is stored as the first element of the underlying vector. +/// It can be accessed through the `default_block` and `default_block_mut` functions. All blocks +/// may be iterated using the `all_branches` and `all_branches_mut` functions, which will both +/// iterate over the default block first. +#[derive(Clone, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct JumpTableData { // Table entries. @@ -22,68 +27,70 @@ pub struct JumpTableData { } impl JumpTableData { - /// Create a new empty jump table. - pub fn new() -> Self { - Self { table: Vec::new() } - } - - /// Create a new empty jump table with the specified capacity. - pub fn with_capacity(capacity: usize) -> Self { + /// Create a new jump table with the provided blocks + pub fn new(def: Block, table: &[Block]) -> Self { Self { - table: Vec::with_capacity(capacity), + table: std::iter::once(def).chain(table.iter().copied()).collect(), } } - /// Get the number of table entries. - pub fn len(&self) -> usize { - self.table.len() + /// Fetch the default block for this jump table. + pub fn default_block(&self) -> Block { + *self.table.first().unwrap() } - /// Append a table entry. - pub fn push_entry(&mut self, dest: Block) { - self.table.push(dest) + /// Mutable access to the default block of this jump table. + pub fn default_block_mut(&mut self) -> &mut Block { + self.table.first_mut().unwrap() } - /// Checks if any of the entries branch to `block`. - pub fn branches_to(&self, block: Block) -> bool { - self.table.iter().any(|target_block| *target_block == block) + /// The jump table and default block as a single slice. The default block will always be first. + pub fn all_branches(&self) -> &[Block] { + self.table.as_slice() + } + + /// The jump table and default block as a single mutable slice. The default block will always + /// be first. + pub fn all_branches_mut(&mut self) -> &mut [Block] { + self.table.as_mut_slice() } - /// Access the whole table as a slice. + /// Access the jump table as a slice. This excludes the default block. pub fn as_slice(&self) -> &[Block] { - self.table.as_slice() + &self.table.as_slice()[1..] } - /// Access the whole table as a mutable slice. + /// Access the jump table as a mutable slice. This excludes the default block. pub fn as_mut_slice(&mut self) -> &mut [Block] { - self.table.as_mut_slice() + &mut self.table.as_mut_slice()[1..] } - /// Returns an iterator over the table. + /// Returns an iterator to the jump table, excluding the default block. + #[deprecated(since = "7.0.0", note = "please use `.as_slice()` instead")] pub fn iter(&self) -> Iter { - self.table.iter() + self.as_slice().iter() } - /// Returns an iterator that allows modifying each value. + /// Returns an iterator that allows modifying each value, excluding the default block. + #[deprecated(since = "7.0.0", note = "please use `.as_mut_slice()` instead")] pub fn iter_mut(&mut self) -> IterMut { - self.table.iter_mut() + self.as_mut_slice().iter_mut() } - /// Clears all entries in this jump table. + /// Clears all entries in this jump table, except for the default block. pub fn clear(&mut self) { - self.table.clear(); + self.table.drain(1..); } } impl Display for JumpTableData { fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { - write!(fmt, "jump_table [")?; - match self.table.first() { - None => (), - Some(first) => write!(fmt, "{}", first)?, - } - for block in self.table.iter().skip(1) { - write!(fmt, ", {}", block)?; + write!(fmt, "{}, [", self.default_block())?; + if let Some((first, rest)) = self.as_slice().split_first() { + write!(fmt, "{}", first)?; + for block in rest { + write!(fmt, ", {}", block)?; + } } write!(fmt, "]") } @@ -98,31 +105,33 @@ mod tests { #[test] fn empty() { - let jt = JumpTableData::new(); + let def = Block::new(0); + + let jt = JumpTableData::new(def, &[]); + + assert_eq!(jt.all_branches().get(0), Some(&def)); assert_eq!(jt.as_slice().get(0), None); assert_eq!(jt.as_slice().get(10), None); - assert_eq!(jt.to_string(), "jump_table []"); + assert_eq!(jt.to_string(), "block0, []"); - let v = jt.as_slice(); - assert_eq!(v, []); + assert_eq!(jt.all_branches(), [def]); + assert_eq!(jt.as_slice(), []); } #[test] fn insert() { + let def = Block::new(0); let e1 = Block::new(1); let e2 = Block::new(2); - let mut jt = JumpTableData::new(); - - jt.push_entry(e1); - jt.push_entry(e2); - jt.push_entry(e1); + let jt = JumpTableData::new(def, &[e1, e2, e1]); - assert_eq!(jt.to_string(), "jump_table [block1, block2, block1]"); + assert_eq!(jt.default_block(), def); + assert_eq!(jt.to_string(), "block0, [block1, block2, block1]"); - let v = jt.as_slice(); - assert_eq!(v, [e1, e2, e1]); + assert_eq!(jt.all_branches(), [def, e1, e2, e1]); + assert_eq!(jt.as_slice(), [e1, e2, e1]); } } diff --git a/cranelift/codegen/src/ir/known_symbol.rs b/cranelift/codegen/src/ir/known_symbol.rs new file mode 100644 index 000000000000..0dd5274d7e3e --- /dev/null +++ b/cranelift/codegen/src/ir/known_symbol.rs @@ -0,0 +1,47 @@ +use core::fmt; +use core::str::FromStr; +#[cfg(feature = "enable-serde")] +use serde::{Deserialize, Serialize}; + +/// A well-known symbol. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub enum KnownSymbol { + /// ELF well-known linker symbol _GLOBAL_OFFSET_TABLE_ + ElfGlobalOffsetTable, + /// TLS index symbol for the current thread. + /// Used in COFF/PE file formats. + CoffTlsIndex, +} + +impl fmt::Display for KnownSymbol { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +impl FromStr for KnownSymbol { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "ElfGlobalOffsetTable" => Ok(Self::ElfGlobalOffsetTable), + "CoffTlsIndex" => Ok(Self::CoffTlsIndex), + _ => Err(()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parsing() { + assert_eq!( + "ElfGlobalOffsetTable".parse(), + Ok(KnownSymbol::ElfGlobalOffsetTable) + ); + assert_eq!("CoffTlsIndex".parse(), Ok(KnownSymbol::CoffTlsIndex)); + } +} diff --git a/cranelift/codegen/src/ir/layout.rs b/cranelift/codegen/src/ir/layout.rs index ec411b4b171b..644665bab5a8 100644 --- a/cranelift/codegen/src/ir/layout.rs +++ b/cranelift/codegen/src/ir/layout.rs @@ -4,7 +4,6 @@ //! determined by the `Layout` data structure defined in this module. use crate::entity::SecondaryMap; -use crate::ir::dfg::DataFlowGraph; use crate::ir::progpoint::{ExpandedProgramPoint, ProgramOrder}; use crate::ir::{Block, Inst}; use crate::packed_option::PackedOption; @@ -25,7 +24,7 @@ use core::iter::{IntoIterator, Iterator}; /// While data dependencies are not recorded, instruction ordering does affect control /// dependencies, so part of the semantics of the program are determined by the layout. /// -#[derive(Clone)] +#[derive(Debug, Clone, PartialEq, Hash)] pub struct Layout { /// Linked list nodes for the layout order of blocks Forms a doubly linked list, terminated in /// both ends by `None`. @@ -311,7 +310,7 @@ impl Layout { /// /// This doesn't affect the position of anything, but it gives more room in the internal /// sequence numbers for inserting instructions later. - fn full_renumber(&mut self) { + pub(crate) fn full_renumber(&mut self) { let _tt = timing::layout_renumber(); let mut seq = 0; let mut next_block = self.first_block; @@ -486,7 +485,7 @@ impl Layout { /// A single node in the linked-list of blocks. // Whenever you add new fields here, don't forget to update the custom serializer for `Layout` too. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq, Hash)] struct BlockNode { prev: PackedOption, next: PackedOption, @@ -594,19 +593,6 @@ impl Layout { self.insts[inst].prev.expand() } - /// Fetch the first instruction in a block's terminal branch group. - pub fn canonical_branch_inst(&self, dfg: &DataFlowGraph, block: Block) -> Option { - // Basic blocks permit at most two terminal branch instructions. - // If two, the former is conditional and the latter is unconditional. - let last = self.last_inst(block)?; - if let Some(prev) = self.prev_inst(last) { - if dfg[prev].opcode().is_branch() { - return Some(prev); - } - } - Some(last) - } - /// Insert `inst` before the instruction `before` in the same block. pub fn insert_inst(&mut self, inst: Inst, before: Inst) { debug_assert_eq!(self.inst_block(inst), None); @@ -662,24 +648,6 @@ impl Layout { } } - /// Iterate over a limited set of instruction which are likely the branches of `block` in layout - /// order. Any instruction not visited by this iterator is not a branch, but an instruction visited by this may not be a branch. - pub fn block_likely_branches(&self, block: Block) -> Insts { - // Note: Checking whether an instruction is a branch or not while walking backward might add - // extra overhead. However, we know that the number of branches is limited to 2 at the end of - // each block, and therefore we can just iterate over the last 2 instructions. - let mut iter = self.block_insts(block); - let head = iter.head; - let tail = iter.tail; - iter.next_back(); - let head = iter.next_back().or(head); - Insts { - layout: self, - head, - tail, - } - } - /// Split the block containing `before` in two. /// /// Insert `new_block` after the old block and move `before` and the following instructions to @@ -748,7 +716,7 @@ impl Layout { } } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq, Hash)] struct InstNode { /// The Block containing this instruction, or `None` if the instruction is not yet inserted. block: PackedOption, diff --git a/cranelift/codegen/src/ir/libcall.rs b/cranelift/codegen/src/ir/libcall.rs index adf343a21687..6734258ed735 100644 --- a/cranelift/codegen/src/ir/libcall.rs +++ b/cranelift/codegen/src/ir/libcall.rs @@ -1,7 +1,9 @@ //! Naming well-known routines in the runtime library. -use crate::ir::{types, AbiParam, ExternalName, FuncRef, Function, Opcode, Signature, Type}; -use crate::isa::CallConv; +use crate::{ + ir::{types, AbiParam, ExternalName, FuncRef, Function, Signature}, + isa::CallConv, +}; use core::fmt; use core::str::FromStr; #[cfg(feature = "enable-serde")] @@ -21,20 +23,6 @@ pub enum LibCall { /// probe for stack overflow. These are emitted for functions which need /// when the `enable_probestack` setting is true. Probestack, - /// udiv.i64 - UdivI64, - /// sdiv.i64 - SdivI64, - /// urem.i64 - UremI64, - /// srem.i64 - SremI64, - /// ishl.i64 - IshlI64, - /// ushr.i64 - UshrI64, - /// sshr.i64 - SshrI64, /// ceil.f32 CeilF32, /// ceil.f64 @@ -66,6 +54,8 @@ pub enum LibCall { /// Elf __tls_get_addr ElfTlsGetAddr, + /// Elf __tls_get_offset + ElfTlsGetOffset, // When adding a new variant make sure to add it to `all_libcalls` too. } @@ -81,13 +71,6 @@ impl FromStr for LibCall { fn from_str(s: &str) -> Result { match s { "Probestack" => Ok(Self::Probestack), - "UdivI64" => Ok(Self::UdivI64), - "SdivI64" => Ok(Self::SdivI64), - "UremI64" => Ok(Self::UremI64), - "SremI64" => Ok(Self::SremI64), - "IshlI64" => Ok(Self::IshlI64), - "UshrI64" => Ok(Self::UshrI64), - "SshrI64" => Ok(Self::SshrI64), "CeilF32" => Ok(Self::CeilF32), "CeilF64" => Ok(Self::CeilF64), "FloorF32" => Ok(Self::FloorF32), @@ -104,60 +87,18 @@ impl FromStr for LibCall { "Memcmp" => Ok(Self::Memcmp), "ElfTlsGetAddr" => Ok(Self::ElfTlsGetAddr), + "ElfTlsGetOffset" => Ok(Self::ElfTlsGetOffset), _ => Err(()), } } } impl LibCall { - /// Get the well-known library call name to use as a replacement for an instruction with the - /// given opcode and controlling type variable. - /// - /// Returns `None` if no well-known library routine name exists for that instruction. - pub fn for_inst(opcode: Opcode, ctrl_type: Type) -> Option { - Some(match ctrl_type { - types::I64 => match opcode { - Opcode::Udiv => Self::UdivI64, - Opcode::Sdiv => Self::SdivI64, - Opcode::Urem => Self::UremI64, - Opcode::Srem => Self::SremI64, - Opcode::Ishl => Self::IshlI64, - Opcode::Ushr => Self::UshrI64, - Opcode::Sshr => Self::SshrI64, - _ => return None, - }, - types::F32 => match opcode { - Opcode::Ceil => Self::CeilF32, - Opcode::Floor => Self::FloorF32, - Opcode::Trunc => Self::TruncF32, - Opcode::Nearest => Self::NearestF32, - Opcode::Fma => Self::FmaF32, - _ => return None, - }, - types::F64 => match opcode { - Opcode::Ceil => Self::CeilF64, - Opcode::Floor => Self::FloorF64, - Opcode::Trunc => Self::TruncF64, - Opcode::Nearest => Self::NearestF64, - Opcode::Fma => Self::FmaF64, - _ => return None, - }, - _ => return None, - }) - } - /// Get a list of all known `LibCall`'s. pub fn all_libcalls() -> &'static [LibCall] { use LibCall::*; &[ Probestack, - UdivI64, - SdivI64, - UremI64, - SremI64, - IshlI64, - UshrI64, - SshrI64, CeilF32, CeilF64, FloorF32, @@ -173,6 +114,7 @@ impl LibCall { Memmove, Memcmp, ElfTlsGetAddr, + ElfTlsGetOffset, ] } @@ -182,17 +124,6 @@ impl LibCall { let mut sig = Signature::new(call_conv); match self { - LibCall::UdivI64 - | LibCall::SdivI64 - | LibCall::UremI64 - | LibCall::SremI64 - | LibCall::IshlI64 - | LibCall::UshrI64 - | LibCall::SshrI64 => { - sig.params.push(AbiParam::new(I64)); - sig.params.push(AbiParam::new(I64)); - sig.returns.push(AbiParam::new(I64)); - } LibCall::CeilF32 | LibCall::FloorF32 | LibCall::TruncF32 | LibCall::NearestF32 => { sig.params.push(AbiParam::new(F32)); sig.returns.push(AbiParam::new(F32)); @@ -214,7 +145,8 @@ impl LibCall { | LibCall::Memset | LibCall::Memmove | LibCall::Memcmp - | LibCall::ElfTlsGetAddr => unimplemented!(), + | LibCall::ElfTlsGetAddr + | LibCall::ElfTlsGetOffset => unimplemented!(), } sig diff --git a/cranelift/codegen/src/ir/mod.rs b/cranelift/codegen/src/ir/mod.rs index ac0a3bb44cef..7b000c8e72e7 100644 --- a/cranelift/codegen/src/ir/mod.rs +++ b/cranelift/codegen/src/ir/mod.rs @@ -11,10 +11,10 @@ mod extfunc; mod extname; pub mod function; mod globalvalue; -mod heap; pub mod immediates; pub mod instructions; pub mod jumptable; +pub(crate) mod known_symbol; pub mod layout; pub(crate) mod libcall; mod memflags; @@ -33,27 +33,28 @@ pub use crate::ir::builder::{ InsertBuilder, InstBuilder, InstBuilderBase, InstInserterBase, ReplaceBuilder, }; pub use crate::ir::constant::{ConstantData, ConstantPool}; -pub use crate::ir::dfg::{DataFlowGraph, ValueDef}; -pub use crate::ir::dynamic_type::{DynamicTypeData, DynamicTypes}; +pub use crate::ir::dfg::{BlockData, DataFlowGraph, ValueDef}; +pub use crate::ir::dynamic_type::{dynamic_to_fixed, DynamicTypeData, DynamicTypes}; pub use crate::ir::entities::{ - Block, Constant, DynamicStackSlot, DynamicType, FuncRef, GlobalValue, Heap, Immediate, Inst, - JumpTable, SigRef, StackSlot, Table, Value, + Block, Constant, DynamicStackSlot, DynamicType, FuncRef, GlobalValue, Immediate, Inst, + JumpTable, SigRef, StackSlot, Table, UserExternalNameRef, Value, }; pub use crate::ir::extfunc::{ AbiParam, ArgumentExtension, ArgumentPurpose, ExtFuncData, Signature, }; -pub use crate::ir::extname::ExternalName; +pub use crate::ir::extname::{ExternalName, UserExternalName, UserFuncName}; pub use crate::ir::function::{DisplayFunctionAnnotations, Function}; pub use crate::ir::globalvalue::GlobalValueData; -pub use crate::ir::heap::{HeapData, HeapStyle}; pub use crate::ir::instructions::{ - InstructionData, Opcode, ValueList, ValueListPool, VariableArgs, + BlockCall, InstructionData, Opcode, ValueList, ValueListPool, VariableArgs, }; pub use crate::ir::jumptable::JumpTableData; +pub use crate::ir::known_symbol::KnownSymbol; pub use crate::ir::layout::Layout; pub use crate::ir::libcall::{get_probestack_funcref, LibCall}; pub use crate::ir::memflags::{Endianness, MemFlags}; pub use crate::ir::progpoint::{ExpandedProgramPoint, ProgramOrder, ProgramPoint}; +pub use crate::ir::sourceloc::RelSourceLoc; pub use crate::ir::sourceloc::SourceLoc; pub use crate::ir::stackslot::{ DynamicStackSlotData, DynamicStackSlots, StackSlotData, StackSlotKind, StackSlots, @@ -69,7 +70,7 @@ use crate::entity::{entity_impl, PrimaryMap, SecondaryMap}; pub type JumpTables = PrimaryMap; /// Source locations for instructions. -pub type SourceLocs = SecondaryMap; +pub(crate) type SourceLocs = SecondaryMap; /// Marked with a label value. #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -78,18 +79,18 @@ pub struct ValueLabel(u32); entity_impl!(ValueLabel, "val"); /// A label of a Value. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct ValueLabelStart { /// Source location when it is in effect - pub from: SourceLoc, + pub from: RelSourceLoc, /// The label index. pub label: ValueLabel, } /// Value label assignements: label starts or value aliases. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum ValueLabelAssignments { /// Original value labels assigned at transform. @@ -98,7 +99,7 @@ pub enum ValueLabelAssignments { /// A value alias to original value. Alias { /// Source location when it is in effect - from: SourceLoc, + from: RelSourceLoc, /// The label index. value: Value, diff --git a/cranelift/codegen/src/ir/progpoint.rs b/cranelift/codegen/src/ir/progpoint.rs index 0152949e7af0..39c4d98fbe3c 100644 --- a/cranelift/codegen/src/ir/progpoint.rs +++ b/cranelift/codegen/src/ir/progpoint.rs @@ -37,6 +37,7 @@ impl From for ProgramPoint { match def { ValueDef::Result(inst, _) => inst.into(), ValueDef::Param(block, _) => block.into(), + ValueDef::Union(_, _) => panic!("Union does not have a single program point"), } } } @@ -78,6 +79,7 @@ impl From for ExpandedProgramPoint { match def { ValueDef::Result(inst, _) => inst.into(), ValueDef::Param(block, _) => block.into(), + ValueDef::Union(_, _) => panic!("Union does not have a single program point"), } } } diff --git a/cranelift/codegen/src/ir/sourceloc.rs b/cranelift/codegen/src/ir/sourceloc.rs index ccab62f89bd4..53331ee8c67f 100644 --- a/cranelift/codegen/src/ir/sourceloc.rs +++ b/cranelift/codegen/src/ir/sourceloc.rs @@ -14,7 +14,7 @@ use serde::{Deserialize, Serialize}; /// /// The default source location uses the all-ones bit pattern `!0`. It is used for instructions /// that can't be given a real source location. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct SourceLoc(u32); @@ -51,6 +51,57 @@ impl fmt::Display for SourceLoc { } } +/// Source location relative to another base source location. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub struct RelSourceLoc(u32); + +impl RelSourceLoc { + /// Create a new relative source location with the given bits. + pub fn new(bits: u32) -> Self { + Self(bits) + } + + /// Creates a new `RelSourceLoc` based on the given base and offset. + pub fn from_base_offset(base: SourceLoc, offset: SourceLoc) -> Self { + if base.is_default() || offset.is_default() { + Self::default() + } else { + Self(offset.bits().wrapping_sub(base.bits())) + } + } + + /// Expands the relative source location into an absolute one, using the given base. + pub fn expand(&self, base: SourceLoc) -> SourceLoc { + if self.is_default() || base.is_default() { + Default::default() + } else { + SourceLoc::new(self.0.wrapping_add(base.bits())) + } + } + + /// Is this the default relative source location? + pub fn is_default(self) -> bool { + self == Default::default() + } +} + +impl Default for RelSourceLoc { + fn default() -> Self { + Self(!0) + } +} + +impl fmt::Display for RelSourceLoc { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.is_default() { + write!(f, "@-") + } else { + write!(f, "@+{:04x}", self.0) + } + } +} + #[cfg(test)] mod tests { use crate::ir::SourceLoc; diff --git a/cranelift/codegen/src/ir/stackslot.rs b/cranelift/codegen/src/ir/stackslot.rs index e4db80d5d75c..aa77a7ac7e57 100644 --- a/cranelift/codegen/src/ir/stackslot.rs +++ b/cranelift/codegen/src/ir/stackslot.rs @@ -27,7 +27,7 @@ use serde::{Deserialize, Serialize}; pub type StackSize = u32; /// The kind of a stack slot. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub enum StackSlotKind { /// An explicit stack slot. This is a chunk of stack memory for use by the `stack_load` @@ -62,7 +62,7 @@ impl fmt::Display for StackSlotKind { } /// Contents of a stack slot. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct StackSlotData { /// The kind of stack slot. @@ -100,7 +100,7 @@ impl fmt::Display for StackSlotData { } /// Contents of a dynamic stack slot. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct DynamicStackSlotData { /// The kind of stack slot. diff --git a/cranelift/codegen/src/ir/table.rs b/cranelift/codegen/src/ir/table.rs index 713d1f5df799..6acfb14fa179 100644 --- a/cranelift/codegen/src/ir/table.rs +++ b/cranelift/codegen/src/ir/table.rs @@ -8,7 +8,7 @@ use core::fmt; use serde::{Deserialize, Serialize}; /// Information about a table declaration. -#[derive(Clone)] +#[derive(Clone, PartialEq, Hash)] #[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] pub struct TableData { /// Global value giving the address of the start of the table. diff --git a/cranelift/codegen/src/ir/trapcode.rs b/cranelift/codegen/src/ir/trapcode.rs index 0ef55a81d7b6..590c82a8b3df 100644 --- a/cranelift/codegen/src/ir/trapcode.rs +++ b/cranelift/codegen/src/ir/trapcode.rs @@ -49,13 +49,29 @@ pub enum TrapCode { /// This trap is resumable. Interrupt, - /// A reference that should not be null was null - NullReference, - /// A user-defined trap code. User(u16), } +impl TrapCode { + /// Returns a slice of all traps except `TrapCode::User` traps + pub const fn non_user_traps() -> &'static [TrapCode] { + &[ + TrapCode::StackOverflow, + TrapCode::HeapOutOfBounds, + TrapCode::HeapMisaligned, + TrapCode::TableOutOfBounds, + TrapCode::IndirectCallToNull, + TrapCode::BadSignature, + TrapCode::IntegerOverflow, + TrapCode::IntegerDivisionByZero, + TrapCode::BadConversionToInteger, + TrapCode::UnreachableCodeReached, + TrapCode::Interrupt, + ] + } +} + impl Display for TrapCode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { use self::TrapCode::*; @@ -71,7 +87,6 @@ impl Display for TrapCode { BadConversionToInteger => "bad_toint", UnreachableCodeReached => "unreachable", Interrupt => "interrupt", - NullReference => "null_reference", User(x) => return write!(f, "user{}", x), }; f.write_str(identifier) @@ -95,7 +110,6 @@ impl FromStr for TrapCode { "bad_toint" => Ok(BadConversionToInteger), "unreachable" => Ok(UnreachableCodeReached), "interrupt" => Ok(Interrupt), - "null_reference" => Ok(NullReference), _ if s.starts_with("user") => s[4..].parse().map(User).map_err(|_| ()), _ => Err(()), } @@ -107,24 +121,9 @@ mod tests { use super::*; use alloc::string::ToString; - // Everything but user-defined codes. - const CODES: [TrapCode; 11] = [ - TrapCode::StackOverflow, - TrapCode::HeapOutOfBounds, - TrapCode::HeapMisaligned, - TrapCode::TableOutOfBounds, - TrapCode::IndirectCallToNull, - TrapCode::BadSignature, - TrapCode::IntegerOverflow, - TrapCode::IntegerDivisionByZero, - TrapCode::BadConversionToInteger, - TrapCode::UnreachableCodeReached, - TrapCode::Interrupt, - ]; - #[test] fn display() { - for r in &CODES { + for r in TrapCode::non_user_traps() { let tc = *r; assert_eq!(tc.to_string().parse(), Ok(tc)); } diff --git a/cranelift/codegen/src/ir/types.rs b/cranelift/codegen/src/ir/types.rs index 311addadf7cf..2bbbd223055b 100644 --- a/cranelift/codegen/src/ir/types.rs +++ b/cranelift/codegen/src/ir/types.rs @@ -17,10 +17,7 @@ use target_lexicon::{PointerWidth, Triple}; /// /// Basic floating point types: `F32` and `F64`. IEEE single and double precision. /// -/// Boolean types: `B1`, `B8`, `B16`, `B32`, `B64`, and `B128`. These all encode 'true' or 'false'. The -/// larger types use redundant bits. -/// -/// SIMD vector types have power-of-two lanes, up to 256. Lanes can be any int/float/bool type. +/// SIMD vector types have power-of-two lanes, up to 256. Lanes can be any int/float type. /// /// Note that this is encoded in a `u16` currently for extensibility, /// but allows only 14 bits to be used due to some bitpacking tricks @@ -59,12 +56,11 @@ impl Type { /// Get log_2 of the number of bits in a lane. pub fn log2_lane_bits(self) -> u32 { match self.lane_type() { - B1 => 0, - B8 | I8 => 3, - B16 | I16 => 4, - B32 | I32 | F32 | R32 => 5, - B64 | I64 | F64 | R64 => 6, - B128 | I128 => 7, + I8 => 3, + I16 => 4, + I32 | F32 | R32 => 5, + I64 | F64 | R64 => 6, + I128 => 7, _ => 0, } } @@ -72,12 +68,11 @@ impl Type { /// Get the number of bits in a lane. pub fn lane_bits(self) -> u32 { match self.lane_type() { - B1 => 1, - B8 | I8 => 8, - B16 | I16 => 16, - B32 | I32 | F32 | R32 => 32, - B64 | I64 | F64 | R64 => 64, - B128 | I128 => 128, + I8 => 8, + I16 => 16, + I32 | F32 | R32 => 32, + I64 | F64 | R64 => 64, + I128 => 128, _ => 0, } } @@ -141,13 +136,13 @@ impl Type { pub fn as_bool_pedantic(self) -> Self { // Replace the low 4 bits with the boolean version, preserve the high 4 bits. self.replace_lanes(match self.lane_type() { - B8 | I8 => B8, - B16 | I16 => B16, - B32 | I32 | F32 => B32, - B64 | I64 | F64 => B64, + I8 => I8, + I16 => I16, + I32 | F32 => I32, + I64 | F64 => I64, R32 | R64 => panic!("Reference types should not convert to bool"), - B128 | I128 => B128, - _ => B1, + I128 => I128, + _ => I8, }) } @@ -157,7 +152,7 @@ impl Type { /// Scalar types are all converted to `b1` which is usually what you want. pub fn as_bool(self) -> Self { if !self.is_vector() { - B1 + I8 } else { self.as_bool_pedantic() } @@ -169,11 +164,11 @@ impl Type { /// Scalar types follow this same rule, but `b1` is converted into `i8` pub fn as_int(self) -> Self { self.replace_lanes(match self.lane_type() { - I8 | B1 | B8 => I8, - I16 | B16 => I16, - I32 | B32 | F32 => I32, - I64 | B64 | F64 => I64, - I128 | B128 => I128, + I8 => I8, + I16 => I16, + I32 | F32 => I32, + I64 | F64 => I64, + I128 => I128, _ => unimplemented!(), }) } @@ -187,10 +182,6 @@ impl Type { I64 => I32, I128 => I64, F64 => F32, - B16 => B8, - B32 => B16, - B64 => B32, - B128 => B64, _ => return None, })) } @@ -204,10 +195,6 @@ impl Type { I32 => I64, I64 => I128, F32 => F64, - B8 => B16, - B16 => B32, - B32 => B64, - B64 => B128, _ => return None, })) } @@ -241,19 +228,6 @@ impl Type { self.0 >= constants::DYNAMIC_VECTOR_BASE } - /// Is this a scalar boolean type? - pub fn is_bool(self) -> bool { - match self { - B1 | B8 | B16 | B32 | B64 | B128 => true, - _ => false, - } - } - - /// Is this a vector boolean type? - pub fn is_bool_vector(self) -> bool { - self.is_vector() && self.lane_type().is_bool() - } - /// Is this a scalar integer type? pub fn is_int(self) -> bool { match self { @@ -270,14 +244,6 @@ impl Type { } } - /// Is this a CPU flags type? - pub fn is_flags(self) -> bool { - match self { - IFLAGS | FFLAGS => true, - _ => false, - } - } - /// Is this a ref type? pub fn is_ref(self) -> bool { match self { @@ -398,17 +364,6 @@ impl Type { Some(Self(self.0 - constants::VECTOR_BASE)) } - /// Get a SIMD vector with half the number of lanes. - /// - /// There is no `double_vector()` method. Use `t.by(2)` instead. - pub fn half_vector(self) -> Option { - if self.is_vector() && !self.is_dynamic_vector() { - Some(Self(self.0 - 0x10)) - } else { - None - } - } - /// Split the lane width in half and double the number of lanes to maintain the same bit-width. /// /// If this is a scalar type of `n` bits, it produces a SIMD vector type of `(n/2)x2`. @@ -425,7 +380,13 @@ impl Type { /// If this is a scalar type, it will return `None`. pub fn merge_lanes(self) -> Option { match self.double_width() { - Some(double_width) => double_width.half_vector(), + Some(double_width) => { + if double_width.is_vector() && !double_width.is_dynamic_vector() { + Some(Self(double_width.0 - 0x10)) + } else { + None + } + } None => None, } } @@ -453,19 +414,6 @@ impl Type { } } - /// Coerces boolean types (scalar and vectors) into their integer counterparts. - /// B1 is converted into I8. - pub fn coerce_bools_to_ints(self) -> Self { - let is_scalar_bool = self.is_bool(); - let is_vector_bool = self.is_vector() && self.lane_type().is_bool(); - - if is_scalar_bool || is_vector_bool { - self.as_int() - } else { - self - } - } - /// Gets a bit-level representation of the type. Used only /// internally for efficiently storing types. pub(crate) fn repr(self) -> u16 { @@ -481,9 +429,7 @@ impl Type { impl Display for Type { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if self.is_bool() { - write!(f, "b{}", self.lane_bits()) - } else if self.is_int() { + if self.is_int() { write!(f, "i{}", self.lane_bits()) } else if self.is_float() { write!(f, "f{}", self.lane_bits()) @@ -494,21 +440,17 @@ impl Display for Type { } else if self.is_ref() { write!(f, "r{}", self.lane_bits()) } else { - f.write_str(match *self { - IFLAGS => "iflags", - FFLAGS => "fflags", + match *self { INVALID => panic!("INVALID encountered"), _ => panic!("Unknown Type(0x{:x})", self.0), - }) + } } } } impl Debug for Type { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - if self.is_bool() { - write!(f, "types::B{}", self.lane_bits()) - } else if self.is_int() { + if self.is_int() { write!(f, "types::I{}", self.lane_bits()) } else if self.is_float() { write!(f, "types::F{}", self.lane_bits()) @@ -521,8 +463,6 @@ impl Debug for Type { } else { match *self { INVALID => write!(f, "types::INVALID"), - IFLAGS => write!(f, "types::IFLAGS"), - FFLAGS => write!(f, "types::FFLAGS"), _ => write!(f, "Type(0x{:x})", self.0), } } @@ -544,16 +484,6 @@ mod tests { fn basic_scalars() { assert_eq!(INVALID, INVALID.lane_type()); assert_eq!(0, INVALID.bits()); - assert_eq!(IFLAGS, IFLAGS.lane_type()); - assert_eq!(0, IFLAGS.bits()); - assert_eq!(FFLAGS, FFLAGS.lane_type()); - assert_eq!(0, FFLAGS.bits()); - assert_eq!(B1, B1.lane_type()); - assert_eq!(B8, B8.lane_type()); - assert_eq!(B16, B16.lane_type()); - assert_eq!(B32, B32.lane_type()); - assert_eq!(B64, B64.lane_type()); - assert_eq!(B128, B128.lane_type()); assert_eq!(I8, I8.lane_type()); assert_eq!(I16, I16.lane_type()); assert_eq!(I32, I32.lane_type()); @@ -561,21 +491,12 @@ mod tests { assert_eq!(I128, I128.lane_type()); assert_eq!(F32, F32.lane_type()); assert_eq!(F64, F64.lane_type()); - assert_eq!(B1, B1.by(8).unwrap().lane_type()); assert_eq!(I32, I32X4.lane_type()); assert_eq!(F64, F64X2.lane_type()); assert_eq!(R32, R32.lane_type()); assert_eq!(R64, R64.lane_type()); assert_eq!(INVALID.lane_bits(), 0); - assert_eq!(IFLAGS.lane_bits(), 0); - assert_eq!(FFLAGS.lane_bits(), 0); - assert_eq!(B1.lane_bits(), 1); - assert_eq!(B8.lane_bits(), 8); - assert_eq!(B16.lane_bits(), 16); - assert_eq!(B32.lane_bits(), 32); - assert_eq!(B64.lane_bits(), 64); - assert_eq!(B128.lane_bits(), 128); assert_eq!(I8.lane_bits(), 8); assert_eq!(I16.lane_bits(), 16); assert_eq!(I32.lane_bits(), 32); @@ -591,13 +512,6 @@ mod tests { fn typevar_functions() { assert_eq!(INVALID.half_width(), None); assert_eq!(INVALID.half_width(), None); - assert_eq!(FFLAGS.half_width(), None); - assert_eq!(B1.half_width(), None); - assert_eq!(B8.half_width(), None); - assert_eq!(B16.half_width(), Some(B8)); - assert_eq!(B32.half_width(), Some(B16)); - assert_eq!(B64.half_width(), Some(B32)); - assert_eq!(B128.half_width(), Some(B64)); assert_eq!(I8.half_width(), None); assert_eq!(I16.half_width(), Some(I8)); assert_eq!(I32.half_width(), Some(I16)); @@ -608,14 +522,6 @@ mod tests { assert_eq!(F64.half_width(), Some(F32)); assert_eq!(INVALID.double_width(), None); - assert_eq!(IFLAGS.double_width(), None); - assert_eq!(FFLAGS.double_width(), None); - assert_eq!(B1.double_width(), None); - assert_eq!(B8.double_width(), Some(B16)); - assert_eq!(B16.double_width(), Some(B32)); - assert_eq!(B32.double_width(), Some(B64)); - assert_eq!(B64.double_width(), Some(B128)); - assert_eq!(B128.double_width(), None); assert_eq!(I8.double_width(), Some(I16)); assert_eq!(I16.double_width(), Some(I32)); assert_eq!(I32.double_width(), Some(I64)); @@ -633,11 +539,6 @@ mod tests { assert_eq!(big.lane_count(), 256); assert_eq!(big.bits(), 64 * 256); - assert_eq!(big.half_vector().unwrap().to_string(), "f64x128"); - assert_eq!(B1.by(2).unwrap().half_vector().unwrap().to_string(), "b1"); - assert_eq!(I32.half_vector(), None); - assert_eq!(INVALID.half_vector(), None); - // Check that the generated constants match the computed vector types. assert_eq!(I32.by(4), Some(I32X4)); assert_eq!(F64.by(8), Some(F64X8)); @@ -647,7 +548,6 @@ mod tests { fn dynamic_vectors() { // Identification. assert_eq!(I8X16XN.is_dynamic_vector(), true); - assert_eq!(B16X4XN.is_dynamic_vector(), true); assert_eq!(F32X8XN.is_dynamic_vector(), true); assert_eq!(F64X4XN.is_dynamic_vector(), true); assert_eq!(I128X2XN.is_dynamic_vector(), true); @@ -656,28 +556,18 @@ mod tests { assert_eq!(I16X8XN.lane_count(), 0); assert_eq!(I16X8XN.min_lane_count(), 8); - // Size - assert_eq!(B32X2XN.bits(), 0); - assert_eq!(B32X2XN.min_bits(), 64); - // Change lane counts - assert_eq!(F64X4XN.half_vector(), None); assert_eq!(I8X8XN.by(2), None); // Conversions to and from vectors. - assert_eq!(B8.by(8).unwrap().vector_to_dynamic(), Some(B8X8XN)); assert_eq!(I8.by(16).unwrap().vector_to_dynamic(), Some(I8X16XN)); assert_eq!(I16.by(8).unwrap().vector_to_dynamic(), Some(I16X8XN)); - assert_eq!(B16.by(16).unwrap().vector_to_dynamic(), Some(B16X16XN)); - assert_eq!(B32.by(2).unwrap().vector_to_dynamic(), Some(B32X2XN)); - assert_eq!(B32.by(8).unwrap().vector_to_dynamic(), Some(B32X8XN)); assert_eq!(I32.by(4).unwrap().vector_to_dynamic(), Some(I32X4XN)); assert_eq!(F32.by(4).unwrap().vector_to_dynamic(), Some(F32X4XN)); assert_eq!(F64.by(2).unwrap().vector_to_dynamic(), Some(F64X2XN)); assert_eq!(I128.by(2).unwrap().vector_to_dynamic(), Some(I128X2XN)); assert_eq!(I128X2XN.dynamic_to_vector(), Some(I128X2)); - assert_eq!(B64X2XN.dynamic_to_vector(), Some(B64X2)); assert_eq!(F32X4XN.dynamic_to_vector(), Some(F32X4)); assert_eq!(F64X4XN.dynamic_to_vector(), Some(F64X4)); assert_eq!(I32X2XN.dynamic_to_vector(), Some(I32X2)); @@ -686,7 +576,6 @@ mod tests { assert_eq!(I8X32XN.dynamic_to_vector(), Some(I8X32)); assert_eq!(I8X64.vector_to_dynamic(), None); - assert_eq!(B16X32.vector_to_dynamic(), None); assert_eq!(F32X16.vector_to_dynamic(), None); assert_eq!(I64X8.vector_to_dynamic(), None); assert_eq!(I128X4.vector_to_dynamic(), None); @@ -694,14 +583,6 @@ mod tests { #[test] fn format_scalars() { - assert_eq!(IFLAGS.to_string(), "iflags"); - assert_eq!(FFLAGS.to_string(), "fflags"); - assert_eq!(B1.to_string(), "b1"); - assert_eq!(B8.to_string(), "b8"); - assert_eq!(B16.to_string(), "b16"); - assert_eq!(B32.to_string(), "b32"); - assert_eq!(B64.to_string(), "b64"); - assert_eq!(B128.to_string(), "b128"); assert_eq!(I8.to_string(), "i8"); assert_eq!(I16.to_string(), "i16"); assert_eq!(I32.to_string(), "i32"); @@ -715,11 +596,6 @@ mod tests { #[test] fn format_vectors() { - assert_eq!(B1.by(8).unwrap().to_string(), "b1x8"); - assert_eq!(B8.by(1).unwrap().to_string(), "b8"); - assert_eq!(B16.by(256).unwrap().to_string(), "b16x256"); - assert_eq!(B32.by(4).unwrap().by(2).unwrap().to_string(), "b32x8"); - assert_eq!(B64.by(8).unwrap().to_string(), "b64x8"); assert_eq!(I8.by(64).unwrap().to_string(), "i8x64"); assert_eq!(F64.by(2).unwrap().to_string(), "f64x2"); assert_eq!(I8.by(3), None); @@ -729,19 +605,10 @@ mod tests { #[test] fn as_bool() { - assert_eq!(I32X4.as_bool(), B32X4); - assert_eq!(I32.as_bool(), B1); - assert_eq!(I32X4.as_bool_pedantic(), B32X4); - assert_eq!(I32.as_bool_pedantic(), B32); - } - - #[test] - fn as_int() { - assert_eq!(B32X4.as_int(), I32X4); - assert_eq!(B8X8.as_int(), I8X8); - assert_eq!(B1.as_int(), I8); - assert_eq!(B8.as_int(), I8); - assert_eq!(B128.as_int(), I128); + assert_eq!(I32X4.as_bool(), I32X4); + assert_eq!(I32.as_bool(), I8); + assert_eq!(I32X4.as_bool_pedantic(), I32X4); + assert_eq!(I32.as_bool_pedantic(), I32); } #[test] diff --git a/cranelift/codegen/src/isa/aarch64/abi.rs b/cranelift/codegen/src/isa/aarch64/abi.rs index 5d25aaab1c5b..8d8074177032 100644 --- a/cranelift/codegen/src/isa/aarch64/abi.rs +++ b/cranelift/codegen/src/isa/aarch64/abi.rs @@ -5,7 +5,7 @@ use crate::ir::types; use crate::ir::types::*; use crate::ir::MemFlags; use crate::ir::Opcode; -use crate::ir::{ExternalName, LibCall, Signature}; +use crate::ir::{dynamic_to_fixed, ExternalName, LibCall, Signature}; use crate::isa; use crate::isa::aarch64::{inst::EmitState, inst::*, settings as aarch64_settings}; use crate::isa::unwind::UnwindInst; @@ -21,22 +21,22 @@ use smallvec::{smallvec, SmallVec}; // these ABIs are very similar. /// Support for the AArch64 ABI from the callee side (within a function body). -pub(crate) type AArch64ABICallee = ABICalleeImpl; +pub(crate) type AArch64Callee = Callee; /// Support for the AArch64 ABI from the caller side (at a callsite). -pub(crate) type AArch64ABICaller = ABICallerImpl; +pub(crate) type AArch64Caller = Caller; /// This is the limit for the size of argument and return-value areas on the /// stack. We place a reasonable limit here to avoid integer overflow issues /// with 32-bit arithmetic: for now, 128 MB. -static STACK_ARG_RET_SIZE_LIMIT: u64 = 128 * 1024 * 1024; +static STACK_ARG_RET_SIZE_LIMIT: u32 = 128 * 1024 * 1024; impl Into for StackAMode { fn into(self) -> AMode { match self { - StackAMode::FPOffset(off, ty) => AMode::FPOffset(off, ty), - StackAMode::NominalSPOffset(off, ty) => AMode::NominalSPOffset(off, ty), - StackAMode::SPOffset(off, ty) => AMode::SPOffset(off, ty), + StackAMode::FPOffset(off, ty) => AMode::FPOffset { off, ty }, + StackAMode::NominalSPOffset(off, ty) => AMode::NominalSPOffset { off, ty }, + StackAMode::SPOffset(off, ty) => AMode::SPOffset { off, ty }, } } } @@ -65,9 +65,13 @@ fn saved_reg_stack_size( /// AArch64-specific ABI behavior. This struct just serves as an implementation /// point for the trait; it is never actually instantiated. -pub(crate) struct AArch64MachineDeps; +pub struct AArch64MachineDeps; -impl IsaFlags for aarch64_settings::Flags {} +impl IsaFlags for aarch64_settings::Flags { + fn is_forward_edge_cfi_enabled(&self) -> bool { + self.use_bti() + } +} impl ABIMachineSpec for AArch64MachineDeps { type I = Inst; @@ -83,13 +87,17 @@ impl ABIMachineSpec for AArch64MachineDeps { 16 } - fn compute_arg_locs( + fn compute_arg_locs<'a, I>( call_conv: isa::CallConv, _flags: &settings::Flags, - params: &[ir::AbiParam], + params: I, args_or_rets: ArgsOrRets, add_ret_area_ptr: bool, - ) -> CodegenResult<(ABIArgVec, i64, Option)> { + mut args: ArgsAccumulator<'_>, + ) -> CodegenResult<(u32, Option)> + where + I: IntoIterator, + { let is_apple_cc = call_conv.extends_apple_aarch64(); // See AArch64 ABI (https://github.com/ARM-software/abi-aa/blob/2021Q1/aapcs64/aapcs64.rst#64parameter-passing), sections 6.4. @@ -108,8 +116,7 @@ impl ABIMachineSpec for AArch64MachineDeps { let mut next_xreg = 0; let mut next_vreg = 0; - let mut next_stack: u64 = 0; - let mut ret = ABIArgVec::new(); + let mut next_stack: u32 = 0; let (max_per_class_reg_vals, mut remaining_reg_vals) = match args_or_rets { ArgsOrRets::Args => (8, 16), // x0-x7 and v0-v7 @@ -134,20 +141,6 @@ impl ABIMachineSpec for AArch64MachineDeps { }; for param in params { - // Validate "purpose". - match ¶m.purpose { - &ir::ArgumentPurpose::VMContext - | &ir::ArgumentPurpose::Normal - | &ir::ArgumentPurpose::StackLimit - | &ir::ArgumentPurpose::SignatureId - | &ir::ArgumentPurpose::StructReturn - | &ir::ArgumentPurpose::StructArgument(_) => {} - _ => panic!( - "Unsupported argument purpose {:?} in signature: {:?}", - param.purpose, params - ), - } - assert!( legal_type_for_machine(param.value_type), "Invalid type for AArch64: {:?}", @@ -157,19 +150,38 @@ impl ABIMachineSpec for AArch64MachineDeps { let (rcs, reg_types) = Inst::rc_for_type(param.value_type)?; if let ir::ArgumentPurpose::StructArgument(size) = param.purpose { + assert_eq!(args_or_rets, ArgsOrRets::Args); let offset = next_stack as i64; - let size = size as u64; + let size = size; assert!(size % 8 == 0, "StructArgument size is not properly aligned"); next_stack += size; - ret.push(ABIArg::StructArg { + args.push(ABIArg::StructArg { pointer: None, offset, - size, + size: size as u64, purpose: param.purpose, }); continue; } + if let ir::ArgumentPurpose::StructReturn = param.purpose { + // FIXME add assert_eq!(args_or_rets, ArgsOrRets::Args); once + // ensure_struct_return_ptr_is_returned is gone. + assert!( + param.value_type == types::I64, + "StructReturn must be a pointer sized integer" + ); + args.push(ABIArg::Slots { + slots: smallvec![ABIArgSlot::Reg { + reg: xreg(8).to_real_reg().unwrap(), + ty: types::I64, + extension: param.extension, + },], + purpose: ir::ArgumentPurpose::StructReturn, + }); + continue; + } + // Handle multi register params // // See AArch64 ABI (https://github.com/ARM-software/abi-aa/blob/2021Q1/aapcs64/aapcs64.rst#642parameter-passing-rules), (Section 6.4.2 Stage C). @@ -216,16 +228,16 @@ impl ABIMachineSpec for AArch64MachineDeps { let lower_reg = xreg(next_xreg); let upper_reg = xreg(next_xreg + 1); - ret.push(ABIArg::Slots { + args.push(ABIArg::Slots { slots: smallvec![ ABIArgSlot::Reg { reg: lower_reg.to_real_reg().unwrap(), - ty: param.value_type, + ty: reg_types[0], extension: param.extension, }, ABIArgSlot::Reg { reg: upper_reg.to_real_reg().unwrap(), - ty: param.value_type, + ty: reg_types[1], extension: param.extension, }, ], @@ -255,7 +267,7 @@ impl ABIMachineSpec for AArch64MachineDeps { } else { param.value_type }; - ret.push(ABIArg::reg( + args.push(ABIArg::reg( reg.to_real_reg().unwrap(), ty, param.extension, @@ -270,7 +282,7 @@ impl ABIMachineSpec for AArch64MachineDeps { // Spill to the stack // Compute the stack slot's size. - let size = (ty_bits(param.value_type) / 8) as u64; + let size = (ty_bits(param.value_type) / 8) as u32; let size = if is_apple_cc || (call_conv.extends_wasmtime() && args_or_rets == ArgsOrRets::Rets) @@ -296,7 +308,7 @@ impl ABIMachineSpec for AArch64MachineDeps { // Build the stack locations from each slot .scan(next_stack, |next_stack, ty| { let slot_offset = *next_stack as i64; - *next_stack += (ty_bits(ty) / 8) as u64; + *next_stack += (ty_bits(ty) / 8) as u32; Some((ty, slot_offset)) }) @@ -307,7 +319,7 @@ impl ABIMachineSpec for AArch64MachineDeps { }) .collect(); - ret.push(ABIArg::Slots { + args.push(ABIArg::Slots { slots, purpose: param.purpose, }); @@ -318,14 +330,14 @@ impl ABIMachineSpec for AArch64MachineDeps { let extra_arg = if add_ret_area_ptr { debug_assert!(args_or_rets == ArgsOrRets::Args); if next_xreg < max_per_class_reg_vals && remaining_reg_vals > 0 { - ret.push(ABIArg::reg( + args.push(ABIArg::reg( xreg(next_xreg).to_real_reg().unwrap(), I64, ir::ArgumentExtension::None, ir::ArgumentPurpose::Normal, )); } else { - ret.push(ABIArg::stack( + args.push(ABIArg::stack( next_stack as i64, I64, ir::ArgumentExtension::None, @@ -333,7 +345,7 @@ impl ABIMachineSpec for AArch64MachineDeps { )); next_stack += 8; } - Some(ret.len() - 1) + Some(args.args().len() - 1) } else { None }; @@ -346,7 +358,7 @@ impl ABIMachineSpec for AArch64MachineDeps { return Err(CodegenError::ImplLimitExceeded); } - Ok((ret, next_stack as i64, extra_arg)) + Ok((next_stack, extra_arg)) } fn fp_to_arg_offset(_call_conv: isa::CallConv, _flags: &settings::Flags) -> i64 { @@ -382,7 +394,11 @@ impl ABIMachineSpec for AArch64MachineDeps { } } - fn gen_ret(setup_frame: bool, isa_flags: &aarch64_settings::Flags, rets: Vec) -> Inst { + fn gen_args(_isa_flags: &aarch64_settings::Flags, args: Vec) -> Inst { + Inst::Args { args } + } + + fn gen_ret(setup_frame: bool, isa_flags: &aarch64_settings::Flags, rets: Vec) -> Inst { if isa_flags.sign_return_address() && (setup_frame || isa_flags.sign_return_address_all()) { let key = if isa_flags.sign_return_address_with_bkey() { APIKey::B @@ -414,7 +430,10 @@ impl ABIMachineSpec for AArch64MachineDeps { } else { let scratch2 = writable_tmp2_reg(); assert_ne!(scratch2.to_reg(), from_reg); - insts.extend(Inst::load_constant(scratch2, imm.into())); + // `gen_add_imm` is only ever called after register allocation has taken place, and as a + // result it's ok to reuse the scratch2 register here. If that changes, we'll need to + // plumb through a way to allocate temporary virtual registers + insts.extend(Inst::load_constant(scratch2, imm.into(), &mut |_| scratch2)); insts.push(Inst::AluRRRExtend { alu_op: ALUOp::Add, size: OperandSize::Size64, @@ -457,12 +476,20 @@ impl ABIMachineSpec for AArch64MachineDeps { } fn gen_load_base_offset(into_reg: Writable, base: Reg, offset: i32, ty: Type) -> Inst { - let mem = AMode::RegOffset(base, offset as i64, ty); + let mem = AMode::RegOffset { + rn: base, + off: offset as i64, + ty, + }; Inst::gen_load(into_reg, mem, ty, MemFlags::trusted()) } fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Inst { - let mem = AMode::RegOffset(base, offset as i64, ty); + let mem = AMode::RegOffset { + rn: base, + off: offset as i64, + ty, + }; Inst::gen_store(mem, from_reg, ty, MemFlags::trusted()) } @@ -491,7 +518,9 @@ impl ABIMachineSpec for AArch64MachineDeps { ret.push(adj_inst); } else { let tmp = writable_spilltmp_reg(); - let const_inst = Inst::load_constant(tmp, amount); + // `gen_sp_reg_adjust` is called after regalloc2, so it's acceptable to reuse `tmp` for + // intermediates in `load_constant`. + let const_inst = Inst::load_constant(tmp, amount, &mut |_| tmp); let adj_inst = Inst::AluRRRExtend { alu_op, size: OperandSize::Size64, @@ -536,13 +565,21 @@ impl ABIMachineSpec for AArch64MachineDeps { }, }); } - } else if flags.unwind_info() && call_conv.extends_apple_aarch64() { - // The macOS unwinder seems to require this. - insts.push(Inst::Unwind { - inst: UnwindInst::Aarch64SetPointerAuth { - return_addresses: false, - }, - }); + } else { + if isa_flags.use_bti() { + insts.push(Inst::Bti { + targets: BranchTargetType::C, + }); + } + + if flags.unwind_info() && call_conv.extends_apple_aarch64() { + // The macOS unwinder seems to require this. + insts.push(Inst::Unwind { + inst: UnwindInst::Aarch64SetPointerAuth { + return_addresses: false, + }, + }); + } } insts @@ -555,10 +592,7 @@ impl ABIMachineSpec for AArch64MachineDeps { insts.push(Inst::StoreP64 { rt: fp_reg(), rt2: link_reg(), - mem: PairAMode::PreIndexed( - writable_stack_reg(), - SImm7Scaled::maybe_from_i64(-16, types::I64).unwrap(), - ), + mem: PairAMode::SPPreIndexed(SImm7Scaled::maybe_from_i64(-16, types::I64).unwrap()), flags: MemFlags::trusted(), }); @@ -596,23 +630,68 @@ impl ABIMachineSpec for AArch64MachineDeps { insts.push(Inst::LoadP64 { rt: writable_fp_reg(), rt2: writable_link_reg(), - mem: PairAMode::PostIndexed( - writable_stack_reg(), - SImm7Scaled::maybe_from_i64(16, types::I64).unwrap(), - ), + mem: PairAMode::SPPostIndexed(SImm7Scaled::maybe_from_i64(16, types::I64).unwrap()), flags: MemFlags::trusted(), }); insts } - fn gen_probestack(_: u32) -> SmallInstVec { + fn gen_probestack(_insts: &mut SmallInstVec, _: u32) { // TODO: implement if we ever require stack probes on an AArch64 host // (unlikely unless Lucet is ported) - smallvec![] + unimplemented!("Stack probing is unimplemented on AArch64"); + } + + fn gen_inline_probestack(insts: &mut SmallInstVec, frame_size: u32, guard_size: u32) { + // The stack probe loop currently takes 6 instructions and each inline + // probe takes 2 (ish, these numbers sort of depend on the constants). + // Set this to 3 to keep the max size of the probe to 6 instructions. + const PROBE_MAX_UNROLL: u32 = 3; + + let probe_count = align_to(frame_size, guard_size) / guard_size; + if probe_count <= PROBE_MAX_UNROLL { + // When manually unrolling stick an instruction that stores 0 at a + // constant offset relative to the stack pointer. This will + // turn into something like `movn tmp, #n ; stur xzr [sp, tmp]`. + // + // Note that this may actually store beyond the stack size for the + // last item but that's ok since it's unused stack space and if + // that faults accidentally we're so close to faulting it shouldn't + // make too much difference to fault there. + insts.reserve(probe_count as usize); + for i in 0..probe_count { + let offset = (guard_size * (i + 1)) as i64; + insts.push(Self::gen_store_stack( + StackAMode::SPOffset(-offset, I8), + zero_reg(), + I32, + )); + } + } else { + // The non-unrolled version uses two temporary registers. The + // `start` contains the current offset from sp and counts downwards + // during the loop by increments of `guard_size`. The `end` is + // the size of the frame and where we stop. + // + // Note that this emission is all post-regalloc so it should be ok + // to use the temporary registers here as input/output as the loop + // itself is not allowed to use the registers. + let start = writable_spilltmp_reg(); + let end = writable_tmp2_reg(); + // `gen_inline_probestack` is called after regalloc2, so it's acceptable to reuse + // `start` and `end` as temporaries in load_constant. + insts.extend(Inst::load_constant(start, 0, &mut |_| start)); + insts.extend(Inst::load_constant(end, frame_size.into(), &mut |_| end)); + insts.push(Inst::StackProbeLoop { + start, + end: end.to_reg(), + step: Imm12::maybe_from_u64(guard_size.into()).unwrap(), + }); + } } // Returns stack bytes used as well as instructions. Does not adjust - // nominal SP offset; abi_impl generic code will do that. + // nominal SP offset; abi generic code will do that. fn gen_clobber_save( _call_conv: isa::CallConv, setup_frame: bool, @@ -671,10 +750,9 @@ impl ABIMachineSpec for AArch64MachineDeps { // str rd, [sp, #-16]! insts.push(Inst::Store64 { rd, - mem: AMode::PreIndexed( - writable_stack_reg(), - SImm9::maybe_from_i64(-clobber_offset_change).unwrap(), - ), + mem: AMode::SPPreIndexed { + simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(), + }, flags: MemFlags::trusted(), }); @@ -703,8 +781,7 @@ impl ABIMachineSpec for AArch64MachineDeps { insts.push(Inst::StoreP64 { rt, rt2, - mem: PairAMode::PreIndexed( - writable_stack_reg(), + mem: PairAMode::SPPreIndexed( SImm7Scaled::maybe_from_i64(-clobber_offset_change, types::I64).unwrap(), ), flags: MemFlags::trusted(), @@ -729,10 +806,9 @@ impl ABIMachineSpec for AArch64MachineDeps { let store_vec_reg = |rd| Inst::FpuStore64 { rd, - mem: AMode::PreIndexed( - writable_stack_reg(), - SImm9::maybe_from_i64(-clobber_offset_change).unwrap(), - ), + mem: AMode::SPPreIndexed { + simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(), + }, flags: MemFlags::trusted(), }; let iter = clobbered_vec.chunks_exact(2); @@ -761,8 +837,7 @@ impl ABIMachineSpec for AArch64MachineDeps { Inst::FpuStoreP64 { rt, rt2, - mem: PairAMode::PreIndexed( - writable_stack_reg(), + mem: PairAMode::SPPreIndexed( SImm7Scaled::maybe_from_i64(-clobber_offset_change, F64).unwrap(), ), flags: MemFlags::trusted(), @@ -826,16 +901,15 @@ impl ABIMachineSpec for AArch64MachineDeps { let load_vec_reg = |rd| Inst::FpuLoad64 { rd, - mem: AMode::PostIndexed(writable_stack_reg(), SImm9::maybe_from_i64(16).unwrap()), + mem: AMode::SPPostIndexed { + simm9: SImm9::maybe_from_i64(16).unwrap(), + }, flags: MemFlags::trusted(), }; let load_vec_reg_pair = |rt, rt2| Inst::FpuLoadP64 { rt, rt2, - mem: PairAMode::PostIndexed( - writable_stack_reg(), - SImm7Scaled::maybe_from_i64(16, F64).unwrap(), - ), + mem: PairAMode::SPPostIndexed(SImm7Scaled::maybe_from_i64(16, F64).unwrap()), flags: MemFlags::trusted(), }; @@ -871,10 +945,7 @@ impl ABIMachineSpec for AArch64MachineDeps { insts.push(Inst::LoadP64 { rt, rt2, - mem: PairAMode::PostIndexed( - writable_stack_reg(), - SImm7Scaled::maybe_from_i64(16, I64).unwrap(), - ), + mem: PairAMode::SPPostIndexed(SImm7Scaled::maybe_from_i64(16, I64).unwrap()), flags: MemFlags::trusted(), }); } @@ -888,7 +959,9 @@ impl ABIMachineSpec for AArch64MachineDeps { // ldr rd, [sp], #16 insts.push(Inst::ULoad64 { rd, - mem: AMode::PostIndexed(writable_stack_reg(), SImm9::maybe_from_i64(16).unwrap()), + mem: AMode::SPPostIndexed { + simm9: SImm9::maybe_from_i64(16).unwrap(), + }, flags: MemFlags::trusted(), }); } @@ -898,8 +971,8 @@ impl ABIMachineSpec for AArch64MachineDeps { fn gen_call( dest: &CallDest, - uses: SmallVec<[Reg; 8]>, - defs: SmallVec<[Writable; 8]>, + uses: CallArgList, + defs: CallRetList, clobbers: PRegSet, opcode: ir::Opcode, tmp: Writable, @@ -953,23 +1026,36 @@ impl ABIMachineSpec for AArch64MachineDeps { insts } - fn gen_memcpy( + fn gen_memcpy Writable>( call_conv: isa::CallConv, dst: Reg, src: Reg, size: usize, + mut alloc_tmp: F, ) -> SmallVec<[Self::I; 8]> { let mut insts = SmallVec::new(); let arg0 = writable_xreg(0); let arg1 = writable_xreg(1); let arg2 = writable_xreg(2); - insts.push(Inst::gen_move(arg0, dst, I64)); - insts.push(Inst::gen_move(arg1, src, I64)); - insts.extend(Inst::load_constant(arg2, size as u64).into_iter()); + let tmp = alloc_tmp(Self::word_type()); + insts.extend(Inst::load_constant(tmp, size as u64, &mut alloc_tmp)); insts.push(Inst::Call { info: Box::new(CallInfo { dest: ExternalName::LibCall(LibCall::Memcpy), - uses: smallvec![arg0.to_reg(), arg1.to_reg(), arg2.to_reg()], + uses: smallvec![ + CallArgPair { + vreg: dst, + preg: arg0.to_reg() + }, + CallArgPair { + vreg: src, + preg: arg1.to_reg() + }, + CallArgPair { + vreg: tmp.to_reg(), + preg: arg2.to_reg() + } + ], defs: smallvec![], clobbers: Self::get_regs_clobbered_by_call(call_conv), opcode: Opcode::Call, diff --git a/cranelift/codegen/src/isa/aarch64/inst.isle b/cranelift/codegen/src/isa/aarch64/inst.isle index b5826a226ea9..b8bf2ef480b0 100644 --- a/cranelift/codegen/src/isa/aarch64/inst.isle +++ b/cranelift/codegen/src/isa/aarch64/inst.isle @@ -167,17 +167,33 @@ ;; Like `Move` but with a particular `PReg` source (for implementing CLIF ;; instructions like `get_stack_pointer`). - (MovPReg + (MovFromPReg (rd WritableReg) (rm PReg)) - ;; A MOV[Z,N,K] with a 16-bit immediate. + ;; Like `Move` but with a particular `PReg` destination (for + ;; implementing CLIF instructions like `set_pinned_reg`). + (MovToPReg + (rd PReg) + (rm Reg)) + + ;; A MOV[Z,N] with a 16-bit immediate. (MovWide (op MoveWideOp) (rd WritableReg) (imm MoveWideConst) (size OperandSize)) + ;; A MOVK with a 16-bit immediate. Modifies its register; we + ;; model this with a seprate input `rn` and output `rd` virtual + ;; register, with a regalloc constraint to tie them together. + (MovK + (rd WritableReg) + (rn Reg) + (imm MoveWideConst) + (size OperandSize)) + + ;; A sign- or zero-extend operation. (Extend (rd WritableReg) @@ -210,6 +226,14 @@ (rd WritableReg) (cond Cond)) + ;; A conditional comparison with a second register. + (CCmp + (size OperandSize) + (rn Reg) + (rm Reg) + (nzcv NZCV) + (cond Cond)) + ;; A conditional comparison with an immediate. (CCmpImm (size OperandSize) @@ -232,7 +256,13 @@ ;; x28 (wr) scratch reg; value afterwards has no meaning (AtomicRMWLoop (ty Type) ;; I8, I16, I32 or I64 - (op AtomicRMWLoopOp)) + (op AtomicRMWLoopOp) + (flags MemFlags) + (addr Reg) + (operand Reg) + (oldval WritableReg) + (scratch1 WritableReg) + (scratch2 WritableReg)) ;; Similar to AtomicRMWLoop, a compare-and-swap operation implemented using a load-linked ;; store-conditional loop, with acquire-release semantics. @@ -245,7 +275,12 @@ ;; x24 (wr) scratch reg; value afterwards has no meaning (AtomicCASLoop (ty Type) ;; I8, I16, I32 or I64 - ) + (flags MemFlags) + (addr Reg) + (expected Reg) + (replacement Reg) + (oldval WritableReg) + (scratch WritableReg)) ;; An atomic read-modify-write operation. These instructions require the ;; Large System Extension (LSE) ISA support (FEAT_LSE). The instructions have @@ -255,16 +290,21 @@ (rs Reg) (rt WritableReg) (rn Reg) - (ty Type)) + (ty Type) + (flags MemFlags)) ;; An atomic compare-and-swap operation. These instructions require the ;; Large System Extension (LSE) ISA support (FEAT_LSE). The instructions have ;; acquire-release semantics. (AtomicCAS - (rs WritableReg) + ;; `rd` is really `rs` in the encoded instruction (so `rd` == `rs`); we separate + ;; them here to have separate use and def vregs for regalloc. + (rd WritableReg) + (rs Reg) (rt Reg) (rn Reg) - (ty Type)) + (ty Type) + (flags MemFlags)) ;; Read `access_ty` bits from address `rt`, either 8, 16, 32 or 64-bits, and put ;; it in `rn`, optionally zero-extending to fill a word or double word result. @@ -272,14 +312,16 @@ (LoadAcquire (access_ty Type) ;; I8, I16, I32 or I64 (rt WritableReg) - (rn Reg)) + (rn Reg) + (flags MemFlags)) ;; Write the lowest `ty` bits of `rt` to address `rn`. ;; This instruction is sequentially consistent. (StoreRelease (access_ty Type) ;; I8, I16, I32 or I64 (rt Reg) - (rn Reg)) + (rn Reg) + (flags MemFlags)) ;; A memory fence. This must provide ordering to ensure that, at a minimum, neither loads ;; nor stores may move forwards or backwards across the fence. Currently emitted as "dmb @@ -334,6 +376,16 @@ (rd WritableReg) (rn Reg)) + ;; Variant of FpuRRI that modifies its `rd`, and so we name the + ;; input state `ri` (for "input") and constrain the two + ;; together. + (FpuRRIMod + (fpu_op FPUOpRIMod) + (rd WritableReg) + (ri Reg) + (rn Reg)) + + ;; 3-op FPU instruction. ;; 16-bit scalars require half-precision floating-point support (FEAT_FP16). (FpuRRRR @@ -471,6 +523,7 @@ ;; Move to a vector element from a GPR. (MovToVec (rd WritableReg) + (ri Reg) (rn Reg) (idx u8) (size VectorSize)) @@ -520,11 +573,13 @@ (t VecExtendOp) (rd WritableReg) (rn Reg) - (high_half bool)) + (high_half bool) + (lane_size ScalarSize)) ;; Move vector element to another vector element. (VecMovElement (rd WritableReg) + (ri Reg) (rn Reg) (dest_idx u8) (src_idx u8) @@ -537,12 +592,19 @@ (rn Reg) (high_half bool)) - ;; Vector narrowing operation. - (VecRRNarrow + ;; Vector narrowing operation -- low half. + (VecRRNarrowLow (op VecRRNarrowOp) (rd WritableReg) (rn Reg) - (high_half bool) + (lane_size ScalarSize)) + + ;; Vector narrowing operation -- high half. + (VecRRNarrowHigh + (op VecRRNarrowOp) + (rd WritableReg) + (ri Reg) + (rn Reg) (lane_size ScalarSize)) ;; 1-operand vector instruction that operates on a pair of elements. @@ -560,6 +622,17 @@ (rm Reg) (high_half bool)) + ;; 2-operand vector instruction that produces a result with + ;; twice the lane width and half the number of lanes. Variant + ;; that modifies `rd` (so takes its initial state as `ri`). + (VecRRRLongMod + (alu_op VecRRRLongModOp) + (rd WritableReg) + (ri Reg) + (rn Reg) + (rm Reg) + (high_half bool)) + ;; 1-operand vector instruction that extends elements of the input ;; register and operates on a pair of elements. The output lane width ;; is double that of the input. @@ -576,6 +649,15 @@ (rm Reg) (size VectorSize)) + ;; A vector ALU op modifying a source register. + (VecRRRMod + (alu_op VecALUModOp) + (rd WritableReg) + (ri Reg) + (rn Reg) + (rm Reg) + (size VectorSize)) + ;; Vector two register miscellaneous instruction. (VecMisc (op VecMisc2) @@ -602,6 +684,15 @@ (size VectorSize) (imm u8)) + ;; Destructive vector shift by immediate. + (VecShiftImmMod + (op VecShiftImmModOp) + (rd WritableReg) + (ri Reg) + (rn Reg) + (size VectorSize) + (imm u8)) + ;; Vector extract - create a new vector, being the concatenation of the lowest `imm4` bytes ;; of `rm` followed by the uppermost `16 - imm4` bytes of `rn`. (VecExtract @@ -610,29 +701,55 @@ (rm Reg) (imm4 u8)) - ;; Table vector lookup - single register table. The table consists of 8-bit elements and is - ;; stored in `rn`, while `rm` contains 8-bit element indices. `is_extension` specifies whether - ;; to emit a TBX or a TBL instruction, i.e. whether to leave the elements in the destination - ;; vector that correspond to out-of-range indices (greater than 15) unmodified or to set them - ;; to 0. + ;; Table vector lookup - single register table. The table + ;; consists of 8-bit elements and is stored in `rn`, while `rm` + ;; contains 8-bit element indices. This variant emits `TBL`, + ;; which sets elements that correspond to out-of-range indices + ;; (greater than 15) to 0. (VecTbl (rd WritableReg) (rn Reg) - (rm Reg) - (is_extension bool)) - - ;; Table vector lookup - two register table. The table consists of 8-bit elements and is - ;; stored in `rn` and `rn2`, while `rm` contains 8-bit element indices. `is_extension` - ;; specifies whether to emit a TBX or a TBL instruction, i.e. whether to leave the elements in - ;; the destination vector that correspond to out-of-range indices (greater than 31) unmodified - ;; or to set them to 0. The table registers `rn` and `rn2` must have consecutive numbers - ;; modulo 32, that is v31 and v0 (in that order) are consecutive registers. + (rm Reg)) + + ;; Table vector lookup - single register table. The table + ;; consists of 8-bit elements and is stored in `rn`, while `rm` + ;; contains 8-bit element indices. This variant emits `TBX`, + ;; which leaves elements that correspond to out-of-range indices + ;; (greater than 15) unmodified. Hence, it takes an input vreg in + ;; `ri` that is constrained to the same allocation as `rd`. + (VecTblExt + (rd WritableReg) + (ri Reg) + (rn Reg) + (rm Reg)) + + ;; Table vector lookup - two register table. The table consists + ;; of 8-bit elements and is stored in `rn` and `rn2`, while + ;; `rm` contains 8-bit element indices. The table registers + ;; `rn` and `rn2` must have consecutive numbers modulo 32, that + ;; is v31 and v0 (in that order) are consecutive registers. + ;; This variant emits `TBL`, which sets out-of-range results to + ;; 0. (VecTbl2 (rd WritableReg) (rn Reg) (rn2 Reg) - (rm Reg) - (is_extension bool)) + (rm Reg)) + + ;; Table vector lookup - two register table. The table consists + ;; of 8-bit elements and is stored in `rn` and `rn2`, while + ;; `rm` contains 8-bit element indices. The table registers + ;; `rn` and `rn2` must have consecutive numbers modulo 32, that + ;; is v31 and v0 (in that order) are consecutive registers. + ;; This variant emits `TBX`, which leaves out-of-range results + ;; unmodified, hence takes the initial state of the result + ;; register in vreg `ri`. + (VecTbl2Ext + (rd WritableReg) + (ri Reg) + (rn Reg) + (rn2 Reg) + (rm Reg)) ;; Load an element and replicate to all lanes of a vector. (VecLoadReplicate @@ -668,11 +785,15 @@ (CallInd (info BoxCallIndInfo)) + ;; A pseudo-instruction that captures register arguments in vregs. + (Args + (args VecArgPair)) + ;; ---- branches (exactly one must appear at end of BB) ---- ;; A machine return instruction. (Ret - (rets VecReg)) + (rets VecRetPair)) ;; A machine return instruction with pointer authentication using SP as the ;; modifier. This instruction requires pointer authentication support @@ -682,7 +803,7 @@ (AuthenticatedRet (key APIKey) (is_hint bool) - (rets VecReg)) + (rets VecRetPair)) ;; An unconditional branch. (Jump @@ -731,6 +852,11 @@ (rd WritableReg) ;; Offset in range -2^20 .. 2^20. (off i32)) + + ;; Compute the address (using a PC-relative offset) of a 4KB page. + (Adrp + (rd WritableReg) + (off i32)) ;; Raw 32-bit word, used for inline constants and jump-table entries. (Word4 @@ -764,6 +890,16 @@ (Pacisp (key APIKey)) + ;; Strip pointer authentication code from instruction address in LR; + ;; equivalent to a no-op if Pointer authentication (FEAT_PAuth) is not + ;; supported. + (Xpaclri) + + ;; Branch target identification; equivalent to a no-op if Branch Target + ;; Identification (FEAT_BTI) is not supported. + (Bti + (targets BranchTargetType)) + ;; Marker, no-op in generated code: SP "virtual offset" is adjusted. This ;; controls how AMode::NominalSPOffset args are lowered. (VirtualSPOffsetAdj @@ -795,7 +931,8 @@ ;; A call to the `ElfTlsGetAddr` libcall. Returns address of TLS symbol in x0. (ElfTlsGetAddr - (symbol ExternalName)) + (symbol ExternalName) + (rd WritableReg)) ;; An unwind pseudo-instruction. (Unwind @@ -803,7 +940,16 @@ ;; A dummy use, useful to keep a value alive. (DummyUse - (reg Reg)))) + (reg Reg)) + + ;; Emits an inline stack probe loop. + ;; + ;; Note that this is emitted post-regalloc so `start` and `end` can be + ;; temporary registers such as the spilltmp and tmp2 registers. This also + ;; means that the internal codegen can't use these registers. + (StackProbeLoop (start WritableReg) + (end Reg) + (step Imm12)))) ;; An ALU operation. This can be paired with several instruction formats ;; below (see `Inst`) in any combination. @@ -857,7 +1003,6 @@ (enum (MovZ) (MovN) - (MovK) )) (type UImm5 (primitive UImm5)) @@ -898,11 +1043,116 @@ (RBit) (Clz) (Cls) + ;; Byte reverse + (Rev16) + (Rev32) + (Rev64) )) -(type AMode extern (enum)) +(type MemLabel extern (enum)) +(type SImm9 extern (enum)) +(type UImm12Scaled extern (enum)) + +;; An addressing mode specified for a load/store operation. +(type AMode + (enum + ;; + ;; Real ARM64 addressing modes: + ;; + ;; "post-indexed" mode as per AArch64 docs: postincrement reg after + ;; address computation. + ;; Specialized here to SP so we don't have to emit regalloc metadata. + (SPPostIndexed + (simm9 SImm9)) + + ;; "pre-indexed" mode as per AArch64 docs: preincrement reg before + ;; address computation. + ;; Specialized here to SP so we don't have to emit regalloc metadata. + (SPPreIndexed + (simm9 SImm9)) + + ;; N.B.: RegReg, RegScaled, and RegScaledExtended all correspond to + ;; what the ISA calls the "register offset" addressing mode. We split + ;; out several options here for more ergonomic codegen. + ;; + ;; Register plus register offset. + (RegReg + (rn Reg) + (rm Reg)) + + ;; Register plus register offset, scaled by type's size. + (RegScaled + (rn Reg) + (rm Reg) + (ty Type)) + + ;; Register plus register offset, scaled by type's size, with index + ;; sign- or zero-extended first. + (RegScaledExtended + (rn Reg) + (rm Reg) + (ty Type) + (extendop ExtendOp)) + + ;; Register plus register offset, with index sign- or zero-extended + ;; first. + (RegExtended + (rn Reg) + (rm Reg) + (extendop ExtendOp)) + + ;; Unscaled signed 9-bit immediate offset from reg. + (Unscaled + (rn Reg) + (simm9 SImm9)) + + ;; Scaled (by size of a type) unsigned 12-bit immediate offset from reg. + (UnsignedOffset + (rn Reg) + (uimm12 UImm12Scaled)) + + ;; virtual addressing modes that are lowered at emission time: + ;; + ;; Reference to a "label": e.g., a symbol. + (Label + (label MemLabel)) + + ;; Arbitrary offset from a register. Converted to generation of large + ;; offsets with multiple instructions as necessary during code emission. + (RegOffset + (rn Reg) + (off i64) + (ty Type)) + + ;; Offset from the stack pointer. + (SPOffset + (off i64) + (ty Type)) + + ;; Offset from the frame pointer. + (FPOffset + (off i64) + (ty Type)) + + ;; Offset from the "nominal stack pointer", which is where the real SP is + ;; just after stack and spill slots are allocated in the function prologue. + ;; At emission time, this is converted to `SPOffset` with a fixup added to + ;; the offset constant. The fixup is a running value that is tracked as + ;; emission iterates through instructions in linear order, and can be + ;; adjusted up and down with [Inst::VirtualSPOffsetAdj]. + ;; + ;; The standard ABI is in charge of handling this (by emitting the + ;; adjustment meta-instructions). It maintains the invariant that "nominal + ;; SP" is where the actual SP is after the function prologue and before + ;; clobber pushes. See the diagram in the documentation for + ;; [crate::isa::aarch64::abi](the ABI module) for more details. + (NominalSPOffset + (off i64) + (ty Type)))) + (type PairAMode extern (enum)) (type FPUOpRI extern (enum)) +(type FPUOpRIMod extern (enum)) (type OperandSize extern (enum Size32 @@ -910,7 +1160,7 @@ ;; Helper for calculating the `OperandSize` corresponding to a type (decl operand_size (Type) OperandSize) -(rule (operand_size (fits_in_32 _ty)) (OperandSize.Size32)) +(rule 1 (operand_size (fits_in_32 _ty)) (OperandSize.Size32)) (rule (operand_size (fits_in_64 _ty)) (OperandSize.Size64)) (type ScalarSize extern @@ -922,20 +1172,22 @@ ;; Helper for calculating the `ScalarSize` corresponding to a type (decl scalar_size (Type) ScalarSize) + (rule (scalar_size $I8) (ScalarSize.Size8)) (rule (scalar_size $I16) (ScalarSize.Size16)) (rule (scalar_size $I32) (ScalarSize.Size32)) (rule (scalar_size $I64) (ScalarSize.Size64)) (rule (scalar_size $I128) (ScalarSize.Size128)) + (rule (scalar_size $F32) (ScalarSize.Size32)) (rule (scalar_size $F64) (ScalarSize.Size64)) ;; Helper for calculating the `ScalarSize` lane type from vector type (decl lane_size (Type) ScalarSize) -(rule (lane_size (multi_lane 8 _)) (ScalarSize.Size8)) -(rule (lane_size (multi_lane 16 _)) (ScalarSize.Size16)) -(rule (lane_size (multi_lane 32 _)) (ScalarSize.Size32)) -(rule (lane_size (multi_lane 64 _)) (ScalarSize.Size64)) +(rule 1 (lane_size (multi_lane 8 _)) (ScalarSize.Size8)) +(rule 1 (lane_size (multi_lane 16 _)) (ScalarSize.Size16)) +(rule 1 (lane_size (multi_lane 32 _)) (ScalarSize.Size32)) +(rule 1 (lane_size (multi_lane 64 _)) (ScalarSize.Size64)) (rule (lane_size (dynamic_lane 8 _)) (ScalarSize.Size8)) (rule (lane_size (dynamic_lane 16 _)) (ScalarSize.Size16)) (rule (lane_size (dynamic_lane 32 _)) (ScalarSize.Size32)) @@ -974,13 +1226,13 @@ ;; Helper for calculating the `VectorSize` corresponding to a type (decl vector_size (Type) VectorSize) -(rule (vector_size (multi_lane 8 8)) (VectorSize.Size8x8)) -(rule (vector_size (multi_lane 8 16)) (VectorSize.Size8x16)) -(rule (vector_size (multi_lane 16 4)) (VectorSize.Size16x4)) -(rule (vector_size (multi_lane 16 8)) (VectorSize.Size16x8)) -(rule (vector_size (multi_lane 32 2)) (VectorSize.Size32x2)) -(rule (vector_size (multi_lane 32 4)) (VectorSize.Size32x4)) -(rule (vector_size (multi_lane 64 2)) (VectorSize.Size64x2)) +(rule 1 (vector_size (multi_lane 8 8)) (VectorSize.Size8x8)) +(rule 1 (vector_size (multi_lane 8 16)) (VectorSize.Size8x16)) +(rule 1 (vector_size (multi_lane 16 4)) (VectorSize.Size16x4)) +(rule 1 (vector_size (multi_lane 16 8)) (VectorSize.Size16x8)) +(rule 1 (vector_size (multi_lane 32 2)) (VectorSize.Size32x2)) +(rule 1 (vector_size (multi_lane 32 4)) (VectorSize.Size32x4)) +(rule 1 (vector_size (multi_lane 64 2)) (VectorSize.Size64x2)) (rule (vector_size (dynamic_lane 8 8)) (VectorSize.Size8x8)) (rule (vector_size (dynamic_lane 8 16)) (VectorSize.Size8x16)) (rule (vector_size (dynamic_lane 16 4)) (VectorSize.Size16x4)) @@ -1059,18 +1311,10 @@ ;; Type of vector element extensions. (type VecExtendOp (enum - ;; Signed extension of 8-bit elements - (Sxtl8) - ;; Signed extension of 16-bit elements - (Sxtl16) - ;; Signed extension of 32-bit elements - (Sxtl32) - ;; Unsigned extension of 8-bit elements - (Uxtl8) - ;; Unsigned extension of 16-bit elements - (Uxtl16) - ;; Unsigned extension of 32-bit elements - (Uxtl32) + ;; Signed extension + (Sxtl) + ;; Unsigned extension + (Uxtl) )) ;; A vector ALU operation. @@ -1108,10 +1352,6 @@ (Orr) ;; Bitwise exclusive or (Eor) - ;; Bitwise select - ;; This opcode should only be used with the `vec_rrr_inplace` - ;; constructor. - (Bsl) ;; Unsigned maximum pairwise (Umaxp) ;; Add @@ -1146,10 +1386,6 @@ (Fmin) ;; Floating-point multiply (Fmul) - ;; Floating-point fused multiply-add vectors - ;; This opcode should only be used with the `vec_rrr_inplace` - ;; constructor. - (Fmla) ;; Add pairwise (Addp) ;; Zip vectors (primary) [meaning, high halves] @@ -1158,6 +1394,15 @@ (Sqrdmulh) )) +;; A Vector ALU operation which modifies a source register. +(type VecALUModOp + (enum + ;; Bitwise select + (Bsl) + ;; Floating-point fused multiply-add vectors + (Fmla) +)) + ;; A Vector miscellaneous operation with two registers. (type VecMisc2 (enum @@ -1255,6 +1500,10 @@ (Umull8) (Umull16) (Umull32) +)) + +(type VecRRRLongModOp + (enum ;; Unsigned multiply add long (Umlal8) (Umlal16) @@ -1300,6 +1549,13 @@ (Sshr) )) +;; Destructive shift-by-immediate operation on each lane of a vector. +(type VecShiftImmModOp + (enum + ;; Shift left and insert + (Sli) +)) + ;; Atomic read-modify-write operations with acquire-release semantics (type AtomicRMWOp (enum @@ -1338,19 +1594,37 @@ (B) )) +;; Branch target types +(type BranchTargetType + (enum + (None) + (C) + (J) + (JC) +)) + ;; Extractors for target features ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(decl pure partial sign_return_address_disabled () Unit) +(extern constructor sign_return_address_disabled sign_return_address_disabled) + (decl use_lse () Inst) (extern extractor use_lse use_lse) ;; Extractor helpers for various immmediate constants ;;;;;;;;;;;;;;;;;;;;;;;;;; -(decl pure imm_logic_from_u64 (Type u64) ImmLogic) +(decl pure partial move_wide_const_from_u64 (Type u64) MoveWideConst) +(extern constructor move_wide_const_from_u64 move_wide_const_from_u64) + +(decl pure partial move_wide_const_from_inverted_u64 (Type u64) MoveWideConst) +(extern constructor move_wide_const_from_inverted_u64 move_wide_const_from_inverted_u64) + +(decl pure partial imm_logic_from_u64 (Type u64) ImmLogic) (extern constructor imm_logic_from_u64 imm_logic_from_u64) -(decl pure imm_logic_from_imm64 (Type Imm64) ImmLogic) +(decl pure partial imm_logic_from_imm64 (Type Imm64) ImmLogic) (extern constructor imm_logic_from_imm64 imm_logic_from_imm64) -(decl pure imm_shift_from_imm64 (Type Imm64) ImmShift) +(decl pure partial imm_shift_from_imm64 (Type Imm64) ImmShift) (extern constructor imm_shift_from_imm64 imm_shift_from_imm64) (decl imm_shift_from_u8 (u8) ImmShift) @@ -1368,21 +1642,68 @@ (decl u64_into_imm_logic (Type u64) ImmLogic) (extern constructor u64_into_imm_logic u64_into_imm_logic) +(decl branch_target (VecMachLabel u8) BranchTarget) +(extern constructor branch_target branch_target) + +(decl targets_jt_size (VecMachLabel) u32) +(extern constructor targets_jt_size targets_jt_size) + +(decl targets_jt_space (VecMachLabel) CodeOffset) +(extern constructor targets_jt_space targets_jt_space) + +(decl targets_jt_info (VecMachLabel) BoxJTSequenceInfo) +(extern constructor targets_jt_info targets_jt_info) + +;; Calculate the minimum floating-point bound for a conversion to floating +;; point from an integer type. +;; Accepts whether the output is signed, the size of the input +;; floating point type in bits, and the size of the output integer type +;; in bits. +(decl min_fp_value (bool u8 u8) Reg) +(extern constructor min_fp_value min_fp_value) + +;; Calculate the maximum floating-point bound for a conversion to floating +;; point from an integer type. +;; Accepts whether the output is signed, the size of the input +;; floating point type in bits, and the size of the output integer type +;; in bits. +(decl max_fp_value (bool u8 u8) Reg) +(extern constructor max_fp_value max_fp_value) + +;; Constructs an FPUOpRI.Ushr* given the size in bits of the value (or lane) +;; and the amount to shift by. +(decl fpu_op_ri_ushr (u8 u8) FPUOpRI) +(extern constructor fpu_op_ri_ushr fpu_op_ri_ushr) + +;; Constructs an FPUOpRIMod.Sli* given the size in bits of the value (or lane) +;; and the amount to shift by. +(decl fpu_op_ri_sli (u8 u8) FPUOpRIMod) +(extern constructor fpu_op_ri_sli fpu_op_ri_sli) + (decl imm12_from_negated_u64 (Imm12) u64) (extern extractor imm12_from_negated_u64 imm12_from_negated_u64) -(decl pure lshr_from_u64 (Type u64) ShiftOpAndAmt) +(decl pure partial lshr_from_u64 (Type u64) ShiftOpAndAmt) (extern constructor lshr_from_u64 lshr_from_u64) -(decl pure lshl_from_imm64 (Type Imm64) ShiftOpAndAmt) +(decl pure partial lshl_from_imm64 (Type Imm64) ShiftOpAndAmt) (extern constructor lshl_from_imm64 lshl_from_imm64) +(decl pure partial lshl_from_u64 (Type u64) ShiftOpAndAmt) +(extern constructor lshl_from_u64 lshl_from_u64) + (decl integral_ty (Type) Type) (extern extractor integral_ty integral_ty) (decl valid_atomic_transaction (Type) Type) (extern extractor valid_atomic_transaction valid_atomic_transaction) +(decl pure partial is_zero_simm9 (SImm9) Unit) +(extern constructor is_zero_simm9 is_zero_simm9) + +(decl pure partial is_zero_uimm12 (UImm12Scaled) Unit) +(extern constructor is_zero_uimm12 is_zero_uimm12) + ;; Helper to go directly from a `Value`, when it's an `iconst`, to an `Imm12`. (decl imm12_from_value (Imm12) Value) (extractor @@ -1412,18 +1733,14 @@ (decl cond_br_zero (Reg) CondBrKind) (extern constructor cond_br_zero cond_br_zero) +(decl cond_br_not_zero (Reg) CondBrKind) +(extern constructor cond_br_not_zero cond_br_not_zero) + (decl cond_br_cond (Cond) CondBrKind) (extern constructor cond_br_cond cond_br_cond) -;; Lower the address of a load or a store. -(decl amode (Type Inst u32) AMode) -;; TODO: Port lower_address() to ISLE. -(extern constructor amode amode) - -;; Matches an `AMode` that is just a register. -(decl pure amode_is_reg (AMode) Reg) -;; TODO: Implement in ISLE. -(extern constructor amode_is_reg amode_is_reg) +(decl pair_amode (Value u32) PairAMode) +(extern constructor pair_amode pair_amode) ;; Instruction creation helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1431,30 +1748,30 @@ (decl zero_reg () Reg) (extern constructor zero_reg zero_reg) -(decl writable_zero_reg () WritableReg) -(extern constructor writable_zero_reg writable_zero_reg) +(decl fp_reg () Reg) +(extern constructor fp_reg fp_reg) -;; Helpers for getting a particular real register -(decl xreg (u8) Reg) -(extern constructor xreg xreg) +(decl stack_reg () Reg) +(extern constructor stack_reg stack_reg) -(decl writable_vreg (u8) WritableReg) -(extern constructor writable_vreg writable_vreg) +(decl writable_link_reg () WritableReg) +(extern constructor writable_link_reg writable_link_reg) -(decl writable_xreg (u8) WritableReg) -(extern constructor writable_xreg writable_xreg) +(decl writable_zero_reg () WritableReg) +(extern constructor writable_zero_reg writable_zero_reg) -;; Helper for emitting `MInst.Mov64` instructions. -(decl mov64_to_real (u8 Reg) Reg) -(rule (mov64_to_real num src) - (let ((dst WritableReg (writable_xreg num)) - (_ Unit (emit (MInst.Mov (operand_size $I64) dst src)))) - dst)) +(decl value_regs_zero () ValueRegs) +(rule (value_regs_zero) + (value_regs + (imm $I64 (ImmExtend.Zero) 0) + (imm $I64 (ImmExtend.Zero) 0))) -(decl mov64_from_real (u8) Reg) -(rule (mov64_from_real num) + +;; Helper for emitting `MInst.Mov` instructions. +(decl mov (Reg Type) Reg) +(rule (mov src ty) (let ((dst WritableReg (temp_writable_reg $I64)) - (_ Unit (emit (MInst.Mov (operand_size $I64) dst (xreg num))))) + (_ Unit (emit (MInst.Mov (operand_size ty) dst src)))) dst)) ;; Helper for emitting `MInst.MovZ` instructions. @@ -1508,11 +1825,22 @@ ;; Helper for emitting `MInst.VecRRR` instructions which use three registers, ;; one of which is both source and output. -(decl vec_rrr_inplace (VecALUOp Reg Reg Reg VectorSize) Reg) -(rule (vec_rrr_inplace op src1 src2 src3 size) +(decl vec_rrr_mod (VecALUModOp Reg Reg Reg VectorSize) Reg) +(rule (vec_rrr_mod op src1 src2 src3 size) (let ((dst WritableReg (temp_writable_reg $I8X16)) - (_1 Unit (emit (MInst.FpuMove128 dst src1))) - (_2 Unit (emit (MInst.VecRRR op dst src2 src3 size)))) + (_1 Unit (emit (MInst.VecRRRMod op dst src1 src2 src3 size)))) + dst)) + +(decl fpu_rri (FPUOpRI Reg) Reg) +(rule (fpu_rri op src) + (let ((dst WritableReg (temp_writable_reg $F64)) + (_ Unit (emit (MInst.FpuRRI op dst src)))) + dst)) + +(decl fpu_rri_mod (FPUOpRIMod Reg Reg) Reg) +(rule (fpu_rri_mod op dst_src src) + (let ((dst WritableReg (temp_writable_reg $F64)) + (_ Unit (emit (MInst.FpuRRIMod op dst dst_src src)))) dst)) ;; Helper for emitting `MInst.FpuRRR` instructions. @@ -1542,6 +1870,13 @@ (_ Unit (emit (MInst.VecLanes op dst src size)))) dst)) +;; Helper for emitting `MInst.VecShiftImm` instructions. +(decl vec_shift_imm (VecShiftImmOp u8 Reg VectorSize) Reg) +(rule (vec_shift_imm op imm src size) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.VecShiftImm op dst src size imm)))) + dst)) + ;; Helper for emitting `MInst.VecDup` instructions. (decl vec_dup (Reg VectorSize) Reg) (rule (vec_dup src size) @@ -1634,25 +1969,25 @@ ;; Helper for materializing a boolean value into a register from ;; flags. -(decl materialize_bool_result (u8 Cond) ConsumesFlags) -(rule (materialize_bool_result 1 cond) +(decl materialize_bool_result (Cond) ConsumesFlags) +(rule (materialize_bool_result cond) (let ((dst WritableReg (temp_writable_reg $I64))) (ConsumesFlags.ConsumesFlagsReturnsReg (MInst.CSet dst cond) dst))) -(rule -1 (materialize_bool_result _ty_bits cond) - (let ((dst WritableReg (temp_writable_reg $I64))) - (ConsumesFlags.ConsumesFlagsReturnsReg - (MInst.CSetm dst cond) - dst))) - (decl cmn_imm (OperandSize Reg Imm12) ProducesFlags) (rule (cmn_imm size src1 src2) (ProducesFlags.ProducesFlagsSideEffect (MInst.AluRRImm12 (ALUOp.AddS) size (writable_zero_reg) src1 src2))) +(decl cmp (OperandSize Reg Reg) ProducesFlags) +(rule (cmp size src1 src2) + (ProducesFlags.ProducesFlagsSideEffect + (MInst.AluRRR (ALUOp.SubS) size (writable_zero_reg) + src1 src2))) + (decl cmp_imm (OperandSize Reg Imm12) ProducesFlags) (rule (cmp_imm size src1 src2) (ProducesFlags.ProducesFlagsSideEffect @@ -1663,6 +1998,12 @@ (rule (cmp64_imm src1 src2) (cmp_imm (OperandSize.Size64) src1 src2)) +(decl cmp_extend (OperandSize Reg Reg ExtendOp) ProducesFlags) +(rule (cmp_extend size src1 src2 extend) + (ProducesFlags.ProducesFlagsSideEffect + (MInst.AluRRRExtend (ALUOp.SubS) size (writable_zero_reg) + src1 src2 extend))) + ;; Helper for emitting `sbc` instructions. (decl sbc_paired (Type Reg Reg) ConsumesFlags) (rule (sbc_paired ty src1 src2) @@ -1679,29 +2020,33 @@ dst)) ;; Helper for emitting `MInst.VecTbl` instructions. -(decl vec_tbl (Reg Reg bool) Reg) -(rule (vec_tbl rn rm is_extension) +(decl vec_tbl (Reg Reg) Reg) +(rule (vec_tbl rn rm) (let ((dst WritableReg (temp_writable_reg $I8X16)) - (_ Unit (emit (MInst.VecTbl dst rn rm is_extension)))) + (_ Unit (emit (MInst.VecTbl dst rn rm)))) + dst)) + +(decl vec_tbl_ext (Reg Reg Reg) Reg) +(rule (vec_tbl_ext ri rn rm) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.VecTblExt dst ri rn rm)))) dst)) ;; Helper for emitting `MInst.VecTbl2` instructions. -;; - 2 register table vector lookups require consecutive table registers; -;; we satisfy this constraint by hardcoding the usage of v30 and v31. -;; - Make sure that both args are in virtual regs, since it is not guaranteed -;; that we can get them safely to the temporaries if either is in a real -;; register. -(decl vec_tbl2 (Reg Reg Reg bool Type) Reg) -(rule (vec_tbl2 rn rn2 rm is_extension ty) +(decl vec_tbl2 (Reg Reg Reg Type) Reg) +(rule (vec_tbl2 rn rn2 rm ty) + (let ( + (dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.VecTbl2 dst rn rn2 rm))) + ) + dst)) + +;; Helper for emitting `MInst.VecTbl2Ext` instructions. +(decl vec_tbl2_ext (Reg Reg Reg Reg Type) Reg) +(rule (vec_tbl2_ext ri rn rn2 rm ty) (let ( - (temp WritableReg (writable_vreg 30)) - (temp2 WritableReg (writable_vreg 31)) (dst WritableReg (temp_writable_reg $I8X16)) - (rn Reg (ensure_in_vreg rn ty)) - (rn2 Reg (ensure_in_vreg rn2 ty)) - (_ Unit (emit (MInst.FpuMove128 temp rn))) - (_ Unit (emit (MInst.FpuMove128 temp2 rn2))) - (_ Unit (emit (MInst.VecTbl2 dst temp temp2 rm is_extension))) + (_ Unit (emit (MInst.VecTbl2Ext dst ri rn rn2 rm))) ) dst)) @@ -1719,22 +2064,18 @@ (_ Unit (emit (MInst.VecRRPairLong op dst src)))) dst)) -;; Helper for emitting `MInst.VecRRRLong` instructions, but for variants -;; where the operation both reads and modifies the destination register. -;; -;; Currently this is only used for `VecRRRLongOp.Umlal*` -(decl vec_rrrr_long (VecRRRLongOp Reg Reg Reg bool) Reg) +;; Helper for emitting `MInst.VecRRRLongMod` instructions. +(decl vec_rrrr_long (VecRRRLongModOp Reg Reg Reg bool) Reg) (rule (vec_rrrr_long op src1 src2 src3 high_half) (let ((dst WritableReg (temp_writable_reg $I8X16)) - (_ Unit (emit (MInst.FpuMove128 dst src1))) - (_ Unit (emit (MInst.VecRRRLong op dst src2 src3 high_half)))) + (_ Unit (emit (MInst.VecRRRLongMod op dst src1 src2 src3 high_half)))) dst)) ;; Helper for emitting `MInst.VecRRNarrow` instructions. -(decl vec_rr_narrow (VecRRNarrowOp Reg ScalarSize) Reg) -(rule (vec_rr_narrow op src size) +(decl vec_rr_narrow_low (VecRRNarrowOp Reg ScalarSize) Reg) +(rule (vec_rr_narrow_low op src size) (let ((dst WritableReg (temp_writable_reg $I8X16)) - (_ Unit (emit (MInst.VecRRNarrow op dst src $false size)))) + (_ Unit (emit (MInst.VecRRNarrowLow op dst src size)))) dst)) ;; Helper for emitting `MInst.VecRRNarrow` instructions which update the @@ -1742,8 +2083,7 @@ (decl vec_rr_narrow_high (VecRRNarrowOp Reg Reg ScalarSize) Reg) (rule (vec_rr_narrow_high op mod src size) (let ((dst WritableReg (temp_writable_reg $I8X16)) - (_ Unit (emit (MInst.FpuMove128 dst mod))) - (_ Unit (emit (MInst.VecRRNarrow op dst src $true size)))) + (_ Unit (emit (MInst.VecRRNarrowHigh op dst mod src size)))) dst)) ;; Helper for emitting `MInst.VecRRLong` instructions. @@ -1768,6 +2108,14 @@ (MInst.FpuCSel64 dst if_true if_false cond) dst))) +;; Helper for emitting `MInst.VecCSel` instructions. +(decl vec_csel (Cond Reg Reg) ConsumesFlags) +(rule (vec_csel cond if_true if_false) + (let ((dst WritableReg (temp_writable_reg $I8X16))) + (ConsumesFlags.ConsumesFlagsReturnsReg + (MInst.VecCSel dst if_true if_false cond) + dst))) + ;; Helper for emitting `MInst.FpuRound` instructions. (decl fpu_round (FpuRoundMode Reg) Reg) (rule (fpu_round op rn) @@ -1775,6 +2123,17 @@ (_ Unit (emit (MInst.FpuRound op dst rn)))) dst)) +;; Helper for emitting `MInst.FpuMove64` and `MInst.FpuMove128` instructions. +(decl fpu_move (Type Reg) Reg) +(rule (fpu_move _ src) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.FpuMove128 dst src)))) + dst)) +(rule 1 (fpu_move (fits_in_64 _) src) + (let ((dst WritableReg (temp_writable_reg $F64)) + (_ Unit (emit (MInst.FpuMove64 dst src)))) + dst)) + ;; Helper for emitting `MInst.MovToFpu` instructions. (decl mov_to_fpu (Reg ScalarSize) Reg) (rule (mov_to_fpu x size) @@ -1786,16 +2145,14 @@ (decl mov_to_vec (Reg Reg u8 VectorSize) Reg) (rule (mov_to_vec src1 src2 lane size) (let ((dst WritableReg (temp_writable_reg $I8X16)) - (_ Unit (emit (MInst.FpuMove128 dst src1))) - (_ Unit (emit (MInst.MovToVec dst src2 lane size)))) + (_ Unit (emit (MInst.MovToVec dst src1 src2 lane size)))) dst)) ;; Helper for emitting `MInst.VecMovElement` instructions. (decl mov_vec_elem (Reg Reg u8 u8 VectorSize) Reg) (rule (mov_vec_elem src1 src2 dst_idx src_idx size) (let ((dst WritableReg (temp_writable_reg $I8X16)) - (_ Unit (emit (MInst.FpuMove128 dst src1))) - (_ Unit (emit (MInst.VecMovElement dst src2 dst_idx src_idx size)))) + (_ Unit (emit (MInst.VecMovElement dst src1 src2 dst_idx src_idx size)))) dst)) ;; Helper for emitting `MInst.MovFromVec` instructions. @@ -1812,6 +2169,12 @@ (_ Unit (emit (MInst.MovFromVecSigned dst rn idx size scalar_size)))) dst)) +(decl fpu_move_from_vec (Reg u8 VectorSize) Reg) +(rule (fpu_move_from_vec rn idx size) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.FpuMoveFromVec dst rn idx size)))) + dst)) + ;; Helper for emitting `MInst.Extend` instructions. (decl extend (Reg bool u8 u8) Reg) (rule (extend rn signed from_bits to_bits) @@ -1826,17 +2189,31 @@ (_ Unit (emit (MInst.FpuExtend dst src size)))) dst)) +;; Helper for emitting `MInst.VecExtend` instructions. +(decl vec_extend (VecExtendOp Reg bool ScalarSize) Reg) +(rule (vec_extend op src high_half size) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.VecExtend op dst src high_half size)))) + dst)) + +;; Helper for emitting `MInst.VecExtract` instructions. +(decl vec_extract (Reg Reg u8) Reg) +(rule (vec_extract src1 src2 idx) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.VecExtract dst src1 src2 idx)))) + dst)) + ;; Helper for emitting `MInst.LoadAcquire` instructions. -(decl load_acquire (Type Reg) Reg) -(rule (load_acquire ty addr) +(decl load_acquire (Type MemFlags Reg) Reg) +(rule (load_acquire ty flags addr) (let ((dst WritableReg (temp_writable_reg $I64)) - (_ Unit (emit (MInst.LoadAcquire ty dst addr)))) + (_ Unit (emit (MInst.LoadAcquire ty dst addr flags)))) dst)) ;; Helper for emitting `MInst.StoreRelease` instructions. -(decl store_release (Type Reg Reg) SideEffectNoResult) -(rule (store_release ty src addr) - (SideEffectNoResult.Inst (MInst.StoreRelease ty src addr))) +(decl store_release (Type MemFlags Reg Reg) SideEffectNoResult) +(rule (store_release ty flags src addr) + (SideEffectNoResult.Inst (MInst.StoreRelease ty src addr flags))) ;; Helper for generating a `tst` instruction. ;; @@ -1863,6 +2240,25 @@ (MInst.CSel dst cond if_true if_false) dst))) +;; Helper for constructing `cset` instructions. +(decl cset (Cond) ConsumesFlags) +(rule (cset cond) + (let ((dst WritableReg (temp_writable_reg $I64))) + (ConsumesFlags.ConsumesFlagsReturnsReg (MInst.CSet dst cond) dst))) + +;; Helper for constructing `cset` instructions, when the flags producer will +;; also return a value. +(decl cset_paired (Cond) ConsumesFlags) +(rule (cset_paired cond) + (let ((dst WritableReg (temp_writable_reg $I64))) + (ConsumesFlags.ConsumesFlagsReturnsResultWithProducer (MInst.CSet dst cond) dst))) + +;; Helper for constructing `csetm` instructions. +(decl csetm (Cond) ConsumesFlags) +(rule (csetm cond) + (let ((dst WritableReg (temp_writable_reg $I64))) + (ConsumesFlags.ConsumesFlagsReturnsReg (MInst.CSetm dst cond) dst))) + ;; Helper for generating a `CSNeg` instruction. ;; ;; Note that this doesn't actually emit anything, instead it produces a @@ -1875,22 +2271,22 @@ (MInst.CSNeg dst cond if_true if_false) dst))) +;; Helper for generating `MInst.CCmp` instructions. +;; Creates a new `ProducesFlags` from the supplied `ProducesFlags` followed +;; immediately by the `MInst.CCmp` instruction. +(decl ccmp (OperandSize Reg Reg NZCV Cond ProducesFlags) ProducesFlags) +(rule (ccmp size rn rm nzcv cond inst_input) + (produces_flags_append inst_input (MInst.CCmp size rn rm nzcv cond))) + ;; Helper for generating `MInst.CCmpImm` instructions. -(decl ccmp_imm (OperandSize u8 Reg UImm5 NZCV Cond) ConsumesFlags) -(rule (ccmp_imm size 1 rn imm nzcv cond) +(decl ccmp_imm (OperandSize Reg UImm5 NZCV Cond) ConsumesFlags) +(rule 1 (ccmp_imm size rn imm nzcv cond) (let ((dst WritableReg (temp_writable_reg $I64))) (ConsumesFlags.ConsumesFlagsTwiceReturnsValueRegs (MInst.CCmpImm size rn imm nzcv cond) (MInst.CSet dst cond) (value_reg dst)))) -(rule (ccmp_imm size _ty_bits rn imm nzcv cond) - (let ((dst WritableReg (temp_writable_reg $I64))) - (ConsumesFlags.ConsumesFlagsTwiceReturnsValueRegs - (MInst.CCmpImm size rn imm nzcv cond) - (MInst.CSetm dst cond) - (value_reg dst)))) - ;; Helpers for generating `add` instructions. (decl add (Type Reg Reg) Reg) @@ -1902,6 +2298,9 @@ (decl add_extend (Type Reg ExtendedValue) Reg) (rule (add_extend ty x y) (alu_rr_extend_reg (ALUOp.Add) ty x y)) +(decl add_extend_op (Type Reg Reg ExtendOp) Reg) +(rule (add_extend_op ty x y extend) (alu_rrr_extend (ALUOp.Add) ty x y extend)) + (decl add_shift (Type Reg Reg ShiftOpAndAmt) Reg) (rule (add_shift ty x y z) (alu_rrr_shift (ALUOp.Add) ty x y z)) @@ -1925,6 +2324,24 @@ (decl sub_vec (Reg Reg VectorSize) Reg) (rule (sub_vec x y size) (vec_rrr (VecALUOp.Sub) x y size)) +(decl sub_i128 (ValueRegs ValueRegs) ValueRegs) +(rule (sub_i128 x y) + (let + ;; Get the high/low registers for `x`. + ((x_regs ValueRegs x) + (x_lo Reg (value_regs_get x_regs 0)) + (x_hi Reg (value_regs_get x_regs 1)) + + ;; Get the high/low registers for `y`. + (y_regs ValueRegs y) + (y_lo Reg (value_regs_get y_regs 0)) + (y_hi Reg (value_regs_get y_regs 1))) + ;; the actual subtraction is `subs` followed by `sbc` which comprises + ;; the low/high bits of the result + (with_flags + (sub_with_flags_paired $I64 x_lo y_lo) + (sbc_paired $I64 x_hi y_hi)))) + ;; Helpers for generating `madd` instructions. (decl madd (Type Reg Reg Reg) Reg) @@ -1973,15 +2390,15 @@ ;; Helper for generating `xtn` instructions. (decl xtn (Reg ScalarSize) Reg) -(rule (xtn x size) (vec_rr_narrow (VecRRNarrowOp.Xtn) x size)) +(rule (xtn x size) (vec_rr_narrow_low (VecRRNarrowOp.Xtn) x size)) ;; Helper for generating `fcvtn` instructions. (decl fcvtn (Reg ScalarSize) Reg) -(rule (fcvtn x size) (vec_rr_narrow (VecRRNarrowOp.Fcvtn) x size)) +(rule (fcvtn x size) (vec_rr_narrow_low (VecRRNarrowOp.Fcvtn) x size)) ;; Helper for generating `sqxtn` instructions. (decl sqxtn (Reg ScalarSize) Reg) -(rule (sqxtn x size) (vec_rr_narrow (VecRRNarrowOp.Sqxtn) x size)) +(rule (sqxtn x size) (vec_rr_narrow_low (VecRRNarrowOp.Sqxtn) x size)) ;; Helper for generating `sqxtn2` instructions. (decl sqxtn2 (Reg Reg ScalarSize) Reg) @@ -1989,7 +2406,7 @@ ;; Helper for generating `sqxtun` instructions. (decl sqxtun (Reg ScalarSize) Reg) -(rule (sqxtun x size) (vec_rr_narrow (VecRRNarrowOp.Sqxtun) x size)) +(rule (sqxtun x size) (vec_rr_narrow_low (VecRRNarrowOp.Sqxtun) x size)) ;; Helper for generating `sqxtun2` instructions. (decl sqxtun2 (Reg Reg ScalarSize) Reg) @@ -1997,7 +2414,7 @@ ;; Helper for generating `uqxtn` instructions. (decl uqxtn (Reg ScalarSize) Reg) -(rule (uqxtn x size) (vec_rr_narrow (VecRRNarrowOp.Uqxtn) x size)) +(rule (uqxtn x size) (vec_rr_narrow_low (VecRRNarrowOp.Uqxtn) x size)) ;; Helper for generating `uqxtn2` instructions. (decl uqxtn2 (Reg Reg ScalarSize) Reg) @@ -2008,6 +2425,11 @@ (rule (aarch64_fence) (SideEffectNoResult.Inst (MInst.Fence))) +;; Helper for generating `csdb` instructions. +(decl csdb () SideEffectNoResult) +(rule (csdb) + (SideEffectNoResult.Inst (MInst.Csdb))) + ;; Helper for generating `brk` instructions. (decl brk () SideEffectNoResult) (rule (brk) @@ -2017,6 +2439,10 @@ (decl addp (Reg Reg VectorSize) Reg) (rule (addp x y size) (vec_rrr (VecALUOp.Addp) x y size)) +;; Helper for generating `zip1` instructions. +(decl zip1 (Reg Reg VectorSize) Reg) +(rule (zip1 x y size) (vec_rrr (VecALUOp.Zip1) x y size)) + ;; Helper for generating vector `abs` instructions. (decl vec_abs (Reg VectorSize) Reg) (rule (vec_abs x size) (vec_misc (VecMisc2.Abs) x size)) @@ -2052,7 +2478,7 @@ ;; Helper for generating `umlal32` instructions. (decl umlal32 (Reg Reg Reg bool) Reg) -(rule (umlal32 x y z high_half) (vec_rrrr_long (VecRRRLongOp.Umlal32) x y z high_half)) +(rule (umlal32 x y z high_half) (vec_rrrr_long (VecRRRLongModOp.Umlal32) x y z high_half)) ;; Helper for generating `smull8` instructions. (decl smull8 (Reg Reg bool) Reg) @@ -2184,6 +2610,17 @@ (decl a64_cls (Type Reg) Reg) (rule (a64_cls ty x) (bit_rr (BitOp.Cls) ty x)) +;; Helpers for generating `rev` instructions + +(decl a64_rev16 (Type Reg) Reg) +(rule (a64_rev16 ty x) (bit_rr (BitOp.Rev16) ty x)) + +(decl a64_rev32 (Type Reg) Reg) +(rule (a64_rev32 ty x) (bit_rr (BitOp.Rev32) ty x)) + +(decl a64_rev64 (Type Reg) Reg) +(rule (a64_rev64 ty x) (bit_rr (BitOp.Rev64) ty x)) + ;; Helpers for generating `eon` instructions. (decl eon (Type Reg Reg) Reg) @@ -2198,10 +2635,7 @@ (decl bsl (Type Reg Reg Reg) Reg) (rule (bsl ty c x y) - (let ((dst WritableReg (temp_writable_reg ty)) - (_ Unit (emit (MInst.FpuMove128 dst c))) - (_ Unit (emit (MInst.VecRRR (VecALUOp.Bsl) dst x y (vector_size ty))))) - dst)) + (vec_rrr_mod (VecALUModOp.Bsl) c x y (vector_size ty))) ;; Helper for generating a `udf` instruction. @@ -2209,6 +2643,101 @@ (rule (udf trap_code) (SideEffectNoResult.Inst (MInst.Udf trap_code))) +;; Helpers for generating various load instructions, with varying +;; widths and sign/zero-extending properties. +(decl aarch64_uload8 (AMode MemFlags) Reg) +(rule (aarch64_uload8 amode flags) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.ULoad8 dst amode flags)))) + dst)) +(decl aarch64_sload8 (AMode MemFlags) Reg) +(rule (aarch64_sload8 amode flags) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.SLoad8 dst amode flags)))) + dst)) +(decl aarch64_uload16 (AMode MemFlags) Reg) +(rule (aarch64_uload16 amode flags) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.ULoad16 dst amode flags)))) + dst)) +(decl aarch64_sload16 (AMode MemFlags) Reg) +(rule (aarch64_sload16 amode flags) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.SLoad16 dst amode flags)))) + dst)) +(decl aarch64_uload32 (AMode MemFlags) Reg) +(rule (aarch64_uload32 amode flags) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.ULoad32 dst amode flags)))) + dst)) +(decl aarch64_sload32 (AMode MemFlags) Reg) +(rule (aarch64_sload32 amode flags) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.SLoad32 dst amode flags)))) + dst)) +(decl aarch64_uload64 (AMode MemFlags) Reg) +(rule (aarch64_uload64 amode flags) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.ULoad64 dst amode flags)))) + dst)) +(decl aarch64_fpuload32 (AMode MemFlags) Reg) +(rule (aarch64_fpuload32 amode flags) + (let ((dst WritableReg (temp_writable_reg $F64)) + (_ Unit (emit (MInst.FpuLoad32 dst amode flags)))) + dst)) +(decl aarch64_fpuload64 (AMode MemFlags) Reg) +(rule (aarch64_fpuload64 amode flags) + (let ((dst WritableReg (temp_writable_reg $F64)) + (_ Unit (emit (MInst.FpuLoad64 dst amode flags)))) + dst)) +(decl aarch64_fpuload128 (AMode MemFlags) Reg) +(rule (aarch64_fpuload128 amode flags) + (let ((dst WritableReg (temp_writable_reg $F64X2)) + (_ Unit (emit (MInst.FpuLoad128 dst amode flags)))) + dst)) +(decl aarch64_loadp64 (PairAMode MemFlags) ValueRegs) +(rule (aarch64_loadp64 amode flags) + (let ((dst1 WritableReg (temp_writable_reg $I64)) + (dst2 WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.LoadP64 dst1 dst2 amode flags)))) + (value_regs dst1 dst2))) + +;; Helpers for generating various store instructions with varying +;; widths. +(decl aarch64_store8 (AMode MemFlags Reg) SideEffectNoResult) +(rule (aarch64_store8 amode flags val) + (SideEffectNoResult.Inst (MInst.Store8 val amode flags))) +(decl aarch64_store16 (AMode MemFlags Reg) SideEffectNoResult) +(rule (aarch64_store16 amode flags val) + (SideEffectNoResult.Inst (MInst.Store16 val amode flags))) +(decl aarch64_store32 (AMode MemFlags Reg) SideEffectNoResult) +(rule (aarch64_store32 amode flags val) + (SideEffectNoResult.Inst (MInst.Store32 val amode flags))) +(decl aarch64_store64 (AMode MemFlags Reg) SideEffectNoResult) +(rule (aarch64_store64 amode flags val) + (SideEffectNoResult.Inst (MInst.Store64 val amode flags))) +(decl aarch64_fpustore32 (AMode MemFlags Reg) SideEffectNoResult) +(rule (aarch64_fpustore32 amode flags val) + (SideEffectNoResult.Inst (MInst.FpuStore32 val amode flags))) +(decl aarch64_fpustore64 (AMode MemFlags Reg) SideEffectNoResult) +(rule (aarch64_fpustore64 amode flags val) + (SideEffectNoResult.Inst (MInst.FpuStore64 val amode flags))) +(decl aarch64_fpustore128 (AMode MemFlags Reg) SideEffectNoResult) +(rule (aarch64_fpustore128 amode flags val) + (SideEffectNoResult.Inst (MInst.FpuStore128 val amode flags))) +(decl aarch64_storep64 (PairAMode MemFlags Reg Reg) SideEffectNoResult) +(rule (aarch64_storep64 amode flags val1 val2) + (SideEffectNoResult.Inst (MInst.StoreP64 val1 val2 amode flags))) + +;; Helper for generating a `trapif` instruction. + +(decl trap_if (ProducesFlags TrapCode Cond) InstOutput) +(rule (trap_if flags trap_code cond) + (side_effect + (with_flags_side_effect flags + (ConsumesFlags.ConsumesFlagsSideEffect + (MInst.TrapIf (cond_br_cond cond) trap_code))))) + ;; Immediate value helpers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Type of extension performed by an immediate helper @@ -2229,9 +2758,18 @@ ;; such as `I8` are either sign- or zero-extended. (decl imm (Type ImmExtend u64) Reg) +;; Move wide immediate instructions; to simplify, we only match when we +;; are zero-extending the value. +(rule 3 (imm (integral_ty ty) (ImmExtend.Zero) k) + (if-let n (move_wide_const_from_u64 ty k)) + (movz n (operand_size ty))) +(rule 2 (imm (integral_ty (ty_32_or_64 ty)) (ImmExtend.Zero) k) + (if-let n (move_wide_const_from_inverted_u64 ty k)) + (movn n (operand_size ty))) + ;; Weird logical-instruction immediate in ORI using zero register; to simplify, ;; we only match when we are zero-extending the value. -(rule (imm (integral_ty ty) (ImmExtend.Zero) k) +(rule 1 (imm (integral_ty ty) (ImmExtend.Zero) k) (if-let n (imm_logic_from_u64 ty k)) (orr_imm ty (zero_reg) n)) @@ -2246,7 +2784,7 @@ ;; Place a `Value` into a register, sign extending it to 32-bits (decl put_in_reg_sext32 (Value) Reg) -(rule (put_in_reg_sext32 val @ (value_type (fits_in_32 ty))) +(rule -1 (put_in_reg_sext32 val @ (value_type (fits_in_32 ty))) (extend val $true (ty_bits ty) 32)) ;; 32/64-bit passthrough. @@ -2255,7 +2793,7 @@ ;; Place a `Value` into a register, zero extending it to 32-bits (decl put_in_reg_zext32 (Value) Reg) -(rule (put_in_reg_zext32 val @ (value_type (fits_in_32 ty))) +(rule -1 (put_in_reg_zext32 val @ (value_type (fits_in_32 ty))) (extend val $false (ty_bits ty) 32)) ;; 32/64-bit passthrough. @@ -2264,7 +2802,7 @@ ;; Place a `Value` into a register, sign extending it to 64-bits (decl put_in_reg_sext64 (Value) Reg) -(rule (put_in_reg_sext64 val @ (value_type (fits_in_32 ty))) +(rule 1 (put_in_reg_sext64 val @ (value_type (fits_in_32 ty))) (extend val $true (ty_bits ty) 64)) ;; 64-bit passthrough. @@ -2272,7 +2810,7 @@ ;; Place a `Value` into a register, zero extending it to 64-bits (decl put_in_reg_zext64 (Value) Reg) -(rule (put_in_reg_zext64 val @ (value_type (fits_in_32 ty))) +(rule 1 (put_in_reg_zext64 val @ (value_type (fits_in_32 ty))) (extend val $false (ty_bits ty) 64)) ;; 64-bit passthrough. @@ -2286,7 +2824,7 @@ reg)) (decl size_from_ty (Type) OperandSize) -(rule (size_from_ty (fits_in_32 _ty)) (OperandSize.Size32)) +(rule 1 (size_from_ty (fits_in_32 _ty)) (OperandSize.Size32)) (rule (size_from_ty $I64) (OperandSize.Size64)) ;; Check for signed overflow. The only case is min_value / -1. @@ -2310,21 +2848,18 @@ ) x)) -;; An atomic load that can be sunk into another operation. -(type SinkableAtomicLoad extern (enum)) +;; Check for unsigned overflow. +(decl trap_if_overflow (ProducesFlags TrapCode) Reg) +(rule (trap_if_overflow producer tc) + (with_flags_reg + producer + (ConsumesFlags.ConsumesFlagsSideEffect + (MInst.TrapIf (cond_br_cond (Cond.Hs)) tc)))) -;; Extract a `SinkableAtomicLoad` that works with `Reg` from a value -;; operand. -(decl sinkable_atomic_load (SinkableAtomicLoad) Value) -(extern extractor sinkable_atomic_load sinkable_atomic_load) - -;; Sink a `SinkableAtomicLoad` into a `Reg`. -;; -;; This is a side-effectful operation that notifies the context that the -;; instruction that produced the `SinkableAtomicLoad` has been sunk into another -;; instruction, and no longer needs to be lowered. -(decl sink_atomic_load (SinkableAtomicLoad) Reg) -(extern constructor sink_atomic_load sink_atomic_load) +(decl sink_atomic_load (Inst) Reg) +(rule (sink_atomic_load x @ (atomic_load _ addr)) + (let ((_ Unit (sink_inst x))) + (put_in_reg addr))) ;; Helper for generating either an `AluRRR`, `AluRRRShift`, or `AluRRImmLogic` ;; instruction depending on the input. Note that this requires that the `ALUOp` @@ -2332,14 +2867,14 @@ (decl alu_rs_imm_logic_commutative (ALUOp Type Value Value) Reg) ;; Base case of operating on registers. -(rule (alu_rs_imm_logic_commutative op ty x y) +(rule -1 (alu_rs_imm_logic_commutative op ty x y) (alu_rrr op ty x y)) ;; Special cases for when one operand is a constant. (rule (alu_rs_imm_logic_commutative op ty x (iconst k)) (if-let imm (imm_logic_from_imm64 ty k)) (alu_rr_imm_logic op ty x imm)) -(rule (alu_rs_imm_logic_commutative op ty (iconst k) x) +(rule 1 (alu_rs_imm_logic_commutative op ty (iconst k) x) (if-let imm (imm_logic_from_imm64 ty k)) (alu_rr_imm_logic op ty x imm)) @@ -2347,14 +2882,14 @@ (rule (alu_rs_imm_logic_commutative op ty x (ishl y (iconst k))) (if-let amt (lshl_from_imm64 ty k)) (alu_rrr_shift op ty x y amt)) -(rule (alu_rs_imm_logic_commutative op ty (ishl x (iconst k)) y) +(rule 1 (alu_rs_imm_logic_commutative op ty (ishl x (iconst k)) y) (if-let amt (lshl_from_imm64 ty k)) (alu_rrr_shift op ty y x amt)) ;; Same as `alu_rs_imm_logic_commutative` above, except that it doesn't require ;; that the operation is commutative. (decl alu_rs_imm_logic (ALUOp Type Value Value) Reg) -(rule (alu_rs_imm_logic op ty x y) +(rule -1 (alu_rs_imm_logic op ty x y) (alu_rrr op ty x y)) (rule (alu_rs_imm_logic op ty x (iconst k)) (if-let imm (imm_logic_from_imm64 ty k)) @@ -2388,16 +2923,52 @@ (_ Unit (emit (MInst.VecLoadReplicate dst src size flags)))) dst)) +;; Helper for emitting `MInst.LoadExtName` instructions. +(decl load_ext_name (BoxExternalName i64) Reg) +(rule (load_ext_name extname offset) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.LoadExtName dst extname offset)))) + dst)) + ;; Helper for emitting `MInst.LoadAddr` instructions. (decl load_addr (AMode) Reg) -(rule (load_addr addr) + +(rule (load_addr (AMode.UnsignedOffset r imm)) + (if (is_zero_uimm12 imm)) + r) + +(rule (load_addr (AMode.Unscaled r imm)) + (if (is_zero_simm9 imm)) + r) + +(rule (load_addr (AMode.RegOffset r 0 _)) r) +(rule (load_addr (AMode.FPOffset 0 _)) (fp_reg)) +(rule (load_addr (AMode.SPOffset 0 _)) (stack_reg)) + +(rule -1 (load_addr addr) (let ((dst WritableReg (temp_writable_reg $I64)) (_ Unit (emit (MInst.LoadAddr dst addr)))) dst)) -(rule (load_addr addr) - (if-let addr_reg (amode_is_reg addr)) - addr_reg) +;; Lower the address of a load or a store. +(decl amode (Type Value u32) AMode) +;; TODO: Port lower_address() to ISLE. +(extern constructor amode amode) + +(decl sink_load_into_amode (Type Inst) AMode) +(rule (sink_load_into_amode ty x @ (load _ addr offset)) + (let ((_ Unit (sink_inst x))) + (amode ty addr offset))) + +;; Lower a constant f32. +(decl constant_f32 (u64) Reg) +;; TODO: Port lower_constant_f32() to ISLE. +(extern constructor constant_f32 constant_f32) + +;; Lower a constant f64. +(decl constant_f64 (u64) Reg) +;; TODO: Port lower_constant_f64() to ISLE. +(extern constructor constant_f64 constant_f64) ;; Lower a constant f128. (decl constant_f128 (u128) Reg) @@ -2409,6 +2980,21 @@ ;; TODO: Port lower_splat_const() to ISLE. (extern constructor splat_const splat_const) +;; Lower a FloatCC to a Cond. +(decl fp_cond_code (FloatCC) Cond) +;; TODO: Port lower_fp_condcode() to ISLE. +(extern constructor fp_cond_code fp_cond_code) + +;; Lower an integer cond code. +(decl cond_code (IntCC) Cond) +;; TODO: Port lower_condcode() to ISLE. +(extern constructor cond_code cond_code) + +;; Invert a condition code. +(decl invert_cond (Cond) Cond) +;; TODO: Port cond.invert() to ISLE. +(extern constructor invert_cond invert_cond) + ;; Generate comparison to zero operator from input condition code (decl float_cc_cmp_zero_to_vec_misc_op (FloatCC) VecMisc2) (extern constructor float_cc_cmp_zero_to_vec_misc_op float_cc_cmp_zero_to_vec_misc_op) @@ -2470,22 +3056,21 @@ (vec_misc (VecMisc2.Cmeq0) rn size)) ;; Helper for emitting `MInst.AtomicRMW` instructions. -(decl lse_atomic_rmw (AtomicRMWOp Value Reg Type) Reg) -(rule (lse_atomic_rmw op p r_arg2 ty) +(decl lse_atomic_rmw (AtomicRMWOp Value Reg Type MemFlags) Reg) +(rule (lse_atomic_rmw op p r_arg2 ty flags) (let ( (r_addr Reg p) (dst WritableReg (temp_writable_reg ty)) - (_ Unit (emit (MInst.AtomicRMW op r_arg2 dst r_addr ty))) + (_ Unit (emit (MInst.AtomicRMW op r_arg2 dst r_addr ty flags))) ) dst)) ;; Helper for emitting `MInst.AtomicCAS` instructions. -(decl lse_atomic_cas (Reg Reg Reg Type) Reg) -(rule (lse_atomic_cas addr expect replace ty) +(decl lse_atomic_cas (Reg Reg Reg Type MemFlags) Reg) +(rule (lse_atomic_cas addr expect replace ty flags) (let ( (dst WritableReg (temp_writable_reg ty)) - (_ Unit (emit (MInst.Mov (operand_size ty) dst expect))) - (_ Unit (emit (MInst.AtomicCAS dst replace addr ty))) + (_ Unit (emit (MInst.AtomicCAS dst expect replace addr ty flags))) ) dst)) @@ -2495,16 +3080,13 @@ ;; regs, and that's not guaranteed safe if either is in a real reg. ;; - Move the args to the preordained AtomicRMW input regs ;; - And finally, copy the preordained AtomicRMW output reg to its destination. -(decl atomic_rmw_loop (AtomicRMWLoopOp Value Value Type) Reg) -(rule (atomic_rmw_loop op p arg2 ty) - (let ( - (v_addr Reg (ensure_in_vreg p $I64)) - (v_arg2 Reg (ensure_in_vreg arg2 $I64)) - (r_addr Reg (mov64_to_real 25 v_addr)) - (r_arg2 Reg (mov64_to_real 26 v_arg2)) - (_ Unit (emit (MInst.AtomicRMWLoop ty op))) - ) - (mov64_from_real 27))) +(decl atomic_rmw_loop (AtomicRMWLoopOp Reg Reg Type MemFlags) Reg) +(rule (atomic_rmw_loop op addr operand ty flags) + (let ((dst WritableReg (temp_writable_reg $I64)) + (scratch1 WritableReg (temp_writable_reg $I64)) + (scratch2 WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.AtomicRMWLoop ty op flags addr operand dst scratch1 scratch2)))) + dst)) ;; Helper for emitting `MInst.AtomicCASLoop` instructions. ;; This is very similar to, but not identical to, the AtomicRmw case. Note @@ -2512,31 +3094,24 @@ ;; about zero-extending narrow (I8/I16/I32) values here. ;; Make sure that all three args are in virtual regs. See corresponding comment ;; for `atomic_rmw_loop` above. -(decl atomic_cas_loop (Reg Reg Reg Type) Reg) -(rule (atomic_cas_loop addr expect replace ty) - (let ( - (v_addr Reg (ensure_in_vreg addr $I64)) - (v_exp Reg (ensure_in_vreg expect $I64)) - (v_rep Reg (ensure_in_vreg replace $I64)) - ;; Move the args to the preordained AtomicCASLoop input regs - (r_addr Reg (mov64_to_real 25 v_addr)) - (r_exp Reg (mov64_to_real 26 v_exp)) - (r_rep Reg (mov64_to_real 28 v_rep)) - ;; Now the AtomicCASLoop itself, implemented in the normal way, with a - ;; load-exclusive, store-exclusive loop - (_ Unit (emit (MInst.AtomicCASLoop ty))) - ) - ;; And finally, copy the preordained AtomicCASLoop output reg to its destination. - ;; Also, x24 and x28 are trashed. - (mov64_from_real 27))) +(decl atomic_cas_loop (Reg Reg Reg Type MemFlags) Reg) +(rule (atomic_cas_loop addr expect replace ty flags) + (let ((dst WritableReg (temp_writable_reg $I64)) + (scratch WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.AtomicCASLoop ty flags addr expect replace dst scratch)))) + dst)) ;; Helper for emitting `MInst.MovPReg` instructions. -(decl mov_preg (PReg) Reg) -(rule (mov_preg src) +(decl mov_from_preg (PReg) Reg) +(rule (mov_from_preg src) (let ((dst WritableReg (temp_writable_reg $I64)) - (_ Unit (emit (MInst.MovPReg dst src)))) + (_ Unit (emit (MInst.MovFromPReg dst src)))) dst)) +(decl mov_to_preg (PReg Reg) SideEffectNoResult) +(rule (mov_to_preg dst src) + (SideEffectNoResult.Inst (MInst.MovToPReg dst src))) + (decl preg_sp () PReg) (extern constructor preg_sp preg_sp) @@ -2546,14 +3121,647 @@ (decl preg_link () PReg) (extern constructor preg_link preg_link) +(decl preg_pinned () PReg) +(extern constructor preg_pinned preg_pinned) + (decl aarch64_sp () Reg) (rule (aarch64_sp) - (mov_preg (preg_sp))) + (mov_from_preg (preg_sp))) (decl aarch64_fp () Reg) (rule (aarch64_fp) - (mov_preg (preg_fp))) + (mov_from_preg (preg_fp))) (decl aarch64_link () Reg) +(rule 1 (aarch64_link) + (if (preserve_frame_pointers)) + (if (sign_return_address_disabled)) + (let ((dst WritableReg (temp_writable_reg $I64)) + ;; Even though LR is not an allocatable register, whether it + ;; contains the return address for the current function is + ;; unknown at this point. For example, this operation may come + ;; immediately after a call, in which case LR would not have a + ;; valid value. That's why we must obtain the return address from + ;; the frame record that corresponds to the current subroutine on + ;; the stack; the presence of the record is guaranteed by the + ;; `preserve_frame_pointers` setting. + (addr AMode (AMode.FPOffset 8 $I64)) + (_ Unit (emit (MInst.ULoad64 dst addr (mem_flags_trusted))))) + dst)) + (rule (aarch64_link) - (mov_preg (preg_link))) + (if (preserve_frame_pointers)) + ;; Similarly to the rule above, we must load the return address from the + ;; the frame record. Furthermore, we can use LR as a scratch register + ;; because the function will set it to the return address immediately + ;; before returning. + (let ((addr AMode (AMode.FPOffset 8 $I64)) + (lr WritableReg (writable_link_reg)) + (_ Unit (emit (MInst.ULoad64 lr addr (mem_flags_trusted)))) + (_ Unit (emit (MInst.Xpaclri)))) + (mov_from_preg (preg_link)))) + +;; Helper for getting the maximum shift amount for a type. + +(decl max_shift (Type) u8) +(rule (max_shift $F64) 63) +(rule (max_shift $F32) 31) + +;; Helper for generating `fcopysign` instruction sequences. + +(decl fcopy_sign (Reg Reg Type) Reg) +(rule 1 (fcopy_sign x y (ty_scalar_float ty)) + (let ((dst WritableReg (temp_writable_reg $F64)) + (tmp Reg (fpu_rri (fpu_op_ri_ushr (ty_bits ty) (max_shift ty)) y)) + (_ Unit (emit (MInst.FpuRRIMod (fpu_op_ri_sli (ty_bits ty) (max_shift ty)) dst x tmp)))) + dst)) +(rule (fcopy_sign x y ty @ (multi_lane _ _)) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (tmp Reg (vec_shift_imm (VecShiftImmOp.Ushr) (max_shift (lane_type ty)) y (vector_size ty))) + (_ Unit (emit (MInst.VecShiftImmMod (VecShiftImmModOp.Sli) dst x tmp (vector_size ty) (max_shift (lane_type ty)))))) + dst)) + +;; Helpers for generating `MInst.FpuToInt` instructions. + +(decl fpu_to_int_nan_check (ScalarSize Reg) Reg) +(rule (fpu_to_int_nan_check size src) + (let ((r ValueRegs + (with_flags (fpu_cmp size src src) + (ConsumesFlags.ConsumesFlagsReturnsReg + (MInst.TrapIf (cond_br_cond (Cond.Vs)) + (trap_code_bad_conversion_to_integer)) + src)))) + (value_regs_get r 0))) + +;; Checks that the value is not less than the minimum bound, +;; accepting a boolean (whether the type is signed), input type, +;; output type, and registers containing the source and minimum bound. +(decl fpu_to_int_underflow_check (bool Type Type Reg Reg) Reg) +(rule (fpu_to_int_underflow_check $true $F32 (fits_in_16 out_ty) src min) + (let ((r ValueRegs + (with_flags (fpu_cmp (ScalarSize.Size32) src min) + (ConsumesFlags.ConsumesFlagsReturnsReg + (MInst.TrapIf (cond_br_cond (Cond.Le)) + (trap_code_integer_overflow)) + src)))) + (value_regs_get r 0))) +(rule (fpu_to_int_underflow_check $true $F64 (fits_in_32 out_ty) src min) + (let ((r ValueRegs + (with_flags (fpu_cmp (ScalarSize.Size64) src min) + (ConsumesFlags.ConsumesFlagsReturnsReg + (MInst.TrapIf (cond_br_cond (Cond.Le)) + (trap_code_integer_overflow)) + src)))) + (value_regs_get r 0))) +(rule -1 (fpu_to_int_underflow_check $true in_ty _out_ty src min) + (let ((r ValueRegs + (with_flags (fpu_cmp (scalar_size in_ty) src min) + (ConsumesFlags.ConsumesFlagsReturnsReg + (MInst.TrapIf (cond_br_cond (Cond.Lt)) + (trap_code_integer_overflow)) + src)))) + (value_regs_get r 0))) +(rule (fpu_to_int_underflow_check $false in_ty _out_ty src min) + (let ((r ValueRegs + (with_flags (fpu_cmp (scalar_size in_ty) src min) + (ConsumesFlags.ConsumesFlagsReturnsReg + (MInst.TrapIf (cond_br_cond (Cond.Le)) + (trap_code_integer_overflow)) + src)))) + (value_regs_get r 0))) + +(decl fpu_to_int_overflow_check (ScalarSize Reg Reg) Reg) +(rule (fpu_to_int_overflow_check size src max) + (let ((r ValueRegs + (with_flags (fpu_cmp size src max) + (ConsumesFlags.ConsumesFlagsReturnsReg + (MInst.TrapIf (cond_br_cond (Cond.Ge)) + (trap_code_integer_overflow)) + src)))) + (value_regs_get r 0))) + +;; Emits the appropriate instruction sequence to convert a +;; floating-point value to an integer, trapping if the value +;; is a NaN or does not fit in the target type. +;; Accepts the specific conversion op, the source register, +;; whether the input is signed, and finally the input and output +;; types. +(decl fpu_to_int_cvt (FpuToIntOp Reg bool Type Type) Reg) +(rule (fpu_to_int_cvt op src signed in_ty out_ty) + (let ((size ScalarSize (scalar_size in_ty)) + (in_bits u8 (ty_bits in_ty)) + (out_bits u8 (ty_bits out_ty)) + (src Reg (fpu_to_int_nan_check size src)) + (min Reg (min_fp_value signed in_bits out_bits)) + (src Reg (fpu_to_int_underflow_check signed in_ty out_ty src min)) + (max Reg (max_fp_value signed in_bits out_bits)) + (src Reg (fpu_to_int_overflow_check size src max))) + (fpu_to_int op src))) + +;; Emits the appropriate instruction sequence to convert a +;; floating-point value to an integer, saturating if the value +;; does not fit in the target type. +;; Accepts the specific conversion op, the source register, +;; whether the input is signed, and finally the output type. +(decl fpu_to_int_cvt_sat (FpuToIntOp Reg bool Type) Reg) +(rule 1 (fpu_to_int_cvt_sat op src _ $I64) + (fpu_to_int op src)) +(rule 1 (fpu_to_int_cvt_sat op src _ $I32) + (fpu_to_int op src)) +(rule (fpu_to_int_cvt_sat op src $false (fits_in_16 out_ty)) + (let ((result Reg (fpu_to_int op src)) + (max Reg (imm out_ty (ImmExtend.Zero) (ty_mask out_ty)))) + (with_flags_reg + (cmp (OperandSize.Size32) result max) + (csel (Cond.Hi) max result)))) +(rule (fpu_to_int_cvt_sat op src $true (fits_in_16 out_ty)) + (let ((result Reg (fpu_to_int op src)) + (max Reg (signed_max out_ty)) + (min Reg (signed_min out_ty)) + (result Reg (with_flags_reg + (cmp (operand_size out_ty) result max) + (csel (Cond.Gt) max result))) + (result Reg (with_flags_reg + (cmp (operand_size out_ty) result min) + (csel (Cond.Lt) min result)))) + result)) + +(decl signed_min (Type) Reg) +(rule (signed_min $I8) (imm $I8 (ImmExtend.Sign) 0x80)) +(rule (signed_min $I16) (imm $I16 (ImmExtend.Sign) 0x8000)) + +(decl signed_max (Type) Reg) +(rule (signed_max $I8) (imm $I8 (ImmExtend.Sign) 0x7F)) +(rule (signed_max $I16) (imm $I16 (ImmExtend.Sign) 0x7FFF)) + +(decl fpu_to_int (FpuToIntOp Reg) Reg) +(rule (fpu_to_int op src) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.FpuToInt op dst src)))) + dst)) + +;; Helper for generating `MInst.IntToFpu` instructions. + +(decl int_to_fpu (IntToFpuOp Reg) Reg) +(rule (int_to_fpu op src) + (let ((dst WritableReg (temp_writable_reg $I8X16)) + (_ Unit (emit (MInst.IntToFpu op dst src)))) + dst)) + +;;;; Helpers for Emitting Calls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(decl gen_call (SigRef ExternalName RelocDistance ValueSlice) InstOutput) +(extern constructor gen_call gen_call) + +(decl gen_call_indirect (SigRef Value ValueSlice) InstOutput) +(extern constructor gen_call_indirect gen_call_indirect) + +;; Helpers for pinned register manipulation. + +(decl write_pinned_reg (Reg) SideEffectNoResult) +(rule (write_pinned_reg val) + (mov_to_preg (preg_pinned) val)) + +;; Helpers for stackslot effective address generation. + +(decl compute_stack_addr (StackSlot Offset32) Reg) +(rule (compute_stack_addr stack_slot offset) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (abi_stackslot_addr dst stack_slot offset)))) + dst)) + +;; Helper for emitting instruction sequences to perform a vector comparison. + +(decl vec_cmp_vc (Reg Reg VectorSize) Reg) +(rule (vec_cmp_vc rn rm size) + (let ((dst Reg (vec_rrr (VecALUOp.Fcmeq) rn rn size)) + (tmp Reg (vec_rrr (VecALUOp.Fcmeq) rm rm size)) + (dst Reg (vec_rrr (VecALUOp.And) dst tmp size))) + dst)) + +(decl vec_cmp (Reg Reg Type Cond) Reg) + +;; Floating point Vs / Vc +(rule (vec_cmp rn rm ty (Cond.Vc)) + (if (ty_vector_float ty)) + (vec_cmp_vc rn rm (vector_size ty))) +(rule (vec_cmp rn rm ty (Cond.Vs)) + (if (ty_vector_float ty)) + (let ((tmp Reg (vec_cmp_vc rn rm (vector_size ty)))) + (vec_misc (VecMisc2.Not) tmp (vector_size ty)))) + +;; 'Less than' operations are implemented by swapping the order of +;; operands and using the 'greater than' instructions. +;; 'Not equal' is implemented with 'equal' and inverting the result. + +;; Floating-point +(rule (vec_cmp rn rm ty (Cond.Eq)) + (if (ty_vector_float ty)) + (vec_rrr (VecALUOp.Fcmeq) rn rm (vector_size ty))) +(rule (vec_cmp rn rm ty (Cond.Ne)) + (if (ty_vector_float ty)) + (let ((tmp Reg (vec_rrr (VecALUOp.Fcmeq) rn rm (vector_size ty)))) + (vec_misc (VecMisc2.Not) tmp (vector_size ty)))) +(rule (vec_cmp rn rm ty (Cond.Ge)) + (if (ty_vector_float ty)) + (vec_rrr (VecALUOp.Fcmge) rn rm (vector_size ty))) +(rule (vec_cmp rn rm ty (Cond.Gt)) + (if (ty_vector_float ty)) + (vec_rrr (VecALUOp.Fcmgt) rn rm (vector_size ty))) +;; Floating-point swapped-operands +(rule (vec_cmp rn rm ty (Cond.Mi)) + (if (ty_vector_float ty)) + (vec_rrr (VecALUOp.Fcmgt) rm rn (vector_size ty))) +(rule (vec_cmp rn rm ty (Cond.Ls)) + (if (ty_vector_float ty)) + (vec_rrr (VecALUOp.Fcmge) rm rn (vector_size ty))) + +;; Integer +(rule 1 (vec_cmp rn rm ty (Cond.Eq)) + (if (ty_vector_not_float ty)) + (vec_rrr (VecALUOp.Cmeq) rn rm (vector_size ty))) +(rule 1 (vec_cmp rn rm ty (Cond.Ne)) + (if (ty_vector_not_float ty)) + (let ((tmp Reg (vec_rrr (VecALUOp.Cmeq) rn rm (vector_size ty)))) + (vec_misc (VecMisc2.Not) tmp (vector_size ty)))) +(rule 1 (vec_cmp rn rm ty (Cond.Ge)) + (if (ty_vector_not_float ty)) + (vec_rrr (VecALUOp.Cmge) rn rm (vector_size ty))) +(rule 1 (vec_cmp rn rm ty (Cond.Gt)) + (if (ty_vector_not_float ty)) + (vec_rrr (VecALUOp.Cmgt) rn rm (vector_size ty))) +(rule (vec_cmp rn rm ty (Cond.Hs)) + (if (ty_vector_not_float ty)) + (vec_rrr (VecALUOp.Cmhs) rn rm (vector_size ty))) +(rule (vec_cmp rn rm ty (Cond.Hi)) + (if (ty_vector_not_float ty)) + (vec_rrr (VecALUOp.Cmhi) rn rm (vector_size ty))) +;; Integer swapped-operands +(rule (vec_cmp rn rm ty (Cond.Le)) + (if (ty_vector_not_float ty)) + (vec_rrr (VecALUOp.Cmge) rm rn (vector_size ty))) +(rule (vec_cmp rn rm ty (Cond.Lt)) + (if (ty_vector_not_float ty)) + (vec_rrr (VecALUOp.Cmgt) rm rn (vector_size ty))) +(rule 1 (vec_cmp rn rm ty (Cond.Ls)) + (if (ty_vector_not_float ty)) + (vec_rrr (VecALUOp.Cmhs) rm rn (vector_size ty))) +(rule (vec_cmp rn rm ty (Cond.Lo)) + (if (ty_vector_not_float ty)) + (vec_rrr (VecALUOp.Cmhi) rm rn (vector_size ty))) + +;; Helper for determining if any value in a vector is true. +;; This operation is implemented by using umaxp to create a scalar value, which +;; is then compared against zero. +;; +;; umaxp vn.4s, vm.4s, vm.4s +;; mov xm, vn.d[0] +;; cmp xm, #0 +(decl vanytrue (Reg Type) ProducesFlags) +(rule 1 (vanytrue src (ty_vec128 ty)) + (let ((src Reg (vec_rrr (VecALUOp.Umaxp) src src (VectorSize.Size32x4))) + (src Reg (mov_from_vec src 0 (ScalarSize.Size64)))) + (cmp_imm (OperandSize.Size64) src (u8_into_imm12 0)))) +(rule (vanytrue src ty) + (if (ty_vec64 ty)) + (let ((src Reg (mov_from_vec src 0 (ScalarSize.Size64)))) + (cmp_imm (OperandSize.Size64) src (u8_into_imm12 0)))) + +;;;; TLS Values ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Helper for emitting ElfTlsGetAddr. +(decl elf_tls_get_addr (ExternalName) Reg) +(rule (elf_tls_get_addr name) + (let ((dst WritableReg (temp_writable_reg $I64)) + (_ Unit (emit (MInst.ElfTlsGetAddr name dst)))) + dst)) + +;; A tuple of `ProducesFlags` and `IntCC`. +(type FlagsAndCC (enum (FlagsAndCC (flags ProducesFlags) + (cc IntCC)))) + +;; Helper constructor for `FlagsAndCC`. +(decl flags_and_cc (ProducesFlags IntCC) FlagsAndCC) +(rule (flags_and_cc flags cc) (FlagsAndCC.FlagsAndCC flags cc)) + +;; Materialize a `FlagsAndCC` into a boolean `ValueRegs`. +(decl flags_and_cc_to_bool (FlagsAndCC) ValueRegs) +(rule (flags_and_cc_to_bool (FlagsAndCC.FlagsAndCC flags cc)) + (with_flags flags (materialize_bool_result (cond_code cc)))) + +;; Get the `ProducesFlags` out of a `FlagsAndCC`. +(decl flags_and_cc_flags (FlagsAndCC) ProducesFlags) +(rule (flags_and_cc_flags (FlagsAndCC.FlagsAndCC flags _cc)) flags) + +;; Get the `IntCC` out of a `FlagsAndCC`. +(decl flags_and_cc_cc (FlagsAndCC) IntCC) +(rule (flags_and_cc_cc (FlagsAndCC.FlagsAndCC _flags cc)) cc) + +;; Helpers for lowering `icmp` sequences. +;; `lower_icmp` contains shared functionality for lowering `icmp` +;; sequences, which `lower_icmp_into_{reg,flags}` extend from. +(decl lower_icmp (IntCC Value Value Type) FlagsAndCC) +(decl lower_icmp_into_reg (IntCC Value Value Type Type) ValueRegs) +(decl lower_icmp_into_flags (IntCC Value Value Type) FlagsAndCC) +(decl lower_icmp_const (IntCC Value u64 Type) FlagsAndCC) +;; For most cases, `lower_icmp_into_flags` is the same as `lower_icmp`, +;; except for some I128 cases (see below). +(rule -1 (lower_icmp_into_flags cond x y ty) (lower_icmp cond x y ty)) + +;; Vectors. +;; `icmp` into flags for vectors is invalid. +(rule 1 (lower_icmp_into_reg cond x y in_ty @ (multi_lane _ _) _out_ty) + (let ((cond Cond (cond_code cond)) + (rn Reg (put_in_reg x)) + (rm Reg (put_in_reg y))) + (vec_cmp rn rm in_ty cond))) + +;; Determines the appropriate extend op given the value type and whether it is signed. +(decl lower_extend_op (Type bool) ExtendOp) +(rule (lower_extend_op $I8 $true) (ExtendOp.SXTB)) +(rule (lower_extend_op $I16 $true) (ExtendOp.SXTH)) +(rule (lower_extend_op $I8 $false) (ExtendOp.UXTB)) +(rule (lower_extend_op $I16 $false) (ExtendOp.UXTH)) + +;; Integers <= 64-bits. +(rule -2 (lower_icmp_into_reg cond rn rm in_ty out_ty) + (if (ty_int_ref_scalar_64 in_ty)) + (let ((cc Cond (cond_code cond))) + (flags_and_cc_to_bool (lower_icmp cond rn rm in_ty)))) + +(rule 1 (lower_icmp cond rn rm (fits_in_16 ty)) + (if (signed_cond_code cond)) + (let ((rn Reg (put_in_reg_sext32 rn))) + (flags_and_cc (cmp_extend (operand_size ty) rn rm (lower_extend_op ty $true)) cond))) +(rule -1 (lower_icmp cond rn (imm12_from_value rm) (fits_in_16 ty)) + (let ((rn Reg (put_in_reg_zext32 rn))) + (flags_and_cc (cmp_imm (operand_size ty) rn rm) cond))) +(rule -2 (lower_icmp cond rn rm (fits_in_16 ty)) + (let ((rn Reg (put_in_reg_zext32 rn))) + (flags_and_cc (cmp_extend (operand_size ty) rn rm (lower_extend_op ty $false)) cond))) +(rule -3 (lower_icmp cond rn (u64_from_iconst c) ty) + (if (ty_int_ref_scalar_64 ty)) + (lower_icmp_const cond rn c ty)) +(rule -4 (lower_icmp cond rn rm ty) + (if (ty_int_ref_scalar_64 ty)) + (flags_and_cc (cmp (operand_size ty) rn rm) cond)) + +;; We get better encodings when testing against an immediate that's even instead +;; of odd, so rewrite comparisons to use even immediates: +;; +;; A >= B + 1 +;; ==> A - 1 >= B +;; ==> A > B +(rule (lower_icmp_const (IntCC.UnsignedGreaterThanOrEqual) a b ty) + (if (ty_int_ref_scalar_64 ty)) + (if-let $true (u64_is_odd b)) + (if-let (imm12_from_u64 imm) (u64_sub b 1)) + (flags_and_cc (cmp_imm (operand_size ty) a imm) (IntCC.UnsignedGreaterThan))) +(rule (lower_icmp_const (IntCC.SignedGreaterThanOrEqual) a b ty) + (if (ty_int_ref_scalar_64 ty)) + (if-let $true (u64_is_odd b)) + (if-let (imm12_from_u64 imm) (u64_sub b 1)) + (flags_and_cc (cmp_imm (operand_size ty) a imm) (IntCC.SignedGreaterThan))) + +(rule -1 (lower_icmp_const cond rn (imm12_from_u64 c) ty) + (if (ty_int_ref_scalar_64 ty)) + (flags_and_cc (cmp_imm (operand_size ty) rn c) cond)) +(rule -2 (lower_icmp_const cond rn c ty) + (if (ty_int_ref_scalar_64 ty)) + (flags_and_cc (cmp (operand_size ty) rn (imm ty (ImmExtend.Zero) c)) cond)) + + +;; 128-bit integers. +(rule (lower_icmp_into_reg cond @ (IntCC.Equal) rn rm $I128 $I8) + (let ((cc Cond (cond_code cond))) + (flags_and_cc_to_bool + (lower_icmp cond rn rm $I128)))) +(rule (lower_icmp_into_reg cond @ (IntCC.NotEqual) rn rm $I128 $I8) + (let ((cc Cond (cond_code cond))) + (flags_and_cc_to_bool + (lower_icmp cond rn rm $I128)))) + +;; cmp lhs_lo, rhs_lo +;; ccmp lhs_hi, rhs_hi, #0, eq +(decl lower_icmp_i128_eq_ne (Value Value) ProducesFlags) +(rule (lower_icmp_i128_eq_ne lhs rhs) + (let ((lhs ValueRegs (put_in_regs lhs)) + (rhs ValueRegs (put_in_regs rhs)) + (lhs_lo Reg (value_regs_get lhs 0)) + (lhs_hi Reg (value_regs_get lhs 1)) + (rhs_lo Reg (value_regs_get rhs 0)) + (rhs_hi Reg (value_regs_get rhs 1)) + (cmp_inst ProducesFlags (cmp (OperandSize.Size64) lhs_lo rhs_lo))) + (ccmp (OperandSize.Size64) lhs_hi rhs_hi + (nzcv $false $false $false $false) (Cond.Eq) cmp_inst))) + +(rule (lower_icmp (IntCC.Equal) lhs rhs $I128) + (flags_and_cc (lower_icmp_i128_eq_ne lhs rhs) (IntCC.Equal))) +(rule (lower_icmp (IntCC.NotEqual) lhs rhs $I128) + (flags_and_cc (lower_icmp_i128_eq_ne lhs rhs) (IntCC.NotEqual))) + +;; cmp lhs_lo, rhs_lo +;; cset tmp1, unsigned_cond +;; cmp lhs_hi, rhs_hi +;; cset tmp2, cond +;; csel dst, tmp1, tmp2, eq +(rule -1 (lower_icmp_into_reg cond lhs rhs $I128 $I8) + (let ((unsigned_cond Cond (cond_code (intcc_unsigned cond))) + (cond Cond (cond_code cond)) + (lhs ValueRegs (put_in_regs lhs)) + (rhs ValueRegs (put_in_regs rhs)) + (lhs_lo Reg (value_regs_get lhs 0)) + (lhs_hi Reg (value_regs_get lhs 1)) + (rhs_lo Reg (value_regs_get rhs 0)) + (rhs_hi Reg (value_regs_get rhs 1)) + (tmp1 Reg (with_flags_reg (cmp (OperandSize.Size64) lhs_lo rhs_lo) + (materialize_bool_result unsigned_cond)))) + (with_flags (cmp (OperandSize.Size64) lhs_hi rhs_hi) + (lower_icmp_i128_consumer cond tmp1)))) + +(decl lower_icmp_i128_consumer (Cond Reg) ConsumesFlags) +(rule (lower_icmp_i128_consumer cond tmp1) + (let ((tmp2 WritableReg (temp_writable_reg $I64)) + (dst WritableReg (temp_writable_reg $I64))) + (ConsumesFlags.ConsumesFlagsTwiceReturnsValueRegs + (MInst.CSet tmp2 cond) + (MInst.CSel dst (Cond.Eq) tmp1 tmp2) + (value_reg dst)))) + +(decl lower_bmask (Type Type ValueRegs) ValueRegs) + + +;; For conversions that exactly fit a register, we can use csetm. +;; +;; cmp val, #0 +;; csetm res, ne +(rule 0 + (lower_bmask (fits_in_64 _) (ty_32_or_64 in_ty) val) + (with_flags_reg + (cmp_imm (operand_size in_ty) (value_regs_get val 0) (u8_into_imm12 0)) + (csetm (Cond.Ne)))) + +;; For conversions from a 128-bit value into a 64-bit or smaller one, we or the +;; two registers of the 128-bit value together, and then recurse with the +;; combined value as a 64-bit test. +;; +;; orr val, lo, hi +;; cmp val, #0 +;; csetm res, ne +(rule 1 + (lower_bmask (fits_in_64 ty) $I128 val) + (let ((lo Reg (value_regs_get val 0)) + (hi Reg (value_regs_get val 1)) + (combined Reg (orr $I64 lo hi))) + (lower_bmask ty $I64 (value_reg combined)))) + +;; For converting from any type into i128, duplicate the result of +;; converting to i64. +(rule 2 + (lower_bmask $I128 in_ty val) + (let ((res ValueRegs (lower_bmask $I64 in_ty val)) + (res Reg (value_regs_get res 0))) + (value_regs res res))) + +;; For conversions smaller than a register, we need to mask off the high bits, and then +;; we can recurse into the general case. +;; +;; and tmp, val, #ty_mask +;; cmp tmp, #0 +;; csetm res, ne +(rule 3 + (lower_bmask out_ty (fits_in_16 in_ty) val) + ; This if-let can't fail due to ty_mask always producing 8/16 consecutive 1s. + (if-let mask_bits (imm_logic_from_u64 $I32 (ty_mask in_ty))) + (let ((masked Reg (and_imm $I32 (value_regs_get val 0) mask_bits))) + (lower_bmask out_ty $I32 masked))) + +;; Exceptional `lower_icmp_into_flags` rules. +;; We need to guarantee that the flags for `cond` are correct, so we +;; compare `dst` with 1. +(rule (lower_icmp_into_flags cond @ (IntCC.SignedGreaterThanOrEqual) lhs rhs $I128) + (let ((dst ValueRegs (lower_icmp_into_reg cond lhs rhs $I128 $I8)) + (dst Reg (value_regs_get dst 0)) + (tmp Reg (imm $I64 (ImmExtend.Sign) 1))) ;; mov tmp, #1 + (flags_and_cc (cmp (OperandSize.Size64) dst tmp) cond))) +(rule (lower_icmp_into_flags cond @ (IntCC.UnsignedGreaterThanOrEqual) lhs rhs $I128) + (let ((dst ValueRegs (lower_icmp_into_reg cond lhs rhs $I128 $I8)) + (dst Reg (value_regs_get dst 0)) + (tmp Reg (imm $I64 (ImmExtend.Zero) 1))) + (flags_and_cc (cmp (OperandSize.Size64) dst tmp) cond))) +(rule (lower_icmp_into_flags cond @ (IntCC.SignedLessThanOrEqual) lhs rhs $I128) + (let ((dst ValueRegs (lower_icmp_into_reg cond lhs rhs $I128 $I8)) + (dst Reg (value_regs_get dst 0)) + (tmp Reg (imm $I64 (ImmExtend.Sign) 1))) + (flags_and_cc (cmp (OperandSize.Size64) tmp dst) cond))) +(rule (lower_icmp_into_flags cond @ (IntCC.UnsignedLessThanOrEqual) lhs rhs $I128) + (let ((dst ValueRegs (lower_icmp_into_reg cond lhs rhs $I128 $I8)) + (dst Reg (value_regs_get dst 0)) + (tmp Reg (imm $I64 (ImmExtend.Zero) 1))) + (flags_and_cc (cmp (OperandSize.Size64) tmp dst) cond))) +;; For strict comparisons, we compare with 0. +(rule (lower_icmp_into_flags cond @ (IntCC.SignedGreaterThan) lhs rhs $I128) + (let ((dst ValueRegs (lower_icmp_into_reg cond lhs rhs $I128 $I8)) + (dst Reg (value_regs_get dst 0))) + (flags_and_cc (cmp (OperandSize.Size64) dst (zero_reg)) cond))) +(rule (lower_icmp_into_flags cond @ (IntCC.UnsignedGreaterThan) lhs rhs $I128) + (let ((dst ValueRegs (lower_icmp_into_reg cond lhs rhs $I128 $I8)) + (dst Reg (value_regs_get dst 0))) + (flags_and_cc (cmp (OperandSize.Size64) dst (zero_reg)) cond))) +(rule (lower_icmp_into_flags cond @ (IntCC.SignedLessThan) lhs rhs $I128) + (let ((dst ValueRegs (lower_icmp_into_reg cond lhs rhs $I128 $I8)) + (dst Reg (value_regs_get dst 0))) + (flags_and_cc (cmp (OperandSize.Size64) (zero_reg) dst) cond))) +(rule (lower_icmp_into_flags cond @ (IntCC.UnsignedLessThan) lhs rhs $I128) + (let ((dst ValueRegs (lower_icmp_into_reg cond lhs rhs $I128 $I8)) + (dst Reg (value_regs_get dst 0))) + (flags_and_cc (cmp (OperandSize.Size64) (zero_reg) dst) cond))) + +;; Helpers for generating select instruction sequences. +(decl lower_select (ProducesFlags Cond Type Value Value) ValueRegs) +(rule 2 (lower_select flags cond (ty_scalar_float ty) rn rm) + (with_flags flags (fpu_csel ty cond rn rm))) +(rule 3 (lower_select flags cond (ty_vec128 ty) rn rm) + (with_flags flags (vec_csel cond rn rm))) +(rule (lower_select flags cond ty rn rm) + (if (ty_vec64 ty)) + (with_flags flags (fpu_csel $F64 cond rn rm))) +(rule 4 (lower_select flags cond $I128 rn rm) + (let ((dst_lo WritableReg (temp_writable_reg $I64)) + (dst_hi WritableReg (temp_writable_reg $I64)) + (rn ValueRegs (put_in_regs rn)) + (rm ValueRegs (put_in_regs rm)) + (rn_lo Reg (value_regs_get rn 0)) + (rn_hi Reg (value_regs_get rn 1)) + (rm_lo Reg (value_regs_get rm 0)) + (rm_hi Reg (value_regs_get rm 1))) + (with_flags flags + (ConsumesFlags.ConsumesFlagsTwiceReturnsValueRegs + (MInst.CSel dst_lo cond rn_lo rm_lo) + (MInst.CSel dst_hi cond rn_hi rm_hi) + (value_regs dst_lo dst_hi))))) +(rule 1 (lower_select flags cond ty rn rm) + (if (ty_int_ref_scalar_64 ty)) + (with_flags flags (csel cond rn rm))) + +;; Helper for emitting `MInst.Jump` instructions. +(decl aarch64_jump (BranchTarget) SideEffectNoResult) +(rule (aarch64_jump target) + (SideEffectNoResult.Inst (MInst.Jump target))) + +;; Helper for emitting `MInst.JTSequence` instructions. +;; Emit the compound instruction that does: +;; +;; b.hs default +;; csel rB, xzr, rIndex, hs +;; csdb +;; adr rA, jt +;; ldrsw rB, [rA, rB, uxtw #2] +;; add rA, rA, rB +;; br rA +;; [jt entries] +;; +;; This must be *one* instruction in the vcode because +;; we cannot allow regalloc to insert any spills/fills +;; in the middle of the sequence; otherwise, the ADR's +;; PC-rel offset to the jumptable would be incorrect. +;; (The alternative is to introduce a relocation pass +;; for inlined jumptables, which is much worse, IMHO.) +(decl jt_sequence (Reg BoxJTSequenceInfo) ConsumesFlags) +(rule (jt_sequence ridx info) + (let ((rtmp1 WritableReg (temp_writable_reg $I64)) + (rtmp2 WritableReg (temp_writable_reg $I64))) + (ConsumesFlags.ConsumesFlagsSideEffect + (MInst.JTSequence info ridx rtmp1 rtmp2)))) + +;; Helper for emitting `MInst.CondBr` instructions. +(decl cond_br (BranchTarget BranchTarget CondBrKind) ConsumesFlags) +(rule (cond_br taken not_taken kind) + (ConsumesFlags.ConsumesFlagsSideEffect + (MInst.CondBr taken not_taken kind))) + +;; Helper for emitting `MInst.MovToNZCV` instructions. +(decl mov_to_nzcv (Reg) ProducesFlags) +(rule (mov_to_nzcv rn) + (ProducesFlags.ProducesFlagsSideEffect + (MInst.MovToNZCV rn))) + +;; Helper for emitting `MInst.EmitIsland` instructions. +(decl emit_island (CodeOffset) SideEffectNoResult) +(rule (emit_island needed_space) + (SideEffectNoResult.Inst + (MInst.EmitIsland needed_space))) + +;; Helper for emitting `br_table` sequences. +(decl br_table_impl (u64 Reg VecMachLabel) Unit) +(rule (br_table_impl (imm12_from_u64 jt_size) ridx targets) + (let ((jt_info BoxJTSequenceInfo (targets_jt_info targets))) + (emit_side_effect (with_flags_side_effect + (cmp_imm (OperandSize.Size32) ridx jt_size) + (jt_sequence ridx jt_info))))) +(rule -1 (br_table_impl jt_size ridx targets) + (let ((jt_size Reg (imm $I64 (ImmExtend.Zero) jt_size)) + (jt_info BoxJTSequenceInfo (targets_jt_info targets))) + (emit_side_effect (with_flags_side_effect + (cmp (OperandSize.Size32) ridx jt_size) + (jt_sequence ridx jt_info))))) diff --git a/cranelift/codegen/src/isa/aarch64/inst/args.rs b/cranelift/codegen/src/isa/aarch64/inst/args.rs index 7ce8a048d183..69eb7e525185 100644 --- a/cranelift/codegen/src/isa/aarch64/inst/args.rs +++ b/cranelift/codegen/src/isa/aarch64/inst/args.rs @@ -3,7 +3,7 @@ use crate::ir::types::*; use crate::ir::Type; use crate::isa::aarch64::inst::*; -use crate::machinst::{ty_bits, MachLabel, PrettyPrint, Reg, Writable}; +use crate::machinst::{ty_bits, MachLabel, PrettyPrint, Reg}; use core::convert::Into; use std::string::String; @@ -14,12 +14,13 @@ use std::string::String; #[derive(Clone, Copy, Debug)] #[repr(u8)] pub enum ShiftOp { + /// Logical shift left. LSL = 0b00, - #[allow(dead_code)] + /// Logical shift right. LSR = 0b01, - #[allow(dead_code)] + /// Arithmentic shift right. ASR = 0b10, - #[allow(dead_code)] + /// Rotate right. ROR = 0b11, } @@ -61,11 +62,14 @@ impl ShiftOpShiftImm { /// A shift operator with an amount, guaranteed to be within range. #[derive(Copy, Clone, Debug)] pub struct ShiftOpAndAmt { + /// The shift operator. op: ShiftOp, + /// The shift operator amount. shift: ShiftOpShiftImm, } impl ShiftOpAndAmt { + /// Create a new shift operator with an amount. pub fn new(op: ShiftOp, shift: ShiftOpShiftImm) -> ShiftOpAndAmt { ShiftOpAndAmt { op, shift } } @@ -85,14 +89,21 @@ impl ShiftOpAndAmt { #[derive(Clone, Copy, Debug)] #[repr(u8)] pub enum ExtendOp { + /// Unsigned extend byte. UXTB = 0b000, + /// Unsigned extend halfword. UXTH = 0b001, + /// Unsigned extend word. UXTW = 0b010, + /// Unsigned extend doubleword. UXTX = 0b011, + /// Signed extend byte. SXTB = 0b100, + /// Signed extend halfword. SXTH = 0b101, + /// Signed extend word. SXTW = 0b110, - #[allow(dead_code)] + /// Signed extend doubleword. SXTX = 0b111, } @@ -115,118 +126,75 @@ pub enum MemLabel { PCRel(i32), } -/// An addressing mode specified for a load/store operation. -#[derive(Clone, Debug)] -pub enum AMode { - // - // Real ARM64 addressing modes: - // - /// "post-indexed" mode as per AArch64 docs: postincrement reg after address computation. - PostIndexed(Writable, SImm9), - /// "pre-indexed" mode as per AArch64 docs: preincrement reg before address computation. - PreIndexed(Writable, SImm9), - - // N.B.: RegReg, RegScaled, and RegScaledExtended all correspond to - // what the ISA calls the "register offset" addressing mode. We split out - // several options here for more ergonomic codegen. - /// Register plus register offset. - RegReg(Reg, Reg), - - #[allow(dead_code)] - /// Register plus register offset, scaled by type's size. - RegScaled(Reg, Reg, Type), - - /// Register plus register offset, scaled by type's size, with index sign- or zero-extended - /// first. - RegScaledExtended(Reg, Reg, Type, ExtendOp), - - /// Register plus register offset, with index sign- or zero-extended first. - RegExtended(Reg, Reg, ExtendOp), - - /// Unscaled signed 9-bit immediate offset from reg. - Unscaled(Reg, SImm9), - - /// Scaled (by size of a type) unsigned 12-bit immediate offset from reg. - UnsignedOffset(Reg, UImm12Scaled), - - // - // virtual addressing modes that are lowered at emission time: - // - /// Reference to a "label": e.g., a symbol. - Label(MemLabel), - - /// Arbitrary offset from a register. Converted to generation of large - /// offsets with multiple instructions as necessary during code emission. - RegOffset(Reg, i64, Type), - - /// Offset from the stack pointer. - SPOffset(i64, Type), - - /// Offset from the frame pointer. - FPOffset(i64, Type), - - /// Offset from the "nominal stack pointer", which is where the real SP is - /// just after stack and spill slots are allocated in the function prologue. - /// At emission time, this is converted to `SPOffset` with a fixup added to - /// the offset constant. The fixup is a running value that is tracked as - /// emission iterates through instructions in linear order, and can be - /// adjusted up and down with [Inst::VirtualSPOffsetAdj]. - /// - /// The standard ABI is in charge of handling this (by emitting the - /// adjustment meta-instructions). It maintains the invariant that "nominal - /// SP" is where the actual SP is after the function prologue and before - /// clobber pushes. See the diagram in the documentation for - /// [crate::isa::aarch64::abi](the ABI module) for more details. - NominalSPOffset(i64, Type), -} - impl AMode { /// Memory reference using an address in a register. pub fn reg(reg: Reg) -> AMode { // Use UnsignedOffset rather than Unscaled to use ldr rather than ldur. // This also does not use PostIndexed / PreIndexed as they update the register. - AMode::UnsignedOffset(reg, UImm12Scaled::zero(I64)) + AMode::UnsignedOffset { + rn: reg, + uimm12: UImm12Scaled::zero(I64), + } } /// Memory reference using `reg1 + sizeof(ty) * reg2` as an address, with `reg2` sign- or /// zero-extended as per `op`. pub fn reg_plus_reg_scaled_extended(reg1: Reg, reg2: Reg, ty: Type, op: ExtendOp) -> AMode { - AMode::RegScaledExtended(reg1, reg2, ty, op) - } - - /// Does the address resolve to just a register value, with no offset or - /// other computation? - pub fn is_reg(&self) -> Option { - match self { - &AMode::UnsignedOffset(r, uimm12) if uimm12.value() == 0 => Some(r), - &AMode::Unscaled(r, imm9) if imm9.value() == 0 => Some(r), - &AMode::RegOffset(r, off, _) if off == 0 => Some(r), - &AMode::FPOffset(off, _) if off == 0 => Some(fp_reg()), - &AMode::SPOffset(off, _) if off == 0 => Some(stack_reg()), - _ => None, + AMode::RegScaledExtended { + rn: reg1, + rm: reg2, + ty, + extendop: op, } } - pub fn with_allocs(&self, allocs: &mut AllocationConsumer<'_>) -> Self { + pub(crate) fn with_allocs(&self, allocs: &mut AllocationConsumer<'_>) -> Self { // This should match `memarg_operands()`. match self { - &AMode::Unscaled(reg, imm9) => AMode::Unscaled(allocs.next(reg), imm9), - &AMode::UnsignedOffset(r, uimm12) => AMode::UnsignedOffset(allocs.next(r), uimm12), - &AMode::RegReg(r1, r2) => AMode::RegReg(allocs.next(r1), allocs.next(r2)), - &AMode::RegScaled(r1, r2, ty) => AMode::RegScaled(allocs.next(r1), allocs.next(r2), ty), - &AMode::RegScaledExtended(r1, r2, ty, ext) => { - AMode::RegScaledExtended(allocs.next(r1), allocs.next(r2), ty, ext) - } - &AMode::RegExtended(r1, r2, ext) => { - AMode::RegExtended(allocs.next(r1), allocs.next(r2), ext) - } - &AMode::PreIndexed(reg, simm9) => AMode::PreIndexed(allocs.next_writable(reg), simm9), - &AMode::PostIndexed(reg, simm9) => AMode::PostIndexed(allocs.next_writable(reg), simm9), - &AMode::RegOffset(r, off, ty) => AMode::RegOffset(allocs.next(r), off, ty), - &AMode::FPOffset(..) - | &AMode::SPOffset(..) - | &AMode::NominalSPOffset(..) - | AMode::Label(..) => self.clone(), + &AMode::Unscaled { rn, simm9 } => AMode::Unscaled { + rn: allocs.next(rn), + simm9, + }, + &AMode::UnsignedOffset { rn, uimm12 } => AMode::UnsignedOffset { + rn: allocs.next(rn), + uimm12, + }, + &AMode::RegReg { rn, rm } => AMode::RegReg { + rn: allocs.next(rn), + rm: allocs.next(rm), + }, + &AMode::RegScaled { rn, rm, ty } => AMode::RegScaled { + rn: allocs.next(rn), + rm: allocs.next(rm), + ty, + }, + &AMode::RegScaledExtended { + rn, + rm, + ty, + extendop, + } => AMode::RegScaledExtended { + rn: allocs.next(rn), + rm: allocs.next(rm), + ty, + extendop, + }, + &AMode::RegExtended { rn, rm, extendop } => AMode::RegExtended { + rn: allocs.next(rn), + rm: allocs.next(rm), + extendop, + }, + &AMode::RegOffset { rn, off, ty } => AMode::RegOffset { + rn: allocs.next(rn), + off, + ty, + }, + &AMode::SPPreIndexed { .. } + | &AMode::SPPostIndexed { .. } + | &AMode::FPOffset { .. } + | &AMode::SPOffset { .. } + | &AMode::NominalSPOffset { .. } + | AMode::Label { .. } => self.clone(), } } } @@ -234,24 +202,22 @@ impl AMode { /// A memory argument to a load/store-pair. #[derive(Clone, Debug)] pub enum PairAMode { + /// Signed, scaled 7-bit offset from a register. SignedOffset(Reg, SImm7Scaled), - PreIndexed(Writable, SImm7Scaled), - PostIndexed(Writable, SImm7Scaled), + /// Pre-increment register before address computation. + SPPreIndexed(SImm7Scaled), + /// Post-increment register after address computation. + SPPostIndexed(SImm7Scaled), } impl PairAMode { - pub fn with_allocs(&self, allocs: &mut AllocationConsumer<'_>) -> Self { + pub(crate) fn with_allocs(&self, allocs: &mut AllocationConsumer<'_>) -> Self { // Should match `pairmemarg_operands()`. match self { &PairAMode::SignedOffset(reg, simm7scaled) => { PairAMode::SignedOffset(allocs.next(reg), simm7scaled) } - &PairAMode::PreIndexed(reg, simm7scaled) => { - PairAMode::PreIndexed(allocs.next_writable(reg), simm7scaled) - } - &PairAMode::PostIndexed(reg, simm7scaled) => { - PairAMode::PostIndexed(allocs.next_writable(reg), simm7scaled) - } + &PairAMode::SPPreIndexed(..) | &PairAMode::SPPostIndexed(..) => self.clone(), } } } @@ -264,21 +230,37 @@ impl PairAMode { #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(u8)] pub enum Cond { + /// Equal. Eq = 0, + /// Not equal. Ne = 1, + /// Unsigned greater than or equal to. Hs = 2, + /// Unsigned less than. Lo = 3, + /// Minus, negative. Mi = 4, + /// Positive or zero. Pl = 5, + /// Signed overflow. Vs = 6, + /// No signed overflow. Vc = 7, + /// Unsigned greater than. Hi = 8, + /// Unsigned less than or equal to. Ls = 9, + /// Signed greater or equal to. Ge = 10, + /// Signed less than. Lt = 11, + /// Signed greater than. Gt = 12, + /// Signed less than or equal. Le = 13, + /// Always executed. Al = 14, + /// Always executed. Nv = 15, } @@ -419,8 +401,8 @@ fn shift_for_type(ty: Type) -> usize { impl PrettyPrint for AMode { fn pretty_print(&self, _: u8, allocs: &mut AllocationConsumer<'_>) -> String { match self { - &AMode::Unscaled(reg, simm9) => { - let reg = pretty_print_reg(reg, allocs); + &AMode::Unscaled { rn, simm9 } => { + let reg = pretty_print_reg(rn, allocs); if simm9.value != 0 { let simm9 = simm9.pretty_print(8, allocs); format!("[{}, {}]", reg, simm9) @@ -428,8 +410,8 @@ impl PrettyPrint for AMode { format!("[{}]", reg) } } - &AMode::UnsignedOffset(reg, uimm12) => { - let reg = pretty_print_reg(reg, allocs); + &AMode::UnsignedOffset { rn, uimm12 } => { + let reg = pretty_print_reg(rn, allocs); if uimm12.value != 0 { let uimm12 = uimm12.pretty_print(8, allocs); format!("[{}, {}]", reg, uimm12) @@ -437,55 +419,58 @@ impl PrettyPrint for AMode { format!("[{}]", reg) } } - &AMode::RegReg(r1, r2) => { - let r1 = pretty_print_reg(r1, allocs); - let r2 = pretty_print_reg(r2, allocs); + &AMode::RegReg { rn, rm } => { + let r1 = pretty_print_reg(rn, allocs); + let r2 = pretty_print_reg(rm, allocs); format!("[{}, {}]", r1, r2) } - &AMode::RegScaled(r1, r2, ty) => { - let r1 = pretty_print_reg(r1, allocs); - let r2 = pretty_print_reg(r2, allocs); + &AMode::RegScaled { rn, rm, ty } => { + let r1 = pretty_print_reg(rn, allocs); + let r2 = pretty_print_reg(rm, allocs); let shift = shift_for_type(ty); format!("[{}, {}, LSL #{}]", r1, r2, shift) } - &AMode::RegScaledExtended(r1, r2, ty, op) => { + &AMode::RegScaledExtended { + rn, + rm, + ty, + extendop, + } => { let shift = shift_for_type(ty); - let size = match op { + let size = match extendop { ExtendOp::SXTW | ExtendOp::UXTW => OperandSize::Size32, _ => OperandSize::Size64, }; - let r1 = pretty_print_reg(r1, allocs); - let r2 = pretty_print_ireg(r2, size, allocs); - let op = op.pretty_print(0, allocs); + let r1 = pretty_print_reg(rn, allocs); + let r2 = pretty_print_ireg(rm, size, allocs); + let op = extendop.pretty_print(0, allocs); format!("[{}, {}, {} #{}]", r1, r2, op, shift) } - &AMode::RegExtended(r1, r2, op) => { - let size = match op { + &AMode::RegExtended { rn, rm, extendop } => { + let size = match extendop { ExtendOp::SXTW | ExtendOp::UXTW => OperandSize::Size32, _ => OperandSize::Size64, }; - let r1 = pretty_print_reg(r1, allocs); - let r2 = pretty_print_ireg(r2, size, allocs); - let op = op.pretty_print(0, allocs); + let r1 = pretty_print_reg(rn, allocs); + let r2 = pretty_print_ireg(rm, size, allocs); + let op = extendop.pretty_print(0, allocs); format!("[{}, {}, {}]", r1, r2, op) } - &AMode::Label(ref label) => label.pretty_print(0, allocs), - &AMode::PreIndexed(r, simm9) => { - let r = pretty_print_reg(r.to_reg(), allocs); + &AMode::Label { ref label } => label.pretty_print(0, allocs), + &AMode::SPPreIndexed { simm9 } => { let simm9 = simm9.pretty_print(8, allocs); - format!("[{}, {}]!", r, simm9) + format!("[sp, {}]!", simm9) } - &AMode::PostIndexed(r, simm9) => { - let r = pretty_print_reg(r.to_reg(), allocs); + &AMode::SPPostIndexed { simm9 } => { let simm9 = simm9.pretty_print(8, allocs); - format!("[{}], {}", r, simm9) + format!("[sp], {}", simm9) } // Eliminated by `mem_finalize()`. - &AMode::SPOffset(..) - | &AMode::FPOffset(..) - | &AMode::NominalSPOffset(..) - | &AMode::RegOffset(..) => { - panic!("Unexpected pseudo mem-arg mode (stack-offset or generic reg-offset)!") + &AMode::SPOffset { .. } + | &AMode::FPOffset { .. } + | &AMode::NominalSPOffset { .. } + | &AMode::RegOffset { .. } => { + panic!("Unexpected pseudo mem-arg mode: {:?}", self) } } } @@ -503,15 +488,13 @@ impl PrettyPrint for PairAMode { format!("[{}]", reg) } } - &PairAMode::PreIndexed(reg, simm7) => { - let reg = pretty_print_reg(reg.to_reg(), allocs); + &PairAMode::SPPreIndexed(simm7) => { let simm7 = simm7.pretty_print(8, allocs); - format!("[{}, {}]!", reg, simm7) + format!("[sp, {}]!", simm7) } - &PairAMode::PostIndexed(reg, simm7) => { - let reg = pretty_print_reg(reg.to_reg(), allocs); + &PairAMode::SPPostIndexed(simm7) => { let simm7 = simm7.pretty_print(8, allocs); - format!("[{}], {}", reg, simm7) + format!("[sp], {}", simm7) } } } @@ -538,7 +521,9 @@ impl PrettyPrint for BranchTarget { /// 64-bit variants of many instructions (and integer registers). #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum OperandSize { + /// 32-bit. Size32, + /// 64-bit. Size64, } @@ -564,6 +549,7 @@ impl OperandSize { } } + /// Return the operand size in bits. pub fn bits(&self) -> u8 { match self { OperandSize::Size32 => 32, @@ -586,6 +572,9 @@ impl OperandSize { } } + /// Register interpretation bit. + /// When 0, the register is interpreted as the 32-bit version. + /// When 1, the register is interpreted as the 64-bit version. pub fn sf_bit(&self) -> u32 { match self { OperandSize::Size32 => 0, @@ -597,26 +586,19 @@ impl OperandSize { /// Type used to communicate the size of a scalar SIMD & FP operand. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum ScalarSize { + /// 8-bit. Size8, + /// 16-bit. Size16, + /// 32-bit. Size32, + /// 64-bit. Size64, + /// 128-bit. Size128, } impl ScalarSize { - /// Convert from a needed width to the smallest size that fits. - pub fn from_bits>(bits: I) -> ScalarSize { - match bits.into().next_power_of_two() { - 8 => ScalarSize::Size8, - 16 => ScalarSize::Size16, - 32 => ScalarSize::Size32, - 64 => ScalarSize::Size64, - 128 => ScalarSize::Size128, - w => panic!("Unexpected type width: {}", w), - } - } - /// Convert to an integer operand size. pub fn operand_size(&self) -> OperandSize { match self { @@ -626,13 +608,6 @@ impl ScalarSize { } } - /// Convert from a type into the smallest size that fits. - pub fn from_ty(ty: Type) -> ScalarSize { - debug_assert!(!ty.is_vector()); - - Self::from_bits(ty_bits(ty)) - } - /// Return the encoding bits that are used by some scalar FP instructions /// for a particular operand size. pub fn ftype(&self) -> u32 { @@ -644,6 +619,7 @@ impl ScalarSize { } } + /// Return the widened version of the scalar size. pub fn widen(&self) -> ScalarSize { match self { ScalarSize::Size8 => ScalarSize::Size16, @@ -653,17 +629,35 @@ impl ScalarSize { ScalarSize::Size128 => panic!("can't widen 128-bits"), } } + + /// Return the narrowed version of the scalar size. + pub fn narrow(&self) -> ScalarSize { + match self { + ScalarSize::Size8 => panic!("can't narrow 8-bits"), + ScalarSize::Size16 => ScalarSize::Size8, + ScalarSize::Size32 => ScalarSize::Size16, + ScalarSize::Size64 => ScalarSize::Size32, + ScalarSize::Size128 => ScalarSize::Size64, + } + } } /// Type used to communicate the size of a vector operand. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum VectorSize { + /// 8-bit, 8 lanes. Size8x8, + /// 8 bit, 16 lanes. Size8x16, + /// 16-bit, 4 lanes. Size16x4, + /// 16-bit, 8 lanes. Size16x8, + /// 32-bit, 2 lanes. Size32x2, + /// 32-bit, 4 lanes. Size32x4, + /// 64-bit, 2 lanes. Size64x2, } @@ -682,32 +676,6 @@ impl VectorSize { } } - /// Convert from a type into a vector operand size. - pub fn from_ty(ty: Type) -> VectorSize { - debug_assert!(ty.is_vector()); - - match ty { - B8X8 => VectorSize::Size8x8, - B8X16 => VectorSize::Size8x16, - B16X4 => VectorSize::Size16x4, - B16X8 => VectorSize::Size16x8, - B32X2 => VectorSize::Size32x2, - B32X4 => VectorSize::Size32x4, - B64X2 => VectorSize::Size64x2, - F32X2 => VectorSize::Size32x2, - F32X4 => VectorSize::Size32x4, - F64X2 => VectorSize::Size64x2, - I8X8 => VectorSize::Size8x8, - I8X16 => VectorSize::Size8x16, - I16X4 => VectorSize::Size16x4, - I16X8 => VectorSize::Size16x8, - I32X2 => VectorSize::Size32x2, - I32X4 => VectorSize::Size32x4, - I64X2 => VectorSize::Size64x2, - _ => unimplemented!("Unsupported type: {}", ty), - } - } - /// Get the integer operand size that corresponds to a lane of a vector with a certain size. pub fn operand_size(&self) -> OperandSize { match self { @@ -726,6 +694,7 @@ impl VectorSize { } } + /// Returns true if the VectorSize is 128-bits. pub fn is_128bits(&self) -> bool { match self { VectorSize::Size8x8 => false, @@ -752,19 +721,14 @@ impl VectorSize { (q, size) } -} -pub(crate) fn dynamic_to_fixed(ty: Type) -> Type { - match ty { - I8X8XN => I8X8, - I8X16XN => I8X16, - I16X4XN => I16X4, - I16X8XN => I16X8, - I32X2XN => I32X2, - I32X4XN => I32X4, - I64X2XN => I64X2, - F32X4XN => F32X4, - F64X2XN => F64X2, - _ => unreachable!("unhandled type: {}", ty), + /// Return the encoding bit that is used by some floating-point SIMD + /// instructions for a particular operand size. + pub fn enc_float_size(&self) -> u32 { + match self.lane_size() { + ScalarSize::Size32 => 0b0, + ScalarSize::Size64 => 0b1, + size => panic!("Unsupported floating-point size for vector op: {:?}", size), + } } } diff --git a/cranelift/codegen/src/isa/aarch64/inst/emit.rs b/cranelift/codegen/src/isa/aarch64/inst/emit.rs index ab210acda8fe..90a5dbd93697 100644 --- a/cranelift/codegen/src/isa/aarch64/inst/emit.rs +++ b/cranelift/codegen/src/isa/aarch64/inst/emit.rs @@ -3,7 +3,7 @@ use regalloc2::Allocation; use crate::binemit::{CodeOffset, Reloc, StackMap}; -use crate::ir::types::*; +use crate::ir::{types::*, RelSourceLoc}; use crate::ir::{LibCall, MemFlags, TrapCode}; use crate::isa::aarch64::inst::*; use crate::machinst::{ty_bits, Reg, RegClass, Writable}; @@ -28,18 +28,18 @@ pub fn mem_finalize( state: &EmitState, ) -> (SmallVec<[Inst; 4]>, AMode) { match mem { - &AMode::RegOffset(_, off, ty) - | &AMode::SPOffset(off, ty) - | &AMode::FPOffset(off, ty) - | &AMode::NominalSPOffset(off, ty) => { + &AMode::RegOffset { off, ty, .. } + | &AMode::SPOffset { off, ty } + | &AMode::FPOffset { off, ty } + | &AMode::NominalSPOffset { off, ty } => { let basereg = match mem { - &AMode::RegOffset(reg, _, _) => reg, - &AMode::SPOffset(..) | &AMode::NominalSPOffset(..) => stack_reg(), - &AMode::FPOffset(..) => fp_reg(), + &AMode::RegOffset { rn, .. } => rn, + &AMode::SPOffset { .. } | &AMode::NominalSPOffset { .. } => stack_reg(), + &AMode::FPOffset { .. } => fp_reg(), _ => unreachable!(), }; let adj = match mem { - &AMode::NominalSPOffset(..) => { + &AMode::NominalSPOffset { .. } => { trace!( "mem_finalize: nominal SP offset {} + adj {} -> {}", off, @@ -53,34 +53,35 @@ pub fn mem_finalize( let off = off + adj; if let Some(simm9) = SImm9::maybe_from_i64(off) { - let mem = AMode::Unscaled(basereg, simm9); + let mem = AMode::Unscaled { rn: basereg, simm9 }; (smallvec![], mem) - } else if let Some(uimm12s) = UImm12Scaled::maybe_from_i64(off, ty) { - let mem = AMode::UnsignedOffset(basereg, uimm12s); + } else if let Some(uimm12) = UImm12Scaled::maybe_from_i64(off, ty) { + let mem = AMode::UnsignedOffset { + rn: basereg, + uimm12, + }; (smallvec![], mem) } else { let tmp = writable_spilltmp_reg(); - let mut const_insts = Inst::load_constant(tmp, off as u64); - // N.B.: we must use AluRRRExtend because AluRRR uses the "shifted register" form - // (AluRRRShift) instead, which interprets register 31 as the zero reg, not SP. SP - // is a valid base (for SPOffset) which we must handle here. - // Also, SP needs to be the first arg, not second. - let add_inst = Inst::AluRRRExtend { - alu_op: ALUOp::Add, - size: OperandSize::Size64, - rd: tmp, - rn: basereg, - rm: tmp.to_reg(), - extendop: ExtendOp::UXTX, - }; - const_insts.push(add_inst); - (const_insts, AMode::reg(tmp.to_reg())) + ( + Inst::load_constant(tmp, off as u64, &mut |_| tmp), + AMode::RegExtended { + rn: basereg, + rm: tmp.to_reg(), + extendop: ExtendOp::SXTX, + }, + ) } } - &AMode::Label(ref label) => { + &AMode::Label { ref label } => { let off = memlabel_finalize(insn_off, label); - (smallvec![], AMode::Label(MemLabel::PCRel(off))) + ( + smallvec![], + AMode::Label { + label: MemLabel::PCRel(off), + }, + ) } _ => (smallvec![], mem.clone()), @@ -184,7 +185,6 @@ fn enc_move_wide(op: MoveWideOp, rd: Writable, imm: MoveWideConst, size: Op let op = match op { MoveWideOp::MovN => 0b00, MoveWideOp::MovZ => 0b10, - MoveWideOp::MovK => 0b11, }; 0x12800000 | size.sf_bit() << 31 @@ -194,6 +194,15 @@ fn enc_move_wide(op: MoveWideOp, rd: Writable, imm: MoveWideConst, size: Op | machreg_to_gpr(rd.to_reg()) } +fn enc_movk(rd: Writable, imm: MoveWideConst, size: OperandSize) -> u32 { + assert!(imm.shift <= 0b11); + 0x72800000 + | size.sf_bit() << 31 + | u32::from(imm.shift) << 21 + | u32::from(imm.bits) << 5 + | machreg_to_gpr(rd.to_reg()) +} + fn enc_ldst_pair(op_31_22: u32, simm7: SImm7Scaled, rn: Reg, rt: Reg, rt2: Reg) -> u32 { (op_31_22 << 22) | (simm7.bits() << 15) @@ -325,11 +334,21 @@ pub(crate) fn enc_br(rn: Reg) -> u32 { 0b1101011_0000_11111_000000_00000_00000 | (machreg_to_gpr(rn) << 5) } -pub(crate) fn enc_adr(off: i32, rd: Writable) -> u32 { +pub(crate) fn enc_adr_inst(opcode: u32, off: i32, rd: Writable) -> u32 { let off = u32::try_from(off).unwrap(); let immlo = off & 3; let immhi = (off >> 2) & ((1 << 19) - 1); - (0b00010000 << 24) | (immlo << 29) | (immhi << 5) | machreg_to_gpr(rd.to_reg()) + opcode | (immlo << 29) | (immhi << 5) | machreg_to_gpr(rd.to_reg()) +} + +pub(crate) fn enc_adr(off: i32, rd: Writable) -> u32 { + let opcode = 0b00010000 << 24; + enc_adr_inst(opcode, off, rd) +} + +pub(crate) fn enc_adrp(off: i32, rd: Writable) -> u32 { + let opcode = 0b10010000 << 24; + enc_adr_inst(opcode, off, rd) } fn enc_csel(rd: Writable, rn: Reg, rm: Reg, cond: Cond, op: u32, o2: u32) -> u32 { @@ -353,6 +372,15 @@ fn enc_fcsel(rd: Writable, rn: Reg, rm: Reg, cond: Cond, size: ScalarSize) | (cond.bits() << 12) } +fn enc_ccmp(size: OperandSize, rn: Reg, rm: Reg, nzcv: NZCV, cond: Cond) -> u32 { + 0b0_1_1_11010010_00000_0000_00_00000_0_0000 + | size.sf_bit() << 31 + | machreg_to_gpr(rm) << 16 + | cond.bits() << 12 + | machreg_to_gpr(rn) << 5 + | nzcv.bits() +} + fn enc_ccmp_imm(size: OperandSize, rn: Reg, imm: UImm5, nzcv: NZCV, cond: Cond) -> u32 { 0b0_1_1_11010010_00000_0000_10_00000_0_0000 | size.sf_bit() << 31 @@ -617,16 +645,16 @@ pub struct EmitState { /// Safepoint stack map for upcoming instruction, as provided to `pre_safepoint()`. stack_map: Option, /// Current source-code location corresponding to instruction to be emitted. - cur_srcloc: SourceLoc, + cur_srcloc: RelSourceLoc, } impl MachInstEmitState for EmitState { - fn new(abi: &dyn ABICallee) -> Self { + fn new(abi: &Callee) -> Self { EmitState { virtual_sp_offset: 0, nominal_sp_to_fp: abi.frame_size() as i64, stack_map: None, - cur_srcloc: SourceLoc::default(), + cur_srcloc: Default::default(), } } @@ -634,7 +662,7 @@ impl MachInstEmitState for EmitState { self.stack_map = Some(stack_map); } - fn pre_sourceloc(&mut self, srcloc: SourceLoc) { + fn pre_sourceloc(&mut self, srcloc: RelSourceLoc) { self.cur_srcloc = srcloc; } } @@ -648,7 +676,7 @@ impl EmitState { self.stack_map = None; } - fn cur_srcloc(&self) -> SourceLoc { + fn cur_srcloc(&self) -> RelSourceLoc { self.cur_srcloc } } @@ -657,7 +685,8 @@ impl EmitState { pub struct EmitInfo(settings::Flags); impl EmitInfo { - pub(crate) fn new(flags: settings::Flags) -> Self { + /// Create a constant state for emission of instructions. + pub fn new(flags: settings::Flags) -> Self { Self(flags) } } @@ -909,6 +938,9 @@ impl MachInstEmit for Inst { BitOp::RBit => (0b00000, 0b000000), BitOp::Clz => (0b00000, 0b000100), BitOp::Cls => (0b00000, 0b000101), + BitOp::Rev16 => (0b00000, 0b000001), + BitOp::Rev32 => (0b00000, 0b000010), + BitOp::Rev64 => (0b00000, 0b000011), }; sink.put4(enc_bit_rr(size.sf_bit(), op1, op2, rn, rd)) } @@ -954,46 +986,47 @@ impl MachInstEmit for Inst { }; let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() && !flags.notrap() { + if !srcloc.is_default() && !flags.notrap() { // Register the offset at which the actual load instruction starts. sink.add_trap(TrapCode::HeapOutOfBounds); } match &mem { - &AMode::Unscaled(reg, simm9) => { - let reg = allocs.next(reg); + &AMode::Unscaled { rn, simm9 } => { + let reg = allocs.next(rn); sink.put4(enc_ldst_simm9(op, simm9, 0b00, reg, rd)); } - &AMode::UnsignedOffset(reg, uimm12scaled) => { - let reg = allocs.next(reg); - if uimm12scaled.value() != 0 { - assert_eq!(bits, ty_bits(uimm12scaled.scale_ty())); + &AMode::UnsignedOffset { rn, uimm12 } => { + let reg = allocs.next(rn); + if uimm12.value() != 0 { + assert_eq!(bits, ty_bits(uimm12.scale_ty())); } - sink.put4(enc_ldst_uimm12(op, uimm12scaled, reg, rd)); + sink.put4(enc_ldst_uimm12(op, uimm12, reg, rd)); } - &AMode::RegReg(r1, r2) => { - let r1 = allocs.next(r1); - let r2 = allocs.next(r2); + &AMode::RegReg { rn, rm } => { + let r1 = allocs.next(rn); + let r2 = allocs.next(rm); sink.put4(enc_ldst_reg( op, r1, r2, /* scaled = */ false, /* extendop = */ None, rd, )); } - &AMode::RegScaled(r1, r2, ty) | &AMode::RegScaledExtended(r1, r2, ty, _) => { - let r1 = allocs.next(r1); - let r2 = allocs.next(r2); + &AMode::RegScaled { rn, rm, ty } + | &AMode::RegScaledExtended { rn, rm, ty, .. } => { + let r1 = allocs.next(rn); + let r2 = allocs.next(rm); assert_eq!(bits, ty_bits(ty)); let extendop = match &mem { - &AMode::RegScaled(..) => None, - &AMode::RegScaledExtended(_, _, _, op) => Some(op), + &AMode::RegScaled { .. } => None, + &AMode::RegScaledExtended { extendop, .. } => Some(extendop), _ => unreachable!(), }; sink.put4(enc_ldst_reg( op, r1, r2, /* scaled = */ true, extendop, rd, )); } - &AMode::RegExtended(r1, r2, extendop) => { - let r1 = allocs.next(r1); - let r2 = allocs.next(r2); + &AMode::RegExtended { rn, rm, extendop } => { + let r1 = allocs.next(rn); + let r2 = allocs.next(rm); sink.put4(enc_ldst_reg( op, r1, @@ -1003,7 +1036,7 @@ impl MachInstEmit for Inst { rd, )); } - &AMode::Label(ref label) => { + &AMode::Label { ref label } => { let offset = match label { // cast i32 to u32 (two's-complement) &MemLabel::PCRel(off) => off as u32, @@ -1031,19 +1064,21 @@ impl MachInstEmit for Inst { _ => panic!("Unspported size for LDR from constant pool!"), } } - &AMode::PreIndexed(reg, simm9) => { - let reg = allocs.next(reg.to_reg()); + &AMode::SPPreIndexed { simm9 } => { + let reg = stack_reg(); sink.put4(enc_ldst_simm9(op, simm9, 0b11, reg, rd)); } - &AMode::PostIndexed(reg, simm9) => { - let reg = allocs.next(reg.to_reg()); + &AMode::SPPostIndexed { simm9 } => { + let reg = stack_reg(); sink.put4(enc_ldst_simm9(op, simm9, 0b01, reg, rd)); } // Eliminated by `mem_finalize()` above. - &AMode::SPOffset(..) | &AMode::FPOffset(..) | &AMode::NominalSPOffset(..) => { - panic!("Should not see stack-offset here!") + &AMode::SPOffset { .. } + | &AMode::FPOffset { .. } + | &AMode::NominalSPOffset { .. } + | &AMode::RegOffset { .. } => { + panic!("Should not see {:?} here!", mem) } - &AMode::RegOffset(..) => panic!("SHould not see generic reg-offset here!"), } } @@ -1074,45 +1109,45 @@ impl MachInstEmit for Inst { }; let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() && !flags.notrap() { + if !srcloc.is_default() && !flags.notrap() { // Register the offset at which the actual store instruction starts. sink.add_trap(TrapCode::HeapOutOfBounds); } match &mem { - &AMode::Unscaled(reg, simm9) => { - let reg = allocs.next(reg); + &AMode::Unscaled { rn, simm9 } => { + let reg = allocs.next(rn); sink.put4(enc_ldst_simm9(op, simm9, 0b00, reg, rd)); } - &AMode::UnsignedOffset(reg, uimm12scaled) => { - let reg = allocs.next(reg); - if uimm12scaled.value() != 0 { - assert_eq!(bits, ty_bits(uimm12scaled.scale_ty())); + &AMode::UnsignedOffset { rn, uimm12 } => { + let reg = allocs.next(rn); + if uimm12.value() != 0 { + assert_eq!(bits, ty_bits(uimm12.scale_ty())); } - sink.put4(enc_ldst_uimm12(op, uimm12scaled, reg, rd)); + sink.put4(enc_ldst_uimm12(op, uimm12, reg, rd)); } - &AMode::RegReg(r1, r2) => { - let r1 = allocs.next(r1); - let r2 = allocs.next(r2); + &AMode::RegReg { rn, rm } => { + let r1 = allocs.next(rn); + let r2 = allocs.next(rm); sink.put4(enc_ldst_reg( op, r1, r2, /* scaled = */ false, /* extendop = */ None, rd, )); } - &AMode::RegScaled(r1, r2, _ty) | &AMode::RegScaledExtended(r1, r2, _ty, _) => { - let r1 = allocs.next(r1); - let r2 = allocs.next(r2); + &AMode::RegScaled { rn, rm, .. } | &AMode::RegScaledExtended { rn, rm, .. } => { + let r1 = allocs.next(rn); + let r2 = allocs.next(rm); let extendop = match &mem { - &AMode::RegScaled(..) => None, - &AMode::RegScaledExtended(_, _, _, op) => Some(op), + &AMode::RegScaled { .. } => None, + &AMode::RegScaledExtended { extendop, .. } => Some(extendop), _ => unreachable!(), }; sink.put4(enc_ldst_reg( op, r1, r2, /* scaled = */ true, extendop, rd, )); } - &AMode::RegExtended(r1, r2, extendop) => { - let r1 = allocs.next(r1); - let r2 = allocs.next(r2); + &AMode::RegExtended { rn, rm, extendop } => { + let r1 = allocs.next(rn); + let r2 = allocs.next(rm); sink.put4(enc_ldst_reg( op, r1, @@ -1122,22 +1157,24 @@ impl MachInstEmit for Inst { rd, )); } - &AMode::Label(..) => { + &AMode::Label { .. } => { panic!("Store to a MemLabel not implemented!"); } - &AMode::PreIndexed(reg, simm9) => { - let reg = allocs.next(reg.to_reg()); + &AMode::SPPreIndexed { simm9 } => { + let reg = stack_reg(); sink.put4(enc_ldst_simm9(op, simm9, 0b11, reg, rd)); } - &AMode::PostIndexed(reg, simm9) => { - let reg = allocs.next(reg.to_reg()); + &AMode::SPPostIndexed { simm9 } => { + let reg = stack_reg(); sink.put4(enc_ldst_simm9(op, simm9, 0b01, reg, rd)); } // Eliminated by `mem_finalize()` above. - &AMode::SPOffset(..) | &AMode::FPOffset(..) | &AMode::NominalSPOffset(..) => { - panic!("Should not see stack-offset here!") + &AMode::SPOffset { .. } + | &AMode::FPOffset { .. } + | &AMode::NominalSPOffset { .. } + | &AMode::RegOffset { .. } => { + panic!("Should not see {:?} here!", mem) } - &AMode::RegOffset(..) => panic!("SHould not see generic reg-offset here!"), } } @@ -1151,7 +1188,7 @@ impl MachInstEmit for Inst { let rt2 = allocs.next(rt2); let mem = mem.with_allocs(&mut allocs); let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() && !flags.notrap() { + if !srcloc.is_default() && !flags.notrap() { // Register the offset at which the actual store instruction starts. sink.add_trap(TrapCode::HeapOutOfBounds); } @@ -1161,14 +1198,14 @@ impl MachInstEmit for Inst { let reg = allocs.next(reg); sink.put4(enc_ldst_pair(0b1010100100, simm7, reg, rt, rt2)); } - &PairAMode::PreIndexed(reg, simm7) => { + &PairAMode::SPPreIndexed(simm7) => { assert_eq!(simm7.scale_ty, I64); - let reg = allocs.next(reg.to_reg()); + let reg = stack_reg(); sink.put4(enc_ldst_pair(0b1010100110, simm7, reg, rt, rt2)); } - &PairAMode::PostIndexed(reg, simm7) => { + &PairAMode::SPPostIndexed(simm7) => { assert_eq!(simm7.scale_ty, I64); - let reg = allocs.next(reg.to_reg()); + let reg = stack_reg(); sink.put4(enc_ldst_pair(0b1010100010, simm7, reg, rt, rt2)); } } @@ -1183,7 +1220,7 @@ impl MachInstEmit for Inst { let rt2 = allocs.next(rt2.to_reg()); let mem = mem.with_allocs(&mut allocs); let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() && !flags.notrap() { + if !srcloc.is_default() && !flags.notrap() { // Register the offset at which the actual load instruction starts. sink.add_trap(TrapCode::HeapOutOfBounds); } @@ -1194,14 +1231,14 @@ impl MachInstEmit for Inst { let reg = allocs.next(reg); sink.put4(enc_ldst_pair(0b1010100101, simm7, reg, rt, rt2)); } - &PairAMode::PreIndexed(reg, simm7) => { + &PairAMode::SPPreIndexed(simm7) => { assert_eq!(simm7.scale_ty, I64); - let reg = allocs.next(reg.to_reg()); + let reg = stack_reg(); sink.put4(enc_ldst_pair(0b1010100111, simm7, reg, rt, rt2)); } - &PairAMode::PostIndexed(reg, simm7) => { + &PairAMode::SPPostIndexed(simm7) => { assert_eq!(simm7.scale_ty, I64); - let reg = allocs.next(reg.to_reg()); + let reg = stack_reg(); sink.put4(enc_ldst_pair(0b1010100011, simm7, reg, rt, rt2)); } } @@ -1223,7 +1260,7 @@ impl MachInstEmit for Inst { let mem = mem.with_allocs(&mut allocs); let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() && !flags.notrap() { + if !srcloc.is_default() && !flags.notrap() { // Register the offset at which the actual load instruction starts. sink.add_trap(TrapCode::HeapOutOfBounds); } @@ -1240,14 +1277,14 @@ impl MachInstEmit for Inst { let reg = allocs.next(reg); sink.put4(enc_ldst_vec_pair(opc, 0b10, true, simm7, reg, rt, rt2)); } - &PairAMode::PreIndexed(reg, simm7) => { + &PairAMode::SPPreIndexed(simm7) => { assert!(simm7.scale_ty == F64 || simm7.scale_ty == I8X16); - let reg = allocs.next(reg.to_reg()); + let reg = stack_reg(); sink.put4(enc_ldst_vec_pair(opc, 0b11, true, simm7, reg, rt, rt2)); } - &PairAMode::PostIndexed(reg, simm7) => { + &PairAMode::SPPostIndexed(simm7) => { assert!(simm7.scale_ty == F64 || simm7.scale_ty == I8X16); - let reg = allocs.next(reg.to_reg()); + let reg = stack_reg(); sink.put4(enc_ldst_vec_pair(opc, 0b01, true, simm7, reg, rt, rt2)); } } @@ -1269,7 +1306,7 @@ impl MachInstEmit for Inst { let mem = mem.with_allocs(&mut allocs); let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() && !flags.notrap() { + if !srcloc.is_default() && !flags.notrap() { // Register the offset at which the actual store instruction starts. sink.add_trap(TrapCode::HeapOutOfBounds); } @@ -1286,14 +1323,14 @@ impl MachInstEmit for Inst { let reg = allocs.next(reg); sink.put4(enc_ldst_vec_pair(opc, 0b10, false, simm7, reg, rt, rt2)); } - &PairAMode::PreIndexed(reg, simm7) => { + &PairAMode::SPPreIndexed(simm7) => { assert!(simm7.scale_ty == F64 || simm7.scale_ty == I8X16); - let reg = allocs.next(reg.to_reg()); + let reg = stack_reg(); sink.put4(enc_ldst_vec_pair(opc, 0b11, false, simm7, reg, rt, rt2)); } - &PairAMode::PostIndexed(reg, simm7) => { + &PairAMode::SPPostIndexed(simm7) => { assert!(simm7.scale_ty == F64 || simm7.scale_ty == I8X16); - let reg = allocs.next(reg.to_reg()); + let reg = stack_reg(); sink.put4(enc_ldst_vec_pair(opc, 0b01, false, simm7, reg, rt, rt2)); } } @@ -1334,19 +1371,48 @@ impl MachInstEmit for Inst { } } } - &Inst::MovPReg { rd, rm } => { + &Inst::MovFromPReg { rd, rm } => { let rd = allocs.next_writable(rd); + allocs.next_fixed_nonallocatable(rm); let rm: Reg = rm.into(); - debug_assert!([regs::fp_reg(), regs::stack_reg(), regs::link_reg()].contains(&rm)); + debug_assert!([ + regs::fp_reg(), + regs::stack_reg(), + regs::link_reg(), + regs::pinned_reg() + ] + .contains(&rm)); assert!(rm.class() == RegClass::Int); assert!(rd.to_reg().class() == rm.class()); let size = OperandSize::Size64; Inst::Mov { size, rd, rm }.emit(&[], sink, emit_info, state); } + &Inst::MovToPReg { rd, rm } => { + allocs.next_fixed_nonallocatable(rd); + let rd: Writable = Writable::from_reg(rd.into()); + let rm = allocs.next(rm); + debug_assert!([ + regs::fp_reg(), + regs::stack_reg(), + regs::link_reg(), + regs::pinned_reg() + ] + .contains(&rd.to_reg())); + assert!(rd.to_reg().class() == RegClass::Int); + assert!(rm.class() == rd.to_reg().class()); + let size = OperandSize::Size64; + Inst::Mov { size, rd, rm }.emit(&[], sink, emit_info, state); + } &Inst::MovWide { op, rd, imm, size } => { let rd = allocs.next_writable(rd); sink.put4(enc_move_wide(op, rd, imm, size)); } + &Inst::MovK { rd, rn, imm, size } => { + let rn = allocs.next(rn); + let rd = allocs.next_writable(rd); + debug_assert_eq!(rn, rd.to_reg()); + sink.put4(enc_movk(rd, imm, size)); + } &Inst::CSel { rd, rn, rm, cond } => { let rd = allocs.next_writable(rd); let rn = allocs.next(rn); @@ -1367,6 +1433,17 @@ impl MachInstEmit for Inst { let rd = allocs.next_writable(rd); sink.put4(enc_csel(rd, zero_reg(), zero_reg(), cond.invert(), 1, 0)); } + &Inst::CCmp { + size, + rn, + rm, + nzcv, + cond, + } => { + let rn = allocs.next(rn); + let rm = allocs.next(rm); + sink.put4(enc_ccmp(size, rn, rm, nzcv, cond)); + } &Inst::CCmpImm { size, rn, @@ -1377,13 +1454,26 @@ impl MachInstEmit for Inst { let rn = allocs.next(rn); sink.put4(enc_ccmp_imm(size, rn, imm, nzcv, cond)); } - &Inst::AtomicRMW { ty, op, rs, rt, rn } => { + &Inst::AtomicRMW { + ty, + op, + rs, + rt, + rn, + flags, + } => { let rs = allocs.next(rs); let rt = allocs.next_writable(rt); let rn = allocs.next(rn); + + let srcloc = state.cur_srcloc(); + if !srcloc.is_default() && !flags.notrap() { + sink.add_trap(TrapCode::HeapOutOfBounds); + } + sink.put4(enc_acq_rel(ty, op, rs, rt, rn)); } - &Inst::AtomicRMWLoop { ty, op } => { + &Inst::AtomicRMWLoop { ty, op, flags, .. } => { /* Emit this: again: ldaxr{,b,h} x/w27, [x25] @@ -1416,10 +1506,12 @@ impl MachInstEmit for Inst { // again: sink.bind_label(again_label); + let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() { + if !srcloc.is_default() && !flags.notrap() { sink.add_trap(TrapCode::HeapOutOfBounds); } + sink.put4(enc_ldaxr(ty, x27wr, x25)); // ldaxr x27, [x25] let size = OperandSize::from_ty(ty); let sign_ext = match op { @@ -1541,7 +1633,7 @@ impl MachInstEmit for Inst { } let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() { + if !srcloc.is_default() && !flags.notrap() { sink.add_trap(TrapCode::HeapOutOfBounds); } if op == AtomicRMWLoopOp::Xchg { @@ -1561,8 +1653,17 @@ impl MachInstEmit for Inst { )); sink.use_label_at_offset(br_offset, again_label, LabelUse::Branch19); } - &Inst::AtomicCAS { rs, rt, rn, ty } => { - let rs = allocs.next_writable(rs); + &Inst::AtomicCAS { + rd, + rs, + rt, + rn, + ty, + flags, + } => { + let rd = allocs.next_writable(rd); + let rs = allocs.next(rs); + debug_assert_eq!(rd.to_reg(), rs); let rt = allocs.next(rt); let rn = allocs.next(rn); let size = match ty { @@ -1573,9 +1674,14 @@ impl MachInstEmit for Inst { _ => panic!("Unsupported type: {}", ty), }; - sink.put4(enc_cas(size, rs, rt, rn)); + let srcloc = state.cur_srcloc(); + if !srcloc.is_default() && !flags.notrap() { + sink.add_trap(TrapCode::HeapOutOfBounds); + } + + sink.put4(enc_cas(size, rd, rt, rn)); } - &Inst::AtomicCASLoop { ty } => { + &Inst::AtomicCASLoop { ty, flags, .. } => { /* Emit this: again: ldaxr{,b,h} x/w27, [x25] @@ -1602,10 +1708,12 @@ impl MachInstEmit for Inst { // again: sink.bind_label(again_label); + let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() { + if !srcloc.is_default() && !flags.notrap() { sink.add_trap(TrapCode::HeapOutOfBounds); } + // ldaxr x27, [x25] sink.put4(enc_ldaxr(ty, x27wr, x25)); @@ -1630,9 +1738,10 @@ impl MachInstEmit for Inst { sink.use_label_at_offset(br_out_offset, out_label, LabelUse::Branch19); let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() { + if !srcloc.is_default() && !flags.notrap() { sink.add_trap(TrapCode::HeapOutOfBounds); } + sink.put4(enc_stlxr(ty, x24wr, x28, x25)); // stlxr w24, x28, [x25] // cbnz w24, again. @@ -1649,14 +1758,36 @@ impl MachInstEmit for Inst { // out: sink.bind_label(out_label); } - &Inst::LoadAcquire { access_ty, rt, rn } => { + &Inst::LoadAcquire { + access_ty, + rt, + rn, + flags, + } => { let rn = allocs.next(rn); let rt = allocs.next_writable(rt); + + let srcloc = state.cur_srcloc(); + if !srcloc.is_default() && !flags.notrap() { + sink.add_trap(TrapCode::HeapOutOfBounds); + } + sink.put4(enc_ldar(access_ty, rt, rn)); } - &Inst::StoreRelease { access_ty, rt, rn } => { + &Inst::StoreRelease { + access_ty, + rt, + rn, + flags, + } => { let rn = allocs.next(rn); let rt = allocs.next(rt); + + let srcloc = state.cur_srcloc(); + if !srcloc.is_default() && !flags.notrap() { + sink.add_trap(TrapCode::HeapOutOfBounds); + } + sink.put4(enc_stlr(access_ty, rt, rn)); } &Inst::Fence {} => { @@ -1768,7 +1899,15 @@ impl MachInstEmit for Inst { | machreg_to_vec(rd.to_reg()), ) } - FPUOpRI::Sli64(imm) => { + } + } + &Inst::FpuRRIMod { fpu_op, rd, ri, rn } => { + let rd = allocs.next_writable(rd); + let ri = allocs.next(ri); + let rn = allocs.next(rn); + debug_assert_eq!(rd.to_reg(), ri); + match fpu_op { + FPUOpRIMod::Sli64(imm) => { debug_assert_eq!(64, imm.lane_size_in_bits); sink.put4( 0b01_1_111110_0000000_010101_00000_00000 @@ -1777,7 +1916,7 @@ impl MachInstEmit for Inst { | machreg_to_vec(rd.to_reg()), ) } - FPUOpRI::Sli32(imm) => { + FPUOpRIMod::Sli32(imm) => { debug_assert_eq!(32, imm.lane_size_in_bits); sink.put4( 0b0_0_1_011110_0000000_010101_00000_00000 @@ -1976,31 +2115,34 @@ impl MachInstEmit for Inst { } => { let rd = allocs.next_writable(rd); let rn = allocs.next(rn); - let (is_shr, template) = match op { - VecShiftImmOp::Ushr => (true, 0b_011_011110_0000_000_000001_00000_00000_u32), - VecShiftImmOp::Sshr => (true, 0b_010_011110_0000_000_000001_00000_00000_u32), - VecShiftImmOp::Shl => (false, 0b_010_011110_0000_000_010101_00000_00000_u32), + let (is_shr, mut template) = match op { + VecShiftImmOp::Ushr => (true, 0b_001_011110_0000_000_000001_00000_00000_u32), + VecShiftImmOp::Sshr => (true, 0b_000_011110_0000_000_000001_00000_00000_u32), + VecShiftImmOp::Shl => (false, 0b_000_011110_0000_000_010101_00000_00000_u32), }; + if size.is_128bits() { + template |= 0b1 << 30; + } let imm = imm as u32; // Deal with the somewhat strange encoding scheme for, and limits on, // the shift amount. - let immh_immb = match (size, is_shr) { - (VectorSize::Size64x2, true) if imm >= 1 && imm <= 64 => { + let immh_immb = match (size.lane_size(), is_shr) { + (ScalarSize::Size64, true) if imm >= 1 && imm <= 64 => { 0b_1000_000_u32 | (64 - imm) } - (VectorSize::Size32x4, true) if imm >= 1 && imm <= 32 => { + (ScalarSize::Size32, true) if imm >= 1 && imm <= 32 => { 0b_0100_000_u32 | (32 - imm) } - (VectorSize::Size16x8, true) if imm >= 1 && imm <= 16 => { + (ScalarSize::Size16, true) if imm >= 1 && imm <= 16 => { 0b_0010_000_u32 | (16 - imm) } - (VectorSize::Size8x16, true) if imm >= 1 && imm <= 8 => { + (ScalarSize::Size8, true) if imm >= 1 && imm <= 8 => { 0b_0001_000_u32 | (8 - imm) } - (VectorSize::Size64x2, false) if imm <= 63 => 0b_1000_000_u32 | imm, - (VectorSize::Size32x4, false) if imm <= 31 => 0b_0100_000_u32 | imm, - (VectorSize::Size16x8, false) if imm <= 15 => 0b_0010_000_u32 | imm, - (VectorSize::Size8x16, false) if imm <= 7 => 0b_0001_000_u32 | imm, + (ScalarSize::Size64, false) if imm <= 63 => 0b_1000_000_u32 | imm, + (ScalarSize::Size32, false) if imm <= 31 => 0b_0100_000_u32 | imm, + (ScalarSize::Size16, false) if imm <= 15 => 0b_0010_000_u32 | imm, + (ScalarSize::Size8, false) if imm <= 7 => 0b_0001_000_u32 | imm, _ => panic!( "aarch64: Inst::VecShiftImm: emit: invalid op/size/imm {:?}, {:?}, {:?}", op, size, imm @@ -2010,6 +2152,53 @@ impl MachInstEmit for Inst { let rd_enc = machreg_to_vec(rd.to_reg()); sink.put4(template | (immh_immb << 16) | (rn_enc << 5) | rd_enc); } + &Inst::VecShiftImmMod { + op, + rd, + ri, + rn, + size, + imm, + } => { + let rd = allocs.next_writable(rd); + let ri = allocs.next(ri); + debug_assert_eq!(rd.to_reg(), ri); + let rn = allocs.next(rn); + let (is_shr, mut template) = match op { + VecShiftImmModOp::Sli => (false, 0b_001_011110_0000_000_010101_00000_00000_u32), + }; + if size.is_128bits() { + template |= 0b1 << 30; + } + let imm = imm as u32; + // Deal with the somewhat strange encoding scheme for, and limits on, + // the shift amount. + let immh_immb = match (size.lane_size(), is_shr) { + (ScalarSize::Size64, true) if imm >= 1 && imm <= 64 => { + 0b_1000_000_u32 | (64 - imm) + } + (ScalarSize::Size32, true) if imm >= 1 && imm <= 32 => { + 0b_0100_000_u32 | (32 - imm) + } + (ScalarSize::Size16, true) if imm >= 1 && imm <= 16 => { + 0b_0010_000_u32 | (16 - imm) + } + (ScalarSize::Size8, true) if imm >= 1 && imm <= 8 => { + 0b_0001_000_u32 | (8 - imm) + } + (ScalarSize::Size64, false) if imm <= 63 => 0b_1000_000_u32 | imm, + (ScalarSize::Size32, false) if imm <= 31 => 0b_0100_000_u32 | imm, + (ScalarSize::Size16, false) if imm <= 15 => 0b_0010_000_u32 | imm, + (ScalarSize::Size8, false) if imm <= 7 => 0b_0001_000_u32 | imm, + _ => panic!( + "aarch64: Inst::VecShiftImmMod: emit: invalid op/size/imm {:?}, {:?}, {:?}", + op, size, imm + ), + }; + let rn_enc = machreg_to_vec(rn); + let rd_enc = machreg_to_vec(rd.to_reg()); + sink.put4(template | (immh_immb << 16) | (rn_enc << 5) | rd_enc); + } &Inst::VecExtract { rd, rn, rm, imm4 } => { let rd = allocs.next_writable(rd); let rn = allocs.next(rn); @@ -2029,30 +2218,43 @@ impl MachInstEmit for Inst { ); } } - &Inst::VecTbl { - rd, - rn, - rm, - is_extension, - } => { + &Inst::VecTbl { rd, rn, rm } => { + let rn = allocs.next(rn); + let rm = allocs.next(rm); + let rd = allocs.next_writable(rd); + sink.put4(enc_tbl(/* is_extension = */ false, 0b00, rd, rn, rm)); + } + &Inst::VecTblExt { rd, ri, rn, rm } => { + let rn = allocs.next(rn); + let rm = allocs.next(rm); + let rd = allocs.next_writable(rd); + let ri = allocs.next(ri); + debug_assert_eq!(rd.to_reg(), ri); + sink.put4(enc_tbl(/* is_extension = */ true, 0b00, rd, rn, rm)); + } + &Inst::VecTbl2 { rd, rn, rn2, rm } => { let rn = allocs.next(rn); + let rn2 = allocs.next(rn2); let rm = allocs.next(rm); let rd = allocs.next_writable(rd); - sink.put4(enc_tbl(is_extension, 0b00, rd, rn, rm)); + assert_eq!(machreg_to_vec(rn2), (machreg_to_vec(rn) + 1) % 32); + sink.put4(enc_tbl(/* is_extension = */ false, 0b01, rd, rn, rm)); } - &Inst::VecTbl2 { + &Inst::VecTbl2Ext { rd, + ri, rn, rn2, rm, - is_extension, } => { let rn = allocs.next(rn); let rn2 = allocs.next(rn2); let rm = allocs.next(rm); let rd = allocs.next_writable(rd); + let ri = allocs.next(ri); + debug_assert_eq!(rd.to_reg(), ri); assert_eq!(machreg_to_vec(rn2), (machreg_to_vec(rn) + 1) % 32); - sink.put4(enc_tbl(is_extension, 0b01, rd, rn, rm)); + sink.put4(enc_tbl(/* is_extension = */ true, 0b01, rd, rn, rm)); } &Inst::FpuCmp { size, rn, rm } => { let rn = allocs.next(rn); @@ -2109,7 +2311,9 @@ impl MachInstEmit for Inst { let rd = allocs.next_writable(rd); let inst = Inst::FpuLoad64 { rd, - mem: AMode::Label(MemLabel::PCRel(8)), + mem: AMode::Label { + label: MemLabel::PCRel(8), + }, flags: MemFlags::trusted(), }; inst.emit(&[], sink, emit_info, state); @@ -2123,7 +2327,9 @@ impl MachInstEmit for Inst { let rd = allocs.next_writable(rd); let inst = Inst::FpuLoad128 { rd, - mem: AMode::Label(MemLabel::PCRel(8)), + mem: AMode::Label { + label: MemLabel::PCRel(8), + }, flags: MemFlags::trusted(), }; inst.emit(&[], sink, emit_info, state); @@ -2187,8 +2393,16 @@ impl MachInstEmit for Inst { | machreg_to_vec(rd.to_reg()), ); } - &Inst::MovToVec { rd, rn, idx, size } => { + &Inst::MovToVec { + rd, + ri, + rn, + idx, + size, + } => { let rd = allocs.next_writable(rd); + let ri = allocs.next(ri); + debug_assert_eq!(rd.to_reg(), ri); let rn = allocs.next(rn); let (imm5, shift) = match size.lane_size() { ScalarSize::Size8 => (0b00001, 1), @@ -2282,13 +2496,15 @@ impl MachInstEmit for Inst { &Inst::VecDupFromFpu { rd, rn, size } => { let rd = allocs.next_writable(rd); let rn = allocs.next(rn); - let imm5 = match size { - VectorSize::Size32x4 => 0b00100, - VectorSize::Size64x2 => 0b01000, + let q = size.is_128bits() as u32; + let imm5 = match size.lane_size() { + ScalarSize::Size32 => 0b00100, + ScalarSize::Size64 => 0b01000, _ => unimplemented!(), }; sink.put4( - 0b010_01110000_00000_000001_00000_00000 + 0b000_01110000_00000_000001_00000_00000 + | (q << 30) | (imm5 << 16) | (machreg_to_vec(rn) << 5) | machreg_to_vec(rd.to_reg()), @@ -2359,16 +2575,19 @@ impl MachInstEmit for Inst { rd, rn, high_half, + lane_size, } => { let rd = allocs.next_writable(rd); let rn = allocs.next(rn); - let (u, immh) = match t { - VecExtendOp::Sxtl8 => (0b0, 0b001), - VecExtendOp::Sxtl16 => (0b0, 0b010), - VecExtendOp::Sxtl32 => (0b0, 0b100), - VecExtendOp::Uxtl8 => (0b1, 0b001), - VecExtendOp::Uxtl16 => (0b1, 0b010), - VecExtendOp::Uxtl32 => (0b1, 0b100), + let immh = match lane_size { + ScalarSize::Size16 => 0b001, + ScalarSize::Size32 => 0b010, + ScalarSize::Size64 => 0b100, + _ => panic!("Unexpected VecExtend to lane size of {:?}", lane_size), + }; + let u = match t { + VecExtendOp::Sxtl => 0b0, + VecExtendOp::Uxtl => 0b1, }; sink.put4( 0b000_011110_0000_000_101001_00000_00000 @@ -2403,15 +2622,26 @@ impl MachInstEmit for Inst { rn, )); } - &Inst::VecRRNarrow { + &Inst::VecRRNarrowLow { op, rd, rn, - high_half, lane_size, + } + | &Inst::VecRRNarrowHigh { + op, + rd, + rn, + lane_size, + .. } => { let rn = allocs.next(rn); let rd = allocs.next_writable(rd); + let high_half = match self { + &Inst::VecRRNarrowLow { .. } => false, + &Inst::VecRRNarrowHigh { .. } => true, + _ => unreachable!(), + }; let size = match lane_size { ScalarSize::Size8 => 0b00, @@ -2444,12 +2674,15 @@ impl MachInstEmit for Inst { } &Inst::VecMovElement { rd, + ri, rn, dest_idx, src_idx, size, } => { let rd = allocs.next_writable(rd); + let ri = allocs.next(ri); + debug_assert_eq!(rd.to_reg(), ri); let rn = allocs.next(rn); let (imm5, shift) = match size.lane_size() { ScalarSize::Size8 => (0b00001, 1), @@ -2497,9 +2730,34 @@ impl MachInstEmit for Inst { VecRRRLongOp::Umull8 => (0b1, 0b00, 0b1), VecRRRLongOp::Umull16 => (0b1, 0b01, 0b1), VecRRRLongOp::Umull32 => (0b1, 0b10, 0b1), - VecRRRLongOp::Umlal8 => (0b1, 0b00, 0b0), - VecRRRLongOp::Umlal16 => (0b1, 0b01, 0b0), - VecRRRLongOp::Umlal32 => (0b1, 0b10, 0b0), + }; + sink.put4(enc_vec_rrr_long( + high_half as u32, + u, + size, + bit14, + rm, + rn, + rd, + )); + } + &Inst::VecRRRLongMod { + rd, + ri, + rn, + rm, + alu_op, + high_half, + } => { + let rd = allocs.next_writable(rd); + let ri = allocs.next(ri); + debug_assert_eq!(rd.to_reg(), ri); + let rn = allocs.next(rn); + let rm = allocs.next(rm); + let (u, size, bit14) = match alu_op { + VecRRRLongModOp::Umlal8 => (0b1, 0b00, 0b0), + VecRRRLongModOp::Umlal16 => (0b1, 0b01, 0b0), + VecRRRLongModOp::Umlal32 => (0b1, 0b10, 0b0), }; sink.put4(enc_vec_rrr_long( high_half as u32, @@ -2543,17 +2801,9 @@ impl MachInstEmit for Inst { | VecALUOp::Fdiv | VecALUOp::Fmax | VecALUOp::Fmin - | VecALUOp::Fmul - | VecALUOp::Fmla => true, + | VecALUOp::Fmul => true, _ => false, }; - let enc_float_size = match (is_float, size) { - (true, VectorSize::Size32x2) => 0b0, - (true, VectorSize::Size32x4) => 0b0, - (true, VectorSize::Size64x2) => 0b1, - (true, _) => unimplemented!(), - _ => 0, - }; let (top11, bit15_10) = match alu_op { VecALUOp::Sqadd => (0b000_01110_00_1 | enc_size << 1, 0b000011), @@ -2574,7 +2824,6 @@ impl MachInstEmit for Inst { VecALUOp::Bic => (0b000_01110_01_1, 0b000111), VecALUOp::Orr => (0b000_01110_10_1, 0b000111), VecALUOp::Eor => (0b001_01110_00_1, 0b000111), - VecALUOp::Bsl => (0b001_01110_01_1, 0b000111), VecALUOp::Umaxp => { debug_assert_ne!(size, VectorSize::Size64x2); @@ -2619,7 +2868,6 @@ impl MachInstEmit for Inst { VecALUOp::Fmax => (0b000_01110_00_1, 0b111101), VecALUOp::Fmin => (0b000_01110_10_1, 0b111101), VecALUOp::Fmul => (0b001_01110_00_1, 0b110111), - VecALUOp::Fmla => (0b000_01110_00_1, 0b110011), VecALUOp::Addp => (0b000_01110_00_1 | enc_size << 1, 0b101111), VecALUOp::Zip1 => (0b01001110_00_0 | enc_size << 1, 0b001110), VecALUOp::Sqrdmulh => { @@ -2632,12 +2880,35 @@ impl MachInstEmit for Inst { } }; let top11 = if is_float { - top11 | enc_float_size << 1 + top11 | size.enc_float_size() << 1 } else { top11 }; sink.put4(enc_vec_rrr(top11 | q << 9, rm, bit15_10, rn, rd)); } + &Inst::VecRRRMod { + rd, + ri, + rn, + rm, + alu_op, + size, + } => { + let rd = allocs.next_writable(rd); + let ri = allocs.next(ri); + debug_assert_eq!(rd.to_reg(), ri); + let rn = allocs.next(rn); + let rm = allocs.next(rm); + let (q, _enc_size) = size.enc_size(); + + let (top11, bit15_10) = match alu_op { + VecALUModOp::Bsl => (0b001_01110_01_1, 0b000111), + VecALUModOp::Fmla => { + (0b000_01110_00_1 | (size.enc_float_size() << 1), 0b110011) + } + }; + sink.put4(enc_vec_rrr(top11 | q << 9, rm, bit15_10, rn, rd)); + } &Inst::VecLoadReplicate { rd, rn, @@ -2649,7 +2920,7 @@ impl MachInstEmit for Inst { let (q, size) = size.enc_size(); let srcloc = state.cur_srcloc(); - if srcloc != SourceLoc::default() && !flags.notrap() { + if !srcloc.is_default() && !flags.notrap() { // Register the offset at which the actual load instruction starts. sink.add_trap(TrapCode::HeapOutOfBounds); } @@ -2774,6 +3045,10 @@ impl MachInstEmit for Inst { // Emit the jump itself. sink.put4(enc_jump26(0b000101, dest.as_offset26_or_zero())); } + &Inst::Args { .. } => { + // Nothing: this is a pseudoinstruction that serves + // only to constrain registers at a certain point. + } &Inst::Ret { .. } => { sink.put4(0xd65f03c0); } @@ -2878,6 +3153,12 @@ impl MachInstEmit for Inst { assert!(off < (1 << 20)); sink.put4(enc_adr(off, rd)); } + &Inst::Adrp { rd, off } => { + let rd = allocs.next_writable(rd); + assert!(off > -(1 << 20)); + assert!(off < (1 << 20)); + sink.put4(enc_adrp(off, rd)); + } &Inst::Word4 { data } => { sink.put4(data); } @@ -2985,18 +3266,52 @@ impl MachInstEmit for Inst { offset, } => { let rd = allocs.next_writable(rd); - let inst = Inst::ULoad64 { - rd, - mem: AMode::Label(MemLabel::PCRel(8)), - flags: MemFlags::trusted(), - }; - inst.emit(&[], sink, emit_info, state); - let inst = Inst::Jump { - dest: BranchTarget::ResolvedOffset(12), - }; - inst.emit(&[], sink, emit_info, state); - sink.add_reloc(Reloc::Abs8, name, offset); - sink.put8(0); + + if emit_info.0.is_pic() { + // See this CE Example for the variations of this with and without BTI & PAUTH + // https://godbolt.org/z/ncqjbbvvn + // + // Emit the following code: + // adrp rd, :got:X + // ldr rd, [rd, :got_lo12:X] + + // adrp rd, symbol + sink.add_reloc(Reloc::Aarch64AdrGotPage21, name, 0); + let inst = Inst::Adrp { rd, off: 0 }; + inst.emit(&[], sink, emit_info, state); + + // ldr rd, [rd, :got_lo12:X] + sink.add_reloc(Reloc::Aarch64Ld64GotLo12Nc, name, 0); + let inst = Inst::ULoad64 { + rd, + mem: AMode::reg(rd.to_reg()), + flags: MemFlags::trusted(), + }; + inst.emit(&[], sink, emit_info, state); + } else { + // With absolute offsets we set up a load from a preallocated space, and then jump + // over it. + // + // Emit the following code: + // ldr rd, #8 + // b #0x10 + // <8 byte space> + + let inst = Inst::ULoad64 { + rd, + mem: AMode::Label { + label: MemLabel::PCRel(8), + }, + flags: MemFlags::trusted(), + }; + inst.emit(&[], sink, emit_info, state); + let inst = Inst::Jump { + dest: BranchTarget::ResolvedOffset(12), + }; + inst.emit(&[], sink, emit_info, state); + sink.add_reloc(Reloc::Abs8, name, offset); + sink.put8(0); + } } &Inst::LoadAddr { rd, ref mem } => { let rd = allocs.next_writable(rd); @@ -3007,17 +3322,17 @@ impl MachInstEmit for Inst { } let (reg, index_reg, offset) = match mem { - AMode::RegExtended(r, idx, extendop) => { - let r = allocs.next(r); - (r, Some((idx, extendop)), 0) + AMode::RegExtended { rn, rm, extendop } => { + let r = allocs.next(rn); + (r, Some((rm, extendop)), 0) } - AMode::Unscaled(r, simm9) => { - let r = allocs.next(r); + AMode::Unscaled { rn, simm9 } => { + let r = allocs.next(rn); (r, None, simm9.value()) } - AMode::UnsignedOffset(r, uimm12scaled) => { - let r = allocs.next(r); - (r, None, uimm12scaled.value() as i32) + AMode::UnsignedOffset { rn, uimm12 } => { + let r = allocs.next(rn); + (r, None, uimm12.value() as i32) } _ => panic!("Unsupported case for LoadAddr: {:?}", mem), }; @@ -3067,7 +3382,7 @@ impl MachInstEmit for Inst { debug_assert!(rd.to_reg() != tmp2_reg()); debug_assert!(reg != tmp2_reg()); let tmp = writable_tmp2_reg(); - for insn in Inst::load_constant(tmp, abs_offset).into_iter() { + for insn in Inst::load_constant(tmp, abs_offset, &mut |_| tmp).into_iter() { insn.emit(&[], sink, emit_info, state); } let add = Inst::AluRRR { @@ -3088,6 +3403,17 @@ impl MachInstEmit for Inst { sink.put4(0xd503233f | key << 6); } + &Inst::Xpaclri => sink.put4(0xd50320ff), + &Inst::Bti { targets } => { + let targets = match targets { + BranchTargetType::None => 0b00, + BranchTargetType::C => 0b01, + BranchTargetType::J => 0b10, + BranchTargetType::JC => 0b11, + }; + + sink.put4(0xd503241f | targets << 6); + } &Inst::VirtualSPOffsetAdj { offset } => { trace!( "virtual sp offset adjusted by {} -> {}", @@ -3108,13 +3434,17 @@ impl MachInstEmit for Inst { } } - &Inst::ElfTlsGetAddr { ref symbol } => { + &Inst::ElfTlsGetAddr { ref symbol, rd } => { + let rd = allocs.next_writable(rd); + assert_eq!(xreg(0), rd.to_reg()); + // This is the instruction sequence that GCC emits for ELF GD TLS Relocations in aarch64 // See: https://gcc.godbolt.org/z/KhMh5Gvra // adrp x0,
{ if let Some(Token::Table(table)) = self.token() { @@ -721,17 +654,6 @@ impl<'a> Parser<'a> { err!(self.loc, err_msg) } - // Match and consume a jump table reference. - fn match_jt(&mut self) -> ParseResult { - if let Some(Token::JumpTable(jt)) = self.token() { - self.consume(); - if let Some(jt) = JumpTable::with_number(jt) { - return Ok(jt); - } - } - err!(self.loc, "expected jump table number: jt«n»") - } - // Match and consume a constant reference. fn match_constant(&mut self) -> ParseResult { if let Some(Token::Constant(c)) = self.token() { @@ -968,20 +890,6 @@ impl<'a> Parser<'a> { } } - // Match and consume a boolean immediate. - fn match_bool(&mut self, err_msg: &str) -> ParseResult { - if let Some(Token::Identifier(text)) = self.token() { - self.consume(); - match text { - "true" => Ok(true), - "false" => Ok(false), - _ => err!(self.loc, err_msg), - } - } else { - err!(self.loc, err_msg) - } - } - // Match and consume an enumerated immediate, like one of the condition codes. fn match_enum(&mut self, err_msg: &str) -> ParseResult { if let Some(Token::Identifier(text)) = self.token() { @@ -1046,15 +954,6 @@ impl<'a> Parser<'a> { }}; } - fn boolean_to_vec(value: bool, ty: Type) -> Vec { - let lane_size = ty.bytes() / u32::from(ty.lane_count()); - if lane_size < 1 { - panic!("The boolean lane must have a byte size greater than zero."); - } - let value = if value { 0xFF } else { 0 }; - vec![value; lane_size as usize] - } - if !ty.is_vector() && !ty.is_dynamic_vector() { err!(self.loc, "Expected a controlling vector type, not {}", ty) } else { @@ -1065,10 +964,6 @@ impl<'a> Parser<'a> { I64 => consume!(ty, self.match_imm64("Expected a 64-bit integer")?), F32 => consume!(ty, self.match_ieee32("Expected a 32-bit float")?), F64 => consume!(ty, self.match_ieee64("Expected a 64-bit float")?), - b if b.is_bool() => consume!( - ty, - boolean_to_vec(self.match_bool("Expected a boolean")?, ty) - ), _ => return err!(self.loc, "Expected a type of: float, int, bool"), }; Ok(constant_data) @@ -1154,9 +1049,24 @@ impl<'a> Parser<'a> { let mut targets = Vec::new(); let mut flag_builder = settings::builder(); - let unwind_info = if options.unwind_info { "true" } else { "false" }; + let bool_to_str = |val: bool| { + if val { + "true" + } else { + "false" + } + }; + + // default to enabling cfg info + flag_builder + .set( + "machine_code_cfg_info", + bool_to_str(options.machine_code_cfg_info), + ) + .expect("machine_code_cfg_info option should be present"); + flag_builder - .set("unwind_info", unwind_info) + .set("unwind_info", bool_to_str(options.unwind_info)) .expect("unwind_info option should be present"); while let Some(Token::Identifier(command)) = self.token() { @@ -1282,7 +1192,7 @@ impl<'a> Parser<'a> { let location = self.loc; // function ::= "function" * name signature "{" preamble function-body "}" - let name = self.parse_external_name()?; + let name = self.parse_user_func_name()?; // function ::= "function" name * signature "{" preamble function-body "}" let sig = self.parse_signature()?; @@ -1307,6 +1217,16 @@ impl<'a> Parser<'a> { self.token(); self.claim_gathered_comments(AnyEntity::Function); + // Claim all the declared user-defined function names. + for (user_func_ref, user_external_name) in + std::mem::take(&mut self.predeclared_external_names) + { + let actual_ref = ctx + .function + .declare_imported_user_function(user_external_name); + assert_eq!(user_func_ref, actual_ref); + } + let details = Details { location, comments: self.take_comments(), @@ -1316,18 +1236,17 @@ impl<'a> Parser<'a> { Ok((ctx.function, details)) } - // Parse an external name. + // Parse a user-defined function name // // For example, in a function decl, the parser would be in this state: // // function ::= "function" * name signature { ... } // - fn parse_external_name(&mut self) -> ParseResult { + fn parse_user_func_name(&mut self) -> ParseResult { match self.token() { Some(Token::Name(s)) => { self.consume(); - s.parse() - .map_err(|_| self.error("invalid test case or libcall name")) + Ok(UserFuncName::testcase(s)) } Some(Token::UserRef(namespace)) => { self.consume(); @@ -1336,19 +1255,84 @@ impl<'a> Parser<'a> { self.consume(); match self.token() { Some(Token::Integer(index_str)) => { + self.consume(); let index: u32 = u32::from_str_radix(index_str, 10).map_err(|_| { self.error("the integer given overflows the u32 type") })?; - self.consume(); - Ok(ExternalName::user(namespace, index)) + Ok(UserFuncName::user(namespace, index)) } _ => err!(self.loc, "expected integer"), } } - _ => err!(self.loc, "expected colon"), + _ => { + err!(self.loc, "expected user function name in the form uX:Y") + } + } + } + _ => err!(self.loc, "expected external name"), + } + } + + // Parse an external name. + // + // For example, in a function reference decl, the parser would be in this state: + // + // fn0 = * name signature + // + fn parse_external_name(&mut self) -> ParseResult { + match self.token() { + Some(Token::Name(s)) => { + self.consume(); + s.parse() + .map_err(|_| self.error("invalid test case or libcall name")) + } + + Some(Token::UserNameRef(name_ref)) => { + self.consume(); + Ok(ExternalName::user(UserExternalNameRef::new( + name_ref as usize, + ))) + } + + Some(Token::UserRef(namespace)) => { + self.consume(); + if let Some(Token::Colon) = self.token() { + self.consume(); + match self.token() { + Some(Token::Integer(index_str)) => { + let index: u32 = u32::from_str_radix(index_str, 10).map_err(|_| { + self.error("the integer given overflows the u32 type") + })?; + self.consume(); + + // Deduplicate the reference (O(n), but should be fine for tests), + // to follow `FunctionParameters::declare_imported_user_function`, + // otherwise this will cause ref mismatches when asserted below. + let name_ref = self + .predeclared_external_names + .iter() + .find_map(|(reff, name)| { + if name.index == index && name.namespace == namespace { + Some(reff) + } else { + None + } + }) + .unwrap_or_else(|| { + self.predeclared_external_names + .push(ir::UserExternalName { namespace, index }) + }); + + Ok(ExternalName::user(name_ref)) + } + _ => err!(self.loc, "expected integer"), + } + } else { + err!(self.loc, "expected colon") } } + _ => err!(self.loc, "expected external name"), } } @@ -1406,10 +1390,10 @@ impl<'a> Parser<'a> { // Parse a single argument type with flags. fn parse_abi_param(&mut self) -> ParseResult { - // abi-param ::= * type { flag } [ argumentloc ] + // abi-param ::= * type { flag } let mut arg = AbiParam::new(self.match_type("expected parameter type")?); - // abi-param ::= type * { flag } [ argumentloc ] + // abi-param ::= type * { flag } while let Some(Token::Identifier(s)) = self.token() { match s { "uext" => arg.extension = ArgumentExtension::Uext, @@ -1472,11 +1456,6 @@ impl<'a> Parser<'a> { self.parse_global_value_decl() .and_then(|(gv, dat)| ctx.add_gv(gv, dat, self.loc)) } - Some(Token::Heap(..)) => { - self.start_gathering_comments(); - self.parse_heap_decl() - .and_then(|(heap, dat)| ctx.add_heap(heap, dat, self.loc)) - } Some(Token::Table(..)) => { self.start_gathering_comments(); self.parse_table_decl() @@ -1493,11 +1472,6 @@ impl<'a> Parser<'a> { self.parse_function_decl(ctx) .and_then(|(fn_, dat)| ctx.add_fn(fn_, dat, self.loc)) } - Some(Token::JumpTable(..)) => { - self.start_gathering_comments(); - self.parse_jump_table_decl() - .and_then(|(jt, dat)| ctx.add_jt(jt, dat, self.loc)) - } Some(Token::Constant(..)) => { self.start_gathering_comments(); self.parse_constant_decl() @@ -1664,77 +1638,6 @@ impl<'a> Parser<'a> { Ok((gv, data)) } - // Parse a heap decl. - // - // heap-decl ::= * Heap(heap) "=" heap-desc - // heap-desc ::= heap-style heap-base { "," heap-attr } - // heap-style ::= "static" | "dynamic" - // heap-base ::= GlobalValue(base) - // heap-attr ::= "min" Imm64(bytes) - // | "bound" Imm64(bytes) - // | "offset_guard" Imm64(bytes) - // | "index_type" type - // - fn parse_heap_decl(&mut self) -> ParseResult<(Heap, HeapData)> { - let heap = self.match_heap("expected heap number: heap«n»")?; - self.match_token(Token::Equal, "expected '=' in heap declaration")?; - - let style_name = self.match_any_identifier("expected 'static' or 'dynamic'")?; - - // heap-desc ::= heap-style * heap-base { "," heap-attr } - // heap-base ::= * GlobalValue(base) - let base = match self.token() { - Some(Token::GlobalValue(base_num)) => match GlobalValue::with_number(base_num) { - Some(gv) => gv, - None => return err!(self.loc, "invalid global value number for heap base"), - }, - _ => return err!(self.loc, "expected heap base"), - }; - self.consume(); - - let mut data = HeapData { - base, - min_size: 0.into(), - offset_guard_size: 0.into(), - style: HeapStyle::Static { bound: 0.into() }, - index_type: ir::types::I32, - }; - - // heap-desc ::= heap-style heap-base * { "," heap-attr } - while self.optional(Token::Comma) { - match self.match_any_identifier("expected heap attribute name")? { - "min" => { - data.min_size = self.match_uimm64("expected integer min size")?; - } - "bound" => { - data.style = match style_name { - "dynamic" => HeapStyle::Dynamic { - bound_gv: self.match_gv("expected gv bound")?, - }, - "static" => HeapStyle::Static { - bound: self.match_uimm64("expected integer bound")?, - }, - t => return err!(self.loc, "unknown heap style '{}'", t), - }; - } - "offset_guard" => { - data.offset_guard_size = - self.match_uimm64("expected integer offset-guard size")?; - } - "index_type" => { - data.index_type = self.match_type("expected index type")?; - } - t => return err!(self.loc, "unknown heap attribute '{}'", t), - } - } - - // Collect any trailing comments. - self.token(); - self.claim_gathered_comments(heap); - - Ok((heap, data)) - } - // Parse a table decl. // // table-decl ::= * Table(table) "=" table-desc @@ -1878,22 +1781,19 @@ impl<'a> Parser<'a> { Ok((fn_, data)) } - // Parse a jump table decl. + // Parse a jump table literal. // - // jump-table-decl ::= * JumpTable(jt) "=" "jump_table" "[" jt-entry {"," jt-entry} "]" - fn parse_jump_table_decl(&mut self) -> ParseResult<(JumpTable, JumpTableData)> { - let jt = self.match_jt()?; - self.match_token(Token::Equal, "expected '=' in jump_table decl")?; - self.match_identifier("jump_table", "expected 'jump_table'")?; + // jump-table-lit ::= "[" block {"," block } "]" + // | "[]" + fn parse_jump_table(&mut self, def: Block) -> ParseResult { self.match_token(Token::LBracket, "expected '[' before jump table contents")?; - let mut data = JumpTableData::new(); + let mut data = Vec::new(); - // jump-table-decl ::= JumpTable(jt) "=" "jump_table" "[" * Block(dest) {"," Block(dest)} "]" match self.token() { Some(Token::Block(dest)) => { self.consume(); - data.push_entry(dest); + data.push(dest); loop { match self.token() { @@ -1901,7 +1801,7 @@ impl<'a> Parser<'a> { self.consume(); if let Some(Token::Block(dest)) = self.token() { self.consume(); - data.push_entry(dest); + data.push(dest); } else { return err!(self.loc, "expected jump_table entry"); } @@ -1917,11 +1817,7 @@ impl<'a> Parser<'a> { self.consume(); - // Collect any trailing comments. - self.token(); - self.claim_gathered_comments(jt); - - Ok((jt, data)) + Ok(JumpTableData::new(def, &data)) } // Parse a constant decl. @@ -1979,8 +1875,8 @@ impl<'a> Parser<'a> { // all references refer to a definition. for block in &ctx.function.layout { for inst in ctx.function.layout.block_insts(block) { - for value in ctx.function.dfg.inst_args(inst) { - if !ctx.map.contains_value(*value) { + for value in ctx.function.dfg.inst_values(inst) { + if !ctx.map.contains_value(value) { return err!( ctx.map.location(AnyEntity::Inst(inst)).unwrap(), "undefined operand value {}", @@ -2249,7 +2145,7 @@ impl<'a> Parser<'a> { .expect("duplicate inst references created"); if !srcloc.is_default() { - ctx.function.srclocs[inst] = srcloc; + ctx.function.set_srcloc(inst, srcloc); } if results.len() != num_results { @@ -2388,86 +2284,6 @@ impl<'a> Parser<'a> { Ok(args) } - /// Parse a vmctx offset annotation - /// - /// vmctx-offset ::= "vmctx" "+" UImm64(offset) - fn parse_vmctx_offset(&mut self) -> ParseResult { - self.match_token(Token::Identifier("vmctx"), "expected a 'vmctx' token")?; - - // The '+' token here gets parsed as part of the integer text, so we can't just match_token it - // and `match_uimm64` doesn't support leading '+' tokens, so we can't use that either. - match self.token() { - Some(Token::Integer(text)) if text.starts_with('+') => { - self.consume(); - - text[1..] - .parse() - .map_err(|_| self.error("expected u64 decimal immediate")) - } - token => err!( - self.loc, - format!("Unexpected token {:?} after vmctx", token) - ), - } - } - - /// Parse a CLIF heap command. - /// - /// heap-command ::= "heap" ":" heap-type { "," heap-attr } - /// heap-attr ::= "size" "=" UImm64(bytes) - fn parse_heap_command(&mut self) -> ParseResult { - self.match_token(Token::Identifier("heap"), "expected a 'heap:' command")?; - self.match_token(Token::Colon, "expected a ':' after heap command")?; - - let mut heap_command = HeapCommand { - heap_type: self.parse_heap_type()?, - size: Uimm64::new(0), - ptr_offset: None, - bound_offset: None, - }; - - while self.optional(Token::Comma) { - let identifier = self.match_any_identifier("expected heap attribute name")?; - self.match_token(Token::Equal, "expected '=' after heap attribute name")?; - - match identifier { - "size" => { - heap_command.size = self.match_uimm64("expected integer size")?; - } - "ptr" => { - heap_command.ptr_offset = Some(self.parse_vmctx_offset()?); - } - "bound" => { - heap_command.bound_offset = Some(self.parse_vmctx_offset()?); - } - t => return err!(self.loc, "unknown heap attribute '{}'", t), - } - } - - if heap_command.size == Uimm64::new(0) { - return err!(self.loc, self.error("Expected a heap size to be specified")); - } - - Ok(heap_command) - } - - /// Parse a heap type. - /// - /// heap-type ::= "static" | "dynamic" - fn parse_heap_type(&mut self) -> ParseResult { - match self.token() { - Some(Token::Identifier("static")) => { - self.consume(); - Ok(HeapType::Static) - } - Some(Token::Identifier("dynamic")) => { - self.consume(); - Ok(HeapType::Dynamic) - } - _ => Err(self.error("expected a heap type, e.g. static or dynamic")), - } - } - /// Parse a CLIF run command. /// /// run-command ::= "run" [":" invocation comparison expected] @@ -2484,14 +2300,14 @@ impl<'a> Parser<'a> { Ok(RunCommand::Run(invocation, comparison, expected)) } else if sig.params.is_empty() && sig.returns.len() == 1 - && sig.returns[0].value_type.is_bool() + && sig.returns[0].value_type.is_int() { // To match the existing run behavior that does not require an explicit - // invocation, we create an invocation from a function like `() -> b*` and - // compare it to `true`. + // invocation, we create an invocation from a function like `() -> i*` and + // require the result to be non-zero. let invocation = Invocation::new("default", vec![]); - let expected = vec![DataValue::B(true)]; - let comparison = Comparison::Equals; + let expected = vec![DataValue::I8(0)]; + let comparison = Comparison::NotEquals; Ok(RunCommand::Run(invocation, comparison, expected)) } else { Err(self.error("unable to parse the run command")) @@ -2632,9 +2448,6 @@ impl<'a> Parser<'a> { return Err(self.error("only 128-bit vectors are currently supported")); } } - _ if ty.is_bool() && !ty.is_vector() => { - DataValue::from(self.match_bool("expected a boolean")?) - } _ => return Err(self.error(&format!("don't know how to parse data values of: {}", ty))), }; Ok(dv) @@ -2665,10 +2478,6 @@ impl<'a> Parser<'a> { opcode, imm: self.match_ieee64("expected immediate 64-bit float operand")?, }, - InstructionFormat::UnaryBool => InstructionData::UnaryBool { - opcode, - imm: self.match_bool("expected immediate boolean operand")?, - }, InstructionFormat::UnaryConst => { let constant_handle = if let Some(Token::Constant(_)) = self.token() { // If handed a `const?`, use that. @@ -2749,62 +2558,30 @@ impl<'a> Parser<'a> { // Parse the destination block number. let block_num = self.match_block("expected jump destination block")?; let args = self.parse_opt_value_list()?; + let destination = ctx.function.dfg.block_call(block_num, &args); InstructionData::Jump { opcode, - destination: block_num, - args: args.into_value_list(&[], &mut ctx.function.dfg.value_lists), - } - } - InstructionFormat::Branch => { - let ctrl_arg = self.match_value("expected SSA value control operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let block_num = self.match_block("expected branch destination block")?; - let args = self.parse_opt_value_list()?; - InstructionData::Branch { - opcode, - destination: block_num, - args: args.into_value_list(&[ctrl_arg], &mut ctx.function.dfg.value_lists), - } - } - InstructionFormat::BranchInt => { - let cond = self.match_enum("expected intcc condition code")?; - let arg = self.match_value("expected SSA value first operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let block_num = self.match_block("expected branch destination block")?; - let args = self.parse_opt_value_list()?; - InstructionData::BranchInt { - opcode, - cond, - destination: block_num, - args: args.into_value_list(&[arg], &mut ctx.function.dfg.value_lists), - } - } - InstructionFormat::BranchFloat => { - let cond = self.match_enum("expected floatcc condition code")?; - let arg = self.match_value("expected SSA value first operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let block_num = self.match_block("expected branch destination block")?; - let args = self.parse_opt_value_list()?; - InstructionData::BranchFloat { - opcode, - cond, - destination: block_num, - args: args.into_value_list(&[arg], &mut ctx.function.dfg.value_lists), + destination, } } - InstructionFormat::BranchIcmp => { - let cond = self.match_enum("expected intcc condition code")?; - let lhs = self.match_value("expected SSA value first operand")?; + InstructionFormat::Brif => { + let arg = self.match_value("expected SSA value control operand")?; self.match_token(Token::Comma, "expected ',' between operands")?; - let rhs = self.match_value("expected SSA value second operand")?; + let block_then = { + let block_num = self.match_block("expected branch then block")?; + let args = self.parse_opt_value_list()?; + ctx.function.dfg.block_call(block_num, &args) + }; self.match_token(Token::Comma, "expected ',' between operands")?; - let block_num = self.match_block("expected branch destination block")?; - let args = self.parse_opt_value_list()?; - InstructionData::BranchIcmp { + let block_else = { + let block_num = self.match_block("expected branch else block")?; + let args = self.parse_opt_value_list()?; + ctx.function.dfg.block_call(block_num, &args) + }; + InstructionData::Brif { opcode, - cond, - destination: block_num, - args: args.into_value_list(&[lhs, rhs], &mut ctx.function.dfg.value_lists), + arg, + blocks: [block_then, block_else], } } InstructionFormat::BranchTable => { @@ -2812,14 +2589,9 @@ impl<'a> Parser<'a> { self.match_token(Token::Comma, "expected ',' between operands")?; let block_num = self.match_block("expected branch destination block")?; self.match_token(Token::Comma, "expected ',' between operands")?; - let table = self.match_jt()?; - ctx.check_jt(table, self.loc)?; - InstructionData::BranchTable { - opcode, - arg, - destination: block_num, - table, - } + let table_data = self.parse_jump_table(block_num)?; + let table = ctx.function.dfg.jump_tables.push(table_data); + InstructionData::BranchTable { opcode, arg, table } } InstructionFormat::TernaryImm8 => { let lhs = self.match_value("expected SSA value first operand")?; @@ -2869,11 +2641,6 @@ impl<'a> Parser<'a> { imm: rhs, } } - InstructionFormat::IntCond => { - let cond = self.match_enum("expected intcc condition code")?; - let arg = self.match_value("expected SSA value")?; - InstructionData::IntCond { opcode, cond, arg } - } InstructionFormat::FloatCompare => { let cond = self.match_enum("expected floatcc condition code")?; let lhs = self.match_value("expected SSA value first operand")?; @@ -2885,24 +2652,6 @@ impl<'a> Parser<'a> { args: [lhs, rhs], } } - InstructionFormat::FloatCond => { - let cond = self.match_enum("expected floatcc condition code")?; - let arg = self.match_value("expected SSA value")?; - InstructionData::FloatCond { opcode, cond, arg } - } - InstructionFormat::IntSelect => { - let cond = self.match_enum("expected intcc condition code")?; - let guard = self.match_value("expected SSA value first operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let v_true = self.match_value("expected SSA value second operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let v_false = self.match_value("expected SSA value third operand")?; - InstructionData::IntSelect { - opcode, - cond, - args: [guard, v_true, v_false], - } - } InstructionFormat::Call => { let func_ref = self.match_fn("expected function reference")?; ctx.check_fn(func_ref, self.loc)?; @@ -2976,20 +2725,6 @@ impl<'a> Parser<'a> { dynamic_stack_slot: dss, } } - InstructionFormat::HeapAddr => { - let heap = self.match_heap("expected heap identifier")?; - ctx.check_heap(heap, self.loc)?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let arg = self.match_value("expected SSA value heap address")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let imm = self.match_uimm32("expected 32-bit integer size")?; - InstructionData::HeapAddr { - opcode, - heap, - arg, - imm, - } - } InstructionFormat::TableAddr => { let table = self.match_table("expected table identifier")?; ctx.check_table(table, self.loc)?; @@ -3038,30 +2773,6 @@ impl<'a> Parser<'a> { let code = self.match_enum("expected trap code")?; InstructionData::CondTrap { opcode, arg, code } } - InstructionFormat::IntCondTrap => { - let cond = self.match_enum("expected intcc condition code")?; - let arg = self.match_value("expected SSA value operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let code = self.match_enum("expected trap code")?; - InstructionData::IntCondTrap { - opcode, - cond, - arg, - code, - } - } - InstructionFormat::FloatCondTrap => { - let cond = self.match_enum("expected floatcc condition code")?; - let arg = self.match_value("expected SSA value operand")?; - self.match_token(Token::Comma, "expected ',' between operands")?; - let code = self.match_enum("expected trap code")?; - InstructionData::FloatCondTrap { - opcode, - cond, - arg, - code, - } - } InstructionFormat::AtomicCas => { let flags = self.optional_memflags(); let addr = self.match_value("expected SSA value address")?; @@ -3108,6 +2819,18 @@ impl<'a> Parser<'a> { args: [arg, addr], } } + InstructionFormat::IntAddTrap => { + let a = self.match_value("expected SSA value operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let b = self.match_value("expected SSA value operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let code = self.match_enum("expected trap code")?; + InstructionData::IntAddTrap { + opcode, + args: [a, b], + code, + } + } }; Ok(idata) } @@ -3327,25 +3050,6 @@ mod tests { assert!(!is_warning); } - #[test] - fn duplicate_jt() { - let ParseError { - location, - message, - is_warning, - } = Parser::new( - "function %blocks() system_v { - jt0 = jump_table [] - jt0 = jump_table []", - ) - .parse_function() - .unwrap_err(); - - assert_eq!(location.line_number, 3); - assert_eq!(message, "duplicate entity: jt0"); - assert!(!is_warning); - } - #[test] fn duplicate_ss() { let ParseError { @@ -3384,25 +3088,6 @@ mod tests { assert!(!is_warning); } - #[test] - fn duplicate_heap() { - let ParseError { - location, - message, - is_warning, - } = Parser::new( - "function %blocks() system_v { - heap0 = static gv0, min 0x1000, bound 0x10_0000, offset_guard 0x1000 - heap0 = static gv0, min 0x1000, bound 0x10_0000, offset_guard 0x1000", - ) - .parse_function() - .unwrap_err(); - - assert_eq!(location.line_number, 3); - assert_eq!(message, "duplicate entity: heap0"); - assert!(!is_warning); - } - #[test] fn duplicate_sig() { let ParseError { @@ -3449,8 +3134,6 @@ mod tests { function %comment() system_v { ; decl ss10 = explicit_slot 13 ; stackslot. ; Still stackslot. - jt10 = jump_table [block0] - ; Jumptable block0: ; Basic block trap user42; Instruction } ; Trailing. @@ -3459,7 +3142,7 @@ mod tests { .parse_function() .unwrap(); assert_eq!(func.name.to_string(), "%comment"); - assert_eq!(comments.len(), 8); // no 'before' comment. + assert_eq!(comments.len(), 7); // no 'before' comment. assert_eq!( comments[0], Comment { @@ -3470,16 +3153,14 @@ mod tests { assert_eq!(comments[1].entity.to_string(), "ss10"); assert_eq!(comments[2].entity.to_string(), "ss10"); assert_eq!(comments[2].text, "; Still stackslot."); - assert_eq!(comments[3].entity.to_string(), "jt10"); - assert_eq!(comments[3].text, "; Jumptable"); - assert_eq!(comments[4].entity.to_string(), "block0"); - assert_eq!(comments[4].text, "; Basic block"); + assert_eq!(comments[3].entity.to_string(), "block0"); + assert_eq!(comments[3].text, "; Basic block"); - assert_eq!(comments[5].entity.to_string(), "inst0"); - assert_eq!(comments[5].text, "; Instruction"); + assert_eq!(comments[4].entity.to_string(), "inst0"); + assert_eq!(comments[4].text, "; Instruction"); + assert_eq!(comments[5].entity, AnyEntity::Function); assert_eq!(comments[6].entity, AnyEntity::Function); - assert_eq!(comments[7].entity, AnyEntity::Function); } #[test] @@ -3726,10 +3407,10 @@ mod tests { can_parse_as_constant_data!("1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16", I8X16); can_parse_as_constant_data!("0x1.1 0x2.2 0x3.3 0x4.4", F32X4); can_parse_as_constant_data!("0x0 0x1 0x2 0x3", I32X4); - can_parse_as_constant_data!("true false true false true false true false", B16X8); + can_parse_as_constant_data!("-1 0 -1 0 -1 0 -1 0", I16X8); can_parse_as_constant_data!("0 -1", I64X2); - can_parse_as_constant_data!("true false", B64X2); - can_parse_as_constant_data!("true true true true true", B32X4); // note that parse_literals_to_constant_data will leave extra tokens unconsumed + can_parse_as_constant_data!("-1 0", I64X2); + can_parse_as_constant_data!("-1 -1 -1 -1 -1", I32X4); // note that parse_literals_to_constant_data will leave extra tokens unconsumed cannot_parse_as_constant_data!("1 2 3", I32X4); cannot_parse_as_constant_data!(" ", F32X4); @@ -3737,8 +3418,8 @@ mod tests { #[test] fn parse_constant_from_booleans() { - let c = Parser::new("true false true false") - .parse_literals_to_constant_data(B32X4) + let c = Parser::new("-1 0 -1 0") + .parse_literals_to_constant_data(I32X4) .unwrap(); assert_eq!( c.into_vec(), @@ -3783,18 +3464,18 @@ mod tests { } assert_roundtrip("run: %fn0() == 42", &sig(&[], &[I32])); assert_roundtrip( - "run: %fn0(8, 16, 32, 64) == true", - &sig(&[I8, I16, I32, I64], &[B8]), + "run: %fn0(8, 16, 32, 64) == 1", + &sig(&[I8, I16, I32, I64], &[I8]), ); assert_roundtrip( - "run: %my_func(true) == 0x0f0e0d0c0b0a09080706050403020100", - &sig(&[B32], &[I8X16]), + "run: %my_func(1) == 0x0f0e0d0c0b0a09080706050403020100", + &sig(&[I32], &[I8X16]), ); // Verify that default invocations are created when not specified. assert_eq!( - parse("run", &sig(&[], &[B32])).unwrap().to_string(), - "run: %default() == true" + parse("run", &sig(&[], &[I32])).unwrap().to_string(), + "run: %default() != 0" ); assert_eq!( parse("print", &sig(&[], &[F32X4, I16X8])) @@ -3804,51 +3485,11 @@ mod tests { ); // Demonstrate some unparseable cases. - assert!(parse("print", &sig(&[I32], &[B32])).is_err()); - assert!(parse("run", &sig(&[], &[I32])).is_err()); + assert!(parse("print", &sig(&[I32], &[I32])).is_err()); assert!(parse("print:", &sig(&[], &[])).is_err()); assert!(parse("run: ", &sig(&[], &[])).is_err()); } - #[test] - fn parse_heap_commands() { - fn parse(text: &str) -> ParseResult { - Parser::new(text).parse_heap_command() - } - - // Check that we can parse and display the same set of heap commands. - fn assert_roundtrip(text: &str) { - assert_eq!(parse(text).unwrap().to_string(), text); - } - - assert_roundtrip("heap: static, size=10"); - assert_roundtrip("heap: dynamic, size=10"); - assert_roundtrip("heap: static, size=10, ptr=vmctx+10"); - assert_roundtrip("heap: static, size=10, bound=vmctx+11"); - assert_roundtrip("heap: static, size=10, ptr=vmctx+10, bound=vmctx+10"); - assert_roundtrip("heap: dynamic, size=10, ptr=vmctx+10"); - assert_roundtrip("heap: dynamic, size=10, bound=vmctx+11"); - assert_roundtrip("heap: dynamic, size=10, ptr=vmctx+10, bound=vmctx+10"); - - let static_heap = parse("heap: static, size=10, ptr=vmctx+8, bound=vmctx+2").unwrap(); - assert_eq!(static_heap.size, Uimm64::new(10)); - assert_eq!(static_heap.heap_type, HeapType::Static); - assert_eq!(static_heap.ptr_offset, Some(Uimm64::new(8))); - assert_eq!(static_heap.bound_offset, Some(Uimm64::new(2))); - let dynamic_heap = parse("heap: dynamic, size=0x10").unwrap(); - assert_eq!(dynamic_heap.size, Uimm64::new(16)); - assert_eq!(dynamic_heap.heap_type, HeapType::Dynamic); - assert_eq!(dynamic_heap.ptr_offset, None); - assert_eq!(dynamic_heap.bound_offset, None); - - assert!(parse("heap: static").is_err()); - assert!(parse("heap: dynamic").is_err()); - assert!(parse("heap: static size=0").is_err()); - assert!(parse("heap: dynamic size=0").is_err()); - assert!(parse("heap: static, size=10, ptr=10").is_err()); - assert!(parse("heap: static, size=10, bound=vmctx-10").is_err()); - } - #[test] fn parse_data_values() { fn parse(text: &str, ty: Type) -> DataValue { @@ -3866,8 +3507,6 @@ mod tests { assert_eq!(parse("1234567", I128).to_string(), "1234567"); assert_eq!(parse("0x32.32", F32).to_string(), "0x1.919000p5"); assert_eq!(parse("0x64.64", F64).to_string(), "0x1.9190000000000p6"); - assert_eq!(parse("true", B1).to_string(), "true"); - assert_eq!(parse("false", B64).to_string(), "false"); assert_eq!( parse("[0 1 2 3]", I32X4).to_string(), "0x00000003000000020000000100000000" diff --git a/cranelift/reader/src/run_command.rs b/cranelift/reader/src/run_command.rs index 99f57e6a036f..643ceaeee77b 100644 --- a/cranelift/reader/src/run_command.rs +++ b/cranelift/reader/src/run_command.rs @@ -39,10 +39,7 @@ impl RunCommand { } RunCommand::Run(invoke, compare, expected) => { let actual = invoke_fn(&invoke.func, &invoke.args)?; - let matched = match compare { - Comparison::Equals => *expected == actual, - Comparison::NotEquals => *expected != actual, - }; + let matched = Self::compare_results(compare, &actual, expected); if !matched { let actual = DisplayDataValues(&actual); return Err(format!("Failed test: {}, actual: {}", self, actual)); @@ -51,6 +48,23 @@ impl RunCommand { } Ok(()) } + + fn compare_results( + compare: &Comparison, + actual: &Vec, + expected: &Vec, + ) -> bool { + let are_equal = actual.len() == expected.len() + && actual + .into_iter() + .zip(expected.into_iter()) + .all(|(a, b)| a.bitwise_eq(b)); + + match compare { + Comparison::Equals => are_equal, + Comparison::NotEquals => !are_equal, + } + } } impl Display for RunCommand { diff --git a/cranelift/reader/src/sourcemap.rs b/cranelift/reader/src/sourcemap.rs index 00425dc5863d..d8c21ebb10b5 100644 --- a/cranelift/reader/src/sourcemap.rs +++ b/cranelift/reader/src/sourcemap.rs @@ -10,8 +10,8 @@ use crate::error::{Location, ParseResult}; use crate::lexer::split_entity_name; use cranelift_codegen::ir::entities::{AnyEntity, DynamicType}; use cranelift_codegen::ir::{ - Block, Constant, DynamicStackSlot, FuncRef, GlobalValue, Heap, JumpTable, SigRef, StackSlot, - Table, Value, + Block, Constant, DynamicStackSlot, FuncRef, GlobalValue, JumpTable, SigRef, StackSlot, Table, + Value, }; use std::collections::HashMap; @@ -49,11 +49,6 @@ impl SourceMap { self.locations.contains_key(&gv.into()) } - /// Look up a heap entity. - pub fn contains_heap(&self, heap: Heap) -> bool { - self.locations.contains_key(&heap.into()) - } - /// Look up a table entity. pub fn contains_table(&self, table: Table) -> bool { self.locations.contains_key(&table.into()) @@ -111,13 +106,6 @@ impl SourceMap { Some(gv.into()) } }), - "heap" => Heap::with_number(num).and_then(|heap| { - if !self.contains_heap(heap) { - None - } else { - Some(heap.into()) - } - }), "table" => Table::with_number(num).and_then(|table| { if !self.contains_table(table) { None @@ -194,11 +182,6 @@ impl SourceMap { self.def_entity(entity.into(), loc) } - /// Define the heap `entity`. - pub fn def_heap(&mut self, entity: Heap, loc: Location) -> ParseResult<()> { - self.def_entity(entity.into(), loc) - } - /// Define the table `entity`. pub fn def_table(&mut self, entity: Table, loc: Location) -> ParseResult<()> { self.def_entity(entity.into(), loc) @@ -244,7 +227,6 @@ mod tests { let tf = parse_test( "function %detail() { ss10 = explicit_slot 13 - jt10 = jump_table [block0] block0(v4: i32, v7: i32): v10 = iadd v4, v7 }", @@ -256,7 +238,6 @@ mod tests { assert_eq!(map.lookup_str("v0"), None); assert_eq!(map.lookup_str("ss1"), None); assert_eq!(map.lookup_str("ss10").unwrap().to_string(), "ss10"); - assert_eq!(map.lookup_str("jt10").unwrap().to_string(), "jt10"); assert_eq!(map.lookup_str("block0").unwrap().to_string(), "block0"); assert_eq!(map.lookup_str("v4").unwrap().to_string(), "v4"); assert_eq!(map.lookup_str("v7").unwrap().to_string(), "v7"); diff --git a/cranelift/serde/Cargo.toml b/cranelift/serde/Cargo.toml index 6839bbe71a1b..9b13f8a23731 100644 --- a/cranelift/serde/Cargo.toml +++ b/cranelift/serde/Cargo.toml @@ -1,23 +1,23 @@ [package] name = "cranelift-serde" -version = "0.88.0" +version = "0.94.0" authors = ["The Cranelift Project Developers"] description = "Serializer/Deserializer for Cranelift IR" repository = "https://github.com/bytecodealliance/wasmtime" license = "Apache-2.0 WITH LLVM-exception" readme = "README.md" keywords = ["webassembly", "serde"] -edition = "2021" +edition.workspace = true [[bin]] name = "clif-json" path = "src/clif-json.rs" [dependencies] -clap = { version = "3.2.0", features = ["derive"] } +clap = { workspace = true } serde_json = "1.0.26" -cranelift-codegen = { path = "../codegen", version = "0.88.0", features = ["enable-serde"] } -cranelift-reader = { path = "../reader", version = "0.88.0" } +cranelift-codegen = { workspace = true, features = ["enable-serde"] } +cranelift-reader = { workspace = true } [badges] maintenance = { status = "experimental" } diff --git a/cranelift/src/bugpoint.rs b/cranelift/src/bugpoint.rs index dcc48245f2ea..5713a6e8a6b8 100644 --- a/cranelift/src/bugpoint.rs +++ b/cranelift/src/bugpoint.rs @@ -1,11 +1,12 @@ //! 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; use cranelift_codegen::cursor::{Cursor, FuncCursor}; use cranelift_codegen::flowgraph::ControlFlowGraph; -use cranelift_codegen::ir::types::{F32, F64}; +use cranelift_codegen::ir::types::{F32, F64, I128, I64}; use cranelift_codegen::ir::{ self, Block, FuncRef, Function, GlobalValueData, Inst, InstBuilder, InstructionData, StackSlots, TrapCode, @@ -13,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; @@ -173,7 +174,7 @@ impl Mutator for ReplaceInstWithConst { |(_prev_block, prev_inst)| { let num_results = func.dfg.inst_results(prev_inst).len(); - let opcode = func.dfg[prev_inst].opcode(); + let opcode = func.dfg.insts[prev_inst].opcode(); if num_results == 0 || opcode == ir::Opcode::Iconst || opcode == ir::Opcode::F32const @@ -182,14 +183,22 @@ impl Mutator for ReplaceInstWithConst { return (func, format!(""), ProgressStatus::Skip); } - if num_results == 1 { - let ty = func.dfg.value_type(func.dfg.first_result(prev_inst)); - let new_inst_name = const_for_type(func.dfg.replace(prev_inst), ty); - return ( - func, - format!("Replace inst {} with {}.", prev_inst, new_inst_name), - ProgressStatus::Changed, - ); + // We replace a i128 const with a uextend+iconst, so we need to match that here + // to avoid processing those multiple times + if opcode == ir::Opcode::Uextend { + let ret_ty = func.dfg.value_type(func.dfg.first_result(prev_inst)); + let is_uextend_i128 = ret_ty == I128; + + let arg = func.dfg.inst_args(prev_inst)[0]; + let arg_def = func.dfg.value_def(arg); + let arg_is_iconst = arg_def + .inst() + .map(|inst| func.dfg.insts[inst].opcode() == ir::Opcode::Iconst) + .unwrap_or(false); + + if is_uextend_i128 && arg_is_iconst { + return (func, format!(""), ProgressStatus::Skip); + } } // At least 2 results. Replace each instruction with as many const instructions as @@ -204,20 +213,24 @@ impl Mutator for ReplaceInstWithConst { pos.func.dfg.clear_results(prev_inst); let mut inst_names = Vec::new(); - for r in results { - let ty = pos.func.dfg.value_type(r); - let builder = pos.ins().with_results([Some(r)]); - let new_inst_name = const_for_type(builder, ty); + for r in &results { + let new_inst_name = replace_with_const(&mut pos, *r); inst_names.push(new_inst_name); } // Remove the instruction. assert_eq!(pos.remove_inst(), prev_inst); + let progress = if results.len() == 1 { + ProgressStatus::Changed + } else { + ProgressStatus::ExpandedOrShrinked + }; + ( func, format!("Replace inst {} with {}", prev_inst, inst_names.join(" / ")), - ProgressStatus::ExpandedOrShrinked, + progress, ) }, ) @@ -253,7 +266,7 @@ impl Mutator for ReplaceInstWithTrap { fn mutate(&mut self, mut func: Function) -> Option<(Function, String, ProgressStatus)> { next_inst_ret_prev(&func, &mut self.block, &mut self.inst).map( |(_prev_block, prev_inst)| { - let status = if func.dfg[prev_inst].opcode() == ir::Opcode::Trap { + let status = if func.dfg.insts[prev_inst].opcode() == ir::Opcode::Trap { ProgressStatus::Skip } else { func.dfg.replace(prev_inst).trap(TrapCode::User(0)); @@ -397,24 +410,23 @@ impl Mutator for ReplaceBlockParamWithConst { let param_index = self.params_remaining; let param = func.dfg.block_params(self.block)[param_index]; - let param_type = func.dfg.value_type(param); func.dfg.remove_block_param(param); let first_inst = func.layout.first_inst(self.block).unwrap(); let mut pos = FuncCursor::new(&mut func).at_inst(first_inst); - let builder = pos.ins().with_results([Some(param)]); - let new_inst_name = const_for_type(builder, param_type); + let new_inst_name = replace_with_const(&mut pos, param); let mut cfg = ControlFlowGraph::new(); cfg.compute(&func); // Remove parameters in branching instructions that point to this block for pred in cfg.pred_iter(self.block) { - let inst = &mut func.dfg[pred.inst]; - let num_fixed_args = inst.opcode().constraints().num_fixed_value_arguments(); - let mut values = inst.take_value_list().unwrap(); - values.remove(num_fixed_args + param_index, &mut func.dfg.value_lists); - func.dfg[pred.inst].put_value_list(values); + let dfg = &mut func.dfg; + for branch in dfg.insts[pred.inst].branch_destination_mut().into_iter() { + if branch.block(&dfg.value_lists) == self.block { + branch.remove(param_index, &mut dfg.value_lists); + } + } } if Some(self.block) == func.layout.entry_block() { @@ -460,7 +472,7 @@ impl Mutator for RemoveUnusedEntities { let mut ext_func_usage_map = HashMap::new(); for block in func.layout.blocks() { for inst in func.layout.block_insts(block) { - match func.dfg[inst] { + match func.dfg.insts[inst] { // Add new cases when there are new instruction formats taking a `FuncRef`. InstructionData::Call { func_ref, .. } | InstructionData::FuncAddr { func_ref, .. } => { @@ -480,7 +492,7 @@ impl Mutator for RemoveUnusedEntities { if let Some(func_ref_usage) = ext_func_usage_map.get(&func_ref) { let new_func_ref = ext_funcs.push(ext_func_data.clone()); for &inst in func_ref_usage { - match func.dfg[inst] { + match func.dfg.insts[inst] { // Keep in sync with the above match. InstructionData::Call { ref mut func_ref, .. @@ -511,7 +523,8 @@ impl Mutator for RemoveUnusedEntities { for block in func.layout.blocks() { for inst in func.layout.block_insts(block) { // Add new cases when there are new instruction formats taking a `SigRef`. - if let InstructionData::CallIndirect { sig_ref, .. } = func.dfg[inst] { + if let InstructionData::CallIndirect { sig_ref, .. } = func.dfg.insts[inst] + { signatures_usage_map .entry(sig_ref) .or_insert_with(Vec::new) @@ -533,7 +546,7 @@ impl Mutator for RemoveUnusedEntities { let new_sig_ref = signatures.push(sig_data.clone()); for &sig_ref_user in sig_ref_usage { match sig_ref_user { - SigRefUser::Instruction(inst) => match func.dfg[inst] { + SigRefUser::Instruction(inst) => match func.dfg.insts[inst] { // Keep in sync with the above match. InstructionData::CallIndirect { ref mut sig_ref, .. @@ -558,7 +571,7 @@ impl Mutator for RemoveUnusedEntities { let mut stack_slot_usage_map = HashMap::new(); for block in func.layout.blocks() { for inst in func.layout.block_insts(block) { - match func.dfg[inst] { + match func.dfg.insts[inst] { // Add new cases when there are new instruction formats taking a `StackSlot`. InstructionData::StackLoad { stack_slot, .. } | InstructionData::StackStore { stack_slot, .. } => { @@ -579,7 +592,7 @@ impl Mutator for RemoveUnusedEntities { if let Some(stack_slot_usage) = stack_slot_usage_map.get(&stack_slot) { let new_stack_slot = stack_slots.push(stack_slot_data.clone()); for &inst in stack_slot_usage { - match &mut func.dfg[inst] { + match &mut func.dfg.insts[inst] { // Keep in sync with the above match. InstructionData::StackLoad { stack_slot, .. } | InstructionData::StackStore { stack_slot, .. } => { @@ -601,7 +614,7 @@ impl Mutator for RemoveUnusedEntities { for inst in func.layout.block_insts(block) { // Add new cases when there are new instruction formats taking a `GlobalValue`. if let InstructionData::UnaryGlobalValue { global_value, .. } = - func.dfg[inst] + func.dfg.insts[inst] { global_value_usage_map .entry(global_value) @@ -629,7 +642,7 @@ impl Mutator for RemoveUnusedEntities { if let Some(global_value_usage) = global_value_usage_map.get(&global_value) { let new_global_value = global_values.push(global_value_data.clone()); for &inst in global_value_usage { - match &mut func.dfg[inst] { + match &mut func.dfg.insts[inst] { // Keep in sync with the above match. InstructionData::UnaryGlobalValue { global_value, .. } => { *global_value = new_global_value; @@ -696,32 +709,31 @@ impl Mutator for MergeBlocks { let pred = cfg.pred_iter(block).next().unwrap(); - // If the branch instruction that lead us to this block is preceded by another branch - // instruction, then we have a conditional jump sequence that we should not break by - // replacing the second instruction by more of them. - if let Some(pred_pred_inst) = func.layout.prev_inst(pred.inst) { - if func.dfg[pred_pred_inst].opcode().is_branch() { - return Some(( - func, - format!("did nothing for {}", block), - ProgressStatus::Skip, - )); - } + // If the branch instruction that lead us to this block wasn't an unconditional jump, then + // we have a conditional jump sequence that we should not break. + let branch_dests = func.dfg.insts[pred.inst].branch_destination(); + if branch_dests.len() != 1 { + return Some(( + func, + format!("did nothing for {}", block), + ProgressStatus::Skip, + )); } - assert!(func.dfg.block_params(block).len() == func.dfg.inst_variable_args(pred.inst).len()); + let branch_args = branch_dests[0].args_slice(&func.dfg.value_lists).to_vec(); - // If there were any block parameters in block, then the last instruction in pred will - // fill these parameters. Make the block params aliases of the terminator arguments. - for (block_param, arg) in func + // TODO: should we free the entity list associated with the block params? + let block_params = func .dfg .detach_block_params(block) .as_slice(&func.dfg.value_lists) - .iter() - .cloned() - .zip(func.dfg.inst_variable_args(pred.inst).iter().cloned()) - .collect::>() - { + .to_vec(); + + assert_eq!(block_params.len(), branch_args.len()); + + // If there were any block parameters in block, then the last instruction in pred will + // fill these parameters. Make the block params aliases of the terminator arguments. + for (block_param, arg) in block_params.into_iter().zip(branch_args) { if block_param != arg { func.dfg.change_to_alias(block_param, arg); } @@ -755,27 +767,29 @@ impl Mutator for MergeBlocks { } } -fn const_for_type<'f, T: InstBuilder<'f>>(mut builder: T, ty: ir::Type) -> &'static str { +fn replace_with_const(pos: &mut FuncCursor, param: Value) -> &'static str { + let ty = pos.func.dfg.value_type(param); if ty == F32 { - builder.f32const(0.0); + pos.ins().with_result(param).f32const(0.0); "f32const" } else if ty == F64 { - builder.f64const(0.0); + pos.ins().with_result(param).f64const(0.0); "f64const" - } else if ty.is_bool() { - builder.bconst(ty, false); - "bconst" } else if ty.is_ref() { - builder.null(ty); + pos.ins().with_result(param).null(ty); "null" } else if ty.is_vector() { let zero_data = vec![0; ty.bytes() as usize].into(); - let zero_handle = builder.data_flow_graph_mut().constants.insert(zero_data); - builder.vconst(ty, zero_handle); + let zero_handle = pos.func.dfg.constants.insert(zero_data); + pos.ins().with_result(param).vconst(ty, zero_handle); "vconst" + } else if ty == I128 { + let res = pos.ins().iconst(I64, 0); + pos.ins().with_result(param).uextend(I128, res); + "iconst+uextend" } else { // Default to an integer type and possibly create verifier error - builder.iconst(ty, 0); + pos.ins().with_result(param).iconst(ty, 0); "iconst" } } @@ -810,9 +824,9 @@ fn inst_count(func: &Function) -> usize { } fn resolve_aliases(func: &mut Function) { - for block in func.layout.blocks() { - for inst in func.layout.block_insts(block) { - func.dfg.resolve_aliases_in_arguments(inst); + for block in func.stencil.layout.blocks() { + for inst in func.stencil.layout.block_insts(block) { + func.stencil.dfg.resolve_aliases_in_arguments(inst); } } } @@ -1011,7 +1025,7 @@ impl<'a> CrashCheckContext<'a> { let contains_call = func.layout.blocks().any(|block| { func.layout .block_insts(block) - .any(|inst| match func.dfg[inst] { + .any(|inst| match func.dfg.insts[inst] { InstructionData::Call { .. } => true, _ => false, }) @@ -1073,9 +1087,13 @@ mod tests { "reduction wasn't maximal for insts" ); - assert_eq!( - format!("{}", reduced_func), - expected_str.replace("\r\n", "\n") + let actual_ir = format!("{}", reduced_func); + let expected_ir = expected_str.replace("\r\n", "\n"); + assert!( + expected_ir == actual_ir, + "Expected:\n{}\nGot:\n{}", + expected_ir, + actual_ir, ); } } diff --git a/cranelift/src/clif-util.rs b/cranelift/src/clif-util.rs old mode 100755 new mode 100644 diff --git a/cranelift/src/compile.rs b/cranelift/src/compile.rs index af03d8a4fe65..be9315c8d049 100644 --- a/cranelift/src/compile.rs +++ b/cranelift/src/compile.rs @@ -1,14 +1,15 @@ //! 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::OwnedFlagsOrIsa; +use cranelift_reader::{parse_sets_and_triple, parse_test, ParseOptions}; use std::path::Path; use std::path::PathBuf; @@ -37,18 +38,50 @@ pub struct Options { /// Specify an input file to be used. Use '-' for stdin. files: Vec, + + /// Output object file + #[clap(short = 'o', long = "output")] + output: Option, } pub fn run(options: &Options) -> Result<()> { let parsed = parse_sets_and_triple(&options.settings, &options.target)?; + + let mut module = match (&options.output, &parsed) { + (Some(output), OwnedFlagsOrIsa::Isa(isa)) => { + let builder = cranelift_object::ObjectBuilder::new( + isa.clone(), + output + .file_name() + .and_then(|s| s.to_str()) + .unwrap_or("a.out"), + cranelift_module::default_libcall_names(), + )?; + Some(cranelift_object::ObjectModule::new(builder)) + } + _ => None, + }; + for path in &options.files { let name = String::from(path.as_os_str().to_string_lossy()); - handle_module(options, path, &name, parsed.as_fisa())?; + handle_module(options, path, &name, parsed.as_fisa(), module.as_mut())?; + } + + if let (Some(module), Some(output)) = (module, &options.output) { + let bytes = module.finish().emit()?; + std::fs::write(output, bytes)?; } + Ok(()) } -fn handle_module(options: &Options, path: &Path, name: &str, fisa: FlagsOrIsa) -> Result<()> { +fn handle_module( + options: &Options, + path: &Path, + name: &str, + fisa: FlagsOrIsa, + module: Option<&mut impl cranelift_module::Module>, +) -> Result<()> { let buffer = read_to_string(&path)?; let test_file = parse_test(&buffer, ParseOptions::default()) .with_context(|| format!("failed to parse {}", name))?; @@ -57,38 +90,48 @@ fn handle_module(options: &Options, path: &Path, name: &str, fisa: FlagsOrIsa) - // file contains a unique isa, use that. let isa = fisa.isa.or(test_file.isa_spec.unique_isa()); - if isa.is_none() { - anyhow::bail!("compilation requires a target isa"); + let isa = match isa { + None => anyhow::bail!("compilation requires a target isa"), + Some(isa) => isa, }; for (func, _) in test_file.functions { - if let Some(isa) = isa { - let mut context = Context::new(); - context.func = func; - let mut mem = vec![]; - - // Compile and encode the result to machine code. - let compiled_code = context - .compile_and_emit(isa, &mut mem) - .map_err(|err| anyhow::anyhow!("{}", pretty_error(&err.func, err.inner)))?; - let code_info = compiled_code.code_info(); - - if options.print { - println!("{}", context.func.display()); - } - - if options.disasm { - let result = context.compiled_code().unwrap(); - print_all( - isa, - &mem, - code_info.total_size, - options.print, - result.buffer.relocs(), - result.buffer.traps(), - result.buffer.stack_maps(), - )?; - } + let mut context = Context::new(); + context.func = func; + let mut mem = vec![]; + + // Compile and encode the result to machine code. + let compiled_code = context + .compile_and_emit(isa, &mut mem) + .map_err(|err| anyhow::anyhow!("{}", pretty_error(&err.func, err.inner)))?; + let code_info = compiled_code.code_info(); + + if let Some(&mut ref mut module) = module { + let name = context.func.name.to_string(); + let fid = module.declare_function( + &name, + cranelift_module::Linkage::Export, + &context.func.signature, + )?; + module.define_function(fid, &mut context)?; + } + + if options.print { + println!("{}", context.func.display()); + } + + if options.disasm { + let result = context.compiled_code().unwrap(); + print_all( + isa, + &context.func.params, + &mem, + code_info.total_size, + options.print, + result.buffer.relocs(), + result.buffer.traps(), + result.buffer.stack_maps(), + )?; } } diff --git a/cranelift/src/disasm.rs b/cranelift/src/disasm.rs index c372707b4a9c..1739f526b8c5 100644 --- a/cranelift/src/disasm.rs +++ b/cranelift/src/disasm.rs @@ -1,10 +1,11 @@ use anyhow::Result; use cfg_if::cfg_if; +use cranelift_codegen::ir::function::FunctionParameters; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::{MachReloc, MachStackMap, MachTrap}; use std::fmt::Write; -pub fn print_relocs(relocs: &[MachReloc]) -> String { +fn print_relocs(func_params: &FunctionParameters, relocs: &[MachReloc]) -> String { let mut text = String::new(); for &MachReloc { kind, @@ -16,7 +17,10 @@ pub fn print_relocs(relocs: &[MachReloc]) -> String { writeln!( text, "reloc_external: {} {} {} at {}", - kind, name, addend, offset + kind, + name.display(Some(func_params)), + addend, + offset ) .unwrap(); } @@ -65,63 +69,8 @@ pub fn print_stack_maps(traps: &[MachStackMap]) -> String { cfg_if! { if #[cfg(feature = "disas")] { - use capstone::prelude::*; - use target_lexicon::Architecture; - - fn get_disassembler(isa: &dyn TargetIsa) -> Result { - let cs = match isa.triple().architecture { - Architecture::X86_32(_) => Capstone::new() - .x86() - .mode(arch::x86::ArchMode::Mode32) - .build() - .map_err(map_caperr)?, - Architecture::X86_64 => Capstone::new() - .x86() - .mode(arch::x86::ArchMode::Mode64) - .build() - .map_err(map_caperr)?, - Architecture::Arm(arm) => { - if arm.is_thumb() { - Capstone::new() - .arm() - .mode(arch::arm::ArchMode::Thumb) - .build() - .map_err(map_caperr)? - } else { - Capstone::new() - .arm() - .mode(arch::arm::ArchMode::Arm) - .build() - .map_err(map_caperr)? - } - } - Architecture::Aarch64 {..} => { - let mut cs = Capstone::new() - .arm64() - .mode(arch::arm64::ArchMode::Arm) - .build() - .map_err(map_caperr)?; - // AArch64 uses inline constants rather than a separate constant pool right now. - // Without this option, Capstone will stop disassembling as soon as it sees - // an inline constant that is not also a valid instruction. With this option, - // Capstone will print a `.byte` directive with the bytes of the inline constant - // and continue to the next instruction. - cs.set_skipdata(true).map_err(map_caperr)?; - cs - } - Architecture::S390x {..} => Capstone::new() - .sysz() - .mode(arch::sysz::ArchMode::Default) - .build() - .map_err(map_caperr)?, - _ => anyhow::bail!("Unknown ISA"), - }; - - Ok(cs) - } - pub fn print_disassembly(isa: &dyn TargetIsa, mem: &[u8]) -> Result<()> { - let cs = get_disassembler(isa)?; + let cs = isa.to_capstone().map_err(|e| anyhow::format_err!("{}", e))?; println!("\nDisassembly of {} bytes:", mem.len()); let insns = cs.disasm_all(&mem, 0x0).unwrap(); @@ -158,10 +107,6 @@ cfg_if! { } Ok(()) } - - fn map_caperr(err: capstone::Error) -> anyhow::Error{ - anyhow::format_err!("{}", err) - } } else { pub fn print_disassembly(_: &dyn TargetIsa, _: &[u8]) -> Result<()> { println!("\nNo disassembly available."); @@ -172,6 +117,7 @@ cfg_if! { pub fn print_all( isa: &dyn TargetIsa, + func_params: &FunctionParameters, mem: &[u8], code_size: u32, print: bool, @@ -184,7 +130,7 @@ pub fn print_all( if print { println!( "\n{}\n{}\n{}", - print_relocs(relocs), + print_relocs(func_params, relocs), print_traps(traps), print_stack_maps(stack_maps) ); diff --git a/cranelift/src/interpret.rs b/cranelift/src/interpret.rs index e2d49db5f1a1..a752d692dac9 100644 --- a/cranelift/src/interpret.rs +++ b/cranelift/src/interpret.rs @@ -156,14 +156,14 @@ mod test { fn nop() { let code = String::from( " - function %test() -> b8 { + function %test() -> i8 { block0: nop - v1 = bconst.b8 true + v1 = iconst.i8 -1 v2 = iconst.i8 42 return v1 } - ; run: %test() == true + ; run: %test() == -1 ", ); FileInterpreter::from_inline_code(code).run().unwrap() diff --git a/cranelift/src/run.rs b/cranelift/src/run.rs index 089d382c901b..5564736e3b68 100644 --- a/cranelift/src/run.rs +++ b/cranelift/src/run.rs @@ -3,8 +3,8 @@ use crate::utils::{iterate_files, read_to_string}; use anyhow::Result; use clap::Parser; -use cranelift_codegen::isa::{CallConv, TargetIsa}; -use cranelift_filetests::SingleFunctionCompiler; +use cranelift_codegen::isa::{CallConv, OwnedTargetIsa}; +use cranelift_filetests::TestFileCompiler; use cranelift_native::builder as host_isa_builder; use cranelift_reader::{parse_run_command, parse_test, Details, IsaSpec, ParseOptions}; use std::path::{Path, PathBuf}; @@ -86,13 +86,17 @@ fn run_file_contents(file_contents: String) -> Result<()> { }; let test_file = parse_test(&file_contents, options)?; let isa = create_target_isa(&test_file.isa_spec)?; - let mut compiler = SingleFunctionCompiler::new(isa); + let mut tfc = TestFileCompiler::new(isa); + tfc.add_testfile(&test_file)?; + let compiled = tfc.compile()?; + for (func, Details { comments, .. }) in test_file.functions { for comment in comments { if let Some(command) = parse_run_command(comment.text, &func.signature)? { - let compiled_fn = compiler.compile(func.clone())?; + let trampoline = compiled.get_trampoline(&func).unwrap(); + command - .run(|_, args| Ok(compiled_fn.call(args))) + .run(|_, args| Ok(trampoline.call(args))) .map_err(|s| anyhow::anyhow!("{}", s))?; } } @@ -101,7 +105,7 @@ fn run_file_contents(file_contents: String) -> Result<()> { } /// Build an ISA based on the current machine running this code (the host) -fn create_target_isa(isa_spec: &IsaSpec) -> Result> { +fn create_target_isa(isa_spec: &IsaSpec) -> Result { if let IsaSpec::None(flags) = isa_spec { // build an ISA for the current machine let builder = host_isa_builder().map_err(|s| anyhow::anyhow!("{}", s))?; @@ -122,10 +126,10 @@ mod test { fn nop() { let code = String::from( " - function %test() -> b8 { + function %test() -> i8 { block0: nop - v1 = bconst.b8 true + v1 = iconst.i8 -1 return v1 } ; run diff --git a/cranelift/src/souper_harvest.rs b/cranelift/src/souper_harvest.rs index 4aa7567f0657..cd8d1b9ebe6d 100644 --- a/cranelift/src/souper_harvest.rs +++ b/cranelift/src/souper_harvest.rs @@ -1,9 +1,11 @@ -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::collections::HashSet; +use std::io::Write; use std::path::{Path, PathBuf}; use std::{fs, io}; @@ -18,9 +20,10 @@ pub struct Options { /// Specify an input file to be used. Use '-' for stdin. input: PathBuf, - /// Specify the output file to be used. Use '-' for stdout. - #[clap(short, long, default_value("-"))] - output: PathBuf, + /// Specify the directory where harvested left-hand side files should be + /// written to. + #[clap(short, long)] + output_dir: PathBuf, /// Configure Cranelift settings #[clap(long = "set")] @@ -29,6 +32,12 @@ pub struct Options { /// Specify the Cranelift target #[clap(long = "target")] target: String, + + /// Add a comment from which CLIF variable and function each left-hand side + /// was harvested from. This prevents deduplicating harvested left-hand + /// sides. + #[clap(long)] + add_harvest_source: bool, } pub fn run(options: &Options) -> Result<()> { @@ -47,13 +56,25 @@ pub fn run(options: &Options) -> Result<()> { )) }; - let mut output: Box = if options.output == Path::new("-") { - Box::new(io::stdout()) - } else { - Box::new(io::BufWriter::new( - fs::File::create(&options.output).context("failed to create output file")?, - )) - }; + match std::fs::create_dir_all(&options.output_dir) { + Ok(_) => {} + Err(e) + if e.kind() == io::ErrorKind::AlreadyExists + && fs::metadata(&options.output_dir) + .with_context(|| { + format!( + "failed to read file metadata: {}", + options.output_dir.display(), + ) + })? + .is_dir() => {} + Err(e) => { + return Err(e).context(format!( + "failed to create output directory: {}", + options.output_dir.display() + )) + } + } let mut contents = vec![]; input @@ -77,13 +98,33 @@ pub fn run(options: &Options) -> Result<()> { let (send, recv) = std::sync::mpsc::channel::(); - let writing_thread = std::thread::spawn(move || -> Result<()> { - for lhs in recv { - output - .write_all(lhs.as_bytes()) - .context("failed to write to output file")?; + let writing_thread = std::thread::spawn({ + let output_dir = options.output_dir.clone(); + let keep_harvest_source = options.add_harvest_source; + move || -> Result<()> { + let mut already_harvested = HashSet::new(); + for lhs in recv { + let lhs = if keep_harvest_source { + &lhs + } else { + // Remove the first `;; Harvested from v12 in u:34` line. + let i = lhs.find('\n').unwrap(); + &lhs[i + 1..] + }; + let hash = fxhash::hash(lhs.as_bytes()); + if already_harvested.insert(hash) { + let output_path = output_dir.join(hash.to_string()); + let mut output = + io::BufWriter::new(fs::File::create(&output_path).with_context(|| { + format!("failed to create file: {}", output_path.display()) + })?); + output.write_all(lhs.as_bytes()).with_context(|| { + format!("failed to write to output file: {}", output_path.display()) + })?; + } + } + Ok(()) } - Ok(()) }); funcs @@ -92,9 +133,8 @@ pub fn run(options: &Options) -> Result<()> { let mut ctx = Context::new(); ctx.func = func; - ctx.compute_cfg(); - ctx.preopt(fisa.isa.unwrap()) - .context("failed to run preopt")?; + ctx.optimize(fisa.isa.unwrap()) + .context("failed to run optimizations")?; ctx.souper_harvest(send) .context("failed to run souper harvester")?; diff --git a/cranelift/src/utils.rs b/cranelift/src/utils.rs index 5ba65f5bac0f..b1645534543a 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 f526e0c8a215..7456b0554ee8 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; @@ -313,6 +313,7 @@ fn handle_module(options: &Options, path: &Path, name: &str, fisa: FlagsOrIsa) - if let Some(total_size) = saved_size { print_all( isa, + &context.func.params, &mem, total_size, options.print, diff --git a/cranelift/tests/bugpoint_consts.clif b/cranelift/tests/bugpoint_consts.clif index e136c7982ca7..449b53ebbe9b 100644 --- a/cranelift/tests/bugpoint_consts.clif +++ b/cranelift/tests/bugpoint_consts.clif @@ -2,13 +2,13 @@ test compile target x86_64 function u0:0() { - sig0 = (f32, f64, i8, i16, i32, i64, i128, b1, b8, b128, r32, r64, b8x16, i16x4, f32x16) + sig0 = (f32, f64, i8, i16, i32, i64, i128, i8, i8, i128, r32, r64, i8x16, i16x4, f32x16) fn0 = u0:1 sig0 block0: trap user0 -block1(v0: f32, v1: f64, v2: i8, v3: i16, v4: i32, v5: i64, v6: i128, v7: b1, v8: b8, v9: b128, v10: r32, v11: r64, v12: b8x16, v13: i16x4, v14: f32x16): +block1(v0: f32, v1: f64, v2: i8, v3: i16, v4: i32, v5: i64, v6: i128, v7: i8, v8: i8, v9: i128, v10: r32, v11: r64, v12: i8x16, v13: i16x4, v14: f32x16): call fn0(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) trap user0 } diff --git a/cranelift/tests/bugpoint_consts_expected.clif b/cranelift/tests/bugpoint_consts_expected.clif index e4de6b9b9586..c344f484f6e4 100644 --- a/cranelift/tests/bugpoint_consts_expected.clif +++ b/cranelift/tests/bugpoint_consts_expected.clif @@ -1,5 +1,5 @@ function u0:0() fast { - sig0 = (f32, f64, i8, i16, i32, i64, i128, b1, b8, b128, r32, r64, b8x16, i16x4, f32x16) fast + sig0 = (f32, f64, i8, i16, i32, i64, i128, i8, i8, i128, r32, r64, i8x16, i16x4, f32x16) fast fn0 = u0:1 sig0 const0 = 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 const1 = 0x0000000000000000 @@ -12,15 +12,17 @@ block1: v3 = iconst.i16 0 v4 = iconst.i32 0 v5 = iconst.i64 0 - v6 = iconst.i128 0 - v7 = bconst.b1 false - v8 = bconst.b8 false - v9 = bconst.b128 false + v16 = iconst.i64 0 + v6 = uextend.i128 v16 ; v16 = 0 + v7 = iconst.i8 0 + v8 = iconst.i8 0 + v15 = iconst.i64 0 + v9 = uextend.i128 v15 ; v15 = 0 v10 = null.r32 v11 = null.r64 - v12 = vconst.b8x16 const2 + v12 = vconst.i8x16 const2 v13 = vconst.i16x4 const1 v14 = vconst.f32x16 const0 - call fn0(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) + call fn0(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) ; v0 = 0.0, v1 = 0.0, v2 = 0, v3 = 0, v4 = 0, v5 = 0, v7 = 0, v8 = 0, v12 = const2, v13 = const1, v14 = const0 trap user0 } diff --git a/cranelift/tests/bugpoint_test.clif b/cranelift/tests/bugpoint_test.clif index ced5b9e80998..f52264543bc5 100644 --- a/cranelift/tests/bugpoint_test.clif +++ b/cranelift/tests/bugpoint_test.clif @@ -418,13 +418,10 @@ block1: v114 = load.i64 v113 v115 = iconst.i64 0 v116 = icmp ugt v114, v115 - v117 = bint.i8 v116 - v118 = uextend.i32 v117 + v118 = uextend.i32 v116 v119 = icmp_imm eq v118, 0 - v120 = bint.i8 v119 - v121 = uextend.i32 v120 - brz v121, block3 - jump block2 + v121 = uextend.i32 v119 + brif v121, block2, block3 block2: v122 = global_value.i64 gv0 @@ -436,13 +433,10 @@ block3: v126 = load.i64 v125 v127 = iconst.i64 0 v128 = icmp ugt v126, v127 - v129 = bint.i8 v128 - v130 = uextend.i32 v129 + v130 = uextend.i32 v128 v131 = icmp_imm eq v130, 0 - v132 = bint.i8 v131 - v133 = uextend.i32 v132 - brz v133, block5 - jump block4 + v133 = uextend.i32 v131 + brif v133, block4, block5 block4: v134 = global_value.i64 gv2 @@ -454,13 +448,10 @@ block5: v138 = load.i64 v137+42 v139 = iconst.i64 0 v140 = icmp ugt v138, v139 - v141 = bint.i8 v140 - v142 = uextend.i32 v141 + v142 = uextend.i32 v140 v143 = icmp_imm eq v142, 0 - v144 = bint.i8 v143 - v145 = uextend.i32 v144 - brz v145, block7 - jump block6 + v145 = uextend.i32 v143 + brif v145, block6, block7 block6: v146 = global_value.i64 gv4 @@ -482,10 +473,8 @@ block9: v153 = load.i8 v6 v154 = uextend.i32 v153 v155 = icmp_imm eq v154, 0 - v156 = bint.i8 v155 - v157 = uextend.i32 v156 - brz v157, block11 - jump block10 + v157 = uextend.i32 v155 + brif v157, block10, block11 block10: v158 = global_value.i64 gv6 @@ -507,10 +496,8 @@ block13: v165 = load.i8 v8 v166 = uextend.i32 v165 v167 = icmp_imm eq v166, 0 - v168 = bint.i8 v167 - v169 = uextend.i32 v168 - brz v169, block15 - jump block14 + v169 = uextend.i32 v167 + brif v169, block14, block15 block14: v170 = global_value.i64 gv8 @@ -527,13 +514,10 @@ block16: v175 = iconst.i64 17 v176 = load.i64 v10 v177 = icmp uge v176, v175 - v178 = bint.i8 v177 - v179 = uextend.i32 v178 + v179 = uextend.i32 v177 v180 = icmp_imm eq v179, 0 - v181 = bint.i8 v180 - v182 = uextend.i32 v181 - brz v182, block18 - jump block17 + v182 = uextend.i32 v180 + brif v182, block17, block18 block17: v183 = global_value.i64 gv10 @@ -553,8 +537,7 @@ block18: v195 = iadd_imm.i64 v12, 8 v196 = load.i8 v195 v197 = uextend.i32 v196 - brz v197, block19 - jump block164 + brif v197, block164, block19 block164: v198 = global_value.i64 gv12 @@ -574,8 +557,7 @@ block19: v208 = iadd_imm.i64 v13, 8 v209 = load.i8 v208 v210 = uextend.i32 v209 - brz v210, block20 - jump block163 + brif v210, block163, block20 block163: v211 = global_value.i64 gv13 @@ -584,13 +566,10 @@ block163: block20: v212 = load.i64 v13 v214 = icmp.i64 ult v213, v212 - v215 = bint.i8 v214 - v216 = uextend.i32 v215 + v216 = uextend.i32 v214 v217 = icmp_imm eq v216, 0 - v218 = bint.i8 v217 - v219 = uextend.i32 v218 - brz v219, block22 - jump block21 + v219 = uextend.i32 v217 + brif v219, block21, block22 block21: v220 = global_value.i64 gv14 @@ -610,8 +589,7 @@ block22: v232 = iadd_imm.i64 v16, 8 v233 = load.i8 v232 v234 = uextend.i32 v233 - brz v234, block23 - jump block162 + brif v234, block162, block23 block162: v235 = global_value.i64 gv16 @@ -638,8 +616,7 @@ block24: v251 = iadd_imm.i64 v19, 8 v252 = load.i8 v251 v253 = uextend.i32 v252 - brz v253, block25 - jump block161 + brif v253, block161, block25 block161: v254 = global_value.i64 gv17 @@ -677,8 +654,7 @@ block27: v277 = iadd_imm.i64 v24, 2 v278 = load.i8 v277 v279 = uextend.i32 v278 - brz v279, block28 - jump block160 + brif v279, block160, block28 block160: v280 = global_value.i64 gv18 @@ -695,8 +671,7 @@ block28: v288 = iadd_imm.i64 v25, 2 v289 = load.i8 v288 v290 = uextend.i32 v289 - brz v290, block29 - jump block159 + brif v290, block159, block29 block159: v291 = global_value.i64 gv19 @@ -716,8 +691,7 @@ block29: v301 = iadd_imm.i64 v26, 2 v302 = load.i8 v301 v303 = uextend.i32 v302 - brz v303, block30 - jump block158 + brif v303, block158, block30 block158: v304 = global_value.i64 gv20 @@ -734,8 +708,7 @@ block30: v312 = iadd_imm.i64 v27, 2 v313 = load.i8 v312 v314 = uextend.i32 v313 - brz v314, block31 - jump block157 + brif v314, block157, block31 block157: v315 = global_value.i64 gv21 @@ -766,8 +739,7 @@ block34: block35: v322 = iconst.i8 1 v323 = uextend.i32 v322 - brz v323, block42 - jump block36 + brif v323, block36, block42 block36: v324 = iadd_imm.i64 v28, 8 @@ -781,13 +753,10 @@ block36: v330 = load.i16 v327 v331 = load.i16 v329 v332 = icmp eq v330, v331 - v333 = bint.i8 v332 - v334 = uextend.i32 v333 + v334 = uextend.i32 v332 v335 = icmp_imm eq v334, 0 - v336 = bint.i8 v335 - v337 = uextend.i32 v336 - brz v337, block38 - jump block37 + v337 = uextend.i32 v335 + brif v337, block37, block38 block37: v338 = global_value.i64 gv22 @@ -833,8 +802,7 @@ block41: block42: v362 = iconst.i8 1 v363 = uextend.i32 v362 - brz v363, block49(v1007) - jump block43 + brif v363, block43, block49(v1007) block43: v364 = iadd_imm.i64 v28, 8 @@ -848,13 +816,10 @@ block43: v370 = load.i16 v367 v371 = load.i16 v369 v372 = icmp eq v370, v371 - v373 = bint.i8 v372 - v374 = uextend.i32 v373 + v374 = uextend.i32 v372 v375 = icmp_imm eq v374, 0 - v376 = bint.i8 v375 - v377 = uextend.i32 v376 - brz v377, block45 - jump block44 + v377 = uextend.i32 v375 + brif v377, block44, block45 block44: v378 = global_value.i64 gv25 @@ -910,8 +875,7 @@ block49(v1006: i16): v410 = iadd_imm.i64 v51, 8 v411 = load.i8 v410 v412 = uextend.i32 v411 - brz v412, block50 - jump block156 + brif v412, block156, block50 block156: v413 = global_value.i64 gv28 @@ -934,8 +898,7 @@ block50: v423 = iadd_imm.i64 v52, 8 v424 = load.i8 v423 v425 = uextend.i32 v424 - brz v425, block51 - jump block155 + brif v425, block155, block51 block155: v426 = global_value.i64 gv29 @@ -949,10 +912,8 @@ block51: v435 -> v429 v430 = iconst.i16 0xffff_ffff_ffff_8000 v431 = icmp eq v429, v430 - v432 = bint.i8 v431 - v433 = uextend.i32 v432 - brz v433, block52 - jump block154 + v433 = uextend.i32 v431 + brif v433, block154, block52 block154: v434 = global_value.i64 gv30 @@ -973,8 +934,7 @@ block52: v446 = iadd_imm.i64 v53, 8 v447 = load.i8 v446 v448 = uextend.i32 v447 - brz v448, block53 - jump block153 + brif v448, block153, block53 block153: v449 = global_value.i64 gv31 @@ -995,8 +955,7 @@ block53: v461 = iadd_imm.i64 v54, 8 v462 = load.i8 v461 v463 = uextend.i32 v462 - brz v463, block54 - jump block152 + brif v463, block152, block54 block152: v464 = global_value.i64 gv32 @@ -1014,8 +973,7 @@ block54: v473 = iadd_imm.i64 v55, 8 v474 = load.i8 v473 v475 = uextend.i32 v474 - brz v475, block55 - jump block151 + brif v475, block151, block55 block151: v476 = global_value.i64 gv33 @@ -1043,8 +1001,7 @@ block56: v492 = iadd_imm.i64 v57, 2 v493 = load.i8 v492 v494 = uextend.i32 v493 - brz v494, block57 - jump block150 + brif v494, block150, block57 block150: v495 = global_value.i64 gv34 @@ -1061,8 +1018,7 @@ block57: v503 = iadd_imm.i64 v58, 2 v504 = load.i8 v503 v505 = uextend.i32 v504 - brz v505, block58 - jump block149 + brif v505, block149, block58 block149: v506 = global_value.i64 gv35 @@ -1079,8 +1035,7 @@ block58: v516 = iadd_imm.i64 v59, 8 v517 = load.i8 v516 v518 = uextend.i32 v517 - brz v518, block59 - jump block148 + brif v518, block148, block59 block148: v519 = global_value.i64 gv36 @@ -1099,8 +1054,7 @@ block59: v529 = iadd_imm.i64 v60, 8 v530 = load.i8 v529 v531 = uextend.i32 v530 - brz v531, block60 - jump block147 + brif v531, block147, block60 block147: v532 = global_value.i64 gv37 @@ -1118,8 +1072,7 @@ block60: v541 = iadd_imm.i64 v61, 8 v542 = load.i8 v541 v543 = uextend.i32 v542 - brz v543, block61 - jump block146 + brif v543, block146, block61 block146: v544 = global_value.i64 gv38 @@ -1172,10 +1125,8 @@ block62(v552: i32, v1009: i64, v1013: i64, v1016: i64, v1019: i64, v1022: i16, v v560 -> v553 v554 = iconst.i32 0 v555 = icmp eq v553, v554 - v556 = bint.i8 v555 - v557 = uextend.i32 v556 - brz v557, block63 - jump block145 + v557 = uextend.i32 v555 + brif v557, block145, block63 block145: v558 = global_value.i64 gv39 @@ -1188,10 +1139,8 @@ block63: v570 -> v563 v564 = iconst.i32 0 v565 = icmp eq v563, v564 - v566 = bint.i8 v565 - v567 = uextend.i32 v566 - brz v567, block64 - jump block144 + v567 = uextend.i32 v565 + brif v567, block144, block64 block144: v568 = global_value.i64 gv40 @@ -1204,19 +1153,15 @@ block64: v1011 -> v571 v572 = iconst.i8 1 v573 = uextend.i32 v572 - brz v573, block68(v561) - jump block65 + brif v573, block65, block68(v561) block65: v575 = iconst.i32 10 v576 = icmp.i32 ult v574, v575 - v577 = bint.i8 v576 - v578 = uextend.i32 v577 + v578 = uextend.i32 v576 v579 = icmp_imm eq v578, 0 - v580 = bint.i8 v579 - v581 = uextend.i32 v580 - brz v581, block67 - jump block66 + v581 = uextend.i32 v579 + brif v581, block66, block67 block66: v582 = global_value.i64 gv41 @@ -1237,8 +1182,7 @@ block68(v584: i32): v592 = iadd_imm.i64 v64, 1 v593 = load.i8 v592 v594 = uextend.i32 v593 - brz v594, block69 - jump block143 + brif v594, block143, block69 block143: v595 = global_value.i64 gv43 @@ -1248,10 +1192,8 @@ block69: v597 = load.i64 v3 v598 = load.i64 v3+8 v599 = icmp.i64 ult v596, v598 - v600 = bint.i8 v599 - v601 = uextend.i32 v600 - brnz v601, block70 - jump block142 + v601 = uextend.i32 v599 + brif v601, block70, block142 block142: v602 = global_value.i64 gv44 @@ -1273,8 +1215,7 @@ block70: v617 = iadd_imm.i64 v65, 8 v618 = load.i8 v617 v619 = uextend.i32 v618 - brz v619, block71 - jump block141 + brif v619, block141, block71 block141: v620 = global_value.i64 gv45 @@ -1296,8 +1237,7 @@ block71: v631 = iadd_imm.i64 v66, 8 v632 = load.i8 v631 v633 = uextend.i32 v632 - brz v633, block72 - jump block140 + brif v633, block140, block72 block140: v634 = global_value.i64 gv46 @@ -1314,8 +1254,7 @@ block72: v643 = iadd_imm.i64 v67, 8 v644 = load.i8 v643 v645 = uextend.i32 v644 - brz v645, block73 - jump block139 + brif v645, block139, block73 block139: v646 = global_value.i64 gv47 @@ -1326,10 +1265,8 @@ block73: v675 -> v647 v692 -> v647 v649 = icmp ult v647, v648 - v650 = bint.i8 v649 - v651 = uextend.i32 v650 - brz v651, block80 - jump block74 + v651 = uextend.i32 v649 + brif v651, block74, block80 block74: v652 = load.i32 v63 @@ -1343,8 +1280,7 @@ block74: v661 = iadd_imm.i64 v68, 8 v662 = load.i8 v661 v663 = uextend.i32 v662 - brz v663, block75 - jump block138 + brif v663, block138, block75 block138: v664 = global_value.i64 gv48 @@ -1374,8 +1310,7 @@ block76: v685 = iadd_imm.i64 v74, 8 v686 = load.i8 v685 v687 = uextend.i32 v686 - brz v687, block77 - jump block137 + brif v687, block137, block77 block137: v688 = global_value.i64 gv49 @@ -1396,16 +1331,13 @@ block79: block80: v697 = uextend.i64 v696 v698 = icmp.i64 ugt v695, v697 - v699 = bint.i8 v698 - v700 = uextend.i32 v699 - brz v700, block96 - jump block81 + v700 = uextend.i32 v698 + brif v700, block81, block96 block81: v701 = iconst.i8 1 v702 = uextend.i32 v701 - brz v702, block88 - jump block82 + brif v702, block82, block88 block82: v703 = global_value.i64 gv50 @@ -1418,13 +1350,10 @@ block82: v708 = load.i32 v705 v709 = load.i32 v707 v710 = icmp eq v708, v709 - v711 = bint.i8 v710 - v712 = uextend.i32 v711 + v712 = uextend.i32 v710 v713 = icmp_imm eq v712, 0 - v714 = bint.i8 v713 - v715 = uextend.i32 v714 - brz v715, block84 - jump block83 + v715 = uextend.i32 v713 + brif v715, block83, block84 block83: v716 = global_value.i64 gv51 @@ -1470,8 +1399,7 @@ block87: block88: v740 = iconst.i8 1 v741 = uextend.i32 v740 - brz v741, block95(v1030, v1031, v1041, v1046, v1054, v1059) - jump block89 + brif v741, block89, block95(v1030, v1031, v1041, v1046, v1054, v1059) block89: v742 = global_value.i64 gv54 @@ -1484,13 +1412,10 @@ block89: v747 = load.i16 v744 v748 = load.i16 v746 v749 = icmp eq v747, v748 - v750 = bint.i8 v749 - v751 = uextend.i32 v750 + v751 = uextend.i32 v749 v752 = icmp_imm eq v751, 0 - v753 = bint.i8 v752 - v754 = uextend.i32 v753 - brz v754, block91 - jump block90 + v754 = uextend.i32 v752 + brif v754, block90, block91 block90: v755 = global_value.i64 gv55 @@ -1548,8 +1473,7 @@ block96: v789 = iadd_imm.i64 v95, 2 v790 = load.i8 v789 v791 = uextend.i32 v790 - brz v791, block97 - jump block136 + brif v791, block136, block97 block136: v792 = global_value.i64 gv58 @@ -1560,10 +1484,8 @@ block97: v794 = iconst.i32 10 v795 = iconst.i32 0 v796 = icmp eq v794, v795 - v797 = bint.i8 v796 - v798 = uextend.i32 v797 - brz v798, block98 - jump block135 + v798 = uextend.i32 v796 + brif v798, block135, block98 block135: v799 = global_value.i64 gv59 @@ -1604,8 +1526,7 @@ block99(v804: i64, v1035: i64, v1037: i64, v1039: i64, v1044: i64, v1052: i16, v v812 = iadd_imm.i64 v96, 8 v813 = load.i8 v812 v814 = uextend.i32 v813 - brz v814, block100 - jump block134 + brif v814, block134, block100 block134: v815 = global_value.i64 gv60 @@ -1626,8 +1547,7 @@ block100: v825 = iadd_imm.i64 v97, 8 v826 = load.i8 v825 v827 = uextend.i32 v826 - brz v827, block101 - jump block133 + brif v827, block133, block101 block133: v828 = global_value.i64 gv61 @@ -1650,8 +1570,7 @@ block101: v838 = iadd_imm.i64 v98, 8 v839 = load.i8 v838 v840 = uextend.i32 v839 - brz v840, block102 - jump block132 + brif v840, block132, block102 block132: v841 = global_value.i64 gv62 @@ -1672,8 +1591,7 @@ block102: v851 = iadd_imm.i64 v99, 8 v852 = load.i8 v851 v853 = uextend.i32 v852 - brz v853, block103 - jump block131 + brif v853, block131, block103 block131: v854 = global_value.i64 gv63 @@ -1692,8 +1610,7 @@ block103: v865 = iadd_imm.i64 v100, 8 v866 = load.i8 v865 v867 = uextend.i32 v866 - brz v867, block104 - jump block130 + brif v867, block130, block104 block130: v868 = global_value.i64 gv64 @@ -1711,8 +1628,7 @@ block104: v877 = iadd_imm.i64 v101, 8 v878 = load.i8 v877 v879 = uextend.i32 v878 - brz v879, block105 - jump block129 + brif v879, block129, block105 block129: v880 = global_value.i64 gv65 @@ -1728,19 +1644,15 @@ block105: v1048 -> v883 v884 = iconst.i8 1 v885 = uextend.i32 v884 - brz v885, block109(v855) - jump block106 + brif v885, block106, block109(v855) block106: v887 = iconst.i64 10 v888 = icmp.i64 ult v886, v887 - v889 = bint.i8 v888 - v890 = uextend.i32 v889 + v890 = uextend.i32 v888 v891 = icmp_imm eq v890, 0 - v892 = bint.i8 v891 - v893 = uextend.i32 v892 - brz v893, block108 - jump block107 + v893 = uextend.i32 v891 + brif v893, block107, block108 block107: v894 = global_value.i64 gv66 @@ -1761,8 +1673,7 @@ block109(v896: i64): v904 = iadd_imm.i64 v102, 1 v905 = load.i8 v904 v906 = uextend.i32 v905 - brz v906, block110 - jump block128 + brif v906, block128, block110 block128: v907 = global_value.i64 gv68 @@ -1772,10 +1683,8 @@ block110: v909 = load.i64 v3 v910 = load.i64 v3+8 v911 = icmp.i64 ult v908, v910 - v912 = bint.i8 v911 - v913 = uextend.i32 v912 - brnz v913, block111 - jump block127 + v913 = uextend.i32 v911 + brif v913, block111, block127 block127: v914 = global_value.i64 gv69 @@ -1797,8 +1706,7 @@ block111: v929 = iadd_imm.i64 v103, 8 v930 = load.i8 v929 v931 = uextend.i32 v930 - brz v931, block112 - jump block126 + brif v931, block126, block112 block126: v932 = global_value.i64 gv70 @@ -1809,10 +1717,8 @@ block112: v954 -> v933 v1047 -> v933 v936 = icmp.i64 ult v934, v935 - v937 = bint.i8 v936 - v938 = uextend.i32 v937 - brz v938, block119 - jump block113 + v938 = uextend.i32 v936 + brif v938, block113, block119 block113: v940 = iconst.i64 1 @@ -1825,8 +1731,7 @@ block113: v947 = iadd_imm.i64 v104, 8 v948 = load.i8 v947 v949 = uextend.i32 v948 - brz v949, block114 - jump block125 + brif v949, block125, block114 block125: v950 = global_value.i64 gv71 @@ -1856,8 +1761,7 @@ block115: v971 = iadd_imm.i64 v110, 8 v972 = load.i8 v971 v973 = uextend.i32 v972 - brz v973, block116 - jump block123 + brif v973, block123, block116 block123: v974 = global_value.i64 gv72 @@ -1874,8 +1778,7 @@ block116: v983 = iadd_imm.i64 v111, 8 v984 = load.i8 v983 v985 = uextend.i32 v984 - brz v985, block117 - jump block122 + brif v985, block122, block117 block122: v986 = global_value.i64 gv73 @@ -1900,8 +1803,7 @@ block119: v1000 = iadd_imm.i64 v112, 2 v1001 = load.i8 v1000 v1002 = uextend.i32 v1001 - brz v1002, block120 - jump block121 + brif v1002, block121, block120 block121: v1003 = global_value.i64 gv74 diff --git a/cranelift/tests/bugpoint_test_expected.clif b/cranelift/tests/bugpoint_test_expected.clif index 308863d9f7a2..982fcb001188 100644 --- a/cranelift/tests/bugpoint_test_expected.clif +++ b/cranelift/tests/bugpoint_test_expected.clif @@ -30,6 +30,6 @@ block0: v990 -> v1052 v1051 -> v1052 v1055 -> v1052 - call fn0(v0, v105, v1052, v883, v829, v987, v951, v842) + call fn0(v0, v105, v1052, v883, v829, v987, v951, v842) ; v0 = 0, v105 = 0, v1052 = 0, v883 = 0, v829 = 0, v987 = 0, v951 = 0, v842 = 0 trap user0 } diff --git a/cranelift/tests/filetests.rs b/cranelift/tests/filetests.rs index a63346110936..72fbe7494c83 100644 --- a/cranelift/tests/filetests.rs +++ b/cranelift/tests/filetests.rs @@ -1,6 +1,5 @@ -#[test] -fn filetests() { +fn main() -> anyhow::Result<()> { // Run all the filetests in the following directories. - cranelift_filetests::run(false, false, &["filetests".into(), "docs".into()]) - .expect("test harness"); + cranelift_filetests::run(false, false, &["filetests".into(), "docs".into()])?; + Ok(()) } diff --git a/cranelift/umbrella/Cargo.toml b/cranelift/umbrella/Cargo.toml index 9773ae2237a4..5f21b540f221 100644 --- a/cranelift/umbrella/Cargo.toml +++ b/cranelift/umbrella/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Cranelift Project Developers"] name = "cranelift" -version = "0.88.0" +version = "0.94.0" description = "Umbrella for commonly-used cranelift crates" license = "Apache-2.0 WITH LLVM-exception" documentation = "https://docs.rs/cranelift" @@ -9,11 +9,11 @@ repository = "https://github.com/bytecodealliance/wasmtime" categories = ["no-std"] readme = "README.md" keywords = ["compile", "compiler", "jit"] -edition = "2021" +edition.workspace = true [dependencies] -cranelift-codegen = { path = "../codegen", version = "0.88.0", default-features = false } -cranelift-frontend = { path = "../frontend", version = "0.88.0", default-features = false } +cranelift-codegen = { workspace = true } +cranelift-frontend = { workspace = true } [features] default = ["std"] diff --git a/cranelift/wasm/Cargo.toml b/cranelift/wasm/Cargo.toml index 16edd285cb47..a35324b55e5b 100644 --- a/cranelift/wasm/Cargo.toml +++ b/cranelift/wasm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cranelift-wasm" -version = "0.88.0" +version = "0.94.0" authors = ["The Cranelift Project Developers"] description = "Translator from WebAssembly to Cranelift IR" documentation = "https://docs.rs/cranelift-wasm" @@ -9,24 +9,23 @@ license = "Apache-2.0 WITH LLVM-exception" categories = ["no-std", "wasm"] readme = "README.md" keywords = ["webassembly", "wasm"] -edition = "2021" +edition.workspace = true [dependencies] -wasmparser = { git = "https://github.com/effect-handlers/wasm-tools", branch = "func-ref-2", default-features = false } -cranelift-codegen = { path = "../codegen", version = "0.88.0", default-features = false } -cranelift-entity = { path = "../entity", version = "0.88.0" } -cranelift-frontend = { path = "../frontend", version = "0.88.0", default-features = false } -wasmtime-types = { path = "../../crates/types", version = "0.41.0" } -hashbrown = { version = "0.12", optional = true } +wasmparser = { workspace = true } +cranelift-codegen = { workspace = true } +cranelift-entity = { workspace = true } +cranelift-frontend = { workspace = true } +wasmtime-types = { workspace = true } +hashbrown = { workspace = true, optional = true } itertools = "0.10.0" -log = { version = "0.4.6", default-features = false } +log = { workspace = true } serde = { version = "1.0.94", features = ["derive"], optional = true } -smallvec = "1.6.1" +smallvec = { workspace = true } [dev-dependencies] -wat = "1.0.47" -target-lexicon = "0.12" -cranelift-codegen = { path = "../codegen", version = "0.88.0", default-features = false } +wat = { workspace = true } +target-lexicon = { workspace = true } [features] default = ["std"] diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index f2454eef3b32..8906fea83e7d 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -71,6 +71,8 @@ //! //! ("Relax verification to allow I8X16 to act as a default vector type") +mod bounds_checks; + use super::{hash_map, HashMap}; use crate::environ::{FuncEnvironment, GlobalVariable}; use crate::state::{ControlStackFrame, ElseData, FuncTranslationState}; @@ -90,31 +92,49 @@ use cranelift_codegen::packed_option::ReservedValue; use cranelift_frontend::{FunctionBuilder, Variable}; use itertools::Itertools; use smallvec::SmallVec; -use std::cmp; use std::convert::TryFrom; use std::vec::Vec; -use wasmparser::{FuncValidator, MemoryImmediate, Operator, ValType, WasmModuleResources}; +use wasmparser::{FuncValidator, MemArg, Operator, WasmModuleResources}; + +/// Given a `Reachability`, unwrap the inner `T` or, when unreachable, set +/// `state.reachable = false` and return. +/// +/// Used in combination with calling `prepare_addr` and `prepare_atomic_addr` +/// when we can statically determine that a Wasm access will unconditionally +/// trap. +macro_rules! unwrap_or_return_unreachable_state { + ($state:ident, $value:expr) => { + match $value { + Reachability::Reachable(x) => x, + Reachability::Unreachable => { + $state.reachable = false; + return Ok(()); + } + } + }; +} // Clippy warns about "align: _" but its important to document that the flags field is ignored #[cfg_attr( feature = "cargo-clippy", allow(clippy::unneeded_field_pattern, clippy::cognitive_complexity) )] -/// Translates wasm operators into Cranelift IR instructions. Returns `true` if it inserted -/// a return. +/// Translates wasm operators into Cranelift IR instructions. pub fn translate_operator( validator: &mut FuncValidator, op: &Operator, builder: &mut FunctionBuilder, state: &mut FuncTranslationState, environ: &mut FE, - ty: Option, ) -> WasmResult<()> { if !state.reachable { translate_unreachable_operator(validator, &op, builder, state, environ)?; return Ok(()); } + // Given that we believe the current block is reachable, the FunctionBuilder ought to agree. + debug_assert!(!builder.is_unreachable()); + // This big match treats all Wasm code operators. match op { /********************************** Locals **************************************** @@ -122,7 +142,7 @@ pub fn translate_operator( * disappear in the Cranelift Code ***********************************************************************************/ Operator::LocalGet { local_index } => { - let val = builder.use_var(Variable::with_u32(*local_index)); + let val = builder.use_var(Variable::from_u32(*local_index)); state.push1(val); let label = ValueLabel::from_u32(*local_index); builder.set_val_label(val, label); @@ -136,7 +156,7 @@ pub fn translate_operator( val = optionally_bitcast_vector(val, I8X16, builder); } - builder.def_var(Variable::with_u32(*local_index), val); + builder.def_var(Variable::from_u32(*local_index), val); let label = ValueLabel::from_u32(*local_index); builder.set_val_label(val, label); } @@ -149,7 +169,7 @@ pub fn translate_operator( val = optionally_bitcast_vector(val, I8X16, builder); } - builder.def_var(Variable::with_u32(*local_index), val); + builder.def_var(Variable::from_u32(*local_index), val); let label = ValueLabel::from_u32(*local_index); builder.set_val_label(val, label); } @@ -246,13 +266,13 @@ pub fn translate_operator( * block and have already been translated) and modify the value stack to use the * possible `Block`'s arguments values. ***********************************************************************************/ - Operator::Block { ty } => { - let (params, results) = blocktype_params_results(validator, *ty)?; + Operator::Block { blockty } => { + let (params, results) = blocktype_params_results(validator, *blockty)?; let next = block_with_params(builder, results.clone(), environ)?; state.push_block(next, params.len(), results.len()); } - Operator::Loop { ty } => { - let (params, results) = blocktype_params_results(validator, *ty)?; + Operator::Loop { blockty } => { + let (params, results) = blocktype_params_results(validator, *blockty)?; let loop_body = block_with_params(builder, params.clone(), environ)?; let next = block_with_params(builder, results.clone(), environ)?; canonicalise_then_jump(builder, loop_body, state.peekn(params.len())); @@ -268,10 +288,11 @@ pub fn translate_operator( builder.switch_to_block(loop_body); environ.translate_loop_header(builder)?; } - Operator::If { ty } => { + Operator::If { blockty } => { let val = state.pop1(); - let (params, results) = blocktype_params_results(validator, *ty)?; + let next_block = builder.create_block(); + let (params, results) = blocktype_params_results(validator, *blockty)?; let (destination, else_data) = if params.clone().eq(results.clone()) { // It is possible there is no `else` block, so we will only // allocate a block for it if/when we find the `else`. For now, @@ -280,21 +301,38 @@ pub fn translate_operator( // up discovering an `else`, then we will allocate a block for it // and go back and patch the jump. let destination = block_with_params(builder, results.clone(), environ)?; - let branch_inst = - canonicalise_then_brz(builder, val, destination, state.peekn(params.len())); - (destination, ElseData::NoElse { branch_inst }) + let branch_inst = canonicalise_brif( + builder, + val, + next_block, + &[], + destination, + state.peekn(params.len()), + ); + ( + destination, + ElseData::NoElse { + branch_inst, + placeholder: destination, + }, + ) } else { // The `if` type signature is not valid without an `else` block, // so we eagerly allocate the `else` block here. let destination = block_with_params(builder, results.clone(), environ)?; let else_block = block_with_params(builder, params.clone(), environ)?; - canonicalise_then_brz(builder, val, else_block, state.peekn(params.len())); + canonicalise_brif( + builder, + val, + next_block, + &[], + else_block, + state.peekn(params.len()), + ); builder.seal_block(else_block); (destination, ElseData::WithElse { else_block }) }; - let next_block = builder.create_block(); - canonicalise_then_jump(builder, next_block, &[]); builder.seal_block(next_block); // Only predecessor is the current block. builder.switch_to_block(next_block); @@ -304,7 +342,13 @@ pub fn translate_operator( // and we add nothing; // - either the If have an Else clause, in that case the destination of this jump // instruction will be changed later when we translate the Else operator. - state.push_if(destination, else_data, params.len(), results.len(), *ty); + state.push_if( + destination, + else_data, + params.len(), + results.len(), + *blockty, + ); } Operator::Else => { let i = state.control_stack.len() - 1; @@ -330,7 +374,10 @@ pub fn translate_operator( // Ensure we have a block for the `else` block (it may have // already been pre-allocated, see `ElseData` for details). let else_block = match *else_data { - ElseData::NoElse { branch_inst } => { + ElseData::NoElse { + branch_inst, + placeholder, + } => { let (params, _results) = blocktype_params_results(validator, blocktype)?; debug_assert_eq!(params.len(), num_return_values); @@ -343,7 +390,11 @@ pub fn translate_operator( ); state.popn(params.len()); - builder.change_jump_destination(branch_inst, else_block); + builder.change_jump_destination( + branch_inst, + placeholder, + else_block, + ); builder.seal_block(else_block); else_block } @@ -380,18 +431,16 @@ pub fn translate_operator( Operator::End => { let frame = state.control_stack.pop().unwrap(); let next_block = frame.following_code(); + let return_count = frame.num_return_values(); + let return_args = state.peekn_mut(return_count); - if !builder.is_unreachable() || !builder.is_pristine() { - let return_count = frame.num_return_values(); - let return_args = state.peekn_mut(return_count); - canonicalise_then_jump(builder, frame.following_code(), return_args); - // You might expect that if we just finished an `if` block that - // didn't have a corresponding `else` block, then we would clean - // up our duplicate set of parameters that we pushed earlier - // right here. However, we don't have to explicitly do that, - // since we truncate the stack back to the original height - // below. - } + canonicalise_then_jump(builder, next_block, return_args); + // You might expect that if we just finished an `if` block that + // didn't have a corresponding `else` block, then we would clean + // up our duplicate set of parameters that we pushed earlier + // right here. However, we don't have to explicitly do that, + // since we truncate the stack back to the original height + // below. builder.switch_to_block(next_block); builder.seal_block(next_block); @@ -446,10 +495,10 @@ pub fn translate_operator( state.reachable = false; } Operator::BrIf { relative_depth } => translate_br_if(*relative_depth, builder, state), - Operator::BrTable { table } => { - let default = table.default(); + Operator::BrTable { targets } => { + let default = targets.default(); let mut min_depth = default; - for depth in table.targets() { + for depth in targets.targets() { let depth = depth?; if depth < min_depth { min_depth = depth; @@ -465,10 +514,10 @@ pub fn translate_operator( } }; let val = state.pop1(); - let mut data = JumpTableData::with_capacity(table.len() as usize); + let mut data = Vec::with_capacity(targets.len() as usize); if jump_args_count == 0 { // No jump arguments - for depth in table.targets() { + for depth in targets.targets() { let depth = depth?; let block = { let i = state.control_stack.len() - 1 - (depth as usize); @@ -476,23 +525,23 @@ pub fn translate_operator( frame.set_branched_to_exit(); frame.br_destination() }; - data.push_entry(block); + data.push(block); } - let jt = builder.create_jump_table(data); let block = { let i = state.control_stack.len() - 1 - (default as usize); let frame = &mut state.control_stack[i]; frame.set_branched_to_exit(); frame.br_destination() }; - builder.ins().br_table(val, block, jt); + let jt = builder.create_jump_table(JumpTableData::new(block, &data)); + builder.ins().br_table(val, jt); } else { // Here we have jump arguments, but Cranelift's br_table doesn't support them // We then proceed to split the edges going out of the br_table let return_count = jump_args_count; let mut dest_block_sequence = vec![]; let mut dest_block_map = HashMap::new(); - for depth in table.targets() { + for depth in targets.targets() { let depth = depth?; let branch_block = match dest_block_map.entry(depth as usize) { hash_map::Entry::Occupied(entry) => *entry.get(), @@ -502,7 +551,7 @@ pub fn translate_operator( *entry.insert(block) } }; - data.push_entry(branch_block); + data.push(branch_block); } let default_branch_block = match dest_block_map.entry(default as usize) { hash_map::Entry::Occupied(entry) => *entry.get(), @@ -512,8 +561,8 @@ pub fn translate_operator( *entry.insert(block) } }; - let jt = builder.create_jump_table(data); - builder.ins().br_table(val, default_branch_block, jt); + let jt = builder.create_jump_table(JumpTableData::new(default_branch_block, &data)); + builder.ins().br_table(val, jt); for (depth, dest_block) in dest_block_sequence { builder.switch_to_block(dest_block); builder.seal_block(dest_block); @@ -590,13 +639,14 @@ pub fn translate_operator( state.pushn(inst_results); } Operator::CallIndirect { - index, + type_index, table_index, table_byte: _, } => { - // `index` is the index of the function's signature and `table_index` is the index of - // the table to search the function in. - let (sigref, num_args) = state.get_indirect_sig(builder.func, *index, environ)?; + // `type_index` is the index of the function's signature and + // `table_index` is the index of the table to search the function + // in. + let (sigref, num_args) = state.get_indirect_sig(builder.func, *type_index, environ)?; let table = state.get_or_create_table(builder.func, *table_index, environ)?; let callee = state.pop1(); @@ -608,7 +658,7 @@ pub fn translate_operator( builder, TableIndex::from_u32(*table_index), table, - TypeIndex::from_u32(*index), + TypeIndex::from_u32(*type_index), sigref, callee, state.peekn(num_args), @@ -644,78 +694,141 @@ pub fn translate_operator( * The memory base address is provided by the environment. ************************************************************************************/ Operator::I32Load8U { memarg } => { - translate_load(memarg, ir::Opcode::Uload8, I32, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Uload8, I32, builder, state, environ)? + ); } Operator::I32Load16U { memarg } => { - translate_load(memarg, ir::Opcode::Uload16, I32, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Uload16, I32, builder, state, environ)? + ); } Operator::I32Load8S { memarg } => { - translate_load(memarg, ir::Opcode::Sload8, I32, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Sload8, I32, builder, state, environ)? + ); } Operator::I32Load16S { memarg } => { - translate_load(memarg, ir::Opcode::Sload16, I32, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Sload16, I32, builder, state, environ)? + ); } Operator::I64Load8U { memarg } => { - translate_load(memarg, ir::Opcode::Uload8, I64, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Uload8, I64, builder, state, environ)? + ); } Operator::I64Load16U { memarg } => { - translate_load(memarg, ir::Opcode::Uload16, I64, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Uload16, I64, builder, state, environ)? + ); } Operator::I64Load8S { memarg } => { - translate_load(memarg, ir::Opcode::Sload8, I64, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Sload8, I64, builder, state, environ)? + ); } Operator::I64Load16S { memarg } => { - translate_load(memarg, ir::Opcode::Sload16, I64, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Sload16, I64, builder, state, environ)? + ); } Operator::I64Load32S { memarg } => { - translate_load(memarg, ir::Opcode::Sload32, I64, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Sload32, I64, builder, state, environ)? + ); } Operator::I64Load32U { memarg } => { - translate_load(memarg, ir::Opcode::Uload32, I64, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Uload32, I64, builder, state, environ)? + ); } Operator::I32Load { memarg } => { - translate_load(memarg, ir::Opcode::Load, I32, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Load, I32, builder, state, environ)? + ); } Operator::F32Load { memarg } => { - translate_load(memarg, ir::Opcode::Load, F32, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Load, F32, builder, state, environ)? + ); } Operator::I64Load { memarg } => { - translate_load(memarg, ir::Opcode::Load, I64, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Load, I64, builder, state, environ)? + ); } Operator::F64Load { memarg } => { - translate_load(memarg, ir::Opcode::Load, F64, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Load, F64, builder, state, environ)? + ); } Operator::V128Load { memarg } => { - translate_load(memarg, ir::Opcode::Load, I8X16, builder, state, environ)?; + unwrap_or_return_unreachable_state!( + state, + translate_load(memarg, ir::Opcode::Load, I8X16, builder, state, environ)? + ); } Operator::V128Load8x8S { memarg } => { - let (flags, base, offset) = prepare_addr(memarg, 8, builder, state, environ)?; - let loaded = builder.ins().sload8x8(flags, base, offset); + let (flags, base) = unwrap_or_return_unreachable_state!( + state, + prepare_addr(memarg, 8, builder, state, environ)? + ); + let loaded = builder.ins().sload8x8(flags, base, 0); state.push1(loaded); } Operator::V128Load8x8U { memarg } => { - let (flags, base, offset) = prepare_addr(memarg, 8, builder, state, environ)?; - let loaded = builder.ins().uload8x8(flags, base, offset); + let (flags, base) = unwrap_or_return_unreachable_state!( + state, + prepare_addr(memarg, 8, builder, state, environ)? + ); + let loaded = builder.ins().uload8x8(flags, base, 0); state.push1(loaded); } Operator::V128Load16x4S { memarg } => { - let (flags, base, offset) = prepare_addr(memarg, 8, builder, state, environ)?; - let loaded = builder.ins().sload16x4(flags, base, offset); + let (flags, base) = unwrap_or_return_unreachable_state!( + state, + prepare_addr(memarg, 8, builder, state, environ)? + ); + let loaded = builder.ins().sload16x4(flags, base, 0); state.push1(loaded); } Operator::V128Load16x4U { memarg } => { - let (flags, base, offset) = prepare_addr(memarg, 8, builder, state, environ)?; - let loaded = builder.ins().uload16x4(flags, base, offset); + let (flags, base) = unwrap_or_return_unreachable_state!( + state, + prepare_addr(memarg, 8, builder, state, environ)? + ); + let loaded = builder.ins().uload16x4(flags, base, 0); state.push1(loaded); } Operator::V128Load32x2S { memarg } => { - let (flags, base, offset) = prepare_addr(memarg, 8, builder, state, environ)?; - let loaded = builder.ins().sload32x2(flags, base, offset); + let (flags, base) = unwrap_or_return_unreachable_state!( + state, + prepare_addr(memarg, 8, builder, state, environ)? + ); + let loaded = builder.ins().sload32x2(flags, base, 0); state.push1(loaded); } Operator::V128Load32x2U { memarg } => { - let (flags, base, offset) = prepare_addr(memarg, 8, builder, state, environ)?; - let loaded = builder.ins().uload32x2(flags, base, offset); + let (flags, base) = unwrap_or_return_unreachable_state!( + state, + prepare_addr(memarg, 8, builder, state, environ)? + ); + let loaded = builder.ins().uload32x2(flags, base, 0); state.push1(loaded); } /****************************** Store instructions *********************************** @@ -860,19 +973,19 @@ pub fn translate_operator( } Operator::F32ReinterpretI32 => { let val = state.pop1(); - state.push1(builder.ins().bitcast(F32, val)); + state.push1(builder.ins().bitcast(F32, MemFlags::new(), val)); } Operator::F64ReinterpretI64 => { let val = state.pop1(); - state.push1(builder.ins().bitcast(F64, val)); + state.push1(builder.ins().bitcast(F64, MemFlags::new(), val)); } Operator::I32ReinterpretF32 => { let val = state.pop1(); - state.push1(builder.ins().bitcast(I32, val)); + state.push1(builder.ins().bitcast(I32, MemFlags::new(), val)); } Operator::I64ReinterpretF64 => { let val = state.pop1(); - state.push1(builder.ins().bitcast(I64, val)); + state.push1(builder.ins().bitcast(I64, MemFlags::new(), val)); } Operator::I32Extend8S => { let val = state.pop1(); @@ -1021,7 +1134,7 @@ pub fn translate_operator( Operator::I32Eqz | Operator::I64Eqz => { let arg = state.pop1(); let val = builder.ins().icmp_imm(IntCC::Equal, arg, 0); - state.push1(builder.ins().bint(I32, val)); + state.push1(builder.ins().uextend(I32, val)); } Operator::I32Eq | Operator::I64Eq => translate_icmp(IntCC::Equal, builder, state), Operator::F32Eq | Operator::F64Eq => translate_fcmp(FloatCC::Equal, builder, state), @@ -1035,8 +1148,8 @@ pub fn translate_operator( Operator::F32Le | Operator::F64Le => { translate_fcmp(FloatCC::LessThanOrEqual, builder, state) } - Operator::RefNull { ty } => { - state.push1(environ.translate_ref_null(builder.cursor(), (*ty).into())?) + Operator::RefNull { hty } => { + state.push1(environ.translate_ref_null(builder.cursor(), (*hty).into())?) } Operator::RefIsNull => { let value = state.pop1(); @@ -1059,16 +1172,24 @@ pub fn translate_operator( let heap = state.get_heap(builder.func, memarg.memory, environ)?; let timeout = state.pop1(); // 64 (fixed) let expected = state.pop1(); // 32 or 64 (per the `Ixx` in `IxxAtomicWait`) - let (_flags, addr) = - prepare_atomic_addr(memarg, implied_ty.bytes(), builder, state, environ)?; assert!(builder.func.dfg.value_type(expected) == implied_ty); + let addr = state.pop1(); + let effective_addr = if memarg.offset == 0 { + addr + } else { + let index_type = environ.heaps()[heap].index_type; + let offset = builder.ins().iconst(index_type, memarg.offset as i64); + builder + .ins() + .uadd_overflow_trap(addr, offset, ir::TrapCode::HeapOutOfBounds) + }; // `fn translate_atomic_wait` can inspect the type of `expected` to figure out what // code it needs to generate, if it wants. let res = environ.translate_atomic_wait( builder.cursor(), heap_index, heap, - addr, + effective_addr, expected, timeout, )?; @@ -1078,12 +1199,23 @@ pub fn translate_operator( let heap_index = MemoryIndex::from_u32(memarg.memory); let heap = state.get_heap(builder.func, memarg.memory, environ)?; let count = state.pop1(); // 32 (fixed) - - // `memory.atomic.notify` is defined to have an access size of 4 - // bytes in the spec, even though it doesn't necessarily access memory. - let (_flags, addr) = prepare_atomic_addr(memarg, 4, builder, state, environ)?; - let res = - environ.translate_atomic_notify(builder.cursor(), heap_index, heap, addr, count)?; + let addr = state.pop1(); + let effective_addr = if memarg.offset == 0 { + addr + } else { + let index_type = environ.heaps()[heap].index_type; + let offset = builder.ins().iconst(index_type, memarg.offset as i64); + builder + .ins() + .uadd_overflow_trap(addr, offset, ir::TrapCode::HeapOutOfBounds) + }; + let res = environ.translate_atomic_notify( + builder.cursor(), + heap_index, + heap, + effective_addr, + count, + )?; state.push1(res); } Operator::I32AtomicLoad { memarg } => { @@ -1287,11 +1419,11 @@ pub fn translate_operator( Operator::AtomicFence { .. } => { builder.ins().fence(); } - Operator::MemoryCopy { src, dst } => { - let src_index = MemoryIndex::from_u32(*src); - let dst_index = MemoryIndex::from_u32(*dst); - let src_heap = state.get_heap(builder.func, *src, environ)?; - let dst_heap = state.get_heap(builder.func, *dst, environ)?; + Operator::MemoryCopy { src_mem, dst_mem } => { + let src_index = MemoryIndex::from_u32(*src_mem); + let dst_index = MemoryIndex::from_u32(*dst_mem); + let src_heap = state.get_heap(builder.func, *src_mem, environ)?; + let dst_heap = state.get_heap(builder.func, *dst_mem, environ)?; let len = state.pop1(); let src_pos = state.pop1(); let dst_pos = state.pop1(); @@ -1314,7 +1446,7 @@ pub fn translate_operator( let dest = state.pop1(); environ.translate_memory_fill(builder.cursor(), heap_index, heap, dest, val, len)?; } - Operator::MemoryInit { segment, mem } => { + Operator::MemoryInit { data_index, mem } => { let heap_index = MemoryIndex::from_u32(*mem); let heap = state.get_heap(builder.func, *mem, environ)?; let len = state.pop1(); @@ -1324,14 +1456,14 @@ pub fn translate_operator( builder.cursor(), heap_index, heap, - *segment, + *data_index, dest, src, len, )?; } - Operator::DataDrop { segment } => { - environ.translate_data_drop(builder.cursor(), *segment)?; + Operator::DataDrop { data_index } => { + environ.translate_data_drop(builder.cursor(), *data_index)?; } Operator::TableSize { table: index } => { let table = state.get_or_create_table(builder.func, *index, environ)?; @@ -1395,7 +1527,7 @@ pub fn translate_operator( environ.translate_table_fill(builder.cursor(), table_index, dest, val, len)?; } Operator::TableInit { - segment, + elem_index, table: table_index, } => { let table = state.get_or_create_table(builder.func, *table_index, environ)?; @@ -1404,7 +1536,7 @@ pub fn translate_operator( let dest = state.pop1(); environ.translate_table_init( builder.cursor(), - *segment, + *elem_index, TableIndex::from_u32(*table_index), table, dest, @@ -1412,14 +1544,14 @@ pub fn translate_operator( len, )?; } - Operator::ElemDrop { segment } => { - environ.translate_elem_drop(builder.cursor(), *segment)?; + Operator::ElemDrop { elem_index } => { + environ.translate_elem_drop(builder.cursor(), *elem_index)?; } Operator::V128Const { value } => { let data = value.bytes().to_vec().into(); let handle = builder.func.dfg.constants.insert(data); let value = builder.ins().vconst(I8X16, handle); - // the v128.const is typed in CLIF as a I8x16 but raw_bitcast to a different type + // the v128.const is typed in CLIF as a I8x16 but bitcast to a different type // before use state.push1(value) } @@ -1439,26 +1571,32 @@ pub fn translate_operator( | Operator::V128Load16Splat { memarg } | Operator::V128Load32Splat { memarg } | Operator::V128Load64Splat { memarg } => { - translate_load( - memarg, - ir::Opcode::Load, - type_of(op).lane_type(), - builder, + unwrap_or_return_unreachable_state!( state, - environ, - )?; + translate_load( + memarg, + ir::Opcode::Load, + type_of(op).lane_type(), + builder, + state, + environ, + )? + ); let splatted = builder.ins().splat(type_of(op), state.pop1()); state.push1(splatted) } Operator::V128Load32Zero { memarg } | Operator::V128Load64Zero { memarg } => { - translate_load( - memarg, - ir::Opcode::Load, - type_of(op).lane_type(), - builder, + unwrap_or_return_unreachable_state!( state, - environ, - )?; + translate_load( + memarg, + ir::Opcode::Load, + type_of(op).lane_type(), + builder, + state, + environ, + )? + ); let as_vector = builder.ins().scalar_to_vector(type_of(op), state.pop1()); state.push1(as_vector) } @@ -1467,14 +1605,17 @@ pub fn translate_operator( | Operator::V128Load32Lane { memarg, lane } | Operator::V128Load64Lane { memarg, lane } => { let vector = pop1_with_bitcast(state, type_of(op), builder); - translate_load( - memarg, - ir::Opcode::Load, - type_of(op).lane_type(), - builder, + unwrap_or_return_unreachable_state!( state, - environ, - )?; + translate_load( + memarg, + ir::Opcode::Load, + type_of(op).lane_type(), + builder, + state, + environ, + )? + ); let replacement = state.pop1(); state.push1(builder.ins().insertlane(vector, replacement, *lane)) } @@ -1528,7 +1669,7 @@ pub fn translate_operator( let shuffled = builder.ins().shuffle(a, b, mask); state.push1(shuffled) // At this point the original types of a and b are lost; users of this value (i.e. this - // WASM-to-CLIF translator) may need to raw_bitcast for type-correctness. This is due + // WASM-to-CLIF translator) may need to bitcast for type-correctness. This is due // to WASM using the less specific v128 type for certain operations and more specific // types (e.g. i8x16) for others. } @@ -1562,7 +1703,7 @@ pub fn translate_operator( } Operator::I8x16MinS | Operator::I16x8MinS | Operator::I32x4MinS => { let (a, b) = pop2_with_bitcast(state, type_of(op), builder); - state.push1(builder.ins().imin(a, b)) + state.push1(builder.ins().smin(a, b)) } Operator::I8x16MinU | Operator::I16x8MinU | Operator::I32x4MinU => { let (a, b) = pop2_with_bitcast(state, type_of(op), builder); @@ -1570,13 +1711,13 @@ pub fn translate_operator( } Operator::I8x16MaxS | Operator::I16x8MaxS | Operator::I32x4MaxS => { let (a, b) = pop2_with_bitcast(state, type_of(op), builder); - state.push1(builder.ins().imax(a, b)) + state.push1(builder.ins().smax(a, b)) } Operator::I8x16MaxU | Operator::I16x8MaxU | Operator::I32x4MaxU => { let (a, b) = pop2_with_bitcast(state, type_of(op), builder); state.push1(builder.ins().umax(a, b)) } - Operator::I8x16RoundingAverageU | Operator::I16x8RoundingAverageU => { + Operator::I8x16AvgrU | Operator::I16x8AvgrU => { let (a, b) = pop2_with_bitcast(state, type_of(op), builder); state.push1(builder.ins().avg_round(a, b)) } @@ -1645,7 +1786,7 @@ pub fn translate_operator( Operator::V128AnyTrue => { let a = pop1_with_bitcast(state, type_of(op), builder); let bool_result = builder.ins().vany_true(a); - state.push1(builder.ins().bint(I32, bool_result)) + state.push1(builder.ins().uextend(I32, bool_result)) } Operator::I8x16AllTrue | Operator::I16x8AllTrue @@ -1653,7 +1794,7 @@ pub fn translate_operator( | Operator::I64x2AllTrue => { let a = pop1_with_bitcast(state, type_of(op), builder); let bool_result = builder.ins().vall_true(a); - state.push1(builder.ins().bint(I32, bool_result)) + state.push1(builder.ins().uextend(I32, bool_result)) } Operator::I8x16Bitmask | Operator::I16x8Bitmask @@ -2000,29 +2141,39 @@ pub fn translate_operator( Operator::ReturnCall { .. } | Operator::ReturnCallIndirect { .. } => { return Err(wasm_unsupported!("proposed tail-call operator {:?}", op)); } + Operator::MemoryDiscard { .. } => { + return Err(wasm_unsupported!( + "proposed memory-control operator {:?}", + op + )); + } Operator::I8x16RelaxedSwizzle | Operator::I32x4RelaxedTruncSatF32x4S | Operator::I32x4RelaxedTruncSatF32x4U | Operator::I32x4RelaxedTruncSatF64x2SZero | Operator::I32x4RelaxedTruncSatF64x2UZero - | Operator::F32x4Fma - | Operator::F32x4Fms - | Operator::F64x2Fma - | Operator::F64x2Fms - | Operator::I8x16LaneSelect - | Operator::I16x8LaneSelect - | Operator::I32x4LaneSelect - | Operator::I64x2LaneSelect + | Operator::F32x4RelaxedFma + | Operator::F32x4RelaxedFnma + | Operator::F64x2RelaxedFma + | Operator::F64x2RelaxedFnma + | Operator::I8x16RelaxedLaneselect + | Operator::I16x8RelaxedLaneselect + | Operator::I32x4RelaxedLaneselect + | Operator::I64x2RelaxedLaneselect | Operator::F32x4RelaxedMin | Operator::F32x4RelaxedMax | Operator::F64x2RelaxedMin - | Operator::F64x2RelaxedMax => { + | Operator::F64x2RelaxedMax + | Operator::I16x8RelaxedQ15mulrS + | Operator::I16x8DotI8x16I7x16S + | Operator::I32x4DotI8x16I7x16AddS + | Operator::F32x4RelaxedDotBf16x8AddF32x4 => { return Err(wasm_unsupported!("proposed relaxed-simd operator {:?}", op)); } // TODO(dhil) fixme: merge into the above list. // Function references instructions - Operator::ReturnCallRef => { + Operator::ReturnCallRef { hty: _ } => { return Err(wasm_unsupported!( "proposed tail-call operator for function references {:?}", op @@ -2032,7 +2183,8 @@ pub fn translate_operator( let r = state.pop1(); let (br_destination, inputs) = translate_br_if_args(*relative_depth, state); let is_null = environ.translate_ref_is_null(builder.cursor(), r)?; - canonicalise_then_brnz(builder, is_null, br_destination, inputs); + //canonicalise_then_brnz(builder, is_null, br_destination, inputs); + todo!("implement jump"); let next_block = builder.create_block(); canonicalise_then_jump(builder, next_block, &[]); @@ -2049,7 +2201,8 @@ pub fn translate_operator( // Else: Execute the instruction (br relative_depth). let is_null = environ.translate_ref_is_null(builder.cursor(), state.peek1())?; let (br_destination, inputs) = translate_br_if_args(*relative_depth, state); - canonicalise_then_brz(builder, is_null, br_destination, inputs); + //canonicalise_then_brz(builder, is_null, br_destination, inputs); + todo!("implement jump"); // In the null case, pop the ref state.pop1(); let next_block = builder.create_block(); @@ -2060,15 +2213,11 @@ pub fn translate_operator( // currently an empty block builder.switch_to_block(next_block); } - Operator::CallRef => { + Operator::CallRef { hty } => { // Get function signature - let index = match ty { - None => panic!("expected Some val type"), - Some(wasmparser::ValType::Ref(wasmparser::RefType { - heap_type: wasmparser::HeapType::Index(type_idx), - .. - })) => type_idx, - _ => panic!("unexpected val type"), + let index = match hty { + wasmparser::HeapType::TypedFunc(type_idx) => >::into(*type_idx), + _ => panic!("expected typed func"), }; // `index` is the index of the function's signature and `table_index` is the index of // the table to search the function in. @@ -2094,7 +2243,7 @@ pub fn translate_operator( Operator::RefAsNonNull => { let r = state.pop1(); let is_null = environ.translate_ref_is_null(builder.cursor(), r)?; - builder.ins().trapnz(is_null, ir::TrapCode::NullReference); + builder.ins().trapnz(is_null, ir::TrapCode::IndirectCallToNull); state.push1(r); } }; @@ -2115,20 +2264,21 @@ fn translate_unreachable_operator( ) -> WasmResult<()> { debug_assert!(!state.reachable); match *op { - Operator::If { ty } => { + Operator::If { blockty } => { // Push a placeholder control stack entry. The if isn't reachable, // so we don't have any branches anywhere. state.push_if( ir::Block::reserved_value(), ElseData::NoElse { branch_inst: ir::Inst::reserved_value(), + placeholder: ir::Block::reserved_value(), }, 0, 0, - ty, + blockty, ); } - Operator::Loop { ty: _ } | Operator::Block { ty: _ } => { + Operator::Loop { blockty: _ } | Operator::Block { blockty: _ } => { state.push_block(ir::Block::reserved_value(), 0, 0); } Operator::Else => { @@ -2149,7 +2299,10 @@ fn translate_unreachable_operator( state.reachable = true; let else_block = match *else_data { - ElseData::NoElse { branch_inst } => { + ElseData::NoElse { + branch_inst, + placeholder, + } => { let (params, _results) = blocktype_params_results(validator, blocktype)?; let else_block = block_with_params(builder, params, environ)?; @@ -2157,7 +2310,11 @@ fn translate_unreachable_operator( frame.truncate_value_stack_to_else_params(&mut state.stack); // We change the target of the branch instruction. - builder.change_jump_destination(branch_inst, else_block); + builder.change_jump_destination( + branch_inst, + placeholder, + else_block, + ); builder.seal_block(else_block); else_block } @@ -2237,21 +2394,25 @@ fn translate_unreachable_operator( /// This function is a generalized helper for validating that a wasm-supplied /// heap address is in-bounds. /// -/// This function takes a litany of parameters and requires that the address to -/// be verified is at the top of the stack in `state`. This will generate -/// necessary IR to validate that the heap address is correctly in-bounds, and -/// various parameters are returned describing the valid heap address if -/// execution reaches that point. -fn prepare_addr( - memarg: &MemoryImmediate, - access_size: u32, +/// This function takes a litany of parameters and requires that the *Wasm* +/// address to be verified is at the top of the stack in `state`. This will +/// generate necessary IR to validate that the heap address is correctly +/// in-bounds, and various parameters are returned describing the valid *native* +/// heap address if execution reaches that point. +/// +/// Returns `None` when the Wasm access will unconditionally trap. +fn prepare_addr( + memarg: &MemArg, + access_size: u8, builder: &mut FunctionBuilder, state: &mut FuncTranslationState, environ: &mut FE, -) -> WasmResult<(MemFlags, Value, Offset32)> { - let addr = state.pop1(); +) -> WasmResult> +where + FE: FuncEnvironment + ?Sized, +{ + let index = state.pop1(); let heap = state.get_heap(builder.func, memarg.memory, environ)?; - let offset_guard_size: u64 = builder.func.heaps[heap].offset_guard_size.into(); // How exactly the bounds check is performed here and what it's performed // on is a bit tricky. Generally we want to rely on access violations (e.g. @@ -2310,10 +2471,9 @@ fn prepare_addr( // hit like so: // // * For wasm32, wasmtime defaults to 4gb "static" memories with 2gb guard - // regions. This means our `adjusted_offset` is 1 for all offsets <=2gb. - // This hits the optimized case for `heap_addr` on static memories 4gb in - // size in cranelift's legalization of `heap_addr`, eliding the bounds - // check entirely. + // regions. This means that for all offsets <=2gb, we hit the optimized + // case for `heap_addr` on static memories 4gb in size in cranelift's + // legalization of `heap_addr`, eliding the bounds check entirely. // // * For wasm64 offsets <=2gb will generate a single `heap_addr` // instruction, but at this time all heaps are "dyanmic" which means that @@ -2324,43 +2484,21 @@ fn prepare_addr( // offsets in `memarg` are <=2gb, which means we get the fast path of one // `heap_addr` instruction plus a hardcoded i32-offset in memory-related // instructions. - let adjusted_offset = if offset_guard_size == 0 { - // Why saturating? see (1) above - memarg.offset.saturating_add(u64::from(access_size)) - } else { - // Why is there rounding here? see (2) above - assert!(access_size < 1024); - cmp::max(memarg.offset / offset_guard_size * offset_guard_size, 1) - }; - - debug_assert!(adjusted_offset > 0); // want to bounds check at least 1 byte - let (addr, offset) = match u32::try_from(adjusted_offset) { - // If our adjusted offset fits within a u32, then we can place the - // entire offset into the offset of the `heap_addr` instruction. After - // the `heap_addr` instruction, though, we need to factor the the offset - // into the returned address. This is either an immediate to later - // memory instructions if the offset further fits within `i32`, or a - // manual add instruction otherwise. - // - // Note that native instructions take a signed offset hence the switch - // to i32. Note also the lack of overflow checking in the offset - // addition, which should be ok since if `heap_addr` passed we're - // guaranteed that this won't overflow. - Ok(adjusted_offset) => { - let base = builder - .ins() - .heap_addr(environ.pointer_type(), heap, addr, adjusted_offset); - match i32::try_from(memarg.offset) { - Ok(val) => (base, val), - Err(_) => { - let adj = builder.ins().iadd_imm(base, memarg.offset as i64); - (adj, 0) - } - } - } + let heap = environ.heaps()[heap].clone(); + let addr = match u32::try_from(memarg.offset) { + // If our offset fits within a u32, then we can place the it into the + // offset immediate of the `heap_addr` instruction. + Ok(offset) => bounds_checks::bounds_check_and_compute_addr( + builder, + environ, + &heap, + index, + offset, + access_size, + )?, - // If the adjusted offset doesn't fit within a u32, then we can't pass - // the adjust sized to `heap_addr` raw. + // If the offset doesn't fit within a u32, then we can't pass it + // directly into `heap_addr`. // // One reasonable question you might ask is "why not?". There's no // fundamental reason why `heap_addr` *must* take a 32-bit offset. The @@ -2379,8 +2517,6 @@ fn prepare_addr( // // Once we have the effective address, offset already folded in, then // `heap_addr` is used to verify that the address is indeed in-bounds. - // The access size of the `heap_addr` is what we were passed in from - // above. // // Note that this is generating what's likely to be at least two // branches, one for the overflow and one for the bounds check itself. @@ -2388,20 +2524,25 @@ fn prepare_addr( // relatively odd/rare. In the future if needed we can look into // optimizing this more. Err(_) => { - let index_type = builder.func.heaps[heap].index_type; - let offset = builder.ins().iconst(index_type, memarg.offset as i64); - let (addr, overflow) = builder.ins().iadd_ifcout(addr, offset); - builder.ins().trapif( - environ.unsigned_add_overflow_condition(), - overflow, - ir::TrapCode::HeapOutOfBounds, - ); - let base = builder - .ins() - .heap_addr(environ.pointer_type(), heap, addr, access_size); - (base, 0) + let offset = builder.ins().iconst(heap.index_type, memarg.offset as i64); + let adjusted_index = + builder + .ins() + .uadd_overflow_trap(index, offset, ir::TrapCode::HeapOutOfBounds); + bounds_checks::bounds_check_and_compute_addr( + builder, + environ, + &heap, + adjusted_index, + 0, + access_size, + )? } }; + let addr = match addr { + Reachability::Unreachable => return Ok(Reachability::Unreachable), + Reachability::Reachable(a) => a, + }; // Note that we don't set `is_aligned` here, even if the load instruction's // alignment immediate may says it's aligned, because WebAssembly's @@ -2416,16 +2557,15 @@ fn prepare_addr( // vmctx, stack) accesses. flags.set_heap(); - Ok((flags, addr, offset.into())) + Ok(Reachability::Reachable((flags, addr))) } -fn prepare_atomic_addr( - memarg: &MemoryImmediate, - loaded_bytes: u32, +fn align_atomic_addr( + memarg: &MemArg, + loaded_bytes: u8, builder: &mut FunctionBuilder, state: &mut FuncTranslationState, - environ: &mut FE, -) -> WasmResult<(MemFlags, Value)> { +) { // Atomic addresses must all be aligned correctly, and for now we check // alignment before we check out-of-bounds-ness. The order of this check may // need to be updated depending on the outcome of the official threads @@ -2450,80 +2590,96 @@ fn prepare_atomic_addr( let misalignment = builder .ins() .band_imm(effective_addr, i64::from(loaded_bytes - 1)); - let f = builder.ins().ifcmp_imm(misalignment, 0); - builder - .ins() - .trapif(IntCC::NotEqual, f, ir::TrapCode::HeapMisaligned); + let f = builder.ins().icmp_imm(IntCC::NotEqual, misalignment, 0); + builder.ins().trapnz(f, ir::TrapCode::HeapMisaligned); } +} - let (flags, mut addr, offset) = prepare_addr(memarg, loaded_bytes, builder, state, environ)?; - - // Currently cranelift IR operations for atomics don't have offsets - // associated with them so we fold the offset into the address itself. Note - // that via the `prepare_addr` helper we know that if execution reaches - // this point that this addition won't overflow. - let offset: i64 = offset.into(); - if offset != 0 { - addr = builder.ins().iadd_imm(addr, offset); - } +/// Like `prepare_addr` but for atomic accesses. +/// +/// Returns `None` when the Wasm access will unconditionally trap. +fn prepare_atomic_addr( + memarg: &MemArg, + loaded_bytes: u8, + builder: &mut FunctionBuilder, + state: &mut FuncTranslationState, + environ: &mut FE, +) -> WasmResult> { + align_atomic_addr(memarg, loaded_bytes, builder, state); + prepare_addr(memarg, loaded_bytes, builder, state, environ) +} - Ok((flags, addr)) +/// Like `Option` but specifically for passing information about transitions +/// from reachable to unreachable state and the like from callees to callers. +/// +/// Marked `must_use` to force callers to update +/// `FuncTranslationState::reachable` as necessary. +#[derive(PartialEq, Eq)] +#[must_use] +pub enum Reachability { + /// The Wasm execution state is reachable, here is a `T`. + Reachable(T), + /// The Wasm execution state has been determined to be statically + /// unreachable. It is the receiver of this value's responsibility to update + /// `FuncTranslationState::reachable` as necessary. + Unreachable, } /// Translate a load instruction. +/// +/// Returns the execution state's reachability after the load is translated. fn translate_load( - memarg: &MemoryImmediate, + memarg: &MemArg, opcode: ir::Opcode, result_ty: Type, builder: &mut FunctionBuilder, state: &mut FuncTranslationState, environ: &mut FE, -) -> WasmResult<()> { - let (flags, base, offset) = prepare_addr( +) -> WasmResult> { + let (flags, base) = match prepare_addr( memarg, mem_op_size(opcode, result_ty), builder, state, environ, - )?; - let (load, dfg) = builder.ins().Load(opcode, result_ty, flags, offset, base); + )? { + Reachability::Unreachable => return Ok(Reachability::Unreachable), + Reachability::Reachable((f, b)) => (f, b), + }; + let (load, dfg) = builder + .ins() + .Load(opcode, result_ty, flags, Offset32::new(0), base); state.push1(dfg.first_result(load)); - Ok(()) + Ok(Reachability::Reachable(())) } /// Translate a store instruction. fn translate_store( - memarg: &MemoryImmediate, + memarg: &MemArg, opcode: ir::Opcode, builder: &mut FunctionBuilder, state: &mut FuncTranslationState, environ: &mut FE, ) -> WasmResult<()> { - let mut val = state.pop1(); - let mut val_ty = builder.func.dfg.value_type(val); - - // Boolean-vector types don't validate with a `store` instruction, so - // bitcast them to a vector type which is compatible with the store - // instruction. - if val_ty.is_vector() && val_ty.lane_type().is_bool() { - val = builder.ins().raw_bitcast(I8X16, val); - val_ty = I8X16; - } + let val = state.pop1(); + let val_ty = builder.func.dfg.value_type(val); - let (flags, base, offset) = - prepare_addr(memarg, mem_op_size(opcode, val_ty), builder, state, environ)?; + let (flags, base) = unwrap_or_return_unreachable_state!( + state, + prepare_addr(memarg, mem_op_size(opcode, val_ty), builder, state, environ)? + ); builder .ins() - .Store(opcode, val_ty, flags, offset.into(), val, base); + .Store(opcode, val_ty, flags, Offset32::new(0), val, base); Ok(()) } -fn mem_op_size(opcode: ir::Opcode, ty: Type) -> u32 { +fn mem_op_size(opcode: ir::Opcode, ty: Type) -> u8 { match opcode { ir::Opcode::Istore8 | ir::Opcode::Sload8 | ir::Opcode::Uload8 => 1, ir::Opcode::Istore16 | ir::Opcode::Sload16 | ir::Opcode::Uload16 => 2, ir::Opcode::Istore32 | ir::Opcode::Sload32 | ir::Opcode::Uload32 => 4, - ir::Opcode::Store | ir::Opcode::Load => ty.bytes(), + ir::Opcode::Store | ir::Opcode::Load => u8::try_from(ty.bytes()).unwrap(), _ => panic!("unknown size of mem op for {:?}", opcode), } } @@ -2531,14 +2687,14 @@ fn mem_op_size(opcode: ir::Opcode, ty: Type) -> u32 { fn translate_icmp(cc: IntCC, builder: &mut FunctionBuilder, state: &mut FuncTranslationState) { let (arg0, arg1) = state.pop2(); let val = builder.ins().icmp(cc, arg0, arg1); - state.push1(builder.ins().bint(I32, val)); + state.push1(builder.ins().uextend(I32, val)); } fn translate_atomic_rmw( widened_ty: Type, access_ty: Type, op: AtomicRmwOp, - memarg: &MemoryImmediate, + memarg: &MemArg, builder: &mut FunctionBuilder, state: &mut FuncTranslationState, environ: &mut FE, @@ -2568,7 +2724,16 @@ fn translate_atomic_rmw( arg2 = builder.ins().ireduce(access_ty, arg2); } - let (flags, addr) = prepare_atomic_addr(memarg, access_ty.bytes(), builder, state, environ)?; + let (flags, addr) = unwrap_or_return_unreachable_state!( + state, + prepare_atomic_addr( + memarg, + u8::try_from(access_ty.bytes()).unwrap(), + builder, + state, + environ, + )? + ); let mut res = builder.ins().atomic_rmw(access_ty, flags, op, addr, arg2); if access_ty != widened_ty { @@ -2581,7 +2746,7 @@ fn translate_atomic_rmw( fn translate_atomic_cas( widened_ty: Type, access_ty: Type, - memarg: &MemoryImmediate, + memarg: &MemArg, builder: &mut FunctionBuilder, state: &mut FuncTranslationState, environ: &mut FE, @@ -2616,7 +2781,16 @@ fn translate_atomic_cas( replacement = builder.ins().ireduce(access_ty, replacement); } - let (flags, addr) = prepare_atomic_addr(memarg, access_ty.bytes(), builder, state, environ)?; + let (flags, addr) = unwrap_or_return_unreachable_state!( + state, + prepare_atomic_addr( + memarg, + u8::try_from(access_ty.bytes()).unwrap(), + builder, + state, + environ, + )? + ); let mut res = builder.ins().atomic_cas(flags, addr, expected, replacement); if access_ty != widened_ty { res = builder.ins().uextend(widened_ty, res); @@ -2628,7 +2802,7 @@ fn translate_atomic_cas( fn translate_atomic_load( widened_ty: Type, access_ty: Type, - memarg: &MemoryImmediate, + memarg: &MemArg, builder: &mut FunctionBuilder, state: &mut FuncTranslationState, environ: &mut FE, @@ -2650,7 +2824,16 @@ fn translate_atomic_load( }; assert!(w_ty_ok && widened_ty.bytes() >= access_ty.bytes()); - let (flags, addr) = prepare_atomic_addr(memarg, access_ty.bytes(), builder, state, environ)?; + let (flags, addr) = unwrap_or_return_unreachable_state!( + state, + prepare_atomic_addr( + memarg, + u8::try_from(access_ty.bytes()).unwrap(), + builder, + state, + environ, + )? + ); let mut res = builder.ins().atomic_load(access_ty, flags, addr); if access_ty != widened_ty { res = builder.ins().uextend(widened_ty, res); @@ -2661,7 +2844,7 @@ fn translate_atomic_load( fn translate_atomic_store( access_ty: Type, - memarg: &MemoryImmediate, + memarg: &MemArg, builder: &mut FunctionBuilder, state: &mut FuncTranslationState, environ: &mut FE, @@ -2690,7 +2873,16 @@ fn translate_atomic_store( data = builder.ins().ireduce(access_ty, data); } - let (flags, addr) = prepare_atomic_addr(memarg, access_ty.bytes(), builder, state, environ)?; + let (flags, addr) = unwrap_or_return_unreachable_state!( + state, + prepare_atomic_addr( + memarg, + u8::try_from(access_ty.bytes()).unwrap(), + builder, + state, + environ, + )? + ); builder.ins().atomic_store(flags, data, addr); Ok(()) } @@ -2710,7 +2902,7 @@ fn translate_vector_icmp( fn translate_fcmp(cc: FloatCC, builder: &mut FunctionBuilder, state: &mut FuncTranslationState) { let (arg0, arg1) = state.pop2(); let val = builder.ins().fcmp(cc, arg0, arg1); - state.push1(builder.ins().bint(I32, val)); + state.push1(builder.ins().uextend(I32, val)); } fn translate_vector_fcmp( @@ -2732,10 +2924,9 @@ fn translate_br_if( ) { let val = state.pop1(); let (br_destination, inputs) = translate_br_if_args(relative_depth, state); - canonicalise_then_brnz(builder, val, br_destination, inputs); - let next_block = builder.create_block(); - canonicalise_then_jump(builder, next_block, &[]); + canonicalise_brif(builder, val, br_destination, inputs, next_block, &[]); + builder.seal_block(next_block); // The only predecessor is the current block. builder.switch_to_block(next_block); } @@ -2809,7 +3000,7 @@ fn type_of(operator: &Operator) -> Type { | Operator::I8x16MinU | Operator::I8x16MaxS | Operator::I8x16MaxU - | Operator::I8x16RoundingAverageU + | Operator::I8x16AvgrU | Operator::I8x16Bitmask | Operator::I8x16Popcnt => I8X16, @@ -2846,7 +3037,7 @@ fn type_of(operator: &Operator) -> Type { | Operator::I16x8MinU | Operator::I16x8MaxS | Operator::I16x8MaxU - | Operator::I16x8RoundingAverageU + | Operator::I16x8AvgrU | Operator::I16x8Mul | Operator::I16x8Bitmask => I16X8, @@ -2969,14 +3160,16 @@ fn type_of(operator: &Operator) -> Type { } /// Some SIMD operations only operate on I8X16 in CLIF; this will convert them to that type by -/// adding a raw_bitcast if necessary. +/// adding a bitcast if necessary. fn optionally_bitcast_vector( value: Value, needed_type: Type, builder: &mut FunctionBuilder, ) -> Value { if builder.func.dfg.value_type(value) != needed_type { - builder.ins().raw_bitcast(needed_type, value) + let mut flags = MemFlags::new(); + flags.set_endianness(ir::Endianness::Little); + builder.ins().bitcast(needed_type, flags, value) } else { value } @@ -2985,7 +3178,7 @@ fn optionally_bitcast_vector( #[inline(always)] fn is_non_canonical_v128(ty: ir::Type) -> bool { match ty { - B8X16 | B16X8 | B32X4 | B64X2 | I64X2 | I32X4 | I16X8 | F32X4 | F64X2 => true, + I64X2 | I32X4 | I16X8 | F32X4 | F64X2 => true, _ => false, } } @@ -3011,7 +3204,9 @@ fn canonicalise_v128_values<'a>( // Otherwise we'll have to cast, and push the resulting `Value`s into `canonicalised`. for v in values { tmp_canonicalised.push(if is_non_canonical_v128(builder.func.dfg.value_type(*v)) { - builder.ins().raw_bitcast(I8X16, *v) + let mut flags = MemFlags::new(); + flags.set_endianness(ir::Endianness::Little); + builder.ins().bitcast(I8X16, flags, *v) } else { *v }); @@ -3032,28 +3227,28 @@ fn canonicalise_then_jump( builder.ins().jump(destination, canonicalised) } -/// The same but for a `brz` instruction. -fn canonicalise_then_brz( +/// The same but for a `brif` instruction. +fn canonicalise_brif( builder: &mut FunctionBuilder, cond: ir::Value, - destination: ir::Block, - params: &[Value], + block_then: ir::Block, + params_then: &[ir::Value], + block_else: ir::Block, + params_else: &[ir::Value], ) -> ir::Inst { - let mut tmp_canonicalised = SmallVec::<[ir::Value; 16]>::new(); - let canonicalised = canonicalise_v128_values(&mut tmp_canonicalised, builder, params); - builder.ins().brz(cond, destination, canonicalised) -} - -/// The same but for a `brnz` instruction. -fn canonicalise_then_brnz( - builder: &mut FunctionBuilder, - cond: ir::Value, - destination: ir::Block, - params: &[Value], -) -> ir::Inst { - let mut tmp_canonicalised = SmallVec::<[ir::Value; 16]>::new(); - let canonicalised = canonicalise_v128_values(&mut tmp_canonicalised, builder, params); - builder.ins().brnz(cond, destination, canonicalised) + let mut tmp_canonicalised_then = SmallVec::<[ir::Value; 16]>::new(); + let canonicalised_then = + canonicalise_v128_values(&mut tmp_canonicalised_then, builder, params_then); + let mut tmp_canonicalised_else = SmallVec::<[ir::Value; 16]>::new(); + let canonicalised_else = + canonicalise_v128_values(&mut tmp_canonicalised_else, builder, params_else); + builder.ins().brif( + cond, + block_then, + canonicalised_then, + block_else, + canonicalised_else, + ) } /// A helper for popping and bitcasting a single value; since SIMD values can lose their type by @@ -3122,7 +3317,7 @@ fn bitcast_arguments<'a>( /// A helper for bitcasting a sequence of return values for the function currently being built. If /// a value is a vector type that does not match its expected type, this will modify the value in -/// place to point to the result of a `raw_bitcast`. This conversion is necessary to translate Wasm +/// place to point to the result of a `bitcast`. This conversion is necessary to translate Wasm /// code that uses `V128` as function parameters (or implicitly in block parameters) and still use /// specific CLIF types (e.g. `I32X4`) in the function body. pub fn bitcast_wasm_returns( @@ -3134,7 +3329,9 @@ pub fn bitcast_wasm_returns( environ.is_wasm_return(&builder.func.signature, i) }); for (t, arg) in changes { - *arg = builder.ins().raw_bitcast(t, *arg); + let mut flags = MemFlags::new(); + flags.set_endianness(ir::Endianness::Little); + *arg = builder.ins().bitcast(t, flags, *arg); } } @@ -3150,6 +3347,8 @@ fn bitcast_wasm_params( environ.is_wasm_parameter(&callee_signature, i) }); for (t, arg) in changes { - *arg = builder.ins().raw_bitcast(t, *arg); + let mut flags = MemFlags::new(); + flags.set_endianness(ir::Endianness::Little); + *arg = builder.ins().bitcast(t, flags, *arg); } } diff --git a/cranelift/wasm/src/code_translator/bounds_checks.rs b/cranelift/wasm/src/code_translator/bounds_checks.rs new file mode 100644 index 000000000000..edb02b9492de --- /dev/null +++ b/cranelift/wasm/src/code_translator/bounds_checks.rs @@ -0,0 +1,413 @@ +//! Implementation of Wasm to CLIF memory access translation. +//! +//! Given +//! +//! * a dynamic Wasm memory index operand, +//! * a static offset immediate, and +//! * a static access size, +//! +//! bounds check the memory access and translate it into a native memory access. + +use super::Reachability; +use crate::{FuncEnvironment, HeapData, HeapStyle}; +use cranelift_codegen::{ + cursor::{Cursor, FuncCursor}, + ir::{self, condcodes::IntCC, InstBuilder, RelSourceLoc}, +}; +use cranelift_frontend::FunctionBuilder; +use wasmtime_types::WasmResult; +use Reachability::*; + +/// Helper used to emit bounds checks (as necessary) and compute the native +/// address of a heap access. +/// +/// Returns the `ir::Value` holding the native address of the heap access, or +/// `None` if the heap access will unconditionally trap. +pub fn bounds_check_and_compute_addr( + builder: &mut FunctionBuilder, + env: &mut Env, + heap: &HeapData, + // Dynamic operand indexing into the heap. + index: ir::Value, + // Static immediate added to the index. + offset: u32, + // Static size of the heap access. + access_size: u8, +) -> WasmResult> +where + Env: FuncEnvironment + ?Sized, +{ + let index = cast_index_to_pointer_ty( + index, + heap.index_type, + env.pointer_type(), + &mut builder.cursor(), + ); + let offset_and_size = offset_plus_size(offset, access_size); + let spectre_mitigations_enabled = env.heap_access_spectre_mitigation(); + + // We need to emit code that will trap (or compute an address that will trap + // when accessed) if + // + // index + offset + access_size > bound + // + // or if the `index + offset + access_size` addition overflows. + // + // Note that we ultimately want a 64-bit integer (we only target 64-bit + // architectures at the moment) and that `offset` is a `u32` and + // `access_size` is a `u8`. This means that we can add the latter together + // as `u64`s without fear of overflow, and we only have to be concerned with + // whether adding in `index` will overflow. + // + // Finally, the following right-hand sides of the matches do have a little + // bit of duplicated code across them, but I think writing it this way is + // worth it for readability and seeing very clearly each of our cases for + // different bounds checks and optimizations of those bounds checks. It is + // intentionally written in a straightforward case-matching style that will + // hopefully make it easy to port to ISLE one day. + Ok(match heap.style { + // ====== Dynamic Memories ====== + // + // 1. First special case for when `offset + access_size == 1`: + // + // index + 1 > bound + // ==> index >= bound + // + // 1.a. When Spectre mitigations are enabled, avoid duplicating + // bounds checks between the mitigations and the regular bounds + // checks. + HeapStyle::Dynamic { bound_gv } if offset_and_size == 1 && spectre_mitigations_enabled => { + let bound = builder.ins().global_value(env.pointer_type(), bound_gv); + Reachable(compute_addr( + &mut builder.cursor(), + heap, + env.pointer_type(), + index, + offset, + Some(SpectreOobComparison { + cc: IntCC::UnsignedGreaterThanOrEqual, + lhs: index, + rhs: bound, + }), + )) + } + // 1.b. Emit explicit `index >= bound` bounds checks. + HeapStyle::Dynamic { bound_gv } if offset_and_size == 1 => { + let bound = builder.ins().global_value(env.pointer_type(), bound_gv); + let oob = builder + .ins() + .icmp(IntCC::UnsignedGreaterThanOrEqual, index, bound); + builder.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds); + Reachable(compute_addr( + &mut builder.cursor(), + heap, + env.pointer_type(), + index, + offset, + None, + )) + } + + // 2. Second special case for when `offset + access_size <= min_size`. + // + // We know that `bound >= min_size`, so we can do the following + // comparison, without fear of the right-hand side wrapping around: + // + // index + offset + access_size > bound + // ==> index > bound - (offset + access_size) + // + // 2.a. Dedupe bounds checks with Spectre mitigations. + HeapStyle::Dynamic { bound_gv } + if offset_and_size <= heap.min_size.into() && spectre_mitigations_enabled => + { + let bound = builder.ins().global_value(env.pointer_type(), bound_gv); + let adjusted_bound = builder.ins().iadd_imm(bound, -(offset_and_size as i64)); + Reachable(compute_addr( + &mut builder.cursor(), + heap, + env.pointer_type(), + index, + offset, + Some(SpectreOobComparison { + cc: IntCC::UnsignedGreaterThan, + lhs: index, + rhs: adjusted_bound, + }), + )) + } + // 2.b. Emit explicit `index > bound - (offset + access_size)` bounds + // checks. + HeapStyle::Dynamic { bound_gv } if offset_and_size <= heap.min_size.into() => { + let bound = builder.ins().global_value(env.pointer_type(), bound_gv); + let adjusted_bound = builder.ins().iadd_imm(bound, -(offset_and_size as i64)); + let oob = builder + .ins() + .icmp(IntCC::UnsignedGreaterThan, index, adjusted_bound); + builder.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds); + Reachable(compute_addr( + &mut builder.cursor(), + heap, + env.pointer_type(), + index, + offset, + None, + )) + } + + // 3. General case for dynamic memories: + // + // index + offset + access_size > bound + // + // And we have to handle the overflow case in the left-hand side. + // + // 3.a. Dedupe bounds checks with Spectre mitigations. + HeapStyle::Dynamic { bound_gv } if spectre_mitigations_enabled => { + let access_size_val = builder + .ins() + .iconst(env.pointer_type(), offset_and_size as i64); + let adjusted_index = builder.ins().uadd_overflow_trap( + index, + access_size_val, + ir::TrapCode::HeapOutOfBounds, + ); + let bound = builder.ins().global_value(env.pointer_type(), bound_gv); + Reachable(compute_addr( + &mut builder.cursor(), + heap, + env.pointer_type(), + index, + offset, + Some(SpectreOobComparison { + cc: IntCC::UnsignedGreaterThan, + lhs: adjusted_index, + rhs: bound, + }), + )) + } + // 3.b. Emit an explicit `index + offset + access_size > bound` + // check. + HeapStyle::Dynamic { bound_gv } => { + let access_size_val = builder + .ins() + .iconst(env.pointer_type(), offset_and_size as i64); + let adjusted_index = builder.ins().uadd_overflow_trap( + index, + access_size_val, + ir::TrapCode::HeapOutOfBounds, + ); + let bound = builder.ins().global_value(env.pointer_type(), bound_gv); + let oob = builder + .ins() + .icmp(IntCC::UnsignedGreaterThan, adjusted_index, bound); + builder.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds); + Reachable(compute_addr( + &mut builder.cursor(), + heap, + env.pointer_type(), + index, + offset, + None, + )) + } + + // ====== Static Memories ====== + // + // With static memories we know the size of the heap bound at compile + // time. + // + // 1. First special case: trap immediately if `offset + access_size > + // bound`, since we will end up being out-of-bounds regardless of the + // given `index`. + HeapStyle::Static { bound } if offset_and_size > bound.into() => { + env.before_unconditionally_trapping_memory_access(builder)?; + builder.ins().trap(ir::TrapCode::HeapOutOfBounds); + Unreachable + } + + // 2. Second special case for when we can completely omit explicit + // bounds checks for 32-bit static memories. + // + // First, let's rewrite our comparison to move all of the constants + // to one side: + // + // index + offset + access_size > bound + // ==> index > bound - (offset + access_size) + // + // We know the subtraction on the right-hand side won't wrap because + // we didn't hit the first special case. + // + // Additionally, we add our guard pages (if any) to the right-hand + // side, since we can rely on the virtual memory subsystem at runtime + // to catch out-of-bound accesses within the range `bound .. bound + + // guard_size`. So now we are dealing with + // + // index > bound + guard_size - (offset + access_size) + // + // Note that `bound + guard_size` cannot overflow for + // correctly-configured heaps, as otherwise the heap wouldn't fit in + // a 64-bit memory space. + // + // The complement of our should-this-trap comparison expression is + // the should-this-not-trap comparison expression: + // + // index <= bound + guard_size - (offset + access_size) + // + // If we know the right-hand side is greater than or equal to + // `u32::MAX`, then + // + // index <= u32::MAX <= bound + guard_size - (offset + access_size) + // + // This expression is always true when the heap is indexed with + // 32-bit integers because `index` cannot be larger than + // `u32::MAX`. This means that `index` is always either in bounds or + // within the guard page region, neither of which require emitting an + // explicit bounds check. + HeapStyle::Static { bound } + if heap.index_type == ir::types::I32 + && u64::from(u32::MAX) + <= u64::from(bound) + u64::from(heap.offset_guard_size) - offset_and_size => + { + Reachable(compute_addr( + &mut builder.cursor(), + heap, + env.pointer_type(), + index, + offset, + None, + )) + } + + // 3. General case for static memories. + // + // We have to explicitly test whether + // + // index > bound - (offset + access_size) + // + // and trap if so. + // + // Since we have to emit explicit bounds checks, we might as well be + // precise, not rely on the virtual memory subsystem at all, and not + // factor in the guard pages here. + // + // 3.a. Dedupe the Spectre mitigation and the explicit bounds check. + HeapStyle::Static { bound } if spectre_mitigations_enabled => { + // NB: this subtraction cannot wrap because we didn't hit the first + // special case. + let adjusted_bound = u64::from(bound) - offset_and_size; + let adjusted_bound = builder + .ins() + .iconst(env.pointer_type(), adjusted_bound as i64); + Reachable(compute_addr( + &mut builder.cursor(), + heap, + env.pointer_type(), + index, + offset, + Some(SpectreOobComparison { + cc: IntCC::UnsignedGreaterThan, + lhs: index, + rhs: adjusted_bound, + }), + )) + } + // 3.b. Emit the explicit `index > bound - (offset + access_size)` + // check. + HeapStyle::Static { bound } => { + // See comment in 3.a. above. + let adjusted_bound = u64::from(bound) - offset_and_size; + let oob = + builder + .ins() + .icmp_imm(IntCC::UnsignedGreaterThan, index, adjusted_bound as i64); + builder.ins().trapnz(oob, ir::TrapCode::HeapOutOfBounds); + Reachable(compute_addr( + &mut builder.cursor(), + heap, + env.pointer_type(), + index, + offset, + None, + )) + } + }) +} + +fn cast_index_to_pointer_ty( + index: ir::Value, + index_ty: ir::Type, + pointer_ty: ir::Type, + pos: &mut FuncCursor, +) -> ir::Value { + if index_ty == pointer_ty { + return index; + } + // Note that using 64-bit heaps on a 32-bit host is not currently supported, + // would require at least a bounds check here to ensure that the truncation + // from 64-to-32 bits doesn't lose any upper bits. For now though we're + // mostly interested in the 32-bit-heaps-on-64-bit-hosts cast. + assert!(index_ty.bits() < pointer_ty.bits()); + + // Convert `index` to `addr_ty`. + let extended_index = pos.ins().uextend(pointer_ty, index); + + // Add debug value-label alias so that debuginfo can name the extended + // value as the address + let loc = pos.srcloc(); + let loc = RelSourceLoc::from_base_offset(pos.func.params.base_srcloc(), loc); + pos.func + .stencil + .dfg + .add_value_label_alias(extended_index, loc, index); + + extended_index +} + +struct SpectreOobComparison { + cc: IntCC, + lhs: ir::Value, + rhs: ir::Value, +} + +/// Emit code for the base address computation of a `heap_addr` instruction, +/// without any bounds checks (other than optional Spectre mitigations). +fn compute_addr( + pos: &mut FuncCursor, + heap: &HeapData, + addr_ty: ir::Type, + index: ir::Value, + offset: u32, + // If we are performing Spectre mitigation with conditional selects, the + // values to compare and the condition code that indicates an out-of bounds + // condition; on this condition, the conditional move will choose a + // speculatively safe address (a zero / null pointer) instead. + spectre_oob_comparison: Option, +) -> ir::Value { + debug_assert_eq!(pos.func.dfg.value_type(index), addr_ty); + + // Add the heap base address base + let base = pos.ins().global_value(addr_ty, heap.base); + + let final_base = pos.ins().iadd(base, index); + let final_addr = if offset == 0 { + final_base + } else { + // NB: The addition of the offset immediate must happen *before* the + // `select_spectre_guard`. If it happens after, then we potentially are + // letting speculative execution read the whole first 4GiB of memory. + pos.ins().iadd_imm(final_base, offset as i64) + }; + + if let Some(SpectreOobComparison { cc, lhs, rhs }) = spectre_oob_comparison { + let null = pos.ins().iconst(addr_ty, 0); + let cmp = pos.ins().icmp(cc, lhs, rhs); + pos.ins().select_spectre_guard(cmp, null, final_addr) + } else { + final_addr + } +} + +#[inline] +fn offset_plus_size(offset: u32, size: u8) -> u64 { + // Cannot overflow because we are widening to `u64`. + offset as u64 + size as u64 +} diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs index a8bb65cfbdd4..27f77273130f 100644 --- a/cranelift/wasm/src/environ/dummy.rs +++ b/cranelift/wasm/src/environ/dummy.rs @@ -10,14 +10,14 @@ use crate::func_translator::FuncTranslator; use crate::state::FuncTranslationState; use crate::WasmType; use crate::{ - DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, - Table, TableIndex, TypeIndex, WasmFuncType, WasmResult, + DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex, Heap, HeapData, + HeapStyle, Memory, MemoryIndex, Table, TableIndex, TypeIndex, WasmFuncType, WasmResult, }; use core::convert::TryFrom; use cranelift_codegen::cursor::FuncCursor; use cranelift_codegen::ir::immediates::{Offset32, Uimm64}; -use cranelift_codegen::ir::types::*; use cranelift_codegen::ir::{self, InstBuilder}; +use cranelift_codegen::ir::{types::*, UserFuncName}; use cranelift_codegen::isa::{CallConv, TargetFrontendConfig}; use cranelift_entity::{EntityRef, PrimaryMap, SecondaryMap}; use cranelift_frontend::FunctionBuilder; @@ -26,11 +26,6 @@ use std::string::String; use std::vec::Vec; use wasmparser::{FuncValidator, FunctionBody, Operator, ValidatorResources, WasmFeatures}; -/// Compute a `ir::ExternalName` for a given wasm function index. -fn get_func_name(func_index: FuncIndex) -> ir::ExternalName { - ir::ExternalName::user(0, func_index.as_u32()) -} - /// A collection of names under which a given entity is exported. pub struct Exportable { /// A wasm entity. @@ -143,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, @@ -158,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 { @@ -181,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 } @@ -210,13 +207,18 @@ 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. expected_reachability: Option, + + /// Heaps we have created to implement Wasm linear memories. + pub heaps: PrimaryMap, } impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> { + /// Construct a new `DummyFuncEnvironment`. pub fn new( mod_info: &'dummy_environment DummyModuleInfo, expected_reachability: Option, @@ -224,12 +226,13 @@ impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> { Self { mod_info, expected_reachability, + heaps: Default::default(), } } - // 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(), @@ -251,6 +254,10 @@ impl<'dummy_environment> TargetEnvironment for DummyFuncEnvironment<'dummy_envir fn target_config(&self) -> TargetFrontendConfig { self.mod_info.config } + + fn heap_access_spectre_mitigation(&self) -> bool { + false + } } impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environment> { @@ -277,7 +284,11 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ }) } - fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> WasmResult { + fn heaps(&self) -> &PrimaryMap { + &self.heaps + } + + fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> WasmResult { // Create a static heap whose base address is stored at `vmctx+0`. let addr = func.create_global_value(ir::GlobalValueData::VMContext); let gv = func.create_global_value(ir::GlobalValueData::Load { @@ -287,12 +298,12 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ readonly: true, }); - Ok(func.create_heap(ir::HeapData { + Ok(self.heaps.push(HeapData { base: gv, - min_size: 0.into(), - offset_guard_size: 0x8000_0000.into(), - style: ir::HeapStyle::Static { - bound: 0x1_0000_0000.into(), + min_size: 0, + offset_guard_size: 0x8000_0000, + style: HeapStyle::Static { + bound: 0x1_0000_0000, }, index_type: I32, })) @@ -342,7 +353,11 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ // A real implementation would probably add a `vmctx` argument. // And maybe attempt some signature de-duplication. let signature = func.import_signature(self.vmctx_sig(sigidx)); - let name = get_func_name(index); + let name = + ir::ExternalName::User(func.declare_imported_user_function(ir::UserExternalName { + namespace: 0, + index: index.as_u32(), + })); Ok(func.import_function(ir::ExtFuncData { name, signature, @@ -463,7 +478,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ &mut self, mut pos: FuncCursor, _index: MemoryIndex, - _heap: ir::Heap, + _heap: Heap, _val: ir::Value, ) -> WasmResult { Ok(pos.ins().iconst(I32, -1)) @@ -473,7 +488,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ &mut self, mut pos: FuncCursor, _index: MemoryIndex, - _heap: ir::Heap, + _heap: Heap, ) -> WasmResult { Ok(pos.ins().iconst(I32, -1)) } @@ -482,9 +497,9 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ &mut self, _pos: FuncCursor, _src_index: MemoryIndex, - _src_heap: ir::Heap, + _src_heap: Heap, _dst_index: MemoryIndex, - _dst_heap: ir::Heap, + _dst_heap: Heap, _dst: ir::Value, _src: ir::Value, _len: ir::Value, @@ -496,7 +511,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ &mut self, _pos: FuncCursor, _index: MemoryIndex, - _heap: ir::Heap, + _heap: Heap, _dst: ir::Value, _val: ir::Value, _len: ir::Value, @@ -508,7 +523,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ &mut self, _pos: FuncCursor, _index: MemoryIndex, - _heap: ir::Heap, + _heap: Heap, _seg_index: u32, _dst: ir::Value, _src: ir::Value, @@ -633,7 +648,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ &mut self, mut pos: FuncCursor, _index: MemoryIndex, - _heap: ir::Heap, + _heap: Heap, _addr: ir::Value, _expected: ir::Value, _timeout: ir::Value, @@ -645,7 +660,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ &mut self, mut pos: FuncCursor, _index: MemoryIndex, - _heap: ir::Heap, + _heap: Heap, _addr: ir::Value, _count: ir::Value, ) -> WasmResult { @@ -661,6 +676,10 @@ impl TargetEnvironment for DummyEnvironment { fn target_config(&self) -> TargetFrontendConfig { self.info.config } + + fn heap_access_spectre_mitigation(&self) -> bool { + false + } } impl<'data> ModuleEnvironment<'data> for DummyEnvironment { @@ -861,12 +880,15 @@ impl<'data> ModuleEnvironment<'data> for DummyEnvironment { DummyFuncEnvironment::new(&self.info, self.expected_reachability.clone()); let func_index = FuncIndex::new(self.get_num_func_imports() + self.info.function_bodies.len()); - let name = get_func_name(func_index); + let sig = func_environ.vmctx_sig(self.get_func_type(func_index)); - let mut func = ir::Function::with_name_signature(name, sig); + let mut func = + ir::Function::with_name_signature(UserFuncName::user(0, func_index.as_u32()), sig); + if self.debug_info { func.collect_debug_info(); } + self.trans .translate_body(&mut validator, body, &mut func, &mut func_environ)?; func diff --git a/cranelift/wasm/src/environ/mod.rs b/cranelift/wasm/src/environ/mod.rs index 03b6cec37108..34d930ac60d8 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/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index 5d5a4c5959b2..b44e03b054ce 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -8,14 +8,16 @@ use crate::state::FuncTranslationState; use crate::{ - DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex, Memory, MemoryIndex, SignatureIndex, - Table, TableIndex, Tag, TagIndex, TypeIndex, WasmError, WasmFuncType, WasmHeapType, WasmResult, + DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex, Heap, HeapData, Memory, MemoryIndex, + SignatureIndex, Table, TableIndex, Tag, TagIndex, TypeIndex, WasmError, WasmFuncType, + WasmHeapType, WasmResult, }; use core::convert::From; use cranelift_codegen::cursor::FuncCursor; use cranelift_codegen::ir::immediates::Offset32; use cranelift_codegen::ir::{self, InstBuilder}; use cranelift_codegen::isa::TargetFrontendConfig; +use cranelift_entity::PrimaryMap; use cranelift_frontend::FunctionBuilder; use std::boxed::Box; use std::string::ToString; @@ -46,6 +48,9 @@ pub trait TargetEnvironment { /// Get the information needed to produce Cranelift IR for the given target. fn target_config(&self) -> TargetFrontendConfig; + /// Whether to enable Spectre mitigations for heap accesses. + fn heap_access_spectre_mitigation(&self) -> bool; + /// Get the Cranelift integer type to use for native pointers. /// /// This returns `I64` for 64-bit architectures and `I32` for 32-bit architectures. @@ -112,11 +117,20 @@ pub trait FuncEnvironment: TargetEnvironment { index: GlobalIndex, ) -> WasmResult; + /// Get the heaps for this function environment. + /// + /// The returned map should provide heap format details (encoded in + /// `HeapData`) for each `Heap` that was previously returned by + /// `make_heap()`. The translator will first call make_heap for each Wasm + /// memory, and then later when translating code, will invoke `heaps()` to + /// learn how to access the environment's implementation of each memory. + fn heaps(&self) -> &PrimaryMap; + /// Set up the necessary preamble definitions in `func` to access the linear memory identified /// by `index`. /// /// The index space covers both imported and locally declared memories. - fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> WasmResult; + fn make_heap(&mut self, func: &mut ir::Function, index: MemoryIndex) -> WasmResult; /// Set up the necessary preamble definitions in `func` to access the table identified /// by `index`. @@ -165,7 +179,7 @@ pub trait FuncEnvironment: TargetEnvironment { /// The signature `sig_ref` was previously created by `make_indirect_sig()`. /// /// Return the call instruction whose results are the WebAssembly return values. - #[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))] + #[allow(clippy::too_many_arguments)] fn translate_call_indirect( &mut self, builder: &mut FunctionBuilder, @@ -222,7 +236,7 @@ pub trait FuncEnvironment: TargetEnvironment { &mut self, pos: FuncCursor, index: MemoryIndex, - heap: ir::Heap, + heap: Heap, val: ir::Value, ) -> WasmResult; @@ -236,7 +250,7 @@ pub trait FuncEnvironment: TargetEnvironment { &mut self, pos: FuncCursor, index: MemoryIndex, - heap: ir::Heap, + heap: Heap, ) -> WasmResult; /// Translate a `memory.copy` WebAssembly instruction. @@ -247,9 +261,9 @@ pub trait FuncEnvironment: TargetEnvironment { &mut self, pos: FuncCursor, src_index: MemoryIndex, - src_heap: ir::Heap, + src_heap: Heap, dst_index: MemoryIndex, - dst_heap: ir::Heap, + dst_heap: Heap, dst: ir::Value, src: ir::Value, len: ir::Value, @@ -263,7 +277,7 @@ pub trait FuncEnvironment: TargetEnvironment { &mut self, pos: FuncCursor, index: MemoryIndex, - heap: ir::Heap, + heap: Heap, dst: ir::Value, val: ir::Value, len: ir::Value, @@ -279,7 +293,7 @@ pub trait FuncEnvironment: TargetEnvironment { &mut self, pos: FuncCursor, index: MemoryIndex, - heap: ir::Heap, + heap: Heap, seg_index: u32, dst: ir::Value, src: ir::Value, @@ -398,7 +412,7 @@ pub trait FuncEnvironment: TargetEnvironment { value: ir::Value, ) -> WasmResult { let is_null = pos.ins().is_null(value); - Ok(pos.ins().bint(ir::types::I32, is_null)) + Ok(pos.ins().uextend(ir::types::I32, is_null)) } /// Translate a `ref.func` WebAssembly instruction. @@ -440,7 +454,7 @@ pub trait FuncEnvironment: TargetEnvironment { &mut self, pos: FuncCursor, index: MemoryIndex, - heap: ir::Heap, + heap: Heap, addr: ir::Value, expected: ir::Value, timeout: ir::Value, @@ -460,7 +474,7 @@ pub trait FuncEnvironment: TargetEnvironment { &mut self, pos: FuncCursor, index: MemoryIndex, - heap: ir::Heap, + heap: Heap, addr: ir::Value, count: ir::Value, ) -> WasmResult; @@ -496,6 +510,18 @@ pub trait FuncEnvironment: TargetEnvironment { Ok(()) } + /// Optional callback for the `FuncEnvironment` performing this translation + /// to maintain, prepare, or finalize custom, internal state when we + /// statically determine that a Wasm memory access will unconditionally + /// trap, rendering the rest of the block unreachable. Called just before + /// the unconditional trap is emitted. + fn before_unconditionally_trapping_memory_access( + &mut self, + _builder: &mut FunctionBuilder, + ) -> WasmResult<()> { + Ok(()) + } + /// Optional callback for the `FunctionEnvironment` performing this translation to perform work /// before the function body is translated. fn before_translate_function( diff --git a/cranelift/wasm/src/func_translator.rs b/cranelift/wasm/src/func_translator.rs index 6950f67369d7..3949342c30a0 100644 --- a/cranelift/wasm/src/func_translator.rs +++ b/cranelift/wasm/src/func_translator.rs @@ -93,12 +93,11 @@ impl FuncTranslator { debug_assert_eq!(func.dfg.num_blocks(), 0, "Function must be empty"); debug_assert_eq!(func.dfg.num_insts(), 0, "Function must be empty"); - // This clears the `FunctionBuilderContext`. let mut builder = FunctionBuilder::new(func, &mut self.func_ctx); builder.set_srcloc(cur_srcloc(&reader)); let entry_block = builder.create_block(); builder.append_block_params_for_function_params(entry_block); - builder.switch_to_block(entry_block); // This also creates values for the arguments. + builder.switch_to_block(entry_block); builder.seal_block(entry_block); // Declare all predecessors known. // Make sure the entry block is inserted in the layout before we make any callbacks to @@ -117,6 +116,7 @@ impl FuncTranslator { parse_function_body(validator, reader, &mut builder, &mut self.state, environ)?; builder.finalize(); + log::trace!("translated Wasm to CLIF:\n{}", func.display()); Ok(()) } } @@ -170,7 +170,7 @@ fn parse_local_decls( builder.set_srcloc(cur_srcloc(reader)); let pos = reader.original_position(); let count = reader.read_var_u32()?; - let ty = reader.read_val_type()?; + let ty = reader.read()?; validator.define_locals(pos, count, ty)?; declare_locals(builder, count, ty, &mut next_local, environ)?; } @@ -182,7 +182,7 @@ fn parse_local_decls( /// Declare `count` local variables of the same type, starting from `next_local`. /// -/// Fail of too many locals are declared in the function, or if the type is not valid for a local. +/// Fail if too many locals are declared in the function, or if the type is not valid for a local. fn declare_locals( builder: &mut FunctionBuilder, count: u32, @@ -232,13 +232,12 @@ fn parse_function_body( environ.before_translate_function(builder, state)?; while !reader.eof() { - let ty = validator.peek(); let pos = reader.original_position(); builder.set_srcloc(cur_srcloc(&reader)); let op = reader.read_operator()?; validator.op(pos, &op)?; environ.before_translate_operator(&op, builder, state)?; - translate_operator(validator, &op, builder, state, environ, ty)?; + translate_operator(validator, &op, builder, state, environ)?; environ.after_translate_operator(&op, builder, state)?; } environ.after_translate_function(builder, state)?; @@ -309,7 +308,7 @@ mod tests { let mut ctx = Context::new(); - ctx.func.name = ir::ExternalName::testcase("small1"); + ctx.func.name = ir::UserFuncName::testcase("small1"); ctx.func.signature.params.push(ir::AbiParam::new(I32)); ctx.func.signature.returns.push(ir::AbiParam::new(I32)); @@ -347,7 +346,7 @@ mod tests { let mut ctx = Context::new(); - ctx.func.name = ir::ExternalName::testcase("small2"); + ctx.func.name = ir::UserFuncName::testcase("small2"); ctx.func.signature.params.push(ir::AbiParam::new(I32)); ctx.func.signature.returns.push(ir::AbiParam::new(I32)); @@ -390,7 +389,7 @@ mod tests { let mut ctx = Context::new(); - ctx.func.name = ir::ExternalName::testcase("infloop"); + ctx.func.name = ir::UserFuncName::testcase("infloop"); ctx.func.signature.returns.push(ir::AbiParam::new(I32)); let (body, mut validator) = extract_func(&wasm); @@ -405,7 +404,10 @@ mod tests { let mut validator = Validator::new(); for payload in Parser::new(0).parse_all(wat) { match validator.payload(&payload.unwrap()).unwrap() { - ValidPayload::Func(validator, body) => return (body, validator), + ValidPayload::Func(validator, body) => { + let validator = validator.into_validator(Default::default()); + return (body, validator); + } _ => {} } } diff --git a/cranelift/wasm/src/heap.rs b/cranelift/wasm/src/heap.rs new file mode 100644 index 000000000000..2b2a9fb99b65 --- /dev/null +++ b/cranelift/wasm/src/heap.rs @@ -0,0 +1,99 @@ +//! Heaps to implement WebAssembly linear memories. + +use cranelift_codegen::ir::{GlobalValue, Type}; +use cranelift_entity::entity_impl; + +/// An opaque reference to a [`HeapData`][crate::HeapData]. +/// +/// While the order is stable, it is arbitrary. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Heap(u32); +entity_impl!(Heap, "heap"); + +/// A heap implementing a WebAssembly linear memory. +/// +/// Code compiled from WebAssembly runs in a sandbox where it can't access all +/// process memory. Instead, it is given a small set of memory areas to work in, +/// and all accesses are bounds checked. `cranelift-wasm` models this through +/// the concept of *heaps*. +/// +/// Heap addresses can be smaller than the native pointer size, for example +/// unsigned `i32` offsets on a 64-bit architecture. +/// +/// A heap appears as three consecutive ranges of address space: +/// +/// 1. The *mapped pages* are the accessible memory range in the heap. A heap +/// may have a minimum guaranteed size which means that some mapped pages are +/// always present. +/// +/// 2. The *unmapped pages* is a possibly empty range of address space that may +/// be mapped in the future when the heap is grown. They are addressable +/// but not accessible. +/// +/// 3. The *offset-guard pages* is a range of address space that is guaranteed +/// to always cause a trap when accessed. It is used to optimize bounds +/// checking for heap accesses with a shared base pointer. They are +/// addressable but not accessible. +/// +/// The *heap bound* is the total size of the mapped and unmapped pages. This is +/// the bound that `heap_addr` checks against. Memory accesses inside the heap +/// bounds can trap if they hit an unmapped page (which is not accessible). +/// +/// Two styles of heaps are supported, *static* and *dynamic*. They behave +/// differently when resized. +/// +/// #### Static heaps +/// +/// A *static heap* starts out with all the address space it will ever need, so it +/// never moves to a different address. At the base address is a number of mapped +/// pages corresponding to the heap's current size. Then follows a number of +/// unmapped pages where the heap can grow up to its maximum size. After the +/// unmapped pages follow the offset-guard pages which are also guaranteed to +/// generate a trap when accessed. +/// +/// #### Dynamic heaps +/// +/// A *dynamic heap* can be relocated to a different base address when it is +/// resized, and its bound can move dynamically. The offset-guard pages move +/// when the heap is resized. The bound of a dynamic heap is stored in a global +/// value. +#[derive(Clone, PartialEq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct HeapData { + /// The address of the start of the heap's storage. + pub base: GlobalValue, + + /// Guaranteed minimum heap size in bytes. Heap accesses before `min_size` + /// don't need bounds checking. + pub min_size: u64, + + /// Size in bytes of the offset-guard pages following the heap. + pub offset_guard_size: u64, + + /// Heap style, with additional style-specific info. + pub style: HeapStyle, + + /// The index type for the heap. + pub index_type: Type, +} + +/// Style of heap including style-specific information. +#[derive(Clone, PartialEq, Hash)] +#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))] +pub enum HeapStyle { + /// A dynamic heap can be relocated to a different base address when it is + /// grown. + Dynamic { + /// Global value providing the current bound of the heap in bytes. + bound_gv: GlobalValue, + }, + + /// A static heap has a fixed base address and a number of not-yet-allocated + /// pages before the offset-guard pages. + Static { + /// Heap bound in bytes. The offset-guard pages are allocated after the + /// bound. + bound: u64, + }, +} diff --git a/cranelift/wasm/src/lib.rs b/cranelift/wasm/src/lib.rs index 112159862920..695d62e7e6a5 100644 --- a/cranelift/wasm/src/lib.rs +++ b/cranelift/wasm/src/lib.rs @@ -51,18 +51,20 @@ use std::collections::{ mod code_translator; mod environ; mod func_translator; +mod heap; mod module_translator; mod sections_translator; 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::heap::{Heap, HeapData, HeapStyle}; pub use crate::module_translator::translate_module; -pub use crate::state::func_state::FuncTranslationState; -pub use crate::state::module_state::ModuleTranslationState; +pub use crate::state::FuncTranslationState; pub use crate::translation_utils::*; pub use cranelift_frontend::FunctionBuilder; pub use wasmtime_types::*; diff --git a/cranelift/wasm/src/module_translator.rs b/cranelift/wasm/src/module_translator.rs index 78feed45f429..6da7d0b5b0fb 100644 --- a/cranelift/wasm/src/module_translator.rs +++ b/cranelift/wasm/src/module_translator.rs @@ -6,7 +6,6 @@ use crate::sections_translator::{ parse_global_section, parse_import_section, parse_memory_section, parse_name_section, parse_start_section, parse_table_section, parse_tag_section, parse_type_section, }; -use crate::state::ModuleTranslationState; use crate::WasmResult; use cranelift_codegen::timing; use std::prelude::v1::*; @@ -17,9 +16,8 @@ use wasmparser::{NameSectionReader, Parser, Payload, Validator}; pub fn translate_module<'data>( data: &'data [u8], environ: &mut dyn ModuleEnvironment<'data>, -) -> WasmResult { +) -> WasmResult<()> { let _tt = timing::wasm_translate_module(); - let mut module_translation_state = ModuleTranslationState::new(); let mut validator = Validator::new_with_features(environ.wasm_features()); for payload in Parser::new(0).parse_all(data) { @@ -37,7 +35,7 @@ pub fn translate_module<'data>( Payload::TypeSection(types) => { validator.type_section(&types)?; - parse_type_section(types, &mut module_translation_state, environ)?; + parse_type_section(types, environ)?; } Payload::ImportSection(imports) => { @@ -91,7 +89,9 @@ pub fn translate_module<'data>( } Payload::CodeSectionEntry(body) => { - let func_validator = validator.code_section_entry(&body)?; + let func_validator = validator + .code_section_entry(&body)? + .into_validator(Default::default()); environ.define_function_body(func_validator, body)?; } @@ -108,9 +108,8 @@ pub fn translate_module<'data>( } Payload::CustomSection(s) if s.name() == "name" => { - let result = NameSectionReader::new(s.data(), s.data_offset()) - .map_err(|e| e.into()) - .and_then(|s| parse_name_section(s, environ)); + let result = + parse_name_section(NameSectionReader::new(s.data(), s.data_offset()), environ); if let Err(e) = result { log::warn!("failed to parse name section {:?}", e); } @@ -125,5 +124,5 @@ pub fn translate_module<'data>( } } - Ok(module_translation_state) + Ok(()) } diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs index fe56317e4971..37cca6b4fa7e 100644 --- a/cranelift/wasm/src/sections_translator.rs +++ b/cranelift/wasm/src/sections_translator.rs @@ -8,20 +8,17 @@ //! is handled, according to the semantics of WebAssembly, to only specific expressions that are //! interpreted on the fly. use crate::environ::ModuleEnvironment; -use crate::state::ModuleTranslationState; use crate::wasm_unsupported; use crate::{ DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Memory, MemoryIndex, Table, TableIndex, Tag, TagIndex, TypeIndex, WasmError, WasmResult, }; -use core::convert::TryFrom; -use core::convert::TryInto; use cranelift_entity::packed_option::ReservedValue; use cranelift_entity::EntityRef; use std::boxed::Box; use std::vec::Vec; use wasmparser::{ - self, Data, DataKind, DataSectionReader, Element, ElementItem, ElementItems, ElementKind, + self, Data, DataKind, DataSectionReader, Element, ElementItems, ElementKind, ElementSectionReader, Export, ExportSectionReader, ExternalKind, FunctionSectionReader, GlobalSectionReader, GlobalType, ImportSectionReader, MemorySectionReader, MemoryType, NameSectionReader, Naming, Operator, TableSectionReader, TableType, TagSectionReader, TagType, @@ -64,20 +61,15 @@ fn global(ty: GlobalType, initializer: GlobalInit) -> WasmResult { /// Parses the Type section of the wasm module. pub fn parse_type_section<'a>( types: TypeSectionReader<'a>, - module_translation_state: &mut ModuleTranslationState, environ: &mut dyn ModuleEnvironment<'a>, ) -> WasmResult<()> { - let count = types.get_count(); - module_translation_state.wasm_types.reserve(count as usize); + let count = types.count(); environ.reserve_types(count)?; for entry in types { match entry? { Type::Func(wasm_func_ty) => { environ.declare_type_func(wasm_func_ty.clone().try_into()?)?; - module_translation_state - .wasm_types - .push((wasm_func_ty.params, wasm_func_ty.returns)); } } } @@ -89,7 +81,7 @@ pub fn parse_import_section<'data>( imports: ImportSectionReader<'data>, environ: &mut dyn ModuleEnvironment<'data>, ) -> WasmResult<()> { - environ.reserve_imports(imports.get_count())?; + environ.reserve_imports(imports.count())?; for entry in imports { let import = entry?; @@ -127,7 +119,7 @@ pub fn parse_function_section( functions: FunctionSectionReader, environ: &mut dyn ModuleEnvironment, ) -> WasmResult<()> { - let num_functions = functions.get_count(); + let num_functions = functions.count(); if num_functions == std::u32::MAX { // We reserve `u32::MAX` for our own use in cranelift-entity. return Err(WasmError::ImplLimitExceeded); @@ -148,10 +140,10 @@ pub fn parse_table_section( tables: TableSectionReader, environ: &mut dyn ModuleEnvironment, ) -> WasmResult<()> { - environ.reserve_tables(tables.get_count())?; + environ.reserve_tables(tables.count())?; for entry in tables { - let ty = table(entry?); + let ty = table(entry?.ty); environ.declare_table(ty)?; } @@ -163,7 +155,7 @@ pub fn parse_memory_section( memories: MemorySectionReader, environ: &mut dyn ModuleEnvironment, ) -> WasmResult<()> { - environ.reserve_memories(memories.get_count())?; + environ.reserve_memories(memories.count())?; for entry in memories { let memory = memory(entry?); @@ -178,7 +170,7 @@ pub fn parse_tag_section( tags: TagSectionReader, environ: &mut dyn ModuleEnvironment, ) -> WasmResult<()> { - environ.reserve_tags(tags.get_count())?; + environ.reserve_tags(tags.count())?; for entry in tags { let tag = tag(entry?); @@ -193,7 +185,7 @@ pub fn parse_global_section( globals: GlobalSectionReader, environ: &mut dyn ModuleEnvironment, ) -> WasmResult<()> { - environ.reserve_globals(globals.get_count())?; + environ.reserve_globals(globals.count())?; for entry in globals { let wasmparser::Global { ty, init_expr } = entry?; @@ -206,7 +198,7 @@ pub fn parse_global_section( Operator::V128Const { value } => { GlobalInit::V128Const(u128::from_le_bytes(*value.bytes())) } - Operator::RefNull { ty: _ } => GlobalInit::RefNullConst, + Operator::RefNull { hty: _ } => GlobalInit::RefNullConst, Operator::RefFunc { function_index } => { GlobalInit::RefFunc(FuncIndex::from_u32(function_index)) } @@ -232,7 +224,7 @@ pub fn parse_export_section<'data>( exports: ExportSectionReader<'data>, environ: &mut dyn ModuleEnvironment<'data>, ) -> WasmResult<()> { - environ.reserve_exports(exports.get_count())?; + environ.reserve_exports(exports.count())?; for entry in exports { let Export { @@ -265,23 +257,28 @@ pub fn parse_start_section(index: u32, environ: &mut dyn ModuleEnvironment) -> W } fn read_elems(items: &ElementItems) -> WasmResult> { - let items_reader = items.get_items_reader()?; - let mut elems = Vec::with_capacity(usize::try_from(items_reader.get_count()).unwrap()); - for item in items_reader { - let elem = match item? { - ElementItem::Expr(init) => match init.get_binary_reader().read_operator()? { - Operator::RefNull { .. } => FuncIndex::reserved_value(), - Operator::RefFunc { function_index } => FuncIndex::from_u32(function_index), - s => { - return Err(WasmError::Unsupported(format!( - "unsupported init expr in element section: {:?}", - s - ))); - } - }, - ElementItem::Func(index) => FuncIndex::from_u32(index), - }; - elems.push(elem); + let mut elems = Vec::new(); + match items { + ElementItems::Functions(funcs) => { + for func in funcs.clone() { + elems.push(FuncIndex::from_u32(func?)); + } + } + ElementItems::Expressions(funcs) => { + for func in funcs.clone() { + let idx = match func?.get_binary_reader().read_operator()? { + Operator::RefNull { .. } => FuncIndex::reserved_value(), + Operator::RefFunc { function_index } => FuncIndex::from_u32(function_index), + s => { + return Err(WasmError::Unsupported(format!( + "unsupported init expr in element section: {:?}", + s + ))); + } + }; + elems.push(idx); + } + } } Ok(elems.into_boxed_slice()) } @@ -291,7 +288,7 @@ pub fn parse_element_section<'data>( elements: ElementSectionReader<'data>, environ: &mut dyn ModuleEnvironment, ) -> WasmResult<()> { - environ.reserve_table_elements(elements.get_count())?; + environ.reserve_table_elements(elements.count())?; for (index, entry) in elements.into_iter().enumerate() { let Element { @@ -304,10 +301,10 @@ pub fn parse_element_section<'data>( match kind { ElementKind::Active { table_index, - offset_expr: init_expr, + offset_expr, } => { - let mut init_expr_reader = init_expr.get_binary_reader(); - let (base, offset) = match init_expr_reader.read_operator()? { + let mut offset_expr_reader = offset_expr.get_binary_reader(); + let (base, offset) = match offset_expr_reader.read_operator()? { Operator::I32Const { value } => (None, value as u32), Operator::GlobalGet { global_index } => { (Some(GlobalIndex::from_u32(global_index)), 0) @@ -343,7 +340,7 @@ pub fn parse_data_section<'data>( data: DataSectionReader<'data>, environ: &mut dyn ModuleEnvironment<'data>, ) -> WasmResult<()> { - environ.reserve_data_initializers(data.get_count())?; + environ.reserve_data_initializers(data.count())?; for (index, entry) in data.into_iter().enumerate() { let Data { @@ -354,10 +351,10 @@ pub fn parse_data_section<'data>( match kind { DataKind::Active { memory_index, - offset_expr: init_expr, + offset_expr, } => { - let mut init_expr_reader = init_expr.get_binary_reader(); - let (base, offset) = match init_expr_reader.read_operator()? { + let mut offset_expr_reader = offset_expr.get_binary_reader(); + let (base, offset) = match offset_expr_reader.read_operator()? { Operator::I32Const { value } => (None, value as u64), Operator::I64Const { value } => (None, value as u64), Operator::GlobalGet { global_index } => { @@ -394,35 +391,27 @@ pub fn parse_name_section<'data>( ) -> WasmResult<()> { for subsection in names { match subsection? { - wasmparser::Name::Function(f) => { - let mut names = f.get_map()?; - for _ in 0..names.get_count() { - let Naming { index, name } = names.read()?; + wasmparser::Name::Function(names) => { + for name in names { + let Naming { index, name } = name?; // We reserve `u32::MAX` for our own use in cranelift-entity. if index != u32::max_value() { environ.declare_func_name(FuncIndex::from_u32(index), name); } } } - wasmparser::Name::Module(module) => { - let name = module.get_name()?; + wasmparser::Name::Module { name, .. } => { environ.declare_module_name(name); } - wasmparser::Name::Local(l) => { - let mut reader = l.get_indirect_map()?; - for _ in 0..reader.get_indirect_count() { - let f = reader.read()?; - if f.indirect_index == u32::max_value() { + wasmparser::Name::Local(reader) => { + for f in reader { + let f = f?; + if f.index == u32::max_value() { continue; } - let mut map = f.get_map()?; - for _ in 0..map.get_count() { - let Naming { index, name } = map.read()?; - environ.declare_local_name( - FuncIndex::from_u32(f.indirect_index), - index, - name, - ) + for name in f.names { + let Naming { index, name } = name?; + environ.declare_local_name(FuncIndex::from_u32(f.index), index, name) } } } diff --git a/cranelift/wasm/src/state/func_state.rs b/cranelift/wasm/src/state.rs similarity index 96% rename from cranelift/wasm/src/state/func_state.rs rename to cranelift/wasm/src/state.rs index bcb97098cb6e..3d775e05ec1a 100644 --- a/cranelift/wasm/src/state/func_state.rs +++ b/cranelift/wasm/src/state.rs @@ -1,13 +1,10 @@ //! WebAssembly module and function translation state. //! -//! The `ModuleTranslationState` struct defined in this module is used to keep track of data about -//! the whole WebAssembly module, such as the decoded type signatures. -//! //! The `FuncTranslationState` struct defined in this module is used to keep track of the WebAssembly //! value and control stacks during the translation of a single function. use crate::environ::{FuncEnvironment, GlobalVariable}; -use crate::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TypeIndex, WasmResult}; +use crate::{FuncIndex, GlobalIndex, Heap, MemoryIndex, TableIndex, TypeIndex, WasmResult}; use crate::{HashMap, Occupied, Vacant}; use cranelift_codegen::ir::{self, Block, Inst, Value}; use std::vec::Vec; @@ -25,6 +22,9 @@ pub enum ElseData { /// instruction that needs to be fixed up to point to the new `else` /// block rather than the destination block after the `if...end`. branch_inst: Inst, + + /// The placeholder block we're replacing. + placeholder: Block, }, /// We have already allocated an `else` block. @@ -46,9 +46,8 @@ pub enum ElseData { /// - `num_return_values`: number of values returned by the control block; /// - `original_stack_size`: size of the value stack at the beginning of the control block. /// -/// Moreover, the `if` frame has the `branch_inst` field that points to the `brz` instruction -/// separating the `true` and `false` branch. The `loop` frame has a `header` field that references -/// the `Block` that contains the beginning of the body of the loop. +/// The `loop` frame has a `header` field that references the `Block` that contains the beginning +/// of the body of the loop. #[derive(Debug)] pub enum ControlStackFrame { If { @@ -228,7 +227,7 @@ pub struct FuncTranslationState { globals: HashMap, // Map of heaps that have been created by `FuncEnvironment::make_heap`. - heaps: HashMap, + memory_to_heap: HashMap, // Map of tables that have been created by `FuncEnvironment::make_table`. pub(crate) tables: HashMap, @@ -261,7 +260,7 @@ impl FuncTranslationState { control_stack: Vec::new(), reachable: true, globals: HashMap::new(), - heaps: HashMap::new(), + memory_to_heap: HashMap::new(), tables: HashMap::new(), signatures: HashMap::new(), functions: HashMap::new(), @@ -273,7 +272,7 @@ impl FuncTranslationState { debug_assert!(self.control_stack.is_empty()); self.reachable = true; self.globals.clear(); - self.heaps.clear(); + self.memory_to_heap.clear(); self.tables.clear(); self.signatures.clear(); self.functions.clear(); @@ -465,9 +464,9 @@ impl FuncTranslationState { func: &mut ir::Function, index: u32, environ: &mut FE, - ) -> WasmResult { + ) -> WasmResult { let index = MemoryIndex::from_u32(index); - match self.heaps.entry(index) { + match self.memory_to_heap.entry(index) { Occupied(entry) => Ok(*entry.get()), Vacant(entry) => Ok(*entry.insert(environ.make_heap(func, index)?)), } diff --git a/cranelift/wasm/src/state/mod.rs b/cranelift/wasm/src/state/mod.rs deleted file mode 100644 index 730dc8beb56d..000000000000 --- a/cranelift/wasm/src/state/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! WebAssembly module and function translation state. -//! -//! The `ModuleTranslationState` struct defined in this module is used to keep track of data about -//! the whole WebAssembly module, such as the decoded type signatures. -//! -//! The `FuncTranslationState` struct defined in this module is used to keep track of the WebAssembly -//! value and control stacks during the translation of a single function. - -pub(crate) mod func_state; -pub(crate) mod module_state; - -// Re-export for convenience. -pub(crate) use func_state::*; -pub(crate) use module_state::*; diff --git a/cranelift/wasm/src/state/module_state.rs b/cranelift/wasm/src/state/module_state.rs deleted file mode 100644 index 9dc6e2c1bb91..000000000000 --- a/cranelift/wasm/src/state/module_state.rs +++ /dev/null @@ -1,75 +0,0 @@ -use crate::{SignatureIndex, WasmError, WasmResult}; -use cranelift_codegen::ir::{types, Type}; -use cranelift_entity::PrimaryMap; -use std::boxed::Box; -use std::vec::Vec; - -/// Map of signatures to a function's parameter and return types. -pub(crate) type WasmTypes = - PrimaryMap, Box<[wasmparser::ValType]>)>; - -/// Contains information decoded from the Wasm module that must be referenced -/// during each Wasm function's translation. -/// -/// This is only for data that is maintained by `cranelift-wasm` itself, as -/// opposed to being maintained by the embedder. Data that is maintained by the -/// embedder is represented with `ModuleEnvironment`. -#[derive(Debug)] -pub struct ModuleTranslationState { - /// A map containing a Wasm module's original, raw signatures. - /// - /// This is used for translating multi-value Wasm blocks inside functions, - /// which are encoded to refer to their type signature via index. - pub(crate) wasm_types: WasmTypes, -} - -/// TODO(dhil): Temporary workaround, should be available from wasmparser/readers/core/types.rs -const EXTERN_REF: wasmparser::RefType = wasmparser::RefType { - nullable: true, - heap_type: wasmparser::HeapType::Extern, -}; - -fn cranelift_to_wasmparser_type(ty: Type) -> WasmResult { - Ok(match ty { - types::I32 => wasmparser::ValType::I32, - types::I64 => wasmparser::ValType::I64, - types::F32 => wasmparser::ValType::F32, - types::F64 => wasmparser::ValType::F64, - types::R32 | types::R64 => wasmparser::ValType::Ref(EXTERN_REF), - _ => { - return Err(WasmError::Unsupported(format!( - "Cannot convert Cranelift type to Wasm signature: {:?}", - ty - ))); - } - }) -} - -impl ModuleTranslationState { - /// Creates a new empty ModuleTranslationState. - pub fn new() -> Self { - Self { - wasm_types: PrimaryMap::new(), - } - } - - /// Create a new ModuleTranslationState with the given function signatures, - /// provided in terms of Cranelift types. The provided slice of signatures - /// is indexed by signature number, and contains pairs of (args, results) - /// slices. - pub fn from_func_sigs(sigs: &[(&[Type], &[Type])]) -> WasmResult { - let mut wasm_types = PrimaryMap::with_capacity(sigs.len()); - for &(ref args, ref results) in sigs { - let args: Vec = args - .iter() - .map(|&ty| cranelift_to_wasmparser_type(ty)) - .collect::>()?; - let results: Vec = results - .iter() - .map(|&ty| cranelift_to_wasmparser_type(ty)) - .collect::>()?; - wasm_types.push((args.into_boxed_slice(), results.into_boxed_slice())); - } - Ok(Self { wasm_types }) - } -} diff --git a/cranelift/wasm/src/translation_utils.rs b/cranelift/wasm/src/translation_utils.rs index 120b3b7dd7a1..20e191da2939 100644 --- a/cranelift/wasm/src/translation_utils.rs +++ b/cranelift/wasm/src/translation_utils.rs @@ -8,32 +8,6 @@ use cranelift_frontend::FunctionBuilder; use serde::{Deserialize, Serialize}; use wasmparser::{FuncValidator, WasmFuncType, WasmModuleResources}; -/// WebAssembly table element. Can be a function or a scalar type. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] -pub enum TableElementType { - /// A scalar type. - Val(ir::Type), - /// A function. - Func, -} - -/// Helper function translating wasmparser types to Cranelift types when possible. -pub fn type_to_type( - ty: wasmparser::ValType, - environ: &PE, -) -> WasmResult { - match ty { - wasmparser::ValType::I32 => Ok(ir::types::I32), - wasmparser::ValType::I64 => Ok(ir::types::I64), - wasmparser::ValType::F32 => Ok(ir::types::F32), - wasmparser::ValType::F64 => Ok(ir::types::F64), - wasmparser::ValType::V128 => Ok(ir::types::I8X16), - wasmparser::ValType::Ref(rt) => Ok(environ.reference_type(rt.heap_type.into())), - wasmparser::ValType::Bot => todo!("ValType::Bot will not exist in final wasm-tools"), - } -} - /// Get the parameter and result types for the given Wasm blocktype. pub fn blocktype_params_results<'a, T>( validator: &'a FuncValidator, @@ -104,7 +78,6 @@ pub fn block_with_params( wasmparser::ValType::V128 => { builder.append_block_param(block, ir::types::I8X16); } - wasmparser::ValType::Bot => todo!("ValType::Bot will not exist in actual wasmparser"), } } Ok(block) diff --git a/crates/asm-macros/Cargo.toml b/crates/asm-macros/Cargo.toml index 5aebdcb4181d..b8b667573f05 100644 --- a/crates/asm-macros/Cargo.toml +++ b/crates/asm-macros/Cargo.toml @@ -1,11 +1,11 @@ [package] authors = ["The Wasmtime Project Developers"] description = "Macros for defining asm functions in Wasmtime" -edition = "2021" +edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" name = "wasmtime-asm-macros" repository = "https://github.com/bytecodealliance/wasmtime" -version = "0.41.0" +version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/asm-macros/src/lib.rs b/crates/asm-macros/src/lib.rs index 50c0ef848e98..efd970d6bca0 100644 --- a/crates/asm-macros/src/lib.rs +++ b/crates/asm-macros/src/lib.rs @@ -5,51 +5,43 @@ //! attributes correct (e.g. ELF symbols get a size and are flagged as a //! function) and additionally handles visibility across platforms. All symbols //! should be visible to Rust but not visible externally outside of a `*.so`. -//! -//! It also exports a an `asm_sym!` macro which can be used to reference symbols -//! from within `global_asm!`-defined functions, and handles adding the leading -//! underscore that macOS prepends to symbols for you. cfg_if::cfg_if! { if #[cfg(target_os = "macos")] { #[macro_export] macro_rules! asm_func { - ($name:expr, $($body:tt)*) => { - std::arch::global_asm!(concat!( - ".p2align 4\n", - ".private_extern _", $name, "\n", - ".global _", $name, "\n", - "_", $name, ":\n", - $($body)* - )); + ($name:expr, $body:expr $(, $($args:tt)*)?) => { + std::arch::global_asm!( + concat!( + ".p2align 4\n", + ".private_extern _", $name, "\n", + ".global _", $name, "\n", + "_", $name, ":\n", + $body, + ), + $($($args)*)? + ); }; } - - #[macro_export] - macro_rules! asm_sym { - ( $( $name:tt )* ) => ( concat!("_", $( $name )* ) ) - } } else if #[cfg(target_os = "windows")] { #[macro_export] macro_rules! asm_func { - ($name:expr, $($body:tt)*) => { - std::arch::global_asm!(concat!( - ".def ", $name, "\n", - ".scl 2\n", - ".type 32\n", - ".endef\n", - ".global ", $name, "\n", - ".p2align 4\n", - $name, ":\n", - $($body)* - )); + ($name:expr, $body:expr $(, $($args:tt)*)?) => { + std::arch::global_asm!( + concat!( + ".def ", $name, "\n", + ".scl 2\n", + ".type 32\n", + ".endef\n", + ".global ", $name, "\n", + ".p2align 4\n", + $name, ":\n", + $body + ), + $($($args)*)? + ); }; } - - #[macro_export] - macro_rules! asm_sym { - ( $( $name:tt )* ) => ( $( $name )* ) - } } else { // Note that for now this "else" clause just assumes that everything // other than macOS is ELF and has the various directives here for @@ -70,22 +62,20 @@ cfg_if::cfg_if! { #[macro_export] macro_rules! asm_func { - ($name:expr, $($body:tt)*) => { - std::arch::global_asm!(concat!( - ".p2align 4\n", - ".hidden ", $name, "\n", - ".global ", $name, "\n", - $crate::elf_func_type_header!($name), - $name, ":\n", - concat!($($body)*), - ".size ", $name, ",.-", $name, - )); + ($name:expr, $body:expr $(, $($args:tt)*)?) => { + std::arch::global_asm!( + concat!( + ".p2align 4\n", + ".hidden ", $name, "\n", + ".global ", $name, "\n", + $crate::elf_func_type_header!($name), + $name, ":\n", + $body, + ".size ", $name, ",.-", $name, + ) + $(, $($args)*)? + ); }; } - - #[macro_export] - macro_rules! asm_sym { - ( $( $name:tt )* ) => ( $( $name )* ) - } } } diff --git a/crates/bench-api/Cargo.toml b/crates/bench-api/Cargo.toml index e8d97cf27d2d..150886297134 100644 --- a/crates/bench-api/Cargo.toml +++ b/crates/bench-api/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "wasmtime-bench-api" -version = "0.19.0" -authors = ["The Wasmtime Project Developers"] +version.workspace = true +authors.workspace = true description = "Exposes a benchmarking API for the Wasmtime runtime" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" readme = "README.md" -edition = "2021" +edition.workspace = true publish = false [lib] @@ -16,21 +16,22 @@ test = false doctest = false [dependencies] -anyhow = "1.0" +anyhow = { workspace = true } shuffling-allocator = { version = "1.1.1", optional = true } -target-lexicon = "0.12" -wasmtime = { path = "../wasmtime", default-features = true } -wasmtime-cli-flags = { path = "../cli-flags", default-features = true } -wasmtime-wasi = { path = "../wasi" } -wasmtime-wasi-crypto = { path = "../wasi-crypto", optional = true } -wasmtime-wasi-nn = { path = "../wasi-nn", optional = true } -wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync" } -cap-std = "0.25.0" +target-lexicon = { workspace = true } +wasmtime = { workspace = true } +wasmtime-cli-flags = { workspace = true, default-features = true } +wasmtime-wasi = { workspace = true } +wasmtime-wasi-crypto = { workspace = true, optional = true } +wasmtime-wasi-nn = { workspace = true, optional = true } +wasi-cap-std-sync = { workspace = true } +cap-std = { workspace = true } +clap = { workspace = true } [dev-dependencies] -wat = "1.0.45" +wat = { workspace = true } [features] -default = ["shuffling-allocator"] +default = ["shuffling-allocator", "wasi-nn"] wasi-crypto = ["wasmtime-wasi-crypto"] wasi-nn = ["wasmtime-wasi-nn"] diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index 0806f43bb620..3d605a75f987 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -136,14 +136,15 @@ mod unsafe_send_sync; use crate::unsafe_send_sync::UnsafeSendSync; -use anyhow::{anyhow, Context, Result}; +use anyhow::{Context, Result}; +use clap::Parser; use std::os::raw::{c_int, c_void}; use std::slice; use std::{env, path::PathBuf}; use target_lexicon::Triple; -use wasmtime::{Config, Engine, Instance, Linker, Module, Store}; -use wasmtime_cli_flags::CommonOptions; -use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx}; +use wasmtime::{Engine, Instance, Linker, Module, Store}; +use wasmtime_cli_flags::{CommonOptions, WasiModules}; +use wasmtime_wasi::{sync::WasiCtxBuilder, I32Exit, WasiCtx}; pub type ExitCode = c_int; pub const OK: ExitCode = 0; @@ -238,20 +239,23 @@ impl WasmBenchConfig { Ok(Some(stdin_path.into())) } - fn execution_flags(&self) -> Result> { - if self.execution_flags_ptr.is_null() { - return Ok(None); - } - - let execution_flags = unsafe { - std::slice::from_raw_parts(self.execution_flags_ptr, self.execution_flags_len) + fn execution_flags(&self) -> Result { + let flags = if self.execution_flags_ptr.is_null() { + "" + } else { + let execution_flags = unsafe { + std::slice::from_raw_parts(self.execution_flags_ptr, self.execution_flags_len) + }; + std::str::from_utf8(execution_flags) + .context("given execution flags string is not valid UTF-8")? }; - let execution_flags = std::str::from_utf8(execution_flags) - .context("given execution flags string is not valid UTF-8")?; - - let options = CommonOptions::parse_from_str(execution_flags)?; - let config = options.config(Some(&Triple::host().to_string()))?; - Ok(Some(config)) + let options = CommonOptions::try_parse_from( + ["wasmtime"] + .into_iter() + .chain(flags.split(' ').filter(|s| !s.is_empty())), + ) + .context("failed to parse options")?; + Ok(options) } } @@ -281,10 +285,10 @@ pub extern "C" fn wasm_bench_create( let stdout_path = config.stdout_path()?; let stderr_path = config.stderr_path()?; let stdin_path = config.stdin_path()?; - let engine_config = config.execution_flags()?; + let options = config.execution_flags()?; let state = Box::new(BenchState::new( - engine_config, + options, config.compilation_timer, config.compilation_start, config.compilation_end, @@ -348,7 +352,7 @@ pub extern "C" fn wasm_bench_create( pub extern "C" fn wasm_bench_free(state: *mut c_void) { assert!(!state.is_null()); unsafe { - Box::from_raw(state as *mut BenchState); + drop(Box::from_raw(state as *mut BenchState)); } } @@ -407,20 +411,21 @@ struct BenchState { make_wasi_cx: Box Result>, module: Option, store_and_instance: Option<(Store, Instance)>, + epoch_interruption: bool, + fuel: Option, } struct HostState { wasi: WasiCtx, #[cfg(feature = "wasi-nn")] wasi_nn: wasmtime_wasi_nn::WasiNnCtx, - #[cfg(feature = "wasi-crypto")] wasi_crypto: wasmtime_wasi_crypto::WasiCryptoCtx, } impl BenchState { fn new( - engine_config: Option, + options: CommonOptions, compilation_timer: *mut u8, compilation_start: extern "C" fn(*mut u8), compilation_end: extern "C" fn(*mut u8), @@ -432,8 +437,10 @@ impl BenchState { execution_end: extern "C" fn(*mut u8), make_wasi_cx: impl FnMut() -> Result + 'static, ) -> Result { - // NB: do not configure a code cache. - let engine = Engine::new(&engine_config.unwrap_or(Config::new()))?; + let mut config = options.config(Some(&Triple::host().to_string()))?; + // NB: always disable the compilation cache. + config.disable_cache(); + let engine = Engine::new(&config)?; let mut linker = Linker::::new(&engine); // Define the benchmarking start/end functions. @@ -451,13 +458,24 @@ impl BenchState { Ok(()) })?; - wasmtime_wasi::add_to_linker(&mut linker, |cx| &mut cx.wasi)?; + let epoch_interruption = options.epoch_interruption; + let fuel = options.fuel; + + let wasi_modules = options.wasi_modules.unwrap_or(WasiModules::default()); + + if wasi_modules.wasi_common { + wasmtime_wasi::add_to_linker(&mut linker, |cx| &mut cx.wasi)?; + } #[cfg(feature = "wasi-nn")] - wasmtime_wasi_nn::add_to_linker(&mut linker, |cx| &mut cx.wasi_nn)?; + if wasi_modules.wasi_nn { + wasmtime_wasi_nn::add_to_linker(&mut linker, |cx| &mut cx.wasi_nn)?; + } #[cfg(feature = "wasi-crypto")] - wasmtime_wasi_crypto::add_to_linker(&mut linker, |cx| &mut cx.wasi_crypto)?; + if wasi_modules.wasi_crypto { + wasmtime_wasi_crypto::add_to_linker(&mut linker, |cx| &mut cx.wasi_crypto)?; + } Ok(Self { linker, @@ -470,6 +488,8 @@ impl BenchState { make_wasi_cx: Box::new(make_wasi_cx) as _, module: None, store_and_instance: None, + epoch_interruption, + fuel, }) } @@ -506,6 +526,13 @@ impl BenchState { // stdin/stdout/stderr. (self.instantiation_start)(self.instantiation_timer); let mut store = Store::new(self.linker.engine(), host); + if self.epoch_interruption { + store.set_epoch_deadline(1); + } + if let Some(fuel) = self.fuel { + store.add_fuel(fuel).unwrap(); + } + let instance = self.linker.instantiate(&mut store, &module)?; (self.instantiation_end)(self.instantiation_timer); @@ -519,20 +546,19 @@ impl BenchState { .take() .expect("instantiate the module before executing it"); - let start_func = instance.get_typed_func::<(), (), _>(&mut store, "_start")?; + let start_func = instance.get_typed_func::<(), ()>(&mut store, "_start")?; match start_func.call(&mut store, ()) { Ok(_) => Ok(()), Err(trap) => { // Since _start will likely return by using the system `exit` call, we must // check the trap code to see if it actually represents a successful exit. - match trap.i32_exit_status() { - Some(0) => Ok(()), - Some(n) => Err(anyhow!("_start exited with a non-zero code: {}", n)), - None => Err(anyhow!( - "executing the benchmark resulted in a trap: {}", - trap - )), + if let Some(exit) = trap.downcast_ref::() { + if exit.0 == 0 { + return Ok(()); + } } + + Err(trap) } } } diff --git a/crates/c-api/CMakeLists.txt b/crates/c-api/CMakeLists.txt index 1e2ddd50f723..3f8ed9364f87 100644 --- a/crates/c-api/CMakeLists.txt +++ b/crates/c-api/CMakeLists.txt @@ -2,14 +2,14 @@ cmake_minimum_required(VERSION 3.10) option(BUILD_SHARED_LIBS "Build using shared libraries" OFF) -if (CMAKE_BUILD_TYPE STREQUAL "Release") +if(CMAKE_BUILD_TYPE STREQUAL "Release") set(WASMTIME_BUILD_TYPE_FLAG "--release") set(WASMTIME_BUILD_TYPE "release") else() set(WASMTIME_BUILD_TYPE "debug") endif() -if (BUILD_SHARED_LIBS) +if(BUILD_SHARED_LIBS) # Copy shared library into build directory if(WIN32) set(WASMTIME_INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_if_different @@ -26,39 +26,100 @@ if (BUILD_SHARED_LIBS) endif() endif() +if(ANDROID) + # TODO wasmtime only supports arm64-v8a right now + if(ANDROID_ABI STREQUAL "armeabi-v7a") + set(ANDROID_TARGET "armv7-linux-androideabi") + set(ANDROID_ARCH_SHORT "arm") + elseif(ANDROID_ABI STREQUAL "arm64-v8a") + set(ANDROID_TARGET "aarch64-linux-android") + set(ANDROID_ARCH_SHORT "aarch64") + elseif(ANDROID_ABI STREQUAL "x86") + set(ANDROID_TARGET "i686-linux-android") + set(ANDROID_ARCH_SHORT "i386") + elseif(ANDROID_ABI STREQUAL "x86_64") + set(ANDROID_TARGET "x86_64-linux-android") + set(ANDROID_ARCH_SHORT "x86_64") + endif() + + set(WASMTIME_BUILD_TARGET "--target=${ANDROID_TARGET}") +endif() + +if (BUILD_SHARED_LIBS AND ANDROID) + message(FATAL_ERROR "Wasmtime cannot be built with BUILD_SHARED_LIBS on Android") +endif() + +if(BUILD_SHARED_LIBS) + if(WIN32) + set(WASMTIME_BUILD_PRODUCT + ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/wasmtime.dll.lib) + elseif(APPLE) + set(WASMTIME_BUILD_PRODUCT + ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/libwasmtime.dylib) + else() + set(WASMTIME_BUILD_PRODUCT + ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/libwasmtime.so) + endif() +else() + if(WIN32) + set(WASMTIME_BUILD_PRODUCT + ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/wasmtime.lib) + elseif(ANDROID) + set(WASMTIME_BUILD_PRODUCT + ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${ANDROID_TARGET}/${WASMTIME_BUILD_TYPE}/libwasmtime.a) + else() + set(WASMTIME_BUILD_PRODUCT + ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/libwasmtime.a) + endif() +endif() + +if(ANDROID) + # Rust attempts to use libgcc.a on NDK versions r23-beta3 and up + # but it has been replaced with libunwind.a (rust-lang/rust#85806) + file(WRITE ${CMAKE_BINARY_DIR}/libgcc.a "INPUT(-lunwind)") + # The version of the clang compiler is part of the libunwind.a path + file(STRINGS ${ANDROID_TOOLCHAIN_ROOT}/AndroidVersion.txt CLANG_VERSION_FILE) + list(GET CLANG_VERSION_FILE 0 CLANG_VERSION) + + # Some crates use the compiler directly, environment variables + # are set to make them use the Android compiler + set(WASMTIME_PREBUILD_COMMAND ${CMAKE_COMMAND} -E env + CC=${ANDROID_TOOLCHAIN_ROOT}/bin/clang + AR=${ANDROID_TOOLCHAIN_ROOT}/bin/llvm-ar + "RUSTFLAGS=-L ${CMAKE_SYSROOT}/usr/lib/${ANDROID_TARGET}/${ANDROID_NATIVE_API_LEVEL} \ + -L ${ANDROID_TOOLCHAIN_ROOT}/lib64/clang/${CLANG_VERSION}/lib/linux/${ANDROID_ARCH_SHORT} \ + -L ${CMAKE_BINARY_DIR} -C linker=${ANDROID_TOOLCHAIN_ROOT}/bin/ld") +endif() include(ExternalProject) ExternalProject_Add( wasmtime-crate DOWNLOAD_COMMAND "" CONFIGURE_COMMAND "" INSTALL_COMMAND "${WASMTIME_INSTALL_COMMAND}" - BUILD_COMMAND cargo build ${WASMTIME_BUILD_TYPE_FLAG} + BUILD_COMMAND ${WASMTIME_PREBUILD_COMMAND} cargo build ${WASMTIME_BUILD_TYPE_FLAG} ${WASMTIME_BUILD_TARGET} BINARY_DIR ${CMAKE_CURRENT_SOURCE_DIR} - BUILD_ALWAYS ON) + BUILD_ALWAYS ON + BUILD_BYPRODUCTS ${WASMTIME_BUILD_PRODUCT}) add_library(wasmtime INTERFACE) add_dependencies(wasmtime wasmtime-crate) -if (BUILD_SHARED_LIBS) - if(WIN32) - target_link_libraries(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/wasmtime.dll.lib) - elseif(APPLE) - target_link_libraries(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/libwasmtime.dylib) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath='$ORIGIN'") - else() - target_link_libraries(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/libwasmtime.so) +if(BUILD_SHARED_LIBS) + if(NOT WIN32) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath='$ORIGIN'") endif() + target_link_libraries(wasmtime INTERFACE ${WASMTIME_BUILD_PRODUCT}) else() if(WIN32) target_compile_options(wasmtime INTERFACE -DWASM_API_EXTERN= -DWASI_API_EXTERN=) - target_link_libraries(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/wasmtime.lib + target_link_libraries(wasmtime INTERFACE ${WASMTIME_BUILD_PRODUCT} ws2_32 advapi32 userenv ntdll shell32 ole32 bcrypt) - elseif(APPLE) - target_link_libraries(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/libwasmtime.a) + elseif(APPLE OR ANDROID) + target_link_libraries(wasmtime INTERFACE ${WASMTIME_BUILD_PRODUCT}) else() - target_link_libraries(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/../../target/${WASMTIME_BUILD_TYPE}/libwasmtime.a + target_link_libraries(wasmtime INTERFACE ${WASMTIME_BUILD_PRODUCT} pthread dl m) endif() endif() -target_include_directories(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include) \ No newline at end of file +target_include_directories(wasmtime INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/wasm-c-api/include) \ No newline at end of file diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index 12a39a58645b..a464c0dbd506 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "wasmtime-c-api" -version = "0.19.0" -authors = ["The Wasmtime Project Developers"] +version.workspace = true +authors.workspace = true description = "C API to expose the Wasmtime runtime" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" readme = "README.md" -edition = "2021" +edition.workspace = true publish = false [lib] @@ -17,23 +17,24 @@ test = false doctest = false [dependencies] -env_logger = "0.9" -anyhow = "1.0" -once_cell = "1.3" -wasmtime = { path = "../wasmtime", default-features = false, features = ['cranelift'] } +env_logger = { workspace = true } +anyhow = { workspace = true } +once_cell = { workspace = true } +wasmtime = { workspace = true, features = ['cranelift'] } wasmtime-c-api-macros = { path = "macros" } # Optional dependency for the `wat2wasm` API -wat = { version = "1.0.47", optional = true } +wat = { workspace = true, optional = true } # Optional dependencies for the `wasi` feature -wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", optional = true } -wasmtime-wasi = { path = "../wasi", optional = true } -cap-std = { version = "0.25.0", optional = true } +wasi-cap-std-sync = { workspace = true, optional = true } +wasmtime-wasi = { workspace = true, optional = true } +cap-std = { workspace = true, optional = true } +wasi-common = { workspace = true, optional = true } [features] default = ['jitdump', 'wat', 'wasi', 'cache', 'parallel-compilation'] jitdump = ["wasmtime/jitdump"] cache = ["wasmtime/cache"] parallel-compilation = ['wasmtime/parallel-compilation'] -wasi = ['wasi-cap-std-sync', 'wasmtime-wasi', 'cap-std'] +wasi = ['wasi-cap-std-sync', 'wasmtime-wasi', 'cap-std', 'wasi-common'] diff --git a/crates/c-api/include/wasi.h b/crates/c-api/include/wasi.h index 994c66b22605..e927e04a44aa 100644 --- a/crates/c-api/include/wasi.h +++ b/crates/c-api/include/wasi.h @@ -7,6 +7,7 @@ #ifndef WASI_H #define WASI_H +#include #include "wasm.h" #ifndef WASI_API_EXTERN @@ -94,6 +95,16 @@ WASI_API_EXTERN void wasi_config_inherit_env(wasi_config_t* config); */ WASI_API_EXTERN bool wasi_config_set_stdin_file(wasi_config_t* config, const char* path); +/** + * \brief Configures standard input to be taken from the specified #wasm_byte_vec_t. + * + * By default WASI programs have no stdin, but this configures the specified + * bytes to be used as stdin for this configuration. + * + * This function takes ownership of the `binary` argument. + */ +WASI_API_EXTERN void wasi_config_set_stdin_bytes(wasi_config_t* config, wasm_byte_vec_t* binary); + /** * \brief Configures this process's own stdin stream to be used as stdin for * this WASI configuration. @@ -146,6 +157,19 @@ WASI_API_EXTERN void wasi_config_inherit_stderr(wasi_config_t* config); */ WASI_API_EXTERN bool wasi_config_preopen_dir(wasi_config_t* config, const char* path, const char* guest_path); +/** + * \brief Configures a "preopened" listen socket to be available to WASI APIs. + * + * By default WASI programs do not have access to open up network sockets on + * the host. This API can be used to grant WASI programs access to a network + * socket file descriptor on the host. + * + * The fd_num argument is the number of the file descriptor by which it will be + * known in WASM and the host_port is the IP address and port (e.g. + * "127.0.0.1:8080") requested to listen on. + */ +WASI_API_EXTERN bool wasi_config_preopen_socket(wasi_config_t* config, uint32_t fd_num, const char* host_port); + #undef own #ifdef __cplusplus diff --git a/crates/c-api/include/wasmtime.h b/crates/c-api/include/wasmtime.h index c70fd8b71339..9a8d70a5ce45 100644 --- a/crates/c-api/include/wasmtime.h +++ b/crates/c-api/include/wasmtime.h @@ -196,6 +196,23 @@ #include #include +/** + * \brief Wasmtime version string. + */ +#define WASMTIME_VERSION "7.0.0" +/** + * \brief Wasmtime major version number. + */ +#define WASMTIME_VERSION_MAJOR 7 +/** + * \brief Wasmtime minor version number. + */ +#define WASMTIME_VERSION_MINOR 0 +/** + * \brief Wasmtime patch version number. + */ +#define WASMTIME_VERSION_PATCH 0 + #ifdef __cplusplus extern "C" { #endif diff --git a/crates/c-api/include/wasmtime/config.h b/crates/c-api/include/wasmtime/config.h index 77c11936322e..951004e96a8e 100644 --- a/crates/c-api/include/wasmtime/config.h +++ b/crates/c-api/include/wasmtime/config.h @@ -200,6 +200,14 @@ WASMTIME_CONFIG_PROP(void, wasm_memory64, bool) */ WASMTIME_CONFIG_PROP(void, strategy, wasmtime_strategy_t) +/** + * \brief Configure wether wasmtime should compile a module using multiple threads. + * + * For more information see the Rust documentation at + * https://docs.wasmtime.dev/api/wasmtime/struct.Config.html#method.parallel_compilation. + */ +WASMTIME_CONFIG_PROP(void, parallel_compilation, bool) + /** * \brief Configures whether Cranelift's debug verifier is enabled. * diff --git a/crates/c-api/include/wasmtime/error.h b/crates/c-api/include/wasmtime/error.h index 2ffee72bed8f..cfa0c9690e2e 100644 --- a/crates/c-api/include/wasmtime/error.h +++ b/crates/c-api/include/wasmtime/error.h @@ -48,6 +48,24 @@ WASM_API_EXTERN void wasmtime_error_message( wasm_name_t *message ); +/** + * \brief Attempts to extract a WASI-specific exit status from this error. + * + * Returns `true` if the error is a WASI "exit" trap and has a return status. + * If `true` is returned then the exit status is returned through the `status` + * pointer. If `false` is returned then this is not a wasi exit trap. + */ +WASM_API_EXTERN bool wasmtime_error_exit_status(const wasmtime_error_t*, int *status); + +/** + * \brief Attempts to extract a WebAssembly trace from this error. + * + * This is similar to #wasm_trap_trace except that it takes a #wasmtime_error_t + * as input. The `out` argument will be filled in with the wasm trace, if + * present. + */ +WASM_API_EXTERN void wasmtime_error_wasm_trace(const wasmtime_error_t*, wasm_frame_vec_t *out); + #ifdef __cplusplus } // extern "C" #endif diff --git a/crates/c-api/include/wasmtime/func.h b/crates/c-api/include/wasmtime/func.h index 2683eaabdff6..e65d002c6631 100644 --- a/crates/c-api/include/wasmtime/func.h +++ b/crates/c-api/include/wasmtime/func.h @@ -241,10 +241,11 @@ WASM_API_EXTERN wasmtime_error_t *wasmtime_func_call( * faster than that function, but the tradeoff is that embeddings must uphold * more invariants rather than relying on Wasmtime to check them for you. */ -WASM_API_EXTERN wasm_trap_t *wasmtime_func_call_unchecked( +WASM_API_EXTERN wasmtime_error_t *wasmtime_func_call_unchecked( wasmtime_context_t *store, const wasmtime_func_t *func, - wasmtime_val_raw_t *args_and_results + wasmtime_val_raw_t *args_and_results, + wasm_trap_t **trap ); /** diff --git a/crates/c-api/include/wasmtime/linker.h b/crates/c-api/include/wasmtime/linker.h index edd52442dfb8..453ef73d64ea 100644 --- a/crates/c-api/include/wasmtime/linker.h +++ b/crates/c-api/include/wasmtime/linker.h @@ -59,6 +59,7 @@ WASM_API_EXTERN void wasmtime_linker_allow_shadowing(wasmtime_linker_t* linker, * \brief Defines a new item in this linker. * * \param linker the linker the name is being defined in. + * \param store the store that the `item` is owned by. * \param module the module name the item is defined under. * \param module_len the byte length of `module` * \param name the field name the item is defined under @@ -73,6 +74,7 @@ WASM_API_EXTERN void wasmtime_linker_allow_shadowing(wasmtime_linker_t* linker, */ WASM_API_EXTERN wasmtime_error_t* wasmtime_linker_define( wasmtime_linker_t *linker, + wasmtime_context_t *store, const char *module, size_t module_len, const char *name, diff --git a/crates/c-api/include/wasmtime/store.h b/crates/c-api/include/wasmtime/store.h index 55c9f680bd80..ba1d74a943e2 100644 --- a/crates/c-api/include/wasmtime/store.h +++ b/crates/c-api/include/wasmtime/store.h @@ -79,6 +79,44 @@ WASM_API_EXTERN wasmtime_store_t *wasmtime_store_new( */ WASM_API_EXTERN wasmtime_context_t *wasmtime_store_context(wasmtime_store_t *store); +/** + * \brief Provides limits for a store. Used by hosts to limit resource + * consumption of instances. Use negative value to keep the default value + * for the limit. + * + * \param store store where the limits should be set. + * \param memory_size the maximum number of bytes a linear memory can grow to. + * Growing a linear memory beyond this limit will fail. By default, + * linear memory will not be limited. + * \param table_elements the maximum number of elements in a table. + * Growing a table beyond this limit will fail. By default, table elements + * will not be limited. + * \param instances the maximum number of instances that can be created + * for a Store. Module instantiation will fail if this limit is exceeded. + * This value defaults to 10,000. + * \param tables the maximum number of tables that can be created for a Store. + * Module instantiation will fail if this limit is exceeded. This value + * defaults to 10,000. + * \param memories the maximum number of linear memories that can be created + * for a Store. Instantiation will fail with an error if this limit is exceeded. + * This value defaults to 10,000. + * + * Use any negative value for the parameters that should be kept on + * the default values. + * + * Note that the limits are only used to limit the creation/growth of + * resources in the future, this does not retroactively attempt to apply + * limits to the store. + */ +WASM_API_EXTERN void wasmtime_store_limiter( + wasmtime_store_t *store, + int64_t memory_size, + int64_t table_elements, + int64_t instances, + int64_t tables, + int64_t memories +); + /** * \brief Deletes a store. */ diff --git a/crates/c-api/include/wasmtime/trap.h b/crates/c-api/include/wasmtime/trap.h index 909a4801f006..2d2e20407c20 100644 --- a/crates/c-api/include/wasmtime/trap.h +++ b/crates/c-api/include/wasmtime/trap.h @@ -46,6 +46,8 @@ enum wasmtime_trap_code_enum { WASMTIME_TRAP_CODE_UNREACHABLE_CODE_REACHED, /// Execution has potentially run too long and may be interrupted. WASMTIME_TRAP_CODE_INTERRUPT, + /// Execution has run out of the configured fuel amount. + WASMTIME_TRAP_CODE_OUT_OF_FUEL, }; /** @@ -69,15 +71,6 @@ WASM_API_EXTERN wasm_trap_t *wasmtime_trap_new(const char *msg, size_t msg_len); */ WASM_API_EXTERN bool wasmtime_trap_code(const wasm_trap_t*, wasmtime_trap_code_t *code); -/** - * \brief Attempts to extract a WASI-specific exit status from this trap. - * - * Returns `true` if the trap is a WASI "exit" trap and has a return status. If - * `true` is returned then the exit status is returned through the `status` - * pointer. If `false` is returned then this is not a wasi exit trap. - */ -WASM_API_EXTERN bool wasmtime_trap_exit_status(const wasm_trap_t*, int *status); - /** * \brief Returns a human-readable name for this frame's function. * diff --git a/crates/c-api/include/wasmtime/val.h b/crates/c-api/include/wasmtime/val.h index ae0a1961cea0..f16c7bd48e40 100644 --- a/crates/c-api/include/wasmtime/val.h +++ b/crates/c-api/include/wasmtime/val.h @@ -119,38 +119,24 @@ typedef uint8_t wasmtime_v128[16]; */ typedef union wasmtime_valunion { /// Field used if #wasmtime_val_t::kind is #WASMTIME_I32 - /// - /// Note that this field is always stored in a little-endian format. int32_t i32; /// Field used if #wasmtime_val_t::kind is #WASMTIME_I64 - /// - /// Note that this field is always stored in a little-endian format. int64_t i64; /// Field used if #wasmtime_val_t::kind is #WASMTIME_F32 - /// - /// Note that this field is always stored in a little-endian format. float32_t f32; /// Field used if #wasmtime_val_t::kind is #WASMTIME_F64 - /// - /// Note that this field is always stored in a little-endian format. float64_t f64; /// Field used if #wasmtime_val_t::kind is #WASMTIME_FUNCREF /// /// If this value represents a `ref.null func` value then the `store_id` field /// is set to zero. - /// - /// Note that this field is always stored in a little-endian format. wasmtime_func_t funcref; /// Field used if #wasmtime_val_t::kind is #WASMTIME_EXTERNREF /// /// If this value represents a `ref.null extern` value then this pointer will /// be `NULL`. - /// - /// Note that this field is always stored in a little-endian format. wasmtime_externref_t *externref; /// Field used if #wasmtime_val_t::kind is #WASMTIME_V128 - /// - /// Note that this field is always stored in a little-endian format. wasmtime_v128 v128; } wasmtime_valunion_t; @@ -169,25 +155,39 @@ typedef union wasmtime_valunion { */ typedef union wasmtime_val_raw { /// Field for when this val is a WebAssembly `i32` value. + /// + /// Note that this field is always stored in a little-endian format. int32_t i32; /// Field for when this val is a WebAssembly `i64` value. + /// + /// Note that this field is always stored in a little-endian format. int64_t i64; /// Field for when this val is a WebAssembly `f32` value. + /// + /// Note that this field is always stored in a little-endian format. float32_t f32; /// Field for when this val is a WebAssembly `f64` value. + /// + /// Note that this field is always stored in a little-endian format. float64_t f64; /// Field for when this val is a WebAssembly `v128` value. + /// + /// Note that this field is always stored in a little-endian format. wasmtime_v128 v128; /// Field for when this val is a WebAssembly `funcref` value. /// /// If this is set to 0 then it's a null funcref, otherwise this must be /// passed to `wasmtime_func_from_raw` to determine the `wasmtime_func_t`. + /// + /// Note that this field is always stored in a little-endian format. size_t funcref; /// Field for when this val is a WebAssembly `externref` value. /// /// If this is set to 0 then it's a null externref, otherwise this must be /// passed to `wasmtime_externref_from_raw` to determine the /// `wasmtime_externref_t`. + /// + /// Note that this field is always stored in a little-endian format. size_t externref; } wasmtime_val_raw_t; diff --git a/crates/c-api/macros/Cargo.toml b/crates/c-api/macros/Cargo.toml index f6eb6ec9dfde..152bdd122296 100644 --- a/crates/c-api/macros/Cargo.toml +++ b/crates/c-api/macros/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "wasmtime-c-api-macros" -version = "0.19.0" +version = "0.0.0" authors = ["The Wasmtime Project Developers"] -edition = "2021" +edition.workspace = true publish = false [lib] diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs index ec7b86a8a656..275730f239d0 100644 --- a/crates/c-api/src/config.rs +++ b/crates/c-api/src/config.rs @@ -112,6 +112,12 @@ pub extern "C" fn wasmtime_config_strategy_set( }); } +#[no_mangle] +#[cfg(feature = "parallel-compilation")] +pub extern "C" fn wasmtime_config_parallel_compilation_set(c: &mut wasm_config_t, enable: bool) { + c.config.parallel_compilation(enable); +} + #[no_mangle] pub extern "C" fn wasmtime_config_cranelift_debug_verifier_set( c: &mut wasm_config_t, diff --git a/crates/c-api/src/error.rs b/crates/c-api/src/error.rs index 073c158c3b4d..e1e066531a0f 100644 --- a/crates/c-api/src/error.rs +++ b/crates/c-api/src/error.rs @@ -1,4 +1,4 @@ -use crate::wasm_name_t; +use crate::{wasm_frame_vec_t, wasm_name_t}; use anyhow::{anyhow, Error, Result}; #[repr(C)] @@ -37,3 +37,25 @@ pub(crate) fn bad_utf8() -> Option> { pub extern "C" fn wasmtime_error_message(error: &wasmtime_error_t, message: &mut wasm_name_t) { message.set_buffer(format!("{:?}", error.error).into_bytes()); } + +#[no_mangle] +pub extern "C" fn wasmtime_error_exit_status(raw: &wasmtime_error_t, status: &mut i32) -> bool { + #[cfg(feature = "wasi")] + if let Some(exit) = raw.error.downcast_ref::() { + *status = exit.0; + return true; + } + + // Squash unused warnings in wasi-disabled builds. + drop((raw, status)); + + false +} + +#[no_mangle] +pub extern "C" fn wasmtime_error_wasm_trace<'a>( + raw: &'a wasmtime_error_t, + out: &mut wasm_frame_vec_t<'a>, +) { + crate::trap::error_trace(&raw.error, out) +} diff --git a/crates/c-api/src/func.rs b/crates/c-api/src/func.rs index 3d3f5ee3cf3a..fe2c27ed0acf 100644 --- a/crates/c-api/src/func.rs +++ b/crates/c-api/src/func.rs @@ -3,6 +3,8 @@ use crate::{ wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t, wasm_val_vec_t, wasmtime_error_t, wasmtime_extern_t, wasmtime_val_t, wasmtime_val_union, CStoreContext, CStoreContextMut, }; +use anyhow::{Error, Result}; +use std::any::Any; use std::ffi::c_void; use std::mem::{self, MaybeUninit}; use std::panic::{self, AssertUnwindSafe}; @@ -67,7 +69,7 @@ unsafe fn create_function( let mut out_results: wasm_val_vec_t = vec![wasm_val_t::default(); results.len()].into(); let out = func(¶ms, &mut out_results); if let Some(trap) = out { - return Err(trap.trap.clone()); + return Err(trap.error); } let out_results = out_results.as_slice(); @@ -152,24 +154,25 @@ pub unsafe extern "C" fn wasm_func_call( } ptr::null_mut() } - Ok(Err(trap)) => match trap.downcast::() { - Ok(trap) => Box::into_raw(Box::new(wasm_trap_t::new(trap))), - Err(err) => Box::into_raw(Box::new(wasm_trap_t::new(err.into()))), - }, + Ok(Err(err)) => Box::into_raw(Box::new(wasm_trap_t::new(err))), Err(panic) => { - let trap = if let Some(msg) = panic.downcast_ref::() { - Trap::new(msg) - } else if let Some(msg) = panic.downcast_ref::<&'static str>() { - Trap::new(*msg) - } else { - Trap::new("rust panic happened") - }; - let trap = Box::new(wasm_trap_t::new(trap)); + let err = error_from_panic(panic); + let trap = Box::new(wasm_trap_t::new(err)); Box::into_raw(trap) } } } +fn error_from_panic(panic: Box) -> Error { + if let Some(msg) = panic.downcast_ref::() { + Error::msg(msg.clone()) + } else if let Some(msg) = panic.downcast_ref::<&'static str>() { + Error::msg(*msg) + } else { + Error::msg("rust panic happened") + } +} + #[no_mangle] pub unsafe extern "C" fn wasm_func_type(f: &wasm_func_t) -> Box { Box::new(wasm_functype_t::new(f.func().ty(f.ext.store.context()))) @@ -235,7 +238,7 @@ pub(crate) unsafe fn c_callback_to_rust_fn( callback: wasmtime_func_callback_t, data: *mut c_void, finalizer: Option, -) -> impl Fn(Caller<'_, crate::StoreData>, &[Val], &mut [Val]) -> Result<(), Trap> { +) -> impl Fn(Caller<'_, crate::StoreData>, &[Val], &mut [Val]) -> Result<()> { let foreign = crate::ForeignData { data, finalizer }; move |mut caller, params, results| { drop(&foreign); // move entire foreign into this closure @@ -264,7 +267,7 @@ pub(crate) unsafe fn c_callback_to_rust_fn( out_results.len(), ); if let Some(trap) = out { - return Err(trap.trap); + return Err(trap.error); } // Translate the `wasmtime_val_t` results into the `results` space @@ -299,14 +302,14 @@ pub(crate) unsafe fn c_unchecked_callback_to_rust_fn( callback: wasmtime_func_unchecked_callback_t, data: *mut c_void, finalizer: Option, -) -> impl Fn(Caller<'_, crate::StoreData>, &mut [ValRaw]) -> Result<(), Trap> { +) -> impl Fn(Caller<'_, crate::StoreData>, &mut [ValRaw]) -> Result<()> { let foreign = crate::ForeignData { data, finalizer }; move |caller, values| { drop(&foreign); // move entire foreign into this closure let mut caller = wasmtime_caller_t { caller }; match callback(foreign.data, &mut caller, values.as_mut_ptr(), values.len()) { None => Ok(()), - Some(trap) => Err(trap.trap), + Some(trap) => Err(trap.error), } } } @@ -348,22 +351,10 @@ pub unsafe extern "C" fn wasmtime_func_call( store.data_mut().wasm_val_storage = params; None } - Ok(Err(trap)) => match trap.downcast::() { - Ok(trap) => { - *trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(trap))); - None - } - Err(err) => Some(Box::new(wasmtime_error_t::from(err))), - }, + Ok(Err(trap)) => store_err(trap, trap_ret), Err(panic) => { - let trap = if let Some(msg) = panic.downcast_ref::() { - Trap::new(msg) - } else if let Some(msg) = panic.downcast_ref::<&'static str>() { - Trap::new(*msg) - } else { - Trap::new("rust panic happened") - }; - *trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(trap))); + let err = error_from_panic(panic); + *trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(err))); None } } @@ -374,10 +365,20 @@ pub unsafe extern "C" fn wasmtime_func_call_unchecked( store: CStoreContextMut<'_>, func: &Func, args_and_results: *mut ValRaw, -) -> *mut wasm_trap_t { + trap_ret: &mut *mut wasm_trap_t, +) -> Option> { match func.call_unchecked(store, args_and_results) { - Ok(()) => ptr::null_mut(), - Err(trap) => Box::into_raw(Box::new(wasm_trap_t::new(trap))), + Ok(()) => None, + Err(trap) => store_err(trap, trap_ret), + } +} + +fn store_err(err: Error, trap_ret: &mut *mut wasm_trap_t) -> Option> { + if err.is::() { + *trap_ret = Box::into_raw(Box::new(wasm_trap_t::new(err))); + None + } else { + Some(Box::new(wasmtime_error_t::from(err))) } } diff --git a/crates/c-api/src/instance.rs b/crates/c-api/src/instance.rs index 4897520bedc4..13fb96a36914 100644 --- a/crates/c-api/src/instance.rs +++ b/crates/c-api/src/instance.rs @@ -41,7 +41,7 @@ pub unsafe extern "C" fn wasm_instance_new( ))), Err(e) => { if let Some(ptr) = result { - *ptr = Box::into_raw(Box::new(wasm_trap_t::new(e.into()))); + *ptr = Box::into_raw(Box::new(wasm_trap_t::new(e))); } None } @@ -98,13 +98,14 @@ pub(crate) fn handle_instantiate( *instance_ptr = i; None } - Err(e) => match e.downcast::() { - Ok(trap) => { - *trap_ptr = Box::into_raw(Box::new(wasm_trap_t::new(trap))); + Err(e) => { + if e.is::() { + *trap_ptr = Box::into_raw(Box::new(wasm_trap_t::new(e))); None + } else { + Some(Box::new(e.into())) } - Err(e) => Some(Box::new(e.into())), - }, + } } } diff --git a/crates/c-api/src/linker.rs b/crates/c-api/src/linker.rs index d5ad429fddba..31a5aeed45cd 100644 --- a/crates/c-api/src/linker.rs +++ b/crates/c-api/src/linker.rs @@ -1,6 +1,6 @@ use crate::{ bad_utf8, handle_result, wasm_engine_t, wasm_functype_t, wasm_trap_t, wasmtime_error_t, - wasmtime_extern_t, wasmtime_module_t, CStoreContextMut, + wasmtime_extern_t, wasmtime_module_t, CStoreContext, CStoreContextMut, }; use std::ffi::c_void; use std::mem::MaybeUninit; @@ -41,6 +41,7 @@ macro_rules! to_str { #[no_mangle] pub unsafe extern "C" fn wasmtime_linker_define( linker: &mut wasmtime_linker_t, + store: CStoreContext<'_>, module: *const u8, module_len: usize, name: *const u8, @@ -51,7 +52,7 @@ pub unsafe extern "C" fn wasmtime_linker_define( let module = to_str!(module, module_len); let name = to_str!(name, name_len); let item = item.to_extern(); - handle_result(linker.define(module, name, item), |_linker| ()) + handle_result(linker.define(&store, module, name, item), |_linker| ()) } #[no_mangle] diff --git a/crates/c-api/src/store.rs b/crates/c-api/src/store.rs index cac07b0ca38f..3949d46b0ea3 100644 --- a/crates/c-api/src/store.rs +++ b/crates/c-api/src/store.rs @@ -2,7 +2,10 @@ use crate::{wasm_engine_t, wasmtime_error_t, wasmtime_val_t, ForeignData}; use std::cell::UnsafeCell; use std::ffi::c_void; use std::sync::Arc; -use wasmtime::{AsContext, AsContextMut, Store, StoreContext, StoreContextMut, Val}; +use wasmtime::{ + AsContext, AsContextMut, Store, StoreContext, StoreContextMut, StoreLimits, StoreLimitsBuilder, + Val, +}; /// This representation of a `Store` is used to implement the `wasm.h` API. /// @@ -77,6 +80,9 @@ pub struct StoreData { /// Temporary storage for usage during host->wasm calls, same as above but /// for a different direction. pub wasm_val_storage: Vec, + + /// Limits for the store. + pub store_limits: StoreLimits, } #[no_mangle] @@ -94,6 +100,7 @@ pub extern "C" fn wasmtime_store_new( wasi: None, hostcall_val_storage: Vec::new(), wasm_val_storage: Vec::new(), + store_limits: StoreLimits::default(), }, ), }) @@ -104,6 +111,35 @@ pub extern "C" fn wasmtime_store_context(store: &mut wasmtime_store_t) -> CStore store.store.as_context_mut() } +#[no_mangle] +pub extern "C" fn wasmtime_store_limiter( + store: &mut wasmtime_store_t, + memory_size: i64, + table_elements: i64, + instances: i64, + tables: i64, + memories: i64, +) { + let mut limiter = StoreLimitsBuilder::new(); + if memory_size >= 0 { + limiter = limiter.memory_size(memory_size as usize); + } + if table_elements >= 0 { + limiter = limiter.table_elements(table_elements as u32); + } + if instances >= 0 { + limiter = limiter.instances(instances as usize); + } + if tables >= 0 { + limiter = limiter.tables(tables as usize); + } + if memories >= 0 { + limiter = limiter.memories(memories as usize); + } + store.store.data_mut().store_limits = limiter.build(); + store.store.limiter(|data| &mut data.store_limits); +} + #[no_mangle] pub extern "C" fn wasmtime_context_get_data(store: CStoreContext<'_>) -> *mut c_void { store.data().foreign.data diff --git a/crates/c-api/src/trap.rs b/crates/c-api/src/trap.rs index 6f60709fe4e6..44b9dfb6043b 100644 --- a/crates/c-api/src/trap.rs +++ b/crates/c-api/src/trap.rs @@ -1,25 +1,37 @@ use crate::{wasm_frame_vec_t, wasm_instance_t, wasm_name_t, wasm_store_t}; +use anyhow::{anyhow, Error}; use once_cell::unsync::OnceCell; -use wasmtime::{Trap, TrapCode}; +use wasmtime::{Trap, WasmBacktrace}; #[repr(C)] -#[derive(Clone)] pub struct wasm_trap_t { - pub(crate) trap: Trap, + pub(crate) error: Error, +} + +// This is currently only needed for the `wasm_trap_copy` API in the C API. +// +// For now the impl here is "fake it til you make it" since this is losing +// context by only cloning the error string. +impl Clone for wasm_trap_t { + fn clone(&self) -> wasm_trap_t { + wasm_trap_t { + error: anyhow!("{:?}", self.error), + } + } } wasmtime_c_api_macros::declare_ref!(wasm_trap_t); impl wasm_trap_t { - pub(crate) fn new(trap: Trap) -> wasm_trap_t { - wasm_trap_t { trap: trap } + pub(crate) fn new(error: Error) -> wasm_trap_t { + wasm_trap_t { error } } } #[repr(C)] #[derive(Clone)] -pub struct wasm_frame_t { - trap: Trap, +pub struct wasm_frame_t<'a> { + trace: &'a WasmBacktrace, idx: usize, func_name: OnceCell>, module_name: OnceCell>, @@ -40,7 +52,7 @@ pub extern "C" fn wasm_trap_new( } let message = String::from_utf8_lossy(&message[..message.len() - 1]); Box::new(wasm_trap_t { - trap: Trap::new(message), + error: Error::msg(message.into_owned()), }) } @@ -49,24 +61,28 @@ pub unsafe extern "C" fn wasmtime_trap_new(message: *const u8, len: usize) -> Bo let bytes = crate::slice_from_raw_parts(message, len); let message = String::from_utf8_lossy(&bytes); Box::new(wasm_trap_t { - trap: Trap::new(message), + error: Error::msg(message.into_owned()), }) } #[no_mangle] pub extern "C" fn wasm_trap_message(trap: &wasm_trap_t, out: &mut wasm_message_t) { let mut buffer = Vec::new(); - buffer.extend_from_slice(trap.trap.to_string().as_bytes()); + buffer.extend_from_slice(format!("{:?}", trap.error).as_bytes()); buffer.reserve_exact(1); buffer.push(0); out.set_buffer(buffer); } #[no_mangle] -pub extern "C" fn wasm_trap_origin(raw: &wasm_trap_t) -> Option> { - if raw.trap.trace().unwrap_or(&[]).len() > 0 { +pub extern "C" fn wasm_trap_origin(raw: &wasm_trap_t) -> Option>> { + let trace = match raw.error.downcast_ref::() { + Some(trap) => trap, + None => return None, + }; + if trace.frames().len() > 0 { Some(Box::new(wasm_frame_t { - trap: raw.trap.clone(), + trace, idx: 0, func_name: OnceCell::new(), module_name: OnceCell::new(), @@ -77,11 +93,19 @@ pub extern "C" fn wasm_trap_origin(raw: &wasm_trap_t) -> Option(raw: &'a wasm_trap_t, out: &mut wasm_frame_vec_t<'a>) { + error_trace(&raw.error, out) +} + +pub(crate) fn error_trace<'a>(error: &'a Error, out: &mut wasm_frame_vec_t<'a>) { + let trace = match error.downcast_ref::() { + Some(trap) => trap, + None => return out.set_buffer(Vec::new()), + }; + let vec = (0..trace.frames().len()) .map(|idx| { Some(Box::new(wasm_frame_t { - trap: raw.trap.clone(), + trace, idx, func_name: OnceCell::new(), module_name: OnceCell::new(), @@ -92,51 +116,43 @@ pub extern "C" fn wasm_trap_trace(raw: &wasm_trap_t, out: &mut wasm_frame_vec_t) } #[no_mangle] -pub extern "C" fn wasmtime_trap_code(raw: &wasm_trap_t, code: &mut i32) -> bool { - match raw.trap.trap_code() { - Some(c) => { - *code = match c { - TrapCode::StackOverflow => 0, - TrapCode::MemoryOutOfBounds => 1, - TrapCode::HeapMisaligned => 2, - TrapCode::TableOutOfBounds => 3, - TrapCode::IndirectCallToNull => 4, - TrapCode::BadSignature => 5, - TrapCode::IntegerOverflow => 6, - TrapCode::IntegerDivisionByZero => 7, - TrapCode::BadConversionToInteger => 8, - TrapCode::UnreachableCodeReached => 9, - TrapCode::Interrupt => 10, - _ => unreachable!(), - }; - true - } - None => false, - } -} - -#[no_mangle] -pub extern "C" fn wasmtime_trap_exit_status(raw: &wasm_trap_t, status: &mut i32) -> bool { - match raw.trap.i32_exit_status() { - Some(i) => { - *status = i; - true - } - None => false, - } +pub extern "C" fn wasmtime_trap_code(raw: &wasm_trap_t, code: &mut u8) -> bool { + let trap = match raw.error.downcast_ref::() { + Some(trap) => trap, + None => return false, + }; + *code = match trap { + Trap::StackOverflow => 0, + Trap::MemoryOutOfBounds => 1, + Trap::HeapMisaligned => 2, + Trap::TableOutOfBounds => 3, + Trap::IndirectCallToNull => 4, + Trap::BadSignature => 5, + Trap::IntegerOverflow => 6, + Trap::IntegerDivisionByZero => 7, + Trap::BadConversionToInteger => 8, + Trap::UnreachableCodeReached => 9, + Trap::Interrupt => 10, + Trap::OutOfFuel => 11, + Trap::AlwaysTrapAdapter => unreachable!("component model not supported"), + _ => unreachable!(), + }; + true } #[no_mangle] -pub extern "C" fn wasm_frame_func_index(frame: &wasm_frame_t) -> u32 { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx].func_index() +pub extern "C" fn wasm_frame_func_index(frame: &wasm_frame_t<'_>) -> u32 { + frame.trace.frames()[frame.idx].func_index() } #[no_mangle] -pub extern "C" fn wasmtime_frame_func_name(frame: &wasm_frame_t) -> Option<&wasm_name_t> { +pub extern "C" fn wasmtime_frame_func_name<'a>( + frame: &'a wasm_frame_t<'_>, +) -> Option<&'a wasm_name_t> { frame .func_name .get_or_init(|| { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx] + frame.trace.frames()[frame.idx] .func_name() .map(|s| wasm_name_t::from(s.to_string().into_bytes())) }) @@ -144,11 +160,13 @@ pub extern "C" fn wasmtime_frame_func_name(frame: &wasm_frame_t) -> Option<&wasm } #[no_mangle] -pub extern "C" fn wasmtime_frame_module_name(frame: &wasm_frame_t) -> Option<&wasm_name_t> { +pub extern "C" fn wasmtime_frame_module_name<'a>( + frame: &'a wasm_frame_t<'_>, +) -> Option<&'a wasm_name_t> { frame .module_name .get_or_init(|| { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx] + frame.trace.frames()[frame.idx] .module_name() .map(|s| wasm_name_t::from(s.to_string().into_bytes())) }) @@ -156,25 +174,25 @@ pub extern "C" fn wasmtime_frame_module_name(frame: &wasm_frame_t) -> Option<&wa } #[no_mangle] -pub extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t) -> usize { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx] +pub extern "C" fn wasm_frame_func_offset(frame: &wasm_frame_t<'_>) -> usize { + frame.trace.frames()[frame.idx] .func_offset() .unwrap_or(usize::MAX) } #[no_mangle] -pub extern "C" fn wasm_frame_instance(_arg1: *const wasm_frame_t) -> *mut wasm_instance_t { +pub extern "C" fn wasm_frame_instance(_arg1: *const wasm_frame_t<'_>) -> *mut wasm_instance_t { unimplemented!("wasm_frame_instance") } #[no_mangle] -pub extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t) -> usize { - frame.trap.trace().expect("backtraces are always enabled")[frame.idx] +pub extern "C" fn wasm_frame_module_offset(frame: &wasm_frame_t<'_>) -> usize { + frame.trace.frames()[frame.idx] .module_offset() .unwrap_or(usize::MAX) } #[no_mangle] -pub extern "C" fn wasm_frame_copy(frame: &wasm_frame_t) -> Box { +pub extern "C" fn wasm_frame_copy<'a>(frame: &wasm_frame_t<'a>) -> Box> { Box::new(frame.clone()) } diff --git a/crates/c-api/src/vec.rs b/crates/c-api/src/vec.rs index 77835b415000..7a5aae734ffa 100644 --- a/crates/c-api/src/vec.rs +++ b/crates/c-api/src/vec.rs @@ -19,7 +19,7 @@ impl wasm_name_t { macro_rules! declare_vecs { ( $(( - name: $name:ident, + name: $name:ident $(<$lt:tt>)?, ty: $elem_ty:ty, new: $new:ident, empty: $empty:ident, @@ -29,12 +29,12 @@ macro_rules! declare_vecs { ))* ) => {$( #[repr(C)] - pub struct $name { + pub struct $name $(<$lt>)? { size: usize, data: *mut $elem_ty, } - impl $name { + impl$(<$lt>)? $name $(<$lt>)? { pub fn set_buffer(&mut self, buffer: Vec<$elem_ty>) { let mut vec = buffer.into_boxed_slice(); self.size = vec.len(); @@ -79,13 +79,13 @@ macro_rules! declare_vecs { } } - impl Clone for $name { + impl$(<$lt>)? Clone for $name $(<$lt>)? { fn clone(&self) -> Self { self.as_slice().to_vec().into() } } - impl From> for $name { + impl$(<$lt>)? From> for $name $(<$lt>)? { fn from(vec: Vec<$elem_ty>) -> Self { let mut vec = vec.into_boxed_slice(); let result = $name { @@ -97,7 +97,7 @@ macro_rules! declare_vecs { } } - impl Drop for $name { + impl$(<$lt>)? Drop for $name $(<$lt>)? { fn drop(&mut self) { drop(self.take()); } @@ -115,8 +115,8 @@ macro_rules! declare_vecs { } #[no_mangle] - pub unsafe extern "C" fn $new( - out: &mut $name, + pub unsafe extern "C" fn $new $(<$lt>)? ( + out: &mut $name $(<$lt>)?, size: usize, ptr: *const $elem_ty, ) { @@ -125,12 +125,15 @@ macro_rules! declare_vecs { } #[no_mangle] - pub extern "C" fn $copy(out: &mut $name, src: &$name) { + pub extern "C" fn $copy $(<$lt>)? ( + out: &mut $name $(<$lt>)?, + src: &$name $(<$lt>)?, + ) { out.set_buffer(src.as_slice().to_vec()); } #[no_mangle] - pub extern "C" fn $delete(out: &mut $name) { + pub extern "C" fn $delete $(<$lt>)? (out: &mut $name $(<$lt>)?) { out.take(); } )*}; @@ -228,8 +231,8 @@ declare_vecs! { delete: wasm_val_vec_delete, ) ( - name: wasm_frame_vec_t, - ty: Option>, + name: wasm_frame_vec_t<'a>, + ty: Option>>, new: wasm_frame_vec_new, empty: wasm_frame_vec_new_empty, uninit: wasm_frame_vec_new_uninitialized, diff --git a/crates/c-api/src/wasi.rs b/crates/c-api/src/wasi.rs index 1d197473b382..e02063ed2124 100644 --- a/crates/c-api/src/wasi.rs +++ b/crates/c-api/src/wasi.rs @@ -1,14 +1,17 @@ //! The WASI embedding API definitions for Wasmtime. +use crate::wasm_byte_vec_t; use anyhow::Result; use cap_std::ambient_authority; +use std::collections::HashMap; use std::ffi::CStr; use std::fs::File; use std::os::raw::{c_char, c_int}; use std::path::{Path, PathBuf}; use std::slice; +use wasi_common::pipe::ReadPipe; use wasmtime_wasi::{ - sync::{Dir, WasiCtxBuilder}, + sync::{Dir, TcpListener, WasiCtxBuilder}, WasiCtx, }; @@ -16,6 +19,10 @@ unsafe fn cstr_to_path<'a>(path: *const c_char) -> Option<&'a Path> { CStr::from_ptr(path).to_str().map(Path::new).ok() } +unsafe fn cstr_to_str<'a>(s: *const c_char) -> Option<&'a str> { + CStr::from_ptr(s).to_str().ok() +} + unsafe fn open_file(path: *const c_char) -> Option { File::open(cstr_to_path(path)?).ok() } @@ -29,15 +36,32 @@ unsafe fn create_file(path: *const c_char) -> Option { pub struct wasi_config_t { args: Vec>, env: Vec<(Vec, Vec)>, - stdin: Option, - stdout: Option, - stderr: Option, - preopens: Vec<(Dir, PathBuf)>, + stdin: WasiConfigReadPipe, + stdout: WasiConfigWritePipe, + stderr: WasiConfigWritePipe, + preopen_dirs: Vec<(Dir, PathBuf)>, + preopen_sockets: HashMap, inherit_args: bool, inherit_env: bool, - inherit_stdin: bool, - inherit_stdout: bool, - inherit_stderr: bool, +} + +#[repr(C)] +#[derive(Default)] +pub enum WasiConfigReadPipe { + #[default] + None, + Inherit, + File(File), + Bytes(Vec), +} + +#[repr(C)] +#[derive(Default)] +pub enum WasiConfigWritePipe { + #[default] + None, + Inherit, + File(File), } wasmtime_c_api_macros::declare_own!(wasi_config_t); @@ -69,30 +93,43 @@ impl wasi_config_t { .collect::>>()?; builder = builder.envs(&env)?; } - if self.inherit_stdin { - builder = builder.inherit_stdin(); - } else if let Some(file) = self.stdin { - let file = cap_std::fs::File::from_std(file); - let file = wasi_cap_std_sync::file::File::from_cap_std(file); - builder = builder.stdin(Box::new(file)); - } - if self.inherit_stdout { - builder = builder.inherit_stdout(); - } else if let Some(file) = self.stdout { - let file = cap_std::fs::File::from_std(file); - let file = wasi_cap_std_sync::file::File::from_cap_std(file); - builder = builder.stdout(Box::new(file)); - } - if self.inherit_stderr { - builder = builder.inherit_stderr(); - } else if let Some(file) = self.stderr { - let file = cap_std::fs::File::from_std(file); - let file = wasi_cap_std_sync::file::File::from_cap_std(file); - builder = builder.stderr(Box::new(file)); - } - for (dir, path) in self.preopens { + builder = match self.stdin { + WasiConfigReadPipe::None => builder, + WasiConfigReadPipe::Inherit => builder.inherit_stdin(), + WasiConfigReadPipe::File(file) => { + let file = cap_std::fs::File::from_std(file); + let file = wasi_cap_std_sync::file::File::from_cap_std(file); + builder.stdin(Box::new(file)) + } + WasiConfigReadPipe::Bytes(binary) => { + let binary = ReadPipe::from(binary); + builder.stdin(Box::new(binary)) + } + }; + builder = match self.stdout { + WasiConfigWritePipe::None => builder, + WasiConfigWritePipe::Inherit => builder.inherit_stdout(), + WasiConfigWritePipe::File(file) => { + let file = cap_std::fs::File::from_std(file); + let file = wasi_cap_std_sync::file::File::from_cap_std(file); + builder.stdout(Box::new(file)) + } + }; + builder = match self.stderr { + WasiConfigWritePipe::None => builder, + WasiConfigWritePipe::Inherit => builder.inherit_stderr(), + WasiConfigWritePipe::File(file) => { + let file = cap_std::fs::File::from_std(file); + let file = wasi_cap_std_sync::file::File::from_cap_std(file); + builder.stderr(Box::new(file)) + } + }; + for (dir, path) in self.preopen_dirs { builder = builder.preopened_dir(dir, path)?; } + for (fd_num, listener) in self.preopen_sockets { + builder = builder.preopened_socket(fd_num, listener)?; + } Ok(builder.build()) } } @@ -159,16 +196,24 @@ pub unsafe extern "C" fn wasi_config_set_stdin_file( None => return false, }; - config.stdin = Some(file); - config.inherit_stdin = false; + config.stdin = WasiConfigReadPipe::File(file); true } +#[no_mangle] +pub unsafe extern "C" fn wasi_config_set_stdin_bytes( + config: &mut wasi_config_t, + binary: &mut wasm_byte_vec_t, +) { + let binary = binary.take(); + + config.stdin = WasiConfigReadPipe::Bytes(binary); +} + #[no_mangle] pub extern "C" fn wasi_config_inherit_stdin(config: &mut wasi_config_t) { - config.stdin = None; - config.inherit_stdin = true; + config.stdin = WasiConfigReadPipe::Inherit; } #[no_mangle] @@ -181,16 +226,14 @@ pub unsafe extern "C" fn wasi_config_set_stdout_file( None => return false, }; - config.stdout = Some(file); - config.inherit_stdout = false; + config.stdout = WasiConfigWritePipe::File(file); true } #[no_mangle] pub extern "C" fn wasi_config_inherit_stdout(config: &mut wasi_config_t) { - config.stdout = None; - config.inherit_stdout = true; + config.stdout = WasiConfigWritePipe::Inherit; } #[no_mangle] @@ -203,16 +246,14 @@ pub unsafe extern "C" fn wasi_config_set_stderr_file( None => return false, }; - (*config).stderr = Some(file); - (*config).inherit_stderr = false; + config.stderr = WasiConfigWritePipe::File(file); true } #[no_mangle] pub extern "C" fn wasi_config_inherit_stderr(config: &mut wasi_config_t) { - config.stderr = None; - config.inherit_stderr = true; + config.stderr = WasiConfigWritePipe::Inherit; } #[no_mangle] @@ -234,7 +275,38 @@ pub unsafe extern "C" fn wasi_config_preopen_dir( None => return false, }; - (*config).preopens.push((dir, guest_path.to_owned())); + (*config).preopen_dirs.push((dir, guest_path.to_owned())); + + true +} + +#[no_mangle] +pub unsafe extern "C" fn wasi_config_preopen_socket( + config: &mut wasi_config_t, + fd_num: u32, + host_port: *const c_char, +) -> bool { + let address = match cstr_to_str(host_port) { + Some(s) => s, + None => return false, + }; + let listener = match std::net::TcpListener::bind(address) { + Ok(listener) => listener, + Err(_) => return false, + }; + + if let Err(_) = listener.set_nonblocking(true) { + return false; + } + + // Caller cannot call in more than once with the same FD number so return an error. + if (*config).preopen_sockets.contains_key(&fd_num) { + return false; + } + + (*config) + .preopen_sockets + .insert(fd_num, TcpListener::from_std(listener)); true } diff --git a/crates/cache/Cargo.toml b/crates/cache/Cargo.toml index 36ad2694b684..b7fb059880d1 100644 --- a/crates/cache/Cargo.toml +++ b/crates/cache/Cargo.toml @@ -1,36 +1,36 @@ [package] name = "wasmtime-cache" -version = "0.41.0" -authors = ["The Wasmtime Project Developers"] +version.workspace = true +authors.workspace = true description = "Support for automatic module caching with Wasmtime" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" documentation = "https://docs.rs/wasmtime-cache/" -edition = "2021" +edition.workspace = true [dependencies] -anyhow = "1.0" -base64 = "0.13.0" +anyhow = { workspace = true } +base64 = "0.21.0" bincode = "1.1.4" directories-next = "2.0" file-per-thread-logger = "0.1.1" -log = { version = "0.4.8", default-features = false } +log = { workspace = true } serde = { version = "1.0.94", features = ["derive"] } -sha2 = "0.9.0" +sha2 = "0.10.2" toml = "0.5.5" zstd = { version = "0.11.1", default-features = false } [target.'cfg(target_os = "windows")'.dependencies.windows-sys] -version = "0.36.0" +workspace = true features = [ "Win32_System_Threading", ] [target.'cfg(not(target_os = "windows"))'.dependencies] -rustix = { version = "0.35.6", features = ["process"] } +rustix = { workspace = true, features = ["process"] } [dev-dependencies] filetime = "0.2.7" -once_cell = "1.12.0" +once_cell = { workspace = true } pretty_env_logger = "0.4.0" tempfile = "3" diff --git a/crates/cache/src/lib.rs b/crates/cache/src/lib.rs index 64f7d8e26267..a997846c1af1 100644 --- a/crates/cache/src/lib.rs +++ b/crates/cache/src/lib.rs @@ -1,3 +1,4 @@ +use base64::Engine; use log::{debug, trace, warn}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -86,7 +87,7 @@ impl<'config> ModuleCacheEntry<'config> { state.hash(&mut hasher); let hash: [u8; 32] = hasher.0.finalize().into(); // standard encoding uses '/' which can't be used for filename - let hash = base64::encode_config(&hash, base64::URL_SAFE_NO_PAD); + let hash = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&hash); if let Some(cached_val) = inner.get_data(&hash) { if let Some(val) = deserialize(state, cached_val) { diff --git a/crates/cli-flags/Cargo.toml b/crates/cli-flags/Cargo.toml index ef8d87419fa9..45bf2b748cc3 100644 --- a/crates/cli-flags/Cargo.toml +++ b/crates/cli-flags/Cargo.toml @@ -1,20 +1,20 @@ [package] name = "wasmtime-cli-flags" -version = "0.41.0" -authors = ["The Wasmtime Project Developers"] +version.workspace = true +authors.workspace = true description = "Exposes common CLI flags used for running Wasmtime" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" documentation = "https://docs.rs/wasmtime-cache/" -edition = "2021" +edition.workspace = true [dependencies] -anyhow = "1.0.19" -clap = { version = "3.2.0", features = ["color", "suggestions", "derive"] } +anyhow = { workspace = true } +clap = { workspace = true } file-per-thread-logger = "0.1.1" pretty_env_logger = "0.4.0" rayon = "1.5.0" -wasmtime = { path = "../wasmtime", version = "0.41.0", default-features = false } +wasmtime = { workspace = true } [features] default = [ @@ -22,7 +22,7 @@ default = [ "wasmtime/cranelift", "wasmtime/jitdump", "wasmtime/vtune", + "wasmtime/parallel-compilation", ] pooling-allocator = [] -memory-init-cow = [] component-model = [] diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index 386f6fbe498a..b423db6a3c50 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -16,13 +16,11 @@ ) )] -use anyhow::{bail, Context, Result}; +use anyhow::{bail, Result}; use clap::Parser; use std::collections::HashMap; use std::path::PathBuf; use wasmtime::{Config, ProfilingStrategy}; -#[cfg(feature = "pooling-allocator")] -use wasmtime::{InstanceLimits, PoolingAllocationStrategy}; pub const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ ("all", "enables all supported WebAssembly features"), @@ -56,13 +54,17 @@ pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[ "wasi-common", "enables support for the WASI common APIs, see https://github.com/WebAssembly/WASI", ), + ( + "experimental-wasi-crypto", + "enables support for the WASI cryptography APIs (experimental), see https://github.com/WebAssembly/wasi-crypto", + ), ( "experimental-wasi-nn", "enables support for the WASI neural network API (experimental), see https://github.com/WebAssembly/wasi-nn", ), ( - "experimental-wasi-crypto", - "enables support for the WASI cryptography APIs (experimental), see https://github.com/WebAssembly/wasi-crypto", + "experimental-wasi-threads", + "enables support for the WASI threading API (experimental), see https://github.com/WebAssembly/wasi-threads", ), ]; @@ -112,11 +114,11 @@ pub struct CommonOptions { #[clap(long, parse(from_os_str), value_name = "CONFIG_PATH")] pub config: Option, - /// Disable logging. + /// Disable logging #[clap(long, conflicts_with = "log-to-files")] pub disable_logging: bool, - /// Log to per-thread log files instead of stderr. + /// Log to per-thread log files instead of stderr #[clap(long)] pub log_to_files: bool, @@ -128,11 +130,15 @@ pub struct CommonOptions { #[clap(long)] pub disable_cache: bool, - /// Enables or disables WebAssembly features + /// Disable parallel compilation + #[clap(long)] + pub disable_parallel_compilation: bool, + + /// Enable or disable WebAssembly features #[clap(long, value_name = "FEATURE,FEATURE,...", parse(try_from_str = parse_wasm_features))] pub wasm_features: Option, - /// Enables or disables WASI modules + /// Enable or disable WASI modules #[clap(long, value_name = "MODULE,MODULE,...", parse(try_from_str = parse_wasi_modules))] pub wasi_modules: Option, @@ -178,15 +184,15 @@ pub struct CommonOptions { #[clap(long, value_name = "MAXIMUM")] pub static_memory_maximum_size: Option, - /// Force using a "static" style for all wasm memories. + /// Force using a "static" style for all wasm memories #[clap(long)] pub static_memory_forced: bool, - /// Byte size of the guard region after static memories are allocated. + /// Byte size of the guard region after static memories are allocated #[clap(long, value_name = "SIZE")] pub static_memory_guard_size: Option, - /// Byte size of the guard region after dynamic memories are allocated. + /// Byte size of the guard region after dynamic memories are allocated #[clap(long, value_name = "SIZE")] pub dynamic_memory_guard_size: Option, @@ -214,31 +220,28 @@ pub struct CommonOptions { #[clap(long)] pub epoch_interruption: bool, - /// Disables the on-by-default address map from native code to wasm code. + /// Disable the on-by-default address map from native code to wasm code #[clap(long)] pub disable_address_map: bool, - /// Disables the default of attempting to initialize linear memory via a - /// copy-on-write mapping. - #[cfg(feature = "memory-init-cow")] + /// Disable the default of attempting to initialize linear memory via a + /// copy-on-write mapping #[clap(long)] pub disable_memory_init_cow: bool, - /// Enables the pooling allocator, in place of the on-demand + /// Enable the pooling allocator, in place of the on-demand /// allocator. #[cfg(feature = "pooling-allocator")] #[clap(long)] pub pooling_allocator: bool, + + /// Maximum stack size, in bytes, that wasm is allowed to consume before a + /// stack overflow is reported. + #[clap(long)] + pub max_wasm_stack: Option, } impl CommonOptions { - pub fn parse_from_str(s: &str) -> Result { - let parts = s.split(" "); - let options = - Self::try_parse_from(parts).context("unable to parse options from passed flags")?; - Ok(options) - } - pub fn init_logging(&self) { if self.disable_logging { return; @@ -292,6 +295,10 @@ impl CommonOptions { } } + if self.disable_parallel_compilation { + config.parallel_compilation(false); + } + if let Some(max) = self.static_memory_maximum_size { config.static_memory_maximum_size(max); } @@ -313,20 +320,19 @@ impl CommonOptions { config.epoch_interruption(self.epoch_interruption); config.generate_address_map(!self.disable_address_map); - #[cfg(feature = "memory-init-cow")] config.memory_init_cow(!self.disable_memory_init_cow); #[cfg(feature = "pooling-allocator")] { if self.pooling_allocator { - let instance_limits = InstanceLimits::default(); - config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling { - strategy: PoolingAllocationStrategy::NextAvailable, - instance_limits, - }); + config.allocation_strategy(wasmtime::InstanceAllocationStrategy::pooling()); } } + if let Some(max) = self.max_wasm_stack { + config.max_wasm_stack(max); + } + Ok(config) } @@ -474,8 +480,9 @@ fn parse_wasi_modules(modules: &str) -> Result { let mut set = |module: &str, enable: bool| match module { "" => Ok(()), "wasi-common" => Ok(wasi_modules.wasi_common = enable), - "experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable), "experimental-wasi-crypto" => Ok(wasi_modules.wasi_crypto = enable), + "experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable), + "experimental-wasi-threads" => Ok(wasi_modules.wasi_threads = enable), "default" => bail!("'default' cannot be specified with other WASI modules"), _ => bail!("unsupported WASI module '{}'", module), }; @@ -502,19 +509,23 @@ pub struct WasiModules { /// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.). pub wasi_common: bool, + /// Enable the experimental wasi-crypto implementation. + pub wasi_crypto: bool, + /// Enable the experimental wasi-nn implementation. pub wasi_nn: bool, - /// Enable the experimental wasi-crypto implementation. - pub wasi_crypto: bool, + /// Enable the experimental wasi-threads implementation. + pub wasi_threads: bool, } impl Default for WasiModules { fn default() -> Self { Self { wasi_common: true, - wasi_nn: false, wasi_crypto: false, + wasi_nn: false, + wasi_threads: false, } } } @@ -526,6 +537,7 @@ impl WasiModules { wasi_common: false, wasi_nn: false, wasi_crypto: false, + wasi_threads: false, } } } @@ -677,8 +689,9 @@ mod test { options.wasi_modules.unwrap(), WasiModules { wasi_common: true, + wasi_crypto: false, wasi_nn: false, - wasi_crypto: false + wasi_threads: false } ); } @@ -690,8 +703,9 @@ mod test { options.wasi_modules.unwrap(), WasiModules { wasi_common: true, + wasi_crypto: false, wasi_nn: false, - wasi_crypto: false + wasi_threads: false } ); } @@ -707,8 +721,9 @@ mod test { options.wasi_modules.unwrap(), WasiModules { wasi_common: false, + wasi_crypto: false, wasi_nn: true, - wasi_crypto: false + wasi_threads: false } ); } @@ -721,29 +736,10 @@ mod test { options.wasi_modules.unwrap(), WasiModules { wasi_common: false, + wasi_crypto: false, wasi_nn: false, - wasi_crypto: false + wasi_threads: false } ); } - - #[test] - fn test_parse_from_str() { - fn use_func(flags: &str) -> CommonOptions { - CommonOptions::parse_from_str(flags).unwrap() - } - fn use_clap_parser(flags: &[&str]) -> CommonOptions { - CommonOptions::try_parse_from(flags).unwrap() - } - - assert_eq!(use_func(""), use_clap_parser(&[])); - assert_eq!( - use_func("foo --wasm-features=threads"), - use_clap_parser(&["foo", "--wasm-features=threads"]) - ); - assert_eq!( - use_func("foo --cranelift-set enable_simd=true"), - use_clap_parser(&["foo", "--cranelift-set", "enable_simd=true"]) - ); - } } diff --git a/crates/component-macro/Cargo.toml b/crates/component-macro/Cargo.toml index 6ca751501420..ea816d8a50e4 100644 --- a/crates/component-macro/Cargo.toml +++ b/crates/component-macro/Cargo.toml @@ -1,23 +1,36 @@ [package] name = "wasmtime-component-macro" -version = "0.41.0" -authors = ["The Wasmtime Project Developers"] +version.workspace = true +authors.workspace = true description = "Macros for deriving component interface types from Rust types" license = "Apache-2.0 WITH LLVM-exception" repository = "https://github.com/bytecodealliance/wasmtime" documentation = "https://docs.rs/wasmtime-component-macro/" categories = ["wasm"] keywords = ["webassembly", "wasm"] -edition = "2021" +edition.workspace = true [lib] proc-macro = true +test = false +doctest = false [dependencies] +anyhow = "1.0" proc-macro2 = "1.0" quote = "1.0" syn = { version = "1.0", features = ["extra-traits"] } -wasmtime-component-util = { path = "../component-util", version = "=0.41.0" } +wasmtime-component-util = { workspace = true } +wasmtime-wit-bindgen = { workspace = true } +wit-parser = { workspace = true } [badges] maintenance = { status = "actively-developed" } + +[dev-dependencies] +wasmtime = { path = '../wasmtime', features = ['component-model'] } +component-macro-test-helpers = { path = 'test-helpers' } +tracing = { workspace = true } + +[features] +async = [] diff --git a/crates/component-macro/src/bindgen.rs b/crates/component-macro/src/bindgen.rs new file mode 100644 index 000000000000..c96e55e54bd4 --- /dev/null +++ b/crates/component-macro/src/bindgen.rs @@ -0,0 +1,213 @@ +use proc_macro2::{Span, TokenStream}; +use std::path::{Path, PathBuf}; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{braced, token, Ident, Token}; +use wasmtime_wit_bindgen::{Opts, TrappableError}; +use wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId}; + +pub struct Config { + opts: Opts, + resolve: Resolve, + world: WorldId, + files: Vec, +} + +pub fn expand(input: &Config) -> Result { + if !cfg!(feature = "async") && input.opts.async_ { + return Err(Error::new( + Span::call_site(), + "cannot enable async bindings unless `async` crate feature is active", + )); + } + + let src = input.opts.generate(&input.resolve, input.world); + let mut contents = src.parse::().unwrap(); + + // Include a dummy `include_str!` for any files we read so rustc knows that + // we depend on the contents of those files. + for file in input.files.iter() { + contents.extend( + format!("const _: &str = include_str!(r#\"{}\"#);\n", file.display()) + .parse::() + .unwrap(), + ); + } + + Ok(contents) +} + +enum Source { + Path(String), + Inline(String), +} + +impl Parse for Config { + fn parse(input: ParseStream<'_>) -> Result { + let call_site = Span::call_site(); + let mut opts = Opts::default(); + let mut source = None; + let mut world = None; + + if input.peek(token::Brace) { + let content; + syn::braced!(content in input); + let fields = Punctuated::::parse_terminated(&content)?; + let mut world = None; + for field in fields.into_pairs() { + match field.into_value() { + Opt::Path(s) => { + if source.is_some() { + return Err(Error::new(s.span(), "cannot specify second source")); + } + source = Some(Source::Path(s.value())); + } + Opt::World(s) => { + if world.is_some() { + return Err(Error::new(s.span(), "cannot specify second world")); + } + world = Some(s.value()); + } + Opt::Inline(s) => { + if source.is_some() { + return Err(Error::new(s.span(), "cannot specify second source")); + } + source = Some(Source::Inline(s.value())); + } + Opt::Tracing(val) => opts.tracing = val, + Opt::Async(val) => opts.async_ = val, + Opt::TrappableErrorType(val) => opts.trappable_error_type = val, + } + } + } else { + world = input.parse::>()?.map(|s| s.value()); + if input.parse::>()?.is_some() { + source = Some(Source::Path(input.parse::()?.value())); + } + } + let (resolve, pkg, files) = + parse_source(&source).map_err(|err| Error::new(call_site, format!("{err:?}")))?; + let world = resolve + .select_world(pkg, world.as_deref()) + .map_err(|e| Error::new(call_site, format!("{e:?}")))?; + Ok(Config { + opts, + resolve, + world, + files, + }) + } +} + +fn parse_source(source: &Option) -> anyhow::Result<(Resolve, PackageId, Vec)> { + let mut resolve = Resolve::default(); + let mut files = Vec::new(); + let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + let mut parse = |path: &Path| -> anyhow::Result<_> { + if path.is_dir() { + let (pkg, sources) = resolve.push_dir(&path)?; + files = sources; + Ok(pkg) + } else { + let pkg = UnresolvedPackage::parse_file(path)?; + files.extend(pkg.source_files().map(|s| s.to_owned())); + resolve.push(pkg, &Default::default()) + } + }; + let pkg = match source { + Some(Source::Inline(s)) => resolve.push( + UnresolvedPackage::parse("macro-input".as_ref(), &s)?, + &Default::default(), + )?, + Some(Source::Path(s)) => parse(&root.join(&s))?, + None => parse(&root.join("wit"))?, + }; + + Ok((resolve, pkg, files)) +} + +mod kw { + syn::custom_keyword!(inline); + syn::custom_keyword!(path); + syn::custom_keyword!(tracing); + syn::custom_keyword!(trappable_error_type); + syn::custom_keyword!(world); +} + +enum Opt { + World(syn::LitStr), + Path(syn::LitStr), + Inline(syn::LitStr), + Tracing(bool), + Async(bool), + TrappableErrorType(Vec), +} + +impl Parse for Opt { + fn parse(input: ParseStream<'_>) -> Result { + let l = input.lookahead1(); + if l.peek(kw::path) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Path(input.parse()?)) + } else if l.peek(kw::inline) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Inline(input.parse()?)) + } else if l.peek(kw::world) { + input.parse::()?; + input.parse::()?; + Ok(Opt::World(input.parse()?)) + } else if l.peek(kw::tracing) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Tracing(input.parse::()?.value)) + } else if l.peek(Token![async]) { + input.parse::()?; + input.parse::()?; + Ok(Opt::Async(input.parse::()?.value)) + } else if l.peek(kw::trappable_error_type) { + input.parse::()?; + input.parse::()?; + let contents; + let _lbrace = braced!(contents in input); + let fields: Punctuated<(String, String, String), Token![,]> = + contents.parse_terminated(trappable_error_field_parse)?; + Ok(Opt::TrappableErrorType( + fields + .into_iter() + .map(|(wit_owner, wit_name, rust_name)| TrappableError { + wit_owner: Some(wit_owner), + wit_name, + rust_name, + }) + .collect(), + )) + } else { + Err(l.error()) + } + } +} + +fn trappable_error_field_parse(input: ParseStream<'_>) -> Result<(String, String, String)> { + // Accept a Rust identifier or a string literal. This is required + // because not all wit identifiers are Rust identifiers, so we can + // smuggle the invalid ones inside quotes. + fn ident_or_str(input: ParseStream<'_>) -> Result { + let l = input.lookahead1(); + if l.peek(syn::LitStr) { + Ok(input.parse::()?.value()) + } else if l.peek(syn::Ident) { + Ok(input.parse::()?.to_string()) + } else { + Err(l.error()) + } + } + + let interface = ident_or_str(input)?; + input.parse::()?; + let type_ = ident_or_str(input)?; + input.parse::()?; + let rust_type = input.parse::()?.to_string(); + Ok((interface, type_, rust_type)) +} diff --git a/crates/component-macro/src/component.rs b/crates/component-macro/src/component.rs new file mode 100644 index 000000000000..b054f993ce11 --- /dev/null +++ b/crates/component-macro/src/component.rs @@ -0,0 +1,1199 @@ +use proc_macro2::{Literal, TokenStream, TokenTree}; +use quote::{format_ident, quote}; +use std::collections::HashSet; +use std::fmt; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::{braced, parse_quote, Data, DeriveInput, Error, Result, Token}; +use wasmtime_component_util::{DiscriminantSize, FlagsSize}; + +#[derive(Debug, Copy, Clone)] +pub enum VariantStyle { + Variant, + Enum, + Union, +} + +impl fmt::Display for VariantStyle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Self::Variant => "variant", + Self::Enum => "enum", + Self::Union => "union", + }) + } +} + +#[derive(Debug, Copy, Clone)] +enum Style { + Record, + Variant(VariantStyle), +} + +fn find_style(input: &DeriveInput) -> Result