diff --git a/js/src/jit-test/tests/wasm/binary.js b/js/src/jit-test/tests/wasm/binary.js index fcdfc18a1dee..d95294d63cec 100644 --- a/js/src/jit-test/tests/wasm/binary.js +++ b/js/src/jit-test/tests/wasm/binary.js @@ -230,19 +230,10 @@ assertErrorMessage(() => wasmEval(moduleWithSections([ nameSection([moduleNameSubsection('hi')])]) ).f(), RuntimeError, /unreachable/); -// Diagnose invalid block signature types. -for (var bad of [0xff, 1, 0x3f]) +// Diagnose nonstandard block signature types. +for (var bad of [0xff, 0, 1, 0x3f]) assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid .*block type/); -if (wasmMultiValueEnabled()) { - // In this test module, 0 denotes a void-to-void block type. - let binary = moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, 0, EndCode]})])]); - assertEq(WebAssembly.validate(binary), true); -} else { - const bad = 0; - assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid .*block type/); -} - // Ensure all invalid opcodes rejected for (let op of undefinedOpcodes) { let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[op]})])]); diff --git a/js/src/jit-test/tests/wasm/gc/binary.js b/js/src/jit-test/tests/wasm/gc/binary.js index 28b6cd81646c..1b72d88599f7 100644 --- a/js/src/jit-test/tests/wasm/gc/binary.js +++ b/js/src/jit-test/tests/wasm/gc/binary.js @@ -18,7 +18,7 @@ const invalidRefBlockType = funcBody({locals:[], body:[ 0x42, EndCode, ]}); -checkInvalid(invalidRefBlockType, /ref/); +checkInvalid(invalidRefBlockType, /invalid inline block type/); const invalidTooBigRefType = funcBody({locals:[], body:[ BlockCode, @@ -26,4 +26,4 @@ const invalidTooBigRefType = funcBody({locals:[], body:[ varU32(1000000), EndCode, ]}); -checkInvalid(invalidTooBigRefType, /ref/); +checkInvalid(invalidTooBigRefType, /invalid inline block type/); diff --git a/js/src/jit-test/tests/wasm/regress/misc-control-flow.js b/js/src/jit-test/tests/wasm/regress/misc-control-flow.js index 382840eaa493..6aea6e9b0fbb 100644 --- a/js/src/jit-test/tests/wasm/regress/misc-control-flow.js +++ b/js/src/jit-test/tests/wasm/regress/misc-control-flow.js @@ -202,7 +202,7 @@ wasmFailValidateText(` (br_table 1 0 (i32.const 15)) ) ) -)`, /br_table targets must all have the same arity/); +)`, /br_table operand must be subtype of all target types/); wasmFailValidateText(` (module @@ -212,7 +212,7 @@ wasmFailValidateText(` (br_table 1 0 (i32.const 15)) ) ) -)`, /br_table targets must all have the same arity/); +)`, /br_table operand must be subtype of all target types/); wasmValidateText(` (module diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp index 100c7658574b..6305a8d6e64a 100644 --- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -2441,21 +2441,11 @@ class BaseCompiler final : public BaseCompilerInterface { deadThenBranch(false) {} }; - class NothingVector { - Nothing unused_; - - public: - bool resize(size_t length) { return true; } - Nothing& operator[](size_t) { return unused_; } - Nothing& back() { return unused_; } - }; - struct BaseCompilePolicy { // The baseline compiler tracks values on a stack of its own -- it // needs to scan that stack for spilling -- and thus has no need // for the values maintained by the iterator. using Value = Nothing; - using ValueVector = NothingVector; // The baseline compiler uses the iterator's control stack, attaching // its own control information. @@ -2787,105 +2777,89 @@ class BaseCompiler final : public BaseCompilerInterface { } } - void maybeReserveJoinRegI(ResultType type) { - if (type.empty()) { - return; - } - MOZ_ASSERT(type.length() == 1, "multi-value joins unimplemented"); - switch (type[0].code()) { - case ValType::I32: + void maybeReserveJoinRegI(ExprType type) { + switch (type.code()) { + case ExprType::I32: needI32(joinRegI32_); break; - case ValType::I64: + case ExprType::I64: needI64(joinRegI64_); break; - case ValType::F32: - case ValType::F64: - break; - case ValType::FuncRef: - case ValType::AnyRef: - case ValType::NullRef: - case ValType::Ref: + case ExprType::FuncRef: + case ExprType::AnyRef: + case ExprType::NullRef: + case ExprType::Ref: needRef(joinRegPtr_); break; + default:; } } - void maybeUnreserveJoinRegI(ResultType type) { - if (type.empty()) { - return; - } - MOZ_ASSERT(type.length() == 1, "multi-value joins unimplemented"); - switch (type[0].code()) { - case ValType::I32: + void maybeUnreserveJoinRegI(ExprType type) { + switch (type.code()) { + case ExprType::I32: freeI32(joinRegI32_); break; - case ValType::I64: + case ExprType::I64: freeI64(joinRegI64_); break; - case ValType::F32: - case ValType::F64: - break; - case ValType::FuncRef: - case ValType::AnyRef: - case ValType::NullRef: - case ValType::Ref: + case ExprType::FuncRef: + case ExprType::AnyRef: + case ExprType::NullRef: + case ExprType::Ref: freeRef(joinRegPtr_); break; + default:; } } - void maybeReserveJoinReg(ResultType type) { - if (type.empty()) { - return; - } - MOZ_ASSERT(type.length() == 1, "multi-value joins unimplemented"); - switch (type[0].code()) { - case ValType::I32: + void maybeReserveJoinReg(ExprType type) { + switch (type.code()) { + case ExprType::I32: needI32(joinRegI32_); break; - case ValType::I64: + case ExprType::I64: needI64(joinRegI64_); break; - case ValType::F32: + case ExprType::F32: needF32(joinRegF32_); break; - case ValType::F64: + case ExprType::F64: needF64(joinRegF64_); break; - case ValType::Ref: - case ValType::NullRef: - case ValType::FuncRef: - case ValType::AnyRef: + case ExprType::Ref: + case ExprType::NullRef: + case ExprType::FuncRef: + case ExprType::AnyRef: needRef(joinRegPtr_); break; + default: + break; } } - void maybeUnreserveJoinReg(ResultType type) { - if (type.empty()) { - return; - } - MOZ_ASSERT(type.length() == 1, "multi-value joins unimplemented"); - switch (type[0].code()) { - case ValType::I32: + void maybeUnreserveJoinReg(ExprType type) { + switch (type.code()) { + case ExprType::I32: freeI32(joinRegI32_); break; - case ValType::I64: + case ExprType::I64: freeI64(joinRegI64_); break; - case ValType::F32: + case ExprType::F32: freeF32(joinRegF32_); break; - case ValType::F64: + case ExprType::F64: freeF64(joinRegF64_); break; - case ValType::Ref: - case ValType::NullRef: - case ValType::FuncRef: - case ValType::AnyRef: + case ExprType::Ref: + case ExprType::NullRef: + case ExprType::FuncRef: + case ExprType::AnyRef: freeRef(joinRegPtr_); break; + default: + break; } } @@ -3779,47 +3753,48 @@ class BaseCompiler final : public BaseCompilerInterface { // popping of the stack we can just use the JoinReg as it will // become available in that process. - MOZ_MUST_USE Maybe popJoinRegUnlessVoid(ResultType type) { - if (type.empty()) { - return Nothing(); - } - MOZ_ASSERT(type.length() == 1, "multi-value return unimplemented"); - switch (type[0].code()) { - case ValType::I32: { + MOZ_MUST_USE Maybe popJoinRegUnlessVoid(ExprType type) { + switch (type.code()) { + case ExprType::Void: { + return Nothing(); + } + case ExprType::I32: { DebugOnly k(stk_.back().kind()); MOZ_ASSERT(k == Stk::RegisterI32 || k == Stk::ConstI32 || k == Stk::MemI32 || k == Stk::LocalI32); return Some(AnyReg(popI32(joinRegI32_))); } - case ValType::I64: { + case ExprType::I64: { DebugOnly k(stk_.back().kind()); MOZ_ASSERT(k == Stk::RegisterI64 || k == Stk::ConstI64 || k == Stk::MemI64 || k == Stk::LocalI64); return Some(AnyReg(popI64(joinRegI64_))); } - case ValType::F64: { + case ExprType::F64: { DebugOnly k(stk_.back().kind()); MOZ_ASSERT(k == Stk::RegisterF64 || k == Stk::ConstF64 || k == Stk::MemF64 || k == Stk::LocalF64); return Some(AnyReg(popF64(joinRegF64_))); } - case ValType::F32: { + case ExprType::F32: { DebugOnly k(stk_.back().kind()); MOZ_ASSERT(k == Stk::RegisterF32 || k == Stk::ConstF32 || k == Stk::MemF32 || k == Stk::LocalF32); return Some(AnyReg(popF32(joinRegF32_))); } - case ValType::Ref: - case ValType::NullRef: - case ValType::FuncRef: - case ValType::AnyRef: { + case ExprType::Ref: + case ExprType::NullRef: + case ExprType::FuncRef: + case ExprType::AnyRef: { DebugOnly k(stk_.back().kind()); MOZ_ASSERT(k == Stk::RegisterRef || k == Stk::ConstRef || k == Stk::MemRef || k == Stk::LocalRef); return Some(AnyReg(popRef(joinRegPtr_))); } + default: { + MOZ_CRASH("Compiler bug: unexpected expression type"); + } } - // MOZ_CRASH("Compiler bug: unexpected expression type"); } // If we ever start not sync-ing on entry to Block (but instead try to sync @@ -3828,37 +3803,36 @@ class BaseCompiler final : public BaseCompilerInterface { // joinreg in the contexts it's being used, so some other solution will need // to be found. - MOZ_MUST_USE Maybe captureJoinRegUnlessVoid(ResultType type) { - if (type.empty()) { - return Nothing(); - } - MOZ_ASSERT(type.length() == 1, "multi-value return unimplemented"); - switch (type[0].code()) { - case ValType::I32: + MOZ_MUST_USE Maybe captureJoinRegUnlessVoid(ExprType type) { + switch (type.code()) { + case ExprType::I32: MOZ_ASSERT(isAvailableI32(joinRegI32_)); needI32(joinRegI32_); return Some(AnyReg(joinRegI32_)); - case ValType::I64: + case ExprType::I64: MOZ_ASSERT(isAvailableI64(joinRegI64_)); needI64(joinRegI64_); return Some(AnyReg(joinRegI64_)); - case ValType::F32: + case ExprType::F32: MOZ_ASSERT(isAvailableF32(joinRegF32_)); needF32(joinRegF32_); return Some(AnyReg(joinRegF32_)); - case ValType::F64: + case ExprType::F64: MOZ_ASSERT(isAvailableF64(joinRegF64_)); needF64(joinRegF64_); return Some(AnyReg(joinRegF64_)); - case ValType::Ref: - case ValType::NullRef: - case ValType::FuncRef: - case ValType::AnyRef: + case ExprType::Ref: + case ExprType::NullRef: + case ExprType::FuncRef: + case ExprType::AnyRef: MOZ_ASSERT(isAvailableRef(joinRegPtr_)); needRef(joinRegPtr_); return Some(AnyReg(joinRegPtr_)); + case ExprType::Void: + return Nothing(); + default: + MOZ_CRASH("Compiler bug: unexpected type"); } - // MOZ_CRASH("Compiler bug: unexpected type"); } void pushJoinRegUnlessVoid(const Maybe& r) { @@ -6597,22 +6571,23 @@ class BaseCompiler final : public BaseCompilerInterface { const StackHeight stackHeight; // The value to pop to along the taken edge, // unless !hasPop() const bool invertBranch; // If true, invert the sense of the branch - const ResultType resultType; // The result propagated along the edges + const ExprType + resultType; // The result propagated along the edges, or Void explicit BranchState(Label* label) : label(label), stackHeight(StackHeight::Invalid()), invertBranch(false), - resultType(ResultType::Empty()) {} + resultType(ExprType::Void) {} BranchState(Label* label, bool invertBranch) : label(label), stackHeight(StackHeight::Invalid()), invertBranch(invertBranch), - resultType(ResultType::Empty()) {} + resultType(ExprType::Void) {} BranchState(Label* label, StackHeight stackHeight, bool invertBranch, - ResultType resultType) + ExprType resultType) : label(label), stackHeight(stackHeight), invertBranch(invertBranch), @@ -6744,10 +6719,9 @@ class BaseCompiler final : public BaseCompilerInterface { template MOZ_MUST_USE bool emitSetOrTeeLocal(uint32_t slot); - void endBlock(ResultType type); - void endLoop(ResultType type); + void endBlock(ExprType type); void endIfThen(); - void endIfThenElse(ResultType type); + void endIfThenElse(ExprType type); void doReturn(bool popStack); void pushReturnValueOfCall(const FunctionCall& call, ValType type); @@ -8160,7 +8134,7 @@ bool BaseCompiler::emitBlock() { return true; } -void BaseCompiler::endBlock(ResultType type) { +void BaseCompiler::endBlock(ExprType type) { Control& block = controlItem(); // Save the value. @@ -8277,10 +8251,10 @@ void BaseCompiler::endIfThen() { } bool BaseCompiler::emitElse() { - ResultType thenType; - NothingVector unused_thenValues; + ExprType thenType; + Nothing unused_thenValue; - if (!iter_.readElse(&thenType, &unused_thenValues)) { + if (!iter_.readElse(&thenType, &unused_thenValue)) { return false; } @@ -8321,7 +8295,7 @@ bool BaseCompiler::emitElse() { return true; } -void BaseCompiler::endIfThenElse(ResultType type) { +void BaseCompiler::endIfThenElse(ExprType type) { Control& ifThenElse = controlItem(); // The expression type is not a reliable guide to what we'll find @@ -8365,9 +8339,9 @@ void BaseCompiler::endIfThenElse(ResultType type) { bool BaseCompiler::emitEnd() { LabelKind kind; - ResultType type; - NothingVector unused_values; - if (!iter_.readEnd(&kind, &type, &unused_values)) { + ExprType type; + Nothing unused_value; + if (!iter_.readEnd(&kind, &type, &unused_value)) { return false; } @@ -8400,9 +8374,9 @@ bool BaseCompiler::emitEnd() { bool BaseCompiler::emitBr() { uint32_t relativeDepth; - ResultType type; - NothingVector unused_values; - if (!iter_.readBr(&relativeDepth, &type, &unused_values)) { + ExprType type; + Nothing unused_value; + if (!iter_.readBr(&relativeDepth, &type, &unused_value)) { return false; } @@ -8433,10 +8407,9 @@ bool BaseCompiler::emitBr() { bool BaseCompiler::emitBrIf() { uint32_t relativeDepth; - ResultType type; - NothingVector unused_values; - Nothing unused_condition; - if (!iter_.readBrIf(&relativeDepth, &type, &unused_values, + ExprType type; + Nothing unused_value, unused_condition; + if (!iter_.readBrIf(&relativeDepth, &type, &unused_value, &unused_condition)) { return false; } @@ -8459,17 +8432,10 @@ bool BaseCompiler::emitBrIf() { bool BaseCompiler::emitBrTable() { Uint32Vector depths; uint32_t defaultDepth; - ResultType type; - NothingVector unused_values; - Nothing unused_index; - // N.B., `type' gets set to the type of the default branch target. In the - // presence of subtyping, it could be that the different branch targets have - // different types. Here we rely on the assumption that the value - // representations (e.g. Stk value types) of all branch target types are the - // same, in the baseline compiler. Notably, this means that all Ref types - // should be represented the same. - if (!iter_.readBrTable(&depths, &defaultDepth, &type, &unused_values, - &unused_index)) { + ExprType branchValueType; + Nothing unused_value, unused_index; + if (!iter_.readBrTable(&depths, &defaultDepth, &branchValueType, + &unused_value, &unused_index)) { return false; } @@ -8478,14 +8444,14 @@ bool BaseCompiler::emitBrTable() { } // Don't use joinReg for rc - maybeReserveJoinRegI(type); + maybeReserveJoinRegI(branchValueType); // Table switch value always on top. RegI32 rc = popI32(); - maybeUnreserveJoinRegI(type); + maybeUnreserveJoinRegI(branchValueType); - Maybe r = popJoinRegUnlessVoid(type); + Maybe r = popJoinRegUnlessVoid(branchValueType); Label dispatchCode; masm.branch32(Assembler::Below, rc, Imm32(depths.length()), &dispatchCode); @@ -8599,8 +8565,8 @@ void BaseCompiler::doReturn(bool popStack) { } bool BaseCompiler::emitReturn() { - NothingVector unused_values; - if (!iter_.readReturn(&unused_values)) { + Nothing unused_value; + if (!iter_.readReturn(&unused_value)) { return false; } @@ -8687,7 +8653,7 @@ bool BaseCompiler::emitCall() { uint32_t lineOrBytecode = readCallSiteLineOrBytecode(); uint32_t funcIndex; - NothingVector args_; + BaseOpIter::ValueVector args_; if (!iter_.readCall(&funcIndex, &args_)) { return false; } @@ -8741,7 +8707,7 @@ bool BaseCompiler::emitCallIndirect() { uint32_t funcTypeIndex; uint32_t tableIndex; Nothing callee_; - NothingVector args_; + BaseOpIter::ValueVector args_; if (!iter_.readCallIndirect(&funcTypeIndex, &tableIndex, &callee_, &args_)) { return false; } @@ -8846,6 +8812,7 @@ bool BaseCompiler::emitUnaryMathBuiltinCall(SymbolicAddress callee, popValueStackBy(numArgs); + // We know retType isn't ExprType::Void here, so there's no need to check it. pushReturnValueOfCall(baselineCall, retType); return true; @@ -10474,7 +10441,7 @@ bool BaseCompiler::emitStructNew() { uint32_t lineOrBytecode = readCallSiteLineOrBytecode(); uint32_t typeIndex; - NothingVector args; + BaseOpIter::ValueVector args; if (!iter_.readStructNew(&typeIndex, &args)) { return false; } @@ -10495,6 +10462,10 @@ bool BaseCompiler::emitStructNew() { return false; } + // As many arguments as there are fields. + + MOZ_ASSERT(args.length() == structType.fields_.length()); + // Optimization opportunity: Iterate backward to pop arguments off the // stack. This will generate more instructions than we want, since we // really only need to pop the stack once at the end, not for every element, diff --git a/js/src/wasm/WasmConstants.h b/js/src/wasm/WasmConstants.h index fc4f133ec169..f45b04f7cf57 100644 --- a/js/src/wasm/WasmConstants.h +++ b/js/src/wasm/WasmConstants.h @@ -42,13 +42,7 @@ enum class SectionId { GcFeatureOptIn = 42 // Arbitrary, but fits in 7 bits }; -// WebAssembly type encodings are all single-byte negative SLEB128s, hence: -// forall tc:TypeCode. ((tc & SLEB128SignMask) == SLEB128SignBit -static const uint8_t SLEB128SignMask = 0xc0; -static const uint8_t SLEB128SignBit = 0x40; - enum class TypeCode { - I32 = 0x7f, // SLEB128(-0x01) I64 = 0x7e, // SLEB128(-0x02) F32 = 0x7d, // SLEB128(-0x03) @@ -69,7 +63,7 @@ enum class TypeCode { // Type constructor for structure types - unofficial Struct = 0x50, // SLEB128(-0x30) - // The 'empty' case of blocktype. + // Special code representing the block signature ()->() BlockVoid = 0x40, // SLEB128(-0x40) // Type designator for null - unofficial, will not appear in the binary format diff --git a/js/src/wasm/WasmIonCompile.cpp b/js/src/wasm/WasmIonCompile.cpp index d24b449b194d..c0cc36e42f52 100644 --- a/js/src/wasm/WasmIonCompile.cpp +++ b/js/src/wasm/WasmIonCompile.cpp @@ -43,12 +43,10 @@ using mozilla::Some; namespace { typedef Vector BlockVector; -typedef Vector DefVector; struct IonCompilePolicy { // We store SSA definitions in the value stack. typedef MDefinition* Value; - typedef DefVector ValueVector; // We store loop headers and then/else blocks in the control flow stack. typedef MBasicBlock* ControlItem; @@ -1153,18 +1151,23 @@ class FunctionCompiler { inline bool inDeadCode() const { return curBlock_ == nullptr; } - void returnValues(const DefVector& values) { + void returnExpr(MDefinition* operand) { if (inDeadCode()) { return; } - MOZ_ASSERT(values.length() <= 1, "until multi-return"); + MWasmReturn* ins = MWasmReturn::New(alloc(), operand); + curBlock_->end(ins); + curBlock_ = nullptr; + } - if (values.empty()) { - curBlock_->end(MWasmReturnVoid::New(alloc())); - } else { - curBlock_->end(MWasmReturn::New(alloc(), values[0])); + void returnVoid() { + if (inDeadCode()) { + return; } + + MWasmReturnVoid* ins = MWasmReturnVoid::New(alloc()); + curBlock_->end(ins); curBlock_ = nullptr; } @@ -1180,42 +1183,39 @@ class FunctionCompiler { } private: - static uint32_t numPushed(MBasicBlock* block) { - return block->stackDepth() - block->info().firstStackSlot(); + static bool hasPushed(MBasicBlock* block) { + uint32_t numPushed = block->stackDepth() - block->info().firstStackSlot(); + MOZ_ASSERT(numPushed == 0 || numPushed == 1); + return numPushed; } public: - void pushDefs(const DefVector& defs) { + void pushDef(MDefinition* def) { if (inDeadCode()) { return; } - MOZ_ASSERT(numPushed(curBlock_) == 0); - for (MDefinition* def : defs) { - MOZ_ASSERT(def->type() != MIRType::None); + MOZ_ASSERT(!hasPushed(curBlock_)); + if (def && def->type() != MIRType::None) { curBlock_->push(def); } } - bool popPushedDefs(DefVector* defs) { - size_t n = numPushed(curBlock_); - if (!defs->resizeUninitialized(n)) { - return false; + MDefinition* popDefIfPushed() { + if (!hasPushed(curBlock_)) { + return nullptr; } - for (; n > 0; n--) { - MDefinition* def = curBlock_->pop(); - MOZ_ASSERT(def->type() != MIRType::Value); - (*defs)[n - 1] = def; - } - return true; + MDefinition* def = curBlock_->pop(); + MOZ_ASSERT(def->type() != MIRType::Value); + return def; } private: - void addJoinPredecessor(const DefVector& defs, MBasicBlock** joinPred) { + void addJoinPredecessor(MDefinition* def, MBasicBlock** joinPred) { *joinPred = curBlock_; if (inDeadCode()) { return; } - pushDefs(defs); + pushDef(def); } public: @@ -1241,15 +1241,15 @@ class FunctionCompiler { } bool switchToElse(MBasicBlock* elseBlock, MBasicBlock** thenJoinPred) { - DefVector values; - if (!finishBlock(&values)) { + MDefinition* ifDef; + if (!finishBlock(&ifDef)) { return false; } if (!elseBlock) { *thenJoinPred = nullptr; } else { - addJoinPredecessor(values, thenJoinPred); + addJoinPredecessor(ifDef, thenJoinPred); curBlock_ = elseBlock; mirGraph().moveBlockToEnd(curBlock_); @@ -1258,44 +1258,47 @@ class FunctionCompiler { return startBlock(); } - bool joinIfElse(MBasicBlock* thenJoinPred, DefVector* defs) { - DefVector values; - if (!finishBlock(&values)) { + bool joinIfElse(MBasicBlock* thenJoinPred, MDefinition** def) { + MDefinition* elseDef; + if (!finishBlock(&elseDef)) { return false; } if (!thenJoinPred && inDeadCode()) { - return true; - } + *def = nullptr; + } else { + MBasicBlock* elseJoinPred; + addJoinPredecessor(elseDef, &elseJoinPred); - MBasicBlock* elseJoinPred; - addJoinPredecessor(values, &elseJoinPred); + mozilla::Array blocks; + size_t numJoinPreds = 0; + if (thenJoinPred) { + blocks[numJoinPreds++] = thenJoinPred; + } + if (elseJoinPred) { + blocks[numJoinPreds++] = elseJoinPred; + } - mozilla::Array blocks; - size_t numJoinPreds = 0; - if (thenJoinPred) { - blocks[numJoinPreds++] = thenJoinPred; - } - if (elseJoinPred) { - blocks[numJoinPreds++] = elseJoinPred; - } + if (numJoinPreds == 0) { + *def = nullptr; + return true; + } - if (numJoinPreds == 0) { - return true; - } - - MBasicBlock* join; - if (!goToNewBlock(blocks[0], &join)) { - return false; - } - for (size_t i = 1; i < numJoinPreds; ++i) { - if (!goToExistingBlock(blocks[i], join)) { + MBasicBlock* join; + if (!goToNewBlock(blocks[0], &join)) { return false; } + for (size_t i = 1; i < numJoinPreds; ++i) { + if (!goToExistingBlock(blocks[i], join)) { + return false; + } + } + + curBlock_ = join; + *def = popDefIfPushed(); } - curBlock_ = join; - return popPushedDefs(defs); + return true; } bool startBlock() { @@ -1305,10 +1308,10 @@ class FunctionCompiler { return true; } - bool finishBlock(DefVector* defs) { + bool finishBlock(MDefinition** def) { MOZ_ASSERT(blockDepth_); uint32_t topLabel = --blockDepth_; - return bindBranches(topLabel, defs); + return bindBranches(topLabel, def); } bool startLoop(MBasicBlock** loopHeader) { @@ -1398,7 +1401,7 @@ class FunctionCompiler { } public: - bool closeLoop(MBasicBlock* loopHeader, DefVector* loopResults) { + bool closeLoop(MBasicBlock* loopHeader, MDefinition** loopResult) { MOZ_ASSERT(blockDepth_ >= 1); MOZ_ASSERT(loopDepth_); @@ -1410,6 +1413,7 @@ class FunctionCompiler { blockPatches_[headerLabel].empty()); blockDepth_--; loopDepth_--; + *loopResult = nullptr; return true; } @@ -1424,7 +1428,7 @@ class FunctionCompiler { // branches as forward jumps to a single backward jump. This is // unfortunate but the optimizer is able to fold these into single jumps // to backedges. - DefVector _; + MDefinition* _; if (!bindBranches(headerLabel, &_)) { return false; } @@ -1433,7 +1437,7 @@ class FunctionCompiler { if (curBlock_) { // We're on the loop backedge block, created by bindBranches. - for (size_t i = 0, n = numPushed(curBlock_); i != n; i++) { + if (hasPushed(curBlock_)) { curBlock_->pop(); } @@ -1458,7 +1462,8 @@ class FunctionCompiler { } blockDepth_ -= 1; - return inDeadCode() || popPushedDefs(loopResults); + *loopResult = inDeadCode() ? nullptr : popDefIfPushed(); + return true; } bool addControlFlowPatch(MControlInstruction* ins, uint32_t relative, @@ -1474,7 +1479,7 @@ class FunctionCompiler { return blockPatches_[absolute].append(ControlFlowPatch(ins, index)); } - bool br(uint32_t relativeDepth, const DefVector& values) { + bool br(uint32_t relativeDepth, MDefinition* maybeValue) { if (inDeadCode()) { return true; } @@ -1484,14 +1489,14 @@ class FunctionCompiler { return false; } - pushDefs(values); + pushDef(maybeValue); curBlock_->end(jump); curBlock_ = nullptr; return true; } - bool brIf(uint32_t relativeDepth, const DefVector& values, + bool brIf(uint32_t relativeDepth, MDefinition* maybeValue, MDefinition* condition) { if (inDeadCode()) { return true; @@ -1507,7 +1512,7 @@ class FunctionCompiler { return false; } - pushDefs(values); + pushDef(maybeValue); curBlock_->end(test); curBlock_ = joinBlock; @@ -1515,7 +1520,7 @@ class FunctionCompiler { } bool brTable(MDefinition* operand, uint32_t defaultDepth, - const Uint32Vector& depths, const DefVector& values) { + const Uint32Vector& depths, MDefinition* maybeValue) { if (inDeadCode()) { return true; } @@ -1568,7 +1573,7 @@ class FunctionCompiler { } } - pushDefs(values); + pushDef(maybeValue); curBlock_->end(table); curBlock_ = nullptr; @@ -1616,9 +1621,10 @@ class FunctionCompiler { return next->addPredecessor(alloc(), prev); } - bool bindBranches(uint32_t absolute, DefVector* defs) { + bool bindBranches(uint32_t absolute, MDefinition** def) { if (absolute >= blockPatches_.length() || blockPatches_[absolute].empty()) { - return inDeadCode() || popPushedDefs(defs); + *def = inDeadCode() ? nullptr : popDefIfPushed(); + return true; } ControlFlowPatchVector& patches = blockPatches_[absolute]; @@ -1658,9 +1664,7 @@ class FunctionCompiler { curBlock_ = join; - if (!popPushedDefs(defs)) { - return false; - } + *def = popDefIfPushed(); patches.clear(); return true; @@ -1785,13 +1789,15 @@ static bool EmitIf(FunctionCompiler& f) { } static bool EmitElse(FunctionCompiler& f) { - ResultType thenType; - DefVector thenValues; - if (!f.iter().readElse(&thenType, &thenValues)) { + ExprType thenType; + MDefinition* thenValue; + if (!f.iter().readElse(&thenType, &thenValue)) { return false; } - f.pushDefs(thenValues); + if (!IsVoid(thenType)) { + f.pushDef(thenValue); + } if (!f.switchToElse(f.iter().controlItem(), &f.iter().controlItem())) { return false; @@ -1802,33 +1808,40 @@ static bool EmitElse(FunctionCompiler& f) { static bool EmitEnd(FunctionCompiler& f) { LabelKind kind; - ResultType type; - DefVector preJoinDefs; - if (!f.iter().readEnd(&kind, &type, &preJoinDefs)) { + ExprType type; + MDefinition* value; + if (!f.iter().readEnd(&kind, &type, &value)) { return false; } MBasicBlock* block = f.iter().controlItem(); + f.iter().popEnd(); - f.pushDefs(preJoinDefs); + if (!IsVoid(type)) { + f.pushDef(value); + } - DefVector postJoinDefs; + MDefinition* def = nullptr; switch (kind) { case LabelKind::Body: MOZ_ASSERT(f.iter().controlStackEmpty()); - if (!f.finishBlock(&postJoinDefs)) { + if (!f.finishBlock(&def)) { return false; } - f.returnValues(postJoinDefs); + if (f.inDeadCode() || IsVoid(type)) { + f.returnVoid(); + } else { + f.returnExpr(def); + } return f.iter().readFunctionEnd(f.iter().end()); case LabelKind::Block: - if (!f.finishBlock(&postJoinDefs)) { + if (!f.finishBlock(&def)) { return false; } break; case LabelKind::Loop: - if (!f.closeLoop(block, &postJoinDefs)) { + if (!f.closeLoop(block, &def)) { return false; } break; @@ -1839,54 +1852,76 @@ static bool EmitEnd(FunctionCompiler& f) { return false; } - if (!f.joinIfElse(block, &postJoinDefs)) { + if (!f.joinIfElse(block, &def)) { return false; } break; case LabelKind::Else: - if (!f.joinIfElse(block, &postJoinDefs)) { + if (!f.joinIfElse(block, &def)) { return false; } break; } - MOZ_ASSERT_IF(!f.inDeadCode(), postJoinDefs.length() == type.length()); - f.iter().setResults(postJoinDefs.length(), postJoinDefs); + if (!IsVoid(type)) { + MOZ_ASSERT_IF(!f.inDeadCode(), def); + f.iter().setResult(def); + } return true; } static bool EmitBr(FunctionCompiler& f) { uint32_t relativeDepth; - ResultType type; - DefVector values; - if (!f.iter().readBr(&relativeDepth, &type, &values)) { + ExprType type; + MDefinition* value; + if (!f.iter().readBr(&relativeDepth, &type, &value)) { return false; } - return f.br(relativeDepth, values); + if (IsVoid(type)) { + if (!f.br(relativeDepth, nullptr)) { + return false; + } + } else { + if (!f.br(relativeDepth, value)) { + return false; + } + } + + return true; } static bool EmitBrIf(FunctionCompiler& f) { uint32_t relativeDepth; - ResultType type; - DefVector values; + ExprType type; + MDefinition* value; MDefinition* condition; - if (!f.iter().readBrIf(&relativeDepth, &type, &values, &condition)) { + if (!f.iter().readBrIf(&relativeDepth, &type, &value, &condition)) { return false; } - return f.brIf(relativeDepth, values, condition); + if (IsVoid(type)) { + if (!f.brIf(relativeDepth, nullptr, condition)) { + return false; + } + } else { + if (!f.brIf(relativeDepth, value, condition)) { + return false; + } + } + + return true; } static bool EmitBrTable(FunctionCompiler& f) { Uint32Vector depths; uint32_t defaultDepth; - ResultType branchValueType; - DefVector branchValues; + ExprType branchValueType; + MDefinition* branchValue; MDefinition* index; if (!f.iter().readBrTable(&depths, &defaultDepth, &branchValueType, - &branchValues, &index)) { + &branchValue, &index)) { return false; } @@ -1902,19 +1937,24 @@ static bool EmitBrTable(FunctionCompiler& f) { } if (allSameDepth) { - return f.br(defaultDepth, branchValues); + return f.br(defaultDepth, branchValue); } - return f.brTable(index, defaultDepth, depths, branchValues); + return f.brTable(index, defaultDepth, depths, branchValue); } static bool EmitReturn(FunctionCompiler& f) { - DefVector values; - if (!f.iter().readReturn(&values)) { + MDefinition* value; + if (!f.iter().readReturn(&value)) { return false; } - f.returnValues(values); + if (f.funcType().results().length() == 0) { + f.returnVoid(); + return true; + } + + f.returnExpr(value); return true; } @@ -1927,6 +1967,8 @@ static bool EmitUnreachable(FunctionCompiler& f) { return true; } +typedef IonOpIter::ValueVector DefVector; + static bool EmitCallArgs(FunctionCompiler& f, const FuncType& funcType, const DefVector& args, CallCompileState* call) { for (size_t i = 0, n = funcType.args().length(); i < n; ++i) { diff --git a/js/src/wasm/WasmOpIter.h b/js/src/wasm/WasmOpIter.h index 3682abe4cbd4..aea4cd97f175 100644 --- a/js/src/wasm/WasmOpIter.h +++ b/js/src/wasm/WasmOpIter.h @@ -29,316 +29,6 @@ namespace js { namespace wasm { -template -class TaggedValue { - public: - enum Kind { - ImmediateKind1 = 0, - ImmediateKind2 = 1, - PointerKind1 = 2, - PointerKind2 = 3 - }; - - private: - uintptr_t bits_; - - static constexpr uintptr_t PayloadShift = 2; - static constexpr uintptr_t KindMask = 0x3; - static constexpr uintptr_t PointerKindBit = 0x2; - - constexpr static bool IsPointerKind(Kind kind) { - return uintptr_t(kind) & PointerKindBit; - } - constexpr static bool IsImmediateKind(Kind kind) { - return !IsPointerKind(kind); - } - - static_assert(IsImmediateKind(ImmediateKind1), "immediate kind 1"); - static_assert(IsImmediateKind(ImmediateKind2), "immediate kind 2"); - static_assert(IsPointerKind(PointerKind1), "pointer kind 1"); - static_assert(IsPointerKind(PointerKind2), "pointer kind 2"); - - static uintptr_t PackImmediate(Kind kind, uint32_t imm) { - MOZ_ASSERT(IsImmediateKind(kind)); - MOZ_ASSERT((uintptr_t(kind) & KindMask) == kind); - MOZ_ASSERT((imm & (uint32_t(KindMask) << (32 - PayloadShift))) == 0); - return uintptr_t(kind) | (uintptr_t(imm) << PayloadShift); - } - - static uintptr_t PackPointer(Kind kind, PointerType* ptr) { - uintptr_t ptrBits = reinterpret_cast(ptr); - MOZ_ASSERT(IsPointerKind(kind)); - MOZ_ASSERT((uintptr_t(kind) & KindMask) == kind); - MOZ_ASSERT((ptrBits & KindMask) == 0); - return uintptr_t(kind) | ptrBits; - } - - public: - TaggedValue(Kind kind, uint32_t imm) : bits_(PackImmediate(kind, imm)) {} - TaggedValue(Kind kind, PointerType* ptr) : bits_(PackPointer(kind, ptr)) {} - - uintptr_t bits() const { return bits_; } - Kind kind() const { return Kind(bits() & KindMask); } - uint32_t immediate() const { - MOZ_ASSERT(IsImmediateKind(kind())); - return mozilla::AssertedCast(bits() >> PayloadShift); - } - PointerType* pointer() const { - MOZ_ASSERT(IsPointerKind(kind())); - return reinterpret_cast(bits() & ~KindMask); - } -}; - -// ResultType represents the WebAssembly spec's `resulttype`. Semantically, a -// result type is just a vec(valtype). For effiency, though, the ResultType -// value is packed into a word, with separate encodings for these 3 cases: -// [] -// [valtype] -// pointer to ValTypeVector -// -// Additionally there is an encoding indicating uninitialized ResultType -// values. -// -// Generally in the latter case the ValTypeVector is the args() or results() of -// a FuncType in the compilation unit, so as long as the lifetime of the -// ResultType value is less than the OpIter, we can just borrow the pointer -// without ownership or copying. -class ResultType { - typedef TaggedValue TaggedValue; - TaggedValue tagged_; - - enum Kind { - EmptyKind = TaggedValue::ImmediateKind1, - SingleKind = TaggedValue::ImmediateKind2, -#ifdef ENABLE_WASM_MULTI_VALUE - VectorKind = TaggedValue::PointerKind1, -#endif - InvalidKind = TaggedValue::PointerKind2, - }; - - ResultType(Kind kind, uint32_t imm) : tagged_(TaggedValue::Kind(kind), imm) {} -#ifdef ENABLE_WASM_MULTI_VALUE - explicit ResultType(const ValTypeVector* ptr) - : tagged_(TaggedValue::Kind(VectorKind), ptr) {} -#endif - - Kind kind() const { return Kind(tagged_.kind()); } - - ValType singleValType() const { - MOZ_ASSERT(kind() == SingleKind); - return ValType(PackedTypeCodeFromBits(tagged_.immediate())); - } - -#ifdef ENABLE_WASM_MULTI_VALUE - const ValTypeVector& values() const { - MOZ_ASSERT(kind() == VectorKind); - return *tagged_.pointer(); - } -#endif - - public: - ResultType() : tagged_(TaggedValue::Kind(InvalidKind), nullptr) {} - - static ResultType Empty() { return ResultType(EmptyKind, uint32_t(0)); } - static ResultType Single(ValType vt) { - return ResultType(SingleKind, vt.bitsUnsafe()); - } - static ResultType Vector(const ValTypeVector& vals) { - switch (vals.length()) { - case 0: - return Empty(); - case 1: - return Single(vals[0]); - default: -#ifdef ENABLE_WASM_MULTI_VALUE - return ResultType(&vals); -#else - MOZ_CRASH("multi-value returns not supported"); -#endif - } - } - - bool empty() const { return kind() == EmptyKind; } - - size_t length() const { - switch (kind()) { - case EmptyKind: - return 0; - case SingleKind: - return 1; -#ifdef ENABLE_WASM_MULTI_VALUE - case VectorKind: - return values().length(); -#endif - default: - MOZ_CRASH("bad resulttype"); - } - } - - ValType operator[](size_t i) const { - switch (kind()) { - case SingleKind: - MOZ_ASSERT(i == 0); - return singleValType(); -#ifdef ENABLE_WASM_MULTI_VALUE - case VectorKind: - return values()[i]; -#endif - default: - MOZ_CRASH("bad resulttype"); - } - } - - bool operator==(ResultType rhs) const { - switch (kind()) { - case EmptyKind: - case SingleKind: - case InvalidKind: - return tagged_.bits() == rhs.tagged_.bits(); -#ifdef ENABLE_WASM_MULTI_VALUE - case VectorKind: { - if (rhs.kind() == EmptyKind || rhs.kind() == SingleKind) { - return false; - } - return EqualContainers(values(), rhs.values()); - } -#endif - default: - MOZ_CRASH("bad resulttype"); - } - } - bool operator!=(ResultType rhs) const { return !(*this == rhs); } -}; - -// BlockType represents the WebAssembly spec's `blocktype`. Semantically, a -// block type is just a (vec(valtype) -> vec(valtype)) with four special -// encodings which are represented explicitly in BlockType: -// [] -> [] -// [] -> [valtype] -// [params] -> [results] via pointer to FuncType -// [] -> [results] via pointer to FuncType (ignoring [params]) - -class BlockType { - typedef TaggedValue TaggedValue; - TaggedValue tagged_; - - enum Kind { - VoidToVoidKind = TaggedValue::ImmediateKind1, - VoidToSingleKind = TaggedValue::ImmediateKind2, -#ifdef ENABLE_WASM_MULTI_VALUE - FuncKind = TaggedValue::PointerKind1, - FuncResultsKind = TaggedValue::PointerKind2 -#endif - }; - - BlockType(Kind kind, uint32_t imm) : tagged_(TaggedValue::Kind(kind), imm) {} -#ifdef ENABLE_WASM_MULTI_VALUE - BlockType(Kind kind, const FuncType& type) - : tagged_(TaggedValue::Kind(kind), &type) {} -#endif - - Kind kind() const { return Kind(tagged_.kind()); } - ValType singleValType() const { - MOZ_ASSERT(kind() == VoidToSingleKind); - return ValType(PackedTypeCodeFromBits(tagged_.immediate())); - } - -#ifdef ENABLE_WASM_MULTI_VALUE - const FuncType& funcType() const { return *tagged_.pointer(); } -#endif - - public: - BlockType() - : tagged_(TaggedValue::Kind(VoidToVoidKind), - uint32_t(InvalidPackedTypeCode())) {} - - static BlockType VoidToVoid() { - return BlockType(VoidToVoidKind, uint32_t(0)); - } - static BlockType VoidToSingle(ValType vt) { - return BlockType(VoidToSingleKind, vt.bitsUnsafe()); - } - static BlockType Func(const FuncType& type) { -#ifdef ENABLE_WASM_MULTI_VALUE - if (type.args().length() == 0) { - return FuncResults(type); - } - return BlockType(FuncKind, type); -#else - MOZ_ASSERT(type.args().length() == 0); - return FuncResults(type); -#endif - } - static BlockType FuncResults(const FuncType& type) { - switch (type.results().length()) { - case 0: - return VoidToVoid(); - case 1: - return VoidToSingle(type.results()[0]); - default: -#ifdef ENABLE_WASM_MULTI_VALUE - return BlockType(FuncResultsKind, type); -#else - MOZ_CRASH("multi-value returns not supported"); -#endif - } - } - - ResultType params() const { - switch (kind()) { - case VoidToVoidKind: - case VoidToSingleKind: -#ifdef ENABLE_WASM_MULTI_VALUE - case FuncResultsKind: -#endif - return ResultType::Empty(); -#ifdef ENABLE_WASM_MULTI_VALUE - case FuncKind: - return ResultType::Vector(funcType().args()); -#endif - default: - MOZ_CRASH("unexpected kind"); - } - } - - ResultType results() const { - switch (kind()) { - case VoidToVoidKind: - return ResultType::Empty(); - case VoidToSingleKind: - return ResultType::Single(singleValType()); -#ifdef ENABLE_WASM_MULTI_VALUE - case FuncKind: - case FuncResultsKind: - return ResultType::Vector(funcType().results()); -#endif - default: - MOZ_CRASH("unexpected kind"); - } - } - - bool operator==(BlockType rhs) const { - if (kind() != rhs.kind()) { - return false; - } - switch (kind()) { - case VoidToVoidKind: - case VoidToSingleKind: - return tagged_.bits() == rhs.tagged_.bits(); -#ifdef ENABLE_WASM_MULTI_VALUE - case FuncKind: - return funcType() == rhs.funcType(); - case FuncResultsKind: - return EqualContainers(funcType().results(), rhs.funcType().results()); -#endif - default: - MOZ_CRASH("unexpected kind"); - } - } - - bool operator!=(BlockType rhs) const { return !(*this == rhs); } -}; - // The kind of a control-flow stack item. enum class LabelKind : uint8_t { Body, Block, Loop, Then, Else }; @@ -519,54 +209,48 @@ struct LinearMemoryAddress { template class ControlStackEntry { // Use a Pair to optimize away empty ControlItem. - mozilla::Pair 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_; + mozilla::Pair kindAndItem_; bool polymorphicBase_; - - LabelKind kind_; + ExprType type_; + size_t valueStackStart_; public: - ControlStackEntry(LabelKind kind, BlockType type, uint32_t valueStackBase) - : typeAndItem_(type, ControlItem()), - valueStackBase_(valueStackBase), + ControlStackEntry(LabelKind kind, ExprType type, size_t valueStackStart) + : kindAndItem_(kind, ControlItem()), polymorphicBase_(false), - kind_(kind) { - MOZ_ASSERT(type != BlockType()); + type_(type), + valueStackStart_(valueStackStart) { + MOZ_ASSERT(type != ExprType::Limit); } - LabelKind kind() const { return kind_; } - BlockType type() const { return typeAndItem_.first(); } - ResultType resultType() const { return type().results(); } - ResultType branchTargetType() const { - return kind_ == LabelKind::Loop ? type().params() : type().results(); + LabelKind kind() const { return kindAndItem_.first(); } + ExprType resultType() const { return type_; } + ExprType branchTargetType() const { + return kind() == LabelKind::Loop ? ExprType::Void : type_; } - uint32_t valueStackBase() const { return valueStackBase_; } - ControlItem& controlItem() { return typeAndItem_.second(); } + size_t valueStackStart() const { return valueStackStart_; } + ControlItem& controlItem() { return kindAndItem_.second(); } void setPolymorphicBase() { polymorphicBase_ = true; } bool polymorphicBase() const { return polymorphicBase_; } void switchToElse() { MOZ_ASSERT(kind() == LabelKind::Then); - kind_ = LabelKind::Else; + kindAndItem_.first() = LabelKind::Else; polymorphicBase_ = false; } }; template -class TypeAndValueT { +class TypeAndValue { // Use a Pair to optimize away empty Value. mozilla::Pair tv_; public: - TypeAndValueT() : tv_(StackType::Bottom, Value()) {} - explicit TypeAndValueT(StackType type) : tv_(type, Value()) {} - explicit TypeAndValueT(ValType type) : tv_(StackType(type), Value()) {} - TypeAndValueT(StackType type, Value value) : tv_(type, value) {} - TypeAndValueT(ValType type, Value value) : tv_(StackType(type), value) {} + TypeAndValue() : tv_(StackType::Bottom, Value()) {} + explicit TypeAndValue(StackType type) : tv_(type, Value()) {} + explicit TypeAndValue(ValType type) : tv_(StackType(type), Value()) {} + TypeAndValue(StackType type, Value value) : tv_(type, value) {} + TypeAndValue(ValType type, Value value) : tv_(StackType(type), value) {} StackType type() const { return tv_.first(); } StackType& typeRef() { return tv_.first(); } Value value() const { return tv_.second(); } @@ -581,22 +265,14 @@ class TypeAndValueT { // it to be used on the stack. template class MOZ_STACK_CLASS OpIter : private Policy { - public: typedef typename Policy::Value Value; - typedef typename Policy::ValueVector ValueVector; - typedef TypeAndValueT TypeAndValue; - typedef Vector TypeAndValueStack; typedef typename Policy::ControlItem ControlItem; - typedef ControlStackEntry Control; - typedef Vector ControlStack; - private: Decoder& d_; const ModuleEnvironment& env_; - TypeAndValueStack valueStack_; - TypeAndValueStack thenParamStack_; - ControlStack controlStack_; + Vector, 8, SystemAllocPolicy> valueStack_; + Vector, 8, SystemAllocPolicy> controlStack_; #ifdef DEBUG OpBytes op_; @@ -617,56 +293,68 @@ class MOZ_STACK_CLASS OpIter : private Policy { LinearMemoryAddress* addr); MOZ_MUST_USE bool readLinearMemoryAddressAligned( uint32_t byteSize, LinearMemoryAddress* addr); - MOZ_MUST_USE bool readBlockType(BlockType* type); + MOZ_MUST_USE bool readBlockType(ExprType* expr); MOZ_MUST_USE bool readStructTypeIndex(uint32_t* typeIndex); MOZ_MUST_USE bool readFieldIndex(uint32_t* fieldIndex, const StructType& structType); MOZ_MUST_USE bool popCallArgs(const ValTypeVector& expectedTypes, - ValueVector* values); + Vector* values); MOZ_MUST_USE bool failEmptyStack(); MOZ_MUST_USE bool popStackType(StackType* type, Value* value); - MOZ_MUST_USE bool popWithType(ValType expected, Value* value); - MOZ_MUST_USE bool popWithType(ResultType expected, ValueVector* values); - MOZ_MUST_USE bool popThenPushType(ResultType expected, ValueVector* values); - MOZ_MUST_USE bool ensureTopHasType(ResultType expected, ValueVector* values); + MOZ_MUST_USE bool popWithType(ValType valType, Value* value); + MOZ_MUST_USE bool popWithType(ExprType expectedType, Value* value); + MOZ_MUST_USE bool topWithType(ExprType expectedType, Value* value); + MOZ_MUST_USE bool topWithType(ValType valType, Value* value); + MOZ_MUST_USE bool topIsType(ValType expectedType, StackType* actualType, + Value* value); - MOZ_MUST_USE bool pushControl(LabelKind kind, BlockType type); - MOZ_MUST_USE bool checkStackAtEndOfBlock(ResultType* type, - ValueVector* values); - MOZ_MUST_USE bool getControl(uint32_t relativeDepth, Control** controlEntry); - MOZ_MUST_USE bool checkBranchValue(uint32_t relativeDepth, ResultType* type, - ValueVector* values); + MOZ_MUST_USE bool pushControl(LabelKind kind, ExprType type); + MOZ_MUST_USE bool checkStackAtEndOfBlock(ExprType* type, Value* value); + MOZ_MUST_USE bool getControl(uint32_t relativeDepth, + ControlStackEntry** controlEntry); + MOZ_MUST_USE bool checkBranchValue(uint32_t relativeDepth, ExprType* type, + Value* value); MOZ_MUST_USE bool checkBrTableEntry(uint32_t* relativeDepth, - ResultType prevBranchType, - ResultType* branchType, - ValueVector* branchValues); + uint32_t* branchValueArity, + ExprType* branchValueType, + Value* branchValue); + MOZ_MUST_USE bool push(StackType t) { return valueStack_.emplaceBack(t); } MOZ_MUST_USE bool push(ValType t) { return valueStack_.emplaceBack(t); } - MOZ_MUST_USE bool push(TypeAndValue tv) { return valueStack_.append(tv); } - MOZ_MUST_USE bool push(ResultType t) { - for (size_t i = 0; i < t.length(); i++) { - if (!push(t[i])) { - return false; - } - } - return true; + MOZ_MUST_USE bool push(ExprType t) { + return IsVoid(t) || push(NonVoidToValType(t)); + } + MOZ_MUST_USE bool push(const Maybe& t) { + return t.isNothing() || push(t.ref()); + } + MOZ_MUST_USE bool push(TypeAndValue tv) { + return valueStack_.append(tv); } void infalliblePush(StackType t) { valueStack_.infallibleEmplaceBack(t); } void infalliblePush(ValType t) { valueStack_.infallibleEmplaceBack(StackType(t)); } - void infalliblePush(TypeAndValue tv) { valueStack_.infallibleAppend(tv); } + void infalliblePush(TypeAndValue tv) { + valueStack_.infallibleAppend(tv); + } void afterUnconditionalBranch() { - valueStack_.shrinkTo(controlStack_.back().valueStackBase()); + valueStack_.shrinkTo(controlStack_.back().valueStackStart()); controlStack_.back().setPolymorphicBase(); } + // Compute a type that is a supertype of one and two. This type is not + // guaranteed to be minimal; there may be a more specific supertype of one + // and two that this type is a supertype of. + inline bool weakMeet(ExprType one, ExprType two, ExprType* result) const; + inline bool checkIsSubtypeOf(ValType lhs, ValType rhs); public: + typedef Vector ValueVector; + #ifdef DEBUG explicit OpIter(const ModuleEnvironment& env, Decoder& decoder) : d_(decoder), @@ -719,21 +407,20 @@ class MOZ_STACK_CLASS OpIter : private Policy { MOZ_MUST_USE bool readOp(OpBytes* op); MOZ_MUST_USE bool readFunctionStart(uint32_t funcIndex); MOZ_MUST_USE bool readFunctionEnd(const uint8_t* bodyEnd); - MOZ_MUST_USE bool readReturn(ValueVector* values); + MOZ_MUST_USE bool readReturn(Value* value); MOZ_MUST_USE bool readBlock(); MOZ_MUST_USE bool readLoop(); MOZ_MUST_USE bool readIf(Value* condition); - MOZ_MUST_USE bool readElse(ResultType* thenType, ValueVector* thenValues); - MOZ_MUST_USE bool readEnd(LabelKind* kind, ResultType* type, - ValueVector* values); + MOZ_MUST_USE bool readElse(ExprType* thenType, Value* thenValue); + MOZ_MUST_USE bool readEnd(LabelKind* kind, ExprType* type, Value* value); void popEnd(); - MOZ_MUST_USE bool readBr(uint32_t* relativeDepth, ResultType* type, - ValueVector* values); - MOZ_MUST_USE bool readBrIf(uint32_t* relativeDepth, ResultType* type, - ValueVector* values, Value* condition); + MOZ_MUST_USE bool readBr(uint32_t* relativeDepth, ExprType* type, + Value* value); + MOZ_MUST_USE bool readBrIf(uint32_t* relativeDepth, ExprType* type, + Value* value, Value* condition); MOZ_MUST_USE bool readBrTable(Uint32Vector* depths, uint32_t* defaultDepth, - ResultType* defaultBranchValueType, - ValueVector* branchValues, Value* index); + ExprType* branchValueType, Value* branchValue, + Value* index); MOZ_MUST_USE bool readUnreachable(); MOZ_MUST_USE bool readDrop(); MOZ_MUST_USE bool readUnary(ValType operandType, Value* input); @@ -826,15 +513,6 @@ class MOZ_STACK_CLASS OpIter : private Policy { // ------------------------------------------------------------------------ // Stack management. - // 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]); - } - } - // Set the result value of the current top-of-value-stack expression. void setResult(Value value) { valueStack_.back().setValue(value); } @@ -858,6 +536,22 @@ class MOZ_STACK_CLASS OpIter : private Policy { bool controlStackEmpty() const { return controlStack_.empty(); } }; +template +inline bool OpIter::weakMeet(ExprType one, ExprType two, + ExprType* result) const { + if (MOZ_LIKELY(one == two)) { + *result = one; + return true; + } + + if (one.isReference() && two.isReference()) { + *result = ExprType::AnyRef; + return true; + } + + return false; +} + template inline bool OpIter::checkIsSubtypeOf(ValType actual, ValType expected) { if (actual == expected) { @@ -912,15 +606,13 @@ inline bool OpIter::failEmptyStack() { // 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]`. +// right thing for StackType::Bottom. Prefer (pop|top)WithType. template inline bool OpIter::popStackType(StackType* type, Value* value) { - Control& block = controlStack_.back(); + ControlStackEntry& block = controlStack_.back(); - MOZ_ASSERT(valueStack_.length() >= block.valueStackBase()); - if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackBase())) { + MOZ_ASSERT(valueStack_.length() >= block.valueStackStart()); + if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackStart())) { // If the base of this block's stack is polymorphic, then we can pop a // dummy value of any type; it won't be used since we're in unreachable // code. @@ -936,7 +628,7 @@ inline bool OpIter::popStackType(StackType* type, Value* value) { return failEmptyStack(); } - TypeAndValue& tv = valueStack_.back(); + TypeAndValue& tv = valueStack_.back(); *type = tv.type(); *value = tv.value(); valueStack_.popBack(); @@ -947,7 +639,7 @@ inline bool OpIter::popStackType(StackType* type, Value* value) { // expected type which can either be a specific value type or a type variable. template inline bool OpIter::popWithType(ValType expectedType, Value* value) { - StackType stackType; + StackType stackType(expectedType); if (!popStackType(&stackType, value)) { return false; } @@ -956,184 +648,136 @@ inline bool OpIter::popWithType(ValType expectedType, Value* value) { checkIsSubtypeOf(NonBottomToValType(stackType), expectedType); } -// Pops each of the given expected types (in reverse, because it's a stack). +// This function pops as many types from the stack as determined by the given +// signature. Currently, all signatures are limited to 0 or 1 types, with +// ExprType::Void meaning 0 and all other ValTypes meaning 1, but this will be +// generalized in the future. template -inline bool OpIter::popWithType(ResultType expected, - ValueVector* values) { - size_t expectedLength = expected.length(); - if (!values->resize(expectedLength)) { - return false; - } - 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)) { - return false; - } - } - return true; -} - -// This function is an optimization of the sequence: -// popWithType(ResultType, tmp) -// push(ResultType, tmp) -template -inline bool OpIter::popThenPushType(ResultType expected, - ValueVector* values) { - if (expected.empty()) { +inline bool OpIter::popWithType(ExprType expectedType, Value* value) { + if (IsVoid(expectedType)) { + *value = Value(); return true; } - Control& block = controlStack_.back(); + return popWithType(NonVoidToValType(expectedType), value); +} - size_t expectedLength = expected.length(); - if (!values->resize(expectedLength)) { - return false; - } +// This function is equivalent to: popWithType(expectedType); +// push(expectedType); +template +inline bool OpIter::topWithType(ValType expectedType, Value* value) { + ControlStackEntry& block = controlStack_.back(); - 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]; - Value* value = &(*values)[reverseIndex]; - size_t currentValueStackLength = valueStack_.length() - i; - - 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; they won't be used - // since we're in unreachable code. We must however push these types on - // the operand stack since they are now fixed by this constraint. - if (!valueStack_.insert(valueStack_.begin() + currentValueStackLength, - TypeAndValue(expectedType))) { + MOZ_ASSERT(valueStack_.length() >= block.valueStackStart()); + if (valueStack_.length() == block.valueStackStart()) { + // If the base of this block's stack is polymorphic, then we can just + // pull out a dummy value of the expected type; it won't be used since + // we're in unreachable code. We must however push this value onto the + // stack since it is now fixed to a specific type by this type + // constraint. + if (block.polymorphicBase()) { + if (!valueStack_.emplaceBack(expectedType, Value())) { return false; } *value = Value(); - } else { - TypeAndValue& observed = valueStack_[currentValueStackLength - 1]; - - if (observed.type() == StackType::Bottom) { - observed.typeRef() = StackType(expectedType); - *value = Value(); - } else { - if (!checkIsSubtypeOf(NonBottomToValType(observed.type()), - expectedType)) { - return false; - } - - *value = observed.value(); - } + return true; } + + return failEmptyStack(); } - return true; -} -// This function checks that the top of the stack is a subtype of expected. -// Like topWithType, it may insert synthetic StackType::Bottom entries if the -// block's stack is polymorphic, which happens during unreachable code. However -// unlike popThenPushType, it doesn't otherwise modify the value stack to update -// stack types. Finally, ensureTopHasType allows passing |nullptr| as |values| -// to avoid collecting values. + TypeAndValue& observed = valueStack_.back(); -template -inline bool OpIter::ensureTopHasType(ResultType expected, - ValueVector* values) { - if (expected.empty()) { + if (observed.type() == StackType::Bottom) { + observed.typeRef() = StackType(expectedType); + *value = Value(); return true; } - Control& block = controlStack_.back(); - - size_t expectedLength = expected.length(); - if (values && !values->resize(expectedLength)) { + if (!checkIsSubtypeOf(NonBottomToValType(observed.type()), expectedType)) { return false; } - 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; - } - }; - size_t currentValueStackLength = valueStack_.length() - i; - - MOZ_ASSERT(currentValueStackLength >= block.valueStackBase()); - if (currentValueStackLength == block.valueStackBase()) { - if (!block.polymorphicBase()) { - return failEmptyStack(); - } - - // Fill missing values with StackType::Bottom. - if (!valueStack_.insert(valueStack_.begin() + currentValueStackLength, - TypeAndValue(StackType::Bottom))) { - return false; - } - - collectValue(Value()); - } else { - TypeAndValue& observed = valueStack_[currentValueStackLength - 1]; - - if (observed.type() == StackType::Bottom) { - collectValue(Value()); - } else { - if (!checkIsSubtypeOf(NonBottomToValType(observed.type()), - expectedType)) { - return false; - } - - collectValue(observed.value()); - } - } - } - + *value = observed.value(); return true; } template -inline bool OpIter::pushControl(LabelKind kind, BlockType type) { - ResultType paramType = type.params(); +inline bool OpIter::topWithType(ExprType expectedType, Value* value) { + if (IsVoid(expectedType)) { + *value = Value(); + return true; + } - ValueVector values; - if (!popThenPushType(paramType, &values)) { + return topWithType(NonVoidToValType(expectedType), value); +} + +// This function checks that the top of the stack is a subtype of expectedType +// and returns the value if so. +template +inline bool OpIter::topIsType(ValType expectedType, + StackType* actualType, Value* value) { + ControlStackEntry& block = controlStack_.back(); + + MOZ_ASSERT(valueStack_.length() >= block.valueStackStart()); + if (valueStack_.length() == block.valueStackStart()) { + // If the base of this block's stack is polymorphic, then we can just + // pull out a dummy value of the expected type; it won't be used since + // we're in unreachable code. + if (block.polymorphicBase()) { + *actualType = StackType::Bottom; + *value = Value(); + return true; + } + + return failEmptyStack(); + } + + TypeAndValue& observed = valueStack_.back(); + + if (observed.type() == StackType::Bottom) { + *actualType = StackType::Bottom; + *value = Value(); + return true; + } + + if (!checkIsSubtypeOf(NonBottomToValType(observed.type()), expectedType)) { return false; } - MOZ_ASSERT(valueStack_.length() >= paramType.length()); - uint32_t valueStackBase = valueStack_.length() - paramType.length(); - return controlStack_.emplaceBack(kind, type, valueStackBase); + + *actualType = observed.type(); + *value = observed.value(); + return true; } template -inline bool OpIter::checkStackAtEndOfBlock(ResultType* expectedType, - ValueVector* values) { - Control& block = controlStack_.back(); - *expectedType = block.type().results(); +inline bool OpIter::pushControl(LabelKind kind, ExprType type) { + return controlStack_.emplaceBack(kind, type, valueStack_.length()); +} - MOZ_ASSERT(valueStack_.length() >= block.valueStackBase()); - if (expectedType->length() < valueStack_.length() - block.valueStackBase()) { +template +inline bool OpIter::checkStackAtEndOfBlock(ExprType* type, + Value* value) { + ControlStackEntry& block = controlStack_.back(); + + MOZ_ASSERT(valueStack_.length() >= block.valueStackStart()); + size_t pushed = valueStack_.length() - block.valueStackStart(); + if (pushed > (IsVoid(block.resultType()) ? 0u : 1u)) { return fail("unused values not explicitly dropped by end of block"); } - return popThenPushType(*expectedType, values); + if (!topWithType(block.resultType(), value)) { + return false; + } + + *type = block.resultType(); + return true; } template -inline bool OpIter::getControl(uint32_t relativeDepth, - Control** controlEntry) { +inline bool OpIter::getControl( + uint32_t relativeDepth, ControlStackEntry** controlEntry) { if (relativeDepth >= controlStack_.length()) { return fail("branch depth exceeds current nesting level"); } @@ -1143,42 +787,42 @@ inline bool OpIter::getControl(uint32_t relativeDepth, } template -inline bool OpIter::readBlockType(BlockType* type) { - uint8_t nextByte; - if (!d_.peekByte(&nextByte)) { - return fail("unable to read block type"); +inline bool OpIter::readBlockType(ExprType* type) { + uint8_t uncheckedCode; + uint32_t uncheckedRefTypeIndex; + if (!d_.readBlockType(&uncheckedCode, &uncheckedRefTypeIndex)) { + return fail("unable to read block signature"); } - if (nextByte == uint8_t(TypeCode::BlockVoid)) { - d_.uncheckedReadFixedU8(); - *type = BlockType::VoidToVoid(); - return true; - } - - if ((nextByte & SLEB128SignMask) == SLEB128SignBit) { - ValType v; - if (!readValType(&v)) { - return false; - } - *type = BlockType::VoidToSingle(v); - return true; - } - -#ifdef ENABLE_WASM_MULTI_VALUE - int32_t x; - if (!d_.readVarS32(&x) || x < 0 || uint32_t(x) >= env_.types.length()) { - return fail("invalid block type type index"); - } - - if (!env_.types[x].isFuncType()) { - return fail("block type type index must be func type"); - } - - *type = BlockType::Func(env_.types[x].funcType()); - return true; -#else - return fail("invalid block type reference"); + bool known = false; + switch (uncheckedCode) { + case uint8_t(ExprType::Void): + case uint8_t(ExprType::I32): + case uint8_t(ExprType::I64): + case uint8_t(ExprType::F32): + case uint8_t(ExprType::F64): + known = true; + break; + case uint8_t(ExprType::FuncRef): + case uint8_t(ExprType::AnyRef): +#ifdef ENABLE_WASM_REFTYPES + known = env_.refTypesEnabled(); #endif + break; + case uint8_t(ExprType::Ref): + known = env_.gcTypesEnabled() && uncheckedRefTypeIndex < MaxTypes && + uncheckedRefTypeIndex < env_.types.length(); + break; + case uint8_t(ExprType::Limit): + break; + } + + if (!known) { + return fail("invalid inline block type"); + } + + *type = ExprType(ExprType::Code(uncheckedCode), uncheckedRefTypeIndex); + return true; } template @@ -1211,12 +855,16 @@ inline void OpIter::peekOp(OpBytes* op) { template inline bool OpIter::readFunctionStart(uint32_t funcIndex) { - MOZ_ASSERT(thenParamStack_.empty()); MOZ_ASSERT(valueStack_.empty()); MOZ_ASSERT(controlStack_.empty()); MOZ_ASSERT(op_.b0 == uint16_t(Op::Limit)); - BlockType type = BlockType::FuncResults(*env_.funcTypes[funcIndex]); - return pushControl(LabelKind::Body, type); + const ValTypeVector& results = env_.funcTypes[funcIndex]->results(); + ExprType ret = ExprType::Void; + if (results.length()) { + MOZ_ASSERT(results.length() == 1, "multi-value return unimplemented"); + ret = ExprType(results[0]); + } + return pushControl(LabelKind::Body, ret); } template @@ -1228,7 +876,6 @@ inline bool OpIter::readFunctionEnd(const uint8_t* bodyEnd) { if (!controlStack_.empty()) { return fail("unbalanced function body control flow"); } - MOZ_ASSERT(thenParamStack_.empty()); #ifdef DEBUG op_ = OpBytes(Op::Limit); @@ -1238,13 +885,13 @@ inline bool OpIter::readFunctionEnd(const uint8_t* bodyEnd) { } template -inline bool OpIter::readReturn(ValueVector* values) { +inline bool OpIter::readReturn(Value* value) { MOZ_ASSERT(Classify(op_) == OpKind::Return); - Control& body = controlStack_[0]; + ControlStackEntry& body = controlStack_[0]; MOZ_ASSERT(body.kind() == LabelKind::Body); - if (!popWithType(body.resultType(), values)) { + if (!popWithType(body.resultType(), value)) { return false; } @@ -1256,7 +903,7 @@ template inline bool OpIter::readBlock() { MOZ_ASSERT(Classify(op_) == OpKind::Block); - BlockType type; + ExprType type = ExprType::Limit; if (!readBlockType(&type)) { return false; } @@ -1268,7 +915,7 @@ template inline bool OpIter::readLoop() { MOZ_ASSERT(Classify(op_) == OpKind::Loop); - BlockType type; + ExprType type = ExprType::Limit; if (!readBlockType(&type)) { return false; } @@ -1280,7 +927,7 @@ template inline bool OpIter::readIf(Value* condition) { MOZ_ASSERT(Classify(op_) == OpKind::If); - BlockType type; + ExprType type = ExprType::Limit; if (!readBlockType(&type)) { return false; } @@ -1289,56 +936,51 @@ inline bool OpIter::readIf(Value* condition) { return false; } - if (!pushControl(LabelKind::Then, type)) { - return false; - } - - size_t paramsLength = type.params().length(); - return thenParamStack_.append(valueStack_.end() - paramsLength, paramsLength); + return pushControl(LabelKind::Then, type); } template -inline bool OpIter::readElse(ResultType* thenType, - ValueVector* values) { +inline bool OpIter::readElse(ExprType* type, Value* value) { MOZ_ASSERT(Classify(op_) == OpKind::Else); - Control& block = controlStack_.back(); + // Finish checking the then-block. + + if (!checkStackAtEndOfBlock(type, value)) { + return false; + } + + ControlStackEntry& block = controlStack_.back(); + if (block.kind() != LabelKind::Then) { return fail("else can only be used within an if"); } - if (!checkStackAtEndOfBlock(thenType, values)) { - return false; + // Switch to the else-block. + + if (!IsVoid(block.resultType())) { + valueStack_.popBack(); } - // Restore to the entry state of the then block. Since the then block may - // clobbered any value in the block's params, we must restore from a - // snapshot. - valueStack_.shrinkTo(block.valueStackBase()); - size_t thenParamsLength = block.type().params().length(); - MOZ_ASSERT(thenParamStack_.length() >= thenParamsLength); - valueStack_.infallibleAppend(thenParamStack_.end() - thenParamsLength, - thenParamsLength); - thenParamStack_.shrinkBy(thenParamsLength); + MOZ_ASSERT(valueStack_.length() == block.valueStackStart()); block.switchToElse(); return true; } template -inline bool OpIter::readEnd(LabelKind* kind, ResultType* type, - ValueVector* values) { +inline bool OpIter::readEnd(LabelKind* kind, ExprType* type, + Value* value) { MOZ_ASSERT(Classify(op_) == OpKind::End); - if (!checkStackAtEndOfBlock(type, values)) { + if (!checkStackAtEndOfBlock(type, value)) { return false; } - Control& block = controlStack_.back(); + ControlStackEntry& block = controlStack_.back(); // If an `if` block ends with `end` instead of `else`, then we must // additionally validate that the then-block doesn't push anything. - if (block.kind() == LabelKind::Then && !block.resultType().empty()) { + if (block.kind() == LabelKind::Then && !IsVoid(block.resultType())) { return fail("if without else with a result value"); } @@ -1355,27 +997,26 @@ inline void OpIter::popEnd() { template inline bool OpIter::checkBranchValue(uint32_t relativeDepth, - ResultType* type, - ValueVector* values) { - Control* block = nullptr; + ExprType* type, Value* value) { + ControlStackEntry* block = nullptr; if (!getControl(relativeDepth, &block)) { return false; } *type = block->branchTargetType(); - return popThenPushType(*type, values); + return topWithType(*type, value); } template -inline bool OpIter::readBr(uint32_t* relativeDepth, ResultType* type, - ValueVector* values) { +inline bool OpIter::readBr(uint32_t* relativeDepth, ExprType* type, + Value* value) { MOZ_ASSERT(Classify(op_) == OpKind::Br); if (!readVarU32(relativeDepth)) { return fail("unable to read br depth"); } - if (!checkBranchValue(*relativeDepth, type, values)) { + if (!checkBranchValue(*relativeDepth, type, value)) { return false; } @@ -1384,8 +1025,8 @@ inline bool OpIter::readBr(uint32_t* relativeDepth, ResultType* type, } template -inline bool OpIter::readBrIf(uint32_t* relativeDepth, ResultType* type, - ValueVector* values, Value* condition) { +inline bool OpIter::readBrIf(uint32_t* relativeDepth, ExprType* type, + Value* value, Value* condition) { MOZ_ASSERT(Classify(op_) == OpKind::BrIf); if (!readVarU32(relativeDepth)) { @@ -1396,45 +1037,79 @@ inline bool OpIter::readBrIf(uint32_t* relativeDepth, ResultType* type, return false; } - return checkBranchValue(*relativeDepth, type, values); + return checkBranchValue(*relativeDepth, type, value); } #define UNKNOWN_ARITY UINT32_MAX template inline bool OpIter::checkBrTableEntry(uint32_t* relativeDepth, - ResultType prevType, - ResultType* type, - ValueVector* branchValues) { + uint32_t* branchValueArity, + ExprType* branchValueType, + Value* branchValue) { if (!readVarU32(relativeDepth)) { return false; } - Control* block = nullptr; + ControlStackEntry* block = nullptr; if (!getControl(*relativeDepth, &block)) { return false; } - *type = block->branchTargetType(); + // For the first encountered branch target, do a normal branch value type + // check which will change *branchValueArity and *branchValueType to a + // non-sentinel value. For all subsequent branch targets, check that the + // branch target arity and type matches the now-known branch value arity + // and type. This will need to change with multi-value. + uint32_t labelTypeArity = IsVoid(block->branchTargetType()) ? 0 : 1; - if (prevType != ResultType()) { - if (prevType.length() != type->length()) { - return fail("br_table targets must all have the same arity"); - } - - // Avoid re-collecting the same values for subsequent branch targets. - branchValues = nullptr; + if (*branchValueArity == UNKNOWN_ARITY) { + *branchValueArity = labelTypeArity; + } else if (*branchValueArity != labelTypeArity) { + return fail("br_table operand must be subtype of all target types"); } - return ensureTopHasType(*type, branchValues); + // If the label types are void, no need to check type on the stack + if (labelTypeArity == 0) { + *branchValueType = ExprType::Void; + *branchValue = Value(); + return true; + } + + // Check that the value on the stack is a subtype of the label + StackType actualBranchValueType; + if (!topIsType(NonVoidToValType(block->branchTargetType()), + &actualBranchValueType, branchValue)) { + return false; + } + + // If the value on the stack is the bottom type, it will by definition be a + // subtype of every possible label type. This also implies that the label + // types may not have a subtype relation, and so we cannot report a branch + // value type. Fortunately this only happens in unreachable code, where we + // don't use the branch value type. + if (actualBranchValueType == StackType::Bottom) { + *branchValueType = ExprType::Limit; + return true; + } + + // Compute the branch value type in all other cases + + if (*branchValueType == ExprType::Limit) { + *branchValueType = block->branchTargetType(); + } else if (!weakMeet(*branchValueType, block->branchTargetType(), + branchValueType)) { + return fail("br_table operand must be subtype of all target types"); + } + + return true; } template inline bool OpIter::readBrTable(Uint32Vector* depths, uint32_t* defaultDepth, - ResultType* defaultBranchType, - ValueVector* branchValues, - Value* index) { + ExprType* branchValueType, + Value* branchValue, Value* index) { MOZ_ASSERT(Classify(op_) == OpKind::BrTable); uint32_t tableLength; @@ -1454,22 +1129,22 @@ inline bool OpIter::readBrTable(Uint32Vector* depths, return false; } - ResultType prevBranchType; + uint32_t branchValueArity = UNKNOWN_ARITY; + *branchValueType = ExprType::Limit; + for (uint32_t i = 0; i < tableLength; i++) { - ResultType branchType; - if (!checkBrTableEntry(&(*depths)[i], prevBranchType, &branchType, - branchValues)) { + if (!checkBrTableEntry(&(*depths)[i], &branchValueArity, branchValueType, + branchValue)) { return false; } - prevBranchType = branchType; } - if (!checkBrTableEntry(defaultDepth, prevBranchType, defaultBranchType, - branchValues)) { + if (!checkBrTableEntry(defaultDepth, &branchValueArity, branchValueType, + branchValue)) { return false; } - MOZ_ASSERT(*defaultBranchType != ResultType()); + MOZ_ASSERT(branchValueArity != UNKNOWN_ARITY); afterUnconditionalBranch(); return true; @@ -1670,7 +1345,7 @@ inline bool OpIter::readTeeStore(ValType resultType, uint32_t byteSize, return false; } - infalliblePush(TypeAndValue(resultType, *value)); + infalliblePush(TypeAndValue(resultType, *value)); return true; } @@ -1836,13 +1511,7 @@ inline bool OpIter::readTeeLocal(const ValTypeVector& locals, return fail("local.set index out of range"); } - ValueVector single; - if (!popThenPushType(ResultType::Single(locals[*id]), &single)) { - return false; - } - - *value = single[0]; - return true; + return topWithType(locals[*id], value); } template @@ -1895,14 +1564,7 @@ inline bool OpIter::readTeeGlobal(uint32_t* id, Value* value) { return fail("can't write an immutable global"); } - ValueVector single; - if (!popThenPushType(ResultType::Single(env_.globals[*id].type()), &single)) { - return false; - } - - MOZ_ASSERT(single.length() == 1); - *value = single[0]; - return true; + return topWithType(env_.globals[*id].type(), value); } template @@ -1946,14 +1608,14 @@ inline bool OpIter::readRefFunc(uint32_t* funcTypeIndex) { if (!env_.validForRefFunc.getBit(*funcTypeIndex)) { return fail("function index is not in an element segment"); } - return push(ValType::FuncRef); + return push(StackType(ValType::FuncRef)); } template inline bool OpIter::readRefNull() { MOZ_ASSERT(Classify(op_) == OpKind::RefNull); - return push(ValType::NullRef); + return push(StackType(ValType::NullRef)); } template @@ -2010,7 +1672,7 @@ inline bool OpIter::readCall(uint32_t* funcTypeIndex, return false; } - return push(ResultType::Vector(funcType.results())); + return push(funcType.ret()); } template @@ -2063,7 +1725,7 @@ inline bool OpIter::readCallIndirect(uint32_t* funcTypeIndex, return false; } - return push(ResultType::Vector(funcType.results())); + return push(funcType.ret()); } template @@ -2093,7 +1755,7 @@ inline bool OpIter::readOldCallDirect(uint32_t numFuncImports, return false; } - return push(ResultType::Vector(funcType.results())); + return push(funcType.ret()); } template @@ -2124,7 +1786,11 @@ inline bool OpIter::readOldCallIndirect(uint32_t* funcTypeIndex, return false; } - return push(ResultType::Vector(funcType.results())); + if (!push(funcType.ret())) { + return false; + } + + return true; } template @@ -2717,7 +2383,7 @@ namespace mozilla { // Specialize IsPod for the Nothing specializations. template <> -struct IsPod> : TrueType {}; +struct IsPod> : TrueType {}; template <> struct IsPod> : TrueType {}; diff --git a/js/src/wasm/WasmTextToBinary.cpp b/js/src/wasm/WasmTextToBinary.cpp index afad0204b696..c5673185b17e 100644 --- a/js/src/wasm/WasmTextToBinary.cpp +++ b/js/src/wasm/WasmTextToBinary.cpp @@ -6178,23 +6178,12 @@ static bool EncodeExprList(Encoder& e, const AstExprVector& v) { return true; } -static bool EncodeBlockType(Encoder& e, AstExprType& t) { - ExprType type = t.type(); - static_assert(size_t(TypeCode::Limit) <= UINT8_MAX, "fits"); - MOZ_ASSERT(size_t(type.code()) < size_t(TypeCode::Limit)); - if (type.isRef()) { - return e.writeFixedU8(uint8_t(ExprType::Ref)) && - e.writeVarU32(type.refTypeIndex()); - } - return e.writeFixedU8(uint8_t(type.code())); -} - static bool EncodeBlock(Encoder& e, AstBlock& b) { if (!e.writeOp(b.op())) { return false; } - if (!EncodeBlockType(e, b.type())) { + if (!e.writeBlockType(b.type().type())) { return false; } @@ -6379,7 +6368,7 @@ static bool EncodeIf(Encoder& e, AstIf& i) { return false; } - if (!EncodeBlockType(e, i.type())) { + if (!e.writeBlockType(i.type().type())) { return false; } diff --git a/js/src/wasm/WasmTypes.cpp b/js/src/wasm/WasmTypes.cpp index 00fd59378dcc..7ed68e1dd630 100644 --- a/js/src/wasm/WasmTypes.cpp +++ b/js/src/wasm/WasmTypes.cpp @@ -227,6 +227,21 @@ uint8_t* FuncType::serialize(uint8_t* cursor) const { return cursor; } +namespace js { +namespace wasm { + +// ExprType is not POD while ReadScalar requires POD, so specialize. +template <> +inline const uint8_t* ReadScalar(const uint8_t* src, ExprType* dst) { + static_assert(sizeof(PackedTypeCode) == sizeof(ExprType), + "ExprType must carry only a PackedTypeCode"); + memcpy(dst->packedPtr(), src, sizeof(PackedTypeCode)); + return src + sizeof(*dst); +} + +} // namespace wasm +} // namespace js + const uint8_t* FuncType::deserialize(const uint8_t* cursor) { cursor = DeserializePodVector(cursor, &results_); if (!cursor) { diff --git a/js/src/wasm/WasmTypes.h b/js/src/wasm/WasmTypes.h index ea2e97bb6ed3..002f44d1b805 100644 --- a/js/src/wasm/WasmTypes.h +++ b/js/src/wasm/WasmTypes.h @@ -213,28 +213,26 @@ static_assert(std::is_pod::value, "must be POD to be simply serialized/deserialized"); const uint32_t NoTypeCode = 0xFF; // Only use these -const uint32_t NoRefTypeIndex = 0x3FFFFF; // with PackedTypeCode +const uint32_t NoRefTypeIndex = 0xFFFFFF; // with PackedTypeCode + +static inline PackedTypeCode InvalidPackedTypeCode() { + return PackedTypeCode((NoRefTypeIndex << 8) | NoTypeCode); +} + +static inline PackedTypeCode PackTypeCode(TypeCode tc) { + MOZ_ASSERT(uint32_t(tc) <= 0xFF); + MOZ_ASSERT(tc != TypeCode::Ref); + return PackedTypeCode((NoRefTypeIndex << 8) | uint32_t(tc)); +} static inline PackedTypeCode PackTypeCode(TypeCode tc, uint32_t refTypeIndex) { MOZ_ASSERT(uint32_t(tc) <= 0xFF); MOZ_ASSERT_IF(tc != TypeCode::Ref, refTypeIndex == NoRefTypeIndex); MOZ_ASSERT_IF(tc == TypeCode::Ref, refTypeIndex <= MaxTypes); - // A PackedTypeCode should be representable in a single word, so in the - // smallest case, 32 bits. However sometimes 2 bits of the word may be taken - // by a pointer tag; for that reason, limit to 30 bits; and then there's the - // 8-bit typecode, so 22 bits left for the type index. - static_assert(MaxTypes < (1 << (30 - 8)), "enough bits"); + static_assert(MaxTypes < (1 << (32 - 8)), "enough bits"); return PackedTypeCode((refTypeIndex << 8) | uint32_t(tc)); } -static inline PackedTypeCode PackTypeCode(TypeCode tc) { - return PackTypeCode(tc, NoRefTypeIndex); -} - -static inline PackedTypeCode InvalidPackedTypeCode() { - return PackedTypeCode(NoTypeCode); -} - static inline PackedTypeCode PackedTypeCodeFromBits(uint32_t bits) { return PackTypeCode(TypeCode(bits & 255), bits >> 8); } diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp index beaa96251a91..7b3cbce5840d 100644 --- a/js/src/wasm/WasmValidate.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -444,24 +444,15 @@ bool wasm::DecodeValidatedLocalEntries(Decoder& d, ValTypeVector* locals) { // Function body validation. -class NothingVector { - Nothing unused_; - - public: - bool resize(size_t length) { return true; } - Nothing& operator[](size_t) { return unused_; } - Nothing& back() { return unused_; } -}; - struct ValidatingPolicy { typedef Nothing Value; - typedef NothingVector ValueVector; typedef Nothing ControlItem; }; typedef OpIter ValidatingOpIter; static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env, + // FIXME(1401675): Replace with BlockType. uint32_t funcIndex, const ValTypeVector& locals, const uint8_t* bodyEnd, Decoder* d) { @@ -482,13 +473,12 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env, } Nothing nothing; - NothingVector nothings; - ResultType unusedType; switch (op.b0) { case uint16_t(Op::End): { LabelKind unusedKind; - if (!iter.readEnd(&unusedKind, &unusedType, ¬hings)) { + ExprType unusedType; + if (!iter.readEnd(&unusedKind, &unusedType, ¬hing)) { return false; } iter.popEnd(); @@ -503,12 +493,12 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env, CHECK(iter.readDrop()); case uint16_t(Op::Call): { uint32_t unusedIndex; - NothingVector unusedArgs; + ValidatingOpIter::ValueVector unusedArgs; CHECK(iter.readCall(&unusedIndex, &unusedArgs)); } case uint16_t(Op::CallIndirect): { uint32_t unusedIndex, unusedIndex2; - NothingVector unusedArgs; + ValidatingOpIter::ValueVector unusedArgs; CHECK(iter.readCallIndirect(&unusedIndex, &unusedIndex2, ¬hing, &unusedArgs)); } @@ -583,8 +573,10 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env, CHECK(iter.readLoop()); case uint16_t(Op::If): CHECK(iter.readIf(¬hing)); - case uint16_t(Op::Else): - CHECK(iter.readElse(&unusedType, ¬hings)); + case uint16_t(Op::Else): { + ExprType type; + CHECK(iter.readElse(&type, ¬hing)); + } case uint16_t(Op::I32Clz): case uint16_t(Op::I32Ctz): case uint16_t(Op::I32Popcnt): @@ -823,20 +815,23 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env, CHECK(iter.readMemorySize()); case uint16_t(Op::Br): { uint32_t unusedDepth; - CHECK(iter.readBr(&unusedDepth, &unusedType, ¬hings)); + ExprType unusedType; + CHECK(iter.readBr(&unusedDepth, &unusedType, ¬hing)); } case uint16_t(Op::BrIf): { uint32_t unusedDepth; - CHECK(iter.readBrIf(&unusedDepth, &unusedType, ¬hings, ¬hing)); + ExprType unusedType; + CHECK(iter.readBrIf(&unusedDepth, &unusedType, ¬hing, ¬hing)); } case uint16_t(Op::BrTable): { Uint32Vector unusedDepths; uint32_t unusedDefault; + ExprType unusedType; CHECK(iter.readBrTable(&unusedDepths, &unusedDefault, &unusedType, - ¬hings, ¬hing)); + ¬hing, ¬hing)); } case uint16_t(Op::Return): - CHECK(iter.readReturn(¬hings)); + CHECK(iter.readReturn(¬hing)); case uint16_t(Op::Unreachable): CHECK(iter.readUnreachable()); case uint16_t(Op::MiscPrefix): { @@ -963,7 +958,7 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env, return iter.unrecognizedOpcode(&op); } uint32_t unusedUint; - NothingVector unusedArgs; + ValidatingOpIter::ValueVector unusedArgs; CHECK(iter.readStructNew(&unusedUint, &unusedArgs)); } case uint32_t(MiscOp::StructGet): { @@ -1215,8 +1210,10 @@ static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env, bool wasm::ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, uint32_t bodySize, Decoder& d) { + const FuncType& funcType = *env.funcTypes[funcIndex]; + ValTypeVector locals; - if (!locals.appendAll(env.funcTypes[funcIndex]->args())) { + if (!locals.appendAll(funcType.args())) { return false; } diff --git a/js/src/wasm/WasmValidate.h b/js/src/wasm/WasmValidate.h index 7ba45414646e..979a8a25e0b8 100644 --- a/js/src/wasm/WasmValidate.h +++ b/js/src/wasm/WasmValidate.h @@ -396,6 +396,15 @@ class Encoder { } return writeFixedU8(uint8_t(type.code())); } + MOZ_MUST_USE bool writeBlockType(ExprType type) { + static_assert(size_t(TypeCode::Limit) <= UINT8_MAX, "fits"); + MOZ_ASSERT(size_t(type.code()) < size_t(TypeCode::Limit)); + if (type.isRef()) { + return writeFixedU8(uint8_t(ExprType::Ref)) && + writeVarU32(type.refTypeIndex()); + } + return writeFixedU8(uint8_t(type.code())); + } MOZ_MUST_USE bool writeOp(Op op) { static_assert(size_t(Op::Limit) == 256, "fits"); MOZ_ASSERT(size_t(op) < size_t(Op::Limit)); @@ -617,16 +626,6 @@ class Decoder { const uint8_t* begin() const { return beg_; } const uint8_t* end() const { return end_; } - // Peek at the next byte, if it exists, without advancing the position. - - bool peekByte(uint8_t* byte) { - if (done()) { - return false; - } - *byte = *cur_; - return true; - } - // Fixed-size encoding operations simply copy the literal bytes (without // attempting to align). @@ -709,6 +708,20 @@ class Decoder { } return true; } + MOZ_MUST_USE bool readBlockType(uint8_t* code, uint32_t* refTypeIndex) { + static_assert(size_t(TypeCode::Limit) <= UINT8_MAX, "fits"); + if (!readFixedU8(code)) { + return false; + } + if (*code == uint8_t(TypeCode::Ref)) { + if (!readVarU32(refTypeIndex)) { + return false; + } + } else { + *refTypeIndex = NoRefTypeIndex; + } + return true; + } MOZ_MUST_USE bool readOp(OpBytes* op) { static_assert(size_t(Op::Limit) == 256, "fits"); uint8_t u8;