Skip to content

Commit

Permalink
Bug 1774827 - wasm: Add RecGroup and implement canonicalization of al…
Browse files Browse the repository at this point in the history
…l types. r=yury

This commit adds explicit recursion groups and implements type equality using
canonicalization as specified in the GC proposal.

For the following example:
  0: (struct)
  (rec
  1:  (struct (ref 0))
  2:  (struct (ref 1))
  )
  (rec
  3:  (struct (ref 0))
  4:  (struct (ref 3))
  )

Type equality is done primarily on a recursion group basis.
The first and second explicit rec's are equal, causing the types within them
to be pairwise equal.

Equality is given by the following transformation of the recs to have 'back edges'
to their local recursion group made explicit. The rest of the equality can be
done in a bottom up (inductive) manner.

  (rec
  1:  (struct (ref nonrecursive 0))
  2:  (struct (ref recursive 0))
  )
  (rec
  3:  (struct (ref nonrecursive 0))
  4:  (struct (ref recursive 0))
  )

With this example, you can see that transforming the 'local' edges to a normalized
format allows equality to done by simple comparison.

The biggest change in this commit is to how we store and create type definitions:
  1. TypeDef is now stored inline in a containing RecGroup class
  2. TypeContext now has a 'startRecGroup' and 'endRecGroup' method for building
     rec groups.

There are several other changes required too:
  1. Serialization/deserialization now operates on recursion groups, not type defs
  2. (oom handling fix) Serialization/deserialization initializes refptr's at the correct type
  3. Canonicalization of function types (which existed already for MVP) is now generalized to
     to support canonicalization of all types.

Differential Revision: https://phabricator.services.mozilla.com/D159057
  • Loading branch information
eqrion committed Nov 3, 2022
1 parent c64c8ca commit d51758f
Show file tree
Hide file tree
Showing 19 changed files with 916 additions and 329 deletions.
5 changes: 5 additions & 0 deletions js/public/RefCounted.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ class AtomicRefCounted {
}
}

bool hasOneRef() const {
MOZ_ASSERT(mRefCnt > 0);
return mRefCnt == 1;
}

private:
mutable mozilla::Atomic<MozRefCountType> mRefCnt{0};
};
Expand Down
24 changes: 13 additions & 11 deletions js/src/jit-test/tests/wasm/gc/ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
// Parsing and resolving.

var text = `(module
(type $cons (struct
(field $car i32)
(field $cdr (ref null $cons))))
(type $odd (struct
(field $odd.x i32)
(field $to_even (ref null $even))))
(type $even (struct
(field $even.x i32)
(field $to_odd (ref null $odd))))
(rec
(type $cons (struct
(field $car i32)
(field $cdr (ref null $cons))))
(type $odd (struct
(field $odd.x i32)
(field $to_even (ref null $even))))
(type $even (struct
(field $even.x i32)
(field $to_odd (ref null $odd))))
)
;; Use eqref on the API since struct types cannot be exposed outside the module yet.
Expand Down
172 changes: 100 additions & 72 deletions js/src/jit-test/tests/wasm/gc/subtyping.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// |jit-test| skip-if: !wasmGcEnabled()

function simpleTypeSection(types) {
return types.map((x, i) => `(type \$${i} ${x})`).join('\n');
}

function assertSubtype(superType, subType, types) {
types = types || [];
wasmEvalText(`(module
${types.map((x, i) => `(type \$${i} ${x})`).join('\n')}
${types}
(func
unreachable
(block (param ${subType})
Expand Down Expand Up @@ -41,173 +45,197 @@ assertNotSubtype('eqref', 'externref');
assertSubtype(
'eqref',
'(ref 0)',
['(struct)']);
simpleTypeSection(['(struct)']));

// Struct identity
assertSubtype(
'(ref 0)',
'(ref 1)',
['(struct)', '(struct)']);
simpleTypeSection(['(struct)', '(struct)']));
assertSubtype(
'(ref 1)',
'(ref 0)',
['(struct)', '(struct)']);
simpleTypeSection(['(struct)', '(struct)']));

// Self referential struct
assertSubtype(
'(ref 1)',
'(ref 0)',
['(struct (ref 0))', '(struct (ref 1))']);
simpleTypeSection(['(struct (ref 0))', '(struct (ref 1))']));

// Mutually referential structs
assertSubtype(
'(ref 2)',
'(ref 0)',
['(struct (ref 1))',
'(struct (ref 0))',
'(struct (ref 3))',
'(struct (ref 2))']);
`(rec
(type (struct (ref 1)))
(type (struct (ref 0)))
)
(rec
(type (struct (ref 3)))
(type (struct (ref 2)))
)`);

// Struct subtypes can have extra fields
assertSubtype(
'(ref 0)',
'(ref 1)',
['(struct)',
'(struct (field i32))']);
simpleTypeSection([
'(struct)',
'(struct (field i32))']));
assertSubtype(
'(ref 0)',
'(ref 1)',
['(struct)',
'(struct (field i32) (field i32))']);
simpleTypeSection(['(struct)',
'(struct (field i32) (field i32))']));

// Struct supertypes cannot have extra fields
assertNotSubtype(
'(ref 0)',
'(ref 1)',
['(struct (field i32))',
'(struct)']);
simpleTypeSection([
'(struct (field i32))',
'(struct)']));

// Struct field mutability must match
assertSubtype(
'(ref 0)',
'(ref 1)',
['(struct (field (mut i32)))',
'(struct (field (mut i32)))']);
simpleTypeSection([
'(struct (field (mut i32)))',
'(struct (field (mut i32)))']));
assertSubtype(
'(ref 0)',
'(ref 1)',
['(struct (field i32))',
'(struct (field i32))']);
simpleTypeSection([
'(struct (field i32))',
'(struct (field i32))']));
assertNotSubtype(
'(ref 0)',
'(ref 1)',
['(struct (field (mut i32)))',
'(struct (field i32))']);
simpleTypeSection([
'(struct (field (mut i32)))',
'(struct (field i32))']));
assertNotSubtype(
'(ref 0)',
'(ref 1)',
['(struct (field i32))',
'(struct (field (mut i32)))']);
simpleTypeSection([
'(struct (field i32))',
'(struct (field (mut i32)))']));

// Struct fields are invariant when mutable
assertSubtype(
'(ref 0)',
'(ref 1)',
['(struct (field (mut (ref 2))))',
'(struct (field (mut (ref 3))))',
'(struct)',
'(struct)']);
'(ref 2)',
'(ref 3)',
simpleTypeSection([
'(struct)',
'(struct)',
'(struct (field (mut (ref 0))))',
'(struct (field (mut (ref 1))))']));
assertNotSubtype(
'(ref 0)',
'(ref 1)',
['(struct (field (mut (ref 2))))',
'(struct (field (mut (ref 3))))',
'(struct)',
'(struct (field i32))']);
'(ref 2)',
'(ref 3)',
simpleTypeSection([
'(struct)',
'(struct (field i32))',
'(struct (field (mut (ref 0))))',
'(struct (field (mut (ref 1))))']));

// Struct fields are covariant when immutable
assertSubtype(
'(ref 0)',
'(ref 1)',
['(struct (field (ref 2)))',
'(struct (field (ref 3)))',
'(struct)',
'(struct (field i32))']);
'(ref 2)',
'(ref 3)',
simpleTypeSection([
'(struct)',
'(struct (field i32))',
'(struct (field (ref 0)))',
'(struct (field (ref 1)))']));

// Arrays are subtypes of eqref
assertSubtype(
'eqref',
'(ref 0)',
['(array i32)']);
simpleTypeSection(['(array i32)']));

// Array identity
assertSubtype(
'(ref 0)',
'(ref 1)',
['(array i32)', '(array i32)']);
simpleTypeSection(['(array i32)', '(array i32)']));
assertSubtype(
'(ref 1)',
'(ref 0)',
['(array i32)', '(array i32)']);
simpleTypeSection(['(array i32)', '(array i32)']));

// Self referential array
assertSubtype(
'(ref 1)',
'(ref 0)',
['(array (ref 0))', '(array (ref 1))']);
simpleTypeSection(['(array (ref 0))', '(array (ref 1))']));

// Mutually referential arrays
assertSubtype(
'(ref 2)',
'(ref 0)',
['(array (ref 1))',
'(array (ref 0))',
'(array (ref 3))',
'(array (ref 2))']);
`(rec
(type (array (ref 1)))
(type (array (ref 0)))
)
(rec
(type (array (ref 3)))
(type (array (ref 2)))
)`);

// Array mutability must match
assertSubtype(
'(ref 0)',
'(ref 1)',
['(array (mut i32))',
'(array (mut i32))']);
simpleTypeSection([
'(array (mut i32))',
'(array (mut i32))']));
assertSubtype(
'(ref 0)',
'(ref 1)',
['(array i32)',
'(array i32)']);
simpleTypeSection([
'(array i32)',
'(array i32)']));
assertNotSubtype(
'(ref 0)',
'(ref 1)',
['(array (mut i32))',
'(array i32)']);
simpleTypeSection([
'(array (mut i32))',
'(array i32)']));
assertNotSubtype(
'(ref 0)',
'(ref 1)',
['(array i32)',
'(array (mut i32))']);
simpleTypeSection([
'(array i32)',
'(array (mut i32))']));

// Array elements are invariant when mutable
assertSubtype(
'(ref 0)',
'(ref 1)',
['(array (mut (ref 2)))',
'(array (mut (ref 3)))',
'(ref 2)',
'(ref 3)',
simpleTypeSection([
'(struct)',
'(struct)']);
'(struct)',
'(array (mut (ref 0)))',
'(array (mut (ref 1)))']));
assertNotSubtype(
'(ref 0)',
'(ref 1)',
['(array (mut (ref 2)))',
'(array (mut (ref 3)))',
'(ref 2)',
'(ref 3)',
simpleTypeSection([
'(struct)',
'(struct (field i32))']);
'(struct (field i32))',
'(array (mut (ref 0)))',
'(array (mut (ref 1)))']));

// Array elements are covariant when immutable
assertSubtype(
'(ref 0)',
'(ref 1)',
['(array (ref 2))',
'(array (ref 3))',
'(ref 2)',
'(ref 3)',
simpleTypeSection([
'(struct)',
'(struct (field i32))']);
'(struct (field i32))',
'(array (ref 0))',
'(array (ref 1))']));
2 changes: 1 addition & 1 deletion js/src/vm/MutexIDs.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
_(BufferStreamState, 500) \
_(SharedArrayGrow, 500) \
_(SharedImmutableScriptData, 500) \
_(WasmFuncTypeIdSet, 500) \
_(WasmTypeIdSet, 500) \
_(WasmCodeProfilingLabels, 500) \
_(WasmCodeBytesEnd, 500) \
_(WasmStreamEnd, 500) \
Expand Down
Loading

0 comments on commit d51758f

Please sign in to comment.