diff --git a/src/expression.c b/src/expression.c index 5f6e189..00b3d3b 100644 --- a/src/expression.c +++ b/src/expression.c @@ -1884,6 +1884,117 @@ caml_binaryen_ref_eq(value _module, value _left, value _right) { CAMLreturn(alloc_BinaryenExpressionRef(exp)); } +// Exception handling operations +CAMLprim value +caml_binaryen_try_native(value _module, value _name, value _body, value _catchTags, value _catchBodies, value _delegateTarget) { + CAMLparam5(_module, _name, _body, _catchTags, _catchBodies); + CAMLxparam1(_delegateTarget); + BinaryenModuleRef module = BinaryenModuleRef_val(_module); + char *name; + if (Is_none(_name)) { + name = NULL; + } else { + name = Safe_String_val(Some_val(_name)); + } + BinaryenExpressionRef body = BinaryenExpressionRef_val(_body); + _catchTags = array_of_list(_catchTags); + int catchTagsLen = array_length(_catchTags); + const char* catchTags[catchTagsLen]; + for (int i = 0; i < catchTagsLen; i++) { + catchTags[i] = Safe_String_val(Field(_catchTags, i)); + } + _catchBodies = array_of_list(_catchBodies); + int catchBodiesLen = array_length(_catchBodies); + BinaryenExpressionRef catchBodies[catchBodiesLen]; + for (int i = 0; i < catchBodiesLen; i++) { + catchBodies[i] = BinaryenExpressionRef_val(Field(_catchBodies, i)); + } + char *delegateTarget; + if (Is_none(_delegateTarget)) { + delegateTarget = NULL; + } else { + delegateTarget = Safe_String_val(Some_val(_delegateTarget)); + } + BinaryenExpressionRef exp = BinaryenTry(module, name, body, catchTags, catchTagsLen, catchBodies, catchBodiesLen, delegateTarget); + CAMLreturn(alloc_BinaryenExpressionRef(exp)); +} + +CAMLprim value caml_binaryen_try_bytecode(value *argv, int argn) { + return caml_binaryen_try_native(argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); +} + +CAMLprim value +caml_binaryen_trycatch(value _module, value _name, value _body, value _catchTags, value _catchBodies) { + CAMLparam5(_module, _name, _body, _catchTags, _catchBodies); + BinaryenModuleRef module = BinaryenModuleRef_val(_module); + char *name; + if (Is_none(_name)) { + name = NULL; + } else { + name = Safe_String_val(Some_val(_name)); + } + BinaryenExpressionRef body = BinaryenExpressionRef_val(_body); + _catchTags = array_of_list(_catchTags); + int catchTagsLen = array_length(_catchTags); + const char* catchTags[catchTagsLen]; + for (int i = 0; i < catchTagsLen; i++) { + catchTags[i] = Safe_String_val(Field(_catchTags, i)); + } + _catchBodies = array_of_list(_catchBodies); + int catchBodiesLen = array_length(_catchBodies); + BinaryenExpressionRef catchBodies[catchBodiesLen]; + for (int i = 0; i < catchBodiesLen; i++) { + catchBodies[i] = BinaryenExpressionRef_val(Field(_catchBodies, i)); + } + char *delegateTarget = NULL; + BinaryenExpressionRef exp = BinaryenTry(module, name, body, catchTags, catchTagsLen, catchBodies, catchBodiesLen, delegateTarget); + CAMLreturn(alloc_BinaryenExpressionRef(exp)); +} + +CAMLprim value +caml_binaryen_trydelegate(value _module, value _name, value _body, value _delegateTarget) { + CAMLparam4(_module, _name, _body,_delegateTarget); + BinaryenModuleRef module = BinaryenModuleRef_val(_module); + char *name; + if (Is_none(_name)) { + name = NULL; + } else { + name = Safe_String_val(Some_val(_name)); + } + BinaryenExpressionRef body = BinaryenExpressionRef_val(_body); + char *delegateTarget = Safe_String_val(_delegateTarget); + int catchTagsLen = 0; + int catchBodiesLen = 0; + const char *catchTags[1] = {NULL}; + BinaryenExpressionRef catchBodies[1] = {NULL}; + BinaryenExpressionRef exp = BinaryenTry(module, name, body, catchTags, catchTagsLen, catchBodies, catchBodiesLen, delegateTarget); + CAMLreturn(alloc_BinaryenExpressionRef(exp)); +} + +CAMLprim value +caml_binaryen_throw(value _module, value _tag, value _operands) { + CAMLparam3(_module, _tag, _operands); + BinaryenModuleRef module = BinaryenModuleRef_val(_module); + char *tag = Safe_String_val(_tag); + _operands = array_of_list(_operands); + int operandsLen = array_length(_operands); + BinaryenExpressionRef operands[operandsLen]; + for (int i = 0; i < operandsLen; i++) { + operands[i] = BinaryenExpressionRef_val(Field(_operands, i)); + } + BinaryenExpressionRef exp = BinaryenThrow(module, tag, operands, operandsLen); + CAMLreturn(alloc_BinaryenExpressionRef(exp)); +} + +CAMLprim value +caml_binaryen_rethrow(value _module, value _target) { + CAMLparam2(_module, _target); + BinaryenModuleRef module = BinaryenModuleRef_val(_module); + char *target = Safe_String_val(_target); + BinaryenExpressionRef exp = BinaryenRethrow(module, target); + CAMLreturn(alloc_BinaryenExpressionRef(exp)); +} + // Table operations CAMLprim value caml_binaryen_table_get(value _module, value _name, value _index, value _ty) { diff --git a/src/expression.js b/src/expression.js index b19c5ed..d292192 100644 --- a/src/expression.js +++ b/src/expression.js @@ -1706,6 +1706,67 @@ function caml_binaryen_ref_eq(wasm_mod, left, right) { return wasm_mod.ref.func(left, right); } +// Exception handling operations + +//Provides: caml_binaryen_try_native +//Requires: caml_jsstring_of_string, caml_list_to_js_array +function caml_binaryen_try_native(wasm_mod, name, body, catch_tags, catch_bodies, delegate_target) { + return wasm_mod.try( + caml_jsstring_of_string(name), + body, + caml_list_to_js_array(catch_tags).map(caml_jsstring_of_string), + caml_list_to_js_array(catch_bodies), + delegate_target ? caml_jsstring_of_string(delegate_target[1]) : null, + ); +} + +//Provides: caml_binaryen_try_bytecode +//Requires: caml_binaryen_try_native +function caml_binaryen_try_bytecode() { + return caml_binaryen_try_native(arguments[0], arguments[1], arguments[2], arguments[3], arguments[4], arguments[5]); +} + +//Provides: caml_binaryen_trycatch +//Requires: caml_jsstring_of_string, caml_list_to_js_array +function caml_binaryen_trycatch(wasm_mod, name, body, catch_tags, catch_bodies) { + return wasm_mod.try( + caml_jsstring_of_string(name), + body, + caml_list_to_js_array(catch_tags).map(caml_jsstring_of_string), + caml_list_to_js_array(catch_bodies), + null, + ); +} + +//Provides: caml_binaryen_trydelegate +//Requires: caml_jsstring_of_string, caml_list_to_js_array +function caml_binaryen_trydelegate(wasm_mod, name, body, delegate_target) { + return wasm_mod.try( + caml_jsstring_of_string(name), + body, + [], + [], + delegate_target ? caml_jsstring_of_string(delegate_target[1]) : null, + ); +} + +//Provides: caml_binaryen_throw +//Requires: caml_jsstring_of_string, caml_list_to_js_array +function caml_binaryen_throw(wasm_mod, tag, operands) { + return wasm_mod.throw( + caml_jsstring_of_string(tag), + caml_list_to_js_array(operands), + ); +} + +//Provides: caml_binaryen_rethrow +//Requires: caml_jsstring_of_string, caml_list_to_js_array +function caml_binaryen_rethrow(wasm_mod, target) { + return wasm_mod.rethrow( + caml_jsstring_of_string(target), + ) +} + // Table operations //Provides: caml_binaryen_table_get diff --git a/src/expression.ml b/src/expression.ml index b0fceee..cc0a4a9 100644 --- a/src/expression.ml +++ b/src/expression.ml @@ -849,6 +849,32 @@ module Ref = struct (** Module, left, right *) end +(** Bindings for `try` instruction. For better validation, use `Try_catch` or `Try_Delegate`. *) +module Try = struct + external make : Module.t -> string option -> t -> string list -> t list -> string option -> t = "caml_binaryen_try_bytecode" "caml_binaryen_try_native" + (** Module, name, body, catch tags, catch bodies, delegate target *) +end + +module Try_Catch = struct + external make : Module.t -> string option -> t -> string list -> t list -> t = "caml_binaryen_trycatch" + (** Module, name, body, catch tags, catch bodies *) +end + +module Try_Delegate = struct + external make : Module.t -> string option -> t -> string -> t = "caml_binaryen_trydelegate" + (** Module, name, body, delegate *) +end + +module Throw = struct + external make : Module.t -> string -> t list -> t = "caml_binaryen_throw" + (** Module, tag, operands *) +end + +module Rethrow = struct + external make : Module.t -> string -> t = "caml_binaryen_rethrow" + (** Module, target *) +end + module Table = struct external get : Module.t -> string -> t -> Type.t -> t = "caml_binaryen_table_get" diff --git a/src/expression.mli b/src/expression.mli index c22e181..d916345 100644 --- a/src/expression.mli +++ b/src/expression.mli @@ -350,6 +350,32 @@ module Ref : sig (** Module, left, right *) end +(** Bindings for `try` instruction. For better validation, use `Try_catch` or `Try_Delegate`. *) +module Try : sig + val make : Module.t -> string option -> t -> string list -> t list -> string option -> t + (** Module, name, body, catch tags, catch bodies, delegate target *) +end + +module Try_Catch : sig + val make : Module.t -> string option -> t -> string list -> t list -> t + (** Module, name, body, catch tags, catch bodies *) +end + +module Try_Delegate : sig + val make : Module.t -> string option -> t -> string -> t + (** Module, name, body, delegate *) +end + +module Throw : sig + val make : Module.t -> string -> t list -> t + (** Module, tag, operands *) +end + +module Rethrow : sig + val make : Module.t -> string -> t + (** Module, target *) +end + module Table : sig val get : Module.t -> string -> t -> Type.t -> t (** Module, name, index, type *) diff --git a/test/test.expected b/test/test.expected index d65130c..2a3b653 100644 --- a/test/test.expected +++ b/test/test.expected @@ -6,6 +6,64 @@ (ref.null nofunc) (i32.const 0) ) +(try $tc1 (result i32) + (do + (throw $foo + (i32.const 1) + ) + ) + (catch $foo + (i32.const 2) + ) + (catch $bar + (i32.const 3) + ) +) +(try $tc2 (result i32) + (do + (rethrow $foo) + ) + (catch $foo + (i32.const 2) + ) + (catch $bar + (i32.const 3) + ) +) +(try $tc3 (result i32) + (do + (i32.const 1) + ) + (catch $foo + (i32.const 2) + ) + (catch_all + (i32.const 3) + ) +) +(try $tc4 (result i32) + (do + (i32.const 1) + ) + (catch $foo + (i32.const 2) + ) + (catch_all + (i32.const 3) + ) +) +(try $td1 (result i32) + (do + (i32.const 1) + ) + (delegate $del1) +) +(try $td2 (result i32) + (do + (i32.const 1) + ) + (delegate $del1) +) (module (type $i32_i32_=>_i32 (func (param i32 i32) (result i32))) (type $none_=>_none (func)) diff --git a/test/test.ml b/test/test.ml index b53eba0..0aa879e 100644 --- a/test/test.ml +++ b/test/test.ml @@ -185,6 +185,50 @@ let _ = assert ( Bytes.equal (Memory.get_segment_data wasm_mod 1) (Bytes.of_string "world")) +(* Exception handling *) +let try_catch_1 = + Expression.Try.make wasm_mod (Some "tc1") + (Expression.Throw.make wasm_mod "foo" [Expression.Const.make wasm_mod (Literal.int32 1l)]) + ["foo"; "bar"] + [Expression.Const.make wasm_mod (Literal.int32 2l); Expression.Const.make wasm_mod (Literal.int32 3l)] + None +let try_catch_2 = + Expression.Try_Catch.make wasm_mod (Some "tc2") + (Expression.Rethrow.make wasm_mod "foo") + ["foo"; "bar"] + [Expression.Const.make wasm_mod (Literal.int32 2l); Expression.Const.make wasm_mod (Literal.int32 3l)] + +(* One more catch-body than catch-tag; last body becomes the catch_all *) +let try_catch_all_1 = + Expression.Try.make wasm_mod (Some "tc3") + (Expression.Const.make wasm_mod (Literal.int32 1l)) + ["foo"] + [Expression.Const.make wasm_mod (Literal.int32 2l); Expression.Const.make wasm_mod (Literal.int32 3l)] + None +let try_catch_all_2 = + Expression.Try_Catch.make wasm_mod (Some "tc4") + (Expression.Const.make wasm_mod (Literal.int32 1l)) + ["foo"] + [Expression.Const.make wasm_mod (Literal.int32 2l); Expression.Const.make wasm_mod (Literal.int32 3l)] + +let try_delegate_1 = + Expression.Try.make wasm_mod (Some "td1") + (Expression.Const.make wasm_mod (Literal.int32 1l)) + [] + [] + (Some "del1") +let try_delegate_2 = + Expression.Try_Delegate.make wasm_mod (Some "td2") + (Expression.Const.make wasm_mod (Literal.int32 1l)) + "del1" + +let _ = Expression.print try_catch_1 +let _ = Expression.print try_catch_2 +let _ = Expression.print try_catch_all_1 +let _ = Expression.print try_catch_all_2 +let _ = Expression.print try_delegate_1 +let _ = Expression.print try_delegate_2 + (* Create an imported "write" function i32 (externref, i32, i32) *) (* Similar to the example here: https://bytecodealliance.org/articles/reference-types-in-wasmtime *)