Repository: bytecodealliance/wasm-tools
Pull request #1853 (2024-10-08 17:32:05)

Implement support for the wide-arithmetic proposal
[This proposal](https://github.com/WebAssembly/wide-arithmetic) just recently moved to phase 2 so start implementing support here.


Patch:
diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs
index d493b33a..4ff071b1 100644
--- a/crates/wasm-encoder/src/core/code.rs
+++ b/crates/wasm-encoder/src/core/code.rs
@@ -1223,6 +1223,12 @@ pub enum Instruction<'a> {
         cont_type_index: u32,
         tag_index: u32,
     },
+
+    // Wide Arithmetic
+    I64Add128,
+    I64Sub128,
+    I64MulWideS,
+    I64MulWideU,
 }
 
 impl Encode for Instruction<'_> {
@@ -3757,6 +3763,22 @@ impl Encode for Instruction<'_> {
                 cont_type_index.encode(sink);
                 tag_index.encode(sink);
             }
+            Instruction::I64Add128 => {
+                sink.push(0xFC);
+                19u32.encode(sink);
+            }
+            Instruction::I64Sub128 => {
+                sink.push(0xFC);
+                20u32.encode(sink);
+            }
+            Instruction::I64MulWideS => {
+                sink.push(0xFC);
+                21u32.encode(sink);
+            }
+            Instruction::I64MulWideU => {
+                sink.push(0xFC);
+                22u32.encode(sink);
+            }
         }
     }
 }
diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs
index b6ced3ff..fda50993 100644
--- a/crates/wasm-smith/src/config.rs
+++ b/crates/wasm-smith/src/config.rs
@@ -599,6 +599,13 @@ define_config! {
         ///
         /// Defaults to `false`.
         pub allow_invalid_funcs: bool = false,
+
+        /// Determines whether the [wide-arithmetic proposal] is enabled.
+        ///
+        /// [wide-arithmetic proposal]: https://github.com/WebAssembly/wide-arithmetic
+        ///
+        /// Defaults to `false`.
+        pub wide_arithmetic_enabled: bool = false,
     }
 }
 
@@ -738,6 +745,7 @@ impl<'a> Arbitrary<'a> for Config {
             // Proposals that are not stage4+ are disabled by default.
             memory64_enabled: false,
             custom_page_sizes_enabled: false,
+            wide_arithmetic_enabled: false,
         };
         config.sanitize();
         Ok(config)
diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs
index 8f63a196..310800bf 100644
--- a/crates/wasm-smith/src/core/code_builder.rs
+++ b/crates/wasm-smith/src/core/code_builder.rs
@@ -590,6 +590,10 @@ instructions! {
     (Some(simd_v128_v128_on_stack_relaxed), i16x8_relaxed_q15mulr_s, VectorInt),
     (Some(simd_v128_v128_on_stack_relaxed), i16x8_relaxed_dot_i8x16_i7x16_s, VectorInt),
     (Some(simd_v128_v128_v128_on_stack_relaxed), i32x4_relaxed_dot_i8x16_i7x16_add_s, VectorInt),
+    (Some(wide_arithmetic_binop128_on_stack), i64_add128, NumericInt),
+    (Some(wide_arithmetic_binop128_on_stack), i64_sub128, NumericInt),
+    (Some(wide_arithmetic_mul_wide_on_stack), i64_mul_wide_s, NumericInt),
+    (Some(wide_arithmetic_mul_wide_on_stack), i64_mul_wide_u, NumericInt),
 }
 
 pub(crate) struct CodeBuilderAllocations {
@@ -7266,3 +7270,61 @@ simd_ternop!(
     I32x4RelaxedDotI8x16I7x16AddS,
     i32x4_relaxed_dot_i8x16_i7x16_add_s
 );
+
+#[inline]
+fn wide_arithmetic_binop128_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
+    module.config.wide_arithmetic_enabled && builder.types_on_stack(module, &[ValType::I64; 4])
+}
+
+#[inline]
+fn wide_arithmetic_mul_wide_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool {
+    module.config.wide_arithmetic_enabled && builder.types_on_stack(module, &[ValType::I64; 2])
+}
+
+fn i64_add128(
+    _: &mut Unstructured,
+    module: &Module,
+    builder: &mut CodeBuilder,
+    instructions: &mut Vec<Instruction>,
+) -> Result<()> {
+    builder.pop_operands(module, &[ValType::I64; 4]);
+    builder.push_operands(&[ValType::I64; 2]);
+    instructions.push(Instruction::I64Add128);
+    Ok(())
+}
+
+fn i64_sub128(
+    _: &mut Unstructured,
+    module: &Module,
+    builder: &mut CodeBuilder,
+    instructions: &mut Vec<Instruction>,
+) -> Result<()> {
+    builder.pop_operands(module, &[ValType::I64; 4]);
+    builder.push_operands(&[ValType::I64; 2]);
+    instructions.push(Instruction::I64Sub128);
+    Ok(())
+}
+
+fn i64_mul_wide_s(
+    _: &mut Unstructured,
+    module: &Module,
+    builder: &mut CodeBuilder,
+    instructions: &mut Vec<Instruction>,
+) -> Result<()> {
+    builder.pop_operands(module, &[ValType::I64; 2]);
+    builder.push_operands(&[ValType::I64; 2]);
+    instructions.push(Instruction::I64MulWideS);
+    Ok(())
+}
+
+fn i64_mul_wide_u(
+    _: &mut Unstructured,
+    module: &Module,
+    builder: &mut CodeBuilder,
+    instructions: &mut Vec<Instruction>,
+) -> Result<()> {
+    builder.pop_operands(module, &[ValType::I64; 2]);
+    builder.push_operands(&[ValType::I64; 2]);
+    instructions.push(Instruction::I64MulWideU);
+    Ok(())
+}
diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs
index f21748fa..aab5fc33 100644
--- a/crates/wasmparser/src/binary_reader.rs
+++ b/crates/wasmparser/src/binary_reader.rs
@@ -1357,6 +1357,11 @@ impl<'a> BinaryReader<'a> {
                 visitor.visit_memory_discard(mem)
             }
 
+            0x13 => visitor.visit_i64_add128(),
+            0x14 => visitor.visit_i64_sub128(),
+            0x15 => visitor.visit_i64_mul_wide_s(),
+            0x16 => visitor.visit_i64_mul_wide_u(),
+
             _ => bail!(pos, "unknown 0xfc subopcode: 0x{code:x}"),
         })
     }
diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs
index e595111f..35fb08cb 100644
--- a/crates/wasmparser/src/features.rs
+++ b/crates/wasmparser/src/features.rs
@@ -227,6 +227,8 @@ define_wasm_features! {
         pub gc_types: GC_TYPES(1 << 26) = true;
         /// The WebAssembly [stack-switching proposal](https://github.com/WebAssembly/stack-switching).
         pub stack_switching: STACK_SWITCHING(1 << 27) = false;
+        /// The WebAssembly [wide-arithmetic proposal](https://github.com/WebAssembly/wide-arithmetic).
+        pub wide_arithmetic: WIDE_ARITHMETIC(1 << 28) = false;
     }
 }
 
diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs
index fa5bac1d..5bebb5f4 100644
--- a/crates/wasmparser/src/lib.rs
+++ b/crates/wasmparser/src/lib.rs
@@ -815,6 +815,11 @@ macro_rules! for_each_operator {
             @stack_switching Resume { cont_type_index: u32, resume_table: $crate::ResumeTable } => visit_resume (arity 1 type -> type)
             @stack_switching ResumeThrow { cont_type_index: u32, tag_index: u32, resume_table: $crate::ResumeTable } => visit_resume_throw (arity 1 tag -> type)
             @stack_switching Switch { cont_type_index: u32, tag_index: u32 } => visit_switch (arity type -> ~switch)
+
+            @wide_arithmetic I64Add128 => visit_i64_add128 (arity 4 -> 2)
+            @wide_arithmetic I64Sub128 => visit_i64_sub128 (arity 4 -> 2)
+            @wide_arithmetic I64MulWideS => visit_i64_mul_wide_s (arity 2 -> 2)
+            @wide_arithmetic I64MulWideU => visit_i64_mul_wide_u (arity 2 -> 2)
         }
     };
 }
diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs
index 98ef2ca4..29571ceb 100644
--- a/crates/wasmparser/src/validator/operators.rs
+++ b/crates/wasmparser/src/validator/operators.rs
@@ -1640,6 +1640,24 @@ where
             .zip(ts2.iter())
             .all(|(ty1, ty2)| self.resources.is_subtype(*ty1, *ty2))
     }
+
+    fn check_binop128(&mut self) -> Result<()> {
+        self.pop_operand(Some(ValType::I64))?;
+        self.pop_operand(Some(ValType::I64))?;
+        self.pop_operand(Some(ValType::I64))?;
+        self.pop_operand(Some(ValType::I64))?;
+        self.push_operand(ValType::I64)?;
+        self.push_operand(ValType::I64)?;
+        Ok(())
+    }
+
+    fn check_i64_mul_wide(&mut self) -> Result<()> {
+        self.pop_operand(Some(ValType::I64))?;
+        self.pop_operand(Some(ValType::I64))?;
+        self.push_operand(ValType::I64)?;
+        self.push_operand(ValType::I64)?;
+        Ok(())
+    }
 }
 
 pub fn ty_to_str(ty: ValType) -> &'static str {
@@ -1704,6 +1722,7 @@ macro_rules! validate_proposal {
     (desc gc) => ("gc");
     (desc legacy_exceptions) => ("legacy exceptions");
     (desc stack_switching) => ("stack switching");
+    (desc wide_arithmetic) => ("wide arithmetic");
 }
 
 impl<'a, T> VisitOperator<'a> for WasmProposalValidator<'_, '_, T>
@@ -5035,6 +5054,18 @@ where
         }
         Ok(())
     }
+    fn visit_i64_add128(&mut self) -> Result<()> {
+        self.check_binop128()
+    }
+    fn visit_i64_sub128(&mut self) -> Result<()> {
+        self.check_binop128()
+    }
+    fn visit_i64_mul_wide_s(&mut self) -> Result<()> {
+        self.check_i64_mul_wide()
+    }
+    fn visit_i64_mul_wide_u(&mut self) -> Result<()> {
+        self.check_i64_mul_wide()
+    }
 }
 
 #[derive(Clone, Debug)]
diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs
index 689c469b..6456ecc3 100644
--- a/crates/wasmprinter/src/operator.rs
+++ b/crates/wasmprinter/src/operator.rs
@@ -1379,7 +1379,11 @@ macro_rules! define_visit {
     (name Suspend) => ("suspend");
     (name Resume) => ("resume");
     (name ResumeThrow) => ("resume_throw");
-    (name Switch) => ("switch")
+    (name Switch) => ("switch");
+    (name I64Add128) => ("i64.add128");
+    (name I64Sub128) => ("i64.sub128");
+    (name I64MulWideS) => ("i64.mul_wide_s");
+    (name I64MulWideU) => ("i64.mul_wide_u");
 }
 
 impl<'a> VisitOperator<'a> for PrintOperator<'_, '_, '_, '_> {
diff --git a/crates/wast/src/core/expr.rs b/crates/wast/src/core/expr.rs
index 21bd6d53..228f46b9 100644
--- a/crates/wast/src/core/expr.rs
+++ b/crates/wast/src/core/expr.rs
@@ -1195,6 +1195,12 @@ instructions! {
         Resume(Resume<'a>)             : [0xe3] : "resume",
         ResumeThrow(ResumeThrow<'a>)   : [0xe4] : "resume_throw",
         Switch(Switch<'a>)             : [0xe5] : "switch",
+
+        // Wide arithmetic proposal
+        I64Add128   : [0xfc, 19] : "i64.add128",
+        I64Sub128   : [0xfc, 20] : "i64.sub128",
+        I64MulWideS : [0xfc, 21] : "i64.mul_wide_s",
+        I64MulWideU : [0xfc, 22] : "i64.mul_wide_u",
     }
 }
 
diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs
index 8244bb9c..9130954a 100644
--- a/fuzz/src/lib.rs
+++ b/fuzz/src/lib.rs
@@ -60,6 +60,7 @@ pub fn generate_valid_component(
     config.memory64_enabled = u.arbitrary()?;
     config.exceptions_enabled = u.arbitrary()?;
     config.canonicalize_nans = u.arbitrary()?;
+    config.wide_arithmetic_enabled = u.arbitrary()?;
 
     configure(&mut config, u)?;
 
@@ -111,6 +112,10 @@ pub fn validator_for_config(config: &Config) -> wasmparser::Validator {
     // this point.
     features.set(WasmFeatures::FUNCTION_REFERENCES, config.gc_enabled);
     features.set(WasmFeatures::GC, config.gc_enabled);
+    features.set(
+        WasmFeatures::WIDE_ARITHMETIC,
+        config.wide_arithmetic_enabled,
+    );
     wasmparser::Validator::new_with_features(features)
 }
 


Test patch:
diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr
index 3156c21b..d0d274b7 100644
--- a/tests/cli/validate-unknown-features.wat.stderr
+++ b/tests/cli/validate-unknown-features.wat.stderr
@@ -1,4 +1,4 @@
 error: invalid value 'unknown' for '--features <FEATURES>': unknown feature `unknown`
-Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, component-model-values, component-model-nested-names, component-model-more-flags, component-model-multiple-returns, legacy-exceptions, gc-types, stack-switching, mvp, wasm1, wasm2, wasm3, all
+Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, component-model-values, component-model-nested-names, component-model-more-flags, component-model-multiple-returns, legacy-exceptions, gc-types, stack-switching, wide-arithmetic, mvp, wasm1, wasm2, wasm3, all
 
 For more information, try '--help'.
diff --git a/tests/local/missing-features/wide-arithmetic.wast b/tests/local/missing-features/wide-arithmetic.wast
new file mode 100644
index 00000000..4bf70209
--- /dev/null
+++ b/tests/local/missing-features/wide-arithmetic.wast
@@ -0,0 +1,47 @@
+(assert_invalid
+  (module
+    (func (param i64 i64 i64 i64)
+      local.get 0
+      local.get 1
+      local.get 2
+      local.get 3
+      i64.add128
+      drop
+      drop)
+  )
+  "wide arithmetic support is not enabled")
+
+(assert_invalid
+  (module
+    (func (param i64 i64 i64 i64)
+      local.get 0
+      local.get 1
+      local.get 2
+      local.get 3
+      i64.sub128
+      drop
+      drop)
+  )
+  "wide arithmetic support is not enabled")
+
+(assert_invalid
+  (module
+    (func (param i64 i64)
+      local.get 0
+      local.get 1
+      i64.mul_wide_s
+      drop
+      drop)
+  )
+  "wide arithmetic support is not enabled")
+
+(assert_invalid
+  (module
+    (func (param i64 i64)
+      local.get 0
+      local.get 1
+      i64.mul_wide_u
+      drop
+      drop)
+  )
+  "wide arithmetic support is not enabled")
diff --git a/tests/local/wide-arithmetic.wast b/tests/local/wide-arithmetic.wast
new file mode 100644
index 00000000..83970d90
--- /dev/null
+++ b/tests/local/wide-arithmetic.wast
@@ -0,0 +1,94 @@
+(module
+  (func (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.add128)
+  (func (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.sub128)
+  (func (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s)
+  (func (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u)
+)
+
+(assert_invalid
+  (module
+    (func (param i64 i64 i64 i64) (result i64)
+      local.get 0
+      local.get 1
+      local.get 2
+      local.get 3
+      i64.add128)
+  )
+  "type mismatch")
+(assert_invalid
+  (module
+    (func (param i64 i64 i64) (result i64 i64)
+      local.get 0
+      local.get 1
+      local.get 2
+      i64.add128)
+  )
+  "type mismatch")
+
+(assert_invalid
+  (module
+    (func (param i64 i64 i64 i64) (result i64)
+      local.get 0
+      local.get 1
+      local.get 2
+      local.get 3
+      i64.sub128)
+  )
+  "type mismatch")
+(assert_invalid
+  (module
+    (func (param i64 i64 i64) (result i64 i64)
+      local.get 0
+      local.get 1
+      local.get 2
+      i64.sub128)
+  )
+  "type mismatch")
+
+(assert_invalid
+  (module
+    (func (param i64 i64) (result i64)
+      local.get 0
+      local.get 1
+      i64.mul_wide_s)
+  )
+  "type mismatch")
+(assert_invalid
+  (module
+    (func (param i64) (result i64 i64)
+      local.get 0
+      i64.mul_wide_s)
+  )
+  "type mismatch")
+
+(assert_invalid
+  (module
+    (func (param i64 i64) (result i64)
+      local.get 0
+      local.get 1
+      i64.mul_wide_u)
+  )
+  "type mismatch")
+(assert_invalid
+  (module
+    (func (param i64) (result i64 i64)
+      local.get 0
+      i64.mul_wide_u)
+  )
+  "type mismatch")
diff --git a/tests/snapshots/local/missing-features/wide-arithmetic.wast.json b/tests/snapshots/local/missing-features/wide-arithmetic.wast.json
new file mode 100644
index 00000000..959d76a6
--- /dev/null
+++ b/tests/snapshots/local/missing-features/wide-arithmetic.wast.json
@@ -0,0 +1,33 @@
+{
+  "source_filename": "tests/local/missing-features/wide-arithmetic.wast",
+  "commands": [
+    {
+      "type": "assert_invalid",
+      "line": 2,
+      "filename": "wide-arithmetic.0.wasm",
+      "module_type": "binary",
+      "text": "wide arithmetic support is not enabled"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 15,
+      "filename": "wide-arithmetic.1.wasm",
+      "module_type": "binary",
+      "text": "wide arithmetic support is not enabled"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 28,
+      "filename": "wide-arithmetic.2.wasm",
+      "module_type": "binary",
+      "text": "wide arithmetic support is not enabled"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 39,
+      "filename": "wide-arithmetic.3.wasm",
+      "module_type": "binary",
+      "text": "wide arithmetic support is not enabled"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/wide-arithmetic.wast.json b/tests/snapshots/local/wide-arithmetic.wast.json
new file mode 100644
index 00000000..6ecf7c12
--- /dev/null
+++ b/tests/snapshots/local/wide-arithmetic.wast.json
@@ -0,0 +1,67 @@
+{
+  "source_filename": "tests/local/wide-arithmetic.wast",
+  "commands": [
+    {
+      "type": "module",
+      "line": 1,
+      "filename": "wide-arithmetic.0.wasm",
+      "module_type": "binary"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 25,
+      "filename": "wide-arithmetic.1.wasm",
+      "module_type": "binary",
+      "text": "type mismatch"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 35,
+      "filename": "wide-arithmetic.2.wasm",
+      "module_type": "binary",
+      "text": "type mismatch"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 45,
+      "filename": "wide-arithmetic.3.wasm",
+      "module_type": "binary",
+      "text": "type mismatch"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 55,
+      "filename": "wide-arithmetic.4.wasm",
+      "module_type": "binary",
+      "text": "type mismatch"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 65,
+      "filename": "wide-arithmetic.5.wasm",
+      "module_type": "binary",
+      "text": "type mismatch"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 73,
+      "filename": "wide-arithmetic.6.wasm",
+      "module_type": "binary",
+      "text": "type mismatch"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 81,
+      "filename": "wide-arithmetic.7.wasm",
+      "module_type": "binary",
+      "text": "type mismatch"
+    },
+    {
+      "type": "assert_invalid",
+      "line": 89,
+      "filename": "wide-arithmetic.8.wasm",
+      "module_type": "binary",
+      "text": "type mismatch"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/snapshots/local/wide-arithmetic.wast/0.print b/tests/snapshots/local/wide-arithmetic.wast/0.print
new file mode 100644
index 00000000..ade68d47
--- /dev/null
+++ b/tests/snapshots/local/wide-arithmetic.wast/0.print
@@ -0,0 +1,28 @@
+(module
+  (type (;0;) (func (param i64 i64 i64 i64) (result i64 i64)))
+  (type (;1;) (func (param i64 i64) (result i64 i64)))
+  (func (;0;) (type 0) (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.add128
+  )
+  (func (;1;) (type 0) (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.sub128
+  )
+  (func (;2;) (type 1) (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s
+  )
+  (func (;3;) (type 1) (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u
+  )
+)