Skip to content
Permalink
Browse files
[Wasm-GC] Add support for subtyping declarations
https://bugs.webkit.org/show_bug.cgi?id=239668

Reviewed by Justin Michaud.

Adds support for `sub` type section forms. These introduce subtyping
declarations that can specify parent types for a `func`, `struct`,
etc. type (the MVP GC proposal only allows a single parent type).

Adding `sub` forms changes type expansion slightly, and requires that
checking `isSubtype` look at the type hierarchy if the LHS type is a
`sub` type. This patch also memoizes type expansion to avoid repeated
unrolling of recursive types.

With the addition of `sub`, a bare `func`, `struct`, etc declaration
is treated as implicitly having a `sub` with zero/empty supertypes.
To avoid `(sub () (func))` and `(func)` being represented differently,
we normalize empty-supertype `sub` to be represented as just the
underlying type.

Subtype checking for indexed reference types is done using a display
data structure. Each `sub` declaration has an associated display that
records an array of supertype indices. This allows subtype checking in
constant-time by checking if the supertype index is present in the
subtype at the correct display offset, rather than with a linear
traversal of the hierarchy. If multiple parent types are allowed in the
future, this algorithm will need to change.

* JSTests/wasm/gc/sub.js: Added.
(module):
(testSubDeclaration):
* JSTests/wasm/wasm.json:
* Source/JavaScriptCore/wasm/WasmFormat.h:
(JSC::Wasm::isSubtypeIndex):
(JSC::Wasm::isSubtype):
* Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:
(JSC::Wasm::LLIntGenerator::callInformationForCaller):
(JSC::Wasm::LLIntGenerator::callInformationForCallee):
(JSC::Wasm::LLIntGenerator::addArguments):
* Source/JavaScriptCore/wasm/WasmLimits.h:
* Source/JavaScriptCore/wasm/WasmSectionParser.cpp:
(JSC::Wasm::SectionParser::parseType):
(JSC::Wasm::SectionParser::parseRecursionGroup):
(JSC::Wasm::SectionParser::checkStructuralSubtype):
(JSC::Wasm::SectionParser::checkSubtypeValidity):
(JSC::Wasm::SectionParser::parseSubtype):
* Source/JavaScriptCore/wasm/WasmSectionParser.h:
* Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp:
(JSC::Wasm::TypeDefinition::dump const):
(JSC::Wasm::Subtype::toString const):
(JSC::Wasm::Subtype::dump const):
(JSC::Wasm::computeSubtypeHash):
(JSC::Wasm::TypeDefinition::hash const):
(JSC::Wasm::TypeDefinition::tryCreateSubtype):
(JSC::Wasm::TypeDefinition::replacePlaceholders const):
(JSC::Wasm::TypeDefinition::unroll const):
(JSC::Wasm::TypeDefinition::expand const):
(JSC::Wasm::TypeDefinition::hasRecursiveReference const):
(JSC::Wasm::SubtypeParameterTypes::hash):
(JSC::Wasm::SubtypeParameterTypes::equal):
(JSC::Wasm::SubtypeParameterTypes::translate):
(JSC::Wasm::TypeInformation::typeDefinitionForSubtype):
(JSC::Wasm::TypeInformation::addCachedUnrolling):
(JSC::Wasm::TypeInformation::tryGetCachedUnrolling):
(JSC::Wasm::TypeInformation::tryCleanup):
* Source/JavaScriptCore/wasm/WasmTypeDefinition.h:
(JSC::Wasm::typeKindSizeInBytes):
(JSC::Wasm::Subtype::Subtype):
(JSC::Wasm::Subtype::superType const):
(JSC::Wasm::Subtype::underlyingType const):
(JSC::Wasm::Subtype::displayType const):
(JSC::Wasm::Subtype::displaySize const):
(JSC::Wasm::Subtype::getSuperType):
(JSC::Wasm::Subtype::getUnderlyingType):
(JSC::Wasm::Subtype::getDisplayType):
(JSC::Wasm::Subtype::storage):
(JSC::Wasm::Subtype::storage const):
(JSC::Wasm::TypeDefinition::allocatedSubtypeSize):
* Source/JavaScriptCore/wasm/js/JSWebAssemblyStruct.cpp:
(JSC::JSWebAssemblyStruct::set):
* Source/JavaScriptCore/wasm/js/WasmToJS.cpp:
(JSC::Wasm::wasmToJS):
* Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp:
(JSC::WebAssemblyModuleRecord::initializeImports):
* Source/JavaScriptCore/wasm/wasm.json:

Canonical link: https://commits.webkit.org/256243@main
  • Loading branch information
takikawa authored and justinmichaud committed Nov 2, 2022
1 parent 5ae5d5e commit 431164ca6a4b101688188966ce5384a7f8c68681
Show file tree
Hide file tree
Showing 13 changed files with 866 additions and 39 deletions.

Large diffs are not rendered by default.

@@ -21,6 +21,7 @@
"func": { "type": "varint7", "value": -32, "b3type": "B3::Void", "width": 0 },
"struct": { "type": "varint7", "value": -33, "b3type": "B3::Void", "width": 0 },
"array": { "type": "varint7", "value": -34, "b3type": "B3::Void", "width": 0 },
"sub": { "type": "varint7", "value": -48, "b3type": "B3::Void", "width": 0 },
"rec": { "type": "varint7", "value": -49, "b3type": "B3::Void", "width": 0 },
"void": { "type": "varint7", "value": -64, "b3type": "B3::Void", "width": 0 }
},
@@ -178,6 +178,27 @@ inline bool isTypeIndexHeapType(int32_t heapType)
return heapType >= 0;
}

inline bool isSubtypeIndex(TypeIndex sub, TypeIndex parent)
{
if (sub == parent)
return true;

const TypeDefinition& sig = TypeInformation::get(sub).unroll();
if (sig.is<Subtype>()) {
const Subtype& subtype = *sig.as<Subtype>();
const TypeDefinition& parentSig = TypeInformation::get(parent).unroll();
if (parentSig.is<Subtype>()) {
if (subtype.displaySize() < parentSig.as<Subtype>()->displaySize())
return false;
return parent == subtype.displayType(subtype.displaySize() - parentSig.as<Subtype>()->displaySize() - 1);
}
// If not a subtype itself, the parent must be at the top of the display.
return parent == subtype.displayType(subtype.displaySize() - 1);
}

return false;
}

inline bool isSubtype(Type sub, Type parent)
{
if (sub.isNullable() && !parent.isNullable())
@@ -189,6 +210,9 @@ inline bool isSubtype(Type sub, Type parent)

if (TypeInformation::get(sub.index).expand().is<FunctionSignature>() && isFuncref(parent))
return true;

if (isRefWithTypeIndex(parent))
return isSubtypeIndex(sub.index, parent.index);
}

if (sub.isRef() && parent.isRefNull() && sub.index == parent.index)
@@ -675,6 +675,7 @@ auto LLIntGenerator::callInformationForCaller(const FunctionSignature& signature
case TypeKind::Arrayref:
case TypeKind::I31ref:
case TypeKind::Rec:
case TypeKind::Sub:
RELEASE_ASSERT_NOT_REACHED();
}
};
@@ -738,6 +739,7 @@ auto LLIntGenerator::callInformationForCaller(const FunctionSignature& signature
case TypeKind::Arrayref:
case TypeKind::I31ref:
case TypeKind::Rec:
case TypeKind::Sub:
RELEASE_ASSERT_NOT_REACHED();
}
}
@@ -773,6 +775,7 @@ auto LLIntGenerator::callInformationForCaller(const FunctionSignature& signature
case TypeKind::Arrayref:
case TypeKind::I31ref:
case TypeKind::Rec:
case TypeKind::Sub:
RELEASE_ASSERT_NOT_REACHED();
}
}
@@ -836,6 +839,7 @@ auto LLIntGenerator::callInformationForCallee(const FunctionSignature& signature
case TypeKind::Arrayref:
case TypeKind::I31ref:
case TypeKind::Rec:
case TypeKind::Sub:
RELEASE_ASSERT_NOT_REACHED();
}
}
@@ -892,6 +896,7 @@ auto LLIntGenerator::addArguments(const TypeDefinition& signature) -> PartialRes
case TypeKind::Arrayref:
case TypeKind::I31ref:
case TypeKind::Rec:
case TypeKind::Sub:
RELEASE_ASSERT_NOT_REACHED();
}
}
@@ -46,6 +46,7 @@ constexpr size_t maxGlobals = 1000000;
constexpr size_t maxDataSegments = 100000;
constexpr size_t maxStructFieldCount = 10000;
constexpr size_t maxRecursionGroupCount = 10000;
constexpr size_t maxSubtypeSupertypeCount = 1;

constexpr size_t maxStringSize = 100000;
constexpr size_t maxModuleSize = 1024 * 1024 * 1024;
@@ -83,6 +83,14 @@ auto SectionParser::parseType() -> PartialResult
WASM_FAIL_IF_HELPER_FAILS(parseRecursionGroup(i, signature));
break;
}
case TypeKind::Sub: {
if (!Options::useWebAssemblyGC())
return fail(i, "th type failed to parse because sub types are not enabled");

Vector<TypeIndex> empty;
WASM_FAIL_IF_HELPER_FAILS(parseSubtype(i, signature, empty));
break;
}
default:
return fail(i, "th Type is non-Func, non-Struct, and non-Array ", typeKind);
}
@@ -93,26 +101,19 @@ auto SectionParser::parseType() -> PartialResult
// notations for recursion groups with one type. Here we ensure that if such a
// shorthand type is actually recursive, it is represented with a recursion group.
if (Options::useWebAssemblyGC()) {
bool hasRecursiveReference = false;
if (signature->is<FunctionSignature>())
hasRecursiveReference = signature->as<FunctionSignature>()->hasRecursiveReference();
else if (signature->is<StructType>())
hasRecursiveReference = signature->as<StructType>()->hasRecursiveReference();
else if (signature->is<ArrayType>())
hasRecursiveReference = signature->as<ArrayType>()->hasRecursiveReference();

if (hasRecursiveReference) {
Vector<TypeIndex> types;
bool result = types.tryAppend(signature->index());
WASM_PARSER_FAIL_IF(!result, "can't allocate enough memory for Type section's ", i, "th signature");
RefPtr<TypeDefinition> group = TypeInformation::typeDefinitionForRecursionGroup(types);
RefPtr<TypeDefinition> projection = TypeInformation::typeDefinitionForProjection(group->index(), 0);
m_info->typeSignatures.uncheckedAppend(projection.releaseNonNull());
} else if (!signature->is<RecursionGroup>()) {
// Recursion group parsing will append the entries itself, as there may
// be multiple entries that need to be added to the type section for
// each recursion group.
m_info->typeSignatures.uncheckedAppend(signature.releaseNonNull());
// Recursion group parsing will append the entries itself, as there may
// be multiple entries that need to be added to the type section for
// each recursion group.
if (!signature->is<RecursionGroup>()) {
if (signature->hasRecursiveReference()) {
Vector<TypeIndex> types;
bool result = types.tryAppend(signature->index());
WASM_PARSER_FAIL_IF(!result, "can't allocate enough memory for Type section's ", i, "th signature");
RefPtr<TypeDefinition> group = TypeInformation::typeDefinitionForRecursionGroup(types);
RefPtr<TypeDefinition> projection = TypeInformation::typeDefinitionForProjection(group->index(), 0);
m_info->typeSignatures.uncheckedAppend(projection.releaseNonNull());
} else
m_info->typeSignatures.uncheckedAppend(signature.releaseNonNull());
}
} else
m_info->typeSignatures.uncheckedAppend(signature.releaseNonNull());
@@ -849,6 +850,10 @@ auto SectionParser::parseRecursionGroup(uint32_t position, RefPtr<TypeDefinition
WASM_FAIL_IF_HELPER_FAILS(parseArrayType(i, signature));
break;
}
case TypeKind::Sub: {
WASM_FAIL_IF_HELPER_FAILS(parseSubtype(i, signature, types, DeferSubtypeCheck::Yes));
break;
}
default:
return fail(i, "th Type is non-Func, non-Struct, and non-Array ", typeKind);
}
@@ -870,22 +875,152 @@ auto SectionParser::parseRecursionGroup(uint32_t position, RefPtr<TypeDefinition
for (uint32_t i = 0; i < typeCount; ++i) {
RefPtr<TypeDefinition> projection = TypeInformation::typeDefinitionForProjection(recursionGroup->index(), i);
WASM_PARSER_FAIL_IF(!projection, "can't allocate enough memory for recursion group's ", i, "th projection");

// Checking subtyping requirements has to be deferred until we construct
// projections in case recursive references show up in the type.
if (TypeInformation::get(types[i]).is<Subtype>())
WASM_PARSER_FAIL_IF(!checkSubtypeValidity(projection->unroll()), "structural type is not a subtype of the specified supertype");

m_info->typeSignatures.uncheckedAppend(projection.releaseNonNull());
}
} else {
// If a type definition is idempotent for the below replacement, then it is non-recursive and doesn't need a projection.
if (*firstSignature == firstSignature->replacePlaceholders(recursionGroup->index()))
if (!firstSignature->hasRecursiveReference())
m_info->typeSignatures.uncheckedAppend(firstSignature.releaseNonNull());
else {
RefPtr<TypeDefinition> projection = TypeInformation::typeDefinitionForProjection(recursionGroup->index(), 0);
WASM_PARSER_FAIL_IF(!projection, "can't allocate enough memory for recursion group's 0th projection");
if (firstSignature->is<Subtype>())
WASM_PARSER_FAIL_IF(!checkSubtypeValidity(projection->unroll()), "structural type is not a subtype of the specified supertype");
m_info->typeSignatures.uncheckedAppend(projection.releaseNonNull());
}
}

return { };
}

// Checks subtyping between a concrete structural type and a parent type definition,
// following the subtyping relation described in the GC proposal specification:
//
// https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md#structural-types
//
// The subtype argument should be unrolled before it is passed here, if needed.
bool SectionParser::checkStructuralSubtype(const TypeDefinition& subtype, const TypeDefinition& supertype)
{
ASSERT(subtype.is<FunctionSignature>() || subtype.is<StructType>() || subtype.is<ArrayType>());
const TypeDefinition& expanded = supertype.expand();
ASSERT(expanded.is<FunctionSignature>() || expanded.is<StructType>() || expanded.is<ArrayType>());

if (subtype.is<FunctionSignature>() && expanded.is<FunctionSignature>()) {
const FunctionSignature& subFunc = *subtype.as<FunctionSignature>();
const FunctionSignature& superFunc = *expanded.as<FunctionSignature>();
if (subFunc.argumentCount() == superFunc.argumentCount() && subFunc.returnCount() == superFunc.returnCount()) {
for (FunctionArgCount i = 0; i < subFunc.argumentCount(); ++i) {
if (!isSubtype(superFunc.argumentType(i), subFunc.argumentType(i)))
return false;
}
for (FunctionArgCount i = 0; i < subFunc.returnCount(); ++i) {
if (!isSubtype(subFunc.returnType(i), superFunc.returnType(i)))
return false;
}
return true;
}
} else if (subtype.is<StructType>() && expanded.is<StructType>()) {
const StructType& subStruct = *subtype.as<StructType>();
const StructType& superStruct = *expanded.as<StructType>();
if (subStruct.fieldCount() >= superStruct.fieldCount()) {
for (StructFieldCount i = 0; i < superStruct.fieldCount(); ++i) {
FieldType subField = subStruct.field(i);
FieldType superField = superStruct.field(i);
if (subField.mutability != superField.mutability)
return false;
if (subField.mutability == Mutability::Mutable && subField.type != superField.type)
return false;
if (subField.mutability == Mutability::Immutable && !isSubtype(subField.type, superField.type))
return false;
}
return true;
}
} else if (subtype.is<ArrayType>() && expanded.is<ArrayType>()) {
FieldType subField = subtype.as<ArrayType>()->elementType();
FieldType superField = expanded.as<ArrayType>()->elementType();
if (subField.mutability != superField.mutability)
return false;
if (subField.mutability == Mutability::Mutable && subField.type != superField.type)
return false;
if (subField.mutability == Mutability::Immutable && !isSubtype(subField.type, superField.type))
return false;
return true;
}

return false;
}

bool SectionParser::checkSubtypeValidity(const TypeDefinition& subtype)
{
ASSERT(subtype.is<Subtype>());
return checkStructuralSubtype(TypeInformation::get(subtype.as<Subtype>()->underlyingType()), TypeInformation::get(subtype.as<Subtype>()->superType()));
}

auto SectionParser::parseSubtype(uint32_t position, RefPtr<TypeDefinition>& subtype, Vector<TypeIndex>& recursionGroupTypes, DeferSubtypeCheck defer) -> PartialResult
{
ASSERT(Options::useWebAssemblyGC());

uint32_t supertypeCount;
WASM_PARSER_FAIL_IF(!parseVarUInt32(supertypeCount), "can't get ", position, "th subtype's supertype count");
WASM_PARSER_FAIL_IF(supertypeCount > maxSubtypeSupertypeCount, "number of supertypes for subtype at position ", position, " is too big ", supertypeCount, " maximum ", maxSubtypeSupertypeCount);

// The following assumes the MVP restriction that only up to a single supertype is allowed.
TypeIndex supertypeIndex = TypeDefinition::invalidIndex;
if (supertypeCount > 0) {
uint32_t typeIndex;
WASM_PARSER_FAIL_IF(!parseVarUInt32(typeIndex), "can't get subtype's supertype index");
WASM_PARSER_FAIL_IF(typeIndex >= m_info->typeCount() + recursionGroupTypes.size(), "supertype index is a forward reference");
// It is possible for a sub type declaration to refer to an earlier
// definition in the same recursion group, before it has been
// registered in the type signatures. In that case, we look it up
// in the recursion group type index vector.
if (typeIndex < m_info->typeCount())
supertypeIndex = TypeInformation::get(m_info->typeSignatures[typeIndex]);
else
supertypeIndex = recursionGroupTypes[typeIndex - m_info->typeCount()];
}

int8_t typeKind;
WASM_PARSER_FAIL_IF(!parseInt7(typeKind), "can't get subtype's underlying Type's type");
RefPtr<TypeDefinition> underlyingType;
switch (static_cast<TypeKind>(typeKind)) {
case TypeKind::Func: {
WASM_FAIL_IF_HELPER_FAILS(parseFunctionType(position, underlyingType));
break;
}
case TypeKind::Struct: {
WASM_FAIL_IF_HELPER_FAILS(parseStructType(position, underlyingType));
break;
}
case TypeKind::Array: {
WASM_FAIL_IF_HELPER_FAILS(parseArrayType(position, underlyingType));
break;
}
default:
return fail("invalid structural type definition for subtype ", typeKind);
}

if (defer == DeferSubtypeCheck::No)
WASM_PARSER_FAIL_IF(!checkStructuralSubtype(*underlyingType, TypeInformation::get(supertypeIndex)), "structural type is not a subtype of the specified supertype");

// When no supertypes are specified, we will normalize type definitions to
// not have the subtype. This ensures that type shorthands and the full
// subtype form are represented in the same way.
if (!supertypeCount) {
subtype = underlyingType;
return { };
}

subtype = TypeInformation::typeDefinitionForSubtype(supertypeIndex, TypeInformation::get(*underlyingType));

return { };
}

auto SectionParser::parseI32InitExprForElementSection(std::optional<I32InitExpr>& initExpr) -> PartialResult
{
return parseI32InitExpr(initExpr, "Element init_expr must produce an i32"_s);
@@ -51,6 +51,8 @@ class SectionParser final : public Parser<void> {
PartialResult WARN_UNUSED_RETURN parseCustom();

private:
enum class DeferSubtypeCheck { Yes, No };

template <typename ...Args>
NEVER_INLINE UnexpectedResult WARN_UNUSED_RETURN fail(Args... args) const
{
@@ -73,6 +75,7 @@ class SectionParser final : public Parser<void> {
PartialResult WARN_UNUSED_RETURN parseStructType(uint32_t position, RefPtr<TypeDefinition>&);
PartialResult WARN_UNUSED_RETURN parseArrayType(uint32_t position, RefPtr<TypeDefinition>&);
PartialResult WARN_UNUSED_RETURN parseRecursionGroup(uint32_t position, RefPtr<TypeDefinition>&);
PartialResult WARN_UNUSED_RETURN parseSubtype(uint32_t position, RefPtr<TypeDefinition>&, Vector<TypeIndex>&, DeferSubtypeCheck = DeferSubtypeCheck::No);

PartialResult WARN_UNUSED_RETURN validateElementTableIdx(uint32_t);
PartialResult WARN_UNUSED_RETURN parseI32InitExprForElementSection(std::optional<I32InitExpr>&);
@@ -83,6 +86,9 @@ class SectionParser final : public Parser<void> {

PartialResult WARN_UNUSED_RETURN parseI32InitExprForDataSection(std::optional<I32InitExpr>&);

static bool checkStructuralSubtype(const TypeDefinition&, const TypeDefinition&);
static bool checkSubtypeValidity(const TypeDefinition&);

size_t m_offsetInSource;
Ref<ModuleInformation> m_info;
};

0 comments on commit 431164c

Please sign in to comment.