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

Commit b0d52fb

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 b0d52fb

File tree

11 files changed

+627
-5
lines changed

11 files changed

+627
-5
lines changed

interpreter/binary/decode.ml

+40-3
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,15 @@ 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 h = handler s in
253+
try_ bt es' h
254+
| 0x07 -> error s pos "misplaced CATCH opcode"
255+
| 0x08 -> throw (at var s)
256+
| 0x09 -> rethrow (at var s)
257+
| 0x0a as b -> illegal s pos b
250258
| 0x0b -> error s pos "misplaced END opcode"
251259

252260
| 0x0c -> br (at var s)
@@ -263,7 +271,9 @@ let rec instr s =
263271
let x = at var s in
264272
call_indirect x y
265273

266-
| 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19 as b -> illegal s pos b
274+
| 0x12 | 0x13 | 0x14 | 0x15 | 0x16 | 0x17 as b -> illegal s pos b
275+
| 0x18 -> error s pos "misplaced DELEGATE opcode"
276+
| 0x19 -> error s pos "misplaced CATCH_ALL opcode"
267277

268278
| 0x1a -> drop
269279
| 0x1b -> select None
@@ -499,12 +509,39 @@ let rec instr s =
499509
and instr_block s = List.rev (instr_block' s [])
500510
and instr_block' s es =
501511
match peek s with
502-
| None | Some (0x05 | 0x0b) -> es
512+
| None | Some (0x05 | 0x07 | 0x0a | 0x0b | 0x18 | 0x19) -> es
503513
| _ ->
504514
let pos = pos s in
505515
let e' = instr s in
506516
instr_block' s (Source.(e' @@ region s pos pos) :: es)
507517

518+
and handler s =
519+
let rec catch_list () =
520+
if peek s = Some 0x07 then begin
521+
ignore (u8 s);
522+
let tag = at var s in
523+
let instrs = instr_block s in
524+
(tag, instrs) :: catch_list ()
525+
end else
526+
[]
527+
in
528+
let cs = catch_list () in
529+
let catch_all = if peek s = Some 0x19 then begin
530+
ignore (u8 s);
531+
Some (instr_block s)
532+
end else
533+
None
534+
in
535+
if cs <> [] || catch_all <> None then begin
536+
end_ s;
537+
catch cs catch_all
538+
end else
539+
let pos = pos s in
540+
match op s with
541+
| 0x0b -> catch [] None
542+
| 0x18 -> delegate (at var s)
543+
| b -> illegal s pos b
544+
508545
let const s =
509546
let c = at instr_block s in
510547
end_ s;

interpreter/binary/encode.ml

+17
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,17 @@ let encode m =
156156
op 0x04; block_type bt; list instr es1;
157157
if es2 <> [] then op 0x05;
158158
list instr es2; end_ ()
159+
| Try (bt, es, h) ->
160+
op 0x06; block_type bt; list instr es; handler h
159161

160162
| Br x -> op 0x0c; var x
161163
| BrIf x -> op 0x0d; var x
162164
| BrTable (xs, x) -> op 0x0e; vec var xs; var x
163165
| Return -> op 0x0f
164166
| Call x -> op 0x10; var x
165167
| CallIndirect (x, y) -> op 0x11; var y; var x
168+
| Throw x -> op 0x08; var x
169+
| Rethrow x -> op 0x09; var x
166170

167171
| Drop -> op 0x1a
168172
| Select None -> op 0x1b
@@ -401,6 +405,19 @@ let encode m =
401405
| Convert (F64 F64Op.DemoteF64) -> assert false
402406
| Convert (F64 F64Op.ReinterpretInt) -> op 0xbf
403407

408+
and handler = function
409+
| Catch (cs, c) ->
410+
let catch (tag, es) =
411+
u8 0x07; var tag; list instr es
412+
in
413+
list catch cs;
414+
begin match c with
415+
| None -> ()
416+
| Some es -> u8 0x19; list instr es
417+
end;
418+
end_ ()
419+
| Delegate x -> u8 0x18; var x
420+
404421
let const c =
405422
list instr c.it; end_ ()
406423

interpreter/syntax/ast.ml

+8
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,14 @@ and instr' =
114114
| Unary of unop (* unary numeric operator *)
115115
| Binary of binop (* binary numeric operator *)
116116
| Convert of cvtop (* conversion *)
117+
| Try of block_type * instr list * (* try *)
118+
handler (* handle exception *)
119+
| Throw of var (* throw exception *)
120+
| Rethrow of var (* rethrow exception *)
121+
and handler =
122+
| Catch of (var * instr list) list * (* tagged catch blocks *)
123+
instr list option (* optional catch_all *)
124+
| Delegate of var (* delegate *)
117125

118126

119127
(* Globals & Functions *)

interpreter/syntax/free.ml

+14
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,24 @@ let rec instr (e : instr) =
8787
memories zero
8888
| MemoryInit x -> memories zero ++ datas (var x)
8989
| DataDrop x -> datas (var x)
90+
| Try (bt, es, h) -> block es ++ handler h
91+
| Throw x -> events (var x)
92+
| Rethrow x -> labels (var x)
9093

9194
and block (es : instr list) =
9295
let free = list instr es in {free with labels = shift free.labels}
9396

97+
and handler = function
98+
| Catch (cs, catch_all) ->
99+
let rec catch_list = function
100+
| (tag, es) :: cs ->
101+
events (var tag) ++ (block es) ++ catch_list cs
102+
| [] -> match catch_all with
103+
| Some es -> block es
104+
| None -> empty
105+
in catch_list cs
106+
| Delegate x -> events (var x)
107+
94108
let const (c : const) = block c.it
95109

96110
let global (g : global) = const g.it.ginit

interpreter/syntax/operators.ml

+5
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,18 @@ 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_ bt es h = Try (bt, es, h)
23+
let catch cs c = Catch (cs, c)
24+
let delegate x = Delegate x
2225
let br x = Br x
2326
let br_if x = BrIf x
2427
let br_table xs x = BrTable (xs, x)
2528

2629
let return = Return
2730
let call x = Call x
2831
let call_indirect x y = CallIndirect (x, y)
32+
let throw x = Throw x
33+
let rethrow x = Rethrow x
2934

3035
let local_get x = LocalGet x
3136
let local_set x = LocalSet x

interpreter/text/arrange.ml

+12
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,19 @@ let rec instr e =
279279
| Unary op -> unop op, []
280280
| Binary op -> binop op, []
281281
| Convert op -> cvtop op, []
282+
| Try (bt, es, h) ->
283+
"try", block_type bt @ [Node ("do", list instr es)] @ handler h
284+
| Throw x -> "throw " ^ var x, []
285+
| Rethrow x -> "rethrow " ^ var x, []
282286
in Node (head, inner)
287+
and handler = function
288+
| Catch (cs, c) ->
289+
let catch (tag, es) = Node ("catch " ^ var tag, list instr es) in
290+
let catch_all = match c with
291+
| Some es -> [Node ("catch_all", list instr es)]
292+
| None -> [] in
293+
list catch cs @ catch_all
294+
| Delegate x -> [Node ("delegate " ^ var x, [])]
283295

284296
let const head c =
285297
match c.it with

interpreter/text/lexer.mll

+8
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 }

interpreter/text/parser.mly

+82-2
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,6 +191,7 @@ 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
@@ -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 try_ ts es ($4 c') }
506+
| TRY labeling_opt block DELEGATE var
507+
{ fun c -> let c' = $2 c [] in
508+
let ts, es = $3 c' in try_ ts es (delegate ($5 c label)) }
498509

499510
block :
500511
| type_use block_param_body
@@ -524,6 +535,40 @@ 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 c -> catch (handlers c $1) None }
541+
| catch_list_instr catch_all END
542+
{ fun c -> catch (handlers c $1) (Some ($2 c)) }
543+
| catch_all END
544+
{ fun c -> catch [] (Some ($1 c)) }
545+
| END { fun c -> catch [] None }
546+
547+
catch_list_instr :
548+
| catch catch_list_instr { $1 :: $2 }
549+
| catch { [$1] }
550+
551+
handler :
552+
| catch_list
553+
{ fun _ c' -> catch (List.map (fun (l, i) -> (l c' event, i c')) $1) None }
554+
| catch_list LPAR catch_all RPAR
555+
{ fun _ c' -> catch (List.map (fun (l, i) -> (l c' event, i c')) $1) (Some ($3 c')) }
556+
| LPAR catch_all RPAR
557+
{ fun _ c' -> catch [] (Some ($2 c')) }
558+
| LPAR DELEGATE var RPAR
559+
{ fun c _ -> delegate ($3 c label) }
560+
| /* empty */ { fun c _ -> catch [] None }
561+
562+
catch_list :
563+
| catch_list LPAR catch RPAR { $1 @ [$3] }
564+
| LPAR catch RPAR { [$2] }
565+
566+
catch :
567+
| CATCH var instr_list { ($2, $3) }
568+
569+
catch_all :
570+
| CATCH_ALL instr_list { $2 }
571+
527572

528573
expr : /* Sugar */
529574
| LPAR expr1 RPAR
@@ -545,6 +590,9 @@ expr1 : /* Sugar */
545590
| IF labeling_opt if_block
546591
{ fun c -> let c' = $2 c [] in
547592
let bt, (es, es1, es2) = $3 c c' in es, if_ bt es1 es2 }
593+
| TRY labeling_opt try_block
594+
{ fun c -> let c' = $2 c [] in
595+
let bt, (es, h) = $3 c c' in [], try_ bt es h }
548596

549597
select_expr_results :
550598
| LPAR RESULT value_type_list RPAR select_expr_results
@@ -614,6 +662,38 @@ if_ :
614662
| LPAR THEN instr_list RPAR /* Sugar */
615663
{ fun c c' -> [], $3 c', [] }
616664

665+
try_block :
666+
| type_use try_block_param_body
667+
{ let at = at () in
668+
fun c c' ->
669+
VarBlockType (inline_type_explicit c' ($1 c' type_) (fst $2) at),
670+
snd $2 c c' }
671+
| try_block_param_body /* Sugar */
672+
{ let at = at () in
673+
fun c c' ->
674+
let bt =
675+
match fst $1 with
676+
| FuncType ([], []) -> ValBlockType None
677+
| FuncType ([], [t]) -> ValBlockType (Some t)
678+
| ft -> VarBlockType (inline_type c' ft at)
679+
in bt, snd $1 c c' }
680+
681+
try_block_param_body :
682+
| try_block_result_body { $1 }
683+
| LPAR PARAM value_type_list RPAR try_block_param_body
684+
{ let FuncType (ins, out) = fst $5 in
685+
FuncType ($3 @ ins, out), snd $5 }
686+
687+
try_block_result_body :
688+
| try_ { FuncType ([], []), $1 }
689+
| LPAR RESULT value_type_list RPAR try_block_result_body
690+
{ let FuncType (ins, out) = fst $5 in
691+
FuncType (ins, $3 @ out), snd $5 }
692+
693+
try_ :
694+
| LPAR DO instr_list RPAR handler
695+
{ fun c c' -> $3 c', $5 c c' }
696+
617697
instr_list :
618698
| /* empty */ { fun c -> [] }
619699
| select_instr { fun c -> [$1 c] }

interpreter/valid/valid.ml

+4
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,10 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type =
402402
let t1, t2 = type_cvtop e.at cvtop in
403403
[NumType t1] --> [NumType t2]
404404

405+
| Try _ -> [] --> [] (* TODO *)
406+
| Throw _ -> [] --> [] (* TODO *)
407+
| Rethrow _ -> [] --> [] (* TODO *)
408+
405409
and check_seq (c : context) (s : infer_stack_type) (es : instr list)
406410
: infer_stack_type =
407411
match es with

test/core/event.wast

+8
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@
1919
(module (event (result i32)))
2020
"non-empty event result type"
2121
)
22+
23+
(assert_invalid
24+
(module
25+
(event $e0 (export "e0"))
26+
(event $e1 (export "e0"))
27+
)
28+
"duplicate export name"
29+
)

0 commit comments

Comments
 (0)