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:
Ryan Hunt 2023-12-22 04:33:16 +00:00
Родитель 553181f924
Коммит b40d4aea04
18 изменённых файлов: 1059 добавлений и 68 удалений

Просмотреть файл

@ -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(&params, &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(&params, &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(&nothing));
}
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);