Skip to content

Commit

Permalink
feat: Add bindings for exception handling (try/catch/throw/rethrow)
Browse files Browse the repository at this point in the history
  • Loading branch information
peblair committed Oct 15, 2023
1 parent 74c8ce4 commit 71f8b91
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 0 deletions.
111 changes: 111 additions & 0 deletions src/expression.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(_name);
}
BinaryenExpressionRef body = BinaryenExpressionRef_val(_body);
_catchTags = array_of_list(_catchTags);
int catchTagsLen = array_length(_catchTags);
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_native(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(_name);
}
BinaryenExpressionRef body = BinaryenExpressionRef_val(_body);
_catchTags = array_of_list(_catchTags);
int catchTagsLen = array_length(_catchTags);
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(_name);
}
BinaryenExpressionRef body = BinaryenExpressionRef_val(_body);
char *delegateTarget = Safe_String_val(Some_val(_delegateTarget));
int catchTagsLen = 0;
int catchBodiesLen = 0;
char **catchTags = NULL;
BinaryenExpressionRef *catchBodies = 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);
char* operands[operandsLen];
for (int i = 0; i < operandsLen; i++) {
operands[i] = Safe_String_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) {
Expand Down
55 changes: 55 additions & 0 deletions src/expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -1706,6 +1706,61 @@ function caml_binaryen_ref_eq(wasm_mod, left, right) {
return wasm_mod.ref.func(left, right);
}

// Exception handling operations

//Provides: caml_binaryen_try
//Requires: caml_jsstring_of_string, caml_list_to_js_array
function caml_binaryen_try(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_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
Expand Down
26 changes: 26 additions & 0 deletions src/expression.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
26 changes: 26 additions & 0 deletions src/expression.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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 *)
Expand Down
31 changes: 31 additions & 0 deletions test/test.expected
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,37 @@
(ref.null nofunc)
(i32.const 0)
)
(try $tc1
(throw $foo
(i32.const 1))
(catch $foo
(i32.const 2))
(catch $bar
(i32.const 3)))
(try $tc2
(rethrow $foo)
(catch $foo
(i32.const 2))
(catch $bar
(i32.const 3)))
(try $tc3
(i32.const 1)
(catch $foo
(i32.const 2))
(catch_all
(i32.const 3)))
(try $tc4
(i32.const 1)
(catch $foo
(i32.const 2))
(catch_all
(i32.const 3)))
(try $td1
(i32.const 1)
(delegate $del1))
(try $td2
(i32.const 1)
(delegate $del1))
(module
(type $i32_i32_=>_i32 (func (param i32 i32) (result i32)))
(type $none_=>_none (func))
Expand Down
44 changes: 44 additions & 0 deletions test/test.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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 *)

Expand Down

0 comments on commit 71f8b91

Please sign in to comment.