// Returns whether this input is nullable when interpreted as an operand. // When the type is bottom for unreachable code, this returns false as that // is the most permissive option. bool isNullableAsOperand() const {
MOZ_ASSERT(tc_.isValid()); return isStackBottom() ? false : tc_.isNullable();
}
// Return the OpKind for a given Op. This is used for sanity-checking that // API users use the correct read function for a given Op.
OpKind Classify(OpBytes op); #endif
// Common fields for linear memory access. template <typename Value> struct LinearMemoryAddress {
Value base;
uint32_t memoryIndex;
uint64_t offset;
uint32_t align;
template <typename ControlItem> class ControlStackEntry { // Use a pair to optimize away empty ControlItem.
mozilla::CompactPair<BlockType, ControlItem> typeAndItem_;
// The "base" of a control stack entry is valueStack_.length() minus // type().params().length(), i.e., the size of the value stack "below" // this block.
uint32_t valueStackBase_; bool polymorphicBase_;
// Track state of the non-defaultable locals. Every time such local is // initialized, the stack will record at what depth and which local was set. // On a block end, the "unset" state will be rolled back to how it was before // the block started. // // It is very likely only a few functions will have non-defaultable locals and // very few locals will be non-defaultable. This class is optimized to be fast // for this common case. class UnsetLocalsState { struct SetLocalEntry {
uint32_t depth;
uint32_t localUnsetIndex;
SetLocalEntry(uint32_t depth_, uint32_t localUnsetIndex_)
: depth(depth_), localUnsetIndex(localUnsetIndex_) {}
}; using SetLocalsStack = Vector<SetLocalEntry, 16, SystemAllocPolicy>; using UnsetLocals = Vector<uint32_t, 16, SystemAllocPolicy>;
// Bit array of "unset" function locals. Stores only unset states of the // locals that are declared after the first non-defaultable local.
UnsetLocals unsetLocals_; // Stack of "set" operations. Contains pair where the first field is a depth, // and the second field is local id (offset by firstNonDefaultLocal_).
SetLocalsStack setLocalsStack_;
uint32_t firstNonDefaultLocal_;
inlinevoid set(uint32_t id, uint32_t depth) {
MOZ_ASSERT(isUnset(id));
MOZ_ASSERT(id >= firstNonDefaultLocal_ &&
(id - firstNonDefaultLocal_) / WordBits < unsetLocals_.length());
uint32_t localUnsetIndex = id - firstNonDefaultLocal_;
unsetLocals_[localUnsetIndex / WordBits] ^= 1
<< (localUnsetIndex % WordBits); // The setLocalsStack_ is reserved upfront in the UnsetLocalsState::init. // A SetLocalEntry will be pushed only once per local.
setLocalsStack_.infallibleEmplaceBack(depth, localUnsetIndex);
}
// An iterator over the bytes of a function body. It performs validation // and unpacks the data into a usable form. // // The MOZ_STACK_CLASS attribute here is because of the use of DebugOnly. // There's otherwise nothing inherent in this class which would require // it to be used on the stack. template <typename Policy> class MOZ_STACK_CLASS OpIter : private Policy { public: using Value = typename Policy::Value; using ValueVector = typename Policy::ValueVector; using TypeAndValue = TypeAndValueT<Value>; using TypeAndValueStack = Vector<TypeAndValue, 32, SystemAllocPolicy>; using ControlItem = typename Policy::ControlItem; using Control = ControlStackEntry<ControlItem>; using ControlStack = Vector<Control, 16, SystemAllocPolicy>;
[[nodiscard]] bool failEmptyStack();
[[nodiscard]] bool popStackType(StackType* type, Value* value);
[[nodiscard]] bool popWithType(ValType expected, Value* value,
StackType* stackType);
[[nodiscard]] bool popWithType(ValType expected, Value* value);
[[nodiscard]] bool popWithType(ResultType expected, ValueVector* values); template <typename ValTypeSpanT>
[[nodiscard]] bool popWithTypes(ValTypeSpanT expected, ValueVector* values);
[[nodiscard]] bool popWithRefType(Value* value, StackType* type); // Check that the top of the value stack has type `expected`, bearing in // mind that it may be a block type, hence involving multiple values. // // If `rewriteStackTypes` is true, then stack values will have their types // updated to exactly match the expected type. This is used for control // instructions that specify their own types, e.g. blocks. This prevents // subtypes from leaking - for example, returning `(ref null none)` from a // `block (result anyref)` should result in an `anyref` on the stack, not a // `noneref`. This parameter also controls the types of polymorphic values - // if true, polymorphic values will have their types created/updated from // `expected`; otherwise they will be left as `StackType::bottom()`. // // If `values` is non-null, it is filled in with Value components of the // relevant stack entries, including those of any new entries created.
[[nodiscard]] bool checkTopTypeMatches(ResultType expected,
ValueVector* values, bool rewriteStackTypes);
// Return the offset within the entire module of the last-read op.
size_t lastOpcodeOffset() const { return offsetOfLastReadOp_ ? offsetOfLastReadOp_ : d_.currentOffset();
}
// Return a BytecodeOffset describing where the current op should be reported // to trap/call.
BytecodeOffset bytecodeOffset() const { return BytecodeOffset(lastOpcodeOffset());
}
// Test whether the iterator has reached the end of the buffer. bool done() const { return d_.done(); }
// Return a pointer to the end of the buffer being decoded by this iterator. const uint8_t* end() const { return d_.end(); }
// Report a general failure.
[[nodiscard]] bool fail(constchar* msg) MOZ_COLD;
// Report a general failure with a context
[[nodiscard]] bool fail_ctx(constchar* fmt, constchar* context) MOZ_COLD;
// Return whether the innermost block has a polymorphic base of its stack. // Ideally this accessor would be removed; consider using something else. bool currentBlockHasPolymorphicBase() const { return !controlStack_.empty() && controlStack_.back().polymorphicBase();
}
// If it exists, return the BranchHint value from a function index and a // branch offset. // Branch hints are stored in a sorted vector. Because code in compiled in // order, we keep track of the most recently accessed index. // Retrieving branch hints is also done in order inside a function.
BranchHint getBranchHint(uint32_t funcIndex, uint32_t branchOffset) { if (!codeMeta_.branchHintingEnabled()) { return BranchHint::Invalid;
}
// Get the next hint in the collection while (lastBranchHintIndex_ < branchHintVector_->length() &&
(*branchHintVector_)[lastBranchHintIndex_].branchOffset <
branchOffset) {
lastBranchHintIndex_++;
}
// No hint found for this branch. if (lastBranchHintIndex_ >= branchHintVector_->length() ||
(*branchHintVector_)[lastBranchHintIndex_].branchOffset !=
branchOffset) { return BranchHint::Invalid;
}
// The last index is saved, now return the hint. return (*branchHintVector_)[lastBranchHintIndex_].value;
}
// ------------------------------------------------------------------------ // Decoding and validation interface.
// At a location where readOp is allowed, peek at the next opcode // without consuming it or updating any internal state. // Never fails: returns uint16_t(Op::Limit) in op->b0 if it can't read. void peekOp(OpBytes* op);
// Set the top N result values. void setResults(size_t count, const ValueVector& values) {
MOZ_ASSERT(valueStack_.length() >= count);
size_t base = valueStack_.length() - count; for (size_t i = 0; i < count; i++) {
valueStack_[base + i].setValue(values[i]);
}
}
bool getResults(size_t count, ValueVector* values) {
MOZ_ASSERT(valueStack_.length() >= count); if (!values->resize(count)) { returnfalse;
}
size_t base = valueStack_.length() - count; for (size_t i = 0; i < count; i++) {
(*values)[i] = valueStack_[base + i].value();
} returntrue;
}
// Set the result value of the current top-of-value-stack expression. void setResult(Value value) { valueStack_.back().setValue(value); }
// Return the result value of the current top-of-value-stack expression.
Value getResult() { return valueStack_.back().value(); }
// Return a reference to the top of the control stack.
ControlItem& controlItem() { return controlStack_.back().controlItem(); }
// Return a reference to an element in the control stack.
ControlItem& controlItem(uint32_t relativeDepth) { return controlStack_[controlStack_.length() - 1 - relativeDepth]
.controlItem();
}
// Return the LabelKind of an element in the control stack.
LabelKind controlKind(uint32_t relativeDepth) { return controlStack_[controlStack_.length() - 1 - relativeDepth].kind();
}
// Return a reference to the outermost element on the control stack.
ControlItem& controlOutermost() { return controlStack_[0].controlItem(); }
// Test whether the control-stack is empty, meaning we've consumed the final // end of the function body. bool controlStackEmpty() const { return controlStack_.empty(); }
// Return the depth of the control stack.
size_t controlStackDepth() const { return controlStack_.length(); }
// 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) const {
int32_t fromAbsoluteDepth = controlStack_.length() - fromRelativeDepth - 1; for (int32_t i = fromAbsoluteDepth; i >= 0; i--) { if (predicate(controlStack_[i].kind(), controlStack_[i].controlItem())) {
*foundRelativeDepth = controlStack_.length() - 1 - i; returntrue;
}
} returnfalse;
}
};
template <typename Policy> inlinebool OpIter<Policy>::failEmptyStack() { return valueStack_.empty() ? fail("popping value from empty stack")
: fail("popping value from outside block");
}
// This function pops exactly one value from the stack, yielding Bottom types in // various cases and therefore making it the caller's responsibility to do the // right thing for StackType::Bottom. Prefer (pop|top)WithType. This is an // optimization for the super-common case where the caller is statically // expecting the resulttype `[valtype]`. template <typename Policy> inlinebool OpIter<Policy>::popStackType(StackType* type, Value* value) {
Control& block = controlStack_.back();
MOZ_ASSERT(valueStack_.length() >= block.valueStackBase()); if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackBase())) { // If the base of this block's stack is polymorphic, then we can pop a // dummy value of the bottom type; it won't be used since we're in // unreachable code. if (block.polymorphicBase()) {
*type = StackType::bottom();
*value = Value();
// Maintain the invariant that, after a pop, there is always memory // reserved to push a value infallibly. return valueStack_.reserve(valueStack_.length() + 1);
}
// This function pops exactly one value from the stack, checking that it has the // expected type which can either be a specific value type or the bottom type. template <typename Policy> inlinebool OpIter<Policy>::popWithType(ValType expectedType, Value* value,
StackType* stackType) { if (!popStackType(stackType, value)) { returnfalse;
}
// This function pops exactly one value from the stack, checking that it has the // expected type which can either be a specific value type or the bottom type. template <typename Policy> inlinebool OpIter<Policy>::popWithType(ValType expectedType, Value* value) {
StackType stackType; return popWithType(expectedType, value, &stackType);
}
// Pops each of the given expected types (in reverse, because it's a stack). template <typename Policy> template <typename ValTypeSpanT> inlinebool OpIter<Policy>::popWithTypes(ValTypeSpanT expected,
ValueVector* values) {
size_t expectedLength = expected.size(); if (!values->resize(expectedLength)) { returnfalse;
} for (size_t i = 0; i < expectedLength; i++) {
size_t reverseIndex = expectedLength - i - 1;
ValType expectedType = expected[reverseIndex];
Value* value = &(*values)[reverseIndex]; if (!popWithType(expectedType, value)) { returnfalse;
}
} returntrue;
}
// This function pops exactly one value from the stack, checking that it is a // reference type. template <typename Policy> inlinebool OpIter<Policy>::popWithRefType(Value* value, StackType* type) { if (!popStackType(type, value)) { returnfalse;
}
if (type->isStackBottom() || type->valType().isRefType()) { returntrue;
}
UniqueChars actualText = ToString(type->valType(), codeMeta_.types); if (!actualText) { returnfalse;
}
UniqueChars error(JS_smprintf( "type mismatch: expression has type %s but expected a reference type",
actualText.get())); if (!error) { returnfalse;
}
for (size_t i = 0; i != expectedLength; i++) { // We're iterating as-if we were popping each expected/actual type one by // one, which means iterating the array of expected results backwards. // The "current" value stack length refers to what the value stack length // would have been if we were popping it.
size_t reverseIndex = expectedLength - i - 1;
ValType expectedType = expected[reverseIndex]; auto collectValue = [&](const Value& v) { if (values) {
(*values)[reverseIndex] = v;
}
};
MOZ_ASSERT(currentValueStackLength >= block.valueStackBase()); if (currentValueStackLength == block.valueStackBase()) { if (!block.polymorphicBase()) { return failEmptyStack();
}
// If the base of this block's stack is polymorphic, then we can just // pull out as many fake values as we need to validate, and create dummy // stack entries accordingly; they won't be used since we're in // unreachable code. However, if `rewriteStackTypes` is true, we must // set the types on these new entries to whatever `expected` requires // them to be.
TypeAndValue newTandV =
rewriteStackTypes ? TypeAndValue(expectedType) : TypeAndValue(); if (!valueStack_.insert(valueStack_.begin() + currentValueStackLength,
newTandV)) { returnfalse;
}
MOZ_ASSERT(valueStack_.length() >= block.valueStackBase()); if (expectedType->length() < valueStack_.length() - block.valueStackBase()) { return fail("unused values not explicitly dropped by end of block");
}
// Initialize information related to branch hinting.
lastBranchHintIndex_ = 0; if (codeMeta_.branchHintingEnabled()) {
branchHintVector_ = &codeMeta_.branchHints.getHintVector(funcIndex);
}
size_t numArgs = codeMeta_.getFuncType(funcIndex).args().length(); if (!unsetLocals_.init(locals_, numArgs)) { returnfalse;
}
return pushControl(LabelKind::Body, type);
}
template <typename Policy> inlinebool OpIter<Policy>::endFunction(const uint8_t* bodyEnd) { if (d_.currentPosition() != bodyEnd) { return fail("function body length mismatch");
}
if (!controlStack_.empty()) { return fail("unbalanced function body control flow");
}
MOZ_ASSERT(elseParamStack_.empty());
MOZ_ASSERT(unsetLocals_.empty());
if (!checkStackAtEndOfBlock(type, results)) { returnfalse;
}
if (block.kind() == LabelKind::Then) {
ResultType params = block.type().params(); // If an `if` block ends with `end` instead of `else`, then the `else` block // implicitly passes the `if` parameters as the `else` results. In that // case, assert that the `if`'s param type matches the result type. if (!checkIsSubtypeOf(params, block.type().results())) { return fail( "the parameters to an if without an else must be compatible with the " "if's result type");
}
size_t nparams = params.length();
MOZ_ASSERT(elseParamStack_.length() >= nparams); if (!resultsForEmptyElse->resize(nparams)) { returnfalse;
} const TypeAndValue* elseParams = elseParamStack_.end() - nparams; for (size_t i = 0; i < nparams; i++) {
(*resultsForEmptyElse)[i] = elseParams[i].value();
}
elseParamStack_.shrinkBy(nparams);
}
if (!pushControl(LabelKind::TryTable, *type)) { returnfalse;
}
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)) { returnfalse;
}
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 >= codeMeta_.tags.length()) { return fail("tag index out of range");
} const TagDesc& tagDesc = codeMeta_.tags[tryTableCatch.tagIndex]; const TagType& tagType = *tagDesc.type; if (!tagType.resultTypes().empty()) { return fail("catch tag must not have results");
}
}
// 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");
}
// The target branch depth is relative to the control labels outside of // this try_table. e.g. `0` is a branch to the control outside of this // try_table, not to the try_table itself. However, we've already pushed // the control block for the try_table, and users will read it after we've // returned, so we need to return the relative depth adjusted by 1 to // account for our own control block. if (tryTableCatch.labelRelativeDepth == UINT32_MAX) { return fail("catch depth out of range");
}
tryTableCatch.labelRelativeDepth += 1;
// Tagged catches will unpack the exception package and pass it to the // branch if (tryTableCatch.tagIndex != CatchAllIndex) { const TagType& tagType = *codeMeta_.tags[tryTableCatch.tagIndex].type;
ResultType tagResult = tagType.argResultType(); if (!tagResult.cloneToVector(&tryTableCatch.labelType)) { returnfalse;
}
}
// Any captured exnref is the final parameter if (tryTableCatch.captureExnRef && !tryTableCatch.labelType.append(ValType(
RefType::exn().asNonNullable()))) { returnfalse;
}
Control* block; if (!getControl(tryTableCatch.labelRelativeDepth, &block)) { returnfalse;
}
ResultType blockTargetType = block->branchTargetType(); if (!checkIsSubtypeOf(ResultType::Vector(tryTableCatch.labelType),
blockTargetType)) { returnfalse;
}
if (!readVarU32(tagIndex)) { return fail("expected tag index");
} if (*tagIndex >= codeMeta_.tags.length()) { return fail("tag index out of range");
} const TagDesc& tagDesc = codeMeta_.tags[*tagIndex]; const TagType& tagType = *tagDesc.type; if (!tagType.resultTypes().empty()) { return fail("catch tag must not have results");
}
Control& block = controlStack_.back(); if (block.kind() == LabelKind::CatchAll) { return fail("catch cannot follow a catch_all");
} if (block.kind() != LabelKind::Try && block.kind() != LabelKind::Catch) { return fail("catch can only be used within a try-catch");
}
*kind = block.kind();
*paramType = block.type().params();
if (!checkStackAtEndOfBlock(resultType, tryResults)) { returnfalse;
}
valueStack_.shrinkTo(block.valueStackBase());
block.switchToCatch(); // Reset local state to the beginning of the 'try' block.
unsetLocals_.resetToBlock(controlStack_.length() - 1);
Control& block = controlStack_.back(); if (block.kind() != LabelKind::Try && block.kind() != LabelKind::Catch) { return fail("catch_all can only be used within a try-catch");
}
*kind = block.kind();
*paramType = block.type().params();
if (!checkStackAtEndOfBlock(resultType, tryResults)) { returnfalse;
}
valueStack_.shrinkTo(block.valueStackBase());
block.switchToCatchAll(); // Reset local state to the beginning of the 'try' block.
unsetLocals_.resetToBlock(controlStack_.length() - 1); returntrue;
}
Control& block = controlStack_.back(); if (block.kind() != LabelKind::Try) { return fail("delegate can only be used within a try");
}
uint32_t delegateDepth; if (!readVarU32(&delegateDepth)) { return fail("unable to read delegate depth");
}
// Depths for delegate start counting in the surrounding block. if (delegateDepth >= controlStack_.length() - 1) { return fail("delegate depth exceeds current nesting level");
}
*relativeDepth = delegateDepth + 1;
// Because `delegate` acts like `end` and ends the block, we will check // the stack here. return checkStackAtEndOfBlock(resultType, tryResults);
}
// We need popDelegate because readDelegate cannot pop the control stack // itself, as its caller may need to use the control item for delegate. template <typename Policy> inlinevoid OpIter<Policy>::popDelegate() {
MOZ_ASSERT(Classify(op_) == OpKind::Delegate);
if (!readVarU32(tagIndex)) { return fail("expected tag index");
} if (*tagIndex >= codeMeta_.tags.length()) { return fail("tag index out of range");
} const TagDesc& tagDesc = codeMeta_.tags[*tagIndex]; const TagType& tagType = *tagDesc.type; if (!tagType.resultTypes().empty()) { return fail("throw tag must not have results");
}
if (!popWithType(codeMeta_.tags[*tagIndex].type->argResultType(),
argValues)) { returnfalse;
}
if (undefinedBits != 0) { return fail("invalid memory flags");
}
if (hasMemoryIndex != 0) { if (!readVarU32(&addr->memoryIndex)) { return fail("unable to read memory index");
}
} else {
addr->memoryIndex = 0;
}
if (addr->memoryIndex >= codeMeta_.numMemories()) { return fail("memory index out of range");
}
if (!readVarU64(&addr->offset)) { return fail("unable to read load offset");
}
AddressType at = codeMeta_.memories[addr->memoryIndex].addressType(); if (at == AddressType::I32 && addr->offset > UINT32_MAX) { return fail("offset too large for memory type");
}
if (alignLog2 >= 32 || (uint32_t(1) << alignLog2) > byteSize) { return fail("greater than natural alignment");
}
if (!popWithType(ToValType(at), &addr->base)) { returnfalse;
}
if (typed) {
uint32_t length; if (!readVarU32(&length)) { return fail("unable to read select result length");
} if (length != 1) { return fail("bad number of results");
}
ValType result; if (!readValType(&result)) { return fail("invalid result type for select");
}
if (!popWithType(ValType::I32, condition)) { returnfalse;
} if (!popWithType(result, falseValue)) { returnfalse;
} if (!popWithType(result, trueValue)) { returnfalse;
}
if (!d_.readFuncIndex(funcIndex)) { returnfalse;
} if (*funcIndex >= codeMeta_.funcs.length()) { return fail("function index out of range");
} if (kind_ == OpIter::Func && !codeMeta_.funcs[*funcIndex].canRefFunc()) { return fail( "function index is not declared in a section before the code section");
}
if (!readVarU32(relativeDepth)) { return fail("unable to read br_on_non_null depth");
}
Control* block = nullptr; if (!getControl(*relativeDepth, &block)) { returnfalse;
}
*type = block->branchTargetType();
// Check we at least have one type in the branch target type. if (type->length() < 1) { return fail("type mismatch: target block type expected to be [_, ref]");
}
// Pop the condition reference.
StackType refType; if (!popWithRefType(condition, &refType)) { returnfalse;
}
// Push non-nullable version of condition reference on the stack, prior // checking the target type below. if (!(refType.isStackBottom()
? push(refType)
: push(TypeAndValue(refType.asNonNullable(), *condition)))) { returnfalse;
}
// Check if the type stack matches the branch target type. if (!checkTopTypeMatches(*type, values, /*rewriteStackTypes=*/true)) { returnfalse;
}
// Pop the condition reference -- the null-branch does not receive the value.
StackType unusedType;
Value unusedValue; return popStackType(&unusedType, &unusedValue);
}
template <typename Policy> inlinebool OpIter<Policy>::popCallArgs(const ValTypeVector& expectedTypes,
ValueVector* values) { // Iterate through the argument types backward so that pops occur in the // right order.
if (!values->resize(expectedTypes.length())) { returnfalse;
}
for (int32_t i = int32_t(expectedTypes.length()) - 1; i >= 0; i--) { if (!popWithType(expectedTypes[i], &(*values)[i])) { returnfalse;
}
}
if (!popCallArgs(funcType.args(), argValues)) { returnfalse;
}
// Check if callee results are subtypes of caller's.
Control& body = controlStack_[0];
MOZ_ASSERT(body.kind() == LabelKind::Body); if (!checkIsSubtypeOf(ResultType::Vector(funcType.results()),
body.resultType())) { returnfalse;
}
if (!readVarU32(funcTypeIndex)) { return fail("unable to read call_indirect signature index");
}
if (*funcTypeIndex >= codeMeta_.numTypes()) { return fail("signature index out of range");
}
if (!readVarU32(tableIndex)) { return fail("unable to read call_indirect table index");
} if (*tableIndex >= codeMeta_.tables.length()) { // Special case this for improved user experience. if (!codeMeta_.tables.length()) { return fail("can't call_indirect without a table");
} return fail("table index out of range for call_indirect");
} if (!codeMeta_.tables[*tableIndex].elemType().isFuncHierarchy()) { return fail("indirect calls must go through a table of 'funcref'");
}
if (!popWithType(ToValType(codeMeta_.tables[*tableIndex].addressType()),
callee)) { returnfalse;
}
if (!readVarU32(funcTypeIndex)) { return fail("unable to read return_call_indirect signature index");
} if (*funcTypeIndex >= codeMeta_.numTypes()) { return fail("signature index out of range");
}
if (!readVarU32(tableIndex)) { return fail("unable to read return_call_indirect table index");
} if (*tableIndex >= codeMeta_.tables.length()) { // Special case this for improved user experience. if (!codeMeta_.tables.length()) { return fail("can't return_call_indirect without a table");
} return fail("table index out of range for return_call_indirect");
} if (!codeMeta_.tables[*tableIndex].elemType().isFuncHierarchy()) { return fail("indirect calls must go through a table of 'funcref'");
}
if (!popWithType(ToValType(codeMeta_.tables[*tableIndex].addressType()),
callee)) { returnfalse;
}
if (!popCallArgs(funcType.args(), argValues)) { returnfalse;
}
// Check if callee results are subtypes of caller's.
Control& body = controlStack_[0];
MOZ_ASSERT(body.kind() == LabelKind::Body); if (!checkIsSubtypeOf(ResultType::Vector(funcType.results()),
body.resultType())) { returnfalse;
}
if (!popWithType(ValType(RefType::fromTypeDef(&typeDef, true)), callee)) { returnfalse;
}
if (!popCallArgs(funcType.args(), argValues)) { returnfalse;
}
// Check if callee results are subtypes of caller's.
Control& body = controlStack_[0];
MOZ_ASSERT(body.kind() == LabelKind::Body); if (!checkIsSubtypeOf(ResultType::Vector(funcType.results()),
body.resultType())) { returnfalse;
}
// Spec requires (dest, src) as of 2019-10-04. if (!readVarU32(dstMemOrTableIndex)) { returnfalse;
} if (!readVarU32(srcMemOrTableIndex)) { returnfalse;
}
if (isMem) { if (*srcMemOrTableIndex >= codeMeta_.memories.length() ||
*dstMemOrTableIndex >= codeMeta_.memories.length()) { return fail("memory index out of range for memory.copy");
}
} else { if (*dstMemOrTableIndex >= codeMeta_.tables.length() ||
*srcMemOrTableIndex >= codeMeta_.tables.length()) { return fail("table index out of range for table.copy");
}
ValType dstElemType = codeMeta_.tables[*dstMemOrTableIndex].elemType();
ValType srcElemType = codeMeta_.tables[*srcMemOrTableIndex].elemType(); if (!checkIsSubtypeOf(srcElemType, dstElemType)) { returnfalse;
}
}
if (!readVarU32(segIndex)) { return fail("unable to read segment index");
}
if (isData) { if (codeMeta_.dataCount.isNothing()) { return fail("data.drop requires a DataCount section");
} if (*segIndex >= *codeMeta_.dataCount) { return fail("data.drop segment index out of range");
}
} else { if (*segIndex >= codeMeta_.elemSegmentTypes.length()) { return fail("element segment index out of range for elem.drop");
}
}
if (!readVarU32(segIndex)) { return fail("unable to read segment index");
}
uint32_t memOrTableIndex = 0; if (!readVarU32(&memOrTableIndex)) { returnfalse;
}
if (isMem) { if (memOrTableIndex >= codeMeta_.memories.length()) { return fail("memory index out of range for memory.init");
}
*dstMemOrTableIndex = memOrTableIndex;
if (codeMeta_.dataCount.isNothing()) { return fail("memory.init requires a DataCount section");
} if (*segIndex >= *codeMeta_.dataCount) { return fail("memory.init segment index out of range");
}
} else { if (memOrTableIndex >= codeMeta_.tables.length()) { return fail("table index out of range for table.init");
}
*dstMemOrTableIndex = memOrTableIndex;
if (*segIndex >= codeMeta_.elemSegmentTypes.length()) { return fail("table.init segment index out of range");
} if (!checkIsSubtypeOf(codeMeta_.elemSegmentTypes[*segIndex],
codeMeta_.tables[*dstMemOrTableIndex].elemType())) { returnfalse;
}
}
if (!popWithType(ValType::I32, len)) { returnfalse;
}
if (!popWithType(ValType::I32, src)) { returnfalse;
}
if (!readVarU32(tableIndex)) { return fail("unable to read table index");
} if (*tableIndex >= codeMeta_.tables.length()) { return fail("table index out of range for table.fill");
}
if (!readVarU32(memoryIndex)) { return fail("failed to read memory index");
} if (*memoryIndex >= codeMeta_.memories.length()) { return fail("memory index out of range for memory.discard");
}
if (!readVarU32(tableIndex)) { return fail("unable to read table index");
} if (*tableIndex >= codeMeta_.tables.length()) { return fail("table index out of range for table.get");
}
if (!readVarU32(tableIndex)) { return fail("unable to read table index");
} if (*tableIndex >= codeMeta_.tables.length()) { return fail("table index out of range for table.grow");
}
if (!readVarU32(tableIndex)) { return fail("unable to read table index");
} if (*tableIndex >= codeMeta_.tables.length()) { return fail("table index out of range for table.set");
}
if (!readVarU32(tableIndex)) { return fail("unable to read table index");
} if (*tableIndex >= codeMeta_.tables.length()) { return fail("table index out of range for table.size");
}
// The branch target must equal the tag params length plus one (for the // continuation). Be careful to avoid overflow. if (branchTargetType.empty() ||
branchTargetType.length() - 1 != tagType.argTypes().length()) { return fail("handler: invalid label type for tag");
}
// The branch target must take the tag arguments first. for (uint32_t i = 0; i < tagArgTypes.length(); i++) {
ValType tagArgType = tagArgTypes[i];
ValType branchType = branchTargetType[i]; if (!checkIsSubtypeOf(tagArgType, branchType)) { returnfalse;
}
}
// The branch target must take the suspended cont last. Which makes it // the 'top' of the stack in the block.
ValType suspendedContValType = branchTargetType[tagArgTypes.length()]; if (!suspendedContValType.isTypeRef() ||
!suspendedContValType.typeDef()->isContType()) { return fail("branch label must take a cont");
}
// Check the continuation type the label takes to ensure it is // compatible with the suspend tag and resumed continuation. constTypeDef& suspendedContTypeDef = *suspendedContValType.typeDef(); const ContType& suspendedContType = suspendedContTypeDef.contType(); const ValTypeVector& suspendedContParams = suspendedContType.args(); const ValTypeVector& suspendedContResults = suspendedContType.results();
// The results of the tag are what the `suspend` instruction we are // handling needs to receive to continue execution. This means that that // the suspended continuation's params must be subtypes of the tag // results. // // e.g. a `suspend (tag (result eqref))` can create a // `(cont (param (structref)))`. Resuming the continuation with // a `structref` is safe, because the `suspend` instruction expects a // `eqref`. if (!checkIsSubtypeOf(suspendedContParams, tagResultTypes)) { returnfalse;
}
// The continuation type we are resuming with handlers has results. When // this resume continuation is suspended, the new suspended // continuation's results must be supertypes of the original resume // continuation's results. // // e.g. If we resume a (cont (result (structref))) it can suspend to // become a // (cont (result eqref)). The continuation's results can become less // precise as you suspend/resume a continuation. if (!checkIsSubtypeOf(resumedContType.funcType().results(),
suspendedContResults)) { returnfalse;
}
handlers->infallibleAppend(HandlerExpr(tagIndex, labelDepth)); break;
} case HandlerKind::Switch: { // Switch tag params must be empty if (!tagArgTypes.empty()) { return fail("handler: switch tag cannot have params");
}
// Switch tag results must exactly match the resumed continuation // results if (tagResultTypes.length() !=
resumedContType.funcType().results().length()) { return fail( "handler: switch tag results must match resumed cont results");
} for (uint32_t i = 0; i < tagResultTypes.length(); i++) { if (tagResultTypes[i] != resumedContType.funcType().result(i)) { return fail( "handler: switch tag result must exactly match resumed cont " "result");
}
}
if (!argValues->resize(structType.fields_.length())) { returnfalse;
}
static_assert(MaxStructFields <= INT32_MAX, "Or we iloop below");
for (int32_t i = structType.fields_.length() - 1; i >= 0; i--) { if (!popWithType(structType.fields_[i].type.widenToValType(),
&(*argValues)[i])) { returnfalse;
}
}
if (*numElements > MaxArrayNewFixedElements) { return fail("too many array.new_fixed elements");
}
if (!values->reserve(*numElements)) { returnfalse;
}
ValType widenedElementType = arrayType.elementType().widenToValType(); for (uint32_t i = 0; i < *numElements; i++) {
Value v; if (!popWithType(widenedElementType, &v)) { returnfalse;
}
values->infallibleAppend(v);
}
if (!readArrayTypeIndex(typeIndex)) { returnfalse;
}
if (!readVarU32(segIndex)) { return fail("unable to read segment index");
}
constTypeDef& typeDef = codeMeta_.types->type(*typeIndex); const ArrayType& arrayType = typeDef.arrayType();
StorageType elemType = arrayType.elementType(); if (!elemType.isNumber() && !elemType.isPacked() && !elemType.isVector()) { return fail("element type must be i8/i16/i32/i64/f32/f64/v128");
} if (codeMeta_.dataCount.isNothing()) { return fail("datacount section missing");
} if (*segIndex >= *codeMeta_.dataCount) { return fail("segment index is out of range");
}
if (!popWithType(ValType::I32, numElements)) { returnfalse;
} if (!popWithType(ValType::I32, offset)) { returnfalse;
}
if (!readArrayTypeIndex(typeIndex)) { returnfalse;
}
if (!readVarU32(segIndex)) { return fail("unable to read segment index");
}
constTypeDef& typeDef = codeMeta_.types->type(*typeIndex); const ArrayType& arrayType = typeDef.arrayType();
StorageType dstElemType = arrayType.elementType(); if (!dstElemType.isRefType()) { return fail("element type is not a reftype");
} if (*segIndex >= codeMeta_.elemSegmentTypes.length()) { return fail("segment index is out of range");
}
RefType srcElemType = codeMeta_.elemSegmentTypes[*segIndex]; // srcElemType needs to be a subtype (child) of dstElemType if (!checkIsSubtypeOf(srcElemType, dstElemType.refType())) { return fail("incompatible element types");
}
if (!popWithType(ValType::I32, numElements)) { returnfalse;
} if (!popWithType(ValType::I32, offset)) { returnfalse;
}
if (!readArrayTypeIndex(typeIndex)) { returnfalse;
}
if (!readVarU32(segIndex)) { return fail("unable to read segment index");
}
constTypeDef& typeDef = codeMeta_.types->type(*typeIndex); const ArrayType& arrayType = typeDef.arrayType();
StorageType elemType = arrayType.elementType(); if (!elemType.isNumber() && !elemType.isPacked() && !elemType.isVector()) { return fail("element type must be i8/i16/i32/i64/f32/f64/v128");
} if (!arrayType.isMutable()) { return fail("destination array is not mutable");
} if (codeMeta_.dataCount.isNothing()) { return fail("datacount section missing");
} if (*segIndex >= *codeMeta_.dataCount) { return fail("segment index is out of range");
}
if (!popWithType(ValType::I32, length)) { returnfalse;
} if (!popWithType(ValType::I32, segOffset)) { returnfalse;
} if (!popWithType(ValType::I32, arrayIndex)) { returnfalse;
} return popWithType(RefType::fromTypeDef(&typeDef, true), array);
}
if (!readArrayTypeIndex(typeIndex)) { returnfalse;
}
if (!readVarU32(segIndex)) { return fail("unable to read segment index");
}
constTypeDef& typeDef = codeMeta_.types->type(*typeIndex); const ArrayType& arrayType = typeDef.arrayType();
StorageType dstElemType = arrayType.elementType(); if (!arrayType.isMutable()) { return fail("destination array is not mutable");
} if (!dstElemType.isRefType()) { return fail("element type is not a reftype");
} if (*segIndex >= codeMeta_.elemSegmentTypes.length()) { return fail("segment index is out of range");
}
RefType srcElemType = codeMeta_.elemSegmentTypes[*segIndex]; // srcElemType needs to be a subtype (child) of dstElemType if (!checkIsSubtypeOf(srcElemType, dstElemType.refType())) { return fail("incompatible element types");
}
if (!popWithType(ValType::I32, length)) { returnfalse;
} if (!popWithType(ValType::I32, segOffset)) { returnfalse;
} if (!popWithType(ValType::I32, arrayIndex)) { returnfalse;
} return popWithType(RefType::fromTypeDef(&typeDef, true), array);
}
if (!readArrayTypeIndex(dstArrayTypeIndex)) { returnfalse;
} if (!readArrayTypeIndex(srcArrayTypeIndex)) { returnfalse;
}
// `dstArrayTypeIndex`/`srcArrayTypeIndex` are ensured by the above to both be // array types. Reject if: // * the dst array is not of mutable type // * the element types are incompatible constTypeDef& dstTypeDef = codeMeta_.types->type(*dstArrayTypeIndex); const ArrayType& dstArrayType = dstTypeDef.arrayType(); constTypeDef& srcTypeDef = codeMeta_.types->type(*srcArrayTypeIndex); const ArrayType& srcArrayType = srcTypeDef.arrayType();
StorageType dstElemType = dstArrayType.elementType();
StorageType srcElemType = srcArrayType.elementType(); if (!dstArrayType.isMutable()) { return fail("destination array is not mutable");
}
if (!checkIsSubtypeOf(srcElemType, dstElemType)) { return fail("incompatible element types");
}
MOZ_ASSERT(dstElemType.isRefType() == srcElemType.isRefType());
if (!popWithType(ValType::I32, numElements)) { returnfalse;
} if (!popWithType(ValType::I32, srcIndex)) { returnfalse;
} if (!popWithType(RefType::fromTypeDef(&srcTypeDef, true), srcArray)) { returnfalse;
} if (!popWithType(ValType::I32, dstIndex)) { returnfalse;
}
// `br_on_cast <flags> <labelRelativeDepth> <rt1> <rt2>` // branches if a reference has a given heap type. // // V6 spec text follows - note that br_on_cast and br_on_cast_fail are both // handled by this function (disambiguated by a flag). // // * `br_on_cast <labelidx> <reftype> <reftype>` branches if a reference has a // given type // - `br_on_cast $l rt1 rt2 : [t0* rt1] -> [t0* rt1\rt2]` // - iff `$l : [t0* rt2]` // - and `rt2 <: rt1` // - passes operand along with branch under target type, plus possible extra // args // - if `rt2` contains `null`, branches on null, otherwise does not // * `br_on_cast_fail <labelidx> <reftype> <reftype>` branches if a reference // does not have a given type // - `br_on_cast_fail $l rt1 rt2 : [t0* rt1] -> [t0* rt2]` // - iff `$l : [t0* rt1\rt2]` // - and `rt2 <: rt1` // - passes operand along with branch, plus possible extra args // - if `rt2` contains `null`, does not branch on null, otherwise does // where: // - `(ref null1? ht1)\(ref null ht2) = (ref ht1)` // - `(ref null1? ht1)\(ref ht2) = (ref null1? ht1)` // // The `rt1\rt2` syntax is a "diff" - it is basically rt1 minus rt2, because a // successful cast to rt2 will branch away. So if rt2 allows null, the result // after a non-branch will be non-null; on the other hand, if rt2 is // non-nullable, the cast will have nothing to say about nullability and the // nullability of rt1 will be preserved. // // `values` will be nonempty after the call, and its last entry will be the // type that causes a branch (rt1\rt2 or rt2, depending).
if (!readVarU32(labelRelativeDepth)) { return fail("unable to read br_on_cast depth");
}
// This is distinct from the actual source type we pop from the stack, which // can be more specific and allow for better optimizations.
RefType immediateSourceType; if (!readHeapType(sourceNullable, &immediateSourceType)) { return fail("unable to read br_on_cast source type");
}
if (!readHeapType(destNullable, destType)) { return fail("unable to read br_on_cast dest type");
}
if (!destType->isCastable()) { return fail("ref type is not castable");
}
// Check that source and destination types are compatible if (!checkIsSubtypeOf(*destType, immediateSourceType)) { return fail( "type mismatch: source and destination types for cast are " "incompatible");
}
// Get the branch target type, which will also determine the type of extra // values that are passed along on branch.
Control* block = nullptr; if (!getControl(*labelRelativeDepth, &block)) { returnfalse;
}
*labelType = block->branchTargetType();
// Check we have at least one value slot in the branch target type, so as to // receive the casted or non-casted type when we branch. const size_t labelTypeNumValues = labelType->length(); if (labelTypeNumValues < 1) { return fail("type mismatch: branch target type has no value types");
}
// The last value slot in the branch target type is what is being cast. // This slot is guaranteed to exist by the above check.
// Check that the branch target type can accept typeOnBranch. if (!checkIsSubtypeOf(typeOnBranch, (*labelType)[labelTypeNumValues - 1])) { returnfalse;
}
// Replace the top operand with the result of falling through. Even branching // on success can change the type on top of the stack on fallthrough.
Value inputValue;
StackType inputType; if (!popWithType(immediateSourceType, &inputValue, &inputType)) { returnfalse;
}
*sourceType = inputType.valTypeOr(immediateSourceType).refType();
infalliblePush(TypeAndValue(typeOnFallthrough, inputValue));
// Create a copy of the branch target type, with the relevant value slot // replaced by typeOnFallthrough.
ValTypeVector fallthroughTypes; if (!labelType->cloneToVector(&fallthroughTypes)) { returnfalse;
}
fallthroughTypes[labelTypeNumValues - 1] = typeOnFallthrough;
StackType actualOperandType; if (!popWithType(ValType(operandType), operandValue, &actualOperandType)) { returnfalse;
}
// The result nullability is the same as the operand nullability bool outputNullable = actualOperandType.isNullableAsOperand();
infalliblePush(ValType(resultType.withIsNullable(outputNullable))); returntrue;
}
for (unsignedchar& byte : selectMask->bytes) {
uint8_t tmp; if (!readFixedU8(&tmp)) { return fail("unable to read shuffle index");
} if (tmp > 31) { return fail("shuffle index out of range");
}
byte = tmp;
}
if (!popWithType(ValType::V128, v2)) { returnfalse;
}
if (!popWithType(ValType::V128, v1)) { returnfalse;
}
// cont.bind takes an input continuation type, and binds however many // parameters it needs to produce the output continuation type. if (!readContTypeIndex(inputContTypeIndex) ||
!readContTypeIndex(outputContTypeIndex)) { returnfalse;
}
// The input must have more params than the output. Those are what we are // binding. if (outputArgCount > inputArgCount) { return fail("cont.bind: output cont has too many params");
}
// All remaining params on output are 'unbound'. We must check they are // compatible between input/output.
uint32_t unboundParamCount = outputArgCount; for (uint32_t i = 0; i < unboundParamCount; i++) { // The unbound params start at the end of the continuation type
uint32_t unboundOutputParamIndex = outputArgCount - i - 1;
uint32_t unboundInputParamIndex = inputArgCount - i - 1;
// The output param must be a subtype of the input param. if (!checkIsSubtypeOf(unboundOutputParam, unboundInputParam)) { returnfalse;
}
}
// We aren't binding results, they must have the same length. if (inputResultCount != outputResultCount) { return fail("cont.bind: conts have mismatched results");
}
// The results of input/output continuation must be compatible. for (uint32_t i = 0; i < outputResultCount; i++) {
ValType unboundOutputResult = outputResults[i];
ValType unboundInputResult = inputResults[i];
// The input result must be a subtype of the output result. if (!checkIsSubtypeOf(unboundInputResult, unboundOutputResult)) { returnfalse;
}
}
// Pop the input continuation. if (!popWithType(ValType(RefType::fromTypeDef(&inputContTypeDef, true)),
cont)) { returnfalse;
}
// Pop the bound params.
uint32_t boundParams = inputArgCount - outputArgCount; auto boundArgTypes = mozilla::Span(inputArgs.begin(), boundParams); if (!popWithTypes(boundArgTypes, boundArgs)) { returnfalse;
}
// Push the output continuation.
infalliblePush(ValType(RefType::fromTypeDef(&outputContTypeDef, false))); returntrue;
}
if (!readTagIndex(tagIndex)) { returnfalse;
} const TagType& tagType = codeMeta_.getTagType(*tagIndex); if (!tagType.resultTypes().empty()) { return fail("resume_throw tag must not have results");
}
static_assert(std::is_trivially_copyable<
js::wasm::TypeAndValueT<mozilla::Nothing>>::value, "Must be trivially copyable");
static_assert(std::is_trivially_destructible<
js::wasm::TypeAndValueT<mozilla::Nothing>>::value, "Must be trivially destructible");
static_assert(std::is_trivially_copyable<
js::wasm::ControlStackEntry<mozilla::Nothing>>::value, "Must be trivially copyable");
static_assert(std::is_trivially_destructible<
js::wasm::ControlStackEntry<mozilla::Nothing>>::value, "Must be trivially destructible");
#endif// wasm_op_iter_h
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.79Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-09-30)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.