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

[spec] Initial JS API formal spec for the GC proposal #352

Merged
merged 3 commits into from
Mar 8, 2023
Merged
Changes from 1 commit
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
171 changes: 162 additions & 9 deletions document/js-api/index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT
text: IterableToList; url: sec-iterabletolist
text: ToBigInt64; url: #sec-tobigint64
text: BigInt; url: #sec-ecmascript-language-types-bigint-type
text: MakeBasicObject; url: #sec-makebasicobject
type: abstract-op
text: CreateMethodProperty; url: sec-createmethodproperty
urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: dfn
Expand All @@ -113,6 +114,10 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df
text: ref.null
text: ref.func
text: ref.extern
<!-- FIXME: correct these links once the GC proposal formal spec is complete -->
text: ref.i31
text: ref.array
text: ref.struct
text: function index; url: syntax/modules.html#syntax-funcidx
text: function instance; url: exec/runtime.html#function-instances
text: store_init; url: appendix/embedding.html#embed-store-init
Expand Down Expand Up @@ -142,13 +147,21 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df
text: global_read; url: appendix/embedding.html#embed-global-read
text: global_write; url: appendix/embedding.html#embed-global-write
text: error; url: appendix/embedding.html#embed-error
<!-- FIXME: these links should be updated with the GC proposal formal spec -->
text: i31_new; url: appendix/embedding.html#embed-gc
text: i31_get; url: appendix/embedding.html#embed-gc
text: ref_cast; url: appendix/embedding.html#embed-gc
text: extern_internalize; url: appendix/embedding.html#embed-gc
text: extern_externalize; url: appendix/embedding.html#embed-gc
text: store; url: exec/runtime.html#syntax-store
text: table type; url: syntax/types.html#syntax-tabletype
text: table address; url: exec/runtime.html#syntax-tableaddr
text: function address; url: exec/runtime.html#syntax-funcaddr
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
<!-- FIXME: Update this link with the GC proposal formal spec -->
text: object address; url: exec/runtime.html#syntax-objectaddr
url: syntax/types.html#syntax-numtype
text: i32
text: i64
Expand All @@ -159,6 +172,12 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df
text: reftype
text: funcref
text: externref
text: ref
url: syntax/types.html#heap-types; for: heap-type
text: extern
text: func
text: i31
text: any
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
Expand All @@ -167,7 +186,7 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df
text: module; url: syntax/modules.html#syntax-module
text: imports; url: syntax/modules.html#syntax-module
text: import; url: syntax/modules.html#syntax-import
url: syntax/types.html#external-types
url: syntax/types.html#external-types; for: external-type
text: external type
text: func
text: table
Expand Down Expand Up @@ -358,7 +377,7 @@ A {{Module}} object represents a single WebAssembly module. Each {{Module}} obje
1. Let |o| be ? [=Get=](|importObject|, |moduleName|).
1. If [=Type=](|o|) is not Object, throw a {{TypeError}} exception.
1. Let |v| be ? [=Get=](|o|, |componentName|).
1. If |externtype| is of the form [=func=] |functype|,
1. If |externtype| is of the form [=external-type/func=] |functype|,
1. If [=IsCallable=](|v|) is false, throw a {{LinkError}} exception.
1. If |v| has a \[[FunctionAddress]] internal slot, and therefore is an [=Exported Function=],
1. Let |funcaddr| be the value of |v|'s \[[FunctionAddress]] internal slot.
Expand Down Expand Up @@ -407,7 +426,7 @@ The verification of WebAssembly type requirements is deferred to the
1. [=list/iterate|For each=] (|name|, |externtype|) of [=module_exports=](|module|),
1. Let |externval| be [=instance_export=](|instance|, |name|).
1. Assert: |externval| is not [=error=].
1. If |externtype| is of the form [=func=] <var ignore>functype</var>,
1. If |externtype| is of the form [=external-type/func=] <var ignore>functype</var>,
1. Assert: |externval| is of the form [=external value|func=] |funcaddr|.
1. Let [=external value|func=] |funcaddr| be |externval|.
1. Let |func| be the result of creating [=a new Exported Function=] from |funcaddr|.
Expand Down Expand Up @@ -544,7 +563,7 @@ interface Module {

<div algorithm>
The <dfn>string value of the extern type</dfn> |type| is
* "function" if |type| is of the form [=func=] <var ignore>functype</var>
* "function" if |type| is of the form [=external-type/func=] <var ignore>functype</var>
* "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>
Expand Down Expand Up @@ -1090,6 +1109,11 @@ The algorithm <dfn>ToJSValue</dfn>(|w|) coerces a [=WebAssembly value=] to a Jav
1. If |w| is of the form [=ref.null=] <var ignore>t</var>, return null.
1. If |w| is of the form [=ref.func=] |funcaddr|, return the result of creating [=a new Exported Function=] from |funcaddr|.
1. If |w| is of the form [=ref.extern=] |externaddr|, return the result of [=retrieving an extern value=] from |externaddr|.
1. If |w| is of the form [=ref.array=] |arrayaddr|, return the result of creating [=a new GC Exported Object=] from |arrayaddr|.
1. If |w| is of the form [=ref.struct=] |structaddr|, return the result of creating [=a new GC Exported Object=] from |structaddr|.
1. If |w| is of the form [=ref.i31=] |i31addr|,
1. Let |i32| be [=i31_get=](|i31addr|).
1. Return [=the Number value=] for |i32|.
Copy link
Member

Choose a reason for hiding this comment

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

I believe that externalize needs to be invoke here somehow. In particular, that will also handle values that are not Wasm GC values, such as internalised JS values. The core spec will have to introduce the notion of host object for those.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In the latest commit, I made it call extern.externalize on all of the internal references: 2d39b22#diff-73846d0b7899a94fe050d3c0ba447da607bebedf80f8b5ed31cb4d164756f048R1118

Also added a ref.host as a placeholder for whatever the core spec will use for this. It's created by internalize and just stores an external address so externalize can retrieve the original value.


Note: Number values which are equal to NaN may have various observable NaN payloads; see [=NumberToRawBytes=] for details.
</div>
Expand Down Expand Up @@ -1120,26 +1144,155 @@ The algorithm <dfn>ToWebAssemblyValue</dfn>(|v|, |type|) coerces a JavaScript va
1. If |type| is [=f64=],
1. Let |f64| be ? [=ToNumber=](|v|).
1. Return [=f64.const=] |f64|.
1. If |type| is [=funcref=],
1. If |type| is of the form [=ref=] |null| |heaptype| and is a subtype of [=ref=] |null| [=heap-type/func=],
takikawa marked this conversation as resolved.
Show resolved Hide resolved
1. If |v| is null,
1. Return [=ref.null=] [=funcref=].
1. If |null| is present,
1. Return [=ref.null=] |heaptype|.
1. Otherwise,
1. Throw a {{TypeError}}.
1. If |v| is an [=Exported Function=],
1. Let |funcaddr| be the value of |v|'s \[[FunctionAddress]] internal slot.
1. Return [=ref.func=] |funcaddr|.
1. Let |casted| be [=ref_cast=](|heaptype|, |funcaddr|).
Copy link
Member

Choose a reason for hiding this comment

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

Casts do not change the value, so if the cast succeeds, it would simply return the original funcaddr. But the cast may also fail, so the result type of this operator would have to include an error case.

Hence it is probably more natural for the embedding API to provide a predicate like

ref_typecheck(addr, heaptype) : bool

instead. (Perhaps we even want to separate getting the ref's own type from a subtype function on two types.)

Copy link
Contributor Author

@takikawa takikawa Feb 14, 2023

Choose a reason for hiding this comment

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

Thanks for catching that, I wonder if it would make sense to call the operation ref_test to mirror the ref.test instruction that is similar in functionality.

I could also see the route with using subtype making sense too, it could also help make some of the conditions more precise in the spec text (where it says "if type is of form X and is a subtype of Y").

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The latest commit uses ref_test for now (not tied to this naming, could use something else or go with a different way of checking) and should check the failure case.

1. Return |casted|.
1. Throw a {{TypeError}}.
1. If |type| is [=externref=],
1. If |type| is of the form [=ref=] |null| [=extern=],
1. If |v| is null,
1. Return [=ref.null=] [=externref=].
1. If |null| is present,
1. Return [=ref.null=] [=externref=].
1. Otherwise,
1. Throw a {{TypeError}}.
1. Let |map| be the [=surrounding agent=]'s associated [=extern value cache=].
1. If a [=extern address=] |externaddr| exists such that |map|[|externaddr|] is the same as |v|,
1. Return [=ref.extern=] |externaddr|.
1. Let [=extern address=] |externaddr| be the smallest address such that |map|[|externaddr|] [=map/exists=] is false.
1. [=map/Set=] |map|[|externaddr|] to |v|.
1. Return [=ref.extern=] |externaddr|.
1. If |type| is of the form [=ref=] |null| |heaptype| and is a subtype of [=ref=] |null| [=any=],
1. If |v| is null,
1. If |null| is present,
1. Return [=ref.null=] |heaptype|.
1. Otherwise,
1. Throw a {{TypeError}}.
1. If |heaptype| is [=i31=],
Copy link
Member

@rossberg rossberg Feb 14, 2023

Choose a reason for hiding this comment

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

This condition seems wrong: i31's need to be generated for type any or eq as well, and the conversion should be the same (and accept the same values) regardless of the type. Likewise, you'll need to allow arbitrary JS values for any, not just ints or GC objects.

I think all non-null values ought to be converted uniformly by first performing internalize and then a type check. The internalize function will then have to do the job to convert numbers to i31 if they are within range, and treat them as host values otherwise. The same conversion has to happen when the internalize instruction is invoked in Wasm. Furthermore, externalize has to be the inverse.

This indeed implies that the JS spec needs to amend the core spec by customising the meaning of internalize/externalize. The core spec cannot really define it, because it doesn't know what host values are. We'll need some hook in the core spec for this host-specific semantics.

Copy link
Contributor Author

@takikawa takikawa Feb 14, 2023

Choose a reason for hiding this comment

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

I think all non-null values ought to be converted uniformly by first performing internalize and then a type check.

I've changed it to use to use this approach now: 2d39b22#diff-73846d0b7899a94fe050d3c0ba447da607bebedf80f8b5ed31cb4d164756f048R1170

This indeed implies that the JS spec needs to amend the core spec by customising the meaning of internalize/externalize.

Also added a new section in the spec that details the requirements on externalize & internalize: 2d39b22#diff-73846d0b7899a94fe050d3c0ba447da607bebedf80f8b5ed31cb4d164756f048R1348

1. Let |i32value| be [=ToWebAssemblyValue=](|v|, [=i32=]).
1. Let |ref| be [=i31_new=](|i32value|).
1. Return |ref|.
1. If |v| is a [=GC Exported Object=],
1. Let |objectaddr| be the value of |v|'s \[[ObjectAddress]] internal slot.
1. Let |intern| be [=extern_internalize=]([=ref.extern=] |objectaddr|).
1. Let |casted| be [=ref_cast=](|heaptype|, |intern|).
1. Return |casted|.
1. Throw a {{TypeError}}.
1. Assert: This step is not reached.

</div>

<h3 id="gc-exotic-objects">Garbage Collected Objects</h3>

A WebAssembly struct or array is made available in JavaScript as a <dfn>GC Exported Object</dfn>.
A [=GC Exported Object=] is an exotic object that wraps a garbage collected WebAssembly reference value.
Most JavaScript operations on a [=GC Exported Object=] will throw an exception.

Note: These operations may be refined in the future to allow richer interactions in JavaScript with WebAssembly structs and arrays.

A [=GC Exported Object=] contains an \[[ObjectAddress]] internal slot.
This slot holds a [=extern address=] relative to the [=surrounding agent=]'s [=associated store=].

Note: A [=GC Exported Object=] holds an extern address, rather than the internal address of the corresponding struct or array. This is because the struct or array is first converted to an external representation via [=extern_externalize=] before it is exported to JavaScript.

The internal methods of a [=GC Exported Object=] use the following implementations.

<div algorithm>
The <dfn>\[[GetPrototypeOf]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes no arguments and throws an exception. It performs the following steps when called:

1. Throw a {{TypeError}}.
</div>

<div algorithm>
The <dfn>\[[SetPrototypeOf]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes argument <var ignore>V</var> (an Object or null) and throws an exception. It performs the following steps when called:

1. Throw a {{TypeError}}.
</div>

<div algorithm>
The <dfn>\[[IsExtensible]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes no arguments and returns a boolean. It performs the following steps when called:

1. Return false.
</div>

<div algorithm>
The <dfn>\[[PreventExtensions]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes no arguments and throws an exception. It performs the following steps when called:

1. Throw a {{TypeError}}.
</div>

<div algorithm>
The <dfn>\[[GetOwnProperty]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes argument <var ignore>P</var> (a property key) and returns undefined. It performs the following steps when called:

1. Return undefined.
</div>

<div algorithm>
The <dfn>\[[DefineOwnProperty]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes arguments <var ignore>P</var> (a property key) and <var ignore>Desc</var> (a property descriptor) and throws an exception. It performs the following steps when called:

1. Throw a {{TypeError}}.
</div>

<div algorithm>
The <dfn>\[[HasProperty]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes argument <var ignore>P</var> (a property key) and returns a boolean. It performs the following steps when called:

1. Return false.
</div>

<div algorithm>
The <dfn>\[[Get]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes arguments <var ignore>P</var> (a property key) and <var ignore>Receiver</var> (an ECMAScript language value) and returns undefined. It performs the following steps when called:

1. Return undefined.
</div>

<div algorithm>
The <dfn>\[[Set]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes arguments <var ignore>P</var> (a property key), <var ignore>V</var> (an ECMAScript language value), and <var ignore>Receiver</var> (an ECMAScript language value) and throws an exception. It performs the following steps when called:

1. Throw a {{TypeError}}.
</div>

<div algorithm>
The <dfn>\[[Delete]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes argument <var ignore>P</var> (a property key) and throws an exception. It performs the following steps when called:

1. Throw a {{TypeError}}.
</div>

<div algorithm>
The <dfn>\[[OwnPropertyKeys]] internal method of a GC Exported Object</dfn> <var ignore>O</var> takes no arguments and returns a list. It performs the following steps when called:

1. Let keys be a new empty list.
1. Return keys.
</div>

<div algorithm>
To create <dfn>a new GC Exported Object</dfn> from a WebAssembly [=object address=] |objectaddr|, perform the following steps:

1. Let |externaddr| be [=extern_externalize=](|objectaddr|).
1. Let |map| be the [=surrounding agent=]'s associated [=extern value cache=].
1. If |map|[|externaddr|] [=map/exists=],
1. Return |map|[|externaddr|].
1. Let |object| be [=MakeBasicObject=](« \[[ObjectAddress]] »).
1. Set |object|.\[[ObjectAddress]] to |externaddr|.
1. Set |object|.\[[GetPrototypeOf]] as specified in [=[[GetPrototypeOf]] internal method of a GC Exported Object=].
1. Set |object|.\[[SetPrototypeOf]] as specified in [=[[SetPrototypeOf]] internal method of a GC Exported Object=].
1. Set |object|.\[[IsExtensible]] as specified in [=[[IsExtensible]] internal method of a GC Exported Object=].
1. Set |object|.\[[PreventExtensions]] as specified in [=[[PreventExtensions]] internal method of a GC Exported Object=].
1. Set |object|.\[[GetOwnProperty]] as specified in [=[[GetOwnProperty]] internal method of a GC Exported Object=].
1. Set |object|.\[[DefineOwnProperty]] as specified in [=[[DefineOwnProperty]] internal method of a GC Exported Object=].
1. Set |object|.\[[HasProperty]] as specified in [=[[HasProperty]] internal method of a GC Exported Object=].
1. Set |object|.\[[Get]] as specified in [=[[Get]] internal method of a GC Exported Object=].
1. Set |object|.\[[Set]] as specified in [=[[Set]] internal method of a GC Exported Object=].
1. Set |object|.\[[Delete]] as specified in [=[[Delete]] internal method of a GC Exported Object=].
1. Set |object|.\[[OwnPropertyKeys]] as specified in [=[[OwnPropertyKeys]] internal method of a GC Exported Object=].
1. [=map/Set=] |map|[|externaddr|] to |object|.
1. Return |object|.
</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