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

[js-api] Add specification. #86

Merged
merged 1 commit into from
Jun 23, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 177 additions & 3 deletions document/js-api/index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ urlPrefix: https://webassembly.github.io/reference-types/core/; spec: WebAssembl
text: memory address; url: exec/runtime.html#syntax-memaddr
text: global address; url: exec/runtime.html#syntax-globaladdr
text: extern address; url: exec/runtime.html#syntax-externaddr
text: tag address; url: exec/runtime.html#syntax-tagaddr
url: syntax/types.html#syntax-numtype
text: i32
text: i64
Expand All @@ -160,7 +161,10 @@ urlPrefix: https://webassembly.github.io/reference-types/core/; spec: WebAssembl
text: externref
text: function element; url: exec/runtime.html#syntax-funcelem
text: import component; url: syntax/modules.html#imports
text: external value; url: exec/runtime.html#syntax-externval
url: exec/runtime.html#syntax-externval
text: external value
for: external value
text: tag
text: host function; url: exec/runtime.html#syntax-hostfunc
text: the instantiation algorithm; url: exec/modules.html#instantiation
text: module; url: syntax/modules.html#syntax-module
Expand All @@ -172,6 +176,8 @@ urlPrefix: https://webassembly.github.io/reference-types/core/; spec: WebAssembl
text: table
text: mem
text: global
for: externtype
text: tag
Comment on lines +179 to +180
Copy link
Member

Choose a reason for hiding this comment

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

Why is this externaltype/tag special and not just

text:tag

? And because they are differently defined they are used in different ways too:

externtype/table:

1. If |externtype| is of the form [=table=] <var ignore>tabletype</var>,

externtype/tag:
1. If |externtype| is of the form [=externtype/tag=] |attribute| <var ignore>functype</var>,

I think either way is fine, but do you think it'd be better to make them consistent? The same for external value/tag too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's basically namespacing - I don't recall if I had an actual conflict with "tag" or if I just thought it made more sense. I'd be happy to change the others as well, but I'd rather do that in the main spec repo directly.

text: global type; url: syntax/types.html#syntax-globaltype
url: syntax/types.html#syntax-mut
text: var
Expand All @@ -184,9 +190,12 @@ urlPrefix: https://webassembly.github.io/reference-types/core/; spec: WebAssembl
text: memaddrs; for: moduleinst; url: exec/runtime.html#syntax-moduleinst
text: signed_64; url: exec/numerics.html#aux-signed
text: sequence; url: syntax/conventions.html#grammar-notation
text: exception; for: tagtype/attribute; url: syntax/types.html#syntax-tagtype
urlPrefix: https://heycam.github.io/webidl/; spec: WebIDL
type: dfn
text: create a namespace object; url: create-a-namespace-object
urlPrefix: https://webassembly.github.io/js-types/js-api/; spec: WebAssembly JS API (JS Type Reflection)
type: abstract-op; text: FromValueType; url: abstract-opdef-fromvaluetype
Copy link
Member

Choose a reason for hiding this comment

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

Where is this FromValueType? I can't find it in https://webassembly.github.io/js-types/js-api/#abstract-opdef-fromvaluetype. Also ToValueType is defined within this file; then why is only this method from the type reflection proposal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the publishing is broken - WebAssembly/js-types#23. js-types only needs one direction, but it seems risky to just copy/paste. I can turn the dependency in the other direction if that turns out to be helpful.

</pre>

<pre class='link-defaults'>
Expand Down Expand Up @@ -391,6 +400,12 @@ A {{Module}} object represents a single WebAssembly module. Each {{Module}} obje
1. Let |tableaddr| be |v|.\[[Table]].
1. Let |externtable| be the [=external value=] [=external value|table=] |tableaddr|.
1. [=list/Append=] |externtable| to |imports|.
1. If |externtype| is of the form [=externtype/tag=] |attribute| <var ignore>functype</var>,
1. Assert: |attribute| is [=tagtype/attribute/exception=].
1. If |v| does not [=implement=] {{Tag}}, throw a {{LinkError}} exception.
1. Let |tagaddr| be |v|.\[[Address]].
Copy link
Member

Choose a reason for hiding this comment

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

Which address is this? For tables and globals, it says |v|.\[[Table]] and |v|.[[Global]]. Is there a set of values that v contains?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the field set in "create a Tag object" algorithm below. The supported fields depend on the interface that the value implements, which is checked in the step above.

1. Let |externtag| be the [=external value=] [=external value/tag=] |tagaddr|.
1. [=list/Append=] |externtag| to |imports|.
1. Return |imports|.

Note: This algorithm only verifies the right kind of JavaScript values are passed.
Expand Down Expand Up @@ -424,6 +439,12 @@ The verification of WebAssembly type requirements is deferred to the
1. Let [=external value|table=] |tableaddr| be |externval|.
1. Let |table| be [=create a Table object|a new Table object=] created from |tableaddr|.
1. Let |value| be |table|.
1. If |externtype| is of the form [=externtype/tag=] |attribute| <var ignore>functype</var>,
1. Assert: |attribute| is [=tagtype/attribute/exception=].
1. Assert: |externval| is of the form [=external value/tag=] |tagaddr|.
1. Let [=external value/tag=] |tagaddr| be |externval|.
1. Let |exception| be [=create a Tag object|a new Tag object=] created from |tagaddr|.
1. Let |value| be |exception|.
1. Let |status| be ! [=CreateDataProperty=](|exportsObject|, |name|, |value|).
1. Assert: |status| is true.

Expand Down Expand Up @@ -515,7 +536,8 @@ enum ImportExportKind {
"function",
"table",
"memory",
"global"
"global",
"tag"
};

dictionary ModuleExportDescriptor {
Expand Down Expand Up @@ -545,6 +567,7 @@ interface Module {
* "table" if |type| is of the form [=table=] <var ignore>tabletype</var>
* "memory" if |type| is of the form [=mem=] <var ignore>memtype</var>
* "global" if |type| is of the form [=global=] <var ignore>globaltype</var>
* "tag" if |type| is of the form [=externtype/tag=] <var ignore>tag</var>
</div>

<div algorithm>
Expand Down Expand Up @@ -1005,8 +1028,15 @@ This slot holds a [=function address=] relative to the [=surrounding agent=]'s [
1. [=list/Append=] [=ToWebAssemblyValue=](|arg|, |t|) to |args|.
1. Set |i| to |i| + 1.
1. Let (|store|, |ret|) be the result of [=func_invoke=](|store|, |funcaddr|, |args|).
1. Note: The expectation is that [=func_invoke=] will be updated to return (|store|, <var ignore>val</var>* | [=error=] | (exception |exntag| |payload|)).
1. Set the [=surrounding agent=]'s [=associated store=] to |store|.
1. If |ret| is [=error=], throw an exception. This exception should be a WebAssembly {{RuntimeError}} exception, unless otherwise indicated by <a href="#errors">the WebAssembly error mapping</a>.
1. If |ret| is exception |exntag| |payload|, then
1. If |exntag| is the [=JavaScript exception tag=], then
1. Let « [=ref.extern=] |externaddr| » be |payload|.
1. Throw the result of [=retrieving an extern value=] from |externaddr|.
Comment on lines +1036 to +1037
Copy link
Member

Choose a reason for hiding this comment

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

Do we need these steps? Aren't these two steps cancelling each other?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't fully understand your question. The goal of the "JavaScript exception tag" check is to ensure an exception thrown from js → wasm → js maintains its identity.

Copy link
Member

Choose a reason for hiding this comment

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

Oh I think I was confused. I thought you apply ref.extern to the returned payload; I guess this means if the payload is the form « [=ref.extern=] |externaddr| », then we retrieve an extern value from externaddr. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, exactly. If you have thoughts on how to make this destructuring assignment pattern more readable, I'd love to hear them.

1. Let |exception| be [=create a RuntimeException object|a new RuntimeException=] for |exntag| and |payload|.
1. Throw |exception|.
1. Let |outArity| be the [=list/size=] of |ret|.
1. If |outArity| is 0, return undefined.
1. Otherwise, if |outArity| is 1, return [=ToJSValue=](|ret|[0]).
Expand Down Expand Up @@ -1057,7 +1087,14 @@ Note: Exported Functions do not have a \[[Construct]] method and thus it is not
1. [=Clean up after running a callback=] with |stored settings|.
1. [=Clean up after running script=] with |relevant settings|.
1. Assert: |result|.\[[Type]] is <emu-const>throw</emu-const> or <emu-const>normal</emu-const>.
1. If |result|.\[[Type]] is <emu-const>throw</emu-const>, then trigger a WebAssembly trap, and propagate |result|.\[[Value]] to the enclosing JavaScript.
1. If |result|.\[[Type]] is <emu-const>throw</emu-const>, then:
1. If |v| [=implements=] {{RuntimeException}},
1. Let |type| be |v|.\[[Type]].
1. Let |payload| be |v|.\[[Payload]].
Copy link
Member

Choose a reason for hiding this comment

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

(|store|, <var ignore>val</var>* | [=error=] | (exception |exntag| |payload|)

to

(|store|, <var ignore>val</var>* | [=error=] | (exception |exntag| |payload| |stack|)

?

Copy link
Member

Choose a reason for hiding this comment

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

Ping

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't have time today to page #189 back in, but I'll try to work on it next week.

1. Otherwise,
1. Let |type| be the [=JavaScript exception tag=].
1. Let |payload| be [=ToWebAssemblyValue=](|v|, [=externref=]).
1. [=WebAssembly/Throw=] with |type| and |payload|.
1. Otherwise, return |result|.\[[Value]].
1. Let |store| be the [=surrounding agent=]'s [=associated store=].
1. Let (|store|, |funcaddr|) be [=func_alloc=](|store|, |functype|, |hostfunc|).
Expand Down Expand Up @@ -1126,6 +1163,143 @@ The algorithm <dfn>ToWebAssemblyValue</dfn>(|v|, |type|) coerces a JavaScript va

</div>

<h3 id="tags">Tags</h3>

The <dfn>tag_parameters</dfn> algorithm returns the [=list=] of types for a given
[=tag address=] in the given [=associated store=].

<h4 id="exceptions-types">Exception types</h4>

<pre class="idl">
dictionary TagType {
required sequence&lt;ValueType> parameters;
};

[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
interface Tag {
constructor(TagType type);
TagType type();
};
</pre>

An {{Tag}} value represents a type of exception.

<div algorithm>

To <dfn>create a Tag object</dfn> from a [=tag address=] |tagAddress|, perform the following steps:

TODO: define cache.

1. Let |tag| be a [=new=] {{Tag}}.
1. Set |tag|.\[[Address]] to |tagAddress|.
1. Return |tag|.

</div>

<div algorithm>

The <dfn method for="Tag">type()</dfn> method steps are:

1. Let |store| be the [=surrounding agent=]'s [=associated store=].
1. Let |parameters| be [=tag_parameters=](|store|, **this**.\[[Address]]).
1. Let |idlParameters| be «».
1. [=list/iterate|For each=] |type| of |parameters|,
1. [=list/Append=] [$FromValueType$](|type|) to |idlParameters|.
1. Return «[ "{{TagType/parameters}}" → |idlParameters| ]».

</div>

<h4 id="runtime-exceptions">Runtime exceptions</h4>

<pre class="idl">
[LegacyNamespace=WebAssembly, Exposed=(Window,Worker,Worklet)]
interface RuntimeException {
constructor(Tag exceptionTag, sequence&lt;any> payload);
any getArg(Tag exceptionTag, unsigned long index);
boolean is(Tag exceptionTag);
};
</pre>

A {{RuntimeException}} value represents an exception.

<div algorithm>

To <dfn>create a RuntimeException object</dfn> from a [=tag address=] |tagAddress| and [=list=]
of WebAssembly values |payload|, perform the following steps:
Comment on lines +1227 to +1228
Copy link
Member

Choose a reason for hiding this comment

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

How are "create an Exception object" algorithm and "new Exception(exceptionTag, payload) constructor steps" algorithm different?

To <dfn>create an Exception object</dfn> from a [=tag address=] |tagAddress| and [=list=]
of WebAssembly values |payload|, perform the following steps:
1. Let |store| be the [=surrounding agent=]'s [=associated store=].
1. Let |types| be [=tag_parameters=](|store|, |tagAddress|).
1. Assert: |types|'s [=list/size=] is |payload|'s [=list/size=].
1. [=list/iterate|For each=] |value| and |resultType| of |payload| and |types|, paired linearly,
1. Assert: |value|'s type matches |resultType|.
1. Let |exception| be a [=new=] {{Exception}}.
1. Set |exception|.\[[Type]] to |tagAddress|.
1. Set |exception|.\[[Payload]] to |payload|.
1. Return |exception|.

The <dfn constructor for=Exception
lt="Exception(exceptionTag, payload)">new Exception(|exceptionTag|, |payload|)</dfn>
constructor steps are:
1. Let |store| be the [=surrounding agent=]'s [=associated store=].
1. Let |types| be [=tag_parameters=](|store|, |exceptionTag|.\[[Address]]).
1. If |types|'s [=list/size=] is not |payload|'s [=list/size=],
1. Throw a {{TypeError}}.
1. Let |wasmPayload| be « ».
1. [=list/iterate|For each=] |value| and |resultType| of |payload| and |types|, paired linearly,
1. [=list/Append=] ? [=ToWebAssemblyValue=](|value|, |resultType|) to |wasmPayload|.
1. Set **this**.\[[Type]] to |exceptionTag|.\[[Address]].
1. Set **this**.\[[Payload]] to |wasmPayload|.

Why do they exist separately?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"create an Exception object" is when you already have the wasm values; "new Exception" is the js-side constructor called from webidl. I don't see a super obvious way to share more code between them, unfortunately.


1. Let |store| be the [=surrounding agent=]'s [=associated store=].
1. Let |types| be [=tag_parameters=](|store|, |tagAddress|).
1. Assert: |types|'s [=list/size=] is |payload|'s [=list/size=].
1. [=list/iterate|For each=] |value| and |resultType| of |payload| and |types|, paired linearly,
1. Assert: |value|'s type matches |resultType|.
Comment on lines +1232 to +1234
Copy link
Member

Choose a reason for hiding this comment

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

Here we assert for invariants, but in the constructor algorithm below we throw. What's the difference?

1. Let |exception| be a [=new=] {{RuntimeException}}.
1. Set |exception|.\[[Type]] to |tagAddress|.
1. Set |exception|.\[[Payload]] to |payload|.
Copy link
Member

Choose a reason for hiding this comment

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

In the constructor algorithm below, we run ToWebAssemblyValue on each payload value, but here we do not. What's the difference?

1. Return |exception|.

</div>

<div algorithm>

The <dfn constructor for=RuntimeException
lt="RuntimeException(exceptionTag, payload)">new RuntimeException(|exceptionTag|, |payload|)</dfn>
constructor steps are:

1. Let |types| be [=tag_parameters=](|exceptionTag|.\[[Address]]).
1. If |types|'s [=list/size=] is not |payload|'s [=list/size=],
1. Throw a {{TypeError}}.
1. Let |wasmPayload| be « ».
1. [=list/iterate|For each=] |value| and |resultType| of |payload| and |types|, paired linearly,
1. [=list/Append=] ? [=ToWebAssemblyValue=](|value|, |resultType|) to |wasmPayload|.
1. Set **this**.\[[Type]] to |exceptionTag|.\[[Address]].
1. Set **this**.\[[Payload]] to |wasmPayload|.

</div>

<div algorithm>

The <dfn method for="RuntimeException">getArg(|exceptionTag|, |index|)</dfn> method steps are:

1. If **this**.\[[Type]] is not equal to |exceptionTag|.\[[Address]],
1. Throw a {{TypeError}}.
1. Let |payload| be **this**.\[[Payload]].
1. If |index| ≥ |payload|'s [=list/size=],
1. Throw a {{TypeError}}.
1. Return [=ToJSValue=](|payload|[|index|]).

</div>

<div algorithm>

The <dfn method for="RuntimeException">is(|exceptionTag|)</dfn> method steps are:

1. If **this**.\[[Type]] is not equal to |exceptionTag|.\[[Address]],
1. Return false.
1. Return true.

</div>

<h4 id="js-exceptions">JavaScript exceptions</h4>

The <dfn>JavaScript exception tag</dfn> is a [=tag address=] reserved by this
specification to distinguish exceptions originating from JavaScript.

For any [=associated store=] |store|, the result of
[=tag_parameters=](|store|, [=JavaScript exception tag=]) must be « [=externref=] ».

Issue: Should it be possible for `catch <JS-exception-tag>` to extract the payload from an exception with this tag?
Copy link
Member

Choose a reason for hiding this comment

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

Why should it be not possible? Wasm's catch wouldn't be able to examine the internals of the externref payload, but that's true for all externrefs, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't feel strongly either way, but to support it, we need to expose the tag to core wasm somehow, right? I'm a bit fuzzy on how that would work.

Copy link
Member

Choose a reason for hiding this comment

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

I guess the only way wasm can catch JS exceptions is to import the JS exception tag. Can we define the tag in the JS side and make wasm import it?

Copy link
Member

Choose a reason for hiding this comment

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

Ping in case this was missed :) Do you think this would work?


<div algorithm>

To <dfn for=WebAssembly>throw</dfn> with a [=tag address=] |type| and matching [=list=] of WebAssembly values |payload|, perform the following steps:

1. Unwind the stack until reaching the *catching try block* given |type|.
1. Invoke the catch block with |payload|.

Note: This algorithm is expected to be moved into the core specification.
Comment on lines +1294 to +1299
Copy link
Member

Choose a reason for hiding this comment

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

Is this specific to JS exceptions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this is supposed to be basically equivalent to the throw instruction in core wasm. The core spec wasn't finished when I wrote this, so I put this here so I had an api to use in the rest of the spec.


</div>

<h3 id="error-objects">Error Objects</h3>

WebAssembly defines the following Error classes: <dfn exception>CompileError</dfn>, <dfn exception>LinkError</dfn>, and <dfn exception>RuntimeError</dfn>.
Expand Down