Repository: bytecodealliance/wasm-tools
Pull request #2148 (2025-04-15 22:14:12)

Roundtrip non-malformed test cases through wasmparser / wasmprinter
This PR modifies wasmprinter and wasm-encoder to support roundtripping all non-malformed examples in the testsuite. The main changes were to
  - handle multi-value typed select (currently invalid, but not malformed), and
  - ~~ensure that component printer doesn't use forward-referring name references in the text format (these are rejected by the component parser)~~
  
It then modifies the `wast` subcommand to run each `assert_invalid` test roundtrip through the text format and back.

keithw: This is the last PR in my series. I think it is something of an extension of https://github.com/bytecodealliance/wasm-tools/pull/2146 so no objections to landing that first.

Patch:
diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs
index 7da4fa5d..843f5c83 100644
--- a/crates/wasm-encoder/src/core/code.rs
+++ b/crates/wasm-encoder/src/core/code.rs
@@ -612,6 +612,7 @@ pub enum Instruction<'a> {
 
     // Reference types instructions.
     TypedSelect(ValType),
+    TypedSelectMulti(Cow<'a, [ValType]>),
     RefNull(HeapType),
     RefIsNull,
     RefFunc(u32),
@@ -1284,6 +1285,7 @@ impl Encode for Instruction<'_> {
             Instruction::Drop => sink.drop(),
             Instruction::Select => sink.select(),
             Instruction::TypedSelect(ty) => sink.typed_select(ty),
+            Instruction::TypedSelectMulti(ref tys) => sink.typed_select_multi(tys.as_ref()),
 
             Instruction::TryTable(ty, ref catches) => sink.try_table(ty, catches.iter().cloned()),
 
diff --git a/crates/wasm-encoder/src/core/instructions.rs b/crates/wasm-encoder/src/core/instructions.rs
index d372aa32..c1825505 100644
--- a/crates/wasm-encoder/src/core/instructions.rs
+++ b/crates/wasm-encoder/src/core/instructions.rs
@@ -1353,6 +1353,13 @@ impl<'a> InstructionSink<'a> {
         self
     }
 
+    /// Encode [`Instruction::TypedSelect`] with multiple results (currently invalid).
+    pub fn typed_select_multi(&mut self, tys: &[ValType]) -> &mut Self {
+        self.sink.push(0x1c);
+        tys.encode(self.sink);
+        self
+    }
+
     /// Encode [`Instruction::RefNull`].
     pub fn ref_null(&mut self, ty: HeapType) -> &mut Self {
         self.sink.push(0xd0);
@@ -1846,7 +1853,6 @@ impl<'a> InstructionSink<'a> {
         self.sink.push(0xFD);
         0x54u32.encode(self.sink);
         memarg.encode(self.sink);
-        assert!(lane < 16);
         self.sink.push(lane);
         self
     }
@@ -1856,7 +1862,6 @@ impl<'a> InstructionSink<'a> {
         self.sink.push(0xFD);
         0x55u32.encode(self.sink);
         memarg.encode(self.sink);
-        assert!(lane < 8);
         self.sink.push(lane);
         self
     }
@@ -1866,7 +1871,6 @@ impl<'a> InstructionSink<'a> {
         self.sink.push(0xFD);
         0x56u32.encode(self.sink);
         memarg.encode(self.sink);
-        assert!(lane < 4);
         self.sink.push(lane);
         self
     }
@@ -1876,7 +1880,6 @@ impl<'a> InstructionSink<'a> {
         self.sink.push(0xFD);
         0x57u32.encode(self.sink);
         memarg.encode(self.sink);
-        assert!(lane < 2);
         self.sink.push(lane);
         self
     }
@@ -1886,7 +1889,6 @@ impl<'a> InstructionSink<'a> {
         self.sink.push(0xFD);
         0x58u32.encode(self.sink);
         memarg.encode(self.sink);
-        assert!(lane < 16);
         self.sink.push(lane);
         self
     }
@@ -1896,7 +1898,6 @@ impl<'a> InstructionSink<'a> {
         self.sink.push(0xFD);
         0x59u32.encode(self.sink);
         memarg.encode(self.sink);
-        assert!(lane < 8);
         self.sink.push(lane);
         self
     }
@@ -1906,7 +1907,6 @@ impl<'a> InstructionSink<'a> {
         self.sink.push(0xFD);
         0x5Au32.encode(self.sink);
         memarg.encode(self.sink);
-        assert!(lane < 4);
         self.sink.push(lane);
         self
     }
@@ -1916,7 +1916,6 @@ impl<'a> InstructionSink<'a> {
         self.sink.push(0xFD);
         0x5Bu32.encode(self.sink);
         memarg.encode(self.sink);
-        assert!(lane < 2);
         self.sink.push(lane);
         self
     }
@@ -1933,7 +1932,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i8x16_shuffle(&mut self, lanes: [Lane; 16]) -> &mut Self {
         self.sink.push(0xFD);
         0x0Du32.encode(self.sink);
-        assert!(lanes.iter().all(|l: &u8| *l < 32));
         self.sink.extend(lanes.iter().copied());
         self
     }
@@ -1942,7 +1940,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i8x16_extract_lane_s(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x15u32.encode(self.sink);
-        assert!(lane < 16);
         self.sink.push(lane);
         self
     }
@@ -1951,7 +1948,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i8x16_extract_lane_u(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x16u32.encode(self.sink);
-        assert!(lane < 16);
         self.sink.push(lane);
         self
     }
@@ -1960,7 +1956,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i8x16_replace_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x17u32.encode(self.sink);
-        assert!(lane < 16);
         self.sink.push(lane);
         self
     }
@@ -1969,7 +1964,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i16x8_extract_lane_s(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x18u32.encode(self.sink);
-        assert!(lane < 8);
         self.sink.push(lane);
         self
     }
@@ -1978,7 +1972,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i16x8_extract_lane_u(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x19u32.encode(self.sink);
-        assert!(lane < 8);
         self.sink.push(lane);
         self
     }
@@ -1987,7 +1980,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i16x8_replace_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x1Au32.encode(self.sink);
-        assert!(lane < 8);
         self.sink.push(lane);
         self
     }
@@ -1996,7 +1988,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i32x4_extract_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x1Bu32.encode(self.sink);
-        assert!(lane < 4);
         self.sink.push(lane);
         self
     }
@@ -2005,7 +1996,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i32x4_replace_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x1Cu32.encode(self.sink);
-        assert!(lane < 4);
         self.sink.push(lane);
         self
     }
@@ -2014,7 +2004,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i64x2_extract_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x1Du32.encode(self.sink);
-        assert!(lane < 2);
         self.sink.push(lane);
         self
     }
@@ -2023,7 +2012,6 @@ impl<'a> InstructionSink<'a> {
     pub fn i64x2_replace_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x1Eu32.encode(self.sink);
-        assert!(lane < 2);
         self.sink.push(lane);
         self
     }
@@ -2032,7 +2020,6 @@ impl<'a> InstructionSink<'a> {
     pub fn f32x4_extract_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x1Fu32.encode(self.sink);
-        assert!(lane < 4);
         self.sink.push(lane);
         self
     }
@@ -2041,7 +2028,6 @@ impl<'a> InstructionSink<'a> {
     pub fn f32x4_replace_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x20u32.encode(self.sink);
-        assert!(lane < 4);
         self.sink.push(lane);
         self
     }
@@ -2050,7 +2036,6 @@ impl<'a> InstructionSink<'a> {
     pub fn f64x2_extract_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x21u32.encode(self.sink);
-        assert!(lane < 2);
         self.sink.push(lane);
         self
     }
@@ -2059,7 +2044,6 @@ impl<'a> InstructionSink<'a> {
     pub fn f64x2_replace_lane(&mut self, lane: Lane) -> &mut Self {
         self.sink.push(0xFD);
         0x22u32.encode(self.sink);
-        assert!(lane < 2);
         self.sink.push(lane);
         self
     }
diff --git a/crates/wasm-encoder/src/reencode.rs b/crates/wasm-encoder/src/reencode.rs
index 1d64d8f7..0d80abbb 100644
--- a/crates/wasm-encoder/src/reencode.rs
+++ b/crates/wasm-encoder/src/reencode.rs
@@ -17,6 +17,9 @@ mod component;
 #[cfg(feature = "component-model")]
 pub use self::component::*;
 
+#[cfg(feature = "wasmparser")]
+use alloc::vec::Vec;
+
 #[allow(missing_docs)] // FIXME
 pub trait Reencode {
     type Error;
@@ -228,6 +231,16 @@ pub trait Reencode {
         utils::val_type(self, val_ty)
     }
 
+    fn val_types(
+        &mut self,
+        val_tys: Vec<wasmparser::ValType>,
+    ) -> Result<Vec<crate::ValType>, Error<Self::Error>> {
+        val_tys
+            .iter()
+            .map(|ty| utils::val_type(self, *ty))
+            .collect()
+    }
+
     /// Parses the input `section` given from the `wasmparser` crate and
     /// adds the custom section to the `module`.
     fn parse_custom_section(
@@ -1558,6 +1571,7 @@ pub mod utils {
         arg: wasmparser::Operator<'a>,
     ) -> Result<crate::Instruction<'a>, Error<T::Error>> {
         use crate::Instruction;
+        use alloc::borrow::Cow;
 
         macro_rules! translate {
             ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
@@ -1606,6 +1620,7 @@ pub mod utils {
                 $arg.default(),
             ));
             (map $arg:ident ty) => (reencoder.val_type($arg)?);
+            (map $arg:ident tys) => (reencoder.val_types($arg)?);
             (map $arg:ident hty) => (reencoder.heap_type($arg)?);
             (map $arg:ident from_ref_type) => (reencoder.ref_type($arg)?);
             (map $arg:ident to_ref_type) => (reencoder.ref_type($arg)?);
@@ -1631,6 +1646,7 @@ pub mod utils {
             // wasm-encoder.
             (build $op:ident) => (Instruction::$op);
             (build BrTable $arg:ident) => (Instruction::BrTable($arg.0, $arg.1));
+            (build TypedSelectMulti $arg:ident) => (Instruction::TypedSelectMulti(Cow::from($arg)));
             (build I32Const $arg:ident) => (Instruction::I32Const($arg));
             (build I64Const $arg:ident) => (Instruction::I64Const($arg));
             (build F32Const $arg:ident) => (Instruction::F32Const(f32::from_bits($arg.bits())));
diff --git a/crates/wasmparser/src/arity.rs b/crates/wasmparser/src/arity.rs
index 9deaf92b..bd81c930 100644
--- a/crates/wasmparser/src/arity.rs
+++ b/crates/wasmparser/src/arity.rs
@@ -200,6 +200,12 @@ macro_rules! operator_arity {
 	operator_arity!(count $self { relative_depth: u32 } br)
     }};
 
+    (count $self:ident { $select_tys:ident: $($_:tt)* } select) => {{
+        operator_arity!(select_tys $select_tys);
+	let result_count = $select_tys.len();
+	Some((1 + 2 * result_count, result_count))
+    }};
+
     (tag_index tag_index $($_:tt)*) => {};
     (func_index function_index $($_:tt)*) => {};
     (type_index type_index $($_:tt)*) => {};
@@ -212,6 +218,7 @@ macro_rules! operator_arity {
     (blockty blockty $($_:tt)*) => {};
     (try_table try_table $($_:tt)*) => {};
     (br_table targets $($_:tt)*) => {};
+    (select_tys tys $($_:tt)*) => {};
 
     (fixed load lane $($_:tt)*)        => {(2, 1)};
     (fixed load $($_:tt)*)             => {(1, 1)};
diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs
index 21c37753..4ee709f3 100644
--- a/crates/wasmparser/src/lib.rs
+++ b/crates/wasmparser/src/lib.rs
@@ -331,6 +331,8 @@ macro_rules! _for_each_operator_group {
             // https://github.com/WebAssembly/reference-types
             @reference_types {
                 TypedSelect { ty: $crate::ValType } => visit_typed_select (arity 3 -> 1)
+                // multi-result select is currently invalid, but can be parsed and printed
+                TypedSelectMulti { tys: Vec<$crate::ValType> } => visit_typed_select_multi (arity select -> select)
                 RefNull { hty: $crate::HeapType } => visit_ref_null (arity 0 -> 1)
                 RefIsNull => visit_ref_is_null (arity 1 -> 1)
                 RefFunc { function_index: u32 } => visit_ref_func (arity 0 -> 1)
diff --git a/crates/wasmparser/src/limits.rs b/crates/wasmparser/src/limits.rs
index 33d5578e..953981c0 100644
--- a/crates/wasmparser/src/limits.rs
+++ b/crates/wasmparser/src/limits.rs
@@ -41,6 +41,7 @@ pub const MAX_WASM_CATCHES: usize = 10_000;
 pub const MAX_WASM_SUBTYPING_DEPTH: usize = 63;
 pub const MAX_WASM_HANDLERS: usize = 10_000;
 pub const MAX_WASM_TYPE_SIZE: u32 = 1_000_000;
+pub const MAX_WASM_SELECT_RESULT_SIZE: usize = 10; // values other than 1 are currently invalid
 
 pub const DEFAULT_WASM_PAGE_SIZE: u64 = 1 << 16;
 
diff --git a/crates/wasmparser/src/readers/core/operators.rs b/crates/wasmparser/src/readers/core/operators.rs
index 399fa190..e11b98f4 100644
--- a/crates/wasmparser/src/readers/core/operators.rs
+++ b/crates/wasmparser/src/readers/core/operators.rs
@@ -13,7 +13,9 @@
  * limitations under the License.
  */
 
-use crate::limits::{MAX_WASM_BR_TABLE_SIZE, MAX_WASM_CATCHES, MAX_WASM_HANDLERS};
+use crate::limits::{
+    MAX_WASM_BR_TABLE_SIZE, MAX_WASM_CATCHES, MAX_WASM_HANDLERS, MAX_WASM_SELECT_RESULT_SIZE,
+};
 use crate::prelude::*;
 use crate::{BinaryReader, BinaryReaderError, FromReader, RefType, Result, ValType};
 use core::fmt;
@@ -573,11 +575,19 @@ impl<'a> OperatorsReader<'a> {
             0x1a => visitor.visit_drop(),
             0x1b => visitor.visit_select(),
             0x1c => {
-                let results = self.reader.read_var_u32()?;
-                if results != 1 {
-                    bail!(pos, "invalid result arity");
+                let result_count = self
+                    .reader
+                    .read_size(MAX_WASM_SELECT_RESULT_SIZE, "select types")?;
+                if result_count == 1 {
+                    visitor.visit_typed_select(self.reader.read()?)
+                } else {
+                    let mut results = Vec::new();
+                    results.reserve_exact(result_count);
+                    for _ in 0..result_count {
+                        results.push(self.reader.read()?);
+                    }
+                    visitor.visit_typed_select_multi(results)
                 }
-                visitor.visit_typed_select(self.reader.read()?)
             }
             0x1f => {
                 self.enter(FrameKind::TryTable);
diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs
index 4dbc1298..f79bb080 100644
--- a/crates/wasmparser/src/validator/operators.rs
+++ b/crates/wasmparser/src/validator/operators.rs
@@ -2048,6 +2048,10 @@ where
         self.push_operand(ty)?;
         Ok(())
     }
+    fn visit_typed_select_multi(&mut self, tys: Vec<ValType>) -> Self::Output {
+        debug_assert!(tys.len() != 1);
+        bail!(self.offset, "invalid result arity");
+    }
     fn visit_local_get(&mut self, local_index: u32) -> Self::Output {
         let ty = self.local(local_index)?;
         debug_assert_type_indices_are_ids(ty);
diff --git a/crates/wasmprinter/src/component.rs b/crates/wasmprinter/src/component.rs
index c54516bd..88c05215 100644
--- a/crates/wasmprinter/src/component.rs
+++ b/crates/wasmprinter/src/component.rs
@@ -399,12 +399,12 @@ impl Printer<'_, '_> {
         Ok(())
     }
 
-    pub(crate) fn outer_state(states: &[State], count: u32) -> Result<&State> {
-        let count = count as usize;
-        if count >= states.len() {
-            bail!("invalid outer alias count of {}", count);
+    pub(crate) fn outer_state<'a>(states: &[State], count: u32) -> Result<&State> {
+        if count as usize >= states.len() {
+            bail!("invalid outer alias count {}", count);
         }
 
+        let count: usize = std::cmp::min(count as usize, states.len() - 1);
         Ok(&states[states.len() - count - 1])
     }
 
@@ -416,7 +416,14 @@ impl Printer<'_, '_> {
         index: u32,
     ) -> Result<()> {
         let state = states.last().unwrap();
-        let outer = Self::outer_state(states, count)?;
+        let default_state = State::new(Encoding::Component);
+        let outer = match Self::outer_state(states, count) {
+            Ok(o) => o,
+            Err(e) => {
+                write!(self.result, "(; {e} ;) ")?;
+                &default_state
+            }
+        };
         self.start_group("alias outer ")?;
         if let Some(name) = outer.name.as_ref() {
             name.write(self)?;
@@ -1328,7 +1335,14 @@ impl Printer<'_, '_> {
 
             ComponentAlias::Outer { kind, count, index } => {
                 let state = states.last().unwrap();
-                let outer = Self::outer_state(states, count)?;
+                let default_state = State::new(Encoding::Component);
+                let outer = match Self::outer_state(states, count) {
+                    Ok(o) => o,
+                    Err(e) => {
+                        write!(self.result, "(; {e} ;) ")?;
+                        &default_state
+                    }
+                };
                 self.start_group("alias outer ")?;
                 if let Some(name) = outer.name.as_ref() {
                     name.write(self)?;
diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs
index 1259ba24..6346f212 100644
--- a/crates/wasmprinter/src/lib.rs
+++ b/crates/wasmprinter/src/lib.rs
@@ -156,7 +156,14 @@ struct State {
     core: CoreState,
     #[cfg(feature = "component-model")]
     component: ComponentState,
-    custom_section_place: Option<&'static str>,
+    custom_section_place: Option<(&'static str, usize)>,
+    // `custom_section_place` stores the text representation of the location where
+    // a custom section should be serialized in the binary format.
+    // The tuple elements are a str (e.g. "after elem") and the line number
+    // where the custom section place was set. `update_custom_section_place` won't
+    // update the custom section place unless the line number changes; this prevents
+    // printing a place "after xxx" where the xxx section doesn't appear in the text format
+    // (e.g. because it was present but empty in the binary format).
 }
 
 impl State {
@@ -430,7 +437,8 @@ impl Printer<'_, '_> {
                     match encoding {
                         Encoding::Module => {
                             states.push(State::new(Encoding::Module));
-                            states.last_mut().unwrap().custom_section_place = Some("before first");
+                            states.last_mut().unwrap().custom_section_place =
+                                Some(("before first", self.line));
                             if states.len() > 1 {
                                 self.start_group("core module")?;
                             } else {
@@ -490,48 +498,58 @@ impl Printer<'_, '_> {
                         self.result
                             .print_custom_section(c.name(), c.data_offset(), c.data())?;
                     if printed {
+                        self.update_custom_section_line(&mut states);
                         continue;
                     }
 
                     // If this wasn't handled specifically above then try to
                     // print the known custom builtin sections. If this fails
-                    // because the custom section is malformed then don't fail
-                    // the whole print.
+                    // because the custom section is malformed then print the
+                    // raw contents instead.
                     let state = states.last().unwrap();
                     let start = self.nesting;
-                    let err = match self.print_custom_section(state, c.clone()) {
-                        Ok(()) => continue,
-                        Err(e) if !e.is::<BinaryReaderError>() => return Err(e),
-                        Err(e) => e,
-                    };
-
-                    // Restore the nesting level to what it was prior to
-                    // starting to print the custom section. Then emit a comment
-                    // indicating why the custom section failed to parse.
-                    //
-                    // This may leave broken syntax in the output module but
-                    // it's probably better than swallowing the error.
-                    while self.nesting > start {
-                        self.end_group()?;
-                    }
-                    let msg = format!("failed to parse custom section `{}`: {err}", c.name());
-                    for line in msg.lines() {
-                        self.newline(c.data_offset())?;
-                        write!(self.result, ";; {line}")?;
+                    match c.as_known() {
+                        KnownCustom::Unknown => self.print_raw_custom_section(state, c.clone())?,
+                        _ => {
+                            match (Printer {
+                                config: self.config,
+                                result: &mut PrintFmtWrite(String::new()),
+                                nesting: 0,
+                                line: 0,
+                                group_lines: Vec::new(),
+                                code_section_hints: Vec::new(),
+                            })
+                            .print_known_custom_section(c.clone())
+                            {
+                                Ok(()) => self.print_known_custom_section(c.clone())?,
+                                Err(e) if !e.is::<BinaryReaderError>() => return Err(e),
+                                Err(e) => {
+                                    let msg = format!(
+                                        "failed to parse custom section `{}`: {e}",
+                                        c.name()
+                                    );
+                                    for line in msg.lines() {
+                                        self.newline(c.data_offset())?;
+                                        write!(self.result, ";; {line}")?;
+                                    }
+                                    self.print_raw_custom_section(state, c.clone())?
+                                }
+                            }
+                        }
                     }
-                    self.newline(c.range().end)?;
+                    assert!(self.nesting == start);
+                    self.update_custom_section_line(&mut states);
                 }
                 Payload::TypeSection(s) => {
-                    self.update_custom_section_place(&mut states, "after type");
                     self.print_types(states.last_mut().unwrap(), s)?;
+                    self.update_custom_section_place(&mut states, "after type");
                 }
                 Payload::ImportSection(s) => {
-                    self.update_custom_section_place(&mut states, "after import");
                     Self::ensure_module(&states)?;
-                    self.print_imports(states.last_mut().unwrap(), s)?
+                    self.print_imports(states.last_mut().unwrap(), s)?;
+                    self.update_custom_section_place(&mut states, "after import");
                 }
                 Payload::FunctionSection(reader) => {
-                    self.update_custom_section_place(&mut states, "after func");
                     Self::ensure_module(&states)?;
                     if reader.count() > MAX_WASM_FUNCTIONS {
                         bail!(
@@ -543,60 +561,61 @@ impl Printer<'_, '_> {
                     for ty in reader {
                         states.last_mut().unwrap().core.func_to_type.push(Some(ty?))
                     }
+                    self.update_custom_section_place(&mut states, "after func");
                 }
                 Payload::TableSection(s) => {
-                    self.update_custom_section_place(&mut states, "after table");
                     Self::ensure_module(&states)?;
-                    self.print_tables(states.last_mut().unwrap(), s)?
+                    self.print_tables(states.last_mut().unwrap(), s)?;
+                    self.update_custom_section_place(&mut states, "after table");
                 }
                 Payload::MemorySection(s) => {
-                    self.update_custom_section_place(&mut states, "after memory");
                     Self::ensure_module(&states)?;
-                    self.print_memories(states.last_mut().unwrap(), s)?
+                    self.print_memories(states.last_mut().unwrap(), s)?;
+                    self.update_custom_section_place(&mut states, "after memory");
                 }
                 Payload::TagSection(s) => {
-                    self.update_custom_section_place(&mut states, "after tag");
                     Self::ensure_module(&states)?;
-                    self.print_tags(states.last_mut().unwrap(), s)?
+                    self.print_tags(states.last_mut().unwrap(), s)?;
+                    self.update_custom_section_place(&mut states, "after tag");
                 }
                 Payload::GlobalSection(s) => {
-                    self.update_custom_section_place(&mut states, "after global");
                     Self::ensure_module(&states)?;
-                    self.print_globals(states.last_mut().unwrap(), s)?
+                    self.print_globals(states.last_mut().unwrap(), s)?;
+                    self.update_custom_section_place(&mut states, "after global");
                 }
                 Payload::ExportSection(s) => {
-                    self.update_custom_section_place(&mut states, "after export");
                     Self::ensure_module(&states)?;
-                    self.print_exports(states.last().unwrap(), s)?
+                    self.print_exports(states.last().unwrap(), s)?;
+                    self.update_custom_section_place(&mut states, "after export");
                 }
                 Payload::StartSection { func, range } => {
-                    self.update_custom_section_place(&mut states, "after start");
                     Self::ensure_module(&states)?;
                     self.newline(range.start)?;
                     self.start_group("start ")?;
                     self.print_idx(&states.last().unwrap().core.func_names, func)?;
                     self.end_group()?;
+                    self.update_custom_section_place(&mut states, "after start");
                 }
                 Payload::ElementSection(s) => {
-                    self.update_custom_section_place(&mut states, "after element");
                     Self::ensure_module(&states)?;
                     self.print_elems(states.last_mut().unwrap(), s)?;
+                    self.update_custom_section_place(&mut states, "after elem");
                 }
                 Payload::CodeSectionStart { .. } => {
-                    self.update_custom_section_place(&mut states, "after code");
                     Self::ensure_module(&states)?;
                 }
                 Payload::CodeSectionEntry(body) => {
-                    self.print_code_section_entry(states.last_mut().unwrap(), &body)?
+                    self.print_code_section_entry(states.last_mut().unwrap(), &body)?;
+                    self.update_custom_section_place(&mut states, "after code");
                 }
                 Payload::DataCountSection { .. } => {
                     Self::ensure_module(&states)?;
                     // not part of the text format
                 }
                 Payload::DataSection(s) => {
-                    self.update_custom_section_place(&mut states, "after data");
                     Self::ensure_module(&states)?;
                     self.print_data(states.last_mut().unwrap(), s)?;
+                    self.update_custom_section_place(&mut states, "after data");
                 }
 
                 #[cfg(feature = "component-model")]
@@ -702,8 +721,19 @@ impl Printer<'_, '_> {
 
     fn update_custom_section_place(&self, states: &mut Vec<State>, place: &'static str) {
         if let Some(last) = states.last_mut() {
-            if let Some(prev) = &mut last.custom_section_place {
-                *prev = place;
+            if let Some((prev, prev_line)) = &mut last.custom_section_place {
+                if *prev_line != self.line {
+                    *prev = place;
+                    *prev_line = self.line;
+                }
+            }
+        }
+    }
+
+    fn update_custom_section_line(&self, states: &mut Vec<State>) {
+        if let Some(last) = states.last_mut() {
+            if let Some((_, prev_line)) = &mut last.custom_section_place {
+                *prev_line = self.line;
             }
         }
     }
@@ -1014,6 +1044,14 @@ impl Printer<'_, '_> {
         Ok(())
     }
 
+    fn print_valtypes(&mut self, state: &State, tys: Vec<ValType>) -> Result<()> {
+        for ty in tys {
+            self.result.write_str(" ")?;
+            self.print_valtype(state, ty)?;
+        }
+        Ok(())
+    }
+
     fn print_reftype(&mut self, state: &State, ty: RefType) -> Result<()> {
         if ty.is_nullable() {
             match ty.as_non_null() {
@@ -1501,6 +1539,10 @@ impl Printer<'_, '_> {
         Ok(())
     }
 
+    // Note: in the text format, modules can use identifiers that are defined anywhere, but
+    // components can only use previously-defined identifiers. In the binary format,
+    // invalid components can make forward references to an index that appears in the name section;
+    // these can be printed but the output won't parse.
     fn print_idx<K>(&mut self, names: &NamingMap<u32, K>, idx: u32) -> Result<()>
     where
         K: NamingNamespace,
@@ -1688,6 +1730,7 @@ impl Printer<'_, '_> {
             folded_printer.finalize()?;
         } else {
             let mut op_printer = PrintOperator::new(self, state, &mut operator_state);
+            op_printer.suppress_label_comments();
             Self::print_operators(&mut reader, &[], 0, &mut op_printer)?;
         }
 
@@ -1735,11 +1778,7 @@ impl Printer<'_, '_> {
         Ok(())
     }
 
-    fn print_custom_section(
-        &mut self,
-        state: &State,
-        section: CustomSectionReader<'_>,
-    ) -> Result<()> {
+    fn print_known_custom_section(&mut self, section: CustomSectionReader<'_>) -> Result<()> {
         match section.as_known() {
             // For now `wasmprinter` has invented syntax for `producers` and
             // `dylink.0` below to use in tests. Note that this syntax is not
@@ -1759,10 +1798,9 @@ impl Printer<'_, '_> {
             #[cfg(feature = "component-model")]
             KnownCustom::ComponentName(_) => Ok(()),
 
-            // Custom sections without a text format at this time and unknown
-            // custom sections get a `@custom` annotation printed.
-            _ => self.print_raw_custom_section(state, section),
-        }
+            _ => bail!("unknown custom section"),
+        }?;
+        Ok(())
     }
 
     fn print_raw_custom_section(
@@ -1773,7 +1811,7 @@ impl Printer<'_, '_> {
         self.newline(section.range().start)?;
         self.start_group("@custom ")?;
         self.print_str(section.name())?;
-        if let Some(place) = state.custom_section_place {
+        if let Some((place, _)) = state.custom_section_place {
             write!(self.result, " ({place})")?;
         }
         self.result.write_str(" ")?;
diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs
index 2759c991..a20ac543 100644
--- a/crates/wasmprinter/src/operator.rs
+++ b/crates/wasmprinter/src/operator.rs
@@ -32,6 +32,7 @@ pub struct PrintOperator<'printer, 'state, 'a, 'b> {
     pub(super) printer: &'printer mut Printer<'a, 'b>,
     state: &'state mut State,
     operator_state: &'printer mut OperatorState,
+    suppress_label_comments: bool,
 }
 
 struct FoldedInstruction {
@@ -55,6 +56,7 @@ pub struct PrintOperatorFolded<'printer, 'state, 'a, 'b> {
     pub(super) printer: &'printer mut Printer<'a, 'b>,
     state: &'state mut State,
     operator_state: &'printer mut OperatorState,
+    suppress_label_comments: bool,
     control: Vec<Block>,
     branch_hint: Option<FoldedInstruction>,
     original_separator: OperatorSeparator,
@@ -78,6 +80,7 @@ impl<'printer, 'state, 'a, 'b> PrintOperator<'printer, 'state, 'a, 'b> {
             printer,
             state,
             operator_state,
+            suppress_label_comments: false,
         }
     }
 
@@ -171,7 +174,7 @@ impl<'printer, 'state, 'a, 'b> PrintOperator<'printer, 'state, 'a, 'b> {
     }
 
     fn maybe_blockty_label_comment(&mut self, has_name: bool) -> Result<()> {
-        if !has_name {
+        if !has_name && !self.suppress_label_comments {
             let depth = self.cur_depth();
             self.push_str(" ")?;
             self.result().start_comment()?;
@@ -406,10 +409,7 @@ impl<'printer, 'state, 'a, 'b> PrintOperator<'printer, 'state, 'a, 'b> {
             write!(self.result(), " offset={}", memarg.offset)?;
         }
         if memarg.align != memarg.max_align {
-            if memarg.align >= 32 {
-                bail!("alignment in memarg too large");
-            }
-            let align = 1 << memarg.align;
+            let align = 1_u64 << memarg.align;
             write!(self.result(), " align={}", align)?;
         }
         Ok(())
@@ -560,10 +560,16 @@ macro_rules! define_visit {
         $self.push_str(" ")?;
         $self.printer.print_idx(&$self.state.core.type_names, $ty)?;
     );
-    (payload $self:ident TypedSelect $ty:ident) => (
+    (payload $self:ident TypedSelect $select_ty:ident) => (
         $self.push_str(" ")?;
         $self.printer.start_group("result ")?;
-        $self.printer.print_valtype($self.state, $ty)?;
+        $self.printer.print_valtype($self.state, $select_ty)?;
+        $self.printer.end_group()?;
+    );
+    (payload $self:ident TypedSelectMulti $select_tys:ident) => (
+        $self.push_str(" ")?;
+        $self.printer.start_group("result")?;
+        $self.printer.print_valtypes($self.state, $select_tys)?;
         $self.printer.end_group()?;
     );
     (payload $self:ident RefNull $hty:ident) => (
@@ -789,6 +795,7 @@ macro_rules! define_visit {
     (name Drop) => ("drop");
     (name Select) => ("select");
     (name TypedSelect) => ("select");
+    (name TypedSelectMulti) => ("select");
     (name LocalGet) => ("local.get");
     (name LocalSet) => ("local.set");
     (name LocalTee) => ("local.tee");
@@ -1405,6 +1412,7 @@ pub trait OpPrinter {
     fn branch_hint(&mut self, offset: usize, taken: bool) -> Result<()>;
     fn set_offset(&mut self, offset: usize);
     fn visit_operator(&mut self, reader: &mut OperatorsReader<'_>) -> Result<()>;
+    fn suppress_label_comments(&mut self);
 }
 
 impl OpPrinter for PrintOperator<'_, '_, '_, '_> {
@@ -1424,6 +1432,10 @@ impl OpPrinter for PrintOperator<'_, '_, '_, '_> {
     fn visit_operator(&mut self, reader: &mut OperatorsReader<'_>) -> Result<()> {
         reader.visit_operator(self)?
     }
+
+    fn suppress_label_comments(&mut self) {
+        self.suppress_label_comments = true;
+    }
 }
 
 impl OpPrinter for PrintOperatorFolded<'_, '_, '_, '_> {
@@ -1444,11 +1456,13 @@ impl OpPrinter for PrintOperatorFolded<'_, '_, '_, '_> {
         self.operator_state.op_offset = offset;
     }
 
+    fn suppress_label_comments(&mut self) {
+        self.suppress_label_comments = true;
+    }
+
     fn visit_operator(&mut self, reader: &mut OperatorsReader<'_>) -> Result<()> {
         let operator = reader.clone().read()?;
-        let (params, results) = operator
-            .operator_arity(self)
-            .ok_or_else(|| anyhow::anyhow!("could not calculate operator arity"))?;
+        let (params, results) = operator.operator_arity(self).unwrap_or((0, 0));
         let use_color = self.printer.result.supports_async_color();
         let mut buf_color = PrintTermcolor(Ansi::new(Vec::new()));
         let mut buf_nocolor = PrintTermcolor(NoColor::new(Vec::new()));
@@ -1568,6 +1582,7 @@ impl<'printer, 'state, 'a, 'b> PrintOperatorFolded<'printer, 'state, 'a, 'b> {
             printer,
             state,
             operator_state,
+            suppress_label_comments: false,
             control: Vec::new(),
             branch_hint: None,
             original_separator,
diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs
index 6a5a3251..cfcd67e3 100644
--- a/crates/wit-component/src/gc.rs
+++ b/crates/wit-component/src/gc.rs
@@ -441,6 +441,12 @@ impl<'a> Module<'a> {
         }
     }
 
+    fn valtys(&mut self, tys: &[ValType]) {
+        for i in tys {
+            self.valty(*i);
+        }
+    }
+
     fn refty(&mut self, ty: RefType) {
         self.heapty(ty.heap_type())
     }
@@ -961,6 +967,7 @@ macro_rules! define_visit {
     (mark_live $self:ident $arg:ident memarg) => {$self.memory($arg.memory);};
     (mark_live $self:ident $arg:ident blockty) => {$self.blockty($arg);};
     (mark_live $self:ident $arg:ident ty) => {$self.valty($arg)};
+    (mark_live $self:ident $arg:ident tys) => {$self.valtys(&$arg)};
     (mark_live $self:ident $arg:ident hty) => {$self.heapty($arg)};
     (mark_live $self:ident $arg:ident from_ref_type) => {$self.refty($arg);};
     (mark_live $self:ident $arg:ident to_ref_type) => {$self.refty($arg);};
diff --git a/src/bin/wasm-tools/wast.rs b/src/bin/wasm-tools/wast.rs
index fc9f1ffd..2b55586c 100644
--- a/src/bin/wasm-tools/wast.rs
+++ b/src/bin/wasm-tools/wast.rs
@@ -161,25 +161,10 @@ impl Opts {
             WastDirective::Module(mut module) | WastDirective::ModuleDefinition(mut module) => {
                 let actual = module.encode()?;
 
-                let test_roundtrip = match module {
-                    // Don't test the wasmprinter round trip since these bytes
-                    // may not be in their canonical form (didn't come from the
-                    // `wat` crate).
-                    QuoteWat::Wat(Wat::Module(Module {
-                        kind: ModuleKind::Binary(_),
-                        ..
-                    }))
-                    | QuoteWat::Wat(Wat::Component(Component {
-                        kind: ComponentKind::Binary(_),
-                        ..
-                    })) => false,
-                    _ => true,
-                };
-
                 let mut test_path = test.to_path_buf();
                 test_path.push(idx.to_string());
 
-                self.test_wasm(&test_path, &actual, test_roundtrip)
+                self.test_wasm(&test_path, &actual, expect_binary_roundtrip(&module))
                     .context("failed testing wasm binary produced by `wast`")?;
             }
 
@@ -285,52 +270,31 @@ impl Opts {
             WastDirective::AssertInvalid {
                 mut module,
                 message,
-                span,
+                span: _,
             } => {
-                // Similar to `assert_malformed`, there are some tests in
-                // upstream wasm which are specifically flagged as
-                // `assert_invalid` because the AST can be created for a test
-                // case but the binary itself shouldn't validate. Like with
-                // `assert_malformed`, though, wasmparser doesn't match the
-                // upstream specification 1:1 in all cases. This is a list of
-                // exceptions.
-                let permissive_error_messages = [
-                    // The typed-select instruction spec-wise takes a list of
-                    // types, but in our AST it takes exactly one type to avoid
-                    // heap allocation. That means that we catch
-                    // non-1-length-lists at parse time, not validation time.
-                    "invalid result arity",
-                ];
-                if self.assert(Assert::Permissive) && permissive_error_messages.contains(&message) {
-                    return self.test_wast_directive(
-                        test,
-                        WastDirective::AssertMalformed {
-                            span,
-                            module,
-                            message,
-                        },
-                        idx,
-                    );
-                }
-
-                let wasm = module.encode()?;
+                // ensure module parses successfully
+                let binary_wasm = module.encode()?;
                 let mut parser = wasmparser::Parser::new(0);
                 parser.set_features(self.features.features());
+                parse_binary_wasm(parser, &binary_wasm)?;
 
-                match self.test_wasm_valid(test, &wasm) {
+                // ensure module round-trips successfully (but don't snapshot it)
+                let mut test_path = test.to_path_buf();
+                test_path.push(idx.to_string());
+                self.test_wasm_roundtrip(
+                    &test_path,
+                    &binary_wasm,
+                    expect_binary_roundtrip(&module),
+                    false,
+                )?;
+
+                // now, ensure that module is invalid
+                match self.test_wasm_valid(test, &binary_wasm) {
                     Ok(_) => bail!(
                         "encoded and validated successfully but should have failed with: {}",
                         message,
                     ),
-                    Err(e) => {
-                        self.assert_error_matches(test, &format!("{e:?}"), message)?;
-                    }
-                }
-
-                // For `assert_invalid` all modules should also parse
-                // successfully, so double-check that here.
-                if let Err(e) = parse_binary_wasm(parser, &wasm) {
-                    bail!("failed to parse module when it should parse successfully: {e}");
+                    Err(e) => self.assert_error_matches(test, &format!("{e:?}"), message)?,
                 }
             }
 
@@ -389,49 +353,92 @@ impl Opts {
 
     /// Performs tests about the wasm binary `contents` associated with `test`.
     ///
-    /// If `test_roundtrip` is `false` then it will always skip the roundtrip
-    /// tests.
-    fn test_wasm(&self, test: &Path, contents: &[u8], test_roundtrip: bool) -> Result<()> {
+    /// If `roundtrip_binary` is `false` then it will not try to compare the
+    /// original binary with the result of roundtripping through wasmprinter->wast.
+    fn test_wasm(&self, test: &Path, contents: &[u8], roundtrip_binary: bool) -> Result<()> {
         self.test_wasm_valid(test, contents)
             .context("wasm isn't valid")?;
+        self.test_wasm_roundtrip(test, contents, roundtrip_binary, true)
+            .context("wasm did not roundtrip")
+    }
 
+    fn test_wasm_roundtrip(
+        &self,
+        test: &Path,
+        contents: &[u8],
+        roundtrip_binary: bool,
+        enable_snapshot: bool,
+    ) -> Result<()> {
         // Test that we can print these bytes.
-        let string = wasmprinter::print_bytes(contents).context("failed to print wasm")?;
-        if self.assert(Assert::SnapshotPrint) {
-            self.snapshot("print", test, &string)
+        let text1 = wasmprinter::print_bytes(contents).context("failed to print wasm")?;
+        if enable_snapshot && self.assert(Assert::SnapshotPrint) {
+            self.snapshot("print", test, &text1)
                 .context("failed to validate the `print` snapshot")?;
         }
 
         // Test that we can print these bytes with instructions in folded form.
-        let mut folded_string = String::new();
+        let mut text1_folded = String::new();
         if self.assert(Assert::TestFolded) {
             let mut folding_printer = wasmprinter::Config::new();
             folding_printer.fold_instructions(true);
             folding_printer
-                .print(contents, &mut PrintFmtWrite(&mut folded_string))
+                .print(contents, &mut PrintFmtWrite(&mut text1_folded))
                 .context("failed to print wasm in folded form")?;
 
-            if self.assert(Assert::SnapshotFolded) {
-                self.snapshot("print-folded", test, &folded_string)
+            if enable_snapshot && self.assert(Assert::SnapshotFolded) {
+                self.snapshot("print-folded", test, &text1_folded)
                     .context("failed to validate the `print-folded` snapshot")?;
             }
         }
 
-        // If we can, convert the string back to bytes and assert it has the
-        // same binary representation.
-        if test_roundtrip && self.assert(Assert::Roundtrip) {
-            let binary2 =
-                wat::parse_str(&string).context("failed to parse `wat` from `wasmprinter`")?;
-            self.binary_compare(&binary2, contents)
-                .context("failed to compare original `wat` with roundtrip `wat`")?;
+        if self.assert(Assert::PrettyWhitespace) {
+            self.test_pretty_whitespace(&text1)?;
+            self.test_pretty_whitespace(&text1_folded)?;
+        }
 
-            if self.assert(Assert::TestFolded) {
-                let binary2f = wat::parse_str(&folded_string)
-                    .context("failed to parse folded `wat` from `wasmprinter`")?;
-                self.binary_compare(&binary2f, contents)
+        // Convert the text versions back to binary and compare to original
+        let binary2 = wat::parse_str(&text1).context("failed to parse `wat` from `wasmprinter`")?;
+        let text2 = wasmprinter::print_bytes(&binary2).context("failed to print wasm")?;
+        if roundtrip_binary {
+            self.binary_compare(&binary2, contents)
+                .context("failed to compare original binary with roundtrip through wasmparser->wasmprinter->wat")?;
+        }
+        if text1 != text2 {
+            anyhow::bail!(
+                "original print does not match roundtrip print\n{}",
+                pretty_assertions::StrComparison::new(&text1, &text2)
+            );
+        }
+        if self.assert(Assert::TestFolded) {
+            let binary2_from_folded = wat::parse_str(&text1_folded)
+                .context("failed to parse folded `wat` from `wasmprinter`")?;
+            let mut text2_folded = String::new();
+            let mut folding_printer = wasmprinter::Config::new();
+            folding_printer.fold_instructions(true);
+            folding_printer
+                .print(contents, &mut PrintFmtWrite(&mut text2_folded))
+                .context("failed to print wasm in folded form")?;
+            if roundtrip_binary {
+                self.binary_compare(&binary2_from_folded, contents)
                     .context("failed to compare original `wat` with roundtrip folded `wat`")?;
             }
+            let text3 =
+                wasmprinter::print_bytes(binary2_from_folded).context("failed to print wasm")?;
+            if text1_folded != text2_folded {
+                anyhow::bail!(
+                    "original folded print does not match roundtrip folded print\n{}",
+                    pretty_assertions::StrComparison::new(&text1_folded, &text2_folded)
+                );
+            }
+            if text1 != text3 {
+                anyhow::bail!(
+                    "original print does not match roundtrip folded->flat print\n{}",
+                    pretty_assertions::StrComparison::new(&text1, &text3)
+                );
+            }
+        }
 
+        if roundtrip_binary {
             if wasmparser::Parser::is_component(contents) {
                 let mut reencode = Default::default();
                 RoundtripReencoder
@@ -448,12 +455,6 @@ impl Opts {
                     .context("failed to compare reencoded module with original encoding")?;
             }
         }
-
-        if self.assert(Assert::PrettyWhitespace) {
-            self.test_pretty_whitespace(&string)?;
-            self.test_pretty_whitespace(&folded_string)?;
-        }
-
         Ok(())
     }
 
@@ -680,10 +681,26 @@ impl Opts {
     }
 }
 
+fn expect_binary_roundtrip(module: &QuoteWat) -> bool {
+    match module {
+        // Don't test that wasmprinter roundtrips an external binary as these bytes
+        // may not be in their canonical form (didn't come from the
+        // `wat` crate).
+        QuoteWat::Wat(Wat::Module(Module {
+            kind: ModuleKind::Binary(_),
+            ..
+        }))
+        | QuoteWat::Wat(Wat::Component(Component {
+            kind: ComponentKind::Binary(_),
+            ..
+        })) => false,
+        _ => true,
+    }
+}
+
 #[derive(clap::ValueEnum, Debug, Copy, Clone, PartialEq, Eq)]
 enum Assert {
     Default,
-    Roundtrip,
     PrettyWhitespace,
     SnapshotPrint,
     SnapshotJson,


Test patch:
diff --git a/tests/cli/component-model/instantiate.wast b/tests/cli/component-model/instantiate.wast
index 24a07a12..64c68af2 100644
--- a/tests/cli/component-model/instantiate.wast
+++ b/tests/cli/component-model/instantiate.wast
@@ -389,7 +389,7 @@
   "function index out of bounds")
 
 (assert_invalid
-  (component (instance $i (export "" (instance 0))))
+  (component (instance (export "" (instance 0))))
   "index out of bounds")
 
 (assert_invalid
@@ -397,7 +397,7 @@
   "index out of bounds")
 
 (assert_invalid
-  (component (instance $i (export "" (instance 0))))
+  (component (instance (export "" (instance 0))))
   "index out of bounds")
 
 (assert_invalid
diff --git a/tests/cli/component-model/types.wast b/tests/cli/component-model/types.wast
index 7cc53000..1a9ba7af 100644
--- a/tests/cli/component-model/types.wast
+++ b/tests/cli/component-model/types.wast
@@ -127,9 +127,9 @@
   "unknown core type")
 
 (assert_invalid
-  (component $c
-    (core type $t (module
-      (alias outer $c 0 (type))
+  (component
+    (core type (module
+      (alias outer 1 0 (type))
     ))
   )
   "type index out of bounds")
@@ -148,9 +148,9 @@
   "unknown type")
 
 (assert_invalid
-  (component $c
-    (type $t (component
-      (alias outer $c 0 (type))
+  (component
+    (type (component
+      (alias outer 1 0 (type))
     ))
   )
   "type index out of bounds")
@@ -200,9 +200,9 @@
   "unknown type")
 
 (assert_invalid
-  (component $c
-    (type $t (instance
-      (alias outer $c 0 (type))
+  (component
+    (type (instance
+      (alias outer 1 0 (type))
     ))
   )
   "type index out of bounds")
diff --git a/tests/cli/dump/select.wat.stdout b/tests/cli/dump/select.wat.stdout
index be65160b..98937ed1 100644
--- a/tests/cli/dump/select.wat.stdout
+++ b/tests/cli/dump/select.wat.stdout
@@ -13,9 +13,7 @@
  0x15 | 0c          | size of function
  0x16 | 00          | 0 local blocks
  0x17 | 1b          | select
- 0x18 | 1c 00       | ??
+ 0x18 | 1c 00       | typed_select_multi tys:[]
  0x1a | 1c 01 7f    | typed_select ty:I32
- 0x1d | 1c 02       | ??
- 0x1f | 7f          | i64_div_s
- 0x20 | 7f          | i64_div_s
+ 0x1d | 1c 02 7f 7f | typed_select_multi tys:[I32, I32]
  0x21 | 0b          | end
diff --git a/tests/cli/print-memarg-too-big.wat b/tests/cli/print-memarg-too-big.wat
index ede83106..7e0d851b 100644
--- a/tests/cli/print-memarg-too-big.wat
+++ b/tests/cli/print-memarg-too-big.wat
@@ -1,4 +1,4 @@
-;; FAIL: print %
+;; RUN: print %
 
 (module binary
     "\00asm" "\01\00\00\00"     ;; module header
diff --git a/tests/cli/print-memarg-too-big.wat.stderr b/tests/cli/print-memarg-too-big.wat.stderr
deleted file mode 100644
index e53e7882..00000000
--- a/tests/cli/print-memarg-too-big.wat.stderr
+++ /dev/null
@@ -1,1 +0,0 @@
-error: alignment in memarg too large
diff --git a/tests/cli/print-memarg-too-big.wat.stdout b/tests/cli/print-memarg-too-big.wat.stdout
index 06ed489a..d6ba0f75 100644
--- a/tests/cli/print-memarg-too-big.wat.stdout
+++ b/tests/cli/print-memarg-too-big.wat.stdout
@@ -1,2 +1,3 @@
 (module
-  (data (;0;) (i32.load16_s
+  (data (;0;) (i32.load16_s align=9223372036854775808) "")
+)
diff --git a/tests/snapshots/cli/producers.wast/5.print b/tests/snapshots/cli/producers.wast/5.print
index f7a0254e..323b8f9a 100644
--- a/tests/snapshots/cli/producers.wast/5.print
+++ b/tests/snapshots/cli/producers.wast/5.print
@@ -1,5 +1,4 @@
 (module
-  (@producers)
   ;; failed to parse custom section `producers`: unexpected end-of-file (at offset 0x15)
-  
+  (@custom "producers" (before first) "\01")
 )
diff --git a/tests/snapshots/cli/producers.wast/6.print b/tests/snapshots/cli/producers.wast/6.print
index f03a5a59..fc940ca2 100644
--- a/tests/snapshots/cli/producers.wast/6.print
+++ b/tests/snapshots/cli/producers.wast/6.print
@@ -1,6 +1,5 @@
 (module
-  (@producers)
   ;; failed to parse custom section `producers`: invalid producers field name: `a
   ;; a` (at offset 0x15)
-  
+  (@custom "producers" (before first) "\01\03a\0aa\01\01a\01a")
 )
diff --git a/tests/snapshots/testsuite/custom.wast/1.print b/tests/snapshots/testsuite/custom.wast/1.print
index ce899846..fcc0e2cb 100644
--- a/tests/snapshots/testsuite/custom.wast/1.print
+++ b/tests/snapshots/testsuite/custom.wast/1.print
@@ -1,24 +1,24 @@
 (module
   (@custom "custom" (before first) "payload")
   (@custom "custom" (before first) "payload")
-  (@custom "custom" (after type) "payload")
-  (@custom "custom" (after type) "payload")
-  (@custom "custom" (after import) "payload")
-  (@custom "custom" (after import) "payload")
-  (@custom "custom" (after func) "payload")
-  (@custom "custom" (after func) "payload")
-  (@custom "custom" (after table) "payload")
-  (@custom "custom" (after table) "payload")
-  (@custom "custom" (after memory) "payload")
-  (@custom "custom" (after memory) "payload")
-  (@custom "custom" (after global) "payload")
-  (@custom "custom" (after global) "payload")
-  (@custom "custom" (after export) "payload")
-  (@custom "custom" (after export) "payload")
-  (@custom "custom" (after element) "payload")
-  (@custom "custom" (after element) "payload")
-  (@custom "custom" (after code) "payload")
-  (@custom "custom" (after code) "payload")
-  (@custom "custom" (after data) "payload")
-  (@custom "custom" (after data) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
+  (@custom "custom" (before first) "payload")
 )