diff --git a/hphp/doc/ir.specification b/hphp/doc/ir.specification index 8d2ea6887..845b79d43 100644 --- a/hphp/doc/ir.specification +++ b/hphp/doc/ir.specification @@ -363,6 +363,11 @@ OverrideLoc S0:FramePtr T doesn't have to be related to the local's current type, so this may be used to update tracked state after InterpOne instructions. +SmashLocals S0:FramePtr + + Erases the tracked types and values of all locals in the frame + represented by S0. + D:StkPtr = GuardStk S0:StkPtr Guard that the type of the cell on the stack pointed to by S0 at @@ -1449,13 +1454,14 @@ D:Int LdSwitchObjIndex S0:Obj S1:Int S2:Int in the range [S1:S1 + S2), and if so return the value S1 - (Int)S0. Else, they return the target of the default target, S2 + 1. -D:StkPtr InterpOne S0:FramePtr S1:StkPtr S2:ConstInt S3:ConstInt +D:StkPtr InterpOne S0:FramePtr S1:StkPtr - Call the interpreter implementation function for one opcode. S0 and - S1 are, respectively, the VM frame and stack pointers before this - instruction. S2 is the bytecode offset. S3 is the stack adjustment - performed by this instruction: number of cells popped minus number - of cells pushed. This instruction returns the updated VM stack + Call the interpreter implementation function for one opcode. S0 and S1 are, + respectively, the VM frame and stack pointers before this instruction. T is + the type of the top stack element after the call, or None if it pushes + nothing. bcOff is the bytecode offset. numPopped is the number of stack cells + consumed by the instruction, and numPushed is the number of stack cells + produced by the instruction. This instruction returns the updated VM stack pointer. InterpOneCF S0:FramePtr S1:StkPtr S2:ConstInt S3:ConstInt diff --git a/hphp/runtime/base/runtime_option.h b/hphp/runtime/base/runtime_option.h index bfdc15194..92e771efa 100644 --- a/hphp/runtime/base/runtime_option.h +++ b/hphp/runtime/base/runtime_option.h @@ -403,6 +403,7 @@ public: F(bool, ProfileBC, false) \ F(bool, ProfileHWEnable, true) \ F(string, ProfileHWEvents, string("")) \ + F(bool, JitAlwaysInterpOne, false) \ F(uint32_t, JitMaxTranslations, 12) \ F(uint64_t, JitGlobalTranslationLimit, -1) \ F(bool, JitTrampolines, true) \ diff --git a/hphp/runtime/vm/hhbc.cpp b/hphp/runtime/vm/hhbc.cpp index 3170779e4..404467a91 100644 --- a/hphp/runtime/vm/hhbc.cpp +++ b/hphp/runtime/vm/hhbc.cpp @@ -892,11 +892,20 @@ bool instrIsControlFlow(Op opcode) { } bool instrIsNonCallControlFlow(Op opcode) { - return - instrIsControlFlow(opcode) && - !isFCallStar(opcode) && - opcode != OpContEnter && - opcode != OpFCallBuiltin; + if (!instrIsControlFlow(opcode) || isFCallStar(opcode)) return false; + switch (opcode) { + case OpContEnter: + case OpFCallBuiltin: + case OpIncl: + case OpInclOnce: + case OpReq: + case OpReqOnce: + case OpReqDoc: + return false; + + default: + return true; + } } bool instrAllowsFallThru(Op opcode) { diff --git a/hphp/runtime/vm/jit/codegen.cpp b/hphp/runtime/vm/jit/codegen.cpp index 721f9c0e3..132d483a6 100644 --- a/hphp/runtime/vm/jit/codegen.cpp +++ b/hphp/runtime/vm/jit/codegen.cpp @@ -87,15 +87,16 @@ int64_t spillSlotsToSize(int n) { return n * sizeof(int64_t); } -void cgPunt(const char* file, int line, const char* func, uint32_t bcOff) { +void cgPunt(const char* file, int line, const char* func, uint32_t bcOff, + const Func* vmFunc) { if (dumpIREnabled()) { HPHP::Trace::trace("--------- CG_PUNT %s %d %s bcOff: %d \n", file, line, func, bcOff); } - throw FailedCodeGen(file, line, func, bcOff); + throw FailedCodeGen(file, line, func, bcOff, vmFunc); } -#define CG_PUNT(instr) cgPunt(__FILE__, __LINE__, #instr, m_curBcOff) +#define CG_PUNT(instr) cgPunt(__FILE__, __LINE__, #instr, m_curBcOff, curFunc()) struct CycleInfo { int node; @@ -354,6 +355,7 @@ NOOP_OPCODE(DefFP) NOOP_OPCODE(DefSP) NOOP_OPCODE(AssertLoc) NOOP_OPCODE(OverrideLoc) +NOOP_OPCODE(SmashLocals) NOOP_OPCODE(AssertStk) NOOP_OPCODE(AssertStkVal) NOOP_OPCODE(Nop) @@ -5116,9 +5118,8 @@ void CodeGenerator::cgConcat(IRInstruction* inst) { void CodeGenerator::cgInterpOne(IRInstruction* inst) { SSATmp* fp = inst->src(0); SSATmp* sp = inst->src(1); - SSATmp* pcOffTmp = inst->src(2); - SSATmp* spAdjustmentTmp = inst->src(3); - int64_t pcOff = pcOffTmp->getValInt(); + auto const& extra = *inst->extra(); + int64_t pcOff = extra.bcOff; auto opc = *(curFunc()->unit()->at(pcOff)); void* interpOneHelper = interpOneEntryPoints[opc]; @@ -5130,7 +5131,7 @@ void CodeGenerator::cgInterpOne(IRInstruction* inst) { auto newSpReg = m_regs[inst->dst()].reg(); assert(newSpReg == m_regs[sp].reg()); - int64_t spAdjustBytes = cellsToBytes(spAdjustmentTmp->getValInt()); + int64_t spAdjustBytes = cellsToBytes(extra.cellsPopped - extra.cellsPushed); if (spAdjustBytes != 0) { m_as.addq(spAdjustBytes, newSpReg); } diff --git a/hphp/runtime/vm/jit/codegen.h b/hphp/runtime/vm/jit/codegen.h index 37340fb52..b7e7fe25e 100644 --- a/hphp/runtime/vm/jit/codegen.h +++ b/hphp/runtime/vm/jit/codegen.h @@ -28,17 +28,6 @@ namespace HPHP { namespace JIT { -class FailedCodeGen : public std::exception { - public: - const char* file; - const int line; - const char* func; - const uint32_t bcOff; - FailedCodeGen(const char* _file, int _line, const char* _func, - uint32_t _bcOff) : - file(_file), line(_line), func(_func), bcOff(_bcOff) { } -}; - struct ArgGroup; // DestType describes the return type of native helper calls, particularly diff --git a/hphp/runtime/vm/jit/extradata.h b/hphp/runtime/vm/jit/extradata.h index ff59de9f6..87cd39009 100644 --- a/hphp/runtime/vm/jit/extradata.h +++ b/hphp/runtime/vm/jit/extradata.h @@ -317,6 +317,23 @@ struct CheckDefinedClsData : IRExtraData { const Class* cls; }; +/* + * Offset and stack deltas for InterpOne. + */ +struct InterpOneData : IRExtraData { + InterpOneData(Offset o, int64_t pop, int64_t push) + : bcOff(o), cellsPopped(pop), cellsPushed(push) {} + + // Offset of the instruction to interpret, in the Unit indicated by + // the current Marker. + Offset bcOff; + + // The number of stack cells consumed and produced by the + // instruction, respectively. Includes ActRecs. + int64_t cellsPopped; + int64_t cellsPushed; +}; + ////////////////////////////////////////////////////////////////////// #define X(op, data) \ @@ -380,6 +397,7 @@ X(ReqBindJmpNZero, ReqBindJccData); X(SideExitGuardLoc, SideExitGuardData); X(SideExitGuardStk, SideExitGuardData); X(CheckDefinedClsEq, CheckDefinedClsData); +X(InterpOne, InterpOneData); #undef X diff --git a/hphp/runtime/vm/jit/hhbctranslator.cpp b/hphp/runtime/vm/jit/hhbctranslator.cpp index 00005158a..95e8d9ff7 100644 --- a/hphp/runtime/vm/jit/hhbctranslator.cpp +++ b/hphp/runtime/vm/jit/hhbctranslator.cpp @@ -217,6 +217,17 @@ void HhbcTranslator::replace(uint32_t index, SSATmp* tmp) { m_evalStack.replace(index, tmp); } +Type HhbcTranslator::topType(uint32_t idx) const { + if (idx < m_evalStack.size()) { + return m_evalStack.top(idx)->type(); + } else { + auto stkVal = getStackValue(m_tb->sp(), + idx - m_evalStack.size() + m_stackDeficit); + if (stkVal.knownType.equals(Type::None)) return Type::Gen; + return stkVal.knownType; + } +} + /* * When doing gen-time inlining, we set up a series of IR instructions * that looks like this: @@ -503,7 +514,7 @@ void HhbcTranslator::emitAddElemC() { void HhbcTranslator::emitAddNewElemC() { if (!topC(1)->isA(Type::Arr)) { - return emitInterpOne(Type::Arr, 2, 0); + return emitInterpOne(Type::Arr, 2); } auto const val = popC(); @@ -3278,31 +3289,269 @@ void HhbcTranslator::emitXor() { gen(DecRef, btr); } -/** - * Emit InterpOne instruction. - * - 'type' is the return type of the value the instruction pushes on - * the stack if any (or Type:None if none) - * - 'numPopped' is the number of cells that this instruction pops - * - 'numExtraPushed' is the number of cells this instruction pushes on - * the stack, in addition to the cell corresponding to 'type' - */ -void HhbcTranslator::emitInterpOne(Type type, int numPopped, - int numExtraPushed) { - // We're calling into the interpreter so we want the stack synced to memory. - SSATmp* sp = spillStack(); - // discard the top elements of the stack, which are consumed by this instr - discard(numPopped); - assert(numPopped == m_stackDeficit); - int numPushed = (type == Type::None ? 0 : 1) + numExtraPushed; - gen( - InterpOne, - type, - m_tb->fp(), - sp, - cns(bcOff()), - cns(numPopped - numPushed) - ); - m_stackDeficit = 0; +namespace { + +Type arithOpResult(Type t1, Type t2) { + if (!t1.isKnownDataType() || !t2.isKnownDataType()) { + return Type::Cell; + } + + auto both = t1 | t2; + if (both.maybe(Type::Dbl)) return Type::Dbl; + if (both.maybe(Type::Arr)) return Type::Arr; + if (both.maybe(Type::Str)) return Type::Cell; + return Type::Int; +} + +Type bitOpResult(Type t1, Type t2) { + if (!t1.isKnownDataType() || !t2.isKnownDataType()) { + return Type::Cell; + } + + auto both = t1 | t2; + if (both.subtypeOf(Type::Str)) return Type::Str; + return Type::Int; +} + +Type setOpResult(Type locType, Type valType, SetOpOp op) { + switch (op) { + case SetOpPlusEqual: + case SetOpMinusEqual: + case SetOpMulEqual: return arithOpResult(locType.unbox(), valType); + case SetOpConcatEqual: return Type::Str; + case SetOpDivEqual: + case SetOpModEqual: return Type::Cell; + case SetOpAndEqual: + case SetOpOrEqual: + case SetOpXorEqual: return bitOpResult(locType.unbox(), valType); + case SetOpSlEqual: + case SetOpSrEqual: return Type::Int; + + case SetOp_invalid: not_reached(); + } + not_reached(); +} + +uint32_t localOutputId(const NormalizedInstruction& inst) { + switch (inst.op()) { + case OpUnpackCont: + case OpPackCont: + case OpContRetC: + case OpContSend: + case OpContRaise: + return 0; + + case OpSetWithRefLM: + case OpFPassL: + return inst.imm[1].u_IVA; + + default: + return inst.imm[0].u_IVA; + } +} + +} + +Type HhbcTranslator::interpOutputType(const NormalizedInstruction& inst) const { + using namespace Transl::InstrFlags; + auto localType = [&]{ + auto locId = localOutputId(inst); + assert(locId >= 0 && locId < curFunc()->numLocals()); + auto t = m_tb->getLocalType(locId); + return t.equals(Type::None) ? Type::Gen : t; + }; + auto cell = [](Type t) { + return t.unbox(); + }; + auto boxed = [](Type t) { + if (t.equals(Type::Gen)) return t; + assert(t.isBoxed() || t.notBoxed()); + return t.isBoxed() ? t : boxType(t); + }; + + auto outFlag = getInstrInfo(inst.op()).type; + if (outFlag == OutFInputL) { + outFlag = inst.preppedByRef ? OutVInputL : OutCInputL; + } else if (outFlag == OutFInputR) { + outFlag = inst.preppedByRef ? OutVInput : OutCInput; + } + + switch (outFlag) { + case OutNull: return Type::InitNull; + case OutNullUninit: return Type::Uninit; + case OutString: return Type::Str; + case OutStringImm: return Type::StaticStr; + case OutDouble: return Type::Dbl; + case OutBoolean: + case OutBooleanImm: return Type::Bool; + case OutInt64: return Type::Int; + case OutArray: return Type::Arr; + case OutArrayImm: return Type::Arr; // Should be StaticArr: t2124292 + case OutObject: + case OutThisObject: return Type::Obj; + + case OutFDesc: return Type::None; + case OutUnknown: return Type::Gen; + case OutPred: return inst.outPred; + case OutCns: return Type::Cell; + case OutVUnknown: return Type::BoxedCell; + + case OutSameAsInput: return topType(0); + case OutCInput: return cell(topType(0)); + case OutVInput: return boxed(topType(0)); + case OutCInputL: return cell(localType()); + case OutVInputL: return boxed(localType()); + case OutFInputL: + case OutFInputR: not_reached(); + + case OutArith: return arithOpResult(topType(0), topType(1)); + case OutBitOp: + return bitOpResult(topType(0), + inst.op() == HPHP::OpBitNot ? Type::Bottom + : topType(1)); + case OutSetOp: return setOpResult(localType(), topType(0), + SetOpOp(inst.imm[1].u_OA)); + case OutIncDec: return localType().unbox().isInt() ? Type::Int + : Type::Cell; + case OutStrlen: return topType(0).isString() ? Type::Int : Type::Cell; + case OutClassRef: return Type::Cls; + case OutSetM: return Type::Cell; // Imprecise but we can translate + // all cases that matter. + + case OutNone: return Type::None; + } + not_reached(); +} + +void HhbcTranslator::interpOutputLocals(const NormalizedInstruction& inst) { + using namespace Transl::InstrFlags; + if (!(getInstrInfo(inst.op()).out & Local)) return; + + auto setImmLocType = [&](uint32_t id, Type t = Type::Gen) { + gen(OverrideLoc, t, LocalId(inst.imm[id].u_HA), m_tb->fp()); + }; + + switch (inst.op()) { + case OpSetN: + case OpSetOpN: + case OpIncDecN: + case OpBindN: + case OpUnsetN: + gen(SmashLocals, m_tb->fp()); + break; + + case OpSetOpL: + case OpIncDecL: { + auto locType = m_tb->getLocalType(inst.imm[0].u_HA); + auto stackType = topType(0); + setImmLocType(0, locType.isBoxed() ? stackType.box() : stackType); + break; + } + + case OpStaticLocInit: + setImmLocType(0, Type::BoxedCell); + break; + + case OpInitThisLoc: + setImmLocType(0, Type::Gen); + break; + + case OpSetL: + case OpBindL: + setImmLocType(0, topType(0)); + break; + + case OpUnsetL: + setImmLocType(0, Type::Uninit); + break; + + case OpSetM: + case OpSetOpM: + case OpBindM: + case OpVGetM: + case OpSetWithRefLM: + case OpSetWithRefRM: + switch (inst.immVec.locationCode()) { + case LL: { + auto const& mii = getMInstrInfo(inst.mInstrOp()); + auto const& base = inst.inputs[mii.valCount()]->location; + assert(base.space == Location::Local); + Type baseType = m_tb->getLocalType(base.offset); + assert(baseType.isBoxed() || baseType.notBoxed() || + baseType.equals(Type::Gen)); + const bool baseBoxed = baseType.isBoxed(); + baseType = baseType.strip(); + + // This is a simple, conservative approximation of the real type flow + // logic that happens in VectorTranslator. + if (baseType.maybe(Type::Str | Type::Bool | Type::Null)) { + auto promoted = mcodeMaybePropName(inst.immVecM[0]) ? Type::Obj + : Type::Arr; + if (!baseType.isNull()) { + // Promotion isn't guaranteed to happen so the base might keep + // its original type. + promoted = promoted | baseType; + } + if (baseBoxed) promoted = boxType(promoted); + gen(OverrideLoc, promoted, LocalId(base.offset), m_tb->fp()); + } + break; + } + + case LNL: + case LNC: + gen(SmashLocals, m_tb->fp()); + break; + + default: + break; + } + break; + + case OpMIterInitK: + case OpMIterNextK: + setImmLocType(3, Type::Cell); + case OpMIterInit: + case OpMIterNext: + setImmLocType(2, Type::BoxedCell); + break; + + case OpIterInitK: + case OpWIterInitK: + case OpIterNextK: + case OpWIterNextK: + setImmLocType(3, Type::Cell); + case OpIterInit: + case OpWIterInit: + case OpIterNext: + case OpWIterNext: + setImmLocType(2, Type::Gen); + break; + + default: + not_reached(); + } +} + +void HhbcTranslator::emitInterpOne(const NormalizedInstruction& inst) { + auto stackType = interpOutputType(inst); + auto popped = getStackPopped(inst); + auto pushed = getStackPushed(inst); + FTRACE(1, "emitting InterpOne for {}, result = {}, popped {}, pushed {}\n", + inst.toString(), stackType.toString(), popped, pushed); + emitInterpOne(stackType, popped, pushed); + interpOutputLocals(inst); +} + +void HhbcTranslator::emitInterpOne(Type outType, int popped) { + emitInterpOne(outType, popped, outType.equals(Type::None) ? 0 : 1); +} + +void HhbcTranslator::emitInterpOne(Type outType, int popped, int pushed) { + auto sp = spillStack(); + gen(InterpOne, outType, InterpOneData(bcOff(), popped, pushed), + m_tb->fp(), sp); + assert(m_stackDeficit == 0); } void HhbcTranslator::emitInterpOneCF(int numPopped) { diff --git a/hphp/runtime/vm/jit/hhbctranslator.h b/hphp/runtime/vm/jit/hhbctranslator.h index b6d60e1b2..bf28ba39f 100644 --- a/hphp/runtime/vm/jit/hhbctranslator.h +++ b/hphp/runtime/vm/jit/hhbctranslator.h @@ -151,9 +151,14 @@ struct HhbcTranslator { // Other public functions for irtranslator. void setThisAvailable(); - void emitInterpOne(Type type, int numPopped, int numExtraPushed = 0); + void emitInterpOne(Type t, int popped); + void emitInterpOne(Type t, int popped, int pushed); + void emitInterpOne(const NormalizedInstruction&); void emitInterpOneCF(int numPopped); std::string showStack() const; + bool hasExit() const { + return m_hasExit; + } /* * An emit* function for each HHBC opcode. @@ -664,6 +669,9 @@ private: void emitMarker(); SSATmp* staticTVCns(const TypedValue*); + Type interpOutputType(const NormalizedInstruction&) const; + void interpOutputLocals(const NormalizedInstruction&); + private: // Exit trace creation routines. IRTrace* getExitTrace(Offset targetBcOff = -1); IRTrace* getExitTrace(Offset targetBcOff, @@ -779,6 +787,7 @@ private: SSATmp* popF() { return pop(Type::Gen); } SSATmp* topC(uint32_t i = 0) { return top(Type::Cell, i); } SSATmp* topV(uint32_t i = 0) { return top(Type::BoxedCell, i); } + Type topType(uint32_t i) const; std::vector peekSpillValues() const; SSATmp* emitSpillStack(IRTrace* t, SSATmp* sp, const std::vector& spillVals); diff --git a/hphp/runtime/vm/jit/ir.h b/hphp/runtime/vm/jit/ir.h index c21f75d36..f56fd5ebe 100644 --- a/hphp/runtime/vm/jit/ir.h +++ b/hphp/runtime/vm/jit/ir.h @@ -80,6 +80,28 @@ class FailedIRGen : public std::runtime_error { {} }; +class FailedCodeGen : public std::runtime_error { + public: + const char* file; + const int line; + const char* func; + const Offset bcOff; + const Func* vmFunc; + + FailedCodeGen(const char* _file, int _line, const char* _func, + uint32_t _bcOff, const Func* _vmFunc) + : std::runtime_error(folly::format("FailedCodeGen @ {}:{} in {}. {}@{}", + _file, _line, _func, + _vmFunc->fullName()->data(), _bcOff) + .str()) + , file(_file) + , line(_line) + , func(_func) + , bcOff(_bcOff) + , vmFunc(_vmFunc) + {} +}; + #define SPUNT(instr) do { \ throw FailedIRGen(__FILE__, __LINE__, instr); \ } while(0) @@ -180,6 +202,7 @@ O(GuardRefs, ND, S(Func) \ S(Int), E) \ O(AssertLoc, ND, S(FramePtr), E) \ O(OverrideLoc, ND, S(FramePtr), E) \ +O(SmashLocals, ND, S(FramePtr), E) \ O(BeginCatch, ND, NA, E|Mem) \ O(EndCatch, ND, S(StkPtr), E|Mem) \ O(LdUnwinderValue, DParam, NA, PRc) \ @@ -441,8 +464,8 @@ O(AddNewElem, D(Arr), SUnk, N|Mem|CRc|PRc) \ O(Concat, D(Str), S(Gen) S(Gen), N|Mem|CRc|PRc|Refs) \ O(ArrayAdd, D(Arr), S(Arr) S(Arr), N|Mem|CRc|PRc) \ O(AKExists, D(Bool), S(Cell) S(Cell), C|N) \ -O(InterpOne, D(StkPtr), S(FramePtr) S(StkPtr) \ - C(Int) C(Int), E|N|Mem|Refs|Er) \ +O(InterpOne, D(StkPtr), S(FramePtr) S(StkPtr), \ + E|N|Mem|Refs|Er) \ O(InterpOneCF, ND, S(FramePtr) S(StkPtr) \ C(Int), T|E|N|Mem|Refs|Er) \ O(Spill, DofS(0), SUnk, Mem) \ diff --git a/hphp/runtime/vm/jit/irtranslator.cpp b/hphp/runtime/vm/jit/irtranslator.cpp index c49d26542..299c53729 100644 --- a/hphp/runtime/vm/jit/irtranslator.cpp +++ b/hphp/runtime/vm/jit/irtranslator.cpp @@ -49,6 +49,7 @@ using namespace reg; using namespace Util; using namespace Trace; using std::max; +using JIT::HhbcTranslator; TRACE_SET_MOD(hhir); #ifdef DEBUG @@ -1414,60 +1415,6 @@ Translator::translateCIterFree(const NormalizedInstruction& i) { HHIR_EMIT(CIterFree, i.imm[0].u_IVA); } -// PSEUDOINSTR_DISPATCH is a switch() fragment that routes opcodes to their -// shared handlers, as per the PSEUDOINSTRS macro. -#define PSEUDOINSTR_DISPATCH(func) \ - case OpBitAnd: \ - case OpBitOr: \ - case OpBitXor: \ - case OpSub: \ - case OpMul: \ - func(BinaryArithOp, t, i) \ - case OpSame: \ - case OpNSame: \ - func(SameOp, t, i) \ - case OpEq: \ - case OpNeq: \ - func(EqOp, t, i) \ - case OpLt: \ - case OpLte: \ - case OpGt: \ - case OpGte: \ - func(LtGtOp, t, i) \ - case OpEmptyL: \ - case OpCastBool: \ - func(UnaryBooleanOp, t, i) \ - case OpJmpZ: \ - case OpJmpNZ: \ - func(BranchOp, t, i) \ - case OpSetL: \ - case OpBindL: \ - func(AssignToLocalOp, t, i) \ - case OpFPassC: \ - case OpFPassCW: \ - case OpFPassCE: \ - func(FPassCOp, t, i) \ - case OpFPushCuf: \ - case OpFPushCufF: \ - case OpFPushCufSafe: \ - func(FPushCufOp, t, i) \ - case OpIssetL: \ - case OpIsNullL: \ - case OpIsStringL: \ - case OpIsArrayL: \ - case OpIsIntL: \ - case OpIsObjectL: \ - case OpIsBoolL: \ - case OpIsDoubleL: \ - case OpIsNullC: \ - case OpIsStringC: \ - case OpIsArrayC: \ - case OpIsIntC: \ - case OpIsObjectC: \ - case OpIsBoolC: \ - case OpIsDoubleC: \ - func(CheckTypeOp, t, i) - // All vector instructions are handled by one HhbcTranslator method. #define MII(instr, ...) \ void Translator::translate##instr##M(const NormalizedInstruction& ni) { \ @@ -1477,20 +1424,6 @@ MINSTRS MII(FPass) #undef MII -void -Translator::translateInstrDefault(const NormalizedInstruction& i) { - const char *opNames[] = { -#define O(name, imm, push, pop, flags) \ -"Unimplemented" #name, - OPCODES -#undef O - }; - auto const op = i.op(); - - HHIR_UNIMPLEMENTED_OP(opNames[uint8_t(op)]); - assert(false); -} - void Translator::translateInstrWork(const NormalizedInstruction& i) { auto const op = i.op(); @@ -1498,15 +1431,15 @@ Translator::translateInstrWork(const NormalizedInstruction& i) { switch (op) { #define CASE(iNm) \ case Op ## iNm: \ - translate ## iNm(i); \ + translate ## iNm(i); \ break; -#define TRANSLATE(a, b, c) translate ## a(c); break; +#define TRANSLATE(name, inst) translate ## name(inst); break; INSTRS PSEUDOINSTR_DISPATCH(TRANSLATE) #undef TRANSLATE #undef CASE - default: - translateInstrDefault(i); + default: + not_reached(); } } @@ -1557,33 +1490,15 @@ static int getNumPopped(const NormalizedInstruction& i) { + (pushesActRec(i.op()) ? kNumActRecCells : 0); } -/** - * Returns the number of Act-Rec cells that instruction i pushes onto the stack. - */ -static int getNumARCellsPushed(const NormalizedInstruction& i) { - return pushesActRec(i.op()) ? kNumActRecCells : 0; -} - void Translator::interpretInstr(const NormalizedInstruction& i) { - JIT::Type outStkType = JIT::Type::fromDynLocation(i.outStack); int poppedCells = getNumPopped(i); - int arPushedCells = getNumARCellsPushed(i); - - FTRACE(5, "HHIR: BC Instr {} Popped = {} ARCellsPushed = {}\n", - i.toString(), poppedCells, arPushedCells); + FTRACE(5, "HHIR: BC Instr {} Popped = {}\n", + i.toString(), poppedCells); if (i.changesPC) { m_hhbcTrans->emitInterpOneCF(poppedCells); } else { - m_hhbcTrans->emitInterpOne(outStkType, poppedCells, arPushedCells); - if (i.outLocal) { - // HHIR tracks local values and types, so we should inform it about - // the new local type. This is done via an overriding type assertion. - assert(i.outLocal->isLocal()); - int32_t locId = i.outLocal->location.offset; - JIT::Type newType = JIT::Type::fromRuntimeType(i.outLocal->rtt); - m_hhbcTrans->overrideTypeLocal(locId, newType); - } + m_hhbcTrans->emitInterpOne(i); } } @@ -1611,7 +1526,7 @@ void Translator::translateInstr(const NormalizedInstruction& i) { 1, true); } - if (i.interp) { + if (instrMustInterp(i) || i.interp) { interpretInstr(i); } else { translateInstrWork(i); @@ -1665,21 +1580,9 @@ void TranslatorX64::irEmitResolvedDeps(const ChangeMap& resolvedDeps) { } } -static bool supportedInterpOne(const NormalizedInstruction* i) { - switch (i->op()) { - // Instructions that do function return are not supported yet - case OpRetC: - case OpRetV: - case OpContRetC: - case OpNativeImpl: - return false; - default: - return true; - } -} - TranslatorX64::TranslateResult TranslatorX64::irTranslateTracelet(Tracelet& t) { + FTRACE(2, "attempting to translate tracelet:\n{}\n", t.toString()); const SrcKey &sk = t.m_sk; SrcRec& srcRec = *getSrcRec(sk); assert(srcRec.inProgressTailJumps().size() == 0); @@ -1721,7 +1624,8 @@ TranslatorX64::irTranslateTracelet(Tracelet& t) { }(); // Translate each instruction in the tracelet - for (auto* ni = t.m_instrStream.first; ni; ni = ni->next) { + for (auto* ni = t.m_instrStream.first; ni && !m_hhbcTrans->hasExit(); + ni = ni->next) { try { SKTRACE(1, ni->source, "HHIR: translateInstr\n"); Nuller niNuller(&m_curNI); @@ -1730,12 +1634,11 @@ TranslatorX64::irTranslateTracelet(Tracelet& t) { } catch (JIT::FailedIRGen& fcg) { // If we haven't tried interpreting ni yet, flag it to be interpreted // and retry - if (!ni->interp && supportedInterpOne(ni)) { + if (!ni->interp) { ni->interp = true; return Retry; - } else { - throw fcg; } + throw fcg; } assert(ni->source.offset() >= curFunc()->base()); // We sometimes leave the tail of a truncated tracelet in place to aid @@ -1754,7 +1657,7 @@ TranslatorX64::irTranslateTracelet(Tracelet& t) { // problem, flag it to be interpreted, and retranslate the tracelet. for (auto ni = t.m_instrStream.first; ni; ni = ni->next) { if (ni->source.offset() == fcg.bcOff) { - if (!ni->interp && supportedInterpOne(ni)) { + if (!ni->interp) { ni->interp = true; TRACE(1, "HHIR: RETRY Translation %d: will interpOne BC instr %s " "after failing to code-gen \n\n", diff --git a/hphp/runtime/vm/jit/simplifier.cpp b/hphp/runtime/vm/jit/simplifier.cpp index b00274908..1226a08e0 100644 --- a/hphp/runtime/vm/jit/simplifier.cpp +++ b/hphp/runtime/vm/jit/simplifier.cpp @@ -129,11 +129,17 @@ StackValueInfo getStackValue(SSATmp* sp, uint32_t index) { case InterpOne: { SSATmp* prevSp = inst->src(1); - int64_t spAdjustment = inst->src(3)->getValInt(); // # popped - # pushed + auto const& extra = *inst->extra(); + int64_t spAdjustment = extra.cellsPopped - extra.cellsPushed; Type resultType = inst->typeParam(); if (index == 0 && !resultType.equals(Type::None)) { return StackValueInfo { resultType }; } + + // If the index we're looking for is a cell pushed by the InterpOne (other + // than top of stack), we know nothing about its type. + if (index < extra.cellsPushed) return StackValueInfo{ nullptr }; + return getStackValue(prevSp, index + spAdjustment); } diff --git a/hphp/runtime/vm/jit/tracebuilder.cpp b/hphp/runtime/vm/jit/tracebuilder.cpp index 16195149f..44420f7be 100644 --- a/hphp/runtime/vm/jit/tracebuilder.cpp +++ b/hphp/runtime/vm/jit/tracebuilder.cpp @@ -237,7 +237,8 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) { case InterpOne: { m_spValue = inst->dst(); - int64_t stackAdjustment = inst->src(3)->getValInt(); + auto const& extra = *inst->extra(); + int64_t stackAdjustment = extra.cellsPopped - extra.cellsPushed; // push the return value if any and adjust for the popped values m_spOffset -= stackAdjustment; break; @@ -289,6 +290,10 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) { inst->typeParam()); break; + case SmashLocals: + clearLocals(); + break; + case IterInitK: case WIterInitK: // kill the locals to which this instruction stores iter's key and value @@ -462,14 +467,18 @@ void TraceBuilder::useState(Block* block) { useState(std::move(state)); } -void TraceBuilder::clearTrackedState() { - killCse(); // clears m_cseHash +void TraceBuilder::clearLocals() { for (uint32_t i = 0; i < m_localValues.size(); i++) { m_localValues[i] = nullptr; } for (uint32_t i = 0; i < m_localTypes.size(); i++) { m_localTypes[i] = Type::None; } +} + +void TraceBuilder::clearTrackedState() { + killCse(); // clears m_cseHash + clearLocals(); m_callerAvailableValues.clear(); m_spValue = m_fpValue = nullptr; m_spOffset = 0; diff --git a/hphp/runtime/vm/jit/tracebuilder.h b/hphp/runtime/vm/jit/tracebuilder.h index 9e5e2b8a2..55c12aee6 100644 --- a/hphp/runtime/vm/jit/tracebuilder.h +++ b/hphp/runtime/vm/jit/tracebuilder.h @@ -339,6 +339,7 @@ private: void trackDefInlineFP(IRInstruction* inst); void trackInlineReturn(IRInstruction* inst); void updateTrackedState(IRInstruction* inst); + void clearLocals(); void clearTrackedState(); void dropLocalRefsInnerTypes(); diff --git a/hphp/runtime/vm/jit/translator-instrs.h b/hphp/runtime/vm/jit/translator-instrs.h new file mode 100644 index 000000000..d4b972796 --- /dev/null +++ b/hphp/runtime/vm/jit/translator-instrs.h @@ -0,0 +1,230 @@ +/* + +----------------------------------------------------------------------+ + | HipHop for PHP | + +----------------------------------------------------------------------+ + | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ +#ifndef incl_HPHP_TRANSLATOR_INSTRS_H_ +#define incl_HPHP_TRANSLATOR_INSTRS_H_ + +/* + * Macros used for dispatch in the translator. + */ + +#define INSTRS \ + CASE(PopC) \ + CASE(PopV) \ + CASE(PopR) \ + CASE(UnboxR) \ + CASE(Null) \ + CASE(NullUninit) \ + CASE(True) \ + CASE(False) \ + CASE(Int) \ + CASE(Double) \ + CASE(String) \ + CASE(Array) \ + CASE(NewArray) \ + CASE(NewTuple) \ + CASE(NewCol) \ + CASE(Nop) \ + CASE(AddElemC) \ + CASE(AddNewElemC) \ + CASE(ColAddElemC) \ + CASE(ColAddNewElemC) \ + CASE(Cns) \ + CASE(DefCns) \ + CASE(ClsCnsD) \ + CASE(Concat) \ + CASE(Add) \ + CASE(Xor) \ + CASE(Not) \ + CASE(Mod) \ + CASE(BitNot) \ + CASE(CastInt) \ + CASE(CastString) \ + CASE(CastDouble) \ + CASE(CastArray) \ + CASE(CastObject) \ + CASE(Print) \ + CASE(Jmp) \ + CASE(Switch) \ + CASE(SSwitch) \ + CASE(RetC) \ + CASE(RetV) \ + CASE(NativeImpl) \ + CASE(AGetC) \ + CASE(AGetL) \ + CASE(CGetL) \ + CASE(CGetL2) \ + CASE(CGetS) \ + CASE(CGetM) \ + CASE(CGetG) \ + CASE(VGetL) \ + CASE(VGetG) \ + CASE(VGetM) \ + CASE(IssetM) \ + CASE(EmptyM) \ + CASE(AKExists) \ + CASE(SetS) \ + CASE(SetG) \ + CASE(SetM) \ + CASE(SetWithRefLM) \ + CASE(SetWithRefRM) \ + CASE(SetOpL) \ + CASE(SetOpM) \ + CASE(IncDecL) \ + CASE(IncDecM) \ + CASE(UnsetL) \ + CASE(UnsetM) \ + CASE(BindM) \ + CASE(FPushFuncD) \ + CASE(FPushFunc) \ + CASE(FPushClsMethodD) \ + CASE(FPushClsMethodF) \ + CASE(FPushObjMethodD) \ + CASE(FPushCtor) \ + CASE(FPushCtorD) \ + CASE(FPassR) \ + CASE(FPassL) \ + CASE(FPassM) \ + CASE(FPassS) \ + CASE(FPassG) \ + CASE(This) \ + CASE(BareThis) \ + CASE(CheckThis) \ + CASE(InitThisLoc) \ + CASE(FCall) \ + CASE(FCallArray) \ + CASE(FCallBuiltin) \ + CASE(VerifyParamType) \ + CASE(InstanceOfD) \ + CASE(StaticLocInit) \ + CASE(IterInit) \ + CASE(IterInitK) \ + CASE(IterNext) \ + CASE(IterNextK) \ + CASE(WIterInit) \ + CASE(WIterInitK) \ + CASE(WIterNext) \ + CASE(WIterNextK) \ + CASE(ReqDoc) \ + CASE(DefCls) \ + CASE(DefFunc) \ + CASE(Self) \ + CASE(Parent) \ + CASE(ClassExists) \ + CASE(InterfaceExists) \ + CASE(TraitExists) \ + CASE(Dup) \ + CASE(CreateCl) \ + CASE(CreateCont) \ + CASE(ContEnter) \ + CASE(ContExit) \ + CASE(UnpackCont) \ + CASE(PackCont) \ + CASE(ContRetC) \ + CASE(ContNext) \ + CASE(ContSend) \ + CASE(ContRaise) \ + CASE(ContValid) \ + CASE(ContCurrent) \ + CASE(ContStopped) \ + CASE(ContHandle) \ + CASE(Strlen) \ + CASE(IncStat) \ + CASE(ArrayIdx) \ + CASE(FPushCufIter) \ + CASE(CIterFree) \ + CASE(LateBoundCls) \ + CASE(IssetS) \ + CASE(IssetG) \ + CASE(UnsetG) \ + CASE(EmptyS) \ + CASE(EmptyG) \ + CASE(VGetS) \ + CASE(BindS) \ + CASE(BindG) \ + CASE(IterFree) \ + CASE(FPassV) \ + CASE(UnsetN) \ + CASE(DecodeCufIter) \ + + // These are instruction-like functions which cover more than one + // opcode. +#define PSEUDOINSTRS \ + CASE(BinaryArithOp) \ + CASE(SameOp) \ + CASE(EqOp) \ + CASE(LtGtOp) \ + CASE(UnaryBooleanOp) \ + CASE(BranchOp) \ + CASE(AssignToLocalOp) \ + CASE(FPushCufOp) \ + CASE(FPassCOp) \ + CASE(CheckTypeOp) + +// PSEUDOINSTR_DISPATCH is a switch() fragment that routes opcodes to their +// shared handlers, as per the PSEUDOINSTRS macro. +#define PSEUDOINSTR_DISPATCH(func) \ + case OpBitAnd: \ + case OpBitOr: \ + case OpBitXor: \ + case OpSub: \ + case OpMul: \ + func(BinaryArithOp, i) \ + case OpSame: \ + case OpNSame: \ + func(SameOp, i) \ + case OpEq: \ + case OpNeq: \ + func(EqOp, i) \ + case OpLt: \ + case OpLte: \ + case OpGt: \ + case OpGte: \ + func(LtGtOp, i) \ + case OpEmptyL: \ + case OpCastBool: \ + func(UnaryBooleanOp, i) \ + case OpJmpZ: \ + case OpJmpNZ: \ + func(BranchOp, i) \ + case OpSetL: \ + case OpBindL: \ + func(AssignToLocalOp, i) \ + case OpFPassC: \ + case OpFPassCW: \ + case OpFPassCE: \ + func(FPassCOp, i) \ + case OpFPushCuf: \ + case OpFPushCufF: \ + case OpFPushCufSafe: \ + func(FPushCufOp, i) \ + case OpIssetL: \ + case OpIsNullL: \ + case OpIsStringL: \ + case OpIsArrayL: \ + case OpIsIntL: \ + case OpIsObjectL: \ + case OpIsBoolL: \ + case OpIsDoubleL: \ + case OpIsNullC: \ + case OpIsStringC: \ + case OpIsArrayC: \ + case OpIsIntC: \ + case OpIsObjectC: \ + case OpIsBoolC: \ + case OpIsDoubleC: \ + func(CheckTypeOp, i) + +#endif diff --git a/hphp/runtime/vm/jit/translator-x64.cpp b/hphp/runtime/vm/jit/translator-x64.cpp index 4fc946946..0a9de4213 100644 --- a/hphp/runtime/vm/jit/translator-x64.cpp +++ b/hphp/runtime/vm/jit/translator-x64.cpp @@ -2480,6 +2480,10 @@ bool TranslatorX64::handleServiceRequest(TReqInfo& info, } break; case REQ_RESUME: { + if (UNLIKELY(vmpc() == 0)) { + g_vmContext->m_fp = 0; + return false; + } SrcKey dest(curFunc(), vmpc()); sk = dest; start = getTranslation(TranslArgs(dest, true)); @@ -2726,6 +2730,11 @@ interpOne##opcode(ActRec* ar, Cell* sp, Offset pcOff) { \ assert(toOp(*vmpc()) == Op::opcode); \ VMExecutionContext* ec = g_vmContext; \ Stats::inc(Stats::Instr_InterpOne ## opcode); \ + if (Trace::moduleEnabled(Trace::interpOne, 1)) { \ + static const StringData* cat = StringData::GetStaticString("interpOne"); \ + static const StringData* name = StringData::GetStaticString(#opcode); \ + Stats::incStatGrouped(cat, name, 1); \ + } \ INC_TPC(interp_one) \ /* Correct for over-counting in TC-stats. */ \ Stats::inc(Stats::Instr_TC, -1); \ @@ -3017,65 +3026,11 @@ int64_t switchObjHelper(ObjectData* o, int64_t base, int64_t nTargets) { return switchBoundsCheck(ival, base, nTargets); } -// PSEUDOINSTR_DISPATCH is a switch() fragment that routes opcodes to their -// shared handlers, as per the PSEUDOINSTRS macro. -#define PSEUDOINSTR_DISPATCH(func) \ - case OpBitAnd: \ - case OpBitOr: \ - case OpBitXor: \ - case OpSub: \ - case OpMul: \ - func(BinaryArithOp, t, i) \ - case OpSame: \ - case OpNSame: \ - func(SameOp, t, i) \ - case OpEq: \ - case OpNeq: \ - func(EqOp, t, i) \ - case OpLt: \ - case OpLte: \ - case OpGt: \ - case OpGte: \ - func(LtGtOp, t, i) \ - case OpEmptyL: \ - case OpCastBool: \ - func(UnaryBooleanOp, t, i) \ - case OpJmpZ: \ - case OpJmpNZ: \ - func(BranchOp, t, i) \ - case OpSetL: \ - case OpBindL: \ - func(AssignToLocalOp, t, i) \ - case OpFPassC: \ - case OpFPassCW: \ - case OpFPassCE: \ - func(FPassCOp, t, i) \ - case OpFPushCuf: \ - case OpFPushCufF: \ - case OpFPushCufSafe: \ - func(FPushCufOp, t, i) \ - case OpIssetL: \ - case OpIsNullL: \ - case OpIsStringL: \ - case OpIsArrayL: \ - case OpIsIntL: \ - case OpIsObjectL: \ - case OpIsBoolL: \ - case OpIsDoubleL: \ - case OpIsNullC: \ - case OpIsStringC: \ - case OpIsArrayC: \ - case OpIsIntC: \ - case OpIsObjectC: \ - case OpIsBoolC: \ - case OpIsDoubleC: \ - func(CheckTypeOp, t, i) - bool TranslatorX64::dontGuardAnyInputs(Op op) { switch (op) { #define CASE(iNm) case Op ## iNm: -#define NOOP(a, b, c) +#define NOOP(...) INSTRS PSEUDOINSTR_DISPATCH(NOOP) return false; @@ -3248,6 +3203,7 @@ TranslatorX64::translateWork(const TranslArgs& args) { auto region = JIT::selectRegion(rContext, &t); TranslateResult result = Retry; + RegionBlacklist regionInterps; while (result == Retry) { traceStart(sk.offset()); @@ -3256,7 +3212,7 @@ TranslatorX64::translateWork(const TranslArgs& args) { if (region) { try { assertCleanState(); - result = translateRegion(*region); + result = translateRegion(*region, regionInterps); FTRACE(2, "translateRegion finished with result {}\n", translateResultName(result)); } catch (const std::exception& e) { @@ -3272,9 +3228,9 @@ TranslatorX64::translateWork(const TranslArgs& args) { if (!region || result == Failure) { FTRACE(1, "trying irTranslateTracelet\n"); assertCleanState(); - static const bool requireRegion = getenv("HHVM_REQUIRE_REGION"); - assert(!requireRegion); result = irTranslateTracelet(*tp); + static const bool requireRegion = getenv("HHVM_REQUIRE_REGION"); + assert(IMPLIES(region && requireRegion, result != Success)); } if (result != Success) { diff --git a/hphp/runtime/vm/jit/translator.cpp b/hphp/runtime/vm/jit/translator.cpp index 0d8b83d41..caa04f141 100644 --- a/hphp/runtime/vm/jit/translator.cpp +++ b/hphp/runtime/vm/jit/translator.cpp @@ -57,6 +57,7 @@ namespace Transl { using namespace HPHP; using HPHP::JIT::Type; +using HPHP::JIT::HhbcTranslator; TRACE_SET_MOD(trans) @@ -343,84 +344,6 @@ Translator::tvToLocation(const TypedValue* tv, const TypedValue* frame) { return Location(Location::Local, offset); } -/* Opcode type-table. */ -enum OutTypeConstraints { - OutNull, - OutNullUninit, - OutString, - OutStringImm, // String w/ precisely known immediate. - OutDouble, - OutBoolean, - OutBooleanImm, - OutInt64, - OutArray, - OutArrayImm, - OutObject, - OutThisObject, // Object from current environment - OutFDesc, // Blows away the current function desc - - OutUnknown, // Not known at tracelet compile-time - OutPred, // Unknown, but give prediction a whirl. - OutCns, // Constant; may be known at compile-time - OutVUnknown, // type is V(unknown) - - OutSameAsInput, // type is the same as the first stack inpute - OutCInput, // type is C(input) - OutVInput, // type is V(input) - OutCInputL, // type is C(type) of local input - OutVInputL, // type is V(type) of local input - OutFInputL, // type is V(type) of local input if current param is - // by ref, else type is C(type) of local input - OutFInputR, // Like FInputL, but for R's on the stack. - - OutArith, // For Add, Sub, Mul - OutBitOp, // For BitAnd, BitOr, BitXor - OutSetOp, // For SetOpL - OutIncDec, // For IncDecL - OutStrlen, // OpStrLen - OutClassRef, // KindOfClass - OutNone -}; - -/* - * Input codes indicate what an instruction reads, and some other - * things about their behavior. The order these show up in the inputs - * vector is given in getInputs(), and is relevant in a few cases - * (e.g. instructions taking both stack inputs and MVectors). - */ -enum Operands { - None = 0, - Stack3 = 1 << 0, - Stack2 = 1 << 1, - Stack1 = 1 << 2, - StackIns1 = 1 << 3, // Insert an element under top of stack - StackIns2 = 1 << 4, // Insert an element under top 2 of stack - FuncdRef = 1 << 5, // Input to FPass* - FStack = 1 << 6, // output of FPushFuncD and friends - Local = 1 << 7, // Writes to a local - MVector = 1 << 8, // Member-vector input - Iter = 1 << 9, // Iterator in imm[0] - AllLocals = 1 << 10, // All locals (used by RetC) - DontGuardLocal = 1 << 11, // Dont force a guard on behalf of the local input - DontGuardStack1 = 1 << 12, // Dont force a guard on behalf of stack1 input - DontBreakLocal = 1 << 13, // Dont break a tracelet on behalf of the local - DontBreakStack1 = 1 << 14, // Dont break a tracelet on behalf of stack1 input - IgnoreInnerType = 1 << 15, // Instruction doesnt care about the inner types - DontGuardAny = 1 << 16, // Dont force a guard for any input - This = 1 << 17, // Input to CheckThis - StackN = 1 << 18, // pop N cells from stack; n = imm[0].u_IVA - BStackN = 1 << 19, // consume N cells from stack for builtin call; - // n = imm[0].u_IVA - StackTop2 = Stack1 | Stack2, - StackTop3 = Stack1 | Stack2 | Stack3, - StackCufSafe = StackIns1 | FStack -}; - -Operands -operator|(const Operands& l, const Operands& r) { - return Operands(int(r) | int(l)); -} - static int64_t typeToMask(DataType t) { return (t == KindOfInvalid) ? 1 : (1 << (1 + getDataTypeIndex(t))); } @@ -772,20 +695,16 @@ static RuntimeType setOpOutputType(NormalizedInstruction* ni, case SetOpSlEqual: case SetOpSrEqual: return RuntimeType(KindOfInt64); default: - assert(false); + not_reached(); } - NOT_REACHED(); - return RuntimeType(KindOfInvalid); } static RuntimeType -getDynLocType(const vector& inputs, - const Tracelet& t, - Op opcode, +getDynLocType(const SrcKey startSk, NormalizedInstruction* ni, - Operands op, - OutTypeConstraints constraint, - DynLocation* outDynLoc) { + InstrFlags::OutTypeConstraints constraint) { + using namespace InstrFlags; + auto const& inputs = ni->inputs; assert(constraint != OutFInputL); switch (constraint) { @@ -803,7 +722,7 @@ getDynLocType(const vector& inputs, CS(OutObject, KindOfObject); #undef CS case OutPred: { - auto dt = predictOutputs(t.m_sk, ni); + auto dt = predictOutputs(startSk, ni); if (dt != KindOfInvalid) ni->outputPredicted = true; return RuntimeType(dt); } @@ -897,54 +816,11 @@ getDynLocType(const vector& inputs, // rhs comes before the M-vector elements. op == OpSetL || op == OpSetN || op == OpSetG || op == OpSetS || op == OpBindL || op == OpBindG || op == OpBindS || op == OpBindN || - op == OpSetM || op == OpBindM || + op == OpBindM || // Dup takes a single element. op == OpDup ); - if (op == OpSetM) { - /* - * SetM returns null for "invalid" inputs, or a string if the - * base was a string. VectorTranslator ensures that invalid - * inputs or a string output when we weren't expecting it will - * cause a side exit, so we can keep this fairly simple. - */ - - if (ni->immVecM.size() > 1) { - // We don't know the type of the base for the final - // operation so we can't assume anything about the output - // type. - return RuntimeType(KindOfAny); - } - - // For single-element vectors, we can determine the output - // type from the base. - Type baseType; - switch (ni->immVec.locationCode()) { - case LGL: case LGC: - case LNL: case LNC: - case LSL: case LSC: - baseType = Type::Gen; - break; - - default: - baseType = Type::fromRuntimeType(inputs[1]->rtt); - } - - const bool setElem = mcodeMaybeArrayOrMapKey(ni->immVecM[0]); - const Type valType = Type::fromRuntimeType(inputs[0]->rtt); - if (setElem && baseType.maybe(Type::Str)) { - if (baseType.isString()) { - // The base is a string so our output is a string. - return RuntimeType(KindOfString); - } else if (!valType.isString()) { - // The base might be a string and our value isn't known to - // be a string. The output type could be Str or valType. - return RuntimeType(KindOfAny); - } - } - } - const int idx = 0; // all currently supported cases. if (debug) { @@ -961,6 +837,51 @@ getDynLocType(const vector& inputs, return inputs[idx]->rtt; } + case OutSetM: { + /* + * SetM returns null for "invalid" inputs, or a string if the base was a + * string. VectorTranslator ensures that invalid inputs or a string + * output when we weren't expecting it will cause a side exit, so we can + * keep this fairly simple. + */ + + if (ni->immVecM.size() > 1) { + // We don't know the type of the base for the final operation so we + // can't assume anything about the output + // type. + return RuntimeType(KindOfAny); + } + + // For single-element vectors, we can determine the output type from the + // base. + Type baseType; + switch (ni->immVec.locationCode()) { + case LGL: case LGC: + case LNL: case LNC: + case LSL: case LSC: + baseType = Type::Gen; + break; + + default: + baseType = Type::fromRuntimeType(inputs[1]->rtt); + } + + const bool setElem = mcodeMaybeArrayOrMapKey(ni->immVecM[0]); + const Type valType = Type::fromRuntimeType(inputs[0]->rtt); + if (setElem && baseType.maybe(Type::Str)) { + if (baseType.isString()) { + // The base is a string so our output is a string. + return RuntimeType(KindOfString); + } else if (!valType.isString()) { + // The base might be a string and our value isn't known to + // be a string. The output type could be Str or valType. + return RuntimeType(KindOfAny); + } + } + + return inputs[0]->rtt; + } + case OutCInputL: { assert(inputs.size() >= 1); const DynLocation* in = inputs[inputs.size() - 1]; @@ -1002,7 +923,7 @@ getDynLocType(const vector& inputs, case OutBitOp: { assert(inputs.size() == 2 || - (inputs.size() == 1 && opcode == OpBitNot)); + (inputs.size() == 1 && ni->op() == OpBitNot)); if (inputs.size() == 2) { return bitOpType(inputs[0], inputs[1]); } else { @@ -1024,13 +945,7 @@ getDynLocType(const vector& inputs, * NB: this opcode structure is sparse; it cannot just be indexed by * opcode. */ -struct InstrInfo { - Operands in; - Operands out; - OutTypeConstraints type; // How are outputs related to inputs? - int stackDelta; // Impact on stack: # cells *pushed* -}; - +using namespace InstrFlags; static const struct { Op op; InstrInfo info; @@ -1200,30 +1115,30 @@ static const struct { { OpSetL, {Stack1|Local, Stack1|Local, OutSameAsInput, 0 }}, { OpSetN, {StackTop2, Stack1|Local, OutSameAsInput, -1 }}, - { OpSetG, {StackTop2, Stack1|Local, OutSameAsInput, -1 }}, + { OpSetG, {StackTop2, Stack1, OutSameAsInput, -1 }}, { OpSetS, {StackTop3, Stack1, OutSameAsInput, -2 }}, - { OpSetM, {MVector|Stack1, Stack1|Local, OutSameAsInput, 0 }}, + { OpSetM, {MVector|Stack1, Stack1|Local, OutSetM, 0 }}, { OpSetWithRefLM,{MVector|Local , Local, OutNone, 0 }}, { OpSetWithRefRM,{MVector|Stack1, Local, OutNone, -1 }}, { OpSetOpL, {Stack1|Local, Stack1|Local, OutSetOp, 0 }}, { OpSetOpN, {StackTop2, Stack1|Local, OutUnknown, -1 }}, - { OpSetOpG, {StackTop2, Stack1|Local, OutUnknown, -1 }}, + { OpSetOpG, {StackTop2, Stack1, OutUnknown, -1 }}, { OpSetOpS, {StackTop3, Stack1, OutUnknown, -2 }}, { OpSetOpM, {MVector|Stack1, Stack1|Local, OutUnknown, 0 }}, { OpIncDecL, {Local, Stack1|Local, OutIncDec, 1 }}, { OpIncDecN, {Stack1, Stack1|Local, OutUnknown, 0 }}, - { OpIncDecG, {Stack1, Stack1|Local, OutUnknown, 0 }}, + { OpIncDecG, {Stack1, Stack1, OutUnknown, 0 }}, { OpIncDecS, {StackTop2, Stack1, OutUnknown, -1 }}, { OpIncDecM, {MVector, Stack1, OutUnknown, 1 }}, { OpBindL, {Stack1|Local| IgnoreInnerType, Stack1|Local, OutSameAsInput, 0 }}, { OpBindN, {StackTop2, Stack1|Local, OutSameAsInput, -1 }}, - { OpBindG, {StackTop2, Stack1|Local, OutSameAsInput, -1 }}, + { OpBindG, {StackTop2, Stack1, OutSameAsInput, -1 }}, { OpBindS, {StackTop3, Stack1, OutSameAsInput, -2 }}, { OpBindM, {MVector|Stack1, Stack1|Local, OutSameAsInput, 0 }}, { OpUnsetL, {Local, Local, OutNone, 0 }}, { OpUnsetN, {Stack1, Local, OutNone, -1 }}, - { OpUnsetG, {Stack1, Local, OutNone, -1 }}, + { OpUnsetG, {Stack1, None, OutNone, -1 }}, { OpUnsetM, {MVector, None, OutNone, 0 }}, /*** 8. Call instructions ***/ @@ -1262,9 +1177,9 @@ static const struct { { OpFPushCufSafe,{StackTop2|DontGuardAny, StackCufSafe, OutFDesc, kNumActRecCells }}, - { OpFPassC, {FuncdRef, None, OutNull, 0 }}, - { OpFPassCW, {FuncdRef, None, OutNull, 0 }}, - { OpFPassCE, {FuncdRef, None, OutNull, 0 }}, + { OpFPassC, {FuncdRef, None, OutSameAsInput, 0 }}, + { OpFPassCW, {FuncdRef, None, OutSameAsInput, 0 }}, + { OpFPassCE, {FuncdRef, None, OutSameAsInput, 0 }}, { OpFPassV, {Stack1|FuncdRef, Stack1, OutUnknown, 0 }}, { OpFPassR, {Stack1|FuncdRef, Stack1, OutFInputR, 0 }}, { OpFPassL, {Local|FuncdRef, Stack1, OutFInputL, 1 }}, @@ -1376,11 +1291,81 @@ static void initInstrInfo() { } } +const InstrInfo& getInstrInfo(Op op) { + assert(instrInfoInited); + return instrInfo[op]; +} + static int numHiddenStackInputs(const NormalizedInstruction& ni) { assert(ni.immVec.isValid()); return ni.immVec.numStackValues(); } +namespace { +int64_t countOperands(uint64_t mask) { + const uint64_t ignore = FuncdRef | Local | Iter | AllLocals | + DontGuardLocal | DontGuardStack1 | DontBreakLocal | DontBreakStack1 | + IgnoreInnerType | DontGuardAny | This; + mask &= ~ignore; + + static const uint64_t counts[][2] = { + {Stack3, 1}, + {Stack2, 1}, + {Stack1, 1}, + {StackIns1, 2}, + {StackIns2, 3}, + {FStack, kNumActRecCells}, + }; + + int64_t count = 0; + for (auto const& pair : counts) { + if (mask & pair[0]) { + count += pair[1]; + mask &= ~pair[0]; + } + } + assert(mask == 0); + return count; +} +} + +int64_t getStackPopped(const NormalizedInstruction& ni) { + switch (ni.op()) { + case OpFCall: return ni.imm[0].u_IVA + kNumActRecCells; + case OpFCallArray: return kNumActRecCells + 1; + + case OpFCallBuiltin: + case OpNewTuple: + case OpCreateCl: return ni.imm[0].u_IVA; + + default: break; + } + + uint64_t mask = getInstrInfo(ni.op()).in; + int64_t count = 0; + + if (mask & MVector) { + count += ni.immVec.numStackValues(); + mask &= ~MVector; + } + if (mask & (StackN | BStackN)) { + count += ni.imm[0].u_IVA; + mask &= ~(StackN | BStackN); + } + + return count + countOperands(mask); +} + +int64_t getStackPushed(const NormalizedInstruction& ni) { + switch (ni.op()) { + case OpFPushCufSafe: return kNumActRecCells + 2; + + default: break; + } + + return countOperands(getInstrInfo(ni.op()).out); +} + int getStackDelta(const NormalizedInstruction& ni) { int hiddenStackInputs = 0; initInstrInfo(); @@ -1404,7 +1389,7 @@ int getStackDelta(const NormalizedInstruction& ni) { hiddenStackInputs = numHiddenStackInputs(ni); SKTRACE(2, ni.source, "Has %d hidden stack inputs\n", hiddenStackInputs); } - int delta = instrInfo[op].stackDelta - hiddenStackInputs; + int delta = instrInfo[op].numPushed - hiddenStackInputs; return delta; } @@ -1957,6 +1942,7 @@ bool outputDependsOnInput(const Op instr) { case OutBitOp: case OutSetOp: case OutIncDec: + case OutSetM: return true; } not_reached(); @@ -1978,7 +1964,7 @@ void Translator::getOutputs(/*inout*/ Tracelet& t, varEnvTaint = false; const vector& inputs = ni->inputs; - Op op = ni->op(); + const Op op = ni->op(); initInstrInfo(); assert_not_implemented(instrInfo.find(op) != instrInfo.end()); @@ -2076,9 +2062,7 @@ void Translator::getOutputs(/*inout*/ Tracelet& t, op == OpSetM || op == OpSetOpM || op == OpBindM || op == OpSetWithRefLM || op == OpSetWithRefRM || - op == OpIncDecL || op == OpIncDecG || - op == OpUnsetG || op == OpBindG || - op == OpSetG || op == OpSetOpG || + op == OpIncDecL || op == OpVGetM || op == OpStaticLocInit || op == OpInitThisLoc || op == OpSetL || op == OpBindL || @@ -2100,11 +2084,6 @@ void Translator::getOutputs(/*inout*/ Tracelet& t, ni->outLocal = incDecLoc; continue; // Doesn't mutate a loc's types for int. Carry on. } - if (op == OpSetG || op == OpSetOpG || - op == OpUnsetG || op == OpBindG || - op == OpIncDecG) { - continue; - } if (op == OpUnsetL) { assert(ni->inputs.size() == 1); DynLocation* inLoc = ni->inputs[0]; @@ -2298,8 +2277,7 @@ void Translator::getOutputs(/*inout*/ Tracelet& t, } DynLocation* dl = t.newDynLocation(); dl->location = loc; - dl->rtt = getDynLocType(inputs, t, op, ni, (Operands)opnd, - typeInfo, dl); + dl->rtt = getDynLocType(t.m_sk, ni, typeInfo); SKTRACE(2, ni->source, "recording output t(%d->%d) #(%s, %d)\n", dl->rtt.outerType(), dl->rtt.innerType(), dl->location.spaceName(), dl->location.offset); @@ -3767,6 +3745,24 @@ void Translator::readMetaData(Unit::MetaHandle& handle, } while (handle.nextArg(info)); } +bool Translator::instrMustInterp(const NormalizedInstruction& inst) { + if (RuntimeOption::EvalJitAlwaysInterpOne) return true; + + switch (inst.op()) { + // Generate a case for each instruction we support at least partially. +# define CASE(name) case Op##name: + INSTRS +# undef CASE +# define NOTHING(...) // PSEUDOINSTR_DISPATCH has the cases in it + PSEUDOINSTR_DISPATCH(NOTHING) +# undef NOTHING + return false; + + default: + return true; + } +} + static Location toLocation(const RegionDesc::Location& loc) { typedef RegionDesc::Location::Tag T; switch (loc.tag()) { @@ -3780,7 +3776,8 @@ static Location toLocation(const RegionDesc::Location& loc) { } Translator::TranslateResult -Translator::translateRegion(const RegionDesc& region) { +Translator::translateRegion(const RegionDesc& region, + RegionBlacklist& toInterp) { typedef JIT::RegionDesc::Block Block; FTRACE(1, "translateRegion starting with:\n{}\n", show(region)); assert(!region.blocks.empty()); @@ -3823,6 +3820,11 @@ Translator::translateRegion(const RegionDesc& region) { inst.changesPC = opcodeChangesPC(inst.op()); populateImmediates(inst); + // We can get a more precise output type for interpOne if we know all of + // its inputs, so we still populate the rest of the instruction even if + // this is true. + inst.interp = toInterp.count(sk); + // Apply the first round of metadata from the repo and get a list of // input locations. InputInfos inputInfos; @@ -3884,7 +3886,15 @@ Translator::translateRegion(const RegionDesc& region) { // Emit IR for the body of the instruction. Util::Nuller niNuller(&m_curNI); m_curNI = &inst; - translateInstr(inst); + try { + translateInstr(inst); + } catch (const JIT::FailedIRGen& exn) { + FTRACE(1, "ir generation for {} failed with {}\n", + inst.toString(), exn.what()); + always_assert(!toInterp.count(sk)); + toInterp.insert(sk); + return Retry; + } // Check the prediction. If the predicted type is less specific than what // is currently on the eval stack, checkTypeLocation won't emit any code. @@ -3901,7 +3911,21 @@ Translator::translateRegion(const RegionDesc& region) { } traceEnd(); - traceCodeGen(); + try { + traceCodeGen(); + } catch (const JIT::FailedCodeGen& exn) { + FTRACE(1, "code generation failed with {}\n", exn.what()); + SrcKey sk{exn.vmFunc, exn.bcOff}; + + // Until we can trust the placement of Marker instructions, we can't assert + // that this sk isn't already in the interp set. t2424830 + if (toInterp.count(sk)) { + return Failure; + } + toInterp.insert(sk); + return Retry; + } + return Success; } diff --git a/hphp/runtime/vm/jit/translator.h b/hphp/runtime/vm/jit/translator.h index 6a9252982..e39823166 100644 --- a/hphp/runtime/vm/jit/translator.h +++ b/hphp/runtime/vm/jit/translator.h @@ -33,9 +33,11 @@ #include "hphp/runtime/base/execution_context.h" #include "hphp/runtime/vm/bytecode.h" #include "hphp/runtime/vm/jit/fixup.h" +#include "hphp/runtime/vm/jit/region_selection.h" #include "hphp/runtime/vm/jit/runtime-type.h" #include "hphp/runtime/vm/jit/srcdb.h" #include "hphp/runtime/vm/jit/trans-data.h" +#include "hphp/runtime/vm/jit/translator-instrs.h" #include "hphp/runtime/vm/jit/type.h" #include "hphp/runtime/vm/jit/writelease.h" #include "hphp/runtime/vm/debugger_hook.h" @@ -47,7 +49,6 @@ namespace HPHP { namespace JIT { class HhbcTranslator; class IRFactory; -class RegionDesc; } namespace Debug { class DebugInfo; @@ -669,159 +670,6 @@ struct TranslArgs { bool m_interp; }; -#define INSTRS \ - CASE(PopC) \ - CASE(PopV) \ - CASE(PopR) \ - CASE(UnboxR) \ - CASE(Null) \ - CASE(NullUninit) \ - CASE(True) \ - CASE(False) \ - CASE(Int) \ - CASE(Double) \ - CASE(String) \ - CASE(Array) \ - CASE(NewArray) \ - CASE(NewTuple) \ - CASE(NewCol) \ - CASE(Nop) \ - CASE(AddElemC) \ - CASE(AddNewElemC) \ - CASE(ColAddElemC) \ - CASE(ColAddNewElemC) \ - CASE(Cns) \ - CASE(DefCns) \ - CASE(ClsCnsD) \ - CASE(Concat) \ - CASE(Add) \ - CASE(Xor) \ - CASE(Not) \ - CASE(Mod) \ - CASE(BitNot) \ - CASE(CastInt) \ - CASE(CastString) \ - CASE(CastDouble) \ - CASE(CastArray) \ - CASE(CastObject) \ - CASE(Print) \ - CASE(Jmp) \ - CASE(Switch) \ - CASE(SSwitch) \ - CASE(RetC) \ - CASE(RetV) \ - CASE(NativeImpl) \ - CASE(AGetC) \ - CASE(AGetL) \ - CASE(CGetL) \ - CASE(CGetL2) \ - CASE(CGetS) \ - CASE(CGetM) \ - CASE(CGetG) \ - CASE(VGetL) \ - CASE(VGetG) \ - CASE(VGetM) \ - CASE(IssetM) \ - CASE(EmptyM) \ - CASE(AKExists) \ - CASE(SetS) \ - CASE(SetG) \ - CASE(SetM) \ - CASE(SetWithRefLM) \ - CASE(SetWithRefRM) \ - CASE(SetOpL) \ - CASE(SetOpM) \ - CASE(IncDecL) \ - CASE(IncDecM) \ - CASE(UnsetL) \ - CASE(UnsetM) \ - CASE(BindM) \ - CASE(FPushFuncD) \ - CASE(FPushFunc) \ - CASE(FPushClsMethodD) \ - CASE(FPushClsMethodF) \ - CASE(FPushObjMethodD) \ - CASE(FPushCtor) \ - CASE(FPushCtorD) \ - CASE(FPassR) \ - CASE(FPassL) \ - CASE(FPassM) \ - CASE(FPassS) \ - CASE(FPassG) \ - CASE(This) \ - CASE(BareThis) \ - CASE(CheckThis) \ - CASE(InitThisLoc) \ - CASE(FCall) \ - CASE(FCallArray) \ - CASE(FCallBuiltin) \ - CASE(VerifyParamType) \ - CASE(InstanceOfD) \ - CASE(StaticLocInit) \ - CASE(IterInit) \ - CASE(IterInitK) \ - CASE(IterNext) \ - CASE(IterNextK) \ - CASE(WIterInit) \ - CASE(WIterInitK) \ - CASE(WIterNext) \ - CASE(WIterNextK) \ - CASE(ReqDoc) \ - CASE(DefCls) \ - CASE(DefFunc) \ - CASE(Self) \ - CASE(Parent) \ - CASE(ClassExists) \ - CASE(InterfaceExists) \ - CASE(TraitExists) \ - CASE(Dup) \ - CASE(CreateCl) \ - CASE(CreateCont) \ - CASE(ContEnter) \ - CASE(ContExit) \ - CASE(UnpackCont) \ - CASE(PackCont) \ - CASE(ContRetC) \ - CASE(ContNext) \ - CASE(ContSend) \ - CASE(ContRaise) \ - CASE(ContValid) \ - CASE(ContCurrent) \ - CASE(ContStopped) \ - CASE(ContHandle) \ - CASE(Strlen) \ - CASE(IncStat) \ - CASE(ArrayIdx) \ - CASE(FPushCufIter) \ - CASE(CIterFree) \ - CASE(LateBoundCls) \ - CASE(IssetS) \ - CASE(IssetG) \ - CASE(UnsetG) \ - CASE(EmptyS) \ - CASE(EmptyG) \ - CASE(VGetS) \ - CASE(BindS) \ - CASE(BindG) \ - CASE(IterFree) \ - CASE(FPassV) \ - CASE(UnsetN) \ - CASE(DecodeCufIter) \ - - // These are instruction-like functions which cover more than one - // opcode. -#define PSEUDOINSTRS \ - CASE(BinaryArithOp) \ - CASE(SameOp) \ - CASE(EqOp) \ - CASE(LtGtOp) \ - CASE(UnaryBooleanOp) \ - CASE(BranchOp) \ - CASE(AssignToLocalOp) \ - CASE(FPushCufOp) \ - CASE(FPassCOp) \ - CASE(CheckTypeOp) - /* * Translator annotates a tracelet with input/output locations/types. */ @@ -907,11 +755,11 @@ protected: virtual void traceCodeGen() = 0; void traceEnd(); void traceFree(); + static bool instrMustInterp(const NormalizedInstruction&); private: void interpretInstr(const NormalizedInstruction& i); void translateInstrWork(const NormalizedInstruction& i); - void translateInstrDefault(const NormalizedInstruction& i); void passPredictedAndInferredTypes(const NormalizedInstruction& i); #define CASE(nm) void translate ## nm(const NormalizedInstruction& i); INSTRS @@ -930,7 +778,13 @@ protected: void requestResetHighLevelTranslator(); void populateImmediates(NormalizedInstruction&); - TranslateResult translateRegion(const RegionDesc& region); + + /* translateRegion reads from the RegionBlacklist to determine when + * to interpret an instruction, and adds failed instructions to the + * blacklist so they're interpreted on the next attempt. */ + typedef hphp_hash_set RegionBlacklist; + TranslateResult translateRegion(const RegionDesc& region, + RegionBlacklist& interp); TCA m_resumeHelper; TCA m_resumeHelperRet; @@ -938,7 +792,7 @@ protected: typedef std::map TransDB; TransDB m_transDB; vector m_translations; - vector m_transCounters; + vector m_transCounters; int64_t m_createdTime; @@ -1115,6 +969,8 @@ public: }; int getStackDelta(const NormalizedInstruction& ni); +int64_t getStackPopped(const NormalizedInstruction&); +int64_t getStackPushed(const NormalizedInstruction&); enum class ControlFlowInfo { None, @@ -1212,6 +1068,94 @@ static inline bool isSmartPtrRef(DataType t) { t == KindOfArray || t == KindOfObject; } +namespace InstrFlags { +enum OutTypeConstraints { + OutNull, + OutNullUninit, + OutString, + OutStringImm, // String w/ precisely known immediate. + OutDouble, + OutBoolean, + OutBooleanImm, + OutInt64, + OutArray, + OutArrayImm, + OutObject, + OutThisObject, // Object from current environment + OutFDesc, // Blows away the current function desc + + OutUnknown, // Not known at tracelet compile-time + OutPred, // Unknown, but give prediction a whirl. + OutCns, // Constant; may be known at compile-time + OutVUnknown, // type is V(unknown) + + OutSameAsInput, // type is the same as the first stack inpute + OutCInput, // type is C(input) + OutVInput, // type is V(input) + OutCInputL, // type is C(type) of local input + OutVInputL, // type is V(type) of local input + OutFInputL, // type is V(type) of local input if current param is + // by ref, else type is C(type) of local input + OutFInputR, // Like FInputL, but for R's on the stack. + + OutArith, // For Add, Sub, Mul + OutBitOp, // For BitAnd, BitOr, BitXor + OutSetOp, // For SetOpL + OutIncDec, // For IncDecL + OutStrlen, // OpStrLen + OutClassRef, // KindOfClass + OutSetM, // SetM is a very special snowflake + OutNone +}; + +/* + * Input codes indicate what an instruction reads, and some other + * things about their behavior. The order these show up in the inputs + * vector is given in getInputs(), and is relevant in a few cases + * (e.g. instructions taking both stack inputs and MVectors). + */ +enum Operands { + None = 0, + Stack3 = 1 << 0, + Stack2 = 1 << 1, + Stack1 = 1 << 2, + StackIns1 = 1 << 3, // Insert an element under top of stack + StackIns2 = 1 << 4, // Insert an element under top 2 of stack + FuncdRef = 1 << 5, // Input to FPass* + FStack = 1 << 6, // output of FPushFuncD and friends + Local = 1 << 7, // Writes to a local + MVector = 1 << 8, // Member-vector input + Iter = 1 << 9, // Iterator in imm[0] + AllLocals = 1 << 10, // All locals (used by RetC) + DontGuardLocal = 1 << 11, // Dont force a guard on behalf of the local input + DontGuardStack1 = 1 << 12, // Dont force a guard on behalf of stack1 input + DontBreakLocal = 1 << 13, // Dont break a tracelet on behalf of the local + DontBreakStack1 = 1 << 14, // Dont break a tracelet on behalf of stack1 input + IgnoreInnerType = 1 << 15, // Instruction doesnt care about the inner types + DontGuardAny = 1 << 16, // Dont force a guard for any input + This = 1 << 17, // Input to CheckThis + StackN = 1 << 18, // pop N cells from stack; n = imm[0].u_IVA + BStackN = 1 << 19, // consume N cells from stack for builtin call; + // n = imm[0].u_IVA + StackTop2 = Stack1 | Stack2, + StackTop3 = Stack1 | Stack2 | Stack3, + StackCufSafe = StackIns1 | FStack +}; + +inline Operands operator|(const Operands& l, const Operands& r) { + return Operands(int(r) | int(l)); +} +} + +struct InstrInfo { + InstrFlags::Operands in; + InstrFlags::Operands out; + InstrFlags::OutTypeConstraints type; // How are outputs related to inputs? + int numPushed; +}; + +const InstrInfo& getInstrInfo(Op op); + } } // HPHP::Transl #endif diff --git a/hphp/runtime/vm/jit/type.cpp b/hphp/runtime/vm/jit/type.cpp index 7621832c5..eb4987643 100644 --- a/hphp/runtime/vm/jit/type.cpp +++ b/hphp/runtime/vm/jit/type.cpp @@ -157,23 +157,6 @@ Type builtinReturn(const IRInstruction* inst) { not_reached(); } -Type boxReturn(const IRInstruction* inst, int srcId) { - auto t = inst->src(srcId)->type(); - // If t contains Uninit, replace it with InitNull. - t = t.maybe(Type::Uninit) ? (t - Type::Uninit) | Type::InitNull : t; - // We don't try to track when a BoxedStaticStr might be converted to - // a BoxedStr, and we never guard on staticness for strings, so - // boxing a string needs to forget this detail. Same thing for - // arrays. - if (t.subtypeOf(Type::Str)) { - t = Type::Str; - } else if (t.subtypeOf(Type::Arr)) { - t = Type::Arr; - } - // Everything else is just a pure type-system boxing operation. - return t.box(); -} - Type stkReturn(const IRInstruction* inst, int dstId, std::function inner) { assert(inst->modifiesStack()); @@ -202,6 +185,22 @@ Type binArithResultType(Opcode op, Type t1, Type t2) { } +Type boxType(Type t) { + // If t contains Uninit, replace it with InitNull. + t = t.maybe(Type::Uninit) ? (t - Type::Uninit) | Type::InitNull : t; + // We don't try to track when a BoxedStaticStr might be converted to + // a BoxedStr, and we never guard on staticness for strings, so + // boxing a string needs to forget this detail. Same thing for + // arrays. + if (t.subtypeOf(Type::Str)) { + t = Type::Str; + } else if (t.subtypeOf(Type::Arr)) { + t = Type::Arr; + } + // Everything else is just a pure type-system boxing operation. + return t.box(); +} + Type outputType(const IRInstruction* inst, int dstId) { #define IRT(name, ...) UNUSED static const Type name = Type::name; IR_TYPES @@ -210,7 +209,7 @@ Type outputType(const IRInstruction* inst, int dstId) { #define D(type) return Type::type; #define DofS(n) return inst->src(n)->type(); #define DUnbox(n) return inst->src(n)->type().unbox(); -#define DBox(n) return boxReturn(inst, n); +#define DBox(n) return boxType(inst->src(n)->type()); #define DParam return inst->typeParam(); #define DMulti return Type::None; #define DStk(in) return stkReturn(inst, dstId, \ diff --git a/hphp/runtime/vm/jit/type.h b/hphp/runtime/vm/jit/type.h index b3d2b31d9..1150f8fad 100644 --- a/hphp/runtime/vm/jit/type.h +++ b/hphp/runtime/vm/jit/type.h @@ -493,6 +493,12 @@ static_assert(sizeof(Type) <= 2 * sizeof(uint64_t), */ Type liveTVType(const TypedValue* tv); +/* + * Return the boxed version of the input type, taking into account php + * semantics and subtle implementation details. + */ +Type boxType(Type); + }} #endif diff --git a/hphp/util/trace.cpp b/hphp/util/trace.cpp index e386df2f1..59eaa7f7d 100644 --- a/hphp/util/trace.cpp +++ b/hphp/util/trace.cpp @@ -86,7 +86,8 @@ class Init { if (mod >= 0) { levels[mod] = level; } - if (mod == Trace::minstr) { + if (mod == Trace::minstr || + mod == Trace::interpOne) { levels[Trace::statgroups] = std::max(levels[Trace::statgroups], 1); } } diff --git a/hphp/util/trace.h b/hphp/util/trace.h index dc7c963c9..3b4a3da5e 100644 --- a/hphp/util/trace.h +++ b/hphp/util/trace.h @@ -72,6 +72,7 @@ namespace Trace { TM(treadmill) \ TM(regalloc) \ TM(bcinterp) \ + TM(interpOne) \ TM(refcount) \ TM(asmx64) \ TM(runtime) \