-
Notifications
You must be signed in to change notification settings - Fork 36
[interpreter] Add event section #151
[interpreter] Add event section #151
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, this is great. It looks good overall, up to some details of the factorisation, see comments.
Sorry, I didn't know that you were up to this, otherwise I would have pointed you to the PR at effect-handlers/wasm-spec#4, where I already implemented event sections (though with some extensions for the continuation proposal). That PR also includes basic parts of the exception semantics, so feel free to steal form it to avoid duplicate work. It covers only a subset of the exception proposal's instruction set, though.
(One difference with your PR is that I did not use the indirection through I type index in the representation of event types. That was just laziness on my part, and is a TODO in the repo. Your encoding is more accurate.)
interpreter/runtime/instance.ml
Outdated
@@ -15,6 +16,7 @@ type module_inst = | |||
and func_inst = module_inst ref Func.t | |||
and table_inst = Table.t | |||
and memory_inst = Memory.t | |||
and event_inst = func_type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll need to introduce a new type for this, since events have identity. In my implementation, I introduced a new module Event
defining, analogous to the other external entities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
interpreter/syntax/types.ml
Outdated
@@ -10,11 +10,13 @@ type 'a limits = {min : 'a; max : 'a option} | |||
type mutability = Immutable | Mutable | |||
type table_type = TableType of Int32.t limits * ref_type | |||
type memory_type = MemoryType of Int32.t limits | |||
type event_type = func_type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For symmetry with other types (and better extensibility):
type event_type = EventType of func_type
In fact, this ought to reflect the indirection through a type index in the binary format, so should be
type event_type = func_type | |
type event_type = EventType of int32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
interpreter/text/arrange.ml
Outdated
(* Events *) | ||
|
||
let event off i e = | ||
Node ("event", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could insert a symbolic name for better readability, analogous to the other forms of definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
interpreter/text/parser.mly
Outdated
| LPAR RESULT value_type_list RPAR event_fields_import_result | ||
{ let FuncType (ins, out) = $5 in FuncType (ins, $3 @ out) } | ||
|
||
event_types : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: event_type
, since it represents a single event type. Also, I think you can define this as simply
event_type :
| func_type
{ fun c -> EventType ($1 c) }
(assuming you added the EventType
constructor as suggested above).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I simplified this even further by removing the new rule completely and using func_type directly.
interpreter/binary/decode.ml
Outdated
@@ -555,6 +556,7 @@ let import_desc s = | |||
| 0x01 -> TableImport (table_type s) | |||
| 0x02 -> MemoryImport (memory_type s) | |||
| 0x03 -> GlobalImport (global_type s) | |||
| 0x04 -> ignore (vu32 s); EventImport (at var s) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to check that the value is 0, otherwise it won't be forward-compatible. Also, a single zero byte is enough for an unused extension hook (since it's upward-compatible to LEB), and is what we did elsewhere.
In similar other places, the decoder uses the zero
function for this.
Moreover, I think you want to introduce an event_type
decoding function for this, since the same it's reused for event definitions below. Something like:
let event_type s =
zero s;
let x = at var s in
EventType x
There was a problem hiding this comment.
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
Outdated
(* Event section *) | ||
|
||
let event s = | ||
ignore (vu32 s); var s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, this should probably invoke an event_type
decoder function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
interpreter/binary/encode.ml
Outdated
@@ -426,6 +426,7 @@ let encode m = | |||
| TableImport t -> u8 0x01; table_type t | |||
| MemoryImport t -> u8 0x02; memory_type t | |||
| GlobalImport t -> u8 0x03; global_type t | |||
| EventImport x -> u8 0x04; vu32 0x00l; var x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the decoder, I'd introduce an event_type
encoding function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
f340dab
to
8640005
Compare
Thanks for the review! I was also not aware you were working on this, I will definitely take some inspiration. |
8640005
to
3fc8553
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay. This LGTM modulo a few nits.
There is an open question regarding the proper definition and use of event_type
(see comment below), but I think we can resolve that later.
@@ -10,11 +10,13 @@ type 'a limits = {min : 'a; max : 'a option} | |||
type mutability = Immutable | Mutable | |||
type table_type = TableType of Int32.t limits * ref_type | |||
type memory_type = MemoryType of Int32.t limits | |||
type event_type = EventType of int32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency, can you change the other occurrences to int32
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -118,6 +121,8 @@ let encode m = | |||
let memory_type = function | |||
| MemoryType lim -> limits vu32 lim | |||
|
|||
let event_type x = vu32 0x00l; var x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let event_type x = vu32 0x00l; var x | |
let event_type x = u8 0x00; var x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
interpreter/binary/encode.ml
Outdated
@@ -426,6 +428,7 @@ let encode m = | |||
| TableImport t -> u8 0x01; table_type t | |||
| MemoryImport t -> u8 0x02; memory_type t | |||
| GlobalImport t -> u8 0x03; global_type t | |||
| EventImport x -> u8 0x04; event_type x |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| EventImport x -> u8 0x04; event_type x | |
| EventImport t -> u8 0x04; event_type t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -0,0 +1,10 @@ | |||
open Types |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if its kind of trivial, can you also add an .mli file for this module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -0,0 +1,10 @@ | |||
open Types | |||
|
|||
type event = {ty : func_type} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, it's a bit odd that this is func_type not event_type. I can see why, but it would break down once an event type has additional attributes.
The underlying problem is that there are two notions of event_type: the syntactic one (with a typeidx) and the semantic one (with the function type inline). Depending on context, we want one or the other. This is not a situation we've had before, so I don't have any great suggestion right now. We can resolve this later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ack. Regarding the syntactic vs semantic event_type
: I don't see how this is too different from functions, where the type can also be represented directly or through the type ID? It makes sense to me that if one uses the "semantic" encoding, the other does too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference is the extra indirection. For function types, the type section is just mapping a typeidx to a func type, and the functype itself is a closed type description. Once looked up, we can use that directly. But for event types, the definition itself refers to another typeidx, so isn't self-contained. This becomes clearer if we assume that an event type has additional ingredients, say, a hypothetical event_mode (as per the reserved zero byte). Then for the syntax, we'd need
event_type = EventType of typeidx * event_mode
But but when e.g. defining extern_type, that index is meaningless (because it's used outside any module) and you want ExternEventType of event_type'
where
event_type' = EventType' of func_type * event_mode
FWIW, a similar problem also arises with typed reference types, so the syntactic vs semantic distinction becomes rather ubiquitous.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, and for this reason I think types.ml should contain the resolved type.
We already have the typeidx in the AST:
type event = event' Source.phrase
and event' =
{
etype : var;
}
which makes sense for encoding, decoding and pretty-printing. For type checking, imports/exports and everything else, the resolved type is easier to work with. WDYT about simply reverting back to type event_type = EventType of func_type
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, both etype
and EventImport
ought to carry an event_type
, not a var
. Otherwise, they in turn don't factorise correctly, e.g., when event types were to consist of more than just a var.
I'm afraid we can't hack around the two facets, they're inherent. It's relatively localised here, but becomes ubiquitous with concrete reference types. In the function references proposal I currently resolve it by actually distinguishing syntactic and semantic types. They differ by how variables are represented: for syntactic types it's just an index, with semantic types they become a pointer to the actual definition.
But I suggest to not worry about it for now. I want to play with streamlining this a bit and can fix it later. This needs a representation that cleanly carries over to the paper spec.
interpreter/syntax/ast.ml
Outdated
@@ -150,6 +150,8 @@ and memory' = | |||
mtype : memory_type; | |||
} | |||
|
|||
type event = int32 Source.phrase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type event = int32 Source.phrase | |
type event = event' Source.phrase | |
and event' = | |
{ | |
etype : var; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
test/core/exceptions.wast
Outdated
|
||
(module | ||
(event) | ||
(event (param i32)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing a few more syntactic variations (e.g. with symbolic names) couldn't harm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I did much more extensive testing with my next PR which includes the new EH opcodes. I'll send it for review after this one is merged.
In the meantime I added the relevant tests to this PR as suggested.
test/core/exceptions.wast
Outdated
@@ -0,0 +1,11 @@ | |||
;; Test exception handling support |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Maybe name this file event.wast
, since it's testing event definitions (exception-related instructions should go to separate files).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
75de259
to
34925f9
Compare
@@ -0,0 +1,10 @@ | |||
open Types | |||
|
|||
type event = {ty : func_type} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference is the extra indirection. For function types, the type section is just mapping a typeidx to a func type, and the functype itself is a closed type description. Once looked up, we can use that directly. But for event types, the definition itself refers to another typeidx, so isn't self-contained. This becomes clearer if we assume that an event type has additional ingredients, say, a hypothetical event_mode (as per the reserved zero byte). Then for the syntax, we'd need
event_type = EventType of typeidx * event_mode
But but when e.g. defining extern_type, that index is meaningless (because it's used outside any module) and you want ExternEventType of event_type'
where
event_type' = EventType' of func_type * event_mode
FWIW, a similar problem also arises with typed reference types, so the syntactic vs semantic distinction becomes rather ubiquitous.
This change adds support for the event section in the interpreter. This is based on the explainer and on existing implementations (V8, wabt). The changes are mostly mechanical and follow the syntax and logic of function declarations, with the additional rule that the result type must be empty.