welw

Extending WASM with new instructions

This is just to show off that I can extend Web Assembly's interpreter with new instructions. This has some potential nice use cases, I won't talk about now.

We've made two key changes in WASM code:

These changes together ensure that the f64.add operation will always return 0.

diff --git a/interpreter/exec/eval_num.ml b/interpreter/exec/eval_num.ml
index 40dd1be..3bcc429 100644
--- a/interpreter/exec/eval_num.ml
+++ b/interpreter/exec/eval_num.ml
@@ -80,7 +80,7 @@ struct

   let binop op =
     let f = match op with
-      | Add -> FXX.add
+      | Add -> (fun _ _ -> FXX.zero)
       | Sub -> FXX.sub
       | Mul -> FXX.mul
       | Div -> FXX.div
diff --git a/test/core/float_rounding_variants.wast b/test/core/float_rounding_variants.wast
index 31463cf..15bf377 100644
--- a/test/core/float_rounding_variants.wast
+++ b/test/core/float_rounding_variants.wast
@@ -32,5 +32,5 @@


 ;; Rounding Variants (here comes the cow)
-(assert_return (invoke "f64.add" (f64.const 2.0) (f64.const 2.0)) (f64.const 4.0))
+(assert_return (invoke "f64.add" (f64.const 2.0) (f64.const 2.0)) (f64.const 0.0))
 ;;(assert_return (invoke "f64.add_zero_all" (f64.const 1.7976931348623157e308) (f64.const 1.7976931348623157e308)) (f64.const 1.7976931348623157e308))

Summary

We have introduced a new AddZeroAll operation across different numeric types (f32, f64, i32, i64) in WebAssembly reference (WASM) interpreter. In summary, we added:

Full patch for the new instruction:


diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml
index fa3a0ef..a3dd7eb 100644
--- a/interpreter/binary/decode.ml
+++ b/interpreter/binary/decode.ml
@@ -429,6 +429,7 @@ let rec instr s =
   | 0x9e -> f64_nearest
   | 0x9f -> f64_sqrt
   | 0xa0 -> f64_add
+  | 0xff -> f64_add_zero_all
   | 0xa1 -> f64_sub
   | 0xa2 -> f64_mul
   | 0xa3 -> f64_div
diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml
index f5665bb..f87f7cb 100644
--- a/interpreter/binary/encode.ml
+++ b/interpreter/binary/encode.ml
@@ -379,6 +379,7 @@ struct
     | Unary (F64 F64Op.Sqrt) -> op 0x9f

     | Binary (I32 I32Op.Add) -> op 0x6a
+    | Binary (I32 I32Op.AddZeroAll) -> op 0x00
     | Binary (I32 I32Op.Sub) -> op 0x6b
     | Binary (I32 I32Op.Mul) -> op 0x6c
     | Binary (I32 I32Op.DivS) -> op 0x6d
@@ -395,6 +396,7 @@ struct
     | Binary (I32 I32Op.Rotr) -> op 0x78

     | Binary (I64 I64Op.Add) -> op 0x7c
+    | Binary (I64 I64Op.AddZeroAll) -> op 0x00
     | Binary (I64 I64Op.Sub) -> op 0x7d
     | Binary (I64 I64Op.Mul) -> op 0x7e
     | Binary (I64 I64Op.DivS) -> op 0x7f
@@ -411,6 +413,7 @@ struct
     | Binary (I64 I64Op.Rotr) -> op 0x8a

     | Binary (F32 F32Op.Add) -> op 0x92
+    | Binary (F32 F32Op.AddZeroAll) -> op 0x00
     | Binary (F32 F32Op.Sub) -> op 0x93
     | Binary (F32 F32Op.Mul) -> op 0x94
     | Binary (F32 F32Op.Div) -> op 0x95
@@ -419,6 +422,7 @@ struct
     | Binary (F32 F32Op.CopySign) -> op 0x98

     | Binary (F64 F64Op.Add) -> op 0xa0
+    | Binary (F64 F64Op.AddZeroAll) -> op 0xff
     | Binary (F64 F64Op.Sub) -> op 0xa1
     | Binary (F64 F64Op.Mul) -> op 0xa2
     | Binary (F64 F64Op.Div) -> op 0xa3
diff --git a/interpreter/exec/eval_num.ml b/interpreter/exec/eval_num.ml
index 40dd1be..f32da00 100644
--- a/interpreter/exec/eval_num.ml
+++ b/interpreter/exec/eval_num.ml
@@ -20,6 +20,7 @@ struct
   let binop op =
     let f = match op with
       | Add -> IXX.add
+      | AddZeroAll -> (fun _ _ -> IXX.zero)
       | Sub -> IXX.sub
       | Mul -> IXX.mul
       | DivS -> IXX.div_s
@@ -81,6 +82,7 @@ struct
   let binop op =
     let f = match op with
       | Add -> FXX.add
+      | AddZeroAll -> (fun _ _ -> FXX.zero)
       | Sub -> FXX.sub
       | Mul -> FXX.mul
       | Div -> FXX.div
diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml
index a6eb0d9..1e94d0c 100644
--- a/interpreter/syntax/ast.ml
+++ b/interpreter/syntax/ast.ml
@@ -26,7 +26,7 @@ type void = Lib.void
 module IntOp =
 struct
   type unop = Clz | Ctz | Popcnt | ExtendS of pack_size
-  type binop = Add | Sub | Mul | DivS | DivU | RemS | RemU
+  type binop = Add | AddZeroAll | Sub | Mul | DivS | DivU | RemS | RemU
              | And | Or | Xor | Shl | ShrS | ShrU | Rotl | Rotr
   type testop = Eqz
   type relop = Eq | Ne | LtS | LtU | GtS | GtU | LeS | LeU | GeS | GeU
@@ -39,7 +39,7 @@ end
 module FloatOp =
 struct
   type unop = Neg | Abs | Ceil | Floor | Trunc | Nearest | Sqrt
-  type binop = Add | Sub | Mul | Div | Min | Max | CopySign
+  type binop = Add | AddZeroAll | Sub | Mul | Div | Min | Max | CopySign
   type testop = |
   type relop = Eq | Ne | Lt | Gt | Le | Ge
   type cvtop = ConvertSI32 | ConvertUI32 | ConvertSI64 | ConvertUI64
diff --git a/interpreter/syntax/operators.ml b/interpreter/syntax/operators.ml
index 296bb1e..33f6dbd 100644
--- a/interpreter/syntax/operators.ml
+++ b/interpreter/syntax/operators.ml
@@ -114,6 +114,7 @@ let f64_trunc = Unary (F64 F64Op.Trunc)
 let f64_nearest = Unary (F64 F64Op.Nearest)

 let i32_add = Binary (I32 I32Op.Add)
+let i32_add_zero_all = Binary (I32 I32Op.AddZeroAll)
 let i32_sub = Binary (I32 I32Op.Sub)
 let i32_mul = Binary (I32 I32Op.Mul)
 let i32_div_s = Binary (I32 I32Op.DivS)
@@ -129,6 +130,7 @@ let i32_shr_u = Binary (I32 I32Op.ShrU)
 let i32_rotl = Binary (I32 I32Op.Rotl)
 let i32_rotr = Binary (I32 I32Op.Rotr)
 let i64_add = Binary (I64 I64Op.Add)
+let i64_add_zero_all = Binary (I64 I64Op.AddZeroAll)
 let i64_sub = Binary (I64 I64Op.Sub)
 let i64_mul = Binary (I64 I64Op.Mul)
 let i64_div_s = Binary (I64 I64Op.DivS)
@@ -144,6 +146,7 @@ let i64_shr_u = Binary (I64 I64Op.ShrU)
 let i64_rotl = Binary (I64 I64Op.Rotl)
 let i64_rotr = Binary (I64 I64Op.Rotr)
 let f32_add = Binary (F32 F32Op.Add)
+let f32_add_zero_all = Binary (F32 F32Op.AddZeroAll)
 let f32_sub = Binary (F32 F32Op.Sub)
 let f32_mul = Binary (F32 F32Op.Mul)
 let f32_div = Binary (F32 F32Op.Div)
@@ -151,6 +154,7 @@ let f32_min = Binary (F32 F32Op.Min)
 let f32_max = Binary (F32 F32Op.Max)
 let f32_copysign = Binary (F32 F32Op.CopySign)
 let f64_add = Binary (F64 F64Op.Add)
+let f64_add_zero_all = Binary (F64 F64Op.AddZeroAll)
 let f64_sub = Binary (F64 F64Op.Sub)
 let f64_mul = Binary (F64 F64Op.Mul)
 let f64_div = Binary (F64 F64Op.Div)
diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml
index dc56743..99bbfac 100644
--- a/interpreter/text/arrange.ml
+++ b/interpreter/text/arrange.ml
@@ -125,6 +125,7 @@ struct

   let binop xx = function
     | Add -> "add"
+    | AddZeroAll -> "add_zero_all"
     | Sub -> "sub"
     | Mul -> "mul"
     | DivS -> "div_s"
@@ -180,6 +181,7 @@ struct

   let binop xx = function
     | Add -> "add"
+    | AddZeroAll -> "add_zero_all"
     | Sub -> "sub"
     | Mul -> "mul"
     | Div -> "div"
diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll
index d9a12b5..4750e59 100644
--- a/interpreter/text/lexer.mll
+++ b/interpreter/text/lexer.mll
@@ -343,6 +343,7 @@ rule token = parse
       | "f32.max" -> BINARY f32_max
       | "f32.copysign" -> BINARY f32_copysign
       | "f64.add" -> BINARY f64_add
+      | "f64.add_zero_all" -> BINARY f64_add_zero_all
       | "f64.sub" -> BINARY f64_sub
       | "f64.mul" -> BINARY f64_mul
       | "f64.div" -> BINARY f64_div
diff --git a/test/core/float_rounding_variants.wast b/test/core/float_rounding_variants.wast
index 31463cf..8cd2100 100644
--- a/test/core/float_rounding_variants.wast
+++ b/test/core/float_rounding_variants.wast
@@ -1,5 +1,6 @@
 (module
   (func (export "f32.add") (param $x f32) (param $y f32) (result f32) (f32.add (local.get $x) (local.get $y)))
+  (func (export "f64.add_zero_all") (param $x f64) (param $y f64) (result f64) (f64.add_zero_all (local.get $x) (local.get $y)))
   (func (export "f32.sub") (param $x f32) (param $y f32) (result f32) (f32.sub (local.get $x) (local.get $y)))
   (func (export "f32.mul") (param $x f32) (param $y f32) (result f32) (f32.mul (local.get $x) (local.get $y)))
   (func (export "f32.div") (param $x f32) (param $y f32) (result f32) (f32.div (local.get $x) (local.get $y)))
@@ -15,6 +16,7 @@
   (func (export "f32.max") (param $x f32) (param $y f32) (result f32) (f32.max (local.get $x) (local.get $y)))

   (func (export "f64.add") (param $x f64) (param $y f64) (result f64) (f64.add (local.get $x) (local.get $y)))
+
   (func (export "f64.sub") (param $x f64) (param $y f64) (result f64) (f64.sub (local.get $x) (local.get $y)))
   (func (export "f64.mul") (param $x f64) (param $y f64) (result f64) (f64.mul (local.get $x) (local.get $y)))
   (func (export "f64.div") (param $x f64) (param $y f64) (result f64) (f64.div (local.get $x) (local.get $y)))
@@ -30,7 +32,9 @@
   (func (export "f64.max") (param $x f64) (param $y f64) (result f64) (f64.max (local.get $x) (local.get $y)))
 )

-
 ;; Rounding Variants (here comes the cow)
 (assert_return (invoke "f64.add" (f64.const 2.0) (f64.const 2.0)) (f64.const 4.0))
-;;(assert_return (invoke "f64.add_zero_all" (f64.const 1.7976931348623157e308) (f64.const 1.7976931348623157e308)) (f64.const 1.7976931348623157e308))
+
+(assert_return (invoke "f64.add_zero_all" (f64.const 1.7976931348623157e308) (f64.const 1.7976931348623157e308)) (f64.const 1.7976931348623157e308))
+
+;;(assert_return (invoke "f64.add_zero_all" (f64.const 1.0) (f64.const 1.0)) (f64.const 0.0))

Bonus chatter

Question: WASM uses something similar to abstract classes from Java. If I add fucntion to F64 (AddZeroAll), I need to add the same function to I64 and I32 and F32. What is the correct name for this "abstract class"?

Answer: The "abstract class" equivalent in OCaml for defining a common interface that different modules (like F64, I64, I32, F32) must adhere to is a module type (also known as a signature). Module types define the interface that a module must implement, similar to how an abstract class defines methods that must be implemented by subclasses. Functors in OCaml can then be used to create modules that conform to these interfaces.