Skip to content
Permalink
Browse files
Add support for WebAssembly GC recursion groups
https://bugs.webkit.org/show_bug.cgi?id=239666

Reviewed by Justin Michaud.

Add support for the Wasm GC proposal's recursion groups. These are
a new kind of TypeDefinition that allows for recursive type
definitions. Recursive type index references are only allowed with
recursion group definitions.

Recursion groups can be stored hash-consed like other type
definitions, but to successfully compare them they must store several
definitions in the type store:

  - The recursion group itself (rec (type ...) ...)
  - Each type component in the group (type ...), where any potential
    recursive references are represented as a special value type.
  - Projections into the recursion group ((rec ...).<i>)

Projections can be expanded into the underlying function/struct/etc
type when the validator needs to examine the structure of the type.
This operation exposes the type component in the group, and replaces
recursive references with the appropriate projection (which can then
be expanded if required, and so on).

If expansion becomes a bottleneck, it is easy to memoize it with the
addition of a TypeIndex to TypeIndex mapping in the type store.

Generally, the expansion operation is needed when code relies on the
structure of the underlying type. For functions, these uses usually
look like `signature.as<FunctionSignature>()`. The signature *must* be
expanded before such a conversion is called.

When signatures need to be compared for equality, such as for
call_indirect or for matching function import/export, the comparison
should generally be done by the type index of the projection. That is,
*not* by equality of the underlying/expanded type.

Recursive references are not represented in the binary format and are
internal to the semantics and implementation. To avoid using extra
opcodes, they are internally represented as a type that uses the
opcode for recursion group (TypeKind::Rec), which cannot normally be
used for value types in the binary format, with a heap type that
encodes the index into the recursion group's type list.

* JSTests/wasm/gc/rec.js: Added.
(module):
(testRecDeclaration):
* JSTests/wasm/wasm.json:
* Source/JavaScriptCore/wasm/WasmAirIRGenerator.cpp:
(JSC::Wasm::AirIRGenerator::AirIRGenerator):
(JSC::Wasm::AirIRGenerator::addCallIndirect):
* Source/JavaScriptCore/wasm/WasmB3IRGenerator.cpp:
(JSC::Wasm::B3IRGenerator::addCallIndirect):
* Source/JavaScriptCore/wasm/WasmBBQPlan.cpp:
(JSC::Wasm::BBQPlan::work):
(JSC::Wasm::BBQPlan::compileFunction):
* Source/JavaScriptCore/wasm/WasmCallee.h:
* Source/JavaScriptCore/wasm/WasmFormat.h:
(JSC::Wasm::isValueType):
* Source/JavaScriptCore/wasm/WasmFunctionCodeBlockGenerator.cpp:
(JSC::Wasm::FunctionCodeBlockGenerator::addSignature):
* Source/JavaScriptCore/wasm/WasmFunctionCodeBlockGenerator.h:
* Source/JavaScriptCore/wasm/WasmFunctionParser.h:
(JSC::Wasm::splitStack):
(JSC::Wasm::FunctionParser<Context>::FunctionParser):
(JSC::Wasm::FunctionParser<Context>::parse):
(JSC::Wasm::FunctionParser<Context>::parseExpression):
(JSC::Wasm::FunctionParser<Context>::parseUnreachableExpression):
* Source/JavaScriptCore/wasm/WasmInstance.cpp:
(JSC::Wasm::Instance::initElementSegment):
* Source/JavaScriptCore/wasm/WasmLLIntGenerator.cpp:
(JSC::Wasm::LLIntGenerator::callInformationForCaller):
(JSC::Wasm::LLIntGenerator::callInformationForCallee):
(JSC::Wasm::LLIntGenerator::addArguments):
(JSC::Wasm::LLIntGenerator::addCallIndirect):
(JSC::Wasm::LLIntGenerator::addCallRef):
* Source/JavaScriptCore/wasm/WasmLLIntPlan.cpp:
(JSC::Wasm::LLIntPlan::didCompleteCompilation):
* Source/JavaScriptCore/wasm/WasmLimits.h:
* Source/JavaScriptCore/wasm/WasmParser.h:
(JSC::Wasm::Parser<SuccessType>::Parser):
(JSC::Wasm::Parser<SuccessType>::parseHeapType):
(JSC::Wasm::Parser<SuccessType>::parseValueType):
* Source/JavaScriptCore/wasm/WasmSectionParser.cpp:
(JSC::Wasm::SectionParser::parseType):
(JSC::Wasm::SectionParser::parseElement):
(JSC::Wasm::SectionParser::parseRecursionGroup):
* Source/JavaScriptCore/wasm/WasmSectionParser.h:
* Source/JavaScriptCore/wasm/WasmSlowPaths.cpp:
(JSC::LLInt::doWasmCallIndirect):
(JSC::LLInt::doWasmCallRef):
* Source/JavaScriptCore/wasm/WasmTypeDefinition.cpp:
(JSC::Wasm::TypeDefinition::dump const):
(JSC::Wasm::RecursionGroup::toString const):
(JSC::Wasm::RecursionGroup::dump const):
(JSC::Wasm::Projection::toString const):
(JSC::Wasm::Projection::dump const):
(JSC::Wasm::computeRecursionGroupHash):
(JSC::Wasm::computeProjectionHash):
(JSC::Wasm::TypeDefinition::hash const):
(JSC::Wasm::TypeDefinition::tryCreateFunctionSignature):
(JSC::Wasm::TypeDefinition::tryCreateStructType):
(JSC::Wasm::TypeDefinition::tryCreateRecursionGroup):
(JSC::Wasm::TypeDefinition::tryCreateProjection):
(JSC::Wasm::TypeDefinition::substitute):
(JSC::Wasm::TypeDefinition::replacePlaceholders const):
(JSC::Wasm::TypeDefinition::expand const):
(JSC::Wasm::FunctionParameterTypes::equal):
(JSC::Wasm::StructParameterTypes::equal):
(JSC::Wasm::RecursionGroupParameterTypes::hash):
(JSC::Wasm::RecursionGroupParameterTypes::equal):
(JSC::Wasm::RecursionGroupParameterTypes::translate):
(JSC::Wasm::ProjectionParameterTypes::hash):
(JSC::Wasm::ProjectionParameterTypes::equal):
(JSC::Wasm::ProjectionParameterTypes::translate):
(JSC::Wasm::TypeInformation::typeDefinitionForRecursionGroup):
(JSC::Wasm::TypeInformation::typeDefinitionForProjection):
* Source/JavaScriptCore/wasm/WasmTypeDefinition.h:
(JSC::Wasm::RecursionGroup::RecursionGroup):
(JSC::Wasm::RecursionGroup::typeCount const):
(JSC::Wasm::RecursionGroup::type const):
(JSC::Wasm::RecursionGroup::getType):
(JSC::Wasm::RecursionGroup::storage):
(JSC::Wasm::RecursionGroup::storage const):
(JSC::Wasm::Projection::Projection):
(JSC::Wasm::Projection::recursionGroup const):
(JSC::Wasm::Projection::index const):
(JSC::Wasm::Projection::getRecursionGroup):
(JSC::Wasm::Projection::getIndex):
(JSC::Wasm::Projection::storage):
(JSC::Wasm::Projection::storage const):
(JSC::Wasm::TypeDefinition::TypeDefinition):
(JSC::Wasm::TypeDefinition::allocatedRecursionGroupSize):
(JSC::Wasm::TypeDefinition::allocatedProjectionSize):
* Source/JavaScriptCore/wasm/WasmTypeDefinitionInlines.h:
(JSC::Wasm::TypeInformation::getFunctionSignature):
* Source/JavaScriptCore/wasm/js/WasmToJS.cpp:
(JSC::Wasm::wasmToJS):
* Source/JavaScriptCore/wasm/js/WebAssemblyModuleRecord.cpp:
(JSC::WebAssemblyModuleRecord::initializeExports):
* Source/JavaScriptCore/wasm/wasm.json:

Canonical link: https://commits.webkit.org/253491@main
  • Loading branch information
takikawa authored and justinmichaud committed Aug 16, 2022
1 parent 610abf5 commit 5f3859595e5e1e337beac77a6b45ec05421e1842
Show file tree
Hide file tree
Showing 24 changed files with 763 additions and 62 deletions.
@@ -0,0 +1,235 @@
//@ runWebAssemblySuite("--useWebAssemblyTypedFunctionReferences=true", "--useWebAssemblyGC=true")

import * as assert from "../assert.js";
import { compile, instantiate } from "./wast-wrapper.js";

function module(bytes, valid = true) {
let buffer = new ArrayBuffer(bytes.length);
let view = new Uint8Array(buffer);
for (let i = 0; i < bytes.length; ++i) {
view[i] = bytes.charCodeAt(i);
}
return new WebAssembly.Module(buffer);
}

function testRecDeclaration() {
instantiate(`
(module
(rec (type (func)) (type (struct)))
)
`);

instantiate(`
(module
(rec (type (func)) (type (struct)))
(func (type 0))
)
`);

assert.throws(
() => compile(`
(module
(rec (type (struct)) (type (func)))
(func (type 0))
)
`),
WebAssembly.CompileError,
"type signature was not a function signature"
);

instantiate(`
(module
(rec
(type (func (result (ref 1))))
(type (func (result (ref 0)))))
)
`);

instantiate(`
(module
(rec
(type (func))
(type (func (result (ref 0)))))
)
`);

instantiate(`
(module
(rec (type (func)) (type (struct)))
(rec (type (func)) (type (struct)))
(elem declare funcref (ref.func 0))
(func (type 0))
(func (result (ref 2)) (ref.func 0))
)
`);

{
let m1 = instantiate(`
(module
(rec (type (func)) (type (struct)))
(func (export "f") (type 0))
)
`);
instantiate(`
(module
(rec (type (func)) (type (struct)))
(func (import "m" "f") (type 0))
(start 0)
)
`, { m: { f: m1.exports.f } });
}

{
let m1 = instantiate(`
(module
(rec (type (func)) (type (struct)))
(func (export "f") (type 0))
)
`);
assert.throws(
() => instantiate(`
(module
(rec (type (struct)) (type (func)))
(func (import "m" "f") (type 1))
(start 0)
)
`, { m: { f: m1.exports.f } }),
WebAssembly.LinkError,
"imported function m:f signature doesn't match the provided WebAssembly function's signature"
);
}

{
let m1 = instantiate(`
(module
(rec (type (func)) (type (struct)))
(elem declare funcref (ref.func 0))
(func)
(func (export "f") (result (ref 0)) (ref.func 0))
)
`);
assert.throws(
() => instantiate(`
(module
(rec (type (struct)) (type (func)))
(func (import "m" "f") (type 1))
)
`, { m: { f: m1.exports.f } }),
WebAssembly.LinkError,
"imported function m:f signature doesn't match the provided WebAssembly function's signature"
);
}

assert.throws(
() => compile(`
(module
(rec (type (func)) (type (struct)))
(rec (type (struct)) (type (func)))
(global (ref 0) (ref.func 0))
(func (type 3))
)
`),
WebAssembly.CompileError,
"Global init_expr opcode of type Ref doesn't match global's type Ref"
);

instantiate(`
(module
(rec (type (func (result (ref 1))))
(type (func (result (ref 0)))))
(elem declare funcref (ref.func 0))
(elem declare funcref (ref.func 1))
(func (type 0) (ref.func 1))
(func (type 1) (ref.func 0))
)
`);

assert.throws(
() => compile(`
(module
(rec (type (func (result (ref 1))))
(type (func (result (ref 0)))))
(elem declare funcref (ref.func 0))
(elem declare funcref (ref.func 1))
(func (type 0) (ref.func 1))
(func (type 1) (ref.func 1))
)
`),
WebAssembly.CompileError,
"control flow returns with unexpected type. Ref is not a Ref, in function at index 1"
);

instantiate(`
(module
(rec (type (func (param i32))) (type (struct)))
(elem declare funcref (ref.func 0))
(func (type 0))
(func (call_ref (i32.const 42) (ref.func 0)))
(start 1)
)
`);

instantiate(`
(module
(rec (type (func (result i32))) (type (struct)))
(rec (type (struct)) (type (func (result i32))))
(func (type 0)
(block (type 3)
(i32.const 42)))
)
`);

instantiate(`
(module
(rec (type (func (result i32))) (type (struct)))
(rec (type (struct)) (type (func (result i32))))
(func (type 0)
(loop (type 3)
(i32.const 42)))
)
`);

instantiate(`
(module
(rec (type (func (result i32))) (type (struct)))
(rec (type (struct)) (type (func (result i32))))
(func (type 0)
(i32.const 1)
(if (type 3) (then (i32.const 42)) (else (i32.const 43))))
)
`);

instantiate(`
(module
(rec (type (func)) (type (struct)))
(table 5 funcref)
(elem (offset (i32.const 0)) funcref (ref.func 0))
(func (type 0))
(func (call_indirect (type 0) (i32.const 0)))
(start 1)
)
`);

// Ensure implicit rec groups are accounted for, and treated
// correctly with regards to equality.
instantiate(`
(module
(type $a (struct (field i32)))
(rec (type $b (struct (field i32))))
(type $c (struct (field i32)))
(func (result (ref null $a)) (ref.null $b))
(func (result (ref null $a)) (ref.null $c))
(func (result (ref null $b)) (ref.null $a))
(func (result (ref null $b)) (ref.null $c))
(func (result (ref null $c)) (ref.null $a))
(func (result (ref null $c)) (ref.null $b)))
`);

// This is the same test as above, but using a particular binary encoding.
// The encoding for this test specifically uses both shorthand and the full
// rec form to test the equivalence of the two.
new WebAssembly.Instance(module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x9e\x80\x80\x80\x00\x06\x5f\x01\x7f\x00\x4f\x01\x5f\x01\x7f\x00\x5f\x01\x7f\x00\x60\x00\x01\x6c\x00\x60\x00\x01\x6c\x01\x60\x00\x01\x6c\x02\x03\x87\x80\x80\x80\x00\x06\x03\x03\x04\x04\x05\x05\x0a\xb7\x80\x80\x80\x00\x06\x84\x80\x80\x80\x00\x00\xd0\x01\x0b\x84\x80\x80\x80\x00\x00\xd0\x02\x0b\x84\x80\x80\x80\x00\x00\xd0\x00\x0b\x84\x80\x80\x80\x00\x00\xd0\x02\x0b\x84\x80\x80\x80\x00\x00\xd0\x00\x0b\x84\x80\x80\x80\x00\x00\xd0\x01\x0b"));
}

testRecDeclaration();
@@ -18,6 +18,7 @@
"i31ref": { "type": "varint7", "value": -22, "b3type": "B3::Void" },
"func": { "type": "varint7", "value": -32, "b3type": "B3::Void" },
"struct": { "type": "varint7", "value": -33, "b3type": "B3::Void" },
"rec": { "type": "varint7", "value": -49, "b3type": "B3::Void" },
"void": { "type": "varint7", "value": -64, "b3type": "B3::Void" }
},
"value_type": ["i32", "i64", "f32", "f64", "externref", "funcref"],
@@ -948,7 +948,7 @@ void AirIRGenerator::restoreWasmContextInstance(BasicBlock* block, TypedTmp inst
emitPatchpoint(block, patchpoint, Tmp(), instance);
}

AirIRGenerator::AirIRGenerator(const ModuleInformation& info, B3::Procedure& procedure, InternalFunction* compilation, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, MemoryMode mode, unsigned functionIndex, std::optional<bool> hasExceptionHandlers, TierUpCount* tierUp, const TypeDefinition& signature, unsigned& osrEntryScratchBufferSize)
AirIRGenerator::AirIRGenerator(const ModuleInformation& info, B3::Procedure& procedure, InternalFunction* compilation, Vector<UnlinkedWasmToWasmCall>& unlinkedWasmToWasmCalls, MemoryMode mode, unsigned functionIndex, std::optional<bool> hasExceptionHandlers, TierUpCount* tierUp, const TypeDefinition& originalSignature, unsigned& osrEntryScratchBufferSize)
: m_info(info)
, m_mode(mode)
, m_functionIndex(functionIndex)
@@ -1056,6 +1056,7 @@ AirIRGenerator::AirIRGenerator(const ModuleInformation& info, B3::Procedure& pro
m_mainEntrypointStart = m_code.addBlock();
m_currentBlock = m_mainEntrypointStart;

const TypeDefinition& signature = originalSignature.expand();
ASSERT(!m_locals.size());
m_locals.grow(signature.as<FunctionSignature>()->argumentCount());
for (unsigned i = 0; i < signature.as<FunctionSignature>()->argumentCount(); ++i) {
@@ -3780,9 +3781,10 @@ auto AirIRGenerator::addCall(uint32_t functionIndex, const TypeDefinition& signa
return { };
}

auto AirIRGenerator::addCallIndirect(unsigned tableIndex, const TypeDefinition& signature, Vector<ExpressionType>& args, ResultList& results) -> PartialResult
auto AirIRGenerator::addCallIndirect(unsigned tableIndex, const TypeDefinition& originalSignature, Vector<ExpressionType>& args, ResultList& results) -> PartialResult
{
ExpressionType calleeIndex = args.takeLast();
const TypeDefinition& signature = originalSignature.expand();
ASSERT(signature.as<FunctionSignature>()->argumentCount() == args.size());
ASSERT(m_info.tableCount() > tableIndex);
ASSERT(m_info.tables[tableIndex].type() == TableElementType::Funcref);
@@ -3849,7 +3851,7 @@ auto AirIRGenerator::addCallIndirect(unsigned tableIndex, const TypeDefinition&
});

ExpressionType expectedSignatureIndex = g64();
append(Move, Arg::bigImm(TypeInformation::get(signature)), expectedSignatureIndex);
append(Move, Arg::bigImm(TypeInformation::get(originalSignature)), expectedSignatureIndex);
emitCheck([&] {
return Inst(Branch64, nullptr, Arg::relCond(MacroAssembler::NotEqual), calleeSignatureIndex, expectedSignatureIndex);
}, [=, this] (CCallHelpers& jit, const B3::StackmapGenerationParams&) {
@@ -3066,9 +3066,10 @@ auto B3IRGenerator::addCall(uint32_t functionIndex, const TypeDefinition& signat
return { };
}

auto B3IRGenerator::addCallIndirect(unsigned tableIndex, const TypeDefinition& signature, Vector<ExpressionType>& args, ResultList& results) -> PartialResult
auto B3IRGenerator::addCallIndirect(unsigned tableIndex, const TypeDefinition& originalSignature, Vector<ExpressionType>& args, ResultList& results) -> PartialResult
{
Value* calleeIndex = get(args.takeLast());
const TypeDefinition& signature = originalSignature.expand();
ASSERT(signature.as<FunctionSignature>()->argumentCount() == args.size());

m_makesCalls = true;
@@ -3127,7 +3128,7 @@ auto B3IRGenerator::addCallIndirect(unsigned tableIndex, const TypeDefinition& s

// Check the signature matches the value we expect.
{
Value* expectedSignatureIndex = m_currentBlock->appendNew<Const64Value>(m_proc, origin(), TypeInformation::get(signature));
Value* expectedSignatureIndex = m_currentBlock->appendNew<Const64Value>(m_proc, origin(), TypeInformation::get(originalSignature));
CheckValue* check = m_currentBlock->appendNew<CheckValue>(m_proc, Check, origin(),
m_currentBlock->appendNew<Value>(m_proc, NotEqual, origin(), calleeSignatureIndex, expectedSignatureIndex));

@@ -137,7 +137,7 @@ void BBQPlan::work(CompilationEffort effort)

size_t functionIndexSpace = m_functionIndex + m_moduleInformation->importFunctionCount();
TypeIndex typeIndex = m_moduleInformation->internalFunctionTypeIndices[m_functionIndex];
const TypeDefinition& signature = TypeInformation::get(typeIndex);
const TypeDefinition& signature = TypeInformation::get(typeIndex).expand();
function->entrypoint.compilation = makeUnique<Compilation>(
FINALIZE_WASM_CODE_FOR_MODE(CompilationMode::BBQMode, linkBuffer, JITCompilationPtrTag, "WebAssembly BBQ function[%i] %s name %s", m_functionIndex, signature.toString().ascii().data(), makeString(IndexOrName(functionIndexSpace, m_moduleInformation->nameSection->get(functionIndexSpace))).ascii().data()),
WTFMove(context.wasmEntrypointByproducts));
@@ -206,7 +206,7 @@ void BBQPlan::compileFunction(uint32_t functionIndex)
if (m_exportedFunctionIndices.contains(functionIndex) || m_moduleInformation->referencedFunctions().contains(functionIndex)) {
Locker locker { m_lock };
TypeIndex typeIndex = m_moduleInformation->internalFunctionTypeIndices[functionIndex];
const TypeDefinition& signature = TypeInformation::get(typeIndex);
const TypeDefinition& signature = TypeInformation::get(typeIndex).expand();

m_compilationContexts[functionIndex].embedderEntrypointJIT = makeUnique<CCallHelpers>();
auto embedderToWasmInternalFunction = createJSToWasmWrapper(*m_compilationContexts[functionIndex].embedderEntrypointJIT, signature, &m_unlinkedWasmToWasmCalls[functionIndex], m_moduleInformation.get(), m_mode, functionIndex);
@@ -265,7 +265,7 @@ class LLIntCallee final : public Callee {

LLIntTierUpCounter& tierUpCounter() { return m_tierUpCounter; }

const FunctionSignature& signature(unsigned index) const
const TypeDefinition& signature(unsigned index) const
{
return *m_signatures[index];
}
@@ -304,7 +304,7 @@ class LLIntCallee final : public Callee {
std::unique_ptr<WasmInstructionStream> m_instructions;
const void* m_instructionsRawPointer { nullptr };
FixedVector<WasmInstructionStream::Offset> m_jumpTargets;
FixedVector<const FunctionSignature*> m_signatures;
FixedVector<const TypeDefinition*> m_signatures;
OutOfLineJumpTargets m_outOfLineJumpTargets;
LLIntTierUpCounter m_tierUpCounter;
FixedVector<JumpTable> m_jumpTables;
@@ -74,6 +74,10 @@ inline bool isValueType(Type type)
case TypeKind::Ref:
case TypeKind::RefNull:
return Options::useWebAssemblyTypedFunctionReferences();
// Rec type kinds are used internally to represent `rec.<i>` references
// within recursion groups. They are invalid in other contexts.
case TypeKind::Rec:
return Options::useWebAssemblyGC();
default:
break;
}
@@ -51,7 +51,7 @@ WasmInstructionStream::Offset FunctionCodeBlockGenerator::outOfLineJumpOffset(Wa
return m_outOfLineJumpTargets.get(bytecodeOffset);
}

unsigned FunctionCodeBlockGenerator::addSignature(const FunctionSignature& signature)
unsigned FunctionCodeBlockGenerator::addSignature(const TypeDefinition& signature)
{
unsigned index = m_signatures.size();
m_signatures.append(&signature);
@@ -48,7 +48,7 @@ class BytecodeGeneratorBase;
namespace Wasm {

class LLIntCallee;
class FunctionSignature;
class TypeDefinition;
struct GeneratorTraits;

struct JumpTableEntry {
@@ -116,7 +116,7 @@ class FunctionCodeBlockGenerator {

HashMap<WasmInstructionStream::Offset, LLIntTierUpCounter::OSREntryData>& tierUpCounter() { return m_tierUpCounter; }

unsigned addSignature(const FunctionSignature&);
unsigned addSignature(const TypeDefinition&);

JumpTable& addJumpTable(size_t numberOfEntries);
unsigned numberOfJumpTables() const;
@@ -140,7 +140,7 @@ class FunctionCodeBlockGenerator {
std::unique_ptr<WasmInstructionStream> m_instructions;
const void* m_instructionsRawPointer { nullptr };
Vector<WasmInstructionStream::Offset> m_jumpTargets;
Vector<const FunctionSignature*> m_signatures;
Vector<const TypeDefinition*> m_signatures;
OutOfLineJumpTargets m_outOfLineJumpTargets;
HashMap<WasmInstructionStream::Offset, LLIntTierUpCounter::OSREntryData> m_tierUpCounter;
Vector<JumpTable> m_jumpTables;

0 comments on commit 5f38595

Please sign in to comment.