Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[interpreter] Parse and convert EH opcodes #160

Merged
merged 1 commit into from
Jun 23, 2021

Conversation

thibaudmichaud
Copy link
Collaborator

Please take a look. This adds support for EH opcodes in the parser, AST, encoder, decoder and formatter, and adds core spec tests. This follows the latest discussions that seem to have reached agreement: unwind is not supported (#156), and try blocks may have no handlers attached to them (#157).

This can already be used to convert the tests to JS, so this should be a useful starting point to ensure consistency across JS engines. The interpreter itself is still missing validation and execution.

handler (* handle exception *)
| Throw of var (* throw exception *)
| Rethrow of var (* rethrow exception *)
and handler =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not worth introducing an auxiliary type here (and auxiliary functions for it in various places). Simply make it two separate instructions, TryCatch and TryDelegate. That they share an opcode/keyword is best considered a detail of the binary & textual representation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

interpreter/binary/decode.ml Show resolved Hide resolved
| _ ->
let pos = pos s in
let e' = instr s in
instr_block' s (Source.(e' @@ region s pos pos) :: es)

and handler s =
let rec catch_list () =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: avoid nesting functions unless they close over local variables in a non-trivial fashion.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


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

and handler = function
| Catch (cs, catch_all) ->
let rec catch_list = function
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same nit here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -0,0 +1,429 @@
;; Test exception handling support
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you split this up into files focussing on individual instructions?

throw.wast
rethrow.wast
try_catch.wast
try_delegate.wast

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@takikawa
Copy link
Collaborator

takikawa commented Jun 12, 2021

I was able to run these tests for SpiderMonkey as well using its test harness for spec tests. After some modifications to account for recent changes (e.g., bare try blocks) and test harness issues, I believe SM also agrees on all of these tests as well.

One issue I ran into, however, is that the core tests are using assert_trap to test uncaught exception cases. Would it make sense to instead extend the interpreter with a new assertion (assert_uncaught_exception <action>) for these cases? (since the spec defines uncaught exceptions as host-dependent behavior rather than a trap necessarily)

Comment on lines 253 to 257
let ca = if peek s = Some 0x19 then begin
ignore (u8 s);
Some (instr_block s)
end else
None
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let ca = if peek s = Some 0x19 then begin
ignore (u8 s);
Some (instr_block s)
end else
None
let ca =
if peek s = Some 0x19 then begin
ignore (u8 s);
Some (instr_block s)
end else
None

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 262 to 267
end else
begin match op s with
| 0x0b -> try_catch bt es [] None
| 0x18 -> try_delegate bt es (at var s)
| b -> illegal s pos b
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
end else
begin match op s with
| 0x0b -> try_catch bt es [] None
| 0x18 -> try_delegate bt es (at var s)
| b -> illegal s pos b
end
end else begin
match op s with
| 0x0b -> try_catch bt es [] None
| 0x18 -> try_delegate bt es (at var s)
| b -> illegal s pos b
end

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@thibaudmichaud
Copy link
Collaborator Author

One issue I ran into, however, is that the core tests are using assert_trap to test uncaught exception cases. Would it make sense to instead extend the interpreter with a new assertion (assert_uncaught_exception ) for these cases? (since the spec defines uncaught exceptions as host-dependent behavior rather than a trap necessarily)

I think that makes sense and I just updated the PR with that suggestion. The JS implementation of the rule uses a simple try/catch to accept any exception.

I was actually planning to add such a rule later that uses the JS API to also check the type and content of the exception, but for now I think it is sufficient to add the weak form to solve the issue you pointed out.

@takikawa
Copy link
Collaborator

takikawa commented Jun 15, 2021

I think that makes sense and I just updated the PR with that suggestion. The JS implementation of the rule uses a simple try/catch to accept any exception.

Thanks! I just tried the new commit with assert_uncaught_exception and I was able to get it to work with the SpiderMonkey spec test harness.

@rossberg
Copy link
Member

rossberg commented Jun 16, 2021

Ah, wanted to comment on the assertion question as well, but forgot.

I agree, we should have a dedicated assertion for that. But can we just call it assert_exception though? Assertions are kind of a unit test thing, so "uncaught" seems off, since that's only meaningful in the context of an entire program.

@thibaudmichaud
Copy link
Collaborator Author

I agree, we should have a dedicated assertion for that. But can we just call it assert_exception though? Assertions are kind of a unit test thing, so "uncaught" seems off, since that's only meaningful in the context of an entire program.

Done, both look fine to me.

Copy link
Member

@aheejin aheejin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I haven't read the ML implementation part yet but the test cases here match my understanding.

(do (throw $e0))
(catch $e0
(if (i32.eq (local.get 0) (i32.const 0)) (then (rethrow 1)))
(if (i32.eq (local.get 0) (i32.const 1)) (then (rethrow 2)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(if (i32.eq (local.get 0) (i32.const 1)) (then (rethrow 2)))
(if (i32.eq (local.get 0) (i32.const 1)) (then (rethrow 2)))

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 60 to 61
(do (if (i32.eqz (local.get 0)) (then (rethrow 2))) (i32.const 42))
(catch $e0 (i32.const 23))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(do (if (i32.eqz (local.get 0)) (then (rethrow 2))) (i32.const 42))
(catch $e0 (i32.const 23))
(do (if (i32.eqz (local.get 0)) (then (rethrow 2))) (i32.const 42))
(catch $e0 (i32.const 23))

Tab characters are mixed in there

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


(assert_return (invoke "delegate-merge" (i32.const 1) (i32.const 0)) (i32.const 2))
(assert_exception (invoke "delegate-merge" (i32.const 2) (i32.const 0)))
(assert_return (invoke "delegate-merge" (i32.const 1)) (i32.const 2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it OK to call a function having two parameters with just one parameter? Then are the remaining params are set to 0?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be ok but in this case that was a mistake, thanks for noticing! Fixed.

(module
(event $imported-e0 (import "test" "e0"))
(func $imported-throw (import "test" "throw"))
(event $e0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming that we are changing this to (tag ...) (not necessarily in this PR), do we need a text syntax to represent the attribute (exception)? Currently we are tentatively using

(event $e0 (attr 0))

in Binaryen, but not sure if this is a good one. (I uploaded a PR to change this in Binaryen to (tag ..) in WebAssembly/binaryen#3937, but this attr thing is a separate thing anyway)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As argued elsewhere, I don't think it is necessary to manifest the (so far vacuous) attribute in the text format for the time being. It's not even necessary to put it in the semantics. All we need right now is a reserved zero byte in the binary format, until we know better what possible distinctions are needed in the future.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if we eventually need a different tag kind and go with @aheejin's syntax, exception tags would be a special case as shown below?

(tag)          ;; an exception tag
(tag (attr 0)) ;; an exception tag
(tag (attr 1)) ;; some other tag

I am fine with that, just want to clarify.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would default to the current meaning.

FWIW, it might not even be necessary that we distinguish tags by role. That is, it seems fine to use any tag as an exception, as long as it has a suitable type (with empty result). Provided there is no source of confusion between tags used in different roles.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I'll remove the attr attribute from the Binaryen text format then.

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.
aheejin added a commit to aheejin/binaryen that referenced this pull request Jun 18, 2021
This attribute is always 0 and reserved for future use. In Binayren's
unofficial text format we were writing this field as `(attr 0)`, but we
have recently come to the conclusion that this is not necessary.

Relevant discussion:
WebAssembly/exception-handling#160 (comment)
aheejin added a commit to WebAssembly/binaryen that referenced this pull request Jun 20, 2021
This attribute is always 0 and reserved for future use. In Binayren's
unofficial text format we were writing this field as `(attr 0)`, but we
have recently come to the conclusion that this is not necessary.

Relevant discussion:
WebAssembly/exception-handling#160 (comment)
@aheejin aheejin mentioned this pull request Jun 22, 2021
Copy link
Collaborator

@ioannad ioannad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @aheejin that the change from event to tag could be done in a different PR, so everything LGTM. Sorry for the delayed review!

@thibaudmichaud thibaudmichaud merged commit 7799129 into WebAssembly:master Jun 23, 2021
takikawa added a commit to takikawa/wasm-tools that referenced this pull request Aug 21, 2021
The assert_uncaught_exception assertion used for exception handling
was renamed to assert_exception in the final version of

  WebAssembly/exception-handling#160

but was out-of-date in the wast crate.
alexcrichton pushed a commit to bytecodealliance/wasm-tools that referenced this pull request Aug 21, 2021
The assert_uncaught_exception assertion used for exception handling
was renamed to assert_exception in the final version of

  WebAssembly/exception-handling#160

but was out-of-date in the wast crate.
@aheejin aheejin mentioned this pull request Sep 7, 2021
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants