Skip to content
This repository has been archived by the owner on May 29, 2022. It is now read-only.

Latest commit

 

History

History
315 lines (203 loc) · 12.8 KB

version1.md

File metadata and controls

315 lines (203 loc) · 12.8 KB

Version 1 - the Minimal Viable Alpha

Version 1 is OBSOLETE as of November 26 2018 and is no longer recognized by Firefox Nightly, due to a break in the encoding of ref.null. Use Version 2, which is otherwise compatible.

Version 1 represents a simple system of GC types that is module-internal (types are entirely private to a module) but whose object instances can be passed between wasm modules and between wasm and JS. It aims to be a "minimal viable alpha" (MVA): the minimal system that can do something useful and allow for experimentation, but which compromises on expressibility and performance in several ways.

Table of contents:

Overview
Feature control
Struct and Ref Types
Instructions
JS Interface

Overview

  • new module section to opt-in to this experimental system
  • new --wasm-gc / javascript.options.wasm_gc switches to enable in the engine
  • structure type definitions (struct (field T) ...) w/o explicit inheritance
  • reference types: anyref and (ref T) where T names a structure type
  • nominal type equality for primitive and reference types
  • simple prefix typing based on type equality for implict upcast
  • shallow structural downcast from a type A to a type B where A is a prefix of B
  • locals, parameters, globals, and return values of reference types
  • restrictions on (ref T) types to avoid exposing types outside the module
  • new instructions ref.null, ref.is_null, ref.eq, struct.new, struct.get, struct.set, struct.narrow
  • instances of structure types are visible to JS as TypedObject instances

As of September 21 2018, this has all landed in Firefox. What we want for Version 1 to be complete is to remove the command line switches and some hacks that are turned on by them. Follow bug 1444925 and its blockers.

Feature control

The experimental GC feature is only available if a special section is present in each module. Without this section, validation will fail.

The section has ID = 42 (GcFeatureOptIn), byte length 1, and the single byte in the section is the version number. As we move to later versions, older content may or may not remain compatible with newer engines; newer engines that cannot process older content will reject the content in validation.

The version number must be 1 for nightlies built before November 26 2018 and later.

The new section must be the first non-custom section in the module.

In the textual format accepted by SpiderMonkey's wasmTextToBinary(), write (gc_feature_opt_in 1) to create this section.

Struct and Ref Types

Source syntax:

Struct     ::= (type Name? (struct Field ...))
Field      ::= (field Name? FieldType)
FieldType  ::= ValueType | (mut ValueType)
ValueType  ::= i32 | i64 | f32 | f64 | anyref | (ref Name)  // the parens are required in the "ref" syntax
Name       ::= $whatever

Struct types can be mutually recursive.

Struct names represent type table indices.

The extension of ValueType to incorporate (ref T) extends to all other uses of ValueType: Globals / Global imports, Parameters, Returns, Locals, Block/Loop/If, the Null operator

Field names represent integer field indices. Field names must be unique in the module; a useful trick to make this restriction bearable is to prepend the structure name to every field name along with a legal separator such as _ or ., eg, $mystruct.x for a field logically called x inside a structure called $mystruct.

Encoding draft

In the Types section, struct and function types can be interleaved, and the "form" field of each entry in the section determines what we look at.

In the Types section, a struct type looks like this (temporary encoding):

form         varint7        0x50 = SLEB(-0x30), represents "struct constructor"
flags        varint7        no flags at present
field_count  varuint32
field_types  field_type*

where a field_type is a pair:

flags        varint7
type         value_type    // i32 etc, anyref, ref_type

and the encoding of a ref_type is straightforward:

ref_type     0x6E varuint32    the parameter is a reference into the type section

Validation

The only valid flag in a field_type is 0x01, which indicates a mutable field.

For ref_type, the parameter must name a struct type.

Type semantics / compatibility:

Equality is nominal:

T = U if T and U are the same primitive type (i32, i64, f32, f64)
anyref = anyref
(ref T) = (ref U) if T = U, where T and U are indices into the same type space

Type spaces are per-instance. Thus, the phrase "the same type space" above entails that ref types from two different instances never are equal. In particular, if a module is instantiated twice and a reference to a struct object created in one instance is passed to the other instance, the latter will not recognize the object reference as an instance of any of its types.

Common subtyping rules:

(ref T) <: anyref
(ref T) <: (ref T)
(ref T) <: (ref U) if T <: U
T <: U if there is a type W s.t. T <: W and W <: U

Prefix subtyping rule for structures:

T <: U if T and U are struct types, T has at least as many fields as U, 
          T does not have an extends clause [currently there is no such thing as an extends clause],
          and the types of the corresponding fields of U and T are the same (as determined by =)

Nominal subtyping rule for structures (not yet meaningful because no extends clause):

T <: U if T and U are struct types and T extends U or T extends W where W <: U by the nominal subtyping rule

There are automatic upcasts to supertypes. Currently these upcasts always use the prefix rule.

Instructions

Instruction encodings are temporary. 0xFC is the "misc" prefix, previously "numeric".

Instruction ::= Ref.Null | Ref.IsNull | Ref.Eq | Struct.New | Struct.Get | Struct.Set | Struct.Narrow

ref.null

Create a typed null reference.

Synopsis: Ref.Null ::= ... <ref.null Type>

Syntax: Type is a ValType, indicating the target type

Encoding: 0xD0 Type:ValType

Validation:

  • Type must be anyref or (ref T) where T is a structure

Result type: Type

Execution:

  • Push a null reference

ref.is_null

Compare a reference to null.

Synopsis: Ref.IsNull ::= ... Expr <ref.is_null>

Encoding: 0xD1

Validation:

  • The type of Expr must be anyref or (ref T) where T is a struct type

Result type: i32

Execution:

  • pop Expr
  • if Expr is a null reference, push 1, otherwise push 0

ref.eq

Compare two references for pointer equality.

Synopsis: Ref.Eq ::= ... Expr_0 Expr_1 <ref.eq>

Encoding: 0xD2

Validation:

  • The types of Expr_0 and Expr_1 must unify in the normal way and must be subtypes of anyref

Result type: i32

Execution:

  • pop Expr_0 and Expr_1
  • if Expr_0 and Expr_1 are the same pointer value push 1, otherwise push 0

struct.new

Construct a new object of a given type, providing initial values for all fields.

Struct.New ::= ... Expr_0 Expr_1 ... Expr_k <struct.new StructName>

Syntax: StructName is a type table index embedded in the instruction

Encoding: 0xFC 0x50 StructName:varuint32

Validation:

  • StructName must name a struct type T with K fields
  • The number of values on the stack is greater than or equal to K
  • The type of Expr_0 is the same as the type of field 0; the type of Expr_1 is the same as the type of field 1; etc

Result type: (ref T)

Execution:

  • Create V, a new instance of T.
  • If the allocation fails then trap.
  • Pop K values off the stack and store them into the corresponding fields of V: the value of Expr_0 to field 0, Expr_1 to field 1, and so on.
  • Push &V

struct.get

Read the value of a field from an object via a typed reference.

Synopsis: Struct.Get ::= ... Expr <struct.get StructName FieldName>

Syntax: StructName is a type table index; FieldName is a field index

Encoding: 0xFC 0x51 StructName:varuint32 FieldName:varuint32

Validation:

  • StructName must name a struct type T with L fields
  • FieldName must be less than L
  • The type of Expr must be (ref U) for some struct type U s.t. U <: T

Result type: The type of field FieldName in T

Execution:

  • Let Ptr be the value of Expr
  • If Ptr is NULL then trap
  • Push the value of field FieldName of *Ptr.

struct.set

Write a value to a field of an object via a typed reference.

Synopsis: Struct.Set ::= ... Expr_0 Expr_1 <struct.set StructName FieldName>

Syntax: StructName is a type table index; FieldName is a field index

Encoding: 0xFC 0x52 StructName:varuint32 FieldName:varuint32

Validation:

  • StructName must name a struct type T with L fields
  • FieldName must be less than L
  • The type of Expr_0 must be (ref U) for some struct type U s.t. U <: T
  • Let M be the mutability of field FieldName of T
  • We must have M = "mut"
  • Let V be the type of Expr_1 and W be the type of field FieldName of T
  • We must have V <: W

Result type: void

Execution:

  • Let Ptr be the value of Expr_0
  • Let Value be the value of Expr_1
  • If Ptr is NULL then trap.
  • Set the value of field FieldName of *Ptr to Value

struct.narrow

This is a structural downcast. It is slow.

Synopsis: Struct.Narrow ::= ... Expr <struct.narrow FromType ToType>

Syntax: FromType and ToType are ValTypes, indicating known source type and target type

Encoding: 0xFC 0x53 FromType:ValType ToType:ValType

Validation:

  • FromType must be anyref or (ref T) where T is a structure
  • ToType must be anyref or (ref U) where U is a structure
  • If ToType is anyref then FromType must be anyref
  • If FromType is (ref T) and ToType is (ref U) then we must have U <: T

Result type: ToType

Execution:

  • Let Ptr be the value of Expr
  • If FromType is anyref and ToType is anyref then return Ptr
  • If Ptr is NULL then push NULL and exit
  • If FromType is anyref then we must first unbox:
    • if the referent of Ptr is not a structure type instance then push NULL and exit
  • Let S be the concrete structure type of the referent of Ptr
  • If S <: ToType then push Ptr and exit
  • Push NULL

JS interface

Objects

Wasm struct type instances can escape to JS via anyref parameters/returns/globals and are seen as opaque TypedObjects by JS code. The fields of these instances are named _0, _1, and so on; the naming is stopgap, and the naming system will change eventually.

A struct type defined in Wasm is not directly exportable from Wasm and is available to JS only through reflection. In particular, since each struct instance has a constructor property, JS can normally construct new instances by means of new (obj.constructor)(...).

Fields that are immutable in the wasm struct definition are not writable from JS; an attempt to write such a field will cause an exception to be thrown.

As JS does not yet have an int64 or BigInt type, Wasm i64 fields are reflected in the object instance as two i32 fields. If the i64 field would normally have been named _n, the two fields are named _n_low and _n_high. These fields are always immutable to JS, and a struct with i64 fields cannot be constructed from JS -- the constructor is accessible but will throw.

Fields of type anyref can be written from JS if they are mutable; fields of type (ref T) are however always immutable to JS, and a struct with (ref T) fields cannot be constructed from JS -- the constructor is accessible but will throw.

(The "TypedObjects" are not a standard thing, but a Firefox rendition of an evolution of what was once the proposal for TypedObjects in JS. The best available resource is here, but it too is probably not accurate or complete. For our purposes, TypedObjects are sealed objects with type-constrained properties and private storage.)

Export/import restrictions

As types are module-internal for the time being, entities that would reveal types or would require types to be revealed cannot be imported or exported. Types are revealed by (ref T) types, so these entities cannot be exported or imported directly:

  • functions whose parameters or result are (ref T) types
  • globals that hold a (ref T) type

In addition, if a table is imported or exported, a function whose parameters or result are (ref T) types cannot be stored in the table by means of an initializing element segment.

Finally, we can think of a struct instance escaping to JS as exporting it, and a struct instance flowing into a wasm module as importing it. As described in the previous section, there are run-time restrictions on JS storing values into (ref T) typed fields of "exported" objects by assignment or construction. Furthermore, a module that "imports" an object that was constructed from a type defined in another module (or for that matter in JS) can only downcast the object to one of its own types if it does not have a (ref T) field, a consequence of our current nominal type equivalence.