From d1e3c033ac8a1beefb216d480934af4f46cd4ccd Mon Sep 17 00:00:00 2001 From: Ben Visness Date: Wed, 31 Jul 2024 18:20:54 -0500 Subject: [PATCH] Update JS API to handle index types This introduces an IndexValue typedef, which is a union of Number and BigInt, and two algorithms, IndexValueToU64 and U64ToIndexValue, which can be used to convert between IndexValue and WebAssembly's u64 type (used in the embedding spec). It also makes several drive-by fixes and improvements. --- document/js-api/index.bs | 132 +++++++++++++++++++++++++-------------- 1 file changed, 86 insertions(+), 46 deletions(-) diff --git a/document/js-api/index.bs b/document/js-api/index.bs index 46b00d184..468256651 100644 --- a/document/js-api/index.bs +++ b/document/js-api/index.bs @@ -47,6 +47,7 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT text: SetFunctionLength; url: sec-setfunctionlength text: the Number value; url: sec-ecmascript-language-types-number-type text: is a Number; url: sec-ecmascript-language-types-number-type + text: is a BigInt; url: sec-ecmascript-language-types-bigint-type text: NumberToRawBytes; url: sec-numbertorawbytes text: Built-in Function Objects; url: sec-built-in-function-objects text: NativeError Object Structure; url: sec-nativeerror-object-structure @@ -61,6 +62,7 @@ urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT text: NativeError Object Structure; url: sec-nativeerror-object-structure text: 𝔽; url: #𝔽 text: ℤ; url: #ℤ + text: mathematical value; url: #mathematical-value text: SameValue; url: sec-samevalue urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: dfn url: valid/modules.html#valid-module @@ -121,6 +123,7 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: match_valtype; url: appendix/embedding.html#embed-match-valtype text: error; url: appendix/embedding.html#embed-error text: store; url: exec/runtime.html#syntax-store + text: index type; url: syntax/types.html#syntax-idxtype 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 @@ -139,6 +142,9 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df text: exn_alloc; url: appendix/embedding.html#embed-exn-alloc text: exn_tag; url: appendix/embedding.html#embed-exn-tag text: exn_read; url: appendix/embedding.html#embed-exn-read + url: syntax/values.html#syntax-int + text: u32 + text: u64 url: syntax/types.html#syntax-numtype text: i32 text: i64 @@ -200,6 +206,7 @@ urlPrefix: https://webassembly.github.io/spec/core/; spec: WebAssembly; type: df urlPrefix: https://heycam.github.io/webidl/; spec: WebIDL type: dfn text: create a namespace object; url: create-a-namespace-object + text: [EnforceRange]; url: #EnforceRange 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 urlPrefix: https://tc39.es/proposal-resizablearraybuffer/; spec: ResizableArrayBuffer proposal @@ -561,6 +568,8 @@ enum IndexType { "i64", }; +typedef ([EnforceRange] unsigned long long or bigint) IndexValue; + dictionary ModuleExportDescriptor { required USVString name; required ImportExportKind kind; @@ -665,15 +674,15 @@ Note: The use of this synchronous API is discouraged, as some implementations so
 dictionary MemoryDescriptor {
-  required [EnforceRange] unsigned long long initial;
-  [EnforceRange] unsigned long long maximum;
+  required IndexValue initial;
+  IndexValue maximum;
   IndexType index;
 };
 
 [LegacyNamespace=WebAssembly, Exposed=*]
 interface Memory {
   constructor(MemoryDescriptor descriptor);
-  unsigned long grow([EnforceRange] unsigned long long delta);
+  unsigned long grow(IndexValue delta);
   ArrayBuffer toFixedLengthBuffer();
   ArrayBuffer toResizableBuffer();
   readonly attribute ArrayBuffer buffer;
@@ -736,11 +745,11 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each
 
 
The Memory(|descriptor|) constructor, when invoked, performs the following steps: - 1. Let |initial| be |descriptor|["initial"]. - 1. If |descriptor|["maximum"] [=map/exists=], let |maximum| be |descriptor|["maximum"]; otherwise, let |maximum| be empty. + 1. If |descriptor|["index"] [=map/exists=], let |indextype| be |descriptor|["index"]; otherwise, let |indextype| be "i32". + 1. Let |initial| be [=?=] [=IndexValueToU64=](|descriptor|["initial"], |indextype|). + 1. If |descriptor|["maximum"] [=map/exists=], let |maximum| be [=?=] [=IndexValueToU64=](|descriptor|["maximum"], |indextype|); otherwise, let |maximum| be empty. 1. If |maximum| is not empty and |maximum| < |initial|, throw a {{RangeError}} exception. - 1. If |descriptior|["index"] [=map/exists=], let |index| be |descriptor|["index"]; otherwise, let |index| be "i32". - 1. Let |memtype| be { min |initial|, max |maximum|, index |index| }. + 1. Let |memtype| be (|indextype|, { **min** |initial|, **max** |maximum| }). 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. Let (|store|, |memaddr|) be [=mem_alloc=](|store|, |memtype|). If allocation fails, throw a {{RangeError}} exception. 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. @@ -779,7 +788,10 @@ which can be simultaneously referenced by multiple {{Instance}} objects. Each
The grow(|delta|) method, when invoked, performs the following steps: 1. Let |memaddr| be **this**.\[[Memory]]. - 1. Return the result of [=grow the memory buffer|growing the memory buffer=] associated with |memaddr| by |delta|. + 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. + 1. Let |indextype| be the [=index type=] in [=mem_type=](|store|, |memaddr|). + 1. Let |delta64| be [=?=] [=IndexValueToU64=](|delta|, |indextype|). + 1. Return the result of [=grow the memory buffer|growing the memory buffer=] associated with |memaddr| by |delta64|.
Immediately after a WebAssembly [=memory.grow=] instruction executes, perform the following steps: @@ -857,18 +869,18 @@ enum TableKind { dictionary TableDescriptor { required TableKind element; - required [EnforceRange] unsigned long long initial; - [EnforceRange] unsigned long long maximum; + required IndexValue initial; + IndexValue maximum; IndexType index; }; [LegacyNamespace=WebAssembly, Exposed=*] interface Table { constructor(TableDescriptor descriptor, optional any value); - unsigned long long grow([EnforceRange] unsigned long long delta, optional any value); - any get([EnforceRange] unsigned long long index); - undefined set([EnforceRange] unsigned long long index, optional any value); - readonly attribute unsigned long length; + IndexValue grow(IndexValue delta, optional any value); + any get(IndexValue index); + undefined set(IndexValue index, optional any value); + readonly attribute IndexValue length; };
@@ -896,19 +908,19 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address
The Table(|descriptor|, |value|) constructor, when invoked, performs the following steps: - 1. Let |elementType| be [=ToValueType=](|descriptor|["element"]). - 1. If |elementType| is not a [=reftype=], - 1. Throw a {{TypeError}} exception. - 1. Let |initial| be |descriptor|["initial"]. - 1. If |descriptor|["maximum"] [=map/exists=], let |maximum| be |descriptor|["maximum"]; otherwise, let |maximum| be empty. + 1. Let |elementtype| be [=ToValueType=](|descriptor|["element"]). + 1. If |elementtype| is not a [=reftype=], + 1. [=Throw=] a {{TypeError}} exception. + 1. If |descriptor|["index"] [=map/exists=], let |indextype| be |descriptor|["index"]; otherwise, let |indextype| be "i32". + 1. Let |initial| be [=?=] [=IndexValueToU64=](|descriptor|["initial"], |indextype|). + 1. If |descriptor|["maximum"] [=map/exists=], let |maximum| be [=?=] [=IndexValueToU64=](|descriptor|["maximum"], |indextype|); otherwise, let |maximum| be empty. 1. If |maximum| is not empty and |maximum| < |initial|, throw a {{RangeError}} exception. 1. If |value| is missing, - 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. Let |ref| be [=DefaultValue=](|elementtype|). 1. Assert: |ref| is not [=error=]. 1. Otherwise, - 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). - 1. If |descriptior|["index"] [=map/exists=], let |index| be |descriptor|["index"]; otherwise, let |index| be "i32". - 1. Let |type| be the [=table type=] [=table type|index=] |index| {[=table type|min=] |initial|, [=table type|max=] |maximum|} [=table type|refType=] |elementType|. + 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementtype|). + 1. Let |type| be the [=table type=] (|indextype|, { **min** |initial|, **max** |maximum| }, |elementtype|). 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. Let (|store|, |tableaddr|) be [=table_alloc=](|store|, |type|, |ref|). 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. @@ -920,13 +932,14 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address 1. Let |tableaddr| be **this**.\[[Table]]. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. 1. Let |initialSize| be [=table_size=](|store|, |tableaddr|). - 1. Let (limits, |elementType|) be [=table_type=](|tableaddr|). + 1. Let (|indextype|, limits, |elementtype|) be [=table_type=](|store|, |tableaddr|). + 1. Let |delta64| be [=?=] [=IndexValueToU64=](|delta|, |indextype|). 1. If |value| is missing, - 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. Let |ref| be [=DefaultValue=](|elementtype|). 1. If |ref| is [=error=], throw a {{TypeError}} exception. 1. Otherwise, - 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). - 1. Let |result| be [=table_grow=](|store|, |tableaddr|, |delta|, |ref|). + 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementtype|). + 1. Let |result| be [=table_grow=](|store|, |tableaddr|, |delta64|, |ref|). 1. If |result| is [=error=], throw a {{RangeError}} exception. Note: The above exception can happen due to either insufficient memory or an invalid size parameter. @@ -939,17 +952,20 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address The getter of the length attribute of {{Table}}, when invoked, performs the following steps: 1. Let |tableaddr| be **this**.\[[Table]]. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. - 1. Return [=table_size=](|store|, |tableaddr|). + 1. Let |indextype| be the [=index type=] in [=table_type=](|store|, |tableaddr|). + 1. Let |length64| be [=table_size=](|store|, |tableaddr|). + 1. Return [=U64ToIndexValue=](|length64|, |indextype|).
The get(|index|) method, when invoked, performs the following steps: 1. Let |tableaddr| be **this**.\[[Table]]. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. - 1. Let (limits, |elementType|) be [=table_type=](|store|, |tableaddr|). - 1. If |elementType| is [=exnref=], + 1. Let (|indextype|, limits, |elementtype|) be [=table_type=](|store|, |tableaddr|). + 1. If |elementtype| is [=exnref=], 1. Throw a {{TypeError}} exception. - 1. Let |result| be [=table_read=](|store|, |tableaddr|, |index|). + 1. Let |index64| be [=?=] [=IndexValueToU64=](|index|, |indextype|). + 1. Let |result| be [=table_read=](|store|, |tableaddr|, |index64|). 1. If |result| is [=error=], throw a {{RangeError}} exception. 1. Return [=ToJSValue=](|result|).
@@ -958,16 +974,16 @@ Each {{Table}} object has a \[[Table]] internal slot, which is a [=table address The set(|index|, |value|) method, when invoked, performs the following steps: 1. Let |tableaddr| be **this**.\[[Table]]. 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. - 1. Let (limits, |elementType|) be [=table_type=](|store|, |tableaddr|). - 1. If |elementType| is [=exnref=], + 1. Let (|indextype|, limits, |elementtype|) be [=table_type=](|store|, |tableaddr|). + 1. If |elementtype| is [=exnref=], 1. Throw a {{TypeError}} exception. + 1. Let |index64| be [=?=] [=IndexValueToU64=](|index|, |indextype|). 1. If |value| is missing, - 1. Let |ref| be [=DefaultValue=](|elementType|). + 1. Let |ref| be [=DefaultValue=](|elementtype|). 1. If |ref| is [=error=], throw a {{TypeError}} exception. 1. Otherwise, - 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementType|). - 1. Let |store| be the [=surrounding agent=]'s [=associated store=]. - 1. Let |store| be [=table_write=](|store|, |tableaddr|, |index|, |ref|). + 1. Let |ref| be [=?=] [=ToWebAssemblyValue=](|value|, |elementtype|). + 1. Let |store| be [=table_write=](|store|, |tableaddr|, |index64|, |ref|). 1. If |store| is [=error=], throw a {{RangeError}} exception. 1. Set the [=surrounding agent=]'s [=associated store=] to |store|. @@ -1236,18 +1252,18 @@ The algorithm ToJSValue(|w|) coerces a [=WebAssembly value=] to a Jav 1. Assert: |w| is not of the form [=ref.exn=] exnaddr. 1. If |w| is of the form [=i64.const=] |u64|, 1. Let |i64| be [=signed_64=](|u64|). - 1. Return [=ℤ=](|i64| interpreted as a mathematical value). -1. If |w| is of the form [=i32.const=] |i32|, - 1. Let |i32| be [=signed_32=](|i32|). - 2. Return [=𝔽=](|i32| interpreted as a mathematical value). + 1. Return [=ℤ=](|i64| interpreted as a [=mathematical value=]). +1. If |w| is of the form [=i32.const=] |u32|, + 1. Let |i32| be [=signed_32=](|u32|). + 2. Return [=𝔽=](|i32| interpreted as a [=mathematical value=]). 1. If |w| is of the form [=f32.const=] |f32|, 1. If |f32| is [=+∞=] or [=−∞=], return **+∞**𝔽 or **-∞**𝔽, respectively. 1. If |f32| is [=nan=], return **NaN**. - 1. Return [=𝔽=](|f32| interpreted as a mathematical value). + 1. Return [=𝔽=](|f32| interpreted as a [=mathematical value=]). 1. If |w| is of the form [=f64.const=] |f64|, 1. If |f64| is [=+∞=] or [=−∞=], return **+∞**𝔽 or **-∞**𝔽, respectively. 1. If |f64| is [=nan=], return **NaN**. - 1. Return [=𝔽=](|f64| interpreted as a mathematical value). + 1. Return [=𝔽=](|f64| interpreted as a [=mathematical value=]). 1. If |w| is of the form [=ref.null=] t, return null. 1. If |w| is of the form [=ref.i31=] |u31|, 1. Let |i31| be [=signed_31=](|u31|). @@ -1288,7 +1304,7 @@ The algorithm ToWebAssemblyValue(|v|, |type|) coerces a JavaScript va 1. If |type| is [=f32=], 1. Let |number| be [=?=] [$ToNumber$](|v|). 1. If |number| is **NaN**, - 1. Let |n| be an implementation-defined integer such that [=canon=]32 ≤ |n| < 2[=signif=](32). + 1. Let |n| be an implementation-defined integer such that [=canon=]32 ≤ |n| < 2[=signif=](32). 1. Let |f32| be [=nan=](n). 1. Otherwise, 1. Let |f32| be |number| rounded to the nearest representable value using IEEE 754-2008 round to nearest, ties to even mode. [[IEEE-754]] @@ -1296,7 +1312,7 @@ The algorithm ToWebAssemblyValue(|v|, |type|) coerces a JavaScript va 1. If |type| is [=f64=], 1. Let |number| be [=?=] [$ToNumber$](|v|). 1. If |number| is **NaN**, - 1. Let |n| be an implementation-defined integer such that [=canon=]64 ≤ |n| < 2[=signif=](64). + 1. Let |n| be an implementation-defined integer such that [=canon=]64 ≤ |n| < 2[=signif=](64). 1. Let |f64| be [=nan=](n). 1. Otherwise, 1. Let |f64| be |number|. @@ -1337,6 +1353,31 @@ The algorithm ToWebAssemblyValue(|v|, |type|) coerces a JavaScript va +
+The algorithm IndexValueToU64(|v|, |indextype|) asserts that a JavaScript value is the appropriate variant of {{IndexValue}} for an {{IndexType}}, and ensures that its value is in [=u64=] range for WebAssembly embedding operations, by performing the following steps: + +1. If |indextype| is "i32", + 1. If |v| [=is a Number=], + 1. Assert: Due to WebIDL types and [=[EnforceRange]=], 0 ≤ [=ℝ=](|v|) < 264. + 1. Return [=ℝ=](|v|) as a WebAssembly [=u64=]. + 1. Otherwise, [=throw=] a {{TypeError}}. +1. Else if |indextype| is "i64", + 1. If |v| [=is a BigInt=], + 1. If |v| is not equal to [=!=] [$ToBigUint64$](|v|), [=throw=] a {{RangeError}}. + 1. Return [=ℝ=](|v|) as a WebAssembly [=u64=]. + 1. Otherwise, [=throw=] a {{TypeError}}. +1. Assert: This step is not reached. + +
+ +
+The algorithm U64ToIndexValue(|v|, |indextype|) converts a [=u64=] value from a WebAssembly embedding operation to the correct variant of {{IndexValue}} for an {{IndexType}}, by performing the following steps: + +1. If |indextype| is "i32", return [=𝔽=](|v| interpreted as a [=mathematical value=]). +1. Else if |indextype| is "i64", return [=ℤ=](|v| interpreted as a [=mathematical value=]). +1. Assert: This step is not reached. + +

Tags

@@ -1369,7 +1410,6 @@ To initialize a Tag object |tag| from a [=tag address=] |tagAddress|,
- To create a Tag object from a [=tag address=] |tagAddress|, perform the following steps: 1. Let |map| be the [=surrounding agent=]'s associated [=Tag object cache=].