зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1853454 - wasm: Add exnref, try_table, and throw_ref. r=yury
This commit: - Adds exnref feature and configuration flags - Adds an exnref type - Not exposable to JS, internal to wasm only - Adds a throw_ref instruction which throws an exnref - This will trap on null - Adds a try_table instruction which is intended to replace the old try/catch/catch_all/delegate instruction set. Differential Revision: https://phabricator.services.mozilla.com/D193239
This commit is contained in:
Родитель
553181f924
Коммит
b40d4aea04
|
@ -132,10 +132,21 @@ enum class WasmFeatureStage {
|
|||
/* compile predicate */ true, \
|
||||
/* compiler predicate */ AnyCompilerAvailable(cx), \
|
||||
/* flag predicate */ true, \
|
||||
/* flag force enable */ false, \
|
||||
/* flag force enable */ WasmExnRefFlag(cx), \
|
||||
/* flag fuzz enable */ true, \
|
||||
/* shell flag */ "exceptions", \
|
||||
/* preference name */ "exceptions") \
|
||||
FEATURE( \
|
||||
/* capitalized name */ ExnRef, \
|
||||
/* lower case name */ exnref, \
|
||||
/* stage */ WasmFeatureStage::Experimental, \
|
||||
/* compile predicate */ true, \
|
||||
/* compiler predicate */ AnyCompilerAvailable(cx), \
|
||||
/* flag predicate */ true, \
|
||||
/* flag force enable */ false, \
|
||||
/* flag fuzz enable */ true, \
|
||||
/* shell flag */ "exnref", \
|
||||
/* preference name */ "exnref ") \
|
||||
FEATURE( \
|
||||
/* capitalized name */ FunctionReferences, \
|
||||
/* lower case name */ functionReferences, \
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|jit-test| --wasm-exnref; test-also=--wasm-compiler=optimizing; test-also=--wasm-compiler=baseline; test-also=--wasm-test-serialization; test-also=--test-wasm-await-tier2; include:wasm.js; skip-if: !wasmExnRefEnabled()
|
|
@ -0,0 +1,41 @@
|
|||
wasmFailValidateText(`(module
|
||||
(func
|
||||
throw_ref
|
||||
)
|
||||
)`, /popping value from empty stack/);
|
||||
|
||||
wasmValidateText(`(module
|
||||
(func (param exnref)
|
||||
local.get 0
|
||||
throw_ref
|
||||
)
|
||||
)`);
|
||||
|
||||
// Can rethrow a value
|
||||
{
|
||||
let {test} = wasmEvalText(`(module
|
||||
(tag $a)
|
||||
(func (export "test")
|
||||
try_table (result exnref) (catch_all_ref 0)
|
||||
throw $a
|
||||
unreachable
|
||||
end
|
||||
throw_ref
|
||||
)
|
||||
)`).exports;
|
||||
|
||||
assertErrorMessage(test, WebAssembly.Exception, /.*/);
|
||||
}
|
||||
|
||||
// Traps on null
|
||||
{
|
||||
let {test} = wasmEvalText(`(module
|
||||
(tag $a)
|
||||
(func (export "test")
|
||||
ref.null exn
|
||||
throw_ref
|
||||
)
|
||||
)`).exports;
|
||||
|
||||
assertErrorMessage(test, WebAssembly.RuntimeError, /null/);
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
// A try_table acts like a block label
|
||||
{
|
||||
let {test} = wasmEvalText(`(module
|
||||
(func (export "test") (result i32)
|
||||
try_table
|
||||
br 0
|
||||
(return i32.const 0)
|
||||
end
|
||||
(return i32.const 1)
|
||||
)
|
||||
)`).exports;
|
||||
assertEq(test(), 1);
|
||||
}
|
||||
|
||||
// A try_table can have results
|
||||
{
|
||||
let {test} = wasmEvalText(`(module
|
||||
(func (export "test") (result i32)
|
||||
try_table (result i32)
|
||||
i32.const 1
|
||||
br 0
|
||||
end
|
||||
)
|
||||
)`).exports;
|
||||
assertEq(test(), 1);
|
||||
}
|
||||
|
||||
// A try_table can have params
|
||||
{
|
||||
let {test} = wasmEvalText(`(module
|
||||
(func (export "test") (result i32)
|
||||
i32.const 1
|
||||
try_table (param i32)
|
||||
return
|
||||
end
|
||||
(return i32.const 0)
|
||||
)
|
||||
)`).exports;
|
||||
assertEq(test(), 1);
|
||||
}
|
||||
|
||||
// Test try_table catching exceptions
|
||||
{
|
||||
let {test} = wasmEvalText(`(module
|
||||
(tag $A (param i32))
|
||||
(tag $B (param i32))
|
||||
(tag $C)
|
||||
|
||||
(table funcref (elem $throwA $throwB $throwC $doNothing))
|
||||
|
||||
(type $empty (func))
|
||||
(func $throwA
|
||||
i32.const 1
|
||||
throw $A
|
||||
)
|
||||
(func $throwB
|
||||
i32.const 2
|
||||
throw $B
|
||||
)
|
||||
(func $throwC
|
||||
throw $C
|
||||
)
|
||||
(func $doNothing)
|
||||
|
||||
(func (export "test") (param i32) (result i32)
|
||||
block $handleA (result i32 exnref)
|
||||
block $handleB (result i32 exnref)
|
||||
block $handleUnknown (result exnref)
|
||||
try_table
|
||||
(catch_ref $A $handleA)
|
||||
(catch_ref $B $handleB)
|
||||
(catch_all_ref $handleUnknown)
|
||||
|
||||
(call_indirect (type $empty)
|
||||
local.get 0)
|
||||
end
|
||||
(; nothing threw ;)
|
||||
i32.const -1
|
||||
return
|
||||
end
|
||||
(; $handleUnknown ;)
|
||||
drop
|
||||
i32.const 3
|
||||
return
|
||||
end
|
||||
(; $handleB ;)
|
||||
drop
|
||||
return
|
||||
end
|
||||
(; $handleA ;)
|
||||
drop
|
||||
return
|
||||
)
|
||||
)`).exports;
|
||||
// Throwing A results in 1 from the payload from the catch
|
||||
assertEq(test(0), 1);
|
||||
// Throwing B results in 2 from the payload from the catch
|
||||
assertEq(test(1), 2);
|
||||
// Throwing C results in 3 from the constant in the catch_all
|
||||
assertEq(test(2), 3);
|
||||
// Not throwing anything gets -1 from the fallthrough
|
||||
assertEq(test(3), -1);
|
||||
}
|
||||
|
||||
// Test try_table catching exceptions without capturing the exnref
|
||||
{
|
||||
let {test} = wasmEvalText(`(module
|
||||
(tag $A (param i32))
|
||||
(tag $B (param i32))
|
||||
(tag $C)
|
||||
|
||||
(table funcref (elem $throwA $throwB $throwC $doNothing))
|
||||
|
||||
(type $empty (func))
|
||||
(func $throwA
|
||||
i32.const 1
|
||||
throw $A
|
||||
)
|
||||
(func $throwB
|
||||
i32.const 2
|
||||
throw $B
|
||||
)
|
||||
(func $throwC
|
||||
throw $C
|
||||
)
|
||||
(func $doNothing)
|
||||
|
||||
(func (export "test") (param i32) (result i32)
|
||||
block $handleA (result i32)
|
||||
block $handleB (result i32)
|
||||
block $handleUnknown
|
||||
try_table
|
||||
(catch $A $handleA)
|
||||
(catch $B $handleB)
|
||||
(catch_all $handleUnknown)
|
||||
|
||||
(call_indirect (type $empty)
|
||||
local.get 0)
|
||||
end
|
||||
(; nothing threw ;)
|
||||
i32.const -1
|
||||
return
|
||||
end
|
||||
(; $handleUnknown ;)
|
||||
i32.const 3
|
||||
return
|
||||
end
|
||||
(; $handleB ;)
|
||||
return
|
||||
end
|
||||
(; $handleA ;)
|
||||
return
|
||||
)
|
||||
)`).exports;
|
||||
// Throwing A results in 1 from the payload from the catch
|
||||
assertEq(test(0), 1);
|
||||
// Throwing B results in 2 from the payload from the catch
|
||||
assertEq(test(1), 2);
|
||||
// Throwing C results in 3 from the constant in the catch_all
|
||||
assertEq(test(2), 3);
|
||||
// Not throwing anything gets -1 from the fallthrough
|
||||
assertEq(test(3), -1);
|
||||
}
|
||||
|
||||
// Test try_table catching and rethrowing JS exceptions
|
||||
{
|
||||
let tag = new WebAssembly.Tag({parameters: []});
|
||||
let exn = new WebAssembly.Exception(tag, []);
|
||||
let values = [...WasmExternrefValues, exn];
|
||||
function throwJS(value) {
|
||||
throw value;
|
||||
}
|
||||
let {test} = wasmEvalText(`(module
|
||||
(import "" "tag" (tag $tag))
|
||||
(import "" "throwJS" (func $throwJS (param externref)))
|
||||
(func (export "test") (param externref)
|
||||
try_table (result exnref) (catch_ref $tag 0) (catch_all_ref 0)
|
||||
local.get 0
|
||||
call $throwJS
|
||||
return
|
||||
end
|
||||
throw_ref
|
||||
)
|
||||
)`, {"": {tag, throwJS}}).exports;
|
||||
|
||||
for (let value of values) {
|
||||
// TODO: A JS null value should become a non-null exnref that can be
|
||||
// rethrown without a trap.
|
||||
if (value === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
test(value);
|
||||
assertEq(true, false);
|
||||
} catch (thrownValue) {
|
||||
assertEq(thrownValue, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -81,6 +81,9 @@ struct Control {
|
|||
deadOnArrival(false),
|
||||
deadThenBranch(false),
|
||||
tryNoteIndex(0) {}
|
||||
|
||||
Control(Control&&) = default;
|
||||
Control(const Control&) = delete;
|
||||
};
|
||||
|
||||
// A vector of Nothing values, used for reading opcodes.
|
||||
|
@ -1394,10 +1397,12 @@ struct BaseCompiler final {
|
|||
[[nodiscard]] bool emitBodyDelegateThrowPad();
|
||||
|
||||
[[nodiscard]] bool emitTry();
|
||||
[[nodiscard]] bool emitTryTable();
|
||||
[[nodiscard]] bool emitCatch();
|
||||
[[nodiscard]] bool emitCatchAll();
|
||||
[[nodiscard]] bool emitDelegate();
|
||||
[[nodiscard]] bool emitThrow();
|
||||
[[nodiscard]] bool emitThrowRef();
|
||||
[[nodiscard]] bool emitRethrow();
|
||||
[[nodiscard]] bool emitEnd();
|
||||
[[nodiscard]] bool emitBr();
|
||||
|
@ -1454,6 +1459,7 @@ struct BaseCompiler final {
|
|||
[[nodiscard]] bool endIfThen(ResultType type);
|
||||
[[nodiscard]] bool endIfThenElse(ResultType type);
|
||||
[[nodiscard]] bool endTryCatch(ResultType type);
|
||||
[[nodiscard]] bool endTryTable(ResultType type);
|
||||
|
||||
void doReturn(ContinuationKind kind);
|
||||
void pushReturnValueOfCall(const FunctionCall& call, MIRType type);
|
||||
|
|
|
@ -3804,6 +3804,12 @@ bool BaseCompiler::emitEnd() {
|
|||
}
|
||||
iter_.popEnd();
|
||||
break;
|
||||
case LabelKind::TryTable:
|
||||
if (!endTryTable(type)) {
|
||||
return false;
|
||||
}
|
||||
iter_.popEnd();
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -4060,6 +4066,217 @@ bool BaseCompiler::emitTry() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool BaseCompiler::emitTryTable() {
|
||||
ResultType params;
|
||||
TryTableCatchVector catches;
|
||||
if (!iter_.readTryTable(¶ms, &catches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deadCode_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Simplifies jumping out, but it is also necessary so that control
|
||||
// can re-enter the catch handler without restoring registers.
|
||||
sync();
|
||||
|
||||
initControl(controlItem(), params);
|
||||
// Be conservative for BCE due to complex control flow in try blocks.
|
||||
controlItem().bceSafeOnExit = 0;
|
||||
|
||||
// Emit a landing pad that exceptions will jump into. Jump over it for now.
|
||||
Label skip;
|
||||
masm.jump(&skip);
|
||||
|
||||
StackHeight prePadHeight = fr.stackHeight();
|
||||
uint32_t padOffset = masm.currentOffset();
|
||||
uint32_t padStackHeight = masm.framePushed();
|
||||
|
||||
// Store the Instance that was left in InstanceReg by the exception
|
||||
// handling mechanism, that is this frame's Instance but with the exception
|
||||
// filled in Instance::pendingException.
|
||||
fr.storeInstancePtr(InstanceReg);
|
||||
|
||||
// Ensure we don't take the result register that we'll need for passing
|
||||
// results to the branch target. Also make sure we don't take the
|
||||
// InstanceReg which is used for loading tags.
|
||||
//
|
||||
// TODO: future proof this code for multiple result registers.
|
||||
ResultType resultRegs = ResultType::Single(RefType::extern_());
|
||||
needIntegerResultRegisters(resultRegs);
|
||||
#ifndef RABALDR_PIN_INSTANCE
|
||||
needPtr(RegPtr(InstanceReg));
|
||||
#endif
|
||||
|
||||
// Load exception and tag from instance, clearing it in the process.
|
||||
RegRef exn;
|
||||
RegRef exnTag;
|
||||
consumePendingException(&exn, &exnTag);
|
||||
|
||||
// Get a register to hold the tags for each catch
|
||||
RegRef catchTag = needRef();
|
||||
|
||||
// Release our reserved registers.
|
||||
#ifndef RABALDR_PIN_INSTANCE
|
||||
freePtr(RegPtr(InstanceReg));
|
||||
#endif
|
||||
freeIntegerResultRegisters(resultRegs);
|
||||
|
||||
MOZ_ASSERT(exn != InstanceReg);
|
||||
MOZ_ASSERT(exnTag != InstanceReg);
|
||||
MOZ_ASSERT(catchTag != InstanceReg);
|
||||
bool hadCatchAll = false;
|
||||
for (const TryTableCatch& tryTableCatch : catches) {
|
||||
ResultType labelParams = ResultType::Vector(tryTableCatch.labelType);
|
||||
|
||||
Control& target = controlItem(tryTableCatch.labelRelativeDepth);
|
||||
target.bceSafeOnExit = 0;
|
||||
|
||||
// Handle a catch_all by jumping to the target block
|
||||
if (tryTableCatch.tagIndex == CatchAllIndex) {
|
||||
// Capture the exnref if it has been requested
|
||||
if (tryTableCatch.captureExnRef) {
|
||||
pushRef(exn);
|
||||
}
|
||||
popBlockResults(labelParams, target.stackHeight, ContinuationKind::Jump);
|
||||
masm.jump(&target.label);
|
||||
// The registers holding the join values are free for the remainder of
|
||||
// this block.
|
||||
freeResultRegisters(labelParams);
|
||||
// Free the exn register, as code assumes that it's consumed by the final
|
||||
// catch_all.
|
||||
if (!tryTableCatch.captureExnRef) {
|
||||
freeRef(exn);
|
||||
}
|
||||
|
||||
// Break from the loop and skip the implicit rethrow that's needed
|
||||
// if we didn't have a catch_all
|
||||
hadCatchAll = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const TagType& tagType = *moduleEnv_.tags[tryTableCatch.tagIndex].type;
|
||||
const TagOffsetVector& tagOffsets = tagType.argOffsets();
|
||||
ResultType tagParams = tagType.resultType();
|
||||
|
||||
Label skip;
|
||||
loadTag(RegPtr(InstanceReg), tryTableCatch.tagIndex, catchTag);
|
||||
masm.branchPtr(Assembler::NotEqual, exnTag, catchTag, &skip);
|
||||
|
||||
// Get a register for unpacking exceptions. We re-use the exnTag register
|
||||
// as it is already reserved and dead from this point until the jump to the
|
||||
// target label.
|
||||
RegPtr data = RegPtr(exnTag);
|
||||
|
||||
// Unpack the tag and jump to the block
|
||||
masm.loadPtr(Address(exn, (int32_t)WasmExceptionObject::offsetOfData()),
|
||||
data);
|
||||
// This method can increase stk_.length() by an unbounded amount, so we need
|
||||
// to perform an allocation here to accomodate the variable number of
|
||||
// values. There is enough headroom for the fixed number of values. The
|
||||
// general case is handled in emitBody.
|
||||
if (!stk_.reserve(stk_.length() + labelParams.length())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < tagParams.length(); i++) {
|
||||
int32_t offset = tagOffsets[i];
|
||||
switch (tagParams[i].kind()) {
|
||||
case ValType::I32: {
|
||||
RegI32 reg = needI32();
|
||||
masm.load32(Address(data, offset), reg);
|
||||
pushI32(reg);
|
||||
break;
|
||||
}
|
||||
case ValType::I64: {
|
||||
RegI64 reg = needI64();
|
||||
masm.load64(Address(data, offset), reg);
|
||||
pushI64(reg);
|
||||
break;
|
||||
}
|
||||
case ValType::F32: {
|
||||
RegF32 reg = needF32();
|
||||
masm.loadFloat32(Address(data, offset), reg);
|
||||
pushF32(reg);
|
||||
break;
|
||||
}
|
||||
case ValType::F64: {
|
||||
RegF64 reg = needF64();
|
||||
masm.loadDouble(Address(data, offset), reg);
|
||||
pushF64(reg);
|
||||
break;
|
||||
}
|
||||
case ValType::V128: {
|
||||
#ifdef ENABLE_WASM_SIMD
|
||||
RegV128 reg = needV128();
|
||||
masm.loadUnalignedSimd128(Address(data, offset), reg);
|
||||
pushV128(reg);
|
||||
break;
|
||||
#else
|
||||
MOZ_CRASH("No SIMD support");
|
||||
#endif
|
||||
}
|
||||
case ValType::Ref: {
|
||||
RegRef reg = needRef();
|
||||
masm.loadPtr(Address(data, offset), reg);
|
||||
pushRef(reg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Capture the exnref if it has been requested
|
||||
if (tryTableCatch.captureExnRef) {
|
||||
pushRef(exn);
|
||||
}
|
||||
popBlockResults(labelParams, target.stackHeight, ContinuationKind::Jump);
|
||||
masm.jump(&target.label);
|
||||
// The registers holding the join values are free for the remainder of this
|
||||
// block.
|
||||
freeResultRegisters(labelParams);
|
||||
|
||||
// Reset the stack height for the skip
|
||||
fr.setStackHeight(prePadHeight);
|
||||
masm.bind(&skip);
|
||||
|
||||
// Re-assert ownership of the exnref register for the next branch of the
|
||||
// try switch.
|
||||
if (tryTableCatch.captureExnRef) {
|
||||
needRef(exn);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hadCatchAll) {
|
||||
// If none of the tag checks succeed and there is no catch_all,
|
||||
// then we rethrow the exception.
|
||||
if (!throwFrom(exn)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// `exn` should be consumed by the catch_all code so it doesn't leak
|
||||
MOZ_ASSERT(isAvailableRef(exn));
|
||||
}
|
||||
|
||||
freeRef(catchTag);
|
||||
freeRef(exnTag);
|
||||
|
||||
// Reset stack height for skip.
|
||||
fr.setStackHeight(prePadHeight);
|
||||
|
||||
masm.bind(&skip);
|
||||
|
||||
if (!startTryNote(&controlItem().tryNoteIndex)) {
|
||||
return false;
|
||||
}
|
||||
// The landing pad begins at this point
|
||||
TryNoteVector& tryNotes = masm.tryNotes();
|
||||
TryNote& tryNote = tryNotes[controlItem().tryNoteIndex];
|
||||
tryNote.setLandingPad(padOffset, padStackHeight);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BaseCompiler::emitCatchSetup(LabelKind kind, Control& tryCatch,
|
||||
const ResultType& resultType) {
|
||||
// Catch ends the try or last catch, so we finish this like endIfThen.
|
||||
|
@ -4460,6 +4677,12 @@ bool BaseCompiler::endTryCatch(ResultType type) {
|
|||
return pushBlockResults(type);
|
||||
}
|
||||
|
||||
bool BaseCompiler::endTryTable(ResultType type) {
|
||||
// Mark the end of the try body. This may insert a nop.
|
||||
finishTryNote(controlItem().tryNoteIndex);
|
||||
return endBlock(type);
|
||||
}
|
||||
|
||||
bool BaseCompiler::emitThrow() {
|
||||
uint32_t tagIndex;
|
||||
BaseNothingVector unused_argValues{};
|
||||
|
@ -4565,6 +4788,26 @@ bool BaseCompiler::emitThrow() {
|
|||
return throwFrom(exn);
|
||||
}
|
||||
|
||||
bool BaseCompiler::emitThrowRef() {
|
||||
Nothing unused{};
|
||||
|
||||
if (!iter_.readThrowRef(&unused)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (deadCode_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
RegRef exn = popRef();
|
||||
Label ok;
|
||||
masm.branchWasmAnyRefIsNull(false, exn, &ok);
|
||||
trap(Trap::NullPointerDereference);
|
||||
masm.bind(&ok);
|
||||
deadCode_ = true;
|
||||
return throwFrom(exn);
|
||||
}
|
||||
|
||||
bool BaseCompiler::emitRethrow() {
|
||||
uint32_t relativeDepth;
|
||||
if (!iter_.readRethrow(&relativeDepth)) {
|
||||
|
@ -9677,6 +9920,16 @@ bool BaseCompiler::emitBody() {
|
|||
return iter_.unrecognizedOpcode(&op);
|
||||
}
|
||||
CHECK_NEXT(emitRethrow());
|
||||
case uint16_t(Op::ThrowRef):
|
||||
if (!moduleEnv_.exnrefEnabled()) {
|
||||
return iter_.unrecognizedOpcode(&op);
|
||||
}
|
||||
CHECK_NEXT(emitThrowRef());
|
||||
case uint16_t(Op::TryTable):
|
||||
if (!moduleEnv_.exnrefEnabled()) {
|
||||
return iter_.unrecognizedOpcode(&op);
|
||||
}
|
||||
CHECK_NEXT(emitTryTable());
|
||||
case uint16_t(Op::Br):
|
||||
CHECK_NEXT(emitBr());
|
||||
case uint16_t(Op::BrIf):
|
||||
|
|
|
@ -636,6 +636,7 @@ inline ValType Decoder::uncheckedReadValType(const TypeContext& types) {
|
|||
switch (code) {
|
||||
case uint8_t(TypeCode::FuncRef):
|
||||
case uint8_t(TypeCode::ExternRef):
|
||||
case uint8_t(TypeCode::ExnRef):
|
||||
return RefType::fromTypeCode(TypeCode(code), true);
|
||||
case uint8_t(TypeCode::Ref):
|
||||
case uint8_t(TypeCode::NullableRef): {
|
||||
|
@ -683,6 +684,13 @@ inline bool Decoder::readPackedType(const TypeContext& types,
|
|||
*type = RefType::fromTypeCode(TypeCode(code), true);
|
||||
return true;
|
||||
}
|
||||
case uint8_t(TypeCode::ExnRef): {
|
||||
if (!features.exnref) {
|
||||
return fail("exnref not enabled");
|
||||
}
|
||||
*type = RefType::fromTypeCode(TypeCode(code), true);
|
||||
return true;
|
||||
}
|
||||
case uint8_t(TypeCode::Ref):
|
||||
case uint8_t(TypeCode::NullableRef): {
|
||||
#ifdef ENABLE_WASM_FUNCTION_REFERENCES
|
||||
|
@ -759,6 +767,13 @@ inline bool Decoder::readHeapType(const TypeContext& types,
|
|||
case uint8_t(TypeCode::ExternRef):
|
||||
*type = RefType::fromTypeCode(TypeCode(code), nullable);
|
||||
return true;
|
||||
case uint8_t(TypeCode::ExnRef): {
|
||||
if (!features.exnref) {
|
||||
return fail("exnref not enabled");
|
||||
}
|
||||
*type = RefType::fromTypeCode(TypeCode(code), nullable);
|
||||
return true;
|
||||
}
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case uint8_t(TypeCode::AnyRef):
|
||||
case uint8_t(TypeCode::I31Ref):
|
||||
|
|
|
@ -100,6 +100,9 @@ enum class TypeCode {
|
|||
// A reference to any array value.
|
||||
ArrayRef = 0x6a, // SLEB128(-0x16)
|
||||
|
||||
// A reference to an exception value.
|
||||
ExnRef = 0x69, // SLEB128(-0x17)
|
||||
|
||||
// A null reference in the any hierarchy.
|
||||
NullAnyRef = 0x71, // SLEB128(-0x0F)
|
||||
|
||||
|
@ -265,6 +268,7 @@ enum class Op {
|
|||
Catch = 0x07,
|
||||
Throw = 0x08,
|
||||
Rethrow = 0x09,
|
||||
ThrowRef = 0x0a,
|
||||
End = 0x0b,
|
||||
Br = 0x0c,
|
||||
BrIf = 0x0d,
|
||||
|
@ -288,6 +292,9 @@ enum class Op {
|
|||
SelectNumeric = 0x1b,
|
||||
SelectTyped = 0x1c,
|
||||
|
||||
// Additional exception operators
|
||||
TryTable = 0x1f,
|
||||
|
||||
// Variable access
|
||||
LocalGet = 0x20,
|
||||
LocalSet = 0x21,
|
||||
|
@ -1141,6 +1148,7 @@ static_assert(uint64_t(MaxArrayPayloadBytes) <
|
|||
|
||||
// These limits pertain to our WebAssembly implementation only.
|
||||
|
||||
static const unsigned MaxTryTableCatches = 10000;
|
||||
static const unsigned MaxBrTableElems = 1000000;
|
||||
static const unsigned MaxCodeSectionBytes = MaxModuleBytes;
|
||||
|
||||
|
|
|
@ -96,6 +96,9 @@ void wasm::Dump(RefType type, GenericPrinter& out) {
|
|||
case RefType::Array:
|
||||
literal = "arrayref";
|
||||
break;
|
||||
case RefType::Exn:
|
||||
literal = "exnref";
|
||||
break;
|
||||
case RefType::TypeRef: {
|
||||
MOZ_CRASH("type ref should not be possible here");
|
||||
}
|
||||
|
@ -137,6 +140,9 @@ void wasm::Dump(RefType type, GenericPrinter& out) {
|
|||
case RefType::Array:
|
||||
heapType = "array";
|
||||
break;
|
||||
case RefType::Exn:
|
||||
heapType = "exn";
|
||||
break;
|
||||
case RefType::TypeRef: {
|
||||
uintptr_t typeAddress = (uintptr_t)type.typeDef();
|
||||
out.printf("(ref %s0x%" PRIxPTR ")", type.isNullable() ? "null " : "",
|
||||
|
|
|
@ -25,6 +25,23 @@ namespace wasm {
|
|||
static const uint32_t CatchAllIndex = UINT32_MAX;
|
||||
static_assert(CatchAllIndex > MaxTags);
|
||||
|
||||
struct TryTableCatch {
|
||||
TryTableCatch()
|
||||
: tagIndex(CatchAllIndex), labelRelativeDepth(0), captureExnRef(false) {}
|
||||
|
||||
// The tag index for this catch, or CatchAllIndex for a catch_all.
|
||||
uint32_t tagIndex;
|
||||
// The relative depth of where to branch to when catching an exception.
|
||||
uint32_t labelRelativeDepth;
|
||||
// Whether the exnref that is caught should be captured and passed to the
|
||||
// branch target.
|
||||
bool captureExnRef;
|
||||
// The params that the target branch at `labelRelativeDepth` expects. This
|
||||
// includes any exnref that should or should not be captured.
|
||||
ValTypeVector labelType;
|
||||
};
|
||||
using TryTableCatchVector = Vector<TryTableCatch, 1, SystemAllocPolicy>;
|
||||
|
||||
} // namespace wasm
|
||||
} // namespace js
|
||||
|
||||
|
|
|
@ -63,18 +63,33 @@ using DefVector = Vector<MDefinition*, 8, SystemAllocPolicy>;
|
|||
using ControlInstructionVector =
|
||||
Vector<MControlInstruction*, 8, SystemAllocPolicy>;
|
||||
|
||||
struct TryControl {
|
||||
// Branches to bind to the try's landing pad.
|
||||
ControlInstructionVector landingPadPatches;
|
||||
// For `try_table`, the list of tagged catches and labels to branch to.
|
||||
TryTableCatchVector catches;
|
||||
// Whether this try is in the body and should catch any thrown exception.
|
||||
bool inBody;
|
||||
|
||||
TryControl() : inBody(false) {}
|
||||
|
||||
// Reset the try control for when it is cached in FunctionCompiler.
|
||||
void reset() {
|
||||
landingPadPatches.clearAndFree();
|
||||
catches.clearAndFree();
|
||||
inBody = false;
|
||||
}
|
||||
};
|
||||
using UniqueTryControl = UniquePtr<TryControl>;
|
||||
using VectorUniqueTryControl = Vector<UniqueTryControl, 2, SystemAllocPolicy>;
|
||||
|
||||
struct Control {
|
||||
MBasicBlock* block;
|
||||
// For a try-catch ControlItem, when its block's Labelkind is Try, this
|
||||
// collects branches to later bind and create the try's landing pad.
|
||||
ControlInstructionVector tryPadPatches;
|
||||
UniqueTryControl tryControl;
|
||||
|
||||
Control() : block(nullptr) {}
|
||||
|
||||
explicit Control(MBasicBlock* block) : block(block) {}
|
||||
|
||||
public:
|
||||
void setBlock(MBasicBlock* newBlock) { block = newBlock; }
|
||||
Control() : block(nullptr), tryControl(nullptr) {}
|
||||
Control(Control&&) = default;
|
||||
Control(const Control&) = delete;
|
||||
};
|
||||
|
||||
// [SMDOC] WebAssembly Exception Handling in Ion
|
||||
|
@ -240,6 +255,10 @@ class FunctionCompiler {
|
|||
uint32_t loopDepth_;
|
||||
uint32_t blockDepth_;
|
||||
ControlFlowPatchVectorVector blockPatches_;
|
||||
// Control flow patches created by `delegate` instructions that target the
|
||||
// outermost label of this function. These will be bound to a pad that will
|
||||
// do a rethrow in `emitBodyDelegateThrowPad`.
|
||||
ControlInstructionVector bodyDelegatePadPatches_;
|
||||
|
||||
// Instance pointer argument to the current function.
|
||||
MWasmParameter* instancePointer_;
|
||||
|
@ -248,6 +267,9 @@ class FunctionCompiler {
|
|||
// Reference to masm.tryNotes_
|
||||
wasm::TryNoteVector& tryNotes_;
|
||||
|
||||
// Cache of TryControl to minimize heap allocations
|
||||
VectorUniqueTryControl tryControlCache_;
|
||||
|
||||
public:
|
||||
FunctionCompiler(const ModuleEnvironment& moduleEnv, Decoder& decoder,
|
||||
const FuncCompileInput& func, const ValTypeVector& locals,
|
||||
|
@ -284,6 +306,24 @@ class FunctionCompiler {
|
|||
return moduleEnv_.isAsmJS() ? BytecodeOffset() : iter_.bytecodeOffset();
|
||||
}
|
||||
|
||||
// Try to get a free TryControl from the cache, or allocate a new one.
|
||||
[[nodiscard]] UniqueTryControl newTryControl() {
|
||||
if (tryControlCache_.empty()) {
|
||||
return UniqueTryControl(js_new<TryControl>());
|
||||
}
|
||||
UniqueTryControl tryControl = std::move(tryControlCache_.back());
|
||||
tryControlCache_.popBack();
|
||||
return tryControl;
|
||||
}
|
||||
|
||||
// Release the TryControl to the cache.
|
||||
void freeTryControl(UniqueTryControl&& tryControl) {
|
||||
// Ensure that it's in a consistent state
|
||||
tryControl->reset();
|
||||
// Ignore any OOM, as we'll fail later
|
||||
(void)tryControlCache_.append(std::move(tryControl));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool init() {
|
||||
// Prepare the entry block for MIR generation:
|
||||
|
||||
|
@ -2359,16 +2399,19 @@ class FunctionCompiler {
|
|||
}
|
||||
|
||||
CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Symbolic);
|
||||
auto* ins = MWasmCallUncatchable::NewBuiltinInstanceMethodCall(
|
||||
MInstruction* ins;
|
||||
ins = MWasmCallUncatchable::NewBuiltinInstanceMethodCall(
|
||||
alloc(), desc, builtin.identity, builtin.failureMode, call.instanceArg_,
|
||||
call.regArgs_, StackArgAreaSizeUnaligned(builtin));
|
||||
if (!ins) {
|
||||
return false;
|
||||
}
|
||||
|
||||
curBlock_->add(ins);
|
||||
|
||||
return def ? collectUnaryCallResult(builtin.retType, def) : true;
|
||||
if (!def) {
|
||||
return true;
|
||||
}
|
||||
return collectUnaryCallResult(builtin.retType, def);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_WASM_FUNCTION_REFERENCES
|
||||
|
@ -2716,13 +2759,22 @@ class FunctionCompiler {
|
|||
continue;
|
||||
}
|
||||
Control& control = iter().controlItem(depth);
|
||||
for (MControlInstruction* patch : control.tryPadPatches) {
|
||||
if (!control.tryControl) {
|
||||
continue;
|
||||
}
|
||||
for (MControlInstruction* patch : control.tryControl->landingPadPatches) {
|
||||
MBasicBlock* block = patch->block();
|
||||
if (block->loopDepth() >= loopEntry->loopDepth()) {
|
||||
fixupRedundantPhis(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (MControlInstruction* patch : bodyDelegatePadPatches_) {
|
||||
MBasicBlock* block = patch->block();
|
||||
if (block->loopDepth() >= loopEntry->loopDepth()) {
|
||||
fixupRedundantPhis(block);
|
||||
}
|
||||
}
|
||||
|
||||
// Discard redundant phis and add to the free list.
|
||||
for (MPhiIterator phi = loopEntry->phisBegin();
|
||||
|
@ -2937,8 +2989,16 @@ class FunctionCompiler {
|
|||
|
||||
/********************************************************** Exceptions ***/
|
||||
|
||||
bool inTryBlockFrom(uint32_t fromRelativeDepth, uint32_t* relativeDepth) {
|
||||
return iter().controlFindInnermostFrom(
|
||||
[](LabelKind kind, const Control& control) {
|
||||
return control.tryControl != nullptr && control.tryControl->inBody;
|
||||
},
|
||||
fromRelativeDepth, relativeDepth);
|
||||
}
|
||||
|
||||
bool inTryBlock(uint32_t* relativeDepth) {
|
||||
return iter().controlFindInnermost(LabelKind::Try, relativeDepth);
|
||||
return inTryBlockFrom(0, relativeDepth);
|
||||
}
|
||||
|
||||
bool inTryCode() {
|
||||
|
@ -2994,9 +3054,8 @@ class FunctionCompiler {
|
|||
|
||||
[[nodiscard]] bool addPadPatch(MControlInstruction* ins,
|
||||
size_t relativeTryDepth) {
|
||||
Control& tryControl = iter().controlItem(relativeTryDepth);
|
||||
ControlInstructionVector& padPatches = tryControl.tryPadPatches;
|
||||
return padPatches.emplaceBack(ins);
|
||||
Control& control = iter().controlItem(relativeTryDepth);
|
||||
return control.tryControl->landingPadPatches.emplaceBack(ins);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool endWithPadPatch(uint32_t relativeTryDepth) {
|
||||
|
@ -3012,15 +3071,20 @@ class FunctionCompiler {
|
|||
}
|
||||
|
||||
// Find where we are delegating the pad patches to.
|
||||
ControlInstructionVector* targetPatches;
|
||||
uint32_t targetRelativeDepth;
|
||||
if (!iter().controlFindInnermostFrom(LabelKind::Try, relativeDepth,
|
||||
&targetRelativeDepth)) {
|
||||
if (inTryBlockFrom(relativeDepth, &targetRelativeDepth)) {
|
||||
targetPatches = &iter()
|
||||
.controlItem(targetRelativeDepth)
|
||||
.tryControl->landingPadPatches;
|
||||
} else {
|
||||
MOZ_ASSERT(relativeDepth <= blockDepth_ - 1);
|
||||
targetRelativeDepth = blockDepth_ - 1;
|
||||
targetPatches = &bodyDelegatePadPatches_;
|
||||
}
|
||||
|
||||
// Append the delegate's pad patches to the target's.
|
||||
for (MControlInstruction* ins : patches) {
|
||||
if (!addPadPatch(ins, targetRelativeDepth)) {
|
||||
if (!targetPatches->emplaceBack(ins)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -3066,15 +3130,15 @@ class FunctionCompiler {
|
|||
}
|
||||
|
||||
// Create a landing pad for a try block if there are any throwing
|
||||
// instructions.
|
||||
[[nodiscard]] bool createTryLandingPadIfNeeded(Control& control,
|
||||
MBasicBlock** landingPad) {
|
||||
// instructions. This is also used for the implicit rethrow landing pad used
|
||||
// for delegate instructions that target the outermost label.
|
||||
[[nodiscard]] bool createTryLandingPadIfNeeded(
|
||||
ControlInstructionVector& landingPadPatches, MBasicBlock** landingPad) {
|
||||
// If there are no pad-patches for this try control, it means there are no
|
||||
// instructions in the try code that could throw an exception. In this
|
||||
// case, all the catches are dead code, and the try code ends up equivalent
|
||||
// to a plain wasm block.
|
||||
ControlInstructionVector& patches = control.tryPadPatches;
|
||||
if (patches.empty()) {
|
||||
if (landingPadPatches.empty()) {
|
||||
*landingPad = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
@ -3082,14 +3146,14 @@ class FunctionCompiler {
|
|||
// Otherwise, if there are (pad-) branches from places in the try code that
|
||||
// may throw an exception, bind these branches to a new landing pad
|
||||
// block. This is done similarly to what is done in bindBranches.
|
||||
MControlInstruction* ins = patches[0];
|
||||
MControlInstruction* ins = landingPadPatches[0];
|
||||
MBasicBlock* pred = ins->block();
|
||||
if (!newBlock(pred, landingPad)) {
|
||||
return false;
|
||||
}
|
||||
ins->replaceSuccessor(0, *landingPad);
|
||||
for (size_t i = 1; i < patches.length(); i++) {
|
||||
ins = patches[i];
|
||||
for (size_t i = 1; i < landingPadPatches.length(); i++) {
|
||||
ins = landingPadPatches[i];
|
||||
pred = ins->block();
|
||||
if (!(*landingPad)->addPredecessor(alloc(), pred)) {
|
||||
return false;
|
||||
|
@ -3098,20 +3162,129 @@ class FunctionCompiler {
|
|||
}
|
||||
|
||||
// Set up the slots in the landing pad block.
|
||||
if (!setupLandingPadSlots(*landingPad)) {
|
||||
if (!setupLandingPadSlots(landingPad)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear the now bound pad patches.
|
||||
patches.clear();
|
||||
landingPadPatches.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool createTryTableLandingPad(TryControl* tryControl) {
|
||||
MBasicBlock* landingPad;
|
||||
if (!createTryLandingPadIfNeeded(tryControl->landingPadPatches,
|
||||
&landingPad)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no landing pad created, no exceptions were possibly thrown
|
||||
// and we don't need to do anything here.
|
||||
if (!landingPad) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MBasicBlock* originalBlock = curBlock_;
|
||||
curBlock_ = landingPad;
|
||||
|
||||
bool hadCatchAll = false;
|
||||
for (const TryTableCatch& tryTableCatch : tryControl->catches) {
|
||||
MOZ_ASSERT(numPushed(curBlock_) == 2);
|
||||
|
||||
// Handle a catch_all by jumping to the target block
|
||||
if (tryTableCatch.tagIndex == CatchAllIndex) {
|
||||
// Get the exception from the slots we pushed when adding
|
||||
// control flow patches.
|
||||
curBlock_->pop();
|
||||
MDefinition* exception = curBlock_->pop();
|
||||
|
||||
// Capture the exnref value if we need to
|
||||
DefVector values;
|
||||
if (tryTableCatch.captureExnRef && !values.append(exception)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Branch to the catch_all code
|
||||
if (!br(tryTableCatch.labelRelativeDepth, values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Break from the loop and skip the implicit rethrow that's needed
|
||||
// if we didn't have a catch_all
|
||||
hadCatchAll = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle a tagged catch by doing a compare and branch on the tag index,
|
||||
// jumping to a catch block if they match, or else to a fallthrough block
|
||||
// to continue the landing pad.
|
||||
MBasicBlock* catchBlock = nullptr;
|
||||
MBasicBlock* fallthroughBlock = nullptr;
|
||||
if (!newBlock(curBlock_, &catchBlock) ||
|
||||
!newBlock(curBlock_, &fallthroughBlock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the exception and its tag from the slots we pushed when adding
|
||||
// control flow patches.
|
||||
MDefinition* exceptionTag = curBlock_->pop();
|
||||
curBlock_->pop();
|
||||
|
||||
// Branch to the catch block if the exception's tag matches this catch
|
||||
// block's tag.
|
||||
MDefinition* catchTag = loadTag(tryTableCatch.tagIndex);
|
||||
MDefinition* matchesCatchTag = compare(exceptionTag, catchTag, JSOp::Eq,
|
||||
MCompare::Compare_WasmAnyRef);
|
||||
curBlock_->end(
|
||||
MTest::New(alloc(), matchesCatchTag, catchBlock, fallthroughBlock));
|
||||
|
||||
// Set up the catch block by extracting the values from the exception
|
||||
// object.
|
||||
curBlock_ = catchBlock;
|
||||
|
||||
// Remove the tag and exception slots from the block, they are no
|
||||
// longer necessary.
|
||||
curBlock_->pop();
|
||||
MDefinition* exception = curBlock_->pop();
|
||||
MOZ_ASSERT(numPushed(curBlock_) == 0);
|
||||
|
||||
// Extract the exception values for the catch block
|
||||
DefVector values;
|
||||
if (!loadExceptionValues(exception, tryTableCatch.tagIndex, &values)) {
|
||||
return false;
|
||||
}
|
||||
if (tryTableCatch.captureExnRef && !values.append(exception)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!br(tryTableCatch.labelRelativeDepth, values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
curBlock_ = fallthroughBlock;
|
||||
}
|
||||
|
||||
// If there was no catch_all, we must rethrow this exception.
|
||||
if (!hadCatchAll) {
|
||||
MOZ_ASSERT(numPushed(curBlock_) == 2);
|
||||
MDefinition* tag = curBlock_->pop();
|
||||
MDefinition* exception = curBlock_->pop();
|
||||
MOZ_ASSERT(numPushed(curBlock_) == 0);
|
||||
|
||||
if (!throwFrom(exception, tag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
curBlock_ = originalBlock;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consume the pending exception state from instance, and set up the slots
|
||||
// of the landing pad with the exception state.
|
||||
[[nodiscard]] bool setupLandingPadSlots(MBasicBlock* landingPad) {
|
||||
[[nodiscard]] bool setupLandingPadSlots(MBasicBlock** landingPad) {
|
||||
MBasicBlock* prevBlock = curBlock_;
|
||||
curBlock_ = landingPad;
|
||||
curBlock_ = *landingPad;
|
||||
|
||||
// Load the pending exception and tag
|
||||
MInstruction* exception;
|
||||
|
@ -3126,18 +3299,37 @@ class FunctionCompiler {
|
|||
|
||||
// Push the exception and its tag on the stack to make them available
|
||||
// to the landing pad blocks.
|
||||
if (!landingPad->ensureHasSlots(2)) {
|
||||
if (!curBlock_->ensureHasSlots(2)) {
|
||||
return false;
|
||||
}
|
||||
landingPad->push(exception);
|
||||
landingPad->push(tag);
|
||||
curBlock_->push(exception);
|
||||
curBlock_->push(tag);
|
||||
*landingPad = curBlock_;
|
||||
|
||||
curBlock_ = prevBlock;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool startTry(MBasicBlock** curBlock) {
|
||||
*curBlock = curBlock_;
|
||||
[[nodiscard]] bool startTry() {
|
||||
Control& control = iter().controlItem();
|
||||
control.block = curBlock_;
|
||||
control.tryControl = newTryControl();
|
||||
if (!control.tryControl) {
|
||||
return false;
|
||||
}
|
||||
control.tryControl->inBody = true;
|
||||
return startBlock();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool startTryTable(TryTableCatchVector&& catches) {
|
||||
Control& control = iter().controlItem();
|
||||
control.block = curBlock_;
|
||||
control.tryControl = newTryControl();
|
||||
if (!control.tryControl) {
|
||||
return false;
|
||||
}
|
||||
control.tryControl->inBody = true;
|
||||
control.tryControl->catches = std::move(catches);
|
||||
return startBlock();
|
||||
}
|
||||
|
||||
|
@ -3163,8 +3355,11 @@ class FunctionCompiler {
|
|||
|
||||
// Finish the previous block (either a try or catch block) and then setup a
|
||||
// new catch block.
|
||||
[[nodiscard]] bool switchToCatch(Control& control, const LabelKind& fromKind,
|
||||
[[nodiscard]] bool switchToCatch(Control& control, LabelKind fromKind,
|
||||
uint32_t tagIndex) {
|
||||
// Mark this control node as being no longer in the body of the try
|
||||
control.tryControl->inBody = false;
|
||||
|
||||
// If there is no control block, then either:
|
||||
// - the entry of the try block is dead code, or
|
||||
// - there is no landing pad for the try-catch.
|
||||
|
@ -3184,7 +3379,8 @@ class FunctionCompiler {
|
|||
// guaranteed to happen once and only once before processing catch blocks.
|
||||
if (fromKind == LabelKind::Try) {
|
||||
MBasicBlock* padBlock = nullptr;
|
||||
if (!createTryLandingPadIfNeeded(control, &padBlock)) {
|
||||
if (!createTryLandingPadIfNeeded(control.tryControl->landingPadPatches,
|
||||
&padBlock)) {
|
||||
return false;
|
||||
}
|
||||
// Set the control block for this try-catch to the landing pad.
|
||||
|
@ -3253,7 +3449,7 @@ class FunctionCompiler {
|
|||
// Remove the tag and exception slots from the block, they are no
|
||||
// longer necessary.
|
||||
curBlock_->pop();
|
||||
curBlock_->pop();
|
||||
exception = curBlock_->pop();
|
||||
|
||||
// Extract the exception values for the catch block
|
||||
DefVector values;
|
||||
|
@ -3310,12 +3506,14 @@ class FunctionCompiler {
|
|||
// specify a relativeDepth of '1' to delegate outside of the still
|
||||
// active try block.
|
||||
uint32_t relativeDepth = 1;
|
||||
if (!delegatePadPatches(control.tryPadPatches, relativeDepth)) {
|
||||
if (!delegatePadPatches(control.tryControl->landingPadPatches,
|
||||
relativeDepth)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LabelKind::Catch: {
|
||||
MOZ_ASSERT(!control.tryControl->inBody);
|
||||
// This is a try without a catch_all, we must have a rethrow at the end
|
||||
// of the landing pad (if any).
|
||||
MBasicBlock* padBlock = control.block;
|
||||
|
@ -3331,9 +3529,11 @@ class FunctionCompiler {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case LabelKind::CatchAll:
|
||||
case LabelKind::CatchAll: {
|
||||
MOZ_ASSERT(!control.tryControl->inBody);
|
||||
// This is a try with a catch_all, and requires no special handling.
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
@ -3342,10 +3542,21 @@ class FunctionCompiler {
|
|||
return finishBlock(defs);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool finishTryTable(Control& control, DefVector* defs) {
|
||||
// Mark this control as no longer in the body of the try
|
||||
control.tryControl->inBody = false;
|
||||
// Create a landing pad for all of the catches
|
||||
if (!createTryTableLandingPad(control.tryControl.get())) {
|
||||
return false;
|
||||
}
|
||||
// Finish the block, joining the try and catch blocks
|
||||
return finishBlock(defs);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool emitBodyDelegateThrowPad(Control& control) {
|
||||
// Create a landing pad for any throwing instructions
|
||||
MBasicBlock* padBlock;
|
||||
if (!createTryLandingPadIfNeeded(control, &padBlock)) {
|
||||
if (!createTryLandingPadIfNeeded(bodyDelegatePadPatches_, &padBlock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -3438,6 +3649,27 @@ class FunctionCompiler {
|
|||
return throwFrom(exception, tag);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool emitThrowRef(MDefinition* exnRef) {
|
||||
if (inDeadCode()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The exception must be non-null
|
||||
if (!refAsNonNull(exnRef)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is no surrounding catching block, call an instance method to
|
||||
// throw the exception.
|
||||
if (!emitInstanceCall1(readBytecodeOffset(), SASigThrowException, exnRef)) {
|
||||
return false;
|
||||
}
|
||||
unreachableTrap();
|
||||
|
||||
curBlock_ = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool throwFrom(MDefinition* exn, MDefinition* tag) {
|
||||
if (inDeadCode()) {
|
||||
return true;
|
||||
|
@ -4748,7 +4980,7 @@ static bool EmitLoop(FunctionCompiler& f) {
|
|||
|
||||
f.addInterruptCheck();
|
||||
|
||||
f.iter().controlItem().setBlock(loopHeader);
|
||||
f.iter().controlItem().block = loopHeader;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -4764,7 +4996,7 @@ static bool EmitIf(FunctionCompiler& f) {
|
|||
return false;
|
||||
}
|
||||
|
||||
f.iter().controlItem().setBlock(elseBlock);
|
||||
f.iter().controlItem().block = elseBlock;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -4805,6 +5037,7 @@ static bool EmitEnd(FunctionCompiler& f) {
|
|||
DefVector postJoinDefs;
|
||||
switch (kind) {
|
||||
case LabelKind::Body:
|
||||
MOZ_ASSERT(!control.tryControl);
|
||||
if (!f.emitBodyDelegateThrowPad(control)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -4818,18 +5051,21 @@ static bool EmitEnd(FunctionCompiler& f) {
|
|||
MOZ_ASSERT(f.iter().controlStackEmpty());
|
||||
return f.iter().endFunction(f.iter().end());
|
||||
case LabelKind::Block:
|
||||
MOZ_ASSERT(!control.tryControl);
|
||||
if (!f.finishBlock(&postJoinDefs)) {
|
||||
return false;
|
||||
}
|
||||
f.iter().popEnd();
|
||||
break;
|
||||
case LabelKind::Loop:
|
||||
MOZ_ASSERT(!control.tryControl);
|
||||
if (!f.closeLoop(block, &postJoinDefs)) {
|
||||
return false;
|
||||
}
|
||||
f.iter().popEnd();
|
||||
break;
|
||||
case LabelKind::Then: {
|
||||
MOZ_ASSERT(!control.tryControl);
|
||||
// If we didn't see an Else, create a trivial else block so that we create
|
||||
// a diamond anyway, to preserve Ion invariants.
|
||||
if (!f.switchToElse(block, &block)) {
|
||||
|
@ -4847,6 +5083,7 @@ static bool EmitEnd(FunctionCompiler& f) {
|
|||
break;
|
||||
}
|
||||
case LabelKind::Else:
|
||||
MOZ_ASSERT(!control.tryControl);
|
||||
if (!f.joinIfElse(block, &postJoinDefs)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -4855,9 +5092,19 @@ static bool EmitEnd(FunctionCompiler& f) {
|
|||
case LabelKind::Try:
|
||||
case LabelKind::Catch:
|
||||
case LabelKind::CatchAll:
|
||||
MOZ_ASSERT(control.tryControl);
|
||||
if (!f.finishTryCatch(kind, control, &postJoinDefs)) {
|
||||
return false;
|
||||
}
|
||||
f.freeTryControl(std::move(control.tryControl));
|
||||
f.iter().popEnd();
|
||||
break;
|
||||
case LabelKind::TryTable:
|
||||
MOZ_ASSERT(control.tryControl);
|
||||
if (!f.finishTryTable(control, &postJoinDefs)) {
|
||||
return false;
|
||||
}
|
||||
f.freeTryControl(std::move(control.tryControl));
|
||||
f.iter().popEnd();
|
||||
break;
|
||||
}
|
||||
|
@ -4944,13 +5191,7 @@ static bool EmitTry(FunctionCompiler& f) {
|
|||
return false;
|
||||
}
|
||||
|
||||
MBasicBlock* curBlock = nullptr;
|
||||
if (!f.startTry(&curBlock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
f.iter().controlItem().setBlock(curBlock);
|
||||
return true;
|
||||
return f.startTry();
|
||||
}
|
||||
|
||||
static bool EmitCatch(FunctionCompiler& f) {
|
||||
|
@ -4992,6 +5233,16 @@ static bool EmitCatchAll(FunctionCompiler& f) {
|
|||
return f.switchToCatch(f.iter().controlItem(), kind, CatchAllIndex);
|
||||
}
|
||||
|
||||
static bool EmitTryTable(FunctionCompiler& f) {
|
||||
ResultType params;
|
||||
TryTableCatchVector catches;
|
||||
if (!f.iter().readTryTable(¶ms, &catches)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return f.startTryTable(std::move(catches));
|
||||
}
|
||||
|
||||
static bool EmitDelegate(FunctionCompiler& f) {
|
||||
uint32_t relativeDepth;
|
||||
ResultType resultType;
|
||||
|
@ -5002,15 +5253,18 @@ static bool EmitDelegate(FunctionCompiler& f) {
|
|||
|
||||
Control& control = f.iter().controlItem();
|
||||
MBasicBlock* block = control.block;
|
||||
MOZ_ASSERT(control.tryControl);
|
||||
|
||||
// Unless the entire try-delegate is dead code, delegate any pad-patches from
|
||||
// this try to the next try-block above relativeDepth.
|
||||
if (block) {
|
||||
ControlInstructionVector& delegatePadPatches = control.tryPadPatches;
|
||||
ControlInstructionVector& delegatePadPatches =
|
||||
control.tryControl->landingPadPatches;
|
||||
if (!f.delegatePadPatches(delegatePadPatches, relativeDepth)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
f.freeTryControl(std::move(control.tryControl));
|
||||
f.iter().popDelegate();
|
||||
|
||||
// Push the results of the previous block, and join control flow with
|
||||
|
@ -5039,6 +5293,15 @@ static bool EmitThrow(FunctionCompiler& f) {
|
|||
return f.emitThrow(tagIndex, argValues);
|
||||
}
|
||||
|
||||
static bool EmitThrowRef(FunctionCompiler& f) {
|
||||
MDefinition* exnRef;
|
||||
if (!f.iter().readThrowRef(&exnRef)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return f.emitThrowRef(exnRef);
|
||||
}
|
||||
|
||||
static bool EmitRethrow(FunctionCompiler& f) {
|
||||
uint32_t relativeDepth;
|
||||
if (!f.iter().readRethrow(&relativeDepth)) {
|
||||
|
@ -7808,6 +8071,16 @@ static bool EmitBodyExprs(FunctionCompiler& f) {
|
|||
return f.iter().unrecognizedOpcode(&op);
|
||||
}
|
||||
CHECK(EmitRethrow(f));
|
||||
case uint16_t(Op::ThrowRef):
|
||||
if (!f.moduleEnv().exnrefEnabled()) {
|
||||
return f.iter().unrecognizedOpcode(&op);
|
||||
}
|
||||
CHECK(EmitThrowRef(f));
|
||||
case uint16_t(Op::TryTable):
|
||||
if (!f.moduleEnv().exnrefEnabled()) {
|
||||
return f.iter().unrecognizedOpcode(&op);
|
||||
}
|
||||
CHECK(EmitTryTable(f));
|
||||
case uint16_t(Op::Br):
|
||||
CHECK(EmitBr(f));
|
||||
case uint16_t(Op::BrIf):
|
||||
|
|
|
@ -282,6 +282,10 @@ OpKind wasm::Classify(OpBytes op) {
|
|||
return OpKind::Rethrow;
|
||||
case Op::Try:
|
||||
return OpKind::Try;
|
||||
case Op::ThrowRef:
|
||||
return OpKind::ThrowRef;
|
||||
case Op::TryTable:
|
||||
return OpKind::TryTable;
|
||||
case Op::MemorySize:
|
||||
return OpKind::MemorySize;
|
||||
case Op::MemoryGrow:
|
||||
|
|
|
@ -42,6 +42,7 @@ enum class LabelKind : uint8_t {
|
|||
Try,
|
||||
Catch,
|
||||
CatchAll,
|
||||
TryTable,
|
||||
};
|
||||
|
||||
// The type of values on the operand stack during validation. This is either a
|
||||
|
@ -228,8 +229,10 @@ enum class OpKind {
|
|||
CatchAll,
|
||||
Delegate,
|
||||
Throw,
|
||||
ThrowRef,
|
||||
Rethrow,
|
||||
Try,
|
||||
TryTable,
|
||||
CallBuiltinModuleFunc,
|
||||
};
|
||||
|
||||
|
@ -627,6 +630,8 @@ class MOZ_STACK_CLASS OpIter : private Policy {
|
|||
ResultType* defaultBranchType,
|
||||
ValueVector* branchValues, Value* index);
|
||||
[[nodiscard]] bool readTry(ResultType* type);
|
||||
[[nodiscard]] bool readTryTable(ResultType* type,
|
||||
TryTableCatchVector* catches);
|
||||
[[nodiscard]] bool readCatch(LabelKind* kind, uint32_t* tagIndex,
|
||||
ResultType* paramType, ResultType* resultType,
|
||||
ValueVector* tryResults);
|
||||
|
@ -638,6 +643,7 @@ class MOZ_STACK_CLASS OpIter : private Policy {
|
|||
ValueVector* tryResults);
|
||||
void popDelegate();
|
||||
[[nodiscard]] bool readThrow(uint32_t* tagIndex, ValueVector* argValues);
|
||||
[[nodiscard]] bool readThrowRef(Value* exnRef);
|
||||
[[nodiscard]] bool readRethrow(uint32_t* relativeDepth);
|
||||
[[nodiscard]] bool readUnreachable();
|
||||
[[nodiscard]] bool readDrop();
|
||||
|
@ -875,24 +881,22 @@ class MOZ_STACK_CLASS OpIter : private Policy {
|
|||
// Return the depth of the control stack.
|
||||
size_t controlStackDepth() const { return controlStack_.length(); }
|
||||
|
||||
// Find the innermost control item of a specific kind, starting to search from
|
||||
// a certain relative depth, and returning true if such innermost control item
|
||||
// is found. The relative depth of the found item is returned via a parameter.
|
||||
bool controlFindInnermostFrom(LabelKind kind, uint32_t fromRelativeDepth,
|
||||
// Find the innermost control item matching a predicate, starting to search
|
||||
// from a certain relative depth, and returning true if such innermost
|
||||
// control item is found. The relative depth of the found item is returned
|
||||
// via a parameter.
|
||||
template <typename Predicate>
|
||||
bool controlFindInnermostFrom(Predicate predicate, uint32_t fromRelativeDepth,
|
||||
uint32_t* foundRelativeDepth) {
|
||||
int32_t fromAbsoluteDepth = controlStack_.length() - fromRelativeDepth - 1;
|
||||
for (int32_t i = fromAbsoluteDepth; i >= 0; i--) {
|
||||
if (controlStack_[i].kind() == kind) {
|
||||
if (predicate(controlStack_[i].kind(), controlStack_[i].controlItem())) {
|
||||
*foundRelativeDepth = controlStack_.length() - 1 - i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool controlFindInnermost(LabelKind kind, uint32_t* foundRelativeDepth) {
|
||||
return controlFindInnermostFrom(kind, 0, foundRelativeDepth);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Policy>
|
||||
|
@ -1604,6 +1608,107 @@ inline bool OpIter<Policy>::readTry(ResultType* paramType) {
|
|||
return pushControl(LabelKind::Try, type);
|
||||
}
|
||||
|
||||
enum class TryTableCatchFlags : uint8_t {
|
||||
CaptureExnRef = 0x1,
|
||||
CatchAll = 0x1 << 1,
|
||||
AllowedMask = uint8_t(CaptureExnRef) | uint8_t(CatchAll),
|
||||
};
|
||||
|
||||
template <typename Policy>
|
||||
inline bool OpIter<Policy>::readTryTable(ResultType* paramType,
|
||||
TryTableCatchVector* catches) {
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::TryTable);
|
||||
|
||||
BlockType type;
|
||||
if (!readBlockType(&type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*paramType = type.params();
|
||||
if (!pushControl(LabelKind::TryTable, type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t catchesLength;
|
||||
if (!readVarU32(&catchesLength)) {
|
||||
return fail("failed to read catches length");
|
||||
}
|
||||
|
||||
if (catchesLength > MaxTryTableCatches) {
|
||||
return fail("too many catches");
|
||||
}
|
||||
|
||||
if (!catches->reserve(catchesLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < catchesLength; i++) {
|
||||
TryTableCatch tryTableCatch;
|
||||
|
||||
// Decode the flags
|
||||
uint8_t flags;
|
||||
if (!readFixedU8(&flags)) {
|
||||
return fail("expected flags");
|
||||
}
|
||||
if ((flags & ~uint8_t(TryTableCatchFlags::AllowedMask)) != 0) {
|
||||
return fail("invalid try_table catch flags");
|
||||
}
|
||||
|
||||
// Decode if this catch wants to capture an exnref
|
||||
tryTableCatch.captureExnRef =
|
||||
(flags & uint8_t(TryTableCatchFlags::CaptureExnRef)) != 0;
|
||||
|
||||
// Decode the tag, if any
|
||||
if ((flags & uint8_t(TryTableCatchFlags::CatchAll)) != 0) {
|
||||
tryTableCatch.tagIndex = CatchAllIndex;
|
||||
} else {
|
||||
if (!readVarU32(&tryTableCatch.tagIndex)) {
|
||||
return fail("expected tag index");
|
||||
}
|
||||
if (tryTableCatch.tagIndex >= env_.tags.length()) {
|
||||
return fail("tag index out of range");
|
||||
}
|
||||
}
|
||||
|
||||
// Decode the target branch and construct the type we need to compare
|
||||
// against the branch
|
||||
if (!readVarU32(&tryTableCatch.labelRelativeDepth)) {
|
||||
return fail("unable to read catch depth");
|
||||
}
|
||||
|
||||
// Tagged catches will unpack the exception package and pass it to the
|
||||
// branch
|
||||
if (tryTableCatch.tagIndex != CatchAllIndex) {
|
||||
const TagType& tagType = *env_.tags[tryTableCatch.tagIndex].type;
|
||||
ResultType tagResult = tagType.resultType();
|
||||
if (!tagResult.cloneToVector(&tryTableCatch.labelType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Any captured exnref is the final parameter
|
||||
if (tryTableCatch.captureExnRef &&
|
||||
!tryTableCatch.labelType.append(ValType(RefType::exn()))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Control* block;
|
||||
if (!getControl(tryTableCatch.labelRelativeDepth, &block)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResultType blockTargetType = block->branchTargetType();
|
||||
if (!checkIsSubtypeOf(ResultType::Vector(tryTableCatch.labelType),
|
||||
blockTargetType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
catches->infallibleAppend(std::move(tryTableCatch));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool OpIter<Policy>::readCatch(LabelKind* kind, uint32_t* tagIndex,
|
||||
ResultType* paramType,
|
||||
|
@ -1661,7 +1766,6 @@ inline bool OpIter<Policy>::readCatchAll(LabelKind* kind, ResultType* paramType,
|
|||
block.switchToCatchAll();
|
||||
// Reset local state to the beginning of the 'try' block.
|
||||
unsetLocals_.resetToBlock(controlStack_.length() - 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1722,6 +1826,18 @@ inline bool OpIter<Policy>::readThrow(uint32_t* tagIndex,
|
|||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool OpIter<Policy>::readThrowRef(Value* exnRef) {
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::ThrowRef);
|
||||
|
||||
if (!popWithType(ValType(RefType::exn()), exnRef)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
afterUnconditionalBranch();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Policy>
|
||||
inline bool OpIter<Policy>::readRethrow(uint32_t* relativeDepth) {
|
||||
MOZ_ASSERT(Classify(op_) == OpKind::Rethrow);
|
||||
|
|
|
@ -1347,6 +1347,8 @@ inline RefTypeHierarchy RefType::hierarchy() const {
|
|||
case RefType::Extern:
|
||||
case RefType::NoExtern:
|
||||
return RefTypeHierarchy::Extern;
|
||||
case RefType::Exn:
|
||||
return RefTypeHierarchy::Exn;
|
||||
case RefType::Any:
|
||||
case RefType::None:
|
||||
case RefType::I31:
|
||||
|
@ -1372,6 +1374,7 @@ inline TableRepr RefType::tableRepr() const {
|
|||
switch (hierarchy()) {
|
||||
case RefTypeHierarchy::Any:
|
||||
case RefTypeHierarchy::Extern:
|
||||
case RefTypeHierarchy::Exn:
|
||||
return TableRepr::Ref;
|
||||
case RefTypeHierarchy::Func:
|
||||
return TableRepr::Func;
|
||||
|
|
|
@ -51,6 +51,8 @@ RefType RefType::topType() const {
|
|||
case RefType::Extern:
|
||||
case RefType::NoExtern:
|
||||
return RefType::extern_();
|
||||
case RefType::Exn:
|
||||
return RefType::exn();
|
||||
case RefType::TypeRef:
|
||||
switch (typeDef()->kind()) {
|
||||
case TypeDefKind::Array:
|
||||
|
@ -296,6 +298,9 @@ UniqueChars wasm::ToString(RefType type, const TypeContext* types) {
|
|||
case RefType::Extern:
|
||||
literal = "externref";
|
||||
break;
|
||||
case RefType::Exn:
|
||||
literal = "exnref";
|
||||
break;
|
||||
case RefType::Any:
|
||||
literal = "anyref";
|
||||
break;
|
||||
|
@ -336,6 +341,9 @@ UniqueChars wasm::ToString(RefType type, const TypeContext* types) {
|
|||
case RefType::Extern:
|
||||
heapType = "extern";
|
||||
break;
|
||||
case RefType::Exn:
|
||||
heapType = "exn";
|
||||
break;
|
||||
case RefType::Any:
|
||||
heapType = "any";
|
||||
break;
|
||||
|
|
|
@ -302,7 +302,7 @@ enum class TableRepr { Ref, Func };
|
|||
|
||||
// An enum that describes the different type hierarchies.
|
||||
|
||||
enum class RefTypeHierarchy { Func, Extern, Any };
|
||||
enum class RefTypeHierarchy { Func, Extern, Exn, Any };
|
||||
|
||||
// The RefType carries more information about types t for which t.isRefType()
|
||||
// is true.
|
||||
|
@ -312,6 +312,7 @@ class RefType {
|
|||
enum Kind {
|
||||
Func = uint8_t(TypeCode::FuncRef),
|
||||
Extern = uint8_t(TypeCode::ExternRef),
|
||||
Exn = uint8_t(TypeCode::ExnRef),
|
||||
Any = uint8_t(TypeCode::AnyRef),
|
||||
NoFunc = uint8_t(TypeCode::NullFuncRef),
|
||||
NoExtern = uint8_t(TypeCode::NullExternRef),
|
||||
|
@ -364,6 +365,7 @@ class RefType {
|
|||
switch (ptc_.typeCode()) {
|
||||
case TypeCode::FuncRef:
|
||||
case TypeCode::ExternRef:
|
||||
case TypeCode::ExnRef:
|
||||
case TypeCode::AnyRef:
|
||||
case TypeCode::EqRef:
|
||||
case TypeCode::I31Ref:
|
||||
|
@ -382,6 +384,7 @@ class RefType {
|
|||
|
||||
static RefType func() { return RefType(Func, true); }
|
||||
static RefType extern_() { return RefType(Extern, true); }
|
||||
static RefType exn() { return RefType(Exn, true); }
|
||||
static RefType any() { return RefType(Any, true); }
|
||||
static RefType nofunc() { return RefType(NoFunc, true); }
|
||||
static RefType noextern() { return RefType(NoExtern, true); }
|
||||
|
@ -464,6 +467,7 @@ class FieldTypeTraits {
|
|||
#endif
|
||||
case TypeCode::FuncRef:
|
||||
case TypeCode::ExternRef:
|
||||
case TypeCode::ExnRef:
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case TypeCode::AnyRef:
|
||||
case TypeCode::EqRef:
|
||||
|
@ -541,6 +545,7 @@ class ValTypeTraits {
|
|||
#endif
|
||||
case TypeCode::FuncRef:
|
||||
case TypeCode::ExternRef:
|
||||
case TypeCode::ExnRef:
|
||||
#ifdef ENABLE_WASM_GC
|
||||
case TypeCode::AnyRef:
|
||||
case TypeCode::EqRef:
|
||||
|
@ -711,6 +716,8 @@ class PackedType : public T {
|
|||
|
||||
bool isExternRef() const { return tc_.typeCode() == TypeCode::ExternRef; }
|
||||
|
||||
bool isExnRef() const { return tc_.typeCode() == TypeCode::ExnRef; }
|
||||
|
||||
bool isAnyRef() const { return tc_.typeCode() == TypeCode::AnyRef; }
|
||||
|
||||
bool isNoFunc() const { return tc_.typeCode() == TypeCode::NullFuncRef; }
|
||||
|
@ -737,9 +744,9 @@ class PackedType : public T {
|
|||
// Returns whether the type has a representation in JS.
|
||||
bool isExposable() const {
|
||||
#if defined(ENABLE_WASM_SIMD)
|
||||
return kind() != Kind::V128;
|
||||
return kind() != Kind::V128 && !isExnRef();
|
||||
#else
|
||||
return true;
|
||||
return !isExnRef();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -1331,6 +1331,19 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env,
|
|||
uint32_t unusedDepth;
|
||||
CHECK(iter.readRethrow(&unusedDepth));
|
||||
}
|
||||
case uint16_t(Op::ThrowRef): {
|
||||
if (!env.exnrefEnabled()) {
|
||||
return iter.unrecognizedOpcode(&op);
|
||||
}
|
||||
CHECK(iter.readThrowRef(¬hing));
|
||||
}
|
||||
case uint16_t(Op::TryTable): {
|
||||
if (!env.exnrefEnabled()) {
|
||||
return iter.unrecognizedOpcode(&op);
|
||||
}
|
||||
TryTableCatchVector catches;
|
||||
CHECK(iter.readTryTable(&unusedType, &catches));
|
||||
}
|
||||
case uint16_t(Op::ThreadPrefix): {
|
||||
// Though thread ops can be used on nonshared memories, we make them
|
||||
// unavailable if shared memory has been disabled in the prefs, for
|
||||
|
|
|
@ -128,6 +128,9 @@ bool wasm::CheckRefType(JSContext* cx, RefType targetType, HandleValue v,
|
|||
return CheckFuncRefValue(cx, v, fnval);
|
||||
case RefType::Extern:
|
||||
return AnyRef::fromJSValue(cx, v, refval);
|
||||
case RefType::Exn:
|
||||
// Break to the non-exposable case
|
||||
break;
|
||||
case RefType::Any:
|
||||
return CheckAnyRefValue(cx, v, refval);
|
||||
case RefType::NoFunc:
|
||||
|
@ -680,6 +683,9 @@ bool wasm::ToWebAssemblyValue(JSContext* cx, HandleValue val, FieldType type,
|
|||
case RefType::Extern:
|
||||
return ToWebAssemblyValue_externref<Debug>(cx, val, (void**)loc,
|
||||
mustWrite64);
|
||||
case RefType::Exn:
|
||||
// Break to the non-exposable case
|
||||
break;
|
||||
case RefType::Any:
|
||||
return ToWebAssemblyValue_anyref<Debug>(cx, val, (void**)loc,
|
||||
mustWrite64);
|
||||
|
@ -840,6 +846,9 @@ bool wasm::ToJSValue(JSContext* cx, const void* src, FieldType type,
|
|||
case RefTypeHierarchy::Func:
|
||||
return ToJSValue_funcref<Debug>(
|
||||
cx, *reinterpret_cast<void* const*>(src), dst);
|
||||
case RefTypeHierarchy::Exn:
|
||||
// Break to the non-exposable case
|
||||
break;
|
||||
case RefTypeHierarchy::Extern:
|
||||
return ToJSValue_externref<Debug>(
|
||||
cx, *reinterpret_cast<void* const*>(src), dst);
|
||||
|
|
Загрузка…
Ссылка в новой задаче