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:
In interpreter/exec/eval_num.ml:- Replaced FXX.add with a lambda function
(fun _ _ -> FXX.zero)that ignores both input values and always returns FXX.zero. - This means the f64.add operation will now always return 0 regardless of the input values.
- Replaced FXX.add with a lambda function
In
test/core/float_rounding_variants.wast:- Updated the test case for f64.add to expect 0 as the result instead of the sum of the inputs.
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:
AST Definition (
interpreter/syntax/ast.ml):- Added
AddZeroAllto thebinoptype. - This defines the new operation in the abstract syntax tree (AST), making it a recognized operation in the interpreter.
- Added
Operators (
interpreter/syntax/operators.ml):- Added new operator definitions for
i32_add_zero_all,i64_add_zero_all,f32_add_zero_all, andf64_add_zero_all. - We must add them, because software architecture requires this.
- These definitions map the new operation to the corresponding binary operations in the AST.
- Added new operator definitions for
Binary Encoding/Decoding (
interpreter/binary/decode.mlandinterpreter/binary/encode.ml):- This is so that the interpreter understands instructions coded in machine language.
- Added a new opcode (0xff) for the f64.addzeroall operation. The
0xffis arbitrary, and must not collide with other instructions. - Updated the binary encoder to include the new operation for all numeric types (
i32, i64, f32, f64).
Evaluation Logic (
interpreter/exec/eval_num.ml):- Implemented the behavior of the AddZeroAll operation in the evaluation logic for both integer and floating-point types. Again, we must do it for both integers and floats.
- For now, the implementation simply returns zero for the operation, but this can be updated to perform some logic. It would be fun to have a function which returns the right result from time to time.
- Test Cases (
test/core/float_rounding_variants.wast):- Added a new test case for the
f64.add_zero_alloperation. - This ensures the operation is tested and verified to work correctly in the interpreter.
- Added a new test case for the
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.
welw