Skip to content

Latest commit

 

History

History
722 lines (532 loc) · 35.9 KB

JS.md

File metadata and controls

722 lines (532 loc) · 35.9 KB

JavaScript API

In the MVP, the only way to access WebAssembly on the Web is through an explicit JS API which is defined below. (In the future 🦄, WebAssembly may also be loaded and run directly from an HTML <script type='module'> tag—and any other Web API that loads ES6 modules via URL—as part of ES6 Module integration.)

Traps

Whenever WebAssembly semantics specify a trap, a WebAssembly.RuntimeError object is thrown. WebAssembly code (currently) has no way to catch this exception and thus the exception will necessarily propagate to the enclosing non-WebAssembly caller (either the browser or JavaScript) where it is handled like a normal JavaScript exception.

If WebAssembly calls JavaScript via import and the JavaScript throws an exception, the exception is propagated through the WebAssembly activation to the enclosing caller.

Because JavaScript exceptions can be handled, and JavaScript can continue to call WebAssembly exports after a trap has been handled, traps do not, in general, prevent future execution.

The WebAssembly object

The WebAssembly object is the initial value of the WebAssembly property of the global object. Like the Math and JSON objects, the WebAssembly object is a plain JS object (not a constructor or function) that acts like a namespace and has the following properties:

Constructor Properties of the WebAssembly object

The following intrinsic objects are added:

Function Properties of the WebAssembly object

WebAssembly.validate

The validate function has the signature:

Boolean validate(BufferSource bytes)

If the given bytes argument is not a BufferSource, then a TypeError is thrown.

Otherwise, this function performs validation as defined by the WebAssembly specification and returns true if validation succeeded, false if validation failed.

WebAssembly.compile

The compile function has the signature:

Promise<WebAssembly.Module> compile(BufferSource bytes)

If the given bytes argument is not a BufferSource, the returned Promise is rejected with a TypeError.

Otherwise, this function starts an asychronous task to compile a WebAssembly.Module as described in the WebAssembly.Module constructor. On success, the Promise is fulfilled with the resulting WebAssembly.Module object. On failure, the Promise is rejected with a WebAssembly.CompileError.

The asynchronous compilation is logically performed on a copy of the state of the given BufferSource captured during the call to compile; subsequent mutations of the BufferSource after compile return do not affect ongoing compilations.

In the future 🦄, this function can be extended to accept a stream, thereby enabling asynchronous, background, streaming compilation.

WebAssembly.instantiate

The instantiate function is overloaded based on types of its arguments. If neither of the following overloads match, then the returned Promise is rejected with a TypeError.

Promise<{module:WebAssembly.Module, instance:WebAssembly.Instance}>
  instantiate(BufferSource bytes [, importObject])

This description applies if the first argument is a BufferSource.

This function starts an asynchronous task that first compiles a WebAssembly.Module from bytes as described in the WebAssembly.Module constructor and then instantiate the resulting Module with importObject as described in the WebAssembly.Instance constructor. On success, the Promise is fulfilled with a plain JavaScript object pair {module, instance} containing the resulting WebAssembly.Module and WebAssembly.Instance. On failure, the Promise is rejected with a WebAssembly.CompileError.

The asynchronous compilation is logically performed on a copy of the state of the given BufferSource captured during the call to instantiate; subsequent mutations of the BufferSource after instantiate return do not affect ongoing compilations.

Promise<WebAssembly.Instance> instantiate(moduleObject [, importObject])

This description applies if the first argument is a WebAssembly.Module instance.

This function starts an asynchronous task that instantiates a WebAssembly.Instance from moduleObject and importObject as described in the WebAssembly.Instance constructor. On success, the Promise is fulfilled with the resulting WebAssembly.Instance object. On failure, the Promise is rejected with a WebAssembly.CompileError.

WebAssembly.Module Objects

A WebAssembly.Module object represents the stateless result of compiling a WebAssembly binary-format module and contains one internal slot:

  • [[Module]] : an Ast.module which is the spec definition of a module

WebAssembly.Module Constructor

The WebAssembly.Module constructor has the signature:

new Module(BufferSource bytes)

If the NewTarget is undefined, a TypeError exception is thrown (i.e., this constructor cannot be called as a function without new).

If the given bytes argument is not a BufferSource, a TypeError exception is thrown.

Otherwise, this function performs synchronous compilation of the BufferSource:

  1. The byte range delimited by the BufferSource is first logically decoded according to BinaryEncoding.md and then validated according to the rules in spec/valid.ml.
  2. The spec string values inside Ast.module are decoded as UTF8 as described in Web.md.
  3. On success, a new WebAssembly.Module object is returned with [[Module]] set to the validated Ast.module.
  4. On failure, a new WebAssembly.CompileError is thrown.

WebAssembly.Module.exports

The exports function has the signature:

Array exports(moduleObject)

If moduleObject is not a WebAssembly.Module instance, a TypeError is thrown.

This function returns an Array produced by mapping each Ast.export e of moduleObject.[[Module]].exports to the Object { name: String(e.name), kind: e.ekind } where e.name is decoded as UTF8 and e.ekind is mapped to one of the String values "function", "table", "memory", "global".

Note: other fields like signature may be added in the future.

WebAssembly.Module.imports

The imports function has the signature:

Array imports(moduleObject)

If moduleObject is not a WebAssembly.Module instance, a TypeError is thrown.

This function returns an Array produced by mapping each Ast.import i of moduleObject.[[Module]].imports to the Object { module: String(i.module_name), name: String(i.item_name), kind: i.ikind } where i.module_name and i.item_name are decoded as UTF8 and i.ikind is mapped to one of the String values "function", "table", "memory", "global".

Note: other fields like signature may be added in the future.

Structured Clone of a WebAssembly.Module

A WebAssembly.Module is a cloneable object which means it can be cloned between windows/workers and also stored/retrieved into/from an IDBObjectStore. The semantics of a structured clone is as-if the binary source, from which the WebAssembly.Module was compiled, were cloned and recompiled into the target realm. Engines should attempt to share/reuse internal compiled code when performing a structured clone although, in corner cases like CPU upgrade or browser update, this may not be possible and full recompilation may be necessary.

Given the above engine optimizations, structured cloning provides developers explicit control over both compiled-code caching and cross-window/worker code sharing.

WebAssembly.Instance Objects

A WebAssembly.Instance object represents the instantiation of a WebAssembly.Module into a realm and has one internal slot:

  • [[Instance]] : an Instance.instance which is the WebAssembly spec definition of an instance

as well as one plain data property (configurable, writable, enumerable) added by the constructor:

WebAssembly.Instance Constructor

The WebAssembly.Instance constructor has the signature:

new Instance(moduleObject [, importObject])

If the NewTarget is undefined, a TypeError exception is thrown (i.e., this constructor cannot be called as a function without new).

If moduleObject is not a WebAssembly.Module instance, a TypeError is thrown.

Let module be the Ast.module moduleObject.[[Module]].

If the importObject parameter is not undefined and Type(importObject) is not Object, a TypeError is thrown. If the list of module.imports is not empty and Type(importObject) is not Object, a TypeError is thrown.

Let funcs, memories and tables be initially-empty lists of callable JavaScript objects, WebAssembly.Memory objects and WebAssembly.Table objects, respectively.

Let imports be an initially-empty list of external values.

For each import i in module.imports:

  1. Let o be the resultant value of performing Get(importObject, i.module_name).
  2. If Type(o) is not Object, throw a TypeError.
  3. Let v be the value of performing Get(o, i.item_name)
  4. If i is a function import:
  5. If IsCallable(v) is false, throw a TypeError.
  6. If v is an Exported Function Exotic Object: 1. If the signature of v does not match the signature of i, throw a TypeError. 1. Let closure be v.[[Closure]].
  7. Otherwise: 1. Let closure be a new host function of the given signature which calls v by coercing WebAssembly arguments to JavaScript arguments via ToJSValue and returns the result, if any, by coercing via ToWebAssemblyValue.
  8. Append v to funcs.
  9. Append closure to imports.
  10. If i is a global import:
  11. If i is not an immutable global, throw a TypeError.
  12. If Type(v) is not Number, throw a TypeError.
  13. Append ToWebAssemblyValue(v) to imports.
  14. If i is a memory import:
  15. If v is not a WebAssembly.Memory object, throw a TypeError.
  16. Append v to memories.
  17. Append v.[[Memory]] to imports.
  18. Otherwise (i is a table import):
  19. If v is not a WebAssembly.Table object, throw a TypeError.
  20. Append v to tables.
  21. Append v.[[Table]] to imports.

Let instance be the result of creating a new instance by calling Eval.init given module and imports.

Let exports be a list of (string, JS value) pairs that is mapped from each external value e in instance.exports as follows:

  1. If e is a closure c:
  2. If there is an Exported Function Exotic Object func in funcs whose func.[[Closure]] equals c, then return func.
  3. (Note: At most one wrapper is created for any closure, so func is unique, even if there are multiple occurrances in the list. Moreover, if the item was an import that is already an Exported Function Exotic Object, then the original function object will be found. For imports that are regular JS functions, a new wrapper will be created.)
  4. Otherwise: 1. Let func be an Exported Function Exotic Object created from c. 1. Append func to funcs. 1. Return func.
  5. If e is a global v:
  6. Assert: the global is immutable by MVP validation constraint.
  7. Return ToJSValue(v).
  8. If e is a memory m:
  9. If there is an element memory in memories whose memory.[[Memory]] is m, then return memory.
  10. (Note: At most one WebAssembly.Memory object is created for any memory, so the above memory is unique, even if there are multiple occurrances in the list. Moreover, if the item was an import, the original object will be found.)
  11. Otherwise: 1. Let memory be a new WebAssembly.Memory object created via CreateMemoryObject from m. 1. Append memory to memories. 1. Return memory.
  12. Otherwise e must be a table t:
  13. If there is an element table in tables whose table.[[Table]] is t, then return table.
  14. (Note: At most one WebAssembly.Table object is created for any table, so the above table is unique, even if there are multiple occurrances in the list. Moreover, if the item was an import, the original object will be found.)
  15. Otherwise: 1. Let values be a list of JS values that is mapped from t's elements as follows:
    1. For an element that is uninitialized:
    2. Return null.
    3. For an element that is a closure c:
    4. If there is an Exported Function Exotic Object func in funcs whose func.[[Closure]] equals c, then return func.
    5. (Note: At most one wrapper is created for a any closure, so func is uniquely determined. Moreover, if the item was an import that is already an Exported Function Exotic Object, then the original function object will be found. For imports that are regular JS functions, a new wrapper will be created.)
    6. Otherwise: 1. Let func be an Exported Function Exotic Object created from c. 1. Append func to funcs. 1. Return func. 1. Let table be a new WebAssembly.Table object with [[Table]] set to t and [[Values]] set to values. 1. Append table to tables. 1. Return table.

Note: For the purpose of the above algorithm, two closure values are considered equal if and only if:

  • Either they are both WebAssembly functions for the same instance and referring to the same function definition.
  • Or they are identical host functions (i.e., each host function value created from a JavaScript function is considered fresh).

Let moduleRecord be a new WebAssembly Module Record (given exports).

Let exportStrings be the projected list of only the first (string) components of exports. Let moduleNamespace be the result of calling ModuleNamespaceCreate(moduleRecord, exportStrings). Set moduleRecord.[[Namespace]] to moduleNamespace.

Let instanceObject be a new WebAssembly.Instance object setting [[Instance]] to instance and exports to moduleNamespace.

If, after evaluating the offset initializer expression of every Data and Element Segment, any of the segments do not fit in their respective Memory or Table, throw a RangeError.

Apply all Data and Element segments to their respective Memory or Table in the order in which they appear in the module. Segments may overlap and, if they do, the final value is the last value written in order. Note: there should be no errors possible that would cause this operation to fail partway through. After this operation completes, elements of instance are visible and callable through imported Tables, even if start fails.

If a start is present, it is evaluated given instance. Any errors thrown by start are propagated to the caller.

Return instanceObject.

WebAssembly Module Record

Abstract Module Record is a spec-internal concept used to define ES6 modules. This abstract class currently has one concrete subclass, Source Text Module Record which corresponds to a normal ES6 module. These interfaces are used to define the process of loading a module on the Web.

When WebAssembly gets ES6 Module integration, a new WebAssembly Module Record subclass would be added which would specify the right thing to do for WebAssembly modules as part of the overall loading process.

Until then, the specification of Module Namespace Exotic Objects, (used for the WebAssembly.Instance exports property) still needs to refer to some vestigial Module Record as part of the specification of the [[Get]] method.

More work is needed to flesh out the precise spec interaction here, but the basic idea is to create a Module Environment Record from exports as the [[Environment]] of a new WebAssembly Module Record.

Exported Function Exotic Objects

Functions exported by WebAssembly modules are reflected in JS via a new kind of Exported Function Exotic Object. Like Bound Function Exotic Object, Exported Functions do not have the normal function internal slots but instead have:

  • [[Closure]] : the closure representing the function

as well as the internal slots required of all builtin functions:

Exported Functions also have the following data properties:

  • the length property is set to the exported function's signature's arity
  • the name is set to index as a Number value

WebAssembly Exported Functions have a [[Call]](this, argValues) method defined as:

  1. Let args be an empty list of coerced values.
  2. Let inArity be the number of arguments and outArity be the number of results in the function type of the function's [[Closure]].
  3. For all values v in argValues, in the order of their appearance:
  4. If the length ofargs is less than inArity, append ToWebAssemblyValue(v) to args.
  5. While the length of args is less than inArity, append ToWebAssemblyValue(undefined) to args.
  6. Let ret be the result of calling Eval.invoke passing [[Closure]], and args.
  7. If outArity is 0, return undefined.
  8. Otherwise, return ToJSValue(v), where v is the singular element of ret.

[[Call]](this, argValues) executes in the [[Realm]] of the callee Exported Function. This corresponds to the requirements of builtin function objects in JavaScript.

Exported Functions do not have a [[Construct]] method and thus it is not possible to call one with the new operator.

WebAssembly.Memory Objects

A WebAssembly.Memory object contains a single linear memory which can be simultaneously referenced by multiple Instance objects. Each Memory object has two internal slots:

  • [[Memory]] : a Memory.memory
  • [[BufferObject]] : the current ArrayBuffer whose [[ArrayBufferByteLength]] matches the current byte length of [[Memory]]

WebAssembly.Memory Constructor

The WebAssembly.Memory constructor has the signature:

new Memory(memoryDescriptor)

If the NewTarget is undefined, a TypeError exception is thrown (i.e., this constructor cannot be called as a function without new).

If Type(memoryDescriptor) is not Object, a TypeError is thrown.

Let initial be ToNonWrappingUint32(Get(memoryDescriptor, "initial")).

If HasProperty("maximum"), then let maximum be ToNonWrappingUint32(Get(memoryDescriptor, "maximum")). Otherwise, let maximum be None.

Let memory be the result of calling Memory.create given arguments initial and maximum. Note that initial and maximum are specified in units of WebAssembly pages (64KiB).

Return the result of CreateMemoryObject(memory).

CreateMemoryObject

Given a Memory.memory m, to create a WebAssembly.Memory:

Let buffer be a new ArrayBuffer whose [[ArrayBufferData]] aliases m and whose [[ArrayBufferByteLength]] is set to the byte length of m.

Any attempts to detach buffer other than the detachment performed by m.grow shall throw a TypeError

Return a new WebAssembly.Memory instance with [[Memory]] set to m and [[BufferObject]] set to buffer.

WebAssembly.Memory.prototype.grow

The grow method has the signature:

grow(delta)

Let M be the this value. If M is not a WebAssembly.Memory, a TypeError is thrown.

Let d be ToNonWrappingUint32(delta).

Let ret be the result of performing a grow_memory operation given delta d.

If ret is -1, a RangeError is thrown.

Perform DetachArrayBuffer(M.[[BufferObject]]).

Assign to M.[[BufferObject]] a new ArrayBuffer whose [[ArrayBufferData]] aliases M.[[Memory]] and whose [[ArrayBufferByteLength]] is set to the new byte length of M.[[Memory]].

Return ToJSValue(ret).

WebAssembly.Memory.prototype.buffer

This is an accessor property whose [[Set]] is Undefined and whose [[Get]] accessor function performs the following steps:

If this is not a WebAssembly.Memory, a TypeError is thrown. Otherwise return M.[[BufferObject]].

WebAssembly.Table Objects

A WebAssembly.Table object contains a single table which can be simultaneously referenced by multiple Instance objects. Each Table object has two internal slots:

WebAssembly.Table Constructor

The WebAssembly.Table constructor has the signature:

new Table(tableDescriptor)

If the NewTarget is undefined, a TypeError exception is thrown (i.e., this constructor cannot be called as a function without new).

If Type(tableDescriptor) is not Object, a TypeError is thrown.

Let element be the result of calling Get(tableDescriptor, "element"). If element is not the string "anyfunc", a TypeError is thrown. (Note: this check is intended to be relaxed in the future 🦄 to allow different element types.)

Let initial be ToNonWrappingUint32(Get(tableDescriptor, "initial")).

If HasProperty("maximum"), then let maximum be ToNonWrappingUint32(Get(tableDescriptor, "maximum")). Otherwise, let maximum be None.

Let table be the result of calling Table.create given arguments AnyFuncType, initial and maximum.

Let values be a new empty array of initial elements, all with value null.

Return a new WebAssemby.Table instance with [[Table]] set to table and [[Values]] set to values.

WebAssembly.Table.prototype.length

This is an accessor property whose [[Set]] is Undefined and whose [[Get]] accessor function performs the following steps:

Let T be the this value. If T is not a WebAssembly.Table, a TypeError is thrown.

Return T.[[Values]].length.

WebAssembly.Table.prototype.grow

This method calls Table.grow, having performed ToNonWrappingUint32 on the first argument. On failure, a RangeError is thrown.

WebAssembly.Table.prototype.get

This method has the following signature

get(index)

Let T be the this value. If T is not a WebAssembly.Table, a TypeError is thrown.

Let i be the result of ToNonWrappingUint32(index).

If i is greater or equal than the length of T.[[Values]], a RangeError is thrown.

Return T.[[Values]][i].

WebAssembly.Table.prototype.set

This method has the following signature

set(index, value)

Let T be the this value. If T is not a WebAssembly.Table, a TypeError is thrown.

If value is not an Exported Function Exotic Object or null, throw a TypeError.

Let i be the result of ToNonWrappingUint32(index).

If i is greater or equal than the length of T.[[Values]], a RangeError is thrown.

If value is null, let elem be Uninitialized; otherwise, let elem be value.[[Closure]].

Set T.[[Table]][i] to elem.

Set T.[[Values]][i] to value.

Return undefined.

ToJSValue

To coerce a WebAssembly value to a JavaScript value:

  1. given a WebAssembly i32 is interpreted as a signed integer, converted (losslessly) to an IEEE754 double and then returned as a JavaScript Number
  2. given a WebAssembly i64, throw a TypeError
  3. given a WebAssembly f32 (single-precision IEEE754), convert (losslessly) to a IEEE754 double, possibly canonicalize NaN, and return as a JavaScript Number
  4. given a WebAssembly f64, possibly canonicalize NaN and return as a JavaScript Number

If the WebAssembly value is optional, then given None, return JavaScript value undefined.

ToWebAssemblyValue

To coerce a JavaScript value to a given WebAssembly value type,

  1. coerce to i32 via ToInt32(v)
  2. for i64, throw a TypeError
  3. coerce to f32 by first applying ToNumber(v) and then converting the resulting IEEE754 64-bit double to a 32-bit float using roundTiesToEven
  4. coerce to f64 via ToNumber(v)

If the value type is optional, then given None, the JavaScript value is ignored.

ToNonWrappingUint32

To convert a JavaScript value v to an unsigned integer in the range [0, UINT32_MAX]:

Let i be ToInteger(v).

If i is negative or greater than UINT32_MAX, RangeError is thrown.

Return i.

Sample API Usage

Given demo.was (encoded to demo.wasm):

(module
    (import $i1 "m" "import1")
    (import $i2 "import2" "")
    (func $main (call_import $i1))
    (start $main)
    (func $f (call_import $i2))
    (export "f" $f)
)

and the following JavaScript, run in a browser:

fetch('demo.wasm').then(response =>
    response.arrayBuffer()
).then(buffer =>
    WebAssembly.compile(buffer)
).then(module => {
    var importObj = {
        m: {import1: () => console.log("hello, ")},
        import2: () => console.log("world!\n")
    };
    var instance = new WebAssembly.Instance(module, importObj); // "hello, "
    instance.exports.f(); // "world!"
});

TODO

  • JS API for cyclic imports