Skip to content
This repository was archived by the owner on Apr 25, 2025. It is now read-only.

Commit 1f62ccf

Browse files
[interpreter] Parse and convert EH opcodes
Add support for EH opcodes in the parser, AST, encoder, decoder and formatter, and add spec tests. This can already be used to convert the tests to JS, but not run them with the interpreter yet since validation and execution are still missing.
1 parent a5e87e4 commit 1f62ccf

17 files changed

+652
-7
lines changed

interpreter/binary/decode.ml

+37-3
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,30 @@ let rec instr s =
246246
end
247247

248248
| 0x05 -> error s pos "misplaced ELSE opcode"
249-
| 0x06| 0x07 | 0x08 | 0x09 | 0x0a as b -> illegal s pos b
249+
| 0x06 ->
250+
let bt = block_type s in
251+
let es = instr_block s in
252+
let ct = catch_list s in
253+
let ca =
254+
if peek s = Some 0x19 then begin
255+
ignore (u8 s);
256+
Some (instr_block s)
257+
end else
258+
None
259+
in
260+
if ct <> [] || ca <> None then begin
261+
end_ s;
262+
try_catch bt es ct ca
263+
end else begin
264+
match op s with
265+
| 0x0b -> try_catch bt es [] None
266+
| 0x18 -> try_delegate bt es (at var s)
267+
| b -> illegal s pos b
268+
end
269+
| 0x07 -> error s pos "misplaced CATCH opcode"
270+
| 0x08 -> throw (at var s)
271+
| 0x09 -> rethrow (at var s)
272+
| 0x0a as b -> illegal s pos b
250273
| 0x0b -> error s pos "misplaced END opcode"
251274

252275
| 0x0c -> br (at var s)
@@ -263,7 +286,10 @@ let rec instr s =
263286
let x = at var s in
264287
call_indirect x y
265288

266-
| 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 as b -> illegal s pos b
289+
| 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 as b -> illegal s pos b
290+
291+
| 0x18 -> error s pos "misplaced DELEGATE opcode"
292+
| 0x19 -> error s pos "misplaced CATCH_ALL opcode"
267293

268294
| 0x1a -> drop
269295
| 0x1b -> select None
@@ -499,11 +525,19 @@ let rec instr s =
499525
and instr_block s = List.rev (instr_block' s [])
500526
and instr_block' s es =
501527
match peek s with
502-
| None | Some (0x05 | 0x0b) -> es
528+
| None | Some (0x05 | 0x07 | 0x0a | 0x0b | 0x18 | 0x19) -> es
503529
| _ ->
504530
let pos = pos s in
505531
let e' = instr s in
506532
instr_block' s (Source.(e' @@ region s pos pos) :: es)
533+
and catch_list s =
534+
if peek s = Some 0x07 then begin
535+
ignore (u8 s);
536+
let tag = at var s in
537+
let instrs = instr_block s in
538+
(tag, instrs) :: catch_list s
539+
end else
540+
[]
507541

508542
let const s =
509543
let c = at instr_block s in

interpreter/binary/encode.ml

+16-1
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,28 @@ let encode m =
156156
op 0x04; block_type bt; list instr es1;
157157
if es2 <> [] then op 0x05;
158158
list instr es2; end_ ()
159-
159+
| TryCatch (bt, es, ct, ca) ->
160+
op 0x06; block_type bt; list instr es;
161+
let catch (tag, es) =
162+
op 0x07; var tag; list instr es
163+
in
164+
list catch ct;
165+
begin match ca with
166+
| None -> ()
167+
| Some es -> op 0x19; list instr es
168+
end;
169+
end_ ()
170+
| TryDelegate (bt, es, x) ->
171+
op 0x06; block_type bt; list instr es;
172+
op 0x18; var x
160173
| Br x -> op 0x0c; var x
161174
| BrIf x -> op 0x0d; var x
162175
| BrTable (xs, x) -> op 0x0e; vec var xs; var x
163176
| Return -> op 0x0f
164177
| Call x -> op 0x10; var x
165178
| CallIndirect (x, y) -> op 0x11; var y; var x
179+
| Throw x -> op 0x08; var x
180+
| Rethrow x -> op 0x09; var x
166181

167182
| Drop -> op 0x1a
168183
| Select None -> op 0x1b

interpreter/script/js.ml

+7
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ function assert_trap(action) {
135135
throw new Error("Wasm trap expected");
136136
}
137137

138+
function assert_exception(action) {
139+
try { action() } catch (e) { return; }
140+
throw new Error("exception expected");
141+
}
142+
138143
let StackOverflow;
139144
try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor }
140145

@@ -508,6 +513,8 @@ let of_assertion mods ass =
508513
of_assertion' mods act "assert_trap" [] None
509514
| AssertExhaustion (act, _) ->
510515
of_assertion' mods act "assert_exhaustion" [] None
516+
| AssertUncaughtException act ->
517+
of_assertion' mods act "assert_exception" [] None
511518

512519
let of_command mods cmd =
513520
"\n// " ^ Filename.basename cmd.at.left.file ^

interpreter/script/run.ml

+2
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,8 @@ let run_assertion ass =
458458
| _ -> Assert.error ass.at "expected runtime error"
459459
)
460460

461+
| AssertUncaughtException act -> () (* TODO *)
462+
461463
| AssertExhaustion (act, re) ->
462464
trace ("Asserting exhaustion...");
463465
(match run_action act with

interpreter/script/script.ml

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ and assertion' =
3232
| AssertUninstantiable of definition * string
3333
| AssertReturn of action * result list
3434
| AssertTrap of action * string
35+
| AssertUncaughtException of action
3536
| AssertExhaustion of action * string
3637

3738
type command = command' Source.phrase

interpreter/syntax/ast.ml

+7
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ and instr' =
114114
| Unary of unop (* unary numeric operator *)
115115
| Binary of binop (* binary numeric operator *)
116116
| Convert of cvtop (* conversion *)
117+
| TryCatch of block_type * instr list * (* try *)
118+
(var * instr list) list * (* catch exception with tag *)
119+
instr list option (* catch_all *)
120+
| TryDelegate of block_type * instr list * (* try *)
121+
var (* delegate to outer handler *)
122+
| Throw of var (* throw exception *)
123+
| Rethrow of var (* rethrow exception *)
117124

118125

119126
(* Globals & Functions *)

interpreter/syntax/free.ml

+9
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ let rec instr (e : instr) =
8787
memories zero
8888
| MemoryInit x -> memories zero ++ datas (var x)
8989
| DataDrop x -> datas (var x)
90+
| TryCatch (bt, es, ct, ca) ->
91+
let catch (tag, es) = events (var tag) ++ block es in
92+
let catch_all = function
93+
| None -> empty
94+
| Some es -> block es in
95+
block es ++ (list catch ct) ++ catch_all ca
96+
| TryDelegate (bt, es, x) -> block es ++ events (var x)
97+
| Throw x -> events (var x)
98+
| Rethrow x -> labels (var x)
9099

91100
and block (es : instr list) =
92101
let free = list instr es in {free with labels = shift free.labels}

interpreter/syntax/operators.ml

+4
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ let select t = Select t
1919
let block bt es = Block (bt, es)
2020
let loop bt es = Loop (bt, es)
2121
let if_ bt es1 es2 = If (bt, es1, es2)
22+
let try_catch bt es ct ca = TryCatch (bt, es, ct, ca)
23+
let try_delegate bt es x = TryDelegate (bt, es, x)
2224
let br x = Br x
2325
let br_if x = BrIf x
2426
let br_table xs x = BrTable (xs, x)
2527

2628
let return = Return
2729
let call x = Call x
2830
let call_indirect x y = CallIndirect (x, y)
31+
let throw x = Throw x
32+
let rethrow x = Rethrow x
2933

3034
let local_get x = LocalGet x
3135
let local_set x = LocalSet x

interpreter/text/arrange.ml

+14
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,18 @@ let rec instr e =
279279
| Unary op -> unop op, []
280280
| Binary op -> binop op, []
281281
| Convert op -> cvtop op, []
282+
| TryCatch (bt, es, ct, ca) ->
283+
let catch (tag, es) = Node ("catch " ^ var tag, list instr es) in
284+
let catch_all = match ca with
285+
| Some es -> [Node ("catch_all", list instr es)]
286+
| None -> [] in
287+
let handler = list catch ct @ catch_all in
288+
"try", block_type bt @ [Node ("do", list instr es)] @ handler
289+
| TryDelegate (bt, es, x) ->
290+
let delegate = [Node ("delegate " ^ var x, [])] in
291+
"try", block_type bt @ [Node ("do", list instr es)] @ delegate
292+
| Throw x -> "throw " ^ var x, []
293+
| Rethrow x -> "rethrow " ^ var x, []
282294
in Node (head, inner)
283295

284296
let const head c =
@@ -538,6 +550,8 @@ let assertion mode ass =
538550
[Node ("assert_return", action mode act :: List.map (result mode) results)]
539551
| AssertTrap (act, re) ->
540552
[Node ("assert_trap", [action mode act; Atom (string re)])]
553+
| AssertUncaughtException act ->
554+
[Node ("assert_exception", [action mode act])]
541555
| AssertExhaustion (act, re) ->
542556
[Node ("assert_exhaustion", [action mode act; Atom (string re)])]
543557

interpreter/text/lexer.mll

+9
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,14 @@ rule token = parse
348348
| "i32.reinterpret_f32" { CONVERT i32_reinterpret_f32 }
349349
| "i64.reinterpret_f64" { CONVERT i64_reinterpret_f64 }
350350

351+
| "try" { TRY }
352+
| "do" { DO }
353+
| "catch" { CATCH }
354+
| "catch_all" { CATCH_ALL }
355+
| "delegate" { DELEGATE }
356+
| "throw" { THROW }
357+
| "rethrow" { RETHROW }
358+
351359
| "type" { TYPE }
352360
| "func" { FUNC }
353361
| "start" { START }
@@ -379,6 +387,7 @@ rule token = parse
379387
| "assert_unlinkable" { ASSERT_UNLINKABLE }
380388
| "assert_return" { ASSERT_RETURN }
381389
| "assert_trap" { ASSERT_TRAP }
390+
| "assert_exception" { ASSERT_EXCEPTION }
382391
| "assert_exhaustion" { ASSERT_EXHAUSTION }
383392
| "nan:canonical" { NAN Script.CanonicalNan }
384393
| "nan:arithmetic" { NAN Script.ArithmeticNan }

interpreter/text/parser.mly

+88-3
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ let func_type (c : context) x =
112112
try (Lib.List32.nth c.types.list x.it).it
113113
with Failure _ -> error x.at ("unknown type " ^ Int32.to_string x.it)
114114

115+
let handlers (c : context) h =
116+
List.map (fun (l, i) -> (l c event, i c)) h
115117

116118
let anon category space n =
117119
let i = space.count in
@@ -179,7 +181,8 @@ let inline_type_explicit (c : context) x ft at =
179181
%token NAT INT FLOAT STRING VAR
180182
%token NUM_TYPE FUNCREF EXTERNREF EXTERN MUT
181183
%token UNREACHABLE NOP DROP SELECT
182-
%token BLOCK END IF THEN ELSE LOOP BR BR_IF BR_TABLE
184+
%token BLOCK END IF THEN ELSE LOOP BR BR_IF BR_TABLE TRY DO CATCH CATCH_ALL
185+
%token DELEGATE
183186
%token CALL CALL_INDIRECT RETURN
184187
%token LOCAL_GET LOCAL_SET LOCAL_TEE GLOBAL_GET GLOBAL_SET
185188
%token TABLE_GET TABLE_SET
@@ -188,12 +191,13 @@ let inline_type_explicit (c : context) x ft at =
188191
%token LOAD STORE OFFSET_EQ_NAT ALIGN_EQ_NAT
189192
%token CONST UNARY BINARY TEST COMPARE CONVERT
190193
%token REF_NULL REF_FUNC REF_EXTERN REF_IS_NULL
194+
%token THROW RETHROW
191195
%token FUNC START TYPE PARAM RESULT LOCAL GLOBAL
192196
%token TABLE ELEM MEMORY EVENT DATA DECLARE OFFSET ITEM IMPORT EXPORT
193197
%token MODULE BIN QUOTE
194198
%token SCRIPT REGISTER INVOKE GET
195199
%token ASSERT_MALFORMED ASSERT_INVALID ASSERT_SOFT_INVALID ASSERT_UNLINKABLE
196-
%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXHAUSTION
200+
%token ASSERT_RETURN ASSERT_TRAP ASSERT_EXCEPTION ASSERT_EXHAUSTION
197201
%token NAN
198202
%token INPUT OUTPUT
199203
%token EOF
@@ -358,6 +362,8 @@ plain_instr :
358362
br_table xs x }
359363
| RETURN { fun c -> return }
360364
| CALL var { fun c -> call ($2 c func) }
365+
| THROW var { fun c -> throw ($2 c event) }
366+
| RETHROW var { fun c -> rethrow ($2 c label) }
361367
| LOCAL_GET var { fun c -> local_get ($2 c local) }
362368
| LOCAL_SET var { fun c -> local_set ($2 c local) }
363369
| LOCAL_TEE var { fun c -> local_tee ($2 c local) }
@@ -398,7 +404,6 @@ plain_instr :
398404
| BINARY { fun c -> $1 }
399405
| CONVERT { fun c -> $1 }
400406

401-
402407
select_instr :
403408
| SELECT select_instr_results
404409
{ let at = at () in fun c -> let b, ts = $2 in
@@ -495,6 +500,12 @@ block_instr :
495500
| IF labeling_opt block ELSE labeling_end_opt instr_list END labeling_end_opt
496501
{ fun c -> let c' = $2 c ($5 @ $8) in
497502
let ts, es1 = $3 c' in if_ ts es1 ($6 c') }
503+
| TRY labeling_opt block handler_instr
504+
{ fun c -> let c' = $2 c [] in
505+
let ts, es = $3 c' in $4 ts es c' }
506+
| TRY labeling_opt block DELEGATE var
507+
{ fun c -> let c' = $2 c [] in
508+
let ts, es = $3 c' in try_delegate ts es ($5 c label) }
498509

499510
block :
500511
| type_use block_param_body
@@ -524,6 +535,44 @@ block_result_body :
524535
{ let FuncType (ins, out) = fst $5 in
525536
FuncType (ins, $3 @ out), snd $5 }
526537

538+
handler_instr :
539+
| catch_list_instr END
540+
{ fun bt es c -> try_catch bt es (handlers c $1) None }
541+
| catch_list_instr catch_all END
542+
{ fun bt es c -> try_catch bt es (handlers c $1) (Some ($2 c)) }
543+
| catch_all END
544+
{ fun bt es c -> try_catch bt es [] (Some ($1 c)) }
545+
| END { fun bt es c -> try_catch bt es [] None }
546+
547+
catch_list_instr :
548+
| catch catch_list_instr { $1 :: $2 }
549+
| catch { [$1] }
550+
551+
handler :
552+
| catch_list
553+
{ fun bt es _ c' ->
554+
let cs = (List.map (fun (l, i) -> (l c' event, i c')) $1) in
555+
try_catch bt es cs None }
556+
| catch_list LPAR catch_all RPAR
557+
{ fun bt es _ c' ->
558+
let cs = (List.map (fun (l, i) -> (l c' event, i c')) $1) in
559+
try_catch bt es cs (Some ($3 c')) }
560+
| LPAR catch_all RPAR
561+
{ fun bt es _ c' -> try_catch bt es [] (Some ($2 c')) }
562+
| LPAR DELEGATE var RPAR
563+
{ fun bt es c _ -> try_delegate bt es ($3 c label) }
564+
| /* empty */ { fun bt es c _ -> try_catch bt es [] None }
565+
566+
catch_list :
567+
| catch_list LPAR catch RPAR { $1 @ [$3] }
568+
| LPAR catch RPAR { [$2] }
569+
570+
catch :
571+
| CATCH var instr_list { ($2, $3) }
572+
573+
catch_all :
574+
| CATCH_ALL instr_list { $2 }
575+
527576

528577
expr : /* Sugar */
529578
| LPAR expr1 RPAR
@@ -545,6 +594,8 @@ expr1 : /* Sugar */
545594
| IF labeling_opt if_block
546595
{ fun c -> let c' = $2 c [] in
547596
let bt, (es, es1, es2) = $3 c c' in es, if_ bt es1 es2 }
597+
| TRY labeling_opt try_block
598+
{ fun c -> let c' = $2 c [] in [], $3 c c' }
548599

549600
select_expr_results :
550601
| LPAR RESULT value_type_list RPAR select_expr_results
@@ -614,6 +665,38 @@ if_ :
614665
| LPAR THEN instr_list RPAR /* Sugar */
615666
{ fun c c' -> [], $3 c', [] }
616667

668+
try_block :
669+
| type_use try_block_param_body
670+
{ let at = at () in
671+
fun c c' ->
672+
let bt = VarBlockType (inline_type_explicit c' ($1 c' type_) (fst $2) at) in
673+
snd $2 bt c c' }
674+
| try_block_param_body /* Sugar */
675+
{ let at = at () in
676+
fun c c' ->
677+
let bt =
678+
match fst $1 with
679+
| FuncType ([], []) -> ValBlockType None
680+
| FuncType ([], [t]) -> ValBlockType (Some t)
681+
| ft -> VarBlockType (inline_type c' ft at)
682+
in snd $1 bt c c' }
683+
684+
try_block_param_body :
685+
| try_block_result_body { $1 }
686+
| LPAR PARAM value_type_list RPAR try_block_param_body
687+
{ let FuncType (ins, out) = fst $5 in
688+
FuncType ($3 @ ins, out), snd $5 }
689+
690+
try_block_result_body :
691+
| try_ { FuncType ([], []), $1 }
692+
| LPAR RESULT value_type_list RPAR try_block_result_body
693+
{ let FuncType (ins, out) = fst $5 in
694+
FuncType (ins, $3 @ out), snd $5 }
695+
696+
try_ :
697+
| LPAR DO instr_list RPAR handler
698+
{ fun bt c c' -> $5 bt ($3 c') c c' }
699+
617700
instr_list :
618701
| /* empty */ { fun c -> [] }
619702
| select_instr { fun c -> [$1 c] }
@@ -1085,6 +1168,8 @@ assertion :
10851168
{ AssertUninstantiable (snd $3, $4) @@ at () }
10861169
| LPAR ASSERT_RETURN action result_list RPAR { AssertReturn ($3, $4) @@ at () }
10871170
| LPAR ASSERT_TRAP action STRING RPAR { AssertTrap ($3, $4) @@ at () }
1171+
| LPAR ASSERT_EXCEPTION action RPAR
1172+
{ AssertUncaughtException $3 @@ at () }
10881173
| LPAR ASSERT_EXHAUSTION action STRING RPAR { AssertExhaustion ($3, $4) @@ at () }
10891174

10901175
cmd :

0 commit comments

Comments
 (0)