Repository: bytecodealliance/wasm-tools
Pull request #1881 (2024-10-23 22:52:04)

Support `i8x16.shuffle` in `wasm-mutate`
Support another SIMD instruction in `wasm-mutate` to assist with shrinking functions which might contain this instruction.


Patch:
diff --git a/crates/wasm-encoder/README.md b/crates/wasm-encoder/README.md
index 5f2efbe2..86df7008 100644
--- a/crates/wasm-encoder/README.md
+++ b/crates/wasm-encoder/README.md
@@ -36,7 +36,7 @@ let mut module = Module::new();
 let mut types = TypeSection::new();
 let params = vec![ValType::I32, ValType::I32];
 let results = vec![ValType::I32];
-types.function(params, results);
+types.ty().function(params, results);
 module.section(&types);
 
 // Encode the function section.
diff --git a/crates/wasm-mutate/src/mutators/peephole.rs b/crates/wasm-mutate/src/mutators/peephole.rs
index efa618a8..75ede341 100644
--- a/crates/wasm-mutate/src/mutators/peephole.rs
+++ b/crates/wasm-mutate/src/mutators/peephole.rs
@@ -1595,4 +1595,18 @@ mod tests {
         mutator.rules = Some(rules);
         config.match_mutation(original, mutator, expected);
     }
+
+    #[test]
+    fn i8x16_shuffle_handled() {
+        test_default_peephole_mutator(
+            "(module (func (param v128 v128)
+                local.get 0
+                local.get 1
+                i8x16.shuffle 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+                drop)
+            )",
+            "(module (func (param v128 v128)))",
+            4,
+        );
+    }
 }
diff --git a/crates/wasm-mutate/src/mutators/peephole/dfg.rs b/crates/wasm-mutate/src/mutators/peephole/dfg.rs
index 70a19fc6..78664314 100644
--- a/crates/wasm-mutate/src/mutators/peephole/dfg.rs
+++ b/crates/wasm-mutate/src/mutators/peephole/dfg.rs
@@ -3,7 +3,7 @@
 
 use super::eggsy::encoder::rebuild::build_expr;
 use crate::mutators::peephole::{
-    Lang, MemArg, MemArgLane, MemoryCopy, MemoryInit, RefType, TableCopy, TableInit,
+    Lang, MemArg, MemArgLane, MemoryCopy, MemoryInit, RefType, Shuffle, TableCopy, TableInit,
 };
 use crate::mutators::OperatorAndByteOffset;
 use crate::{ModuleInfo, WasmMutate};
@@ -865,6 +865,11 @@ impl<'a> DFGBuilder {
                 }
 
                 Operator::I8x16Swizzle => self.binop(idx, Lang::I8x16Swizzle),
+                Operator::I8x16Shuffle { lanes } => {
+                    let a = Id::from(self.pop_operand(idx, false));
+                    let b = Id::from(self.pop_operand(idx, false));
+                    self.push_node(Lang::I8x16Shuffle(Shuffle { indices: *lanes }, [b, a]), idx);
+                }
                 Operator::I8x16Splat => self.unop(idx, Lang::I8x16Splat),
                 Operator::I16x8Splat => self.unop(idx, Lang::I16x8Splat),
                 Operator::I32x4Splat => self.unop(idx, Lang::I32x4Splat),
diff --git a/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs b/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs
index c2ee6f6b..a8981f23 100644
--- a/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs
+++ b/crates/wasm-mutate/src/mutators/peephole/eggsy/analysis.rs
@@ -347,6 +347,7 @@ impl PeepholeMutationAnalysis {
             Lang::F64x2ReplaceLane(..) => Ok(PrimitiveTypeInfo::V128),
 
             Lang::I8x16Swizzle(_) => Ok(PrimitiveTypeInfo::V128),
+            Lang::I8x16Shuffle(..) => Ok(PrimitiveTypeInfo::V128),
             Lang::I8x16Splat(_) => Ok(PrimitiveTypeInfo::V128),
             Lang::I16x8Splat(_) => Ok(PrimitiveTypeInfo::V128),
             Lang::I32x4Splat(_) => Ok(PrimitiveTypeInfo::V128),
diff --git a/crates/wasm-mutate/src/mutators/peephole/eggsy/encoder/expr2wasm.rs b/crates/wasm-mutate/src/mutators/peephole/eggsy/encoder/expr2wasm.rs
index 67e2c295..f50049ae 100644
--- a/crates/wasm-mutate/src/mutators/peephole/eggsy/encoder/expr2wasm.rs
+++ b/crates/wasm-mutate/src/mutators/peephole/eggsy/encoder/expr2wasm.rs
@@ -525,6 +525,9 @@ pub fn expr2wasm(
                     Lang::F64x2ReplaceLane(lane, _) => insn(Instruction::F64x2ReplaceLane(*lane)),
 
                     Lang::I8x16Swizzle(_) => insn(Instruction::I8x16Swizzle),
+                    Lang::I8x16Shuffle(indices, _) => {
+                        insn(Instruction::I8x16Shuffle(indices.indices))
+                    }
                     Lang::I8x16Splat(_) => insn(Instruction::I8x16Splat),
                     Lang::I16x8Splat(_) => insn(Instruction::I16x8Splat),
                     Lang::I32x4Splat(_) => insn(Instruction::I32x4Splat),
diff --git a/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs b/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs
index db4d9210..c6640e80 100644
--- a/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs
+++ b/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs
@@ -714,6 +714,7 @@ lang! {
         F64x2ReplaceLane(u8, [Id; 2]) = "f64x2.replace_lane",
 
         I8x16Swizzle([Id; 2]) = "i8x16.swizzle",
+        I8x16Shuffle(Shuffle, [Id; 2]) = "i8x16.shuffle",
         I8x16Splat([Id; 1]) = "i8x16.splat",
         I16x8Splat([Id; 1]) = "i16x8.splat",
         I32x4Splat([Id; 1]) = "i32x4.splat",
@@ -1188,6 +1189,40 @@ impl FromStr for MemArgLane {
     }
 }
 
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
+pub struct Shuffle {
+    pub indices: [u8; 16],
+}
+
+impl fmt::Display for Shuffle {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        for (i, idx) in self.indices.iter().enumerate() {
+            if i != 0 {
+                write!(f, ".")?;
+            }
+            write!(f, "{idx}")?;
+        }
+        Ok(())
+    }
+}
+
+impl FromStr for Shuffle {
+    type Err = String;
+    fn from_str(s: &str) -> Result<Shuffle, String> {
+        let indices = s
+            .split('.')
+            .map(|part| part.parse::<u8>())
+            .collect::<Result<Vec<_>, _>>()
+            .map_err(|_| format!("failed to parse integer"))?;
+        let indices: &[u8; 16] = indices
+            .as_slice()
+            .try_into()
+            .map_err(|_| format!("wrong number of lanes"))?;
+
+        Ok(Shuffle { indices: *indices })
+    }
+}
+
 trait Children: Sized {
     fn from(list: Vec<Id>) -> Result<Self, String>;
     fn as_slice(&self) -> &[Id];
diff --git a/crates/wit-component/src/semver_check.rs b/crates/wit-component/src/semver_check.rs
index 1c4a9f4f..58d0aa74 100644
--- a/crates/wit-component/src/semver_check.rs
+++ b/crates/wit-component/src/semver_check.rs
@@ -55,7 +55,7 @@ pub fn semver_check(mut resolve: Resolve, prev: WorldId, new: WorldId) -> Result
         .package
         .context("new world not in named package")?;
     let new_pkg_name = &resolve.packages[new_pkg_id].name;
-    if old_pkg_id != new_pkg_id {
+    if old_pkg_name != new_pkg_name {
         bail!("the old world is in package {old_pkg_name}, which is not the same as the new world, which is in package {new_pkg_name}", )
     }
 
diff --git a/src/bin/wasm-tools/validate.rs b/src/bin/wasm-tools/validate.rs
index 1ef778b9..c5a149e8 100644
--- a/src/bin/wasm-tools/validate.rs
+++ b/src/bin/wasm-tools/validate.rs
@@ -54,12 +54,19 @@ pub struct Opts {
     /// Available feature options can be found in the wasmparser crate:
     /// <https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wasmparser/src/features.rs>
     #[clap(long, short = 'f', value_parser = parse_features)]
-    features: Option<WasmFeatures>,
+    features: Vec<Vec<FeatureAction>>,
 
     #[clap(flatten)]
     io: wasm_tools::InputOutput,
 }
 
+#[derive(Clone)]
+enum FeatureAction {
+    Reset(WasmFeatures),
+    Enable(WasmFeatures),
+    Disable(WasmFeatures),
+}
+
 impl Opts {
     pub fn general_opts(&self) -> &wasm_tools::GeneralOpts {
         self.io.general_opts()
@@ -92,6 +99,26 @@ impl Opts {
         }
     }
 
+    fn features(&self) -> Result<WasmFeatures> {
+        let mut ret = WasmFeatures::default();
+
+        for action in self.features.iter().flat_map(|v| v) {
+            match action {
+                FeatureAction::Enable(features) => {
+                    ret |= *features;
+                }
+                FeatureAction::Disable(features) => {
+                    ret &= !*features;
+                }
+                FeatureAction::Reset(features) => {
+                    ret = *features;
+                }
+            }
+        }
+
+        Ok(ret)
+    }
+
     fn validate(&self, wasm: &[u8]) -> Result<()> {
         // Note that here we're copying the contents of
         // `Validator::validate_all`, but the end is followed up with a parallel
@@ -103,7 +130,7 @@ impl Opts {
         // `Validator` we're using as we navigate nested modules (the module
         // linking proposal) and any functions found are deferred to get
         // validated later.
-        let mut validator = Validator::new_with_features(self.features.unwrap_or_default());
+        let mut validator = Validator::new_with_features(self.features()?);
         let mut functions_to_validate = Vec::new();
 
         let start = Instant::now();
@@ -182,8 +209,8 @@ impl Opts {
     }
 }
 
-fn parse_features(arg: &str) -> Result<WasmFeatures> {
-    let mut ret = WasmFeatures::default();
+fn parse_features(arg: &str) -> Result<Vec<FeatureAction>> {
+    let mut ret = Vec::new();
 
     const GROUPS: &[(&str, WasmFeatures)] = &[
         ("mvp", WasmFeatures::WASM1),
@@ -226,18 +253,24 @@ fn parse_features(arg: &str) -> Result<WasmFeatures> {
             }
             match action {
                 Action::ChangeAll => {
-                    for flag in WasmFeatures::FLAGS.iter() {
-                        ret.set(*flag.value(), enable);
-                    }
+                    ret.push(if enable {
+                        FeatureAction::Enable(WasmFeatures::all())
+                    } else {
+                        FeatureAction::Disable(WasmFeatures::all())
+                    });
                 }
                 Action::Modify(feature) => {
-                    ret.set(feature, enable);
+                    ret.push(if enable {
+                        FeatureAction::Enable(feature)
+                    } else {
+                        FeatureAction::Disable(feature)
+                    });
                 }
                 Action::Group(features) => {
                     if !enable {
                         bail!("cannot disable `{part}`, it can only be enabled");
                     }
-                    ret = features;
+                    ret.push(FeatureAction::Reset(features));
                 }
             }
             continue 'outer;


Test patch:
diff --git a/tests/cli/semver-check-different-package-versions.wit b/tests/cli/semver-check-different-package-versions.wit
new file mode 100644
index 00000000..ff169c1f
--- /dev/null
+++ b/tests/cli/semver-check-different-package-versions.wit
@@ -0,0 +1,14 @@
+// FAIL: component semver-check % --prev a:b/worldly@0.2.0 --new a:b/worldly@0.2.1
+
+package foo:root;
+package a:b@0.2.0 {
+  world worldly {
+    import a: func();
+    import b: interface {}
+  }
+}
+
+package a:b@0.2.1 {
+  world worldly {
+  }
+}
diff --git a/tests/cli/semver-check-different-package-versions.wit.stderr b/tests/cli/semver-check-different-package-versions.wit.stderr
new file mode 100644
index 00000000..07bfece7
--- /dev/null
+++ b/tests/cli/semver-check-different-package-versions.wit.stderr
@@ -0,0 +1,5 @@
+error: new world is not semver-compatible with the previous world
+
+Caused by:
+    0: type mismatch for import `worldly`
+       missing import named `a` (at offset 0x186)
diff --git a/tests/cli/validate-multiple-flags.wat b/tests/cli/validate-multiple-flags.wat
new file mode 100644
index 00000000..bbcf18a3
--- /dev/null
+++ b/tests/cli/validate-multiple-flags.wat
@@ -0,0 +1,5 @@
+;; RUN: validate % -f wasm1 -f simd
+
+(module
+  (import "" "" (global v128))
+)