From 1a3d4ef641c8c910c93f112c44d9d0a67c18ad6f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Feb 2020 12:54:31 -0800 Subject: [PATCH 1/4] Implement the reference types proposal This commit enables the `reference-types` and `bulk-memory-operations` proposals from the upstream wasm spec test suite. This involved fully implementing the reference types proposal, notably passive element segments as well as a few table-related instructions. A summary of the changes made here are: * `TableCopy`, `TableInit`, and `ElemDrop` are now all implemented instructions. * All element segments now live as an `Element` type rather than being embedded directly into a `Table`. This is because they all need an index to refer to in `ElemDrop` instructions and such. * Type checking has been updated to handle subtyping * The `Funcref` and `Nullref` value types have been added * Tables now list their element type as a struct field * Elements now participate in GC passes * Global initializers are expanded with `ref.func` and `ref.null` --- crates/macro/src/lib.rs | 12 + .../tests/round_trip/elem-segments-2.wat | 10 +- crates/tests/tests/spec-tests.rs | 25 +- src/dot.rs | 5 +- src/init_expr.rs | 13 +- src/ir/mod.rs | 26 ++ src/module/elements.rs | 245 +++++++----------- .../functions/local_function/context.rs | 4 +- src/module/functions/local_function/emit.rs | 14 + src/module/functions/local_function/mod.rs | 140 ++++++---- src/module/imports.rs | 16 +- src/module/mod.rs | 4 +- src/module/tables.rs | 87 ++----- src/parse.rs | 2 +- src/passes/used.rs | 106 ++++---- src/passes/validate.rs | 21 +- src/ty.rs | 19 ++ 17 files changed, 391 insertions(+), 358 deletions(-) diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs index dc3eba51..ed2ab073 100755 --- a/crates/macro/src/lib.rs +++ b/crates/macro/src/lib.rs @@ -561,6 +561,12 @@ fn create_visit(variants: &[WalrusVariant]) -> impl quote::ToTokens { // ... } + /// Visit `ElementId` + #[inline] + fn visit_element_id(&mut self, elem: &crate::ElementId) { + // ... + } + /// Visit `Value`. #[inline] fn visit_value(&mut self, value: &crate::ir::Value) { @@ -645,6 +651,12 @@ fn create_visit(variants: &[WalrusVariant]) -> impl quote::ToTokens { // ... } + /// Visit `ElementId` + #[inline] + fn visit_element_id_mut(&mut self, elem: &mut crate::ElementId) { + // ... + } + /// Visit `Value`. #[inline] fn visit_value_mut(&mut self, value: &mut crate::ir::Value) { diff --git a/crates/tests/tests/round_trip/elem-segments-2.wat b/crates/tests/tests/round_trip/elem-segments-2.wat index 350a9622..53b5f0ae 100644 --- a/crates/tests/tests/round_trip/elem-segments-2.wat +++ b/crates/tests/tests/round_trip/elem-segments-2.wat @@ -6,4 +6,12 @@ (export "foo" (table 0)) ) -;; CHECK: (elem (;0;) (i32.const 1) 0 0) +(; CHECK-ALL: + (module + (type (;0;) (func)) + (func (;0;) (type 0)) + (table (;0;) 1 funcref) + (export "foo" (table 0)) + (elem (;0;) (i32.const 1) 0) + (elem (;1;) (i32.const 2) 0)) +;) diff --git a/crates/tests/tests/spec-tests.rs b/crates/tests/tests/spec-tests.rs index 098080a1..c48b398a 100644 --- a/crates/tests/tests/spec-tests.rs +++ b/crates/tests/tests/spec-tests.rs @@ -28,13 +28,8 @@ fn run(wast: &Path) -> Result<(), anyhow::Error> { Some("sign-extension-ops") => &["--enable-sign-extension"], Some("multi-value") => &["--enable-multi-value"], Some("nontrapping-float-to-int-conversions") => &["--enable-saturating-float-to-int"], - - // Currently wabt doesn't have support for `ref.host` which is used in - // these tests. - Some("reference-types") => return Ok(()), - - // Currently wabt has broken support for `ref.func` initializers - Some("bulk-memory-operations") => return Ok(()), + Some("reference-types") => &["--enable-reference-types", "--enable-bulk-memory"], + Some("bulk-memory-operations") => &["--enable-bulk-memory"], // TODO: should get threads working Some("threads") => return Ok(()), @@ -76,8 +71,12 @@ fn run(wast: &Path) -> Result<(), anyhow::Error> { let path = tempdir.path().join(filename); match command["type"].as_str().unwrap() { "assert_invalid" | "assert_malformed" => { - if command["text"].as_str().unwrap() == "invalid result arity" { - // These tests are valid with multi-value! + // Skip tests that are actually valid with various in-flight proposals + let text = command["text"].as_str().unwrap(); + if text == "invalid result arity" + || text == "multiple memories" + || text == "multiple tables" + { continue; } let wasm = fs::read(&path)?; @@ -89,6 +88,14 @@ fn run(wast: &Path) -> Result<(), anyhow::Error> { if message.contains("invalid result arity") { continue; } + + // MVP wasm considers this tests to fail, but + // reference-types-enhanced wasm considers this test to + // pass. We implement the reference-types semantics, so + // let's go forward with that. + if wast.ends_with("unreached-invalid.wast") && line == 539 { + continue; + } panic!("wasm parsed when it shouldn't (line {})", line); } } diff --git a/src/dot.rs b/src/dot.rs index cd237720..7a710308 100644 --- a/src/dot.rs +++ b/src/dot.rs @@ -281,7 +281,6 @@ impl DotNode for Table { fields.add_field(&[&format!("Table {:?}", self.id())]); fields.add_field(&["initial", &self.initial.to_string()]); fields.add_field(&["maximum", &format!("{:?}", self.maximum)]); - fields.add_field(&["kind", &format!("{:?}", self.kind)]); if self.import.is_some() { fields.add_field_with_port("import", "import"); } @@ -528,7 +527,9 @@ impl DotNode for Element { fn edges(&self, edges: &mut impl EdgeAggregator) { for m in self.members.iter() { - edges.add_edge(m); + if let Some(m) = m { + edges.add_edge(m); + } } } } diff --git a/src/init_expr.rs b/src/init_expr.rs index f17f5aeb..4a4cef91 100644 --- a/src/init_expr.rs +++ b/src/init_expr.rs @@ -3,7 +3,7 @@ use crate::emit::{Emit, EmitContext}; use crate::ir::Value; use crate::parse::IndicesToIds; -use crate::{GlobalId, Result}; +use crate::{GlobalId, Result, FunctionId}; use anyhow::bail; /// A constant which is produced in WebAssembly, typically used in global @@ -14,6 +14,10 @@ pub enum InitExpr { Value(Value), /// A constant value referenced by the global specified Global(GlobalId), + /// A null reference + RefNull, + /// A function initializer + RefFunc(FunctionId), } impl InitExpr { @@ -27,6 +31,8 @@ impl InitExpr { F64Const { value } => InitExpr::Value(Value::F64(f64::from_bits(value.bits()))), V128Const { value } => InitExpr::Value(Value::V128(v128_to_u128(&value))), GlobalGet { global_index } => InitExpr::Global(ids.get_global(global_index)?), + RefNull => InitExpr::RefNull, + RefFunc { function_index } => InitExpr::RefFunc(ids.get_func(function_index)?), _ => bail!("invalid constant expression"), }; match reader.read()? { @@ -47,6 +53,11 @@ impl Emit for InitExpr { cx.encoder.byte(0x23); // global.get cx.encoder.u32(idx); } + InitExpr::RefNull => cx.encoder.byte(0xd0), // ref.null + InitExpr::RefFunc(id) => { + cx.encoder.byte(0xd2); // ref.func + cx.encoder.u32(cx.indices.get_func_index(id)); + } } cx.encoder.byte(0x0b); // end } diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 270aac64..0bb58595 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -10,6 +10,7 @@ pub use self::traversals::*; use crate::encode::Encoder; use crate::{ DataId, FunctionId, GlobalId, LocalFunction, MemoryId, ModuleTypes, TableId, TypeId, ValType, + ElementId, }; use id_arena::Id; use std::fmt; @@ -556,6 +557,28 @@ pub enum Instr { #[walrus(skip_visit)] arg: MemArg, }, + + /// `table.init` + TableInit { + /// The table we're copying into. + table: TableId, + /// The element we're getting items from. + elem: ElementId, + }, + + /// `elem.drop` + ElemDrop { + /// The elem segment to drop + elem: ElementId, + }, + + /// `table.copy` + TableCopy { + /// The source table + src: TableId, + /// The destination table + dst: TableId, + }, } /// Argument in `V128Shuffle` of lane indices to select @@ -1177,6 +1200,9 @@ impl Instr { | Instr::V128Shuffle(..) | Instr::LoadSimd(..) | Instr::AtomicFence(..) + | Instr::TableInit(..) + | Instr::TableCopy(..) + | Instr::ElemDrop(..) | Instr::Drop(..) => false, } } diff --git a/src/module/elements.rs b/src/module/elements.rs index 63403654..fb10c9a6 100644 --- a/src/module/elements.rs +++ b/src/module/elements.rs @@ -1,10 +1,9 @@ //! Table elements within a wasm module. use crate::emit::{Emit, EmitContext, Section}; -use crate::ir::Value; use crate::parse::IndicesToIds; use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; -use crate::{FunctionId, InitExpr, Module, Result, TableKind, ValType}; +use crate::{FunctionId, InitExpr, Module, Result, TableId, ValType, ir::Value}; use anyhow::{bail, Context}; /// A passive element segment identifier @@ -15,8 +14,22 @@ pub type ElementId = Id; pub struct Element { id: Id, + /// Whether this segment is passive or active. + pub kind: ElementKind, + + /// The type of elements in this segment + pub ty: ValType, + /// The function members of this passive elements segment. - pub members: Vec, + pub members: Vec>, +} + +#[allow(missing_docs)] +#[derive(Debug, Copy, Clone)] +pub enum ElementKind { + Passive, + Declared, + Active { table: TableId, offset: InitExpr }, } impl Element { @@ -74,55 +87,52 @@ impl Module { log::debug!("parse element section"); for (i, segment) in section.into_iter().enumerate() { let segment = segment?; - - match segment.kind { - wasmparser::ElementKind::Passive { .. } | wasmparser::ElementKind::Declared => { - bail!("passive element segments not supported yet"); - } + let ty = ValType::parse(&segment.ty)?; + match ty { + ValType::Funcref => {} + _ => bail!("only funcref type allowed in element segments"), + } + let members = segment + .items + .get_items_reader()? + .into_iter() + .map(|e| -> Result<_> { + Ok(match e? { + wasmparser::ElementItem::Func(f) => Some(ids.get_func(f)?), + wasmparser::ElementItem::Null => None, + }) + }) + .collect::>()?; + let id = self.elements.arena.next_id(); + + let kind = match segment.kind { + wasmparser::ElementKind::Passive => ElementKind::Passive, + wasmparser::ElementKind::Declared => ElementKind::Declared, wasmparser::ElementKind::Active { table_index, init_expr, } => { let table = ids.get_table(table_index)?; - let table = match &mut self.tables.get_mut(table).kind { - TableKind::Function(t) => t, - TableKind::Anyref(_) => { - bail!("active anyref segments not supported yet"); - } - }; + self.tables.get_mut(table).elem_segments.insert(id); let offset = InitExpr::eval(&init_expr, ids) .with_context(|| format!("in segment {}", i))?; - let functions = - segment - .items - .get_items_reader()? - .into_iter() - .map(|e| -> Result<_> { - Ok(match e? { - wasmparser::ElementItem::Func(f) => Some(ids.get_func(f)?), - wasmparser::ElementItem::Null => None, - }) - }); - match offset { - InitExpr::Value(Value::I32(n)) => { - let offset = n as usize; - for (i, id) in functions.enumerate() { - while i + offset + 1 > table.elements.len() { - table.elements.push(None); - } - table.elements[i + offset] = id?; - } - } + InitExpr::Value(Value::I32(_)) => {} InitExpr::Global(global) if self.globals.get(global).ty == ValType::I32 => { - let list = functions.collect::>()?; - table.relative_elements.push((global, list)); } _ => bail!("non-i32 constant in segment {}", i), } + ElementKind::Active { table, offset } } - } + }; + self.elements.arena.alloc(Element { + id, + ty, + kind, + members, + }); + ids.push_element(id); } Ok(()) } @@ -130,128 +140,65 @@ impl Module { impl Emit for ModuleElements { fn emit(&self, cx: &mut EmitContext) { - log::debug!("emit element section"); - // Sort table ids for a deterministic emission for now, eventually we - // may want some sort of sorting heuristic here. - let mut active = cx - .module - .tables - .iter() - .filter_map(|t| match &t.kind { - TableKind::Function(list) => Some((t.id(), list)), - TableKind::Anyref(_) => None, - }) - .collect::>(); - active.sort_by_key(|pair| pair.0); - - // Append segments as we find them for all table initializers. We can - // skip initializers for unused tables, and otherwise we just want to - // create an initializer for each contiguous chunk of function indices. - let mut chunks = Vec::new(); - for (table_id, table) in active.iter() { - let mut offset = 0; - let mut len = 0; - for (i, item) in table.elements.iter().enumerate() { - if item.is_some() { - if len == 0 { - offset = i; - } - len += 1; - } else { - if len > 0 { - chunks.push((table_id, table, offset, len)); - } - len = 0; - } - } - - if len > 0 { - chunks.push((table_id, table, offset, len)); - } - } - - let passive = self.iter().count(); - let relative = active - .iter() - .map(|(_, table)| table.relative_elements.len()) - .sum::(); - let total = passive + relative + chunks.len(); - - if total == 0 { + if self.arena.len() == 0 { return; } let mut cx = cx.start_section(Section::Element); - cx.encoder.usize(total); - - // Emits the leading data for describing a table's index - // - // Note that much of this is in accordance with the - // currently-in-progress bulk-memory proposal for WebAssembly. - let active_table_header = |cx: &mut EmitContext, index: u32, exprs: bool| { - let exprs_bit = if exprs { 0x4 } else { 0x0 }; - if index == 0 { - cx.encoder.byte(0x00 | exprs_bit); - } else { - cx.encoder.byte(0x02 | exprs_bit); - cx.encoder.u32(index); - } - }; + cx.encoder.usize(self.arena.len()); - // Emit all contiguous chunks of functions pointers that are located at - // constant offsets - for (&id, table, offset, len) in chunks { - let table_index = cx.indices.get_table_index(id); - active_table_header(&mut cx, table_index, false); - InitExpr::Value(Value::I32(offset as i32)).emit(&mut cx); - cx.encoder.usize(len); - for item in table.elements[offset..][..len].iter() { - let index = cx.indices.get_func_index(item.unwrap()); - cx.encoder.u32(index); + for (id, element) in self.arena.iter() { + cx.indices.push_element(id); + let exprs = element.members.iter().any(|i| i.is_none()); + let exprs_bit = if exprs { 0x04 } else { 0x00 }; + let mut encode_ty = true; + match &element.kind { + ElementKind::Active { table, offset } => { + let table_index = cx.indices.get_table_index(*table); + if table_index == 0 { + cx.encoder.byte(0x00 | exprs_bit); + offset.emit(&mut cx); + encode_ty = false; + } else { + cx.encoder.byte(0x02 | exprs_bit); + cx.encoder.u32(table_index); + offset.emit(&mut cx); + } + } + ElementKind::Passive => { + cx.encoder.byte(0x01 | exprs_bit); + } + ElementKind::Declared => { + cx.encoder.byte(0x03 | exprs_bit); + } + }; + if encode_ty { + if exprs { + element.ty.emit(&mut cx.encoder); + } else { + cx.encoder.byte(0x00); + } } - } - // Emit all chunks of function pointers that are located at relative - // global offsets. - for (id, table) in active.iter() { - let table_index = cx.indices.get_table_index(*id); - for (global, list) in table.relative_elements.iter() { - let exprs = list.iter().any(|i| i.is_none()); - active_table_header(&mut cx, table_index, exprs); - InitExpr::Global(*global).emit(&mut cx); - cx.encoder.usize(list.len()); - for func in list { - match func { - Some(id) => { - let index = cx.indices.get_func_index(*id); - if exprs { - cx.encoder.byte(0xd2); - cx.encoder.u32(index); - cx.encoder.byte(0x0b); - } else { - cx.encoder.u32(index); - } - } - None => { - cx.encoder.byte(0xd1); + cx.encoder.usize(element.members.len()); + for func in element.members.iter() { + match func { + Some(id) => { + let index = cx.indices.get_func_index(*id); + if exprs { + cx.encoder.byte(0xd2); + cx.encoder.u32(index); cx.encoder.byte(0x0b); + } else { + cx.encoder.u32(index); } } + None => { + assert!(exprs); + cx.encoder.byte(0xd0); + cx.encoder.byte(0x0b); + } } } } - - // After all the active segments are added add passive segments next. We - // may want to sort this more intelligently in the future. Otherwise - // emitting a segment here is in general much simpler than above as we - // know there are no holes. - for (id, _) in self.arena.iter() { - cx.indices.push_element(id); - // TODO: sync this with the upstream spec - panic!( - "encoding a passive element segment requires either \ - `ref.null` or `ref.func` encodings, which aren't \ - currently implemented" - ); - } } } diff --git a/src/module/functions/local_function/context.rs b/src/module/functions/local_function/context.rs index 93aacf4c..17bafaa4 100644 --- a/src/module/functions/local_function/context.rs +++ b/src/module/functions/local_function/context.rs @@ -241,12 +241,12 @@ fn impl_pop_operand_expected( (None, expected) => Ok(expected), (actual, None) => Ok(actual), (Some(actual), Some(expected)) => { - if actual != expected { + if !actual.is_subtype_of(expected) { Err(ErrorKind::InvalidWasm) .context(format!("expected type {}", expected)) .context(format!("found type {}", actual)) } else { - Ok(Some(actual)) + Ok(Some(expected)) } } } diff --git a/src/module/functions/local_function/emit.rs b/src/module/functions/local_function/emit.rs index 1ada940e..6f9a498c 100644 --- a/src/module/functions/local_function/emit.rs +++ b/src/module/functions/local_function/emit.rs @@ -822,6 +822,20 @@ impl<'instr> Visitor<'instr> for Emit<'_, '_> { } self.memarg(e.memory, &e.arg); } + TableInit(e) => { + self.encoder.raw(&[0xfc, 0x0c]); + self.encoder.u32(self.indices.get_element_index(e.elem)); + self.encoder.u32(self.indices.get_table_index(e.table)); + } + TableCopy(e) => { + self.encoder.raw(&[0xfc, 0x0e]); + self.encoder.u32(self.indices.get_table_index(e.src)); + self.encoder.u32(self.indices.get_table_index(e.dst)); + } + ElemDrop(e) => { + self.encoder.raw(&[0xfc, 0x0d]); + self.encoder.u32(self.indices.get_element_index(e.elem)); + } } } } diff --git a/src/module/functions/local_function/mod.rs b/src/module/functions/local_function/mod.rs index d4a5ad78..a209d2fa 100644 --- a/src/module/functions/local_function/mod.rs +++ b/src/module/functions/local_function/mod.rs @@ -9,9 +9,7 @@ use crate::encode::Encoder; use crate::ir::*; use crate::map::{IdHashMap, IdHashSet}; use crate::parse::IndicesToIds; -use crate::{ - Data, DataId, FunctionBuilder, FunctionId, Module, Result, TableKind, TypeId, ValType, -}; +use crate::{Data, DataId, FunctionBuilder, FunctionId, Module, Result, TypeId, ValType}; use anyhow::{bail, Context}; use std::collections::BTreeMap; use wasmparser::Operator; @@ -607,17 +605,23 @@ fn validate_instruction<'context>( Operator::Select => { ctx.pop_operand_expected(Some(I32))?; let t1 = ctx.pop_operand()?; + match t1 { + Some(ValType::I32) | Some(ValType::I64) | Some(ValType::F32) + | Some(ValType::F64) => {} + Some(_) => bail!("select without types must use a number-type"), + None => {} + } let t2 = ctx.pop_operand_expected(t1)?; ctx.alloc_instr(Select { ty: None }, loc); ctx.push_operand(t2); } Operator::TypedSelect { ty } => { let ty = ValType::parse(&ty)?; + ctx.pop_operand_expected(Some(I32))?; + ctx.pop_operand_expected(Some(ty))?; ctx.pop_operand_expected(Some(ty))?; - let t1 = ctx.pop_operand()?; - let t2 = ctx.pop_operand_expected(t1)?; ctx.alloc_instr(Select { ty: Some(ty) }, loc); - ctx.push_operand(t2); + ctx.push_operand(Some(ty)); } Operator::Return => { let fn_ty = ctx.module.funcs.get(ctx.func_id).ty(); @@ -740,43 +744,55 @@ fn validate_instruction<'context>( ctx.alloc_instr(BrIf { block }, loc); ctx.push_operands(&expected); } + Operator::BrTable { table } => { - let len = table.len(); - let mut blocks = Vec::with_capacity(len); - let mut label_types = None; - let label_types_ref = &mut label_types; - let mut iter = table.into_iter(); - let mut next = || { - let n = match iter.next() { - Some(n) => n, - None => bail!("malformed `br_table"), - }; - let control = ctx.control(n as usize)?; - match label_types_ref { - None => *label_types_ref = Some(control.label_types().to_vec()), - Some(n) => { - if &n[..] != control.label_types() { - bail!("br_table jump with non-uniform label types") - } + let mut blocks = Vec::with_capacity(table.len()); + let (labels, default) = table.read_table()?; + let default = ctx.control(default as usize)?; + + // Note that with the reference types proposal we, as a type + // checker, need to figure out the type of the `br_table` + // instruction. To do that we start out with the default label's set + // of types, but these types may change as we inspect the other + // labels. All label arities must match, but we need to find a + // "least upper bound" of a type in a sort of unification process to + // figure out the types that we're popping. + let mut types = default.label_types().to_vec(); + let default = default.block; + for label in labels.iter() { + let control = ctx.control(*label as usize)?; + blocks.push(control.block); + if control.label_types().len() != types.len() { + bail!("br_table jump with non-uniform label types") + } + + // As part of the reference types proposal as well, we don't + // typecheck unreachable control blocks. (I think this is right? + // A little uncertain) + if control.unreachable { + continue; + } + + for (actual, expected) in control.label_types().iter().zip(&mut types) { + // If we're already a subtype, nothing to do... + if actual.is_subtype_of(*expected) { + continue; + } + // ... otherwise attempt to "unify" ourselves with the + // actual type, in which case change future expected + // types... + if expected.is_subtype_of(*actual) { + *expected = *actual; + continue; } + // ... and otherwise we found an error! + bail!("br_table jump with non-uniform label types") } - Ok(control.block) - }; - for _ in 0..len { - blocks.push(next()?); - } - let default = next()?; - if iter.next().is_some() { - bail!("malformed `br_table`"); } - - let blocks = blocks.into_boxed_slice(); - let expected = label_types.unwrap().clone(); ctx.pop_operand_expected(Some(I32))?; - - ctx.pop_operands(&expected)?; + ctx.pop_operands(&types)?; + let blocks = blocks.into_boxed_slice(); ctx.alloc_instr(BrTable { blocks, default }, loc); - ctx.unreachable(); } @@ -1155,24 +1171,19 @@ fn validate_instruction<'context>( let table = ctx.indices.get_table(table)?; ctx.pop_operand_expected(Some(I32))?; ctx.alloc_instr(TableGet { table }, loc); - ctx.push_operand(Some(Anyref)); + let result = ctx.module.tables.get(table).element_ty; + ctx.push_operand(Some(result)); } Operator::TableSet { table } => { let table = ctx.indices.get_table(table)?; - let expected_ty = match ctx.module.tables.get(table).kind { - TableKind::Anyref(_) => Anyref, - TableKind::Function(_) => bail!("cannot set function table yet"), - }; + let expected_ty = ctx.module.tables.get(table).element_ty; ctx.pop_operand_expected(Some(expected_ty))?; ctx.pop_operand_expected(Some(I32))?; ctx.alloc_instr(TableSet { table }, loc); } Operator::TableGrow { table } => { let table = ctx.indices.get_table(table)?; - let expected_ty = match ctx.module.tables.get(table).kind { - TableKind::Anyref(_) => Anyref, - TableKind::Function(_) => bail!("cannot grow function table yet"), - }; + let expected_ty = ctx.module.tables.get(table).element_ty; ctx.pop_operand_expected(Some(I32))?; ctx.pop_operand_expected(Some(expected_ty))?; ctx.alloc_instr(TableGrow { table }, loc); @@ -1185,10 +1196,7 @@ fn validate_instruction<'context>( } Operator::TableFill { table } => { let table = ctx.indices.get_table(table)?; - let expected_ty = match ctx.module.tables.get(table).kind { - TableKind::Anyref(_) => Anyref, - TableKind::Function(_) => bail!("cannot set function table yet"), - }; + let expected_ty = ctx.module.tables.get(table).element_ty; ctx.pop_operand_expected(Some(I32))?; ctx.pop_operand_expected(Some(expected_ty))?; ctx.pop_operand_expected(Some(I32))?; @@ -1196,7 +1204,7 @@ fn validate_instruction<'context>( } Operator::RefNull => { ctx.alloc_instr(RefNull {}, loc); - ctx.push_operand(Some(Anyref)); + ctx.push_operand(Some(Nullref)); } Operator::RefIsNull => { ctx.pop_operand_expected(Some(Anyref))?; @@ -1209,7 +1217,7 @@ fn validate_instruction<'context>( .get_func(function_index) .context("invalid call")?; ctx.alloc_instr(RefFunc { func }, loc); - ctx.push_operand(Some(Anyref)); + ctx.push_operand(Some(Funcref)); } Operator::V8x16Swizzle => { @@ -1452,10 +1460,30 @@ fn validate_instruction<'context>( Operator::I8x16RoundingAverageU => binop(ctx, V128, BinaryOp::I8x16RoundingAverageU)?, Operator::I16x8RoundingAverageU => binop(ctx, V128, BinaryOp::I16x8RoundingAverageU)?, - op @ Operator::TableInit { .. } - | op @ Operator::ElemDrop { .. } - | op @ Operator::TableCopy { .. } => { - bail!("Have not implemented support for opcode yet: {:?}", op) + Operator::TableCopy { + src_table, + dst_table, + } => { + let src = ctx.indices.get_table(src_table)?; + let dst = ctx.indices.get_table(dst_table)?; + ctx.pop_operand_expected(Some(I32))?; + ctx.pop_operand_expected(Some(I32))?; + ctx.pop_operand_expected(Some(I32))?; + ctx.alloc_instr(TableCopy { src, dst }, loc); + } + + Operator::TableInit { segment, table } => { + let elem = ctx.indices.get_element(segment)?; + let table = ctx.indices.get_table(table)?; + ctx.pop_operand_expected(Some(I32))?; + ctx.pop_operand_expected(Some(I32))?; + ctx.pop_operand_expected(Some(I32))?; + ctx.alloc_instr(TableInit { elem, table }, loc); + } + + Operator::ElemDrop { segment } => { + let elem = ctx.indices.get_element(segment)?; + ctx.alloc_instr(ElemDrop { elem }, loc); } } Ok(()) diff --git a/src/module/imports.rs b/src/module/imports.rs index 519fa4e1..ccc2be3c 100644 --- a/src/module/imports.rs +++ b/src/module/imports.rs @@ -3,9 +3,8 @@ use crate::emit::{Emit, EmitContext, Section}; use crate::parse::IndicesToIds; use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; -use crate::{FunctionId, FunctionTable, GlobalId, MemoryId, Result, TableId}; -use crate::{Module, TableKind, TypeId, ValType}; -use anyhow::bail; +use crate::{FunctionId, GlobalId, MemoryId, Result, TableId}; +use crate::{Module, TypeId, ValType}; /// The id of an import. pub type ImportId = Id; @@ -122,16 +121,13 @@ impl Module { ids.push_func(id.0); } wasmparser::ImportSectionEntryType::Table(t) => { - let kind = match t.element_type { - wasmparser::Type::AnyFunc => TableKind::Function(FunctionTable::default()), - _ => bail!("invalid table type"), - }; + let ty = ValType::parse(&t.element_type)?; let id = self.add_import_table( entry.module, entry.field, t.limits.initial, t.limits.maximum, - kind, + ty, ); ids.push_table(id.0); } @@ -195,10 +191,10 @@ impl Module { name: &str, initial: u32, max: Option, - kind: TableKind, + ty: ValType, ) -> (TableId, ImportId) { let import = self.imports.arena.next_id(); - let table = self.tables.add_import(initial, max, kind, import); + let table = self.tables.add_import(initial, max, ty, import); self.imports.add(module, name, table); (table, import) } diff --git a/src/module/mod.rs b/src/module/mod.rs index 2be9265b..7cda0517 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -24,6 +24,7 @@ pub use crate::module::custom::{ }; pub use crate::module::data::{ActiveData, ActiveDataLocation, Data, DataId, DataKind, ModuleData}; pub use crate::module::elements::{Element, ElementId, ModuleElements}; +pub use crate::module::elements::ElementKind; pub use crate::module::exports::{Export, ExportId, ExportItem, ModuleExports}; pub use crate::module::functions::{Function, FunctionId, ModuleFunctions}; pub use crate::module::functions::{FunctionKind, ImportedFunction, LocalFunction}; @@ -32,8 +33,7 @@ pub use crate::module::imports::{Import, ImportId, ImportKind, ModuleImports}; pub use crate::module::locals::ModuleLocals; pub use crate::module::memories::{Memory, MemoryId, ModuleMemories}; pub use crate::module::producers::ModuleProducers; -pub use crate::module::tables::FunctionTable; -pub use crate::module::tables::{ModuleTables, Table, TableId, TableKind}; +pub use crate::module::tables::{ModuleTables, Table, TableId}; pub use crate::module::types::ModuleTypes; use crate::parse::IndicesToIds; use anyhow::{bail, Context}; diff --git a/src/module/tables.rs b/src/module/tables.rs index ac78b445..b0b36baa 100644 --- a/src/module/tables.rs +++ b/src/module/tables.rs @@ -2,8 +2,9 @@ use crate::emit::{Emit, EmitContext, Section}; use crate::parse::IndicesToIds; +use crate::map::IdHashSet; use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; -use crate::{FunctionId, GlobalId, ImportId, Module, Result, ValType}; +use crate::{ImportId, Module, Result, ValType, Element}; use anyhow::bail; /// The id of a table. @@ -17,62 +18,16 @@ pub struct Table { pub initial: u32, /// The maximum size of this table pub maximum: Option, - /// Which kind of table this is - pub kind: TableKind, + /// The type of the elements in this table + pub element_ty: ValType, /// Whether or not this table is imported, and if so what imports it. pub import: Option, + /// Active data segments that will be used to initialize this memory. + pub elem_segments: IdHashSet, } impl Tombstone for Table {} -/// The kinds of tables that can exist -#[derive(Debug)] -pub enum TableKind { - /// A table of `anyfunc` functions. - /// - /// Contains the initialization list for this table, if any. - Function(FunctionTable), - - /// A table of type `anyref` values - Anyref(AnyrefTable), -} - -impl TableKind { - /// Unwrap `TableKind` to get inner `FunctionTable`. Panics if `TableKind` is anything other than `Function` - pub fn unwrap_function(&self) -> &FunctionTable { - match *self { - TableKind::Function(ref table) => table, - _ => panic!("not a Function"), - } - } - - /// Unwrap `TableKind` to get inner `Anyref`. Panics if `TableKind` is anything other than `Anyref` - pub fn unwrap_anyref(&self) -> &AnyrefTable { - match *self { - TableKind::Anyref(ref anyref) => anyref, - _ => panic!("not an Anyref"), - } - } -} - -/// Components of a table of functions (`anyfunc` table) -#[derive(Debug, Default)] -pub struct FunctionTable { - /// Layout of this function table that we know of, or those elements which - /// have constant initializers. - pub elements: Vec>, - - /// Elements of this table which are relative to a global, typically - /// imported. - pub relative_elements: Vec<(GlobalId, Vec>)>, -} - -/// Components of a table of `anyref` -#[derive(Debug, Default)] -pub struct AnyrefTable { - // currently intentionally empty -} - impl Table { /// Get this table's id. pub fn id(&self) -> TableId { @@ -82,12 +37,7 @@ impl Table { impl Emit for Table { fn emit(&self, cx: &mut EmitContext) { - match self.kind { - TableKind::Function(_) => { - cx.encoder.byte(0x70); // the `anyfunc` type - } - TableKind::Anyref(_) => ValType::Anyref.emit(&mut cx.encoder), - } + self.element_ty.emit(&mut cx.encoder); cx.encoder.byte(self.maximum.is_some() as u8); cx.encoder.u32(self.initial); if let Some(m) = self.maximum { @@ -109,7 +59,7 @@ impl ModuleTables { &mut self, initial: u32, max: Option, - kind: TableKind, + element_ty: ValType, import: ImportId, ) -> TableId { let id = self.arena.next_id(); @@ -117,21 +67,23 @@ impl ModuleTables { id, initial, maximum: max, - kind, + element_ty, import: Some(import), + elem_segments: Default::default(), }) } /// Construct a new table, that does not originate from any of the input /// wasm tables. - pub fn add_local(&mut self, initial: u32, max: Option, kind: TableKind) -> TableId { + pub fn add_local(&mut self, initial: u32, max: Option, element_ty: ValType) -> TableId { let id = self.arena.next_id(); let id2 = self.arena.alloc(Table { id, initial, maximum: max, - kind, + element_ty, import: None, + elem_segments: Default::default(), }); debug_assert_eq!(id, id2); id @@ -171,12 +123,9 @@ impl ModuleTables { /// /// Returns an error if there are two function tables in this module pub fn main_function_table(&self) -> Result> { - let mut tables = self.iter().filter_map(|t| match t.kind { - TableKind::Function(_) => Some(t.id()), - _ => None, - }); + let mut tables = self.iter().filter(|t| t.element_ty == ValType::Funcref); let id = match tables.next() { - Some(id) => id, + Some(t) => t.id(), None => return Ok(None), }; if tables.next().is_some() { @@ -204,11 +153,7 @@ impl Module { let id = self.tables.add_local( t.limits.initial, t.limits.maximum, - match t.element_type { - wasmparser::Type::AnyFunc => TableKind::Function(FunctionTable::default()), - wasmparser::Type::AnyRef => TableKind::Anyref(AnyrefTable::default()), - _ => bail!("invalid table type"), - }, + ValType::parse(&t.element_type)?, ); ids.push_table(id); } diff --git a/src/parse.rs b/src/parse.rs index 3f46be72..2ada37eb 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -60,7 +60,7 @@ define_push_get!(push_type, get_type, TypeId, types); define_push_get!(push_func, get_func, FunctionId, funcs); define_push_get!(push_global, get_global, GlobalId, globals); define_push_get!(push_memory, get_memory, MemoryId, memories); -//define_push_get!(push_element, get_element, ElementId, elements); +define_push_get!(push_element, get_element, ElementId, elements); define_push_get!(push_data, get_data, DataId, data); impl IndicesToIds { diff --git a/src/passes/used.rs b/src/passes/used.rs index 29e88d04..d78b60f0 100644 --- a/src/passes/used.rs +++ b/src/passes/used.rs @@ -1,9 +1,9 @@ use crate::ir::*; use crate::map::IdHashSet; use crate::{ActiveDataLocation, Data, DataId, DataKind, Element, ExportItem, Function, InitExpr}; +use crate::{ElementId, ElementKind, Module, Type, TypeId}; use crate::{FunctionId, FunctionKind, Global, GlobalId}; -use crate::{GlobalKind, ImportKind, Memory, MemoryId, Table, TableId}; -use crate::{Module, TableKind, Type, TypeId}; +use crate::{GlobalKind, Memory, MemoryId, Table, TableId}; /// Set of all root used items in a wasm module. #[derive(Debug, Default)] @@ -13,6 +13,7 @@ pub struct Roots { globals: Vec, memories: Vec, datas: Vec, + elements: Vec, used: Used, } @@ -65,6 +66,14 @@ impl Roots { } self } + + fn push_element(&mut self, element: ElementId) -> &mut Roots { + if self.used.elements.insert(element) { + log::trace!("element is used: {:?}", element); + self.elements.push(element); + } + self + } } /// Finds the things within a module that are used. @@ -111,29 +120,16 @@ impl Used { stack.push_func(f); } - // Initialization of imported memories or imported tables is a - // side-effectful operation, so be sure to retain any tables/memories - // that are imported and initialized, even if they aren't used. - for import in module.imports.iter() { - match import.kind { - ImportKind::Memory(m) => { - let mem = module.memories.get(m); - if !mem.data_segments.is_empty() { - stack.push_memory(m); - } - } - ImportKind::Table(t) => { - let table = module.tables.get(t); - match &table.kind { - TableKind::Function(init) => { - if !init.elements.is_empty() || !init.relative_elements.is_empty() { - stack.push_table(t); - } - } - TableKind::Anyref(_) => {} - } - } - _ => {} + // Initialization of memories or tables is a side-effectful operation + // because they can be out-of-bounds, so keep all active segments. + for data in module.data.iter() { + if let DataKind::Active { .. } = &data.kind { + stack.push_data(data.id()); + } + } + for elem in module.elements.iter() { + if let ElementKind::Active { .. } = &elem.kind { + stack.push_element(elem.id()); } } @@ -148,6 +144,7 @@ impl Used { || stack.memories.len() > 0 || stack.globals.len() > 0 || stack.datas.len() > 0 + || stack.elements.len() > 0 { while let Some(f) = stack.funcs.pop() { let func = module.funcs.get(f); @@ -164,23 +161,8 @@ impl Used { } while let Some(t) = stack.tables.pop() { - match &module.tables.get(t).kind { - TableKind::Function(list) => { - for id in list.elements.iter() { - if let Some(id) = id { - stack.push_func(*id); - } - } - for (global, list) in list.relative_elements.iter() { - stack.push_global(*global); - for id in list { - if let Some(id) = *id { - stack.push_func(id); - } - } - } - } - TableKind::Anyref(_) => {} + for elem in module.tables.get(t).elem_segments.iter() { + stack.push_element(*elem); } } @@ -190,7 +172,11 @@ impl Used { GlobalKind::Local(InitExpr::Global(global)) => { stack.push_global(*global); } - GlobalKind::Local(InitExpr::Value(_)) => {} + GlobalKind::Local(InitExpr::RefFunc(func)) => { + stack.push_func(*func); + } + GlobalKind::Local(InitExpr::Value(_)) + | GlobalKind::Local(InitExpr::RefNull) => {} } } @@ -202,13 +188,41 @@ impl Used { while let Some(d) = stack.datas.pop() { let d = module.data.get(d); - if let DataKind::Active(ref a) = d.kind { + if let DataKind::Active(a) = &d.kind { stack.push_memory(a.memory); if let ActiveDataLocation::Relative(g) = a.location { stack.push_global(g); } } } + + while let Some(e) = stack.elements.pop() { + let e = module.elements.get(e); + for func in e.members.iter() { + if let Some(func) = func { + stack.push_func(*func); + } + } + if let ElementKind::Active { offset, table } = &e.kind { + if let InitExpr::Global(g) = offset { + stack.push_global(*g); + } + stack.push_table(*table); + } + } + } + + // Wabt seems to have weird behavior where a `data` segment, if present + // even if passive, requires a `memory` declaration. Our GC pass is + // pretty aggressive and if you have a passive data segment and only + // `data.drop` instructions you technically don't need the `memory`. + // Let's keep `wabt` passing though and just say that if there are data + // segments kept, but no memories, then we try to add the first memory, + // if any, to the used set. + if stack.used.data.len() > 0 && stack.used.memories.len() == 0 { + if let Some(mem) = module.memories.iter().next() { + stack.used.memories.insert(mem.id()); + } } stack.used @@ -243,4 +257,8 @@ impl<'expr> Visitor<'expr> for UsedVisitor<'_> { fn visit_data_id(&mut self, &d: &DataId) { self.stack.push_data(d); } + + fn visit_element_id(&mut self, &e: &ElementId) { + self.stack.push_element(e); + } } diff --git a/src/passes/validate.rs b/src/passes/validate.rs index 879dd33b..fb295650 100644 --- a/src/passes/validate.rs +++ b/src/passes/validate.rs @@ -6,7 +6,7 @@ use crate::ir::*; use crate::ValType; use crate::{Function, FunctionKind, InitExpr, Result}; -use crate::{Global, GlobalKind, Memory, MemoryId, Module, Table, TableKind}; +use crate::{Global, GlobalKind, Memory, MemoryId, Module, Table}; use anyhow::{anyhow, bail, Context}; use std::collections::HashSet; @@ -90,15 +90,6 @@ fn validate_memory(m: &Memory) -> Result<()> { fn validate_table(t: &Table) -> Result<()> { validate_limits(t.initial, t.maximum, u32::max_value()).context("when validating a table")?; - - // Ensure that the table element type is `anyfunc`. This does - // nothing, but if new wasm versions and future parity-wasm releases - // get support for new table types, this may need to actually do - // something. - match t.kind { - TableKind::Function(_) => {} - TableKind::Anyref(_) => {} - } Ok(()) } @@ -147,6 +138,16 @@ fn validate_global(module: &Module, global: &Global) -> Result<()> { bail!("locally defined global does not match type of import"); } } + GlobalKind::Local(InitExpr::RefNull) => { + if !ValType::Nullref.is_subtype_of(global.ty) { + bail!("invalid type on global"); + } + } + GlobalKind::Local(InitExpr::RefFunc(_)) => { + if !ValType::Funcref.is_subtype_of(global.ty) { + bail!("invalid type on global"); + } + } } Ok(()) } diff --git a/src/ty.rs b/src/ty.rs index 1ee15eee..1e736cf4 100644 --- a/src/ty.rs +++ b/src/ty.rs @@ -148,6 +148,10 @@ pub enum ValType { V128, /// The `anyref` opaque value type Anyref, + /// The `funcref` value type, representing a callable function + Funcref, + /// The `nullref` value type, representing a value of type `null` + Nullref, } impl ValType { @@ -167,6 +171,8 @@ impl ValType { wasmparser::Type::F64 => Ok(ValType::F64), wasmparser::Type::V128 => Ok(ValType::V128), wasmparser::Type::AnyRef => Ok(ValType::Anyref), + wasmparser::Type::AnyFunc => Ok(ValType::Funcref), + wasmparser::Type::NullRef => Ok(ValType::Nullref), _ => bail!("not a value type"), } } @@ -178,7 +184,18 @@ impl ValType { ValType::F32 => encoder.byte(0x7d), ValType::F64 => encoder.byte(0x7c), ValType::V128 => encoder.byte(0x7b), + ValType::Funcref => encoder.byte(0x70), ValType::Anyref => encoder.byte(0x6f), + ValType::Nullref => encoder.byte(0x6e), + } + } + + pub(crate) fn is_subtype_of(&self, other: ValType) -> bool { + match (self, other) { + (ValType::Nullref, ValType::Funcref) + | (ValType::Nullref, ValType::Anyref) + | (ValType::Funcref, ValType::Anyref) => true, + (a, b) => *a == b, } } } @@ -195,6 +212,8 @@ impl fmt::Display for ValType { ValType::F64 => "f64", ValType::V128 => "v128", ValType::Anyref => "anyref", + ValType::Funcref => "funcref", + ValType::Nullref => "nullref", } ) } From 324bac07f9ff6e0beabef327c5ecce88dd296b60 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Feb 2020 13:08:18 -0800 Subject: [PATCH 2/4] Run rustfmt --- src/init_expr.rs | 2 +- src/ir/mod.rs | 4 ++-- src/module/elements.rs | 2 +- src/module/mod.rs | 2 +- src/module/tables.rs | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/init_expr.rs b/src/init_expr.rs index 4a4cef91..883362ca 100644 --- a/src/init_expr.rs +++ b/src/init_expr.rs @@ -3,7 +3,7 @@ use crate::emit::{Emit, EmitContext}; use crate::ir::Value; use crate::parse::IndicesToIds; -use crate::{GlobalId, Result, FunctionId}; +use crate::{FunctionId, GlobalId, Result}; use anyhow::bail; /// A constant which is produced in WebAssembly, typically used in global diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 0bb58595..485a09d8 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -9,8 +9,8 @@ pub use self::traversals::*; use crate::encode::Encoder; use crate::{ - DataId, FunctionId, GlobalId, LocalFunction, MemoryId, ModuleTypes, TableId, TypeId, ValType, - ElementId, + DataId, ElementId, FunctionId, GlobalId, LocalFunction, MemoryId, ModuleTypes, TableId, TypeId, + ValType, }; use id_arena::Id; use std::fmt; diff --git a/src/module/elements.rs b/src/module/elements.rs index fb10c9a6..dbf233c2 100644 --- a/src/module/elements.rs +++ b/src/module/elements.rs @@ -3,7 +3,7 @@ use crate::emit::{Emit, EmitContext, Section}; use crate::parse::IndicesToIds; use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; -use crate::{FunctionId, InitExpr, Module, Result, TableId, ValType, ir::Value}; +use crate::{ir::Value, FunctionId, InitExpr, Module, Result, TableId, ValType}; use anyhow::{bail, Context}; /// A passive element segment identifier diff --git a/src/module/mod.rs b/src/module/mod.rs index 7cda0517..78501442 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -23,8 +23,8 @@ pub use crate::module::custom::{ UntypedCustomSectionId, }; pub use crate::module::data::{ActiveData, ActiveDataLocation, Data, DataId, DataKind, ModuleData}; -pub use crate::module::elements::{Element, ElementId, ModuleElements}; pub use crate::module::elements::ElementKind; +pub use crate::module::elements::{Element, ElementId, ModuleElements}; pub use crate::module::exports::{Export, ExportId, ExportItem, ModuleExports}; pub use crate::module::functions::{Function, FunctionId, ModuleFunctions}; pub use crate::module::functions::{FunctionKind, ImportedFunction, LocalFunction}; diff --git a/src/module/tables.rs b/src/module/tables.rs index b0b36baa..9195f466 100644 --- a/src/module/tables.rs +++ b/src/module/tables.rs @@ -1,10 +1,10 @@ //! Tables within a wasm module. use crate::emit::{Emit, EmitContext, Section}; -use crate::parse::IndicesToIds; use crate::map::IdHashSet; +use crate::parse::IndicesToIds; use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; -use crate::{ImportId, Module, Result, ValType, Element}; +use crate::{Element, ImportId, Module, Result, ValType}; use anyhow::bail; /// The id of a table. From 3b39ff3fc550b9acbd9a3b9cca84df6bdac0052c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Feb 2020 13:16:05 -0800 Subject: [PATCH 3/4] Checkout submodules on testing --- .github/workflows/main.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 145d6b7c..7bc79de5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,9 @@ jobs: matrix: rust: [stable, beta, nightly] steps: - - uses: actions/checkout@master + - uses: actions/checkout@v1 + with: + submodules: true - name: Install Rust run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - name: Install wabt From 2919da6327160e9373188cca07a0fd0c5b0d0f0c Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Feb 2020 13:49:51 -0800 Subject: [PATCH 4/4] Update test assertions --- .../tests/round_trip/elem-segments-1.wat | 9 +++++- .../tests/round_trip/elem-segments-2.wat | 4 +-- .../tests/round_trip/elem-segments-3.wat | 11 +++++-- .../tests/round_trip/keep-elem-segments.wat | 21 ++++++------- crates/tests/tests/round_trip/simd.wat | 30 +------------------ crates/tests/tests/round_trip/table-init.wast | 16 +++++----- 6 files changed, 40 insertions(+), 51 deletions(-) diff --git a/crates/tests/tests/round_trip/elem-segments-1.wat b/crates/tests/tests/round_trip/elem-segments-1.wat index 8b348403..b63c226a 100644 --- a/crates/tests/tests/round_trip/elem-segments-1.wat +++ b/crates/tests/tests/round_trip/elem-segments-1.wat @@ -5,4 +5,11 @@ (export "foo" (table 0)) ) -;; CHECK: (elem (;0;) (i32.const 1) 0) +(; CHECK-ALL: + (module + (type (;0;) (func)) + (func (;0;) (type 0)) + (table (;0;) 1 funcref) + (export "foo" (table 0)) + (elem (;0;) (i32.const 1) func 0)) +;) diff --git a/crates/tests/tests/round_trip/elem-segments-2.wat b/crates/tests/tests/round_trip/elem-segments-2.wat index 53b5f0ae..58ccbd68 100644 --- a/crates/tests/tests/round_trip/elem-segments-2.wat +++ b/crates/tests/tests/round_trip/elem-segments-2.wat @@ -12,6 +12,6 @@ (func (;0;) (type 0)) (table (;0;) 1 funcref) (export "foo" (table 0)) - (elem (;0;) (i32.const 1) 0) - (elem (;1;) (i32.const 2) 0)) + (elem (;0;) (i32.const 1) func 0) + (elem (;1;) (i32.const 2) func 0)) ;) diff --git a/crates/tests/tests/round_trip/elem-segments-3.wat b/crates/tests/tests/round_trip/elem-segments-3.wat index 9a42a6e4..6870b07c 100644 --- a/crates/tests/tests/round_trip/elem-segments-3.wat +++ b/crates/tests/tests/round_trip/elem-segments-3.wat @@ -6,5 +6,12 @@ (export "foo" (table 0)) ) -;; CHECK: (elem (;0;) (i32.const 1) 0) -;; NEXT: (elem (;1;) (i32.const 3) 0) +(; CHECK-ALL: + (module + (type (;0;) (func)) + (func (;0;) (type 0)) + (table (;0;) 1 funcref) + (export "foo" (table 0)) + (elem (;0;) (i32.const 1) func 0) + (elem (;1;) (i32.const 3) func 0)) +;) diff --git a/crates/tests/tests/round_trip/keep-elem-segments.wat b/crates/tests/tests/round_trip/keep-elem-segments.wat index 9eb9375e..8ee8ed6b 100644 --- a/crates/tests/tests/round_trip/keep-elem-segments.wat +++ b/crates/tests/tests/round_trip/keep-elem-segments.wat @@ -13,13 +13,14 @@ (export "foo" (func 0)) ) -;; CHECK: (module -;; NEXT: (type (;0;) (func)) -;; NEXT: (func (;0;) (type 0) -;; NEXT: i32.const 0 -;; NEXT: call_indirect (type 0)) -;; NEXT: (func (;1;) (type 0)) -;; NEXT: (table (;0;) 1 1 funcref) -;; NEXT: (export "foo" (func 0)) -;; NEXT: (elem (;0;) (i32.const 0) 1)) - +(; CHECK-ALL: + (module + (type (;0;) (func)) + (func (;0;) (type 0) + i32.const 0 + call_indirect (type 0)) + (func (;1;) (type 0)) + (table (;0;) 1 1 funcref) + (export "foo" (func 0)) + (elem (;0;) (i32.const 0) func 1)) +;) diff --git a/crates/tests/tests/round_trip/simd.wat b/crates/tests/tests/round_trip/simd.wat index 78cec071..d081fffc 100644 --- a/crates/tests/tests/round_trip/simd.wat +++ b/crates/tests/tests/round_trip/simd.wat @@ -502,12 +502,6 @@ (func $i32x4_trunc_u_f32x4_sat (export "i32x4_trunc_u_f32x4_sat") (param v128) (result v128) local.get 0 i32x4.trunc_sat_f32x4_u) - (func $i64x2_trunc_s_f64x2_sat (export "i64x2_trunc_s_f64x2_sat") (param v128) (result v128) - local.get 0 - i64x2.trunc_sat_f64x2_s) - (func $i64x2_trunc_u_f64x2_sat (export "i64x2_trunc_u_f64x2_sat") (param v128) (result v128) - local.get 0 - i64x2.trunc_sat_f64x2_u) (func $f32x4.convert_i32x4_s (export "f32x4.convert_i32x4_s") (param v128) (result v128) local.get 0 @@ -515,12 +509,6 @@ (func $f32x4.convert_i32x4_u (export "f32x4.convert_i32x4_u") (param v128) (result v128) local.get 0 f32x4.convert_i32x4_u) - (func $f64x2.convert_i64x2_s (export "f64x2.convert_i64x2_s") (param v128) (result v128) - local.get 0 - f64x2.convert_i64x2_s) - (func $f64x2.convert_i64x2_u (export "f64x2.convert_i64x2_u") (param v128) (result v128) - local.get 0 - f64x2.convert_i64x2_u) ) (; CHECK-ALL: @@ -1015,24 +1003,12 @@ (func $i32x4_trunc_u_f32x4_sat (type 10) (param v128) (result v128) local.get 0 i32x4.trunc_sat_f32x4_u) - (func $i64x2_trunc_s_f64x2_sat (type 10) (param v128) (result v128) - local.get 0 - i64x2.trunc_sat_f64x2_s) - (func $i64x2_trunc_u_f64x2_sat (type 10) (param v128) (result v128) - local.get 0 - i64x2.trunc_sat_f64x2_u) (func $f32x4.convert_i32x4_s (type 10) (param v128) (result v128) local.get 0 f32x4.convert_i32x4_s) (func $f32x4.convert_i32x4_u (type 10) (param v128) (result v128) local.get 0 f32x4.convert_i32x4_u) - (func $f64x2.convert_i64x2_s (type 10) (param v128) (result v128) - local.get 0 - f64x2.convert_i64x2_s) - (func $f64x2.convert_i64x2_u (type 10) (param v128) (result v128) - local.get 0 - f64x2.convert_i64x2_u) (func $v128.const (type 0) (result v128) v128.const i32x4 0x00000001 0x00000002 0x00000003 0x00000004) (memory (;0;) 0) @@ -1164,10 +1140,6 @@ (export "f64x2.max" (func $f64x2.max)) (export "i32x4_trunc_s_f32x4_sat" (func $i32x4_trunc_s_f32x4_sat)) (export "i32x4_trunc_u_f32x4_sat" (func $i32x4_trunc_u_f32x4_sat)) - (export "i64x2_trunc_s_f64x2_sat" (func $i64x2_trunc_s_f64x2_sat)) - (export "i64x2_trunc_u_f64x2_sat" (func $i64x2_trunc_u_f64x2_sat)) (export "f32x4.convert_i32x4_s" (func $f32x4.convert_i32x4_s)) - (export "f32x4.convert_i32x4_u" (func $f32x4.convert_i32x4_u)) - (export "f64x2.convert_i64x2_s" (func $f64x2.convert_i64x2_s)) - (export "f64x2.convert_i64x2_u" (func $f64x2.convert_i64x2_u))) + (export "f32x4.convert_i32x4_u" (func $f32x4.convert_i32x4_u))) ;) diff --git a/crates/tests/tests/round_trip/table-init.wast b/crates/tests/tests/round_trip/table-init.wast index 3441660c..a5bc4771 100644 --- a/crates/tests/tests/round_trip/table-init.wast +++ b/crates/tests/tests/round_trip/table-init.wast @@ -5,10 +5,12 @@ (elem (global.get 0) 0) (export "x" (table 0))) -;; CHECK: (module -;; NEXT: (type -;; NEXT: (import "x" "y" (global (;0;) i32)) -;; NEXT: (func -;; NEXT: (table -;; NEXT: (export "x" (table 0)) -;; NEXT: (elem (;0;) (global.get 0) 0)) +(; CHECK-ALL: + (module + (type (;0;) (func)) + (import "x" "y" (global (;0;) i32)) + (func (;0;) (type 0)) + (table (;0;) 1 funcref) + (export "x" (table 0)) + (elem (;0;) (global.get 0) func 0)) +;)