diff --git a/hphp/doc/ir.specification b/hphp/doc/ir.specification index ff0312c63..c43e213bd 100644 --- a/hphp/doc/ir.specification +++ b/hphp/doc/ir.specification @@ -838,30 +838,45 @@ NewTuple 9. Call & Return -D:ActRec = DefActRec S0:FramePtr - S1:Func - S2:{CtxPtr|ClassPtr|Null} - S3:ConstInt - S4:{String|Null} +D:StkPtr = SpillFrame S0:StkPtr + S1:FramePtr + S1:Func + S2:{Ctx|Class|Null} Operands: - S0 - callee frame pointer - S1 - callee Func - S2 - object (for FPushObjMethod*), class (for FPushClsMethod*), or + S0 - caller stack pointer + S1 - caller frame pointer + S2 - callee Func + S3 - object (for FPushObjMethod*), class (for FPushClsMethod*), or null (for FPushFunc*). - S3 - number of arguments - S4 - invName field for magic calls, or null - Defines the fields for an activation record. May be passed to a - SpillStack to store the fields to the stack. + Defines the fields for an activation record and writes them to the + stack pointed to by S1. D:FramePtr = FreeActRec S0:FramePtr Load the saved frame pointer from the activation record pointed to by S0 into D. -D:StkPtr = Call S0:ActRec S1:ConstInt S2:Func S3... +D:FramePtr = DefInlineFP S0:StkPtr + + Defines a frame pointer for an inlined function. S0 is a StkPtr + that points to the ActRec for the callee (i.e. after parameters have + been popped). + + This instruction is primarily used to represent a frame in the IR in + a way that allows us to eliminate it entirely. When it cannot be + eliminated (or if it is pushed into an unlikely path) it performs + callee-side responsibilities for setting up an activation record + (i.e. setting the return ip and m_soff, storing the frame pointer + into D). + +InlineReturn S0:FramePtr + + Unlinks a frame constructed by DefInlineFP. + +D:StkPtr = Call S0:StkPtr S1:ConstInt S2:Func S3... Invoke the function S2 with ActRec S0 and variadic arguments S3... representing values to pass to the function. A value of type None @@ -988,10 +1003,8 @@ D:StkPtr = SpillStack S0:StkP S1:ConstInt, S2... is pushed S2... - variadic list of elements to spill, with values - representing cells, ActRecs, or None. Each temp of type - ActRec is followed by kSpillStackActRecExtraArgs for - the contents of the ActRec. A temp with type None means - to keep the previous value on the stack. + representing cells. A temp with type None means to + keep the previous value on the stack. 11. Trace exits @@ -1087,12 +1100,51 @@ D:FramePtr = DefFP Creates a temporary D representing the current vm frame pointer. -D:StkPtr = DefSP S0:StkPtr S1:ConstInt +D:StkPtr = DefSP S0:StkPtr Creates a temporary D representing the current vm stack pointer. S0 - is a pointer to the current frame, and S1 is the offset between the - stack pointer and the frame pointer. (XXX: this doesn't work - because of continuations, and is unused.) + is a pointer to the current frame. The 'stackOff' is the logical + offset between S0 and the stack pointer, but in the case of + continuations this is not the physical offset at runtime. + + This instruction is used at the beginning of tracelets to represent + the state of the stack on entry and does not emit code. + +D:StkPtr = ReDefSP S0:FramePtr S1:StkPtr + + Re-define a stack in terms of a frame pointer S0 and an offset, + putting the resulting pointer in D. The resulting stack is assumed + to give the same view as S1, which is a previous stack pointer. + (I.e. for getStackValue we just chain to S1.) + + This instruction is used when entering or "returning" from an + inlined call. The one used on entry will be DCE'd when the actrec + can be eliminated. The one on exit is only needed until TODO(#2288359). + +D:StkPtr = StashGeneratorSP S0:StkPtr + + Store a generator stack pointer, for later use. + + This instruction has exactly the effects of Mov. + + The point of this is a bit of a hack to handle the fact that we + currently assign the same registers to frame (rVmFp) and stack + pointers (rVmSp) through the IR. During an inline call, we want to + be able to have uses of the stack after the call, but we can't + restore it using a frame pointer like with ReDefSP, because the + values of rVmSp and rVmFp are not related like that in a generator. + + This instruction moves the value of rVmSp into a temporary, which we + can get back using ReDefGeneratorSP. + + TODO(#2288359): this shouldn't be needed. + +D:StkPtr = ReDefGeneratorSP S0:StkPtr + + Restore a generator stack pointer. See StashGeneratorSP. + + This instruction has exactly the effects of Mov, except that the + destination is allocated to rVmSp. Nop diff --git a/hphp/runtime/base/runtime_option.h b/hphp/runtime/base/runtime_option.h index a1fea6a20..3004bb984 100644 --- a/hphp/runtime/base/runtime_option.h +++ b/hphp/runtime/base/runtime_option.h @@ -421,6 +421,7 @@ public: F(bool, HHIRExtraOptPass, true) \ F(bool, HHIRMemOpt, true) \ F(uint32_t, HHIRNumFreeRegs, -1) \ + F(bool, HHIREnableGenTimeInlining, true) \ F(bool, HHIREnableRematerialization, true) \ F(bool, HHIREnableCalleeSavedOpt, true) \ F(bool, HHIREnablePreColoring, true) \ diff --git a/hphp/runtime/base/stats.cpp b/hphp/runtime/base/stats.cpp index 52e5ac7be..8e55d1634 100644 --- a/hphp/runtime/base/stats.cpp +++ b/hphp/runtime/base/stats.cpp @@ -119,11 +119,17 @@ void dump() { group.first, url)) << folly::format("{:>45} {:>9} {:>8} {:>8}\n", "name", "count", "% total", "accum %"); + + static const auto maxGroupEnv = getenv("HHVM_STATS_GROUPMAX"); + static const auto maxGroup = maxGroupEnv ? atoi(maxGroupEnv) : INT_MAX; + + int counter = 0; for (auto const& row : rows) { accum += row.second; - stats << folly::format("{:>45} {} {:9} {:8.2%} {:8.2%}\n", + stats << folly::format("{:>70} {} {:9} {:8.2%} {:8.2%}\n", row.first, ':', row.second, (double)row.second / total, (double)accum / total); + if (++counter >= maxGroup) break; } FTRACE(0, "{}\n", stats.str()); } diff --git a/hphp/runtime/vm/bytecode.cpp b/hphp/runtime/vm/bytecode.cpp index edde8b780..859770edd 100644 --- a/hphp/runtime/vm/bytecode.cpp +++ b/hphp/runtime/vm/bytecode.cpp @@ -1645,7 +1645,7 @@ static inline void checkStack(Stack& stk, const Func* f) { // Check whether func's maximum stack usage would overflow the stack. // Both native and VM stack overflows are independently possible. if (!stack_in_bounds(info) || - stk.wouldOverflow(f->maxStackCells())) { + stk.wouldOverflow(f->maxStackCells() + kMaxJITInlineStackCells)) { TRACE(1, "Maximum VM stack depth exceeded.\n"); raise_error("Stack overflow"); } diff --git a/hphp/runtime/vm/bytecode.h b/hphp/runtime/vm/bytecode.h index 8d5e982c6..35ecc19a8 100644 --- a/hphp/runtime/vm/bytecode.h +++ b/hphp/runtime/vm/bytecode.h @@ -415,8 +415,15 @@ struct CallCtx { StringData* invName; }; -static const size_t kNumIterCells = sizeof(Iter) / sizeof(Cell); -static const size_t kNumActRecCells = sizeof(ActRec) / sizeof(Cell); +constexpr size_t kNumIterCells = sizeof(Iter) / sizeof(Cell); +constexpr size_t kNumActRecCells = sizeof(ActRec) / sizeof(Cell); + +/* + * We pad all stack overflow checks by a small amount to allow for + * inlining functions without having to either do another stack check + * or chase down prologues to smash. + */ +constexpr int kMaxJITInlineStackCells = 4 + kNumActRecCells; struct Fault { enum Type : int16_t { diff --git a/hphp/runtime/vm/translator/hopt/codegen.cpp b/hphp/runtime/vm/translator/hopt/codegen.cpp index 30709d3f2..08d494778 100644 --- a/hphp/runtime/vm/translator/hopt/codegen.cpp +++ b/hphp/runtime/vm/translator/hopt/codegen.cpp @@ -266,8 +266,9 @@ ArgDesc::ArgDesc(SSATmp* tmp, bool val) : m_imm(-1), m_zeroExtend(false), } const Func* CodeGenerator::getCurFunc() const { - return m_state.lastMarker ? m_state.lastMarker->func : - m_curTrace->front()->getFunc(); + always_assert(m_state.lastMarker && + "We shouldn't be looking for a func when we have no marker"); + return m_state.lastMarker->func; } Address CodeGenerator::cgInst(IRInstruction* inst) { @@ -306,10 +307,10 @@ NOOP_OPCODE(DefFP) NOOP_OPCODE(DefSP) NOOP_OPCODE(Marker) NOOP_OPCODE(AssertLoc) -NOOP_OPCODE(DefActRec) NOOP_OPCODE(AssertStk) NOOP_OPCODE(Nop) NOOP_OPCODE(DefLabel) +NOOP_OPCODE(ExceptionBarrier) CALL_OPCODE(AddElemStrKey) CALL_OPCODE(AddElemIntKey) @@ -498,10 +499,20 @@ void emitLoadImm(CodeGenerator::Asm& as, int64_t val, PhysReg dstReg) { as.emitImmReg(val, dstReg); } -void emitMovRegReg(CodeGenerator::Asm& as, PhysReg srcReg, PhysReg dstReg) { +static void +emitMovRegReg(CodeGenerator::Asm& as, PhysReg srcReg, PhysReg dstReg) { if (srcReg != dstReg) as.movq(srcReg, dstReg); } +static void emitLea(CodeGenerator::Asm& as, MemoryRef mr, PhysReg dst) { + if (dst == InvalidReg) return; + if (mr.r.disp == 0) { + emitMovRegReg(as, mr.r.base, dst); + } else { + as.lea(mr, dst); + } +} + void shuffle2(CodeGenerator::Asm& a, PhysReg s0, PhysReg s1, PhysReg d0, PhysReg d1) { assert(s0 != s1); @@ -823,9 +834,6 @@ void CodeGenerator::cgCallHelper(Asm& a, // do the call; may use a trampoline m_tx64->emitCall(a, call); - // HHIR:TODO this only does required part of TranslatorX64::recordCallImpl() - // Better to have improved SKTRACE'n by calling recordStubCall, - // recordReentrantCall, or recordReentrantStubCall as appropriate if (sync != kNoSyncPoint) { recordSyncPoint(a, sync); } @@ -2106,14 +2114,55 @@ void CodeGenerator::cgLdSSwitchDestSlow(IRInstruction* inst) { .immPtr(jmptab)); } +/* + * It'd be nice not to have the cgMov here (and just copy propagate + * the source or something), but for now we're keeping it allocated to + * rVmFp so inlined calls to C++ helpers that use the rbp chain to + * find the caller's ActRec will work correctly. + * + * This instruction primarily exists to assist in optimizing away + * unused activation records, so it's usually not going to happen + * anyway. + */ +void CodeGenerator::cgDefInlineFP(IRInstruction* inst) { + auto const fp = inst->getSrc(0)->getReg(); + auto const fakeRet = m_tx64->getRetFromInlinedFrame(); + auto const retBCOff = inst->getExtra()->offset; + + m_as. storeq (fakeRet, fp[AROFF(m_savedRip)]); + m_as. storel (retBCOff, fp[AROFF(m_soff)]); + + cgMov(inst); +} + +void CodeGenerator::cgInlineReturn(IRInstruction* inst) { + auto fpReg = inst->getSrc(0)->getReg(); + assert(fpReg == rVmFp); + m_as. loadq (fpReg[AROFF(m_savedRbp)], rVmFp); +} + +void CodeGenerator::cgReDefSP(IRInstruction* inst) { + // TODO(#2288359): this instruction won't be necessary (for + // non-generator frames) when we don't track rVmSp independently + // from rVmFp. In generator frames we'll have to track offsets from + // a DefGeneratorSP or something similar. + auto fp = inst->getSrc(0)->getReg(); + auto dst = inst->getDst()->getReg(); + auto off = -inst->getExtra()->offset * sizeof(Cell); + emitLea(m_as, fp[off], dst); +} + +void CodeGenerator::cgStashGeneratorSP(IRInstruction* inst) { + cgMov(inst); +} + +void CodeGenerator::cgReDefGeneratorSP(IRInstruction* inst) { + cgMov(inst); +} + void CodeGenerator::cgFreeActRec(IRInstruction* inst) { - SSATmp* outFp = inst->getDst(); - SSATmp* inFp = inst->getSrc(0); - - auto inFpReg = inFp->getReg(); - auto outFpReg = outFp->getReg(); - - m_as.load_reg64_disp_reg64(inFpReg, AROFF(m_savedRbp), outFpReg); + m_as.loadq(inst->getSrc(0)->getReg()[AROFF(m_savedRbp)], + inst->getDst()->getReg()); } void CodeGenerator::cgAllocSpill(IRInstruction* inst) { @@ -2956,22 +3005,15 @@ void CodeGenerator::cgDecRefNZOrBranch(IRInstruction* inst) { cgDecRefWork(inst, true); } -void CodeGenerator::emitSpillActRec(SSATmp* sp, - int64_t spOffset, - SSATmp* defAR) { - if (debug) { - // Ensure srcs of defAR are still live, since we use their registers. - for (SSATmp* UNUSED src : defAR->getInstruction()->getSrcs()) { - assert(src->getInstruction()->getOpcode() == DefConst || - src->getLastUseId() >= m_curInst->getId()); - } - } - auto* defInst = defAR->getInstruction(); - SSATmp* fp = defInst->getSrc(0); - SSATmp* func = defInst->getSrc(1); - SSATmp* objOrCls = defInst->getSrc(2); - SSATmp* nArgs = defInst->getSrc(3); - SSATmp* magicName = defInst->getSrc(4); +void CodeGenerator::cgSpillFrame(IRInstruction* inst) { + auto const sp = inst->getSrc(0); + auto const fp = inst->getSrc(1); + auto const func = inst->getSrc(2); + auto const objOrCls = inst->getSrc(3); + auto const magicName = inst->getExtra()->invName; + auto const nArgs = inst->getExtra()->numArgs; + + const int64_t spOffset = -kNumActRecCells * sizeof(Cell); DEBUG_ONLY bool setThis = true; @@ -2981,27 +3023,27 @@ void CodeGenerator::emitSpillActRec(SSATmp* sp, // store class if (objOrCls->isConst()) { m_as.store_imm64_disp_reg64(uintptr_t(objOrCls->getValClass()) | 1, - spOffset + AROFF(m_this), + spOffset + int(AROFF(m_this)), spReg); } else { Reg64 clsPtrReg = objOrCls->getReg(); m_as.movq (clsPtrReg, rScratch); m_as.orq (1, rScratch); - m_as.storeq(rScratch, spReg[spOffset + AROFF(m_this)]); + m_as.storeq(rScratch, spReg[spOffset + int(AROFF(m_this))]); } } else if (objOrCls->isA(Type::Obj)) { // store this pointer m_as.store_reg64_disp_reg64(objOrCls->getReg(), - spOffset + AROFF(m_this), + spOffset + int(AROFF(m_this)), spReg); } else if (objOrCls->isA(Type::Ctx)) { // Stores either a this pointer or a Cctx -- statically unknown. Reg64 objOrClsPtrReg = objOrCls->getReg(); - m_as.storeq(objOrClsPtrReg, spReg[spOffset + AROFF(m_this)]); + m_as.storeq(objOrClsPtrReg, spReg[spOffset + int(AROFF(m_this))]); } else { assert(objOrCls->isA(Type::InitNull)); // no obj or class; this happens in FPushFunc - int offset_m_this = spOffset + AROFF(m_this); + int offset_m_this = spOffset + int(AROFF(m_this)); // When func is either Type::FuncCls or Type::FuncCtx, // m_this/m_cls will be initialized below if (!func->isConst() && (func->isA(Type::FuncCtx))) { @@ -3012,38 +3054,35 @@ void CodeGenerator::emitSpillActRec(SSATmp* sp, } } // actRec->m_invName - assert(magicName->isConst()); // ActRec::m_invName is encoded as a pointer with bit kInvNameBit // set to distinguish it from m_varEnv and m_extrArgs - uintptr_t invName = - (magicName->getType().isNull() - ? 0 - : (uintptr_t(magicName->getValStr()) | ActRec::kInvNameBit)); + uintptr_t invName = !magicName + ? 0 + : reinterpret_cast(magicName) | ActRec::kInvNameBit; m_as.store_imm64_disp_reg64(invName, - spOffset + AROFF(m_invName), + spOffset + int(AROFF(m_invName)), spReg); // actRec->m_func and possibly actRec->m_cls // Note m_cls is unioned with m_this and may overwrite previous value if (func->getType().isNull()) { assert(func->isConst()); } else if (func->isConst()) { - // TODO: have register allocator materialize constants const Func* f = func->getValFunc(); m_as. mov_imm64_reg((uint64_t)f, rScratch); m_as.store_reg64_disp_reg64(rScratch, - spOffset + AROFF(m_func), + spOffset + int(AROFF(m_func)), spReg); if (func->isA(Type::FuncCtx)) { // Fill in m_cls if provided with both func* and class* CG_PUNT(cgAllocActRec); } } else { - int offset_m_func = spOffset + AROFF(m_func); + int offset_m_func = spOffset + int(AROFF(m_func)); m_as.store_reg64_disp_reg64(func->getReg(0), offset_m_func, spReg); if (func->isA(Type::FuncCtx)) { - int offset_m_cls = spOffset + AROFF(m_cls); + int offset_m_cls = spOffset + int(AROFF(m_cls)); m_as.store_reg64_disp_reg64(func->getReg(1), offset_m_cls, spReg); @@ -3053,14 +3092,17 @@ void CodeGenerator::emitSpillActRec(SSATmp* sp, assert(setThis); // actRec->m_savedRbp m_as.store_reg64_disp_reg64(fp->getReg(), - spOffset + AROFF(m_savedRbp), + spOffset + int(AROFF(m_savedRbp)), spReg); // actRec->m_numArgsAndCtorFlag - assert(nArgs->isConst()); - m_as.store_imm32_disp_reg(nArgs->getValInt(), - spOffset + AROFF(m_numArgsAndCtorFlag), + m_as.store_imm32_disp_reg(nArgs, + spOffset + int(AROFF(m_numArgsAndCtorFlag)), spReg); + + emitAdjustSp(spReg, + inst->getDst()->getReg(), + spOffset); } HOT_FUNC_VM ActRec* @@ -3346,39 +3388,41 @@ void CodeGenerator::cgSpillStack(IRInstruction* inst) { auto const spillCells = spillValueCells(inst); int64_t adjustment = (spDeficit - spillCells) * sizeof(Cell); - for (uint32_t i = 0, cellOff = 0; i < numSpillSrcs; ++i) { - const int64_t offset = cellOff * sizeof(Cell) + adjustment; - if (spillVals[i]->getType() == Type::ActRec) { - emitSpillActRec(sp, offset, spillVals[i]); - cellOff += kNumActRecCells; - i += kSpillStackActRecExtraArgs; - } else if (spillVals[i]->getType() == Type::None) { + for (uint32_t i = 0; i < numSpillSrcs; ++i) { + const int64_t offset = i * sizeof(Cell) + adjustment; + if (spillVals[i]->getType() == Type::None) { // The simplifier detected that we're storing the same value // already in there. - ++cellOff; + continue; + } + + auto* val = spillVals[i]; + auto* inst = val->getInstruction(); + while (inst->isPassthrough()) { + inst = inst->getPassthroughValue()->getInstruction(); + } + // If our value came from a LdStack on the same sp and offset, + // we don't need to spill it. + if (inst->getOpcode() == LdStack && inst->getSrc(0) == sp && + inst->getSrc(1)->getValInt() * sizeof(Cell) == offset) { + FTRACE(1, "{}: Not spilling spill value {} from {}\n", + __func__, i, inst->toString()); } else { - auto* val = spillVals[i]; - auto* inst = val->getInstruction(); - while (inst->isPassthrough()) { - inst = inst->getPassthroughValue()->getInstruction(); - } - // If our value came from a LdStack on the same sp and offset, - // we don't need to spill it. - if (inst->getOpcode() == LdStack && inst->getSrc(0) == sp && - inst->getSrc(1)->getValInt() * sizeof(Cell) == offset) { - FTRACE(1, "{}: Not spilling spill value {} from {}\n", - __func__, i, inst->toString()); - } else { - cgStore(spReg, offset, val); - } - ++cellOff; + cgStore(spReg, offset, val); } } + + emitAdjustSp(spReg, dstReg, adjustment); +} + +void CodeGenerator::emitAdjustSp(PhysReg spReg, + PhysReg dstReg, + int64_t adjustment /* bytes */) { if (adjustment != 0) { if (dstReg != spReg) { - m_as.lea_reg64_disp_reg64(spReg, adjustment, dstReg); + m_as. lea (spReg[adjustment], dstReg); } else { - m_as.add_imm32_reg64(adjustment, dstReg); + m_as. addq (adjustment, dstReg); } } else { emitMovRegReg(m_as, spReg, dstReg); @@ -3732,6 +3776,9 @@ void CodeGenerator::recordSyncPoint(Asm& as, } Offset pcOff = m_state.lastMarker->bcOff - m_state.lastMarker->func->base(); + + FTRACE(3, "IR recordSyncPoint: {} {} {}\n", as.code.frontier, pcOff, + stackOff); m_tx64->recordSyncPoint(as, pcOff, stackOff); } @@ -4847,7 +4894,7 @@ void CodeGenerator::emitTraceCall(CodeGenerator::Asm& as, int64_t pcOff, tx64->emitCall(as, (TCA)traceCallback); } -void patchJumps(Asm& as, CodegenState& state, Block* block) { +static void patchJumps(Asm& as, CodegenState& state, Block* block) { void* list = state.patches[block]; Address labelAddr = as.code.frontier; while (list) { diff --git a/hphp/runtime/vm/translator/hopt/codegen.h b/hphp/runtime/vm/translator/hopt/codegen.h index 5bc0d0dbb..e52b4799c 100644 --- a/hphp/runtime/vm/translator/hopt/codegen.h +++ b/hphp/runtime/vm/translator/hopt/codegen.h @@ -255,9 +255,6 @@ private: PhysReg baseReg, int64_t offset, Block* exit); - void emitSpillActRec(SSATmp* sp, - int64_t spOffset, - SSATmp* defAR); void cgIterNextCommon(IRInstruction* inst, bool isNextK); void cgIterInitCommon(IRInstruction* inst, bool isInitK); @@ -283,6 +280,8 @@ private: int getIterOffset(SSATmp* tmp); void emitReqBindAddr(const Func* func, TCA& dest, Offset offset); + void emitAdjustSp(PhysReg spReg, PhysReg dstReg, int64_t adjustment); + /* * Generate an if-block that branches around some unlikely code, handling * the cases when a == astubs and a != astubs. cc is the branch condition diff --git a/hphp/runtime/vm/translator/hopt/dce.cpp b/hphp/runtime/vm/translator/hopt/dce.cpp index a16ecdd79..2ccf3c0d8 100644 --- a/hphp/runtime/vm/translator/hopt/dce.cpp +++ b/hphp/runtime/vm/translator/hopt/dce.cpp @@ -30,7 +30,12 @@ static const HPHP::Trace::Module TRACEMOD = HPHP::Trace::hhir; /* DceFlags tracks the state of one instruction during dead code analysis. */ struct DceFlags { - DceFlags() : m_state(DEAD), m_decRefNZ(false) {} + DceFlags() + : m_state(DEAD) + , m_weakUseCount(0) + , m_decRefNZ(false) + {} + bool isDead() const { return m_state == DEAD; } bool countConsumed() const { return m_state == REFCOUNT_CONSUMED; } bool countConsumedOffTrace() const { @@ -47,6 +52,27 @@ struct DceFlags { void setCountConsumedOffTrace() { m_state = REFCOUNT_CONSUMED_OFF_TRACE; } void setDecRefNZed() { m_decRefNZ = true; } + /* + * "Weak" uses are used in optimizeActRecs. + * + * If a frame pointer is used for something that can be modified to + * not be a use as long as the whole frame can go away, we'll track + * that here. + */ + void incWeakUse() { + if (m_weakUseCount + 1 > kMaxWeakUseCount) { + always_assert(!"currently there's only one instruction " + "using this machinery, so this shouldn't " + "happen ... "); + return; + } + ++m_weakUseCount; + } + + int32_t weakUseCount() const { + return m_weakUseCount; + } + std::string toString() const { static const char* const names[] = { "DEAD", @@ -76,7 +102,9 @@ private: REFCOUNT_CONSUMED, REFCOUNT_CONSUMED_OFF_TRACE, }; - uint8_t m_state:7; + uint8_t m_state:3; + static constexpr uint8_t kMaxWeakUseCount = 15; + uint8_t m_weakUseCount:4; bool m_decRefNZ:1; }; static_assert(sizeof(DceFlags) == 1, "sizeof(DceFlags) should be 1 byte"); @@ -119,7 +147,7 @@ BlockList removeUnreachable(Trace* trace, IRFactory* factory) { // perform copy propagation on every instruction. Targets that become // unreachable from this pass will be eliminated in step 2 below. forEachTraceInst(trace, [](IRInstruction* inst) { - Simplifier::copyProp(inst); + copyProp(inst); // if this is a load that does not generate a guard, then get rid // of its label so that its not an essential control-flow // instruction @@ -191,6 +219,8 @@ void optimizeRefCount(Trace* trace, DceState& state) { WorkList decrefs; forEachInst(trace, [&](IRInstruction* inst) { if (inst->getOpcode() == IncRef && !state[inst].countConsumedAny()) { + // This assert is often hit when an instruction should have a + // consumesReferences flag but doesn't. auto& s = state[inst]; always_assert_log(s.decRefNZed(), [&]{ Trace* mainTrace = trace->isMain() ? trace : trace->getMain(); @@ -220,7 +250,7 @@ void optimizeRefCount(Trace* trace, DceState& state) { } // Do copyProp at last. When processing DecRefNZs, we still need to look at // its source which should not be trampled over. - Simplifier::copyProp(inst); + copyProp(inst); }); for (const IRInstruction* decref : decrefs) { assert(decref->getOpcode() == DecRef); @@ -256,8 +286,8 @@ void optimizeRefCount(Trace* trace, DceState& state) { void sinkIncRefs(Trace* trace, IRFactory* irFactory, DceState& state) { assert(trace->isMain()); - auto copyProp = [] (Trace* trace) { - forEachInst(trace, Simplifier::copyProp); + auto copyPropTrace = [] (Trace* trace) { + forEachInst(trace, copyProp); }; WorkList toSink; @@ -289,7 +319,7 @@ void sinkIncRefs(Trace* trace, IRFactory* irFactory, DceState& state) { }); // Do copyProp at last, because we need to keep REFCOUNT_CONSUMED_OFF_TRACE // Movs as the prototypes for sunk instructions. - copyProp(exit); + copyPropTrace(exit); }; // An exit trace may be entered from multiple exit points. We keep track of @@ -330,7 +360,100 @@ void sinkIncRefs(Trace* trace, IRFactory* irFactory, DceState& state) { // Do copyProp at last, because we need to keep REFCOUNT_CONSUMED_OFF_TRACE // Movs as the prototypes for sunk instructions. - copyProp(trace); + copyPropTrace(trace); +} + +/* + * Look for InlineReturn instructions that are the only "non-weak" use + * of a DefInlineFP. In this case we can kill both, which may allow + * removing a SpillFrame as well. + */ +void optimizeActRecs(Trace* trace, DceState& state) { + FTRACE(5, "AR:vvvvvvvvvvvvvvvvvvvvv\n"); + SCOPE_EXIT { FTRACE(5, "AR:^^^^^^^^^^^^^^^^^^^^^\n"); }; + + bool killedFrames = false; + + forEachInst(trace, [&](IRInstruction* inst) { + switch (inst->getOpcode()) { + case DecRefKillThis: + { + auto frame = inst->getSrc(1); + auto frameInst = frame->getInstruction(); + if (frameInst->getOpcode() == DefInlineFP) { + FTRACE(5, "DecRefKillThis ({}): weak use of frame {}\n", + inst->getIId(), + frameInst->getIId()); + state[frameInst].incWeakUse(); + } + } + break; + + case InlineReturn: + { + auto frameUses = inst->getSrc(0)->getUseCount(); + auto srcInst = inst->getSrc(0)->getInstruction(); + if (srcInst->getOpcode() == DefInlineFP) { + auto weakUses = state[srcInst].weakUseCount(); + // We haven't counted this InlineReturn as a weak use yet, + // which is where this '1' comes from. + if (frameUses - weakUses == 1) { + FTRACE(5, "killing frame {}\n", srcInst->getIId()); + killedFrames = true; + state[srcInst].setDead(); + } + } + } + break; + + default: + break; + } + }); + + if (!killedFrames) return; + + /* + * The first time through, we've counted up weak uses of the frame + * and then finally marked it dead. The instructions in between + * that were weak uses may need modifications now that their frame + * is going away. + */ + forEachInst(trace, [&](IRInstruction* inst) { + switch (inst->getOpcode()) { + case DecRefKillThis: + { + auto fp = inst->getSrc(1); + if (state[fp->getInstruction()].isDead()) { + FTRACE(5, "DecRefKillThis ({}) -> DecRef\n", inst->getIId()); + inst->setOpcode(DecRef); + inst->setSrc(1, nullptr); + inst->setNumSrcs(1); + } + } + break; + + case InlineReturn: + { + auto fp = inst->getSrc(0); + if (state[fp->getInstruction()].isDead()) { + FTRACE(5, "InlineReturn ({}) setDead\n", inst->getIId()); + state[inst].setDead(); + } + } + break; + + case DefInlineFP: + FTRACE(5, "DefInlineFP ({}): weak/strong uses: {}/{}\n", + inst->getIId(), + state[inst].weakUseCount(), + inst->getDst()->getUseCount()); + break; + + default: + break; + } + }); } // Assuming that the 'consumer' instruction consumes 'src', trace back through @@ -466,6 +589,10 @@ void eliminateDeadCode(Trace* trace, IRFactory* irFactory) { sinkIncRefs(trace, irFactory, state); } + // Optimize unused inlined activation records. It's not necessary + // to look at non-main traces for this. + optimizeActRecs(trace, state); + // now remove instructions whose id == DEAD removeDeadInstructions(trace, state); for (Trace* exit : trace->getExitTraces()) { diff --git a/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp b/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp index e3b2f3a1e..dbe656544 100644 --- a/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp +++ b/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp @@ -159,16 +159,155 @@ void HhbcTranslator::replace(uint32_t index, SSATmp* tmp) { m_evalStack.replace(index, tmp); } -void HhbcTranslator::setBcOff(Offset newOff, bool lastBcOff) { - if (newOff != m_bcOff || m_bcOff == m_startBcOff) { - m_bcOff = newOff; +/* + * When doing gen-time inlining, we set up a series of IR instructions + * that looks like this: + * + * fp0 = DefFP + * sp0 = DefSP + * + * // ... normal stuff happens ... + * // sp_pre = some SpillStack, or maybe the DefSP + * + * // FPI region: + * sp1 = SpillStack sp_pre, ... + * sp2 = SpillFrame sp1, ... + * // ... possibly more spillstacks due to argument expressions + * sp3 = SpillStack sp2, -argCount + * fp2 = DefInlineFP sp2 + * sp4 = ReDefSP fp2 + * + * // ... callee body ... + * + * = InlineReturn fp2 + * + * sp5 = ReDefSP fp0 sp1 + * + * The rest of the code then depends on sp5, and not any of the StkPtr + * tree going through the callee body. The sp5 tmp has the same view + * of the stack as sp1 did, which represents what the stack looks like + * before the return address is pushed but after the activation record + * is popped. + * + * In DCE we attempt to remove the SpillFrame/InlineReturn/DefInlineFP + * instructions if they aren't needed. + */ +void HhbcTranslator::beginInlining(unsigned numParams, + const Func* target, + Offset returnBcOffset) { + assert(!m_fpiStack.empty() && + "Inlining does not support calls with the FPush* in a different Tracelet"); + assert(!target->isGenerator() && "Generator stack handling not implemented"); - MarkerData marker; - marker.bcOff = m_bcOff; - marker.func = getCurFunc(); - marker.stackOff = m_tb->getSpOffset() + - m_evalStack.numCells() - m_stackDeficit; - m_tb->gen(Marker, marker); + FTRACE(1, "[[[ begin inlining: {}\n", target->fullName()->data()); + + { + static const bool enabled = Stats::enabledAny() && + getenv("HHVM_STATS_INLINEFUNC"); + if (enabled) { + m_tb->gen( + IncStatGrouped, + cns(StringData::GetStaticString("HHIRInline")), + cns(target->fullName()), + cns(1) + ); + } + } + + SSATmp* params[numParams]; + for (unsigned i = 0; i < numParams; ++i) { + params[numParams - i - 1] = popF(); + } + + auto prevSP = m_fpiStack.top().first; + auto prevSPOff = m_fpiStack.top().second; + auto calleeSP = spillStack(); + auto calleeFP = m_tb->gen(DefInlineFP, BCOffset(returnBcOffset), calleeSP); + + m_bcStateStack.emplace_back(target->base(), target); + m_tb->beginInlining(target, calleeFP, calleeSP, prevSP, prevSPOff); + profileFunctionEntry("Inline"); + + for (unsigned i = 0; i < numParams; ++i) { + m_tb->setLocalValue(i, params[i]); + } + for (unsigned i = numParams; i < target->numLocals(); ++i) { + /* + * Here we need to be generating hopefully-dead stores to + * initialize non-parameter locals to KindOfUnknownin case we have + * to leave the trace. + */ + always_assert(0 && "unimplemented"); + m_tb->setLocalValue(i, m_tb->genDefUninit()); + } + + emitMarker(); +} + +bool HhbcTranslator::isInlining() const { + return m_bcStateStack.size() > 1; +} + +void HhbcTranslator::emitMarker() { + int32_t stackOff = m_tb->getSpOffset() + + m_evalStack.numCells() - m_stackDeficit; + + FTRACE(2, "emitMarker: bc {} sp {} fn {}\n", + bcOff(), stackOff, getCurFunc()->fullName()->data()); + + MarkerData marker; + marker.bcOff = bcOff(); + marker.func = getCurFunc(); + marker.stackOff = stackOff; + m_tb->gen(Marker, marker); +} + +void HhbcTranslator::profileFunctionEntry(const char* category) { + static const bool enabled = Stats::enabledAny() && + getenv("HHVM_STATS_FUNCENTRY"); + if (!enabled) return; + + m_tb->gen( + IncStatGrouped, + cns(StringData::GetStaticString("FunctionEntry")), + cns(StringData::GetStaticString(category)), + cns(1) + ); +} + +void HhbcTranslator::profileInlineFunctionShape(const std::string& str) { + m_tb->gen( + IncStatGrouped, + cns(StringData::GetStaticString("InlineShape")), + cns(StringData::GetStaticString(str)), + cns(1) + ); +} + +void HhbcTranslator::profileSmallFunctionShape(const std::string& str) { + m_tb->gen( + IncStatGrouped, + cns(StringData::GetStaticString("SmallFunctions")), + cns(StringData::GetStaticString(str)), + cns(1) + ); +} + +void HhbcTranslator::profileFailedInlShape(const std::string& str) { + m_tb->gen( + IncStatGrouped, + cns(StringData::GetStaticString("FailedInl")), + cns(StringData::GetStaticString(str)), + cns(1) + ); +} + +void HhbcTranslator::setBcOff(Offset newOff, bool lastBcOff) { + if (isInlining()) assert(!lastBcOff); + + if (newOff != bcOff()) { + m_bcStateStack.back().bcOff = newOff; + emitMarker(); } m_lastBcOff = lastBcOff; } @@ -213,12 +352,10 @@ void HhbcTranslator::emitUnboxRAux() { } void HhbcTranslator::emitUnboxR() { - TRACE(3, "%u: UnboxR\n", m_bcOff); emitUnboxRAux(); } void HhbcTranslator::emitThis() { - TRACE(3, "%u: This\n", m_bcOff); if (!getCurClass()) { emitInterpOne(Type::Obj, 0); // will throw a fatal return; @@ -227,7 +364,6 @@ void HhbcTranslator::emitThis() { } void HhbcTranslator::emitCheckThis() { - TRACE(3, "%u: CheckThis\n", m_bcOff); if (!getCurClass()) { emitInterpOne(Type::None, 0); // will throw a fatal return; @@ -236,7 +372,6 @@ void HhbcTranslator::emitCheckThis() { } void HhbcTranslator::emitBareThis(int notice) { - TRACE(3, "%u: BareThis %d\n", m_bcOff, notice); // We just exit the trace in the case $this is null. Before exiting // the trace, we could also push null onto the stack and raise a // notice if the notice argument is set. By exiting the trace when @@ -251,13 +386,11 @@ void HhbcTranslator::emitBareThis(int notice) { } void HhbcTranslator::emitArray(int arrayId) { - TRACE(3, "%u: Array %d\n", m_bcOff, arrayId); ArrayData* ad = lookupArrayId(arrayId); push(m_tb->genDefConst(ad)); } void HhbcTranslator::emitNewArray(int capacity) { - TRACE(3, "%u: NewArray %d\n", m_bcOff, capacity); if (capacity == 0) { ArrayData* ad = HphpArray::GetStaticEmptyArray(); push(m_tb->genDefConst(ad)); @@ -272,14 +405,12 @@ void HhbcTranslator::emitNewTuple(int numArgs) { // obtain a pointer to the topmost item; if over-flushing becomes // a problem then we should refactor the NewTuple opcode to take // its values directly as SSA operands. - TRACE(3, "%u: NewTuple %d\n", m_bcOff, numArgs); SSATmp* sp = spillStack(); for (int i = 0; i < numArgs; i++) popC(); push(m_tb->genNewTuple(numArgs, sp)); } void HhbcTranslator::emitArrayAdd() { - TRACE(3, "%u: ArrayAdd\n", m_bcOff); Type type1 = topC(0)->getType(); Type type2 = topC(1)->getType(); if (!type1.isArray() || !type2.isArray()) { @@ -299,7 +430,6 @@ void HhbcTranslator::emitArrayAdd() { } void HhbcTranslator::emitAddElemC() { - TRACE(3, "%u: AddElemC\n", m_bcOff); SSATmp* val = popC(); SSATmp* key = popC(); SSATmp* arr = popC(); @@ -320,7 +450,6 @@ void HhbcTranslator::emitAddElemC() { } void HhbcTranslator::emitAddNewElemC() { - TRACE(3, "%u: AddNewElemC\n", m_bcOff); SSATmp* val = popC(); SSATmp* arr = popC(); // the AddNewElem helper decrefs its args, so don't decref pop'ed values @@ -389,7 +518,6 @@ void HhbcTranslator::emitCns(uint32_t id) { } void HhbcTranslator::emitDefCns(uint32_t id) { - TRACE(3, "%u: DefCns %d\n", m_bcOff, id); StringData* name = lookupStringId(id); SSATmp* val = popC(); push(m_tb->genDefCns(name, val)); @@ -403,12 +531,10 @@ void HhbcTranslator::emitConcat() { } void HhbcTranslator::emitDefCls(int cid, Offset after) { -// m_tb->genDefCls(lookupPreClassId(cid), getCurUnit()->at(after)); emitInterpOneOrPunt(Type::None, 0); } void HhbcTranslator::emitDefFunc(int fid) { -// m_tb->genDefFunc(lookupFuncId(fid)); emitInterpOneOrPunt(Type::None, 0); } @@ -441,47 +567,38 @@ void HhbcTranslator::emitParent() { } void HhbcTranslator::emitString(int strId) { - TRACE(3, "%u: String %d\n", m_bcOff, strId); push(m_tb->genDefConst(lookupStringId(strId))); } void HhbcTranslator::emitInt(int64_t val) { - TRACE(3, "%u: Int %" PRId64 "\n", m_bcOff, val); push(m_tb->genDefConst(val)); } void HhbcTranslator::emitDouble(double val) { - TRACE(3, "%u: Double %f\n", m_bcOff, val); push(m_tb->genDefConst(val)); } void HhbcTranslator::emitNullUninit() { - TRACE(3, "%u: NullUninit\n", m_bcOff); push(m_tb->genDefUninit()); } void HhbcTranslator::emitNull() { - TRACE(3, "%u: Null\n", m_bcOff); push(m_tb->genDefInitNull()); } void HhbcTranslator::emitTrue() { - TRACE(3, "%u: True\n", m_bcOff); push(m_tb->genDefConst(true)); } void HhbcTranslator::emitFalse() { - TRACE(3, "%u: False\n", m_bcOff); push(m_tb->genDefConst(false)); } void HhbcTranslator::emitUninitLoc(uint32_t id) { - TRACE(3, "%u: UninitLoc\n", m_bcOff); m_tb->genInitLoc(id, m_tb->genDefUninit()); } void HhbcTranslator::emitInitThisLoc(int32_t id) { - TRACE(3, "%u: InitThisLoc %d\n", m_bcOff, id); if (!getCurClass()) { // Do nothing if this is null return; @@ -491,13 +608,11 @@ void HhbcTranslator::emitInitThisLoc(int32_t id) { } void HhbcTranslator::emitCGetL(int32_t id) { - TRACE(3, "%u: CGetL %d\n", m_bcOff, id); Trace* exitTrace = getExitTrace(); pushIncRef(emitLdLocWarn(id, exitTrace)); } void HhbcTranslator::emitCGetL2(int32_t id) { - TRACE(3, "%u: CGetL2 %d\n", m_bcOff, id); Trace* exitTrace = getExitTrace(); SSATmp* oldTop = pop(Type::Gen); pushIncRef(emitLdLocWarn(id, exitTrace)); @@ -505,7 +620,6 @@ void HhbcTranslator::emitCGetL2(int32_t id) { } void HhbcTranslator::emitVGetL(int32_t id) { - TRACE(3, "%u: VGetL %d\n", m_bcOff, id); pushIncRef(m_tb->genBoxLoc(id)); } @@ -520,14 +634,12 @@ void HhbcTranslator::emitUnsetG(const StringData* gblName) { } void HhbcTranslator::emitUnsetL(int32_t id) { - TRACE(3, "%u: UnsetL %d\n", m_bcOff, id); m_tb->genBindLoc(id, m_tb->genDefUninit()); } void HhbcTranslator::emitBindL(int32_t id) { - TRACE(3, "%u: BindL %d\n", m_bcOff, id); SSATmp* src = popV(); - if (m_curFunc->isPseudoMain()) { + if (getCurFunc()->isPseudoMain()) { // in pseudo mains, the value of locals could change in functions // called explicitly (or implicitly via exceptions or destructors) // so we need to incref eagerly in case one of these functions @@ -535,13 +647,12 @@ void HhbcTranslator::emitBindL(int32_t id) { pushIncRef(src); } m_tb->genBindLoc(id, src); - if (!m_curFunc->isPseudoMain()) { + if (!getCurFunc()->isPseudoMain()) { pushIncRef(src); } } void HhbcTranslator::emitSetL(int32_t id) { - TRACE(3, "%u: SetL %d\n", m_bcOff, id); Trace* exitTrace = getExitTrace(); SSATmp* src = popC(); // Note we can't use the same trick as emitBindL in which we @@ -603,7 +714,6 @@ static bool isSupportedBinaryArith(Opcode opc, } void HhbcTranslator::emitSetOpL(Opcode subOpc, uint32_t id) { - TRACE(3, "%u: SetOpL %d\n", m_bcOff, id); Trace* exitTrace = getExitTrace(); SSATmp* loc = emitLdLocWarn(id, exitTrace); if (subOpc == Concat) { @@ -673,11 +783,6 @@ void HhbcTranslator::emitStaticLocInit(uint32_t locId, uint32_t litStrId) { void HhbcTranslator::emitReqDoc(const StringData* name) { PUNT(ReqDoc); -// Can't interp one req instructions because their interp one -// function changes the pc, sp, and fp. -// spillStack(); -// popC(); -// emitInterpOne(Type::Cell); } template @@ -742,7 +847,7 @@ void HhbcTranslator::emitCreateCont(bool getArgs, TCA helper = origFunc->isMethod() ? (TCA)&VMExecutionContext::createContinuation : (TCA)&VMExecutionContext::createContinuation; - SSATmp* cont = m_tb->gen(CreateCont, cns(helper), m_tb->getFP(), + SSATmp* cont = m_tb->gen(CreateCont, cns(helper), m_tb->getFp(), cns(getArgs), cns(origFunc), cns(genFunc)); TranslatorX64::ContParamMap params; @@ -765,7 +870,8 @@ void HhbcTranslator::emitCreateCont(bool getArgs, cns(cellsToBytes(genLocals - thisId - 1))); } } else { - m_tb->gen(FillContLocals, m_tb->getFP(), cns(origFunc), cns(genFunc), cont); + m_tb->gen(FillContLocals, m_tb->getFp(), cns(origFunc), + cns(genFunc), cont); } push(cont); @@ -786,7 +892,7 @@ void HhbcTranslator::emitContEnter(int32_t returnBcOffset) { contAR, funcBody, cns(returnBcOffset), - m_tb->getFP() + m_tb->getFp() ); assert(m_stackDeficit == 0); } @@ -795,7 +901,7 @@ void HhbcTranslator::emitContExitImpl() { SSATmp* retAddr = m_tb->genLdRetAddr(); // Despite the name, this doesn't actually free the AR; it updates the // hardware fp and returns the old one - SSATmp* fp = m_tb->genFreeActRec(); + SSATmp* fp = m_tb->gen(FreeActRec, m_tb->getFp()); SSATmp* sp; if (m_stackDeficit) { @@ -928,7 +1034,6 @@ void HhbcTranslator::emitStrlen() { } void HhbcTranslator::emitIncStat(int32_t counter, int32_t value, bool force) { - TRACE(3, "%u: IncStat %d %d\n", m_bcOff, counter, value); if (Stats::enabled() || force) { m_tb->genIncStat(counter, value, force); } @@ -951,7 +1056,7 @@ SSATmp* HhbcTranslator::getStrName(const StringData* knownName) { SSATmp* HhbcTranslator::emitLdClsPropAddrOrExit(const StringData* propName, Block* block) { - if (!block) spillStack(); + if (!block) exceptionBarrier(); SSATmp* clsTmp = popA(); SSATmp* prop = getStrName(propName); @@ -1012,8 +1117,6 @@ void HhbcTranslator::emitMInstr(const NormalizedInstruction& ni) { * Unboxes var if necessary when var is not uninit. */ void HhbcTranslator::emitIssetL(int32_t id) { - TRACE(3, "%u: IssetL %d\n", m_bcOff, id); - Type trackedType = m_tb->getLocalType(id); // guards should ensure we have type info at this point assert(trackedType != Type::None); @@ -1027,22 +1130,18 @@ void HhbcTranslator::emitIssetL(int32_t id) { } void HhbcTranslator::emitIssetG(const StringData* gblName) { - TRACE(3, "%u: IssetG\n", m_bcOff); emitIsset(gblName, &HhbcTranslator::checkSupportedGblName, &HhbcTranslator::emitLdGblAddr); } void HhbcTranslator::emitIssetS(const StringData* propName) { - TRACE(3, "%u: IssetS\n", m_bcOff); emitIsset(propName, &HhbcTranslator::checkSupportedClsProp, &HhbcTranslator::emitLdClsPropAddrOrExit); } void HhbcTranslator::emitEmptyL(int32_t id) { - TRACE(3, "%u: EmptyL %d\n", m_bcOff, id); - Type trackedType = m_tb->getLocalType(id); assert(trackedType != Type::None); if (trackedType == Type::Uninit) { @@ -1055,28 +1154,24 @@ void HhbcTranslator::emitEmptyL(int32_t id) { } void HhbcTranslator::emitEmptyG(const StringData* gblName) { - TRACE(3, "%u: EmptyG\n", m_bcOff); emitEmpty(gblName, &HhbcTranslator::checkSupportedGblName, &HhbcTranslator::emitLdGblAddr); } void HhbcTranslator::emitEmptyS(const StringData* propName) { - TRACE(3, "%u: EmptyS\n", m_bcOff); emitEmpty(propName, &HhbcTranslator::checkSupportedClsProp, &HhbcTranslator::emitLdClsPropAddrOrExit); } void HhbcTranslator::emitIsTypeC(Type t) { - TRACE(3, "%u: Is%sC\n", m_bcOff, t.toString().c_str()); SSATmp* src = popC(); push(m_tb->gen(IsType, t, src)); m_tb->genDecRef(src); } void HhbcTranslator::emitIsTypeL(Type t, int id) { - TRACE(3, "%u: Is%sH\n", m_bcOff, t.toString().c_str()); Trace* exitTrace = getExitTrace(); push(m_tb->gen(IsType, t, emitLdLocWarn(id, exitTrace))); } @@ -1097,31 +1192,26 @@ void HhbcTranslator::emitIsBoolC() { emitIsTypeC(Type::Bool);} void HhbcTranslator::emitIsDoubleC() { emitIsTypeC(Type::Dbl); } void HhbcTranslator::emitPopC() { - TRACE(3, "%u: PopC\n", m_bcOff); popDecRef(Type::Cell); } void HhbcTranslator::emitPopV() { - TRACE(3, "%u: PopV\n", m_bcOff); popDecRef(Type::BoxedCell); } void HhbcTranslator::emitPopR() { - TRACE(3, "%u: PopR\n", m_bcOff); popDecRef(Type::Gen); } void HhbcTranslator::emitDup() { - TRACE(3, "%u: Dup\n", m_bcOff); pushIncRef(topC()); } void HhbcTranslator::emitJmp(int32_t offset, bool breakTracelet, bool noSurprise) { - TRACE(3, "%u: Jmp %d\n", m_bcOff, offset); // If surprise flags are set, exit trace and handle surprise - bool backward = (offset - (int32_t)m_bcOff) < 0; + bool backward = (offset - (int32_t)bcOff()) < 0; if (backward && !noSurprise) { m_tb->genExitWhenSurprised(getExitSlowTrace()); } @@ -1146,22 +1236,18 @@ SSATmp* HhbcTranslator::emitJmpCondHelper(int32_t offset, } void HhbcTranslator::emitJmpZ(int32_t offset) { - TRACE(3, "%u: JmpZ %d\n", m_bcOff, offset); SSATmp* src = popC(); emitJmpCondHelper(offset, true, src); } void HhbcTranslator::emitJmpNZ(int32_t offset) { - TRACE(3, "%u: JmpNZ %d\n", m_bcOff, offset); SSATmp* src = popC(); emitJmpCondHelper(offset, false, src); } void HhbcTranslator::emitCmp(Opcode opc) { - TRACE(3, "%u: Cmp %s\n", m_bcOff, opcodeName(opc)); - if (cmpOpTypesMayReenter(opc, topC(0)->getType(), topC(1)->getType())) { - spillStack(); + exceptionBarrier(); } // src2 opc src1 SSATmp* src1 = popC(); @@ -1176,8 +1262,6 @@ void HhbcTranslator::emitClsCnsD(int32_t cnsNameStrId, int32_t clsNameStrId) { // and can throw a fatal error. const StringData* cnsNameStr = lookupStringId(cnsNameStrId); const StringData* clsNameStr = lookupStringId(clsNameStrId); - TRACE(3, "%u: ClsCnsD %s::%s\n", m_bcOff, clsNameStr->data(), - cnsNameStr->data()); SSATmp* cnsNameTmp = m_tb->genDefConst(cnsNameStr); SSATmp* clsNameTmp = m_tb->genDefConst(clsNameStr); if (0) { @@ -1190,7 +1274,7 @@ void HhbcTranslator::emitClsCnsD(int32_t cnsNameStrId, int32_t clsNameStrId) { // if-then-else // todo: t2068502: refine the type? hhbc spec says null|bool|int|dbl|str // and, str should always be static-str. - spillStack(); // do this on main trace so we update stack tracking once. + exceptionBarrier(); // do on main trace so we update stack tracking once. Type cnsType = Type::Cell; SSATmp* c1 = m_tb->gen(LdClsCns, cnsType, cnsNameTmp, clsNameTmp); SSATmp* result = m_tb->cond(getCurFunc(), @@ -1210,7 +1294,6 @@ void HhbcTranslator::emitClsCnsD(int32_t cnsNameStrId, int32_t clsNameStrId) { } void HhbcTranslator::emitAKExists() { - TRACE(3, "%u: AKExists\n", m_bcOff); SSATmp* arr = popC(); SSATmp* key = popC(); @@ -1227,16 +1310,13 @@ void HhbcTranslator::emitAKExists() { } void HhbcTranslator::emitFPassR() { - TRACE(3, "%u: FPassR\n", m_bcOff); emitUnboxRAux(); } void HhbcTranslator::emitFPassCOp() { - TRACE(3, "%u: FPassCOp\n", m_bcOff); } void HhbcTranslator::emitFPassV() { - TRACE(3, "%u: FPassV\n", m_bcOff); Block* exit = getExitTrace()->front(); SSATmp* tmp = popV(); pushIncRef(m_tb->gen(Unbox, exit, tmp)); @@ -1294,11 +1374,10 @@ void HhbcTranslator::emitFPushCufOp(VM::Op op, Class* cls, StringData* invName, } void HhbcTranslator::emitNativeImpl() { - TRACE(3, "%u: NativeImpl\n", m_bcOff); m_tb->genNativeImpl(); SSATmp* sp = m_tb->genRetAdjustStack(); SSATmp* retAddr = m_tb->genLdRetAddr(); - SSATmp* fp = m_tb->genFreeActRec(); // updates fp + SSATmp* fp = m_tb->gen(FreeActRec, m_tb->getFp()); m_tb->genRetCtrl(sp, fp, retAddr); // Flag that this trace has a Ret instruction so no ExitTrace is needed @@ -1309,22 +1388,61 @@ void HhbcTranslator::emitFPushActRec(SSATmp* func, SSATmp* objOrClass, int32_t numArgs, const StringData* invName) { - SSATmp* actRec = m_tb->genDefActRec(func, objOrClass, numArgs, invName); - m_evalStack.push(actRec); - spillStack(); // TODO(#2036900) + /* + * Before allocating an ActRec, we do a spillStack so we'll have a + * StkPtr that represents what the stack will look like after the + * ActRec is popped. + */ + auto actualStack = spillStack(); + auto returnSp = actualStack; + + /* + * XXX. In a generator, we can't use ReDefSP to restore the stack + * pointer from the frame pointer if we inline the callee. (This is + * because we don't really pay attention to usedefs for allocating + * registers to stack pointers, and rVmFp and rVmSp are not related + * to each other in a generator frame.) + * + * Instead, save it somewhere so we can move it back after. This + * instruction will be dce'd if we don't inline the callee. + * + * TODO(#2288359): freeing up the special-ness of %rbx should + * allow us to avoid this sort of thing. + */ + if (getCurFunc()->isGenerator()) { + returnSp = m_tb->gen(StashGeneratorSP, m_tb->getSp()); + } + + m_fpiStack.emplace(returnSp, m_tb->getSpOffset()); + + ActRecInfo info; + info.numArgs = numArgs; + info.invName = invName; + m_tb->gen( + SpillFrame, + info, + // Using actualStack instead of returnSp so SpillFrame still gets + // the src in rVmSp. (TODO(#2288359).) + actualStack, + m_tb->getFp(), + func, + objOrClass + ); + assert(m_stackDeficit == 0); } void HhbcTranslator::emitFPushCtor(int32_t numParams) { - TRACE(3, "%u: FPushFuncCtor %d\n", m_bcOff, numParams); SSATmp* cls = popA(); - spillStack(); + exceptionBarrier(); m_tb->genNewObj(numParams, cls); + m_fpiStack.emplace(nullptr, 0); } void HhbcTranslator::emitFPushCtorD(int32_t numParams, int32_t classNameStrId) { const StringData* className = lookupStringId(classNameStrId); - TRACE(3, "%u: FPushFuncCtorD %d %s\n", m_bcOff, numParams, className->data()); - spillStack(); + exceptionBarrier(); + m_fpiStack.emplace(nullptr, 0); + // If constructor is the generated 86ctor, no need to call it. if (RuntimeOption::RepoAuthoritative && numParams == 0) { @@ -1370,7 +1488,6 @@ void HhbcTranslator::emitCreateCl(int32_t numParams, int32_t funNameStrId) { } void HhbcTranslator::emitFPushFuncD(int32_t numParams, int32_t funcId) { - TRACE(3, "%u: FPushFuncD %d %d\n", m_bcOff, numParams, funcId); const NamedEntityPair& nep = lookupNamedEntityPairId(funcId); const StringData* name = nep.first; const Func* func = Unit::lookupFunc(nep.second, name); @@ -1384,7 +1501,7 @@ void HhbcTranslator::emitFPushFuncD(int32_t numParams, int32_t funcId) { const bool immutable = func->isNameBindingImmutable(getCurUnit()); if (!immutable) { - spillStack(); // LdFuncCached can reenter + exceptionBarrier(); // LdFuncCached can reenter } SSATmp* ssaFunc = immutable ? m_tb->genDefConst(func) : m_tb->gen(LdFuncCached, m_tb->genDefConst(name)); @@ -1395,7 +1512,6 @@ void HhbcTranslator::emitFPushFuncD(int32_t numParams, int32_t funcId) { } void HhbcTranslator::emitFPushFunc(int32_t numParams) { - TRACE(3, "%u: FPushFuncD %d\n", m_bcOff, numParams); // input must be a string or an object implementing __invoke(); // otherwise fatal SSATmp* funcName = popC(); @@ -1406,7 +1522,7 @@ void HhbcTranslator::emitFPushFunc(int32_t numParams) { } void HhbcTranslator::emitFPushFunc(int32_t numParams, SSATmp* funcName) { - spillStack(); // LdFunc can reenter + exceptionBarrier(); // LdFunc can reenter emitFPushActRec(m_tb->gen(LdFunc, funcName), m_tb->genDefInitNull(), numParams, @@ -1417,10 +1533,6 @@ void HhbcTranslator::emitFPushObjMethodD(int32_t numParams, int32_t methodNameStrId, const Class* baseClass) { const StringData* methodName = lookupStringId(methodNameStrId); - TRACE(3, "%u: FPushObjMethodD %s %d\n", - m_bcOff, - methodName->data(), - numParams); bool magicCall = false; const Func* func = HPHP::VM::Transl::lookupImmutableMethod(baseClass, methodName, @@ -1485,10 +1597,11 @@ void HhbcTranslator::emitFPushObjMethodD(int32_t numParams, obj, numParams, nullptr); - SSATmp* actRec = spillStack(); + auto const actRec = spillStack(); + auto const objCls = m_tb->gen(LdObjClass, obj); m_tb->gen(LdObjMethod, - m_tb->gen(LdObjClass, obj), - m_tb->genDefConst(methodName), + objCls, + cns(methodName), actRec); } } @@ -1525,8 +1638,6 @@ void HhbcTranslator::emitFPushClsMethodD(int32_t numParams, const StringData* methodName = lookupStringId(methodNameStrId); const NamedEntityPair& np = lookupNamedEntityPairId(clssNamedEntityPairId); const StringData* className = np.first; - TRACE(3, "%u: FPushClsMethodD %s::%s %d\n", m_bcOff, className->data(), - methodName->data(), numParams); const Class* baseClass = Unit::lookupUniqueClass(np.second); bool magicCall = false; const Func* func = HPHP::VM::Transl::lookupImmutableMethod(baseClass, @@ -1609,6 +1720,10 @@ void HhbcTranslator::emitFCall(uint32_t numParams, func, numParams, params); + + if (!m_fpiStack.empty()) { + m_fpiStack.pop(); + } } void HhbcTranslator::emitFCallBuiltin(uint32_t numArgs, @@ -1625,7 +1740,7 @@ void HhbcTranslator::emitFCallBuiltin(uint32_t numArgs, // 2. type conversions of the arguments (using tvCast* helpers) // may throw an exception, so we need to have the VM stack // in a clean state at that point. - spillStack(); + exceptionBarrier(); // Convert types if needed for (int i = 0; i < numNonDefault; i++) { const Func::ParamInfo& pi = callee->params()[i]; @@ -1679,11 +1794,86 @@ void HhbcTranslator::emitFCallBuiltin(uint32_t numArgs, push(ret); } +static bool mayHaveThis(const Func* func) { + return func->isPseudoMain() || (func->isMethod() && !func->isStatic()); +} + +void HhbcTranslator::emitRetFromInlined(Type type) { + SSATmp* retVal = pop(type); + + assert(!(getCurFunc()->attrs() & AttrMayUseVV)); + assert(!getCurFunc()->isPseudoMain()); + assert(!m_fpiStack.empty()); + + emitDecRefLocalsInline(retVal); + + /* + * Pop the ActRec and restore the stack and frame pointers. It's + * important that this does endInlining before pushing the return + * value so stack offsets are properly tracked. + */ + m_tb->endInlining(); + FTRACE(1, "]]] end inlining: {}\n", getCurFunc()->fullName()->data()); + m_bcStateStack.pop_back(); + m_fpiStack.pop(); + push(retVal); + + emitMarker(); +} + +/* + * In case retVal comes from a local, the logic below tweaks the code + * so that retVal is DecRef'd and the corresponding local's SSATmp is + * returned. This enables the ref-count optimization to eliminate the + * IncRef/DecRef pair in the main trace. + */ +SSATmp* HhbcTranslator::emitDecRefLocalsInline(SSATmp* retVal) { + SSATmp* retValSrcLoc = nullptr; + Opcode retValSrcOpc = Nop; // Nop flags the ref-count opt is impossible + IRInstruction* retValSrcInstr = retVal->getInstruction(); + if (retValSrcInstr->getOpcode() == IncRef) { + retValSrcLoc = retValSrcInstr->getSrc(0); + retValSrcOpc = retValSrcLoc->getInstruction()->getOpcode(); + if (retValSrcOpc != LdLoc && retValSrcOpc != LdThis) { + retValSrcLoc = nullptr; + retValSrcOpc = Nop; + } + } + + if (mayHaveThis(getCurFunc())) { + if (retValSrcLoc && retValSrcOpc == LdThis) { + m_tb->genDecRef(retVal); + } else { + m_tb->genDecRefThis(); + } + } + + /* + * Note: this is currently off for isInlining() because the shuffle + * was preventing a decref elimination due to ordering. Currently + * we don't inline anything with parameters, though, so it doesn't + * matter. This will need to be revisted then. + */ + int retValLocId = (!isInlining() && retValSrcLoc && retValSrcOpc == LdLoc) ? + retValSrcLoc->getInstruction()->getExtra()->locId : -1; + for (int id = getCurFunc()->numLocals() - 1; id >= 0; --id) { + if (retValLocId == id) { + m_tb->genDecRef(retVal); + } else { + m_tb->genDecRefLoc(id); + } + } + + return retValSrcLoc ? retValSrcLoc : retVal; +} + void HhbcTranslator::emitRet(Type type, bool freeInline) { + if (isInlining()) { + return emitRetFromInlined(type); + } + const Func* curFunc = getCurFunc(); bool mayUseVV = (curFunc->attrs() & AttrMayUseVV); - bool mayHaveThis = (curFunc->isPseudoMain() || - (curFunc->isMethod() && !curFunc->isStatic())); m_tb->genExitWhenSurprised(getExitSlowTrace()); if (mayUseVV) { @@ -1696,45 +1886,11 @@ void HhbcTranslator::emitRet(Type type, bool freeInline) { SSATmp* sp; if (freeInline) { - /* - * In case retVal comes from a local, the logic below tweaks the code - * so that retVal is DecRef'd and the corresponding local's SSATmp is - * returned. This enables the ref-count optimization to eliminate the - * IncRef/DecRef pair in the main trace. - */ - SSATmp* retValSrcLoc = nullptr; - Opcode retValSrcOpc = Nop; // Nop flags the ref-count opti is impossible - IRInstruction* retValSrcInstr = retVal->getInstruction(); - if (retValSrcInstr->getOpcode() == IncRef) { - retValSrcLoc = retValSrcInstr->getSrc(0); - retValSrcOpc = retValSrcLoc->getInstruction()->getOpcode(); - if (retValSrcOpc != LdLoc && retValSrcOpc != LdThis) { - retValSrcLoc = nullptr; - retValSrcOpc = Nop; - } - } - - if (mayHaveThis) { - if (retValSrcLoc && retValSrcOpc == LdThis) { - m_tb->genDecRef(retVal); - } else { - m_tb->genDecRefThis(); - } - } - - int retValLocId = (retValSrcLoc && retValSrcOpc == LdLoc) ? - retValSrcLoc->getInstruction()->getExtra()->locId : -1; - for (int id = curFunc->numLocals() - 1; id >= 0; --id) { - if (retValLocId == id) { - m_tb->genDecRef(retVal); - } else { - m_tb->genDecRefLoc(id); - } - } - m_tb->genRetVal(retValSrcLoc ? retValSrcLoc : retVal); + SSATmp* useRet = emitDecRefLocalsInline(retVal); + m_tb->genRetVal(useRet); sp = m_tb->genRetAdjustStack(); } else { - if (mayHaveThis) { + if (mayHaveThis(curFunc)) { m_tb->genDecRefThis(); } sp = m_tb->genGenericRetDecRefs(retVal, curFunc->numLocals()); @@ -1743,7 +1899,7 @@ void HhbcTranslator::emitRet(Type type, bool freeInline) { // Free ActRec, and return control to caller. SSATmp* retAddr = m_tb->genLdRetAddr(); - SSATmp* fp = m_tb->genFreeActRec(); + SSATmp* fp = m_tb->gen(FreeActRec, m_tb->getFp()); m_tb->genRetCtrl(sp, fp, retAddr); // Flag that this trace has a Ret instruction, so that no ExitTrace is needed @@ -1763,10 +1919,10 @@ void HhbcTranslator::emitSwitch(const ImmVector& iv, SSATmp* ssabase = m_tb->genDefConst(base); SSATmp* ssatargets = m_tb->genDefConst(nTargets); - Offset defaultOff = m_bcOff + iv.vec32()[iv.size() - 1]; + Offset defaultOff = bcOff() + iv.vec32()[iv.size() - 1]; Offset zeroOff = 0; if (base <= 0 && (base + nTargets) > 0) { - zeroOff = m_bcOff + iv.vec32()[0 - base]; + zeroOff = bcOff() + iv.vec32()[0 - base]; } else { zeroOff = defaultOff; } @@ -1775,7 +1931,7 @@ void HhbcTranslator::emitSwitch(const ImmVector& iv, m_tb->genJmp(getExitTrace(zeroOff)); return; } else if (type.subtypeOf(Type::Bool)) { - Offset nonZeroOff = m_bcOff + iv.vec32()[iv.size() - 2]; + Offset nonZeroOff = bcOff() + iv.vec32()[iv.size() - 2]; m_tb->genJmpCond(switchVal, getExitTrace(nonZeroOff), false); m_tb->genJmp(getExitTrace(zeroOff)); return; @@ -1796,7 +1952,7 @@ void HhbcTranslator::emitSwitch(const ImmVector& iv, } else if (type.subtypeOf(Type::Obj)) { // switchObjHelper can throw exceptions and reenter the VM if (type.subtypeOf(Type::Obj)) { - spillStack(); + exceptionBarrier(); } bounded = false; index = m_tb->gen(LdSwitchObjIndex, @@ -1811,7 +1967,7 @@ void HhbcTranslator::emitSwitch(const ImmVector& iv, std::vector targets(iv.size()); for (int i = 0; i < iv.size(); i++) { - targets[i] = m_bcOff + iv.vec32()[i]; + targets[i] = bcOff() + iv.vec32()[i]; } JmpSwitchData data; @@ -1822,7 +1978,7 @@ void HhbcTranslator::emitSwitch(const ImmVector& iv, data.defaultOff = defaultOff; data.targets = &targets[0]; - SSATmp* stack = spillStack(); + auto const stack = spillStack(); m_tb->gen(SyncVMRegs, m_tb->getFp(), stack); m_tb->gen(JmpSwitchDest, data, index); @@ -1848,31 +2004,30 @@ void HhbcTranslator::emitSSwitch(const ImmVector& iv) { ); // The slow path can throw exceptions and reenter the VM. - if (!fastPath) spillStack(); + if (!fastPath) exceptionBarrier(); SSATmp* const testVal = popC(); - assert(m_bcOff != -1); - auto const bcOff = m_bcOff; + assert(bcOff() != -1); std::vector cases(numCases); for (int i = 0; i < numCases; ++i) { auto const& kv = iv.strvec()[i]; cases[i].str = getCurUnit()->lookupLitstrId(kv.str); - cases[i].dest = bcOff + kv.dest; + cases[i].dest = bcOff() + kv.dest; } LdSSwitchData data; data.func = getCurFunc(); data.numCases = numCases; data.cases = &cases[0]; - data.defaultOff = bcOff + iv.strvec()[iv.size() - 1].dest; + data.defaultOff = bcOff() + iv.strvec()[iv.size() - 1].dest; SSATmp* dest = m_tb->gen(fastPath ? LdSSwitchDestFast : LdSSwitchDestSlow, data, testVal); m_tb->genDecRef(testVal); - SSATmp* stack = spillStack(); + auto const stack = spillStack(); m_tb->gen(SyncVMRegs, m_tb->getFp(), stack); m_tb->gen(JmpIndirect, dest); m_hasExit = true; @@ -2024,7 +2179,7 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) { return; } if (tc.isCallable()) { - spillStack(); + exceptionBarrier(); locVal = m_tb->gen(Unbox, getExitTrace(), locVal); m_tb->gen(VerifyParamCallable, locVal, cns(paramId)); return; @@ -2034,7 +2189,7 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) { // guards and never have to do runtime checks. if (!tc.isObjectOrTypedef()) { if (!tc.checkPrimitive(locType.toDataType())) { - spillStack(); + exceptionBarrier(); m_tb->gen(VerifyParamFail, cns(paramId)); return; } @@ -2070,7 +2225,7 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) { } else { // The hint was self or parent and there's no corresponding // class for the current func. This typehint will always fail. - spillStack(); + exceptionBarrier(); m_tb->gen(VerifyParamFail, cns(paramId)); return; } @@ -2091,18 +2246,18 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) { SSATmp* isInstance = haveBit ? m_tb->gen(InstanceOfBitmask, objClass, cns(clsName)) : m_tb->gen(ExtendsClass, objClass, constraint); + exceptionBarrier(); m_tb->ifThen(getCurFunc(), [&](Block* taken) { m_tb->gen(JmpZero, taken, isInstance); }, [&] { // taken: the param type does not match m_tb->hint(Block::Unlikely); - spillStack(); m_tb->gen(VerifyParamFail, cns(paramId)); } ); } else { - spillStack(); + exceptionBarrier(); m_tb->gen(VerifyParamCls, objClass, constraint, @@ -2229,10 +2384,10 @@ void HhbcTranslator::emitCastDouble() { } else if (fromType.isString()) { push(m_tb->gen(ConvStrToDbl, src)); } else if (fromType.isObj()) { - spillStack(); // may throw + exceptionBarrier(); push(m_tb->gen(ConvObjToDbl, src)); } else { - spillStack(); // may throw + exceptionBarrier(); // may throw push(m_tb->gen(ConvCellToDbl, src)); } } @@ -2255,10 +2410,10 @@ void HhbcTranslator::emitCastInt() { push(m_tb->gen(ConvStrToInt, src)); m_tb->genDecRef(src); } else if (fromType.isObj()) { - spillStack(); // may throw + exceptionBarrier(); push(m_tb->gen(ConvObjToInt, src)); } else { - spillStack(); // may throw + exceptionBarrier(); push(m_tb->gen(ConvCellToInt, src)); } } @@ -2290,10 +2445,10 @@ void HhbcTranslator::emitCastString() { } else if (fromType.isInt()) { push(m_tb->gen(ConvIntToStr, src)); } else if (fromType.isObj()) { - spillStack(); // may throw + exceptionBarrier(); push(m_tb->gen(ConvObjToStr, src)); } else { - spillStack(); // may throw + exceptionBarrier(); push(m_tb->gen(ConvCellToStr, src)); } } @@ -2437,28 +2592,24 @@ void HhbcTranslator::emitEmpty(const StringData* name, } void HhbcTranslator::emitBindG(const StringData* gblName) { - TRACE(3, "%u: BindG\n", m_bcOff); emitBind(gblName, &HhbcTranslator::checkSupportedGblName, &HhbcTranslator::emitLdGblAddrDef); } void HhbcTranslator::emitBindS(const StringData* propName) { - TRACE(3, "%u: BindS\n", m_bcOff); emitBind(propName, &HhbcTranslator::checkSupportedClsProp, &HhbcTranslator::emitLdClsPropAddr); } void HhbcTranslator::emitVGetG(const StringData* gblName) { - TRACE(3, "%u: VGetG\n", m_bcOff); emitVGet(gblName, &HhbcTranslator::checkSupportedGblName, &HhbcTranslator::emitLdGblAddrDef); } void HhbcTranslator::emitVGetS(const StringData* propName) { - TRACE(3, "%u: VGetS\n", m_bcOff); emitVGet(propName, &HhbcTranslator::checkSupportedClsProp, &HhbcTranslator::emitLdClsPropAddr); @@ -2508,7 +2659,6 @@ void HhbcTranslator::emitCGet(const StringData* name, void HhbcTranslator::emitCGetG(const StringData* gblName, Type resultType, bool isInferedType) { - TRACE(3, "%u: CGetG\n", m_bcOff); emitCGet(gblName, resultType, isInferedType, true, &HhbcTranslator::checkSupportedGblName, &HhbcTranslator::emitLdGblAddr); @@ -2517,7 +2667,6 @@ void HhbcTranslator::emitCGetG(const StringData* gblName, void HhbcTranslator::emitCGetS(const StringData* propName, Type resultType, bool isInferedType) { - TRACE(3, "%u: CGetS\n", m_bcOff); emitCGet(propName, resultType, isInferedType, false, &HhbcTranslator::checkSupportedClsProp, &HhbcTranslator::emitLdClsPropAddrOrExit); @@ -2555,7 +2704,6 @@ void HhbcTranslator::emitBinaryArith(Opcode opc) { } void HhbcTranslator::emitNot() { - TRACE(3, "%u: Not\n", m_bcOff); SSATmp* src = popC(); push(m_tb->genNot(m_tb->genConvToBool(src))); m_tb->genDecRef(src); @@ -2563,15 +2711,16 @@ void HhbcTranslator::emitNot() { #define BINOP(Opp) \ void HhbcTranslator::emit ## Opp() { \ - TRACE(3, "%u: " #Opp "\n", m_bcOff); \ emitBinaryArith(Op ## Opp); \ } + BINOP(Add) BINOP(Sub) BINOP(Mul) BINOP(BitAnd) BINOP(BitOr) BINOP(BitXor) + #undef BINOP void HhbcTranslator::emitDiv() { @@ -2611,14 +2760,13 @@ void HhbcTranslator::emitMod() { Strings::DIVISION_BY_ZERO))); }, getNextSrcKey().offset() /* exitBcOff */, - m_bcOff + bcOff() ); m_tb->gen(JmpZero, exit, r); push(m_tb->gen(OpMod, Type::Int, l, r)); } void HhbcTranslator::emitBitNot() { - TRACE(3, "%u: BitNot\n", m_bcOff); Type srcType = topC()->getType(); if (srcType == Type::Int) { SSATmp* src = popC(); @@ -2639,7 +2787,6 @@ void HhbcTranslator::emitBitNot() { } void HhbcTranslator::emitXor() { - TRACE(3, "%u: Xor\n", m_bcOff); SSATmp* btr = popC(); SSATmp* btl = popC(); SSATmp* tr = m_tb->genConvToBool(btr); @@ -2659,22 +2806,22 @@ void HhbcTranslator::emitXor() { */ void HhbcTranslator::emitInterpOne(Type type, int numPopped, int numExtraPushed) { - spillStack(); + exceptionBarrier(); // 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; - m_tb->genInterpOne(m_bcOff, numPopped - numPushed, type); + m_tb->genInterpOne(bcOff(), numPopped - numPushed, type); m_stackDeficit = 0; } void HhbcTranslator::emitInterpOneCF(int numPopped) { - spillStack(); + exceptionBarrier(); // discard the top elements of the stack, which are consumed by this instr discard(numPopped); assert(numPopped == m_stackDeficit); m_tb->gen(InterpOneCF, m_tb->getFp(), m_tb->getSp(), - m_tb->genDefConst(m_bcOff)); + m_tb->genDefConst(bcOff())); m_stackDeficit = 0; m_hasExit = true; } @@ -2682,7 +2829,7 @@ void HhbcTranslator::emitInterpOneCF(int numPopped) { void HhbcTranslator::emitInterpOneOrPunt(Type type, int numPopped, int numExtraPushed) { if (RuntimeOption::EvalIRPuntDontInterp) { - Op op = *(Op*)(getCurUnit()->entry() + m_bcOff); + Op op = *(Op*)(getCurUnit()->entry() + bcOff()); const char* name = StringData::GetStaticString( std::string("PuntDontInterp-") + opcodeToName(op))->data(); SPUNT(name); @@ -2692,7 +2839,8 @@ void HhbcTranslator::emitInterpOneOrPunt(Type type, int numPopped, } Trace* HhbcTranslator::getGuardExit() { - assert(m_bcOff == -1 || m_bcOff == m_startBcOff); + assert(bcOff() == -1 || bcOff() == m_startBcOff); + assert(!isInlining()); // stack better be empty since we're at the start of the trace assert((m_evalStack.numCells() - m_stackDeficit) == 0); return m_exitGuardFailureTrace; @@ -2709,22 +2857,7 @@ std::vector HhbcTranslator::getSpillValues() const { ret.reserve(m_evalStack.size()); for (int i = 0; i < m_evalStack.size(); ++i) { SSATmp* elem = m_evalStack.top(i); - ret.push_back(elem); - if (elem->getType() == Type::ActRec) { - /* - * For register allocation purposes, the SpillStack instruction - * should count as a use of each of the SSATmps coming into the - * a DefActRec. - */ - auto* inst = elem->getInstruction(); - assert(inst->getNumSrcs() == 5); - ret.push_back(inst->getSrc(0)); // fp - ret.push_back(inst->getSrc(1)); // func - ret.push_back(inst->getSrc(2)); // objOrCls - ret.push_back(inst->getSrc(3)); // numArgs - ret.push_back(inst->getSrc(4)); // invName - } } return ret; } @@ -2736,7 +2869,7 @@ std::vector HhbcTranslator::getSpillValues() const { */ Trace* HhbcTranslator::getExitSlowTrace() { auto stackValues = getSpillValues(); - return m_tb->getExitSlowTrace(m_bcOff, + return m_tb->getExitSlowTrace(bcOff(), m_stackDeficit, stackValues.size(), stackValues.size() ? &stackValues[0] : 0); @@ -2752,7 +2885,7 @@ Trace* HhbcTranslator::getExitSlowTrace() { */ Trace* HhbcTranslator::getExitTrace(Offset targetBcOff /* = -1 */) { if (targetBcOff == -1) { - targetBcOff = m_bcOff != -1 ? m_bcOff : m_startBcOff; + targetBcOff = bcOff() != -1 ? bcOff() : m_startBcOff; } if (targetBcOff == m_startBcOff) { return m_exitGuardFailureTrace; @@ -2791,6 +2924,11 @@ SSATmp* HhbcTranslator::spillStack() { return sp; } +void HhbcTranslator::exceptionBarrier() { + auto const sp = spillStack(); + m_tb->gen(ExceptionBarrier, sp); +} + SSATmp* HhbcTranslator::loadStackAddr(int32_t offset) { // You're almost certainly doing it wrong if you want to get the address of a // stack cell that's in m_evalStack. @@ -2808,7 +2946,7 @@ SSATmp* HhbcTranslator::emitLdLocWarn(uint32_t id, SSATmp* locVal = m_tb->genLdLocAsCell(id, target); if (locVal->getType().subtypeOf(Type::Uninit)) { - spillStack(); + exceptionBarrier(); m_tb->genRaiseUninitLoc(id); return m_tb->genDefInitNull(); } diff --git a/hphp/runtime/vm/translator/hopt/hhbctranslator.h b/hphp/runtime/vm/translator/hopt/hhbctranslator.h index 3c11d212b..755a29471 100644 --- a/hphp/runtime/vm/translator/hopt/hhbctranslator.h +++ b/hphp/runtime/vm/translator/hopt/hhbctranslator.h @@ -86,10 +86,10 @@ class TypeGuard { }; TypeGuard(Kind kind, uint32_t index, Type type) - : m_kind(kind) - , m_index(index) - , m_type(type) { - } + : m_kind(kind) + , m_index(index) + , m_type(type) + {} Kind getKind() const { return m_kind; } uint32_t getIndex() const { return m_index; } @@ -97,8 +97,8 @@ class TypeGuard { private: Kind m_kind; - uint32_t m_index; - Type m_type; + uint32_t m_index; + Type m_type; }; struct HhbcTranslator { @@ -111,8 +111,7 @@ struct HhbcTranslator { initialSpOffsetFromFp, m_irFactory, func)) - , m_curFunc(func) - , m_bcOff(-1) + , m_bcStateStack {BcState(-1, func)} , m_startBcOff(bcStartOffset) , m_lastBcOff(false) , m_hasExit(false) @@ -124,6 +123,16 @@ struct HhbcTranslator { Trace* getTrace() const { return m_tb->getTrace(); } TraceBuilder* getTraceBuilder() const { return m_tb.get(); } + void beginInlining(unsigned numArgs, + const Func* target, + Offset returnBcOffset); + void endInlining(); + bool isInlining() const; + void profileFunctionEntry(const char* category); + void profileInlineFunctionShape(const std::string& str); + void profileSmallFunctionShape(const std::string& str); + void profileFailedInlShape(const std::string& str); + void setBcOff(Offset newOff, bool lastBcOff); void setBcOffNextTrace(Offset bcOff) { m_bcOffNextTrace = bcOff; } uint32_t getBcOffNextTrace() { return m_bcOffNextTrace; } @@ -271,6 +280,8 @@ struct HhbcTranslator { void emitSwitch(const ImmVector&, int64_t base, bool bounded); void emitSSwitch(const ImmVector&); + // freeInline indicates whether we should be doing decrefs inlined in + // the TC, or using the generic decref helper. void emitRetC(bool freeInline); void emitRetV(bool freeInline); @@ -427,6 +438,7 @@ private: PropInfo propOffset, bool warn, bool define); + Class* contextClass() const; /* * genStk is a wrapper around TraceBuilder::gen() to deal with instructions @@ -479,34 +491,23 @@ private: bool exitOnFailure, CheckSupportedFun checkSupported, EmitLdAddrFun emitLdAddr); - void emitVGetMem(SSATmp* addr); - template void emitVGet(const StringData* name, CheckSupportedFun, EmitLdAddrFun); - void emitBindMem(SSATmp* ptr, SSATmp* src); - template void emitBind(const StringData* name, CheckSupportedFun, EmitLdAddrFun); - void emitSetMem(SSATmp* ptr, SSATmp* src); - template void emitSet(const StringData* name, CheckSupportedFun, EmitLdAddrFun); - template void emitIsset(const StringData* name, CheckSupportedFun, EmitLdAddrFun); - void emitEmptyMem(SSATmp* ptr); - template void emitEmpty(const StringData* name, CheckSupportedFun checkSupported, EmitLdAddrFun emitLdAddr); - void emitIncDecMem(bool pre, bool inc, SSATmp* ptr, Trace* exitTrace); - bool checkSupportedClsProp(const StringData* propName, Type resultType, int stkIndex); @@ -521,9 +522,10 @@ private: SSATmp* emitLdGblAddrDef(const StringData* gblName = nullptr); SSATmp* emitLdGblAddr(const StringData* gblName, Block* block); SSATmp* unboxPtr(SSATmp* ptr); - void emitUnboxRAux(); void emitAGet(SSATmp* src, const StringData* clsName); + void emitRetFromInlined(Type type); + SSATmp* emitDecRefLocalsInline(SSATmp* retVal); void emitRet(Type type, bool freeInline); void emitIsTypeC(Type t); void emitIsTypeL(Type t, int id); @@ -540,19 +542,21 @@ private: void emitBinaryArith(Opcode); template SSATmp* emitIterInitCommon(int offset, Lambda genFunc); + void emitMarker(); /* * Accessors for the current function being compiled and its * class and unit. */ - const Func* getCurFunc() { return m_curFunc; } - Class* getCurClass() { return getCurFunc()->cls(); } - Unit* getCurUnit() { return getCurFunc()->unit(); } + const Func* getCurFunc() const { return m_bcStateStack.back().func; } + Class* getCurClass() const { return getCurFunc()->cls(); } + Unit* getCurUnit() const { return getCurFunc()->unit(); } + Offset bcOff() const { return m_bcStateStack.back().bcOff; } - SrcKey getCurSrcKey() { return SrcKey(m_curFunc, m_bcOff); } - SrcKey getNextSrcKey() { - SrcKey srcKey(m_curFunc, m_bcOff); - srcKey.advance(m_curFunc->unit()); + SrcKey getCurSrcKey() const { return SrcKey(getCurFunc(), bcOff()); } + SrcKey getNextSrcKey() const { + SrcKey srcKey(getCurFunc(), bcOff()); + srcKey.advance(getCurFunc()->unit()); return srcKey; } @@ -581,18 +585,33 @@ private: SSATmp* topC(uint32_t i = 0) { return top(Type::Cell, i); } std::vector getSpillValues() const; SSATmp* spillStack(); + void exceptionBarrier(); SSATmp* loadStackAddr(int32_t offset); SSATmp* top(Type type, uint32_t index = 0); void extendStack(uint32_t index, Type type); void replace(uint32_t index, SSATmp* tmp); void refineType(SSATmp* tmp, Type type); +private: + // Tracks information about the current bytecode offset and which + // function we are in. Goes in m_bcStateStack; we push and pop as + // we deal with inlined calls. + struct BcState { + explicit BcState(Offset bcOff, const Func* func) + : bcOff(bcOff) + , func(func) + {} + + Offset bcOff; + const Func* func; + }; + private: IRFactory& m_irFactory; std::unique_ptr m_tb; - const Func* m_curFunc; - Offset m_bcOff; + std::vector + m_bcStateStack; Offset m_startBcOff; Offset m_bcOffNextTrace; bool m_lastBcOff; @@ -614,6 +633,14 @@ private: uint32_t m_stackDeficit; EvalStack m_evalStack; + /* + * The FPI stack is used for inlining---when we start inlining at an + * FCall, we look in here to find a definition of the StkPtr,offset + * that can be used after the inlined callee "returns". + */ + std::stack> + m_fpiStack; + vector m_typeGuards; Trace* const m_exitGuardFailureTrace; }; diff --git a/hphp/runtime/vm/translator/hopt/ir.cpp b/hphp/runtime/vm/translator/hopt/ir.cpp index 41c4a8b48..4f6246305 100644 --- a/hphp/runtime/vm/translator/hopt/ir.cpp +++ b/hphp/runtime/vm/translator/hopt/ir.cpp @@ -1138,15 +1138,16 @@ void Trace::print(std::ostream& os, const AsmInfo* asmInfo) const { TcaRange(nullptr, nullptr); for (auto it = block->begin(); it != block->end();) { auto& inst = *it; ++it; + if (inst.getOpcode() == Marker) { + os << std::string(kIndent, ' '); + inst.print(os); + os << '\n'; + + // Don't print bytecode in a non-main trace. + if (!isMain()) continue; + auto* marker = inst.getExtra(); - if (!isMain()) { - // Don't print bytecode, but print the label. - os << std::string(kIndent, ' '); - inst.print(os); - os << '\n'; - continue; - } uint32_t bcOffset = marker->bcOff; if (const auto* func = marker->func) { std::ostringstream uStr; @@ -1163,6 +1164,7 @@ void Trace::print(std::ostream& os, const AsmInfo* asmInfo) const { continue; } } + if (inst.getOpcode() == DefLabel) { os << std::string(kIndent - 2, ' '); inst.getBlock()->printLabel(os); @@ -1186,9 +1188,11 @@ void Trace::print(std::ostream& os, const AsmInfo* asmInfo) const { os << '\n'; } } + os << std::string(kIndent, ' '); inst.print(os); os << '\n'; + if (asmInfo) { TcaRange instRange = asmInfo->instRanges[inst]; if (!instRange.empty()) { @@ -1200,6 +1204,7 @@ void Trace::print(std::ostream& os, const AsmInfo* asmInfo) const { } } } + if (asmInfo) { // print code associated with this block that isn't tied to any // instruction. This includes code after the last isntruction (e.g. @@ -1230,16 +1235,7 @@ void Trace::print(std::ostream& os, const AsmInfo* asmInfo) const { int32_t spillValueCells(IRInstruction* spillStack) { assert(spillStack->getOpcode() == SpillStack); int32_t numSrcs = spillStack->getNumSrcs(); - int32_t ret = 0; - for (int i = 2; i < numSrcs; ++i) { - if (spillStack->getSrc(i)->getType() == Type::ActRec) { - ret += kNumActRecCells; - i += kSpillStackActRecExtraArgs; - } else { - ++ret; - } - } - return ret; + return numSrcs - 2; } /** @@ -1430,14 +1426,6 @@ bool hasInternalFlow(Trace* trace) { void dumpTraceImpl(const Trace* trace, std::ostream& out, const AsmInfo* asmInfo) { - auto func = trace->getFunc(); - auto unitName = func->unit()->filepath()->empty() - ? "" - : func->unit()->filepath()->data(); - out << folly::format("{}() @{} ({})\n", - func->fullName()->data(), - trace->getBcOff(), - unitName); trace->print(out, asmInfo); } diff --git a/hphp/runtime/vm/translator/hopt/ir.h b/hphp/runtime/vm/translator/hopt/ir.h index 46f6bc2d7..d9d225471 100644 --- a/hphp/runtime/vm/translator/hopt/ir.h +++ b/hphp/runtime/vm/translator/hopt/ir.h @@ -290,7 +290,7 @@ O(LdClsPropAddr, D(PtrToGen), S(Cls) S(Str) C(Cls), C|E|N|Er) \ O(LdClsPropAddrCached, D(PtrToGen), S(Cls) CStr CStr C(Cls), C|E|N|Er) \ O(LdObjMethod, ND, S(Cls) CStr S(StkPtr), E|N|Refs|Er) \ O(LdGblAddrDef, D(PtrToGen), S(Str), E|N|CRc) \ -O(LdGblAddr, D(PtrToGen), S(Str), N ) \ +O(LdGblAddr, D(PtrToGen), S(Str), N) \ O(LdObjClass, D(Cls), S(Obj), C) \ O(LdFunc, D(Func), S(Str), E|N|CRc|Er) \ O(LdFuncCached, D(Func), CStr, N|C|E|Er) \ @@ -320,11 +320,6 @@ O(CreateCl, D(Obj), C(Cls) \ O(NewArray, D(Arr), C(Int), E|Mem|N|PRc) \ O(NewTuple, D(Arr), C(Int) S(StkPtr), E|Mem|N|PRc|CRc) \ O(LdRaw, DParam, SUnk, NF) \ -O(DefActRec, D(ActRec), S(FramePtr) \ - S(Func,FuncCls,FuncCtx,Null) \ - S(Ctx,Cls,InitNull) \ - C(Int) \ - S(Str,Null), Mem) \ O(FreeActRec, D(FramePtr), S(FramePtr), Mem) \ /* name dstinfo srcinfo flags */ \ O(Call, D(StkPtr), SUnk, E|Mem|CRc|Refs) \ @@ -355,7 +350,12 @@ O(StaticLocInitCached, D(BoxedCell), CStr \ S(FramePtr) \ S(Cell) \ C(CacheHandle), PRc|E|N|Mem) \ -O(SpillStack, D(StkPtr), SUnk, E|Mem|CRc) \ +O(SpillStack, D(StkPtr), SUnk, CRc) \ +O(SpillFrame, D(StkPtr), S(StkPtr) \ + S(FramePtr) \ + S(Func,FuncCls,FuncCtx,Null) \ + S(Ctx,Cls,InitNull), CRc) \ +O(ExceptionBarrier, D(StkPtr), S(StkPtr), E) \ O(ExitTrace, ND, SUnk, T|E) \ O(ExitTraceCc, ND, SUnk, T|E) \ O(ExitSlow, ND, SUnk, T|E) \ @@ -379,8 +379,13 @@ O(DecRefNZ, ND, S(Gen), Mem|CRc) \ O(DecRefNZOrBranch, ND, S(Gen), Mem|CRc) \ O(DefLabel, DMulti, SUnk, E) \ O(Marker, ND, NA, E) \ -O(DefFP, D(FramePtr), NA, E) \ -O(DefSP, D(StkPtr), S(FramePtr) C(Int), E) \ +O(DefInlineFP, D(FramePtr), S(StkPtr), NF) \ +O(InlineReturn, ND, S(FramePtr), E) \ +O(DefFP, D(FramePtr), NA, E) \ +O(DefSP, D(StkPtr), S(FramePtr), E) \ +O(ReDefSP, D(StkPtr), S(FramePtr) S(StkPtr), NF) \ +O(StashGeneratorSP, D(StkPtr), S(StkPtr), NF) \ +O(ReDefGeneratorSP, D(StkPtr), S(StkPtr), NF) \ O(VerifyParamCls, ND, S(Cls) \ S(Cls) \ C(Int) \ @@ -746,6 +751,37 @@ struct ExitData : IRExtraData { std::string show() const; }; +/* + * Compile-time metadata about an ActRec allocation. + */ +struct ActRecInfo : IRExtraData { + const StringData* invName; // may be nullptr + int32_t numArgs; + + std::string show() const { + return folly::to(numArgs, invName ? " M" : ""); + } +}; + +/* + * Stack and bytecode offsets. + */ +struct StackOffset : IRExtraData { + explicit StackOffset(int32_t offset) : offset(offset) {} + + std::string show() const { return folly::to(offset); } + + int32_t offset; +}; + +struct BCOffset : IRExtraData { + explicit BCOffset(Offset offset) : offset(offset) {} + + std::string show() const { return folly::to(offset); } + + Offset offset; +}; + ////////////////////////////////////////////////////////////////////// #define X(op, data) \ @@ -771,6 +807,12 @@ X(LdConst, ConstData); X(Jmp_, EdgeData); X(ExitTrace, ExitData); X(ExitTraceCc, ExitData); +X(SpillFrame, ActRecInfo); +X(ReDefSP, StackOffset); +X(ReDefGeneratorSP, StackOffset); +X(DefSP, StackOffset); +X(DefInlineFP, BCOffset); + #undef X ////////////////////////////////////////////////////////////////////// @@ -2160,7 +2202,6 @@ struct Block : boost::noncopyable { } uint32_t getId() const { return m_id; } - const Func* getFunc() const { return m_func; } Trace* getTrace() const { return m_trace; } void setTrace(Trace* t) { m_trace = t; } void setHint(Hint hint) { m_hint = hint; } @@ -2316,14 +2357,6 @@ public: return b; } - const Func* getFunc() const { - return front()->getFunc(); - } - - const Unit* getUnit() const { - return getFunc()->unit(); - } - uint32_t getBcOff() const { return m_bcOff; } Trace* addExitTrace(Trace* exit) { m_exitTraces.push_back(exit); @@ -2407,12 +2440,6 @@ void optimizeTrace(Trace*, IRFactory* irFactory); */ int32_t spillValueCells(IRInstruction* spillStack); -/* - * When SpillStack takes an ActRec, it has this many extra - * dependencies in the spill vector for the values in the ActRec. - */ -constexpr int kSpillStackActRecExtraArgs = 5; - inline bool isConvIntOrPtrToBool(IRInstruction* instr) { switch (instr->getOpcode()) { case ConvIntToBool: diff --git a/hphp/runtime/vm/translator/hopt/irtranslator.cpp b/hphp/runtime/vm/translator/hopt/irtranslator.cpp index ba3922d09..c63e78d58 100644 --- a/hphp/runtime/vm/translator/hopt/irtranslator.cpp +++ b/hphp/runtime/vm/translator/hopt/irtranslator.cpp @@ -17,6 +17,7 @@ #include #include "folly/Format.h" +#include "folly/Conv.h" #include "util/trace.h" #include "util/stack_trace.h" #include "util/util.h" @@ -611,6 +612,10 @@ void TranslatorX64::irTranslateCreateCont(const Tracelet& t, void TranslatorX64::irTranslateContEnter(const Tracelet& t, const NormalizedInstruction& i) { int after = nextSrcKey(t, i).offset(); + + // ContEnter can't exist in an inlined function right now. (If it + // ever can, this curFunc() needs to change.) + assert(!m_hhbcTrans->isInlining()); const Func* srcFunc = curFunc(); int32_t callOffsetInUnit = after - srcFunc->base(); @@ -973,9 +978,6 @@ TranslatorX64::irTranslateFPushClsMethodF(const Tracelet& t, FPushClsMethodF_unknown); auto cls = classLoc->rtt.valueClass(); - DEBUG_ONLY ActRec* fp = curFrame(); - assert(!fp->hasThis() || fp->getThis()->instanceof(cls)); - HHIR_EMIT(FPushClsMethodF, i.imm[0].u_IVA, // # of arguments cls, @@ -1018,20 +1020,12 @@ void TranslatorX64::irTranslateCreateCl(const Tracelet& t, void TranslatorX64::irTranslateThis(const Tracelet &t, const NormalizedInstruction &i) { - assert(i.outStack && !i.outLocal); - assert(curFunc()->isPseudoMain() || curFunc()->cls() || - curFunc()->isClosureBody()); - HHIR_EMIT(This); } void TranslatorX64::irTranslateBareThis(const Tracelet &t, const NormalizedInstruction &i) { - assert(i.outStack && !i.outLocal); - assert(curFunc()->isPseudoMain() || curFunc()->cls() || - curFunc()->isClosureBody()); - HHIR_EMIT(BareThis, (i.imm[0].u_OA)); } @@ -1044,9 +1038,6 @@ TranslatorX64::irTranslateCheckThis(const Tracelet& t, void TranslatorX64::irTranslateInitThisLoc(const Tracelet& t, const NormalizedInstruction& i) { - assert(i.outLocal && !i.outStack); - assert(curFunc()->isPseudoMain() || curFunc()->cls()); - HHIR_EMIT(InitThisLoc, i.outLocal->location.offset); } @@ -1117,16 +1108,205 @@ TranslatorX64::irTranslateFCallBuiltin(const Tracelet& t, HHIR_EMIT(FCallBuiltin, numArgs, numNonDefault, funcId); } +static bool shouldIRInline(const Func* func, const Tracelet& callee) { + if (!RuntimeOption::EvalHHIREnableGenTimeInlining) { + return false; + } + + auto refuse = [&](const char* why) -> bool { + FTRACE(1, "shouldIRInline: refusing {} \n", + func->fullName()->data(), why); + return false; + }; + auto accept = [&](const char* kind) -> bool { + FTRACE(1, "shouldIRInline: inlining {} \n", + func->fullName()->data(), kind); + return true; + }; + + if (func->numLocals() != func->numParams()) { + return refuse("locals"); + } + if (func->numIterators() != 0) { + return refuse("iterators"); + } + if (func->maxStackCells() >= kMaxJITInlineStackCells) { + FTRACE(1, "{} >= {}\n", + func->maxStackCells(), + kMaxJITInlineStackCells); + return refuse("too many stack cells"); + } + + // Disable anything with locals---specialized RetC generates stores + // that zero out the m_type's and depend on the frame. + if (func->numLocals() != 0) { + return refuse("has locals (would use frame)"); + } + + // Little pattern recognition helpers: + const NormalizedInstruction* cursor; + Opcode current; + auto resetCursor = [&] { + cursor = callee.m_instrStream.first; + current = cursor->op(); + }; + auto next = [&]() -> Opcode { + auto op = cursor->op(); + cursor = cursor->next; + current = cursor->op(); + return op; + }; + auto nextIf = [&](Opcode op) -> bool { + if (current != op) return false; + next(); + return true; + }; + auto atRet = [&] { return current == OpRetC || current == OpRetV; }; + + // Simple operations that just put a Cell on the stack without any + // inputs. For now avoid CreateCont because it depends on the + // frame. + auto simpleCell = [&]() -> bool { + if (cursor->outStack && cursor->inputs.empty() && + current != OpCreateCont) { + next(); + return true; + } + return false; + }; + + // Simple two-cell comparison operators. + auto simpleCmp = [&]() -> bool { + switch (current) { + case OpAdd: case OpSub: case OpMul: case OpDiv: case OpMod: + case OpXor: case OpNot: case OpSame: case OpNSame: case OpEq: + case OpNeq: case OpLt: case OpLte: case OpGt: case OpGte: + case OpBitAnd: case OpBitOr: case OpBitXor: case OpBitNot: + case OpShl: case OpShr: + next(); + return true; + default: + return false; + } + }; + + // In the various patterns below, when we're down to a cell on the + // stack, this is used to allow simple constant-foldable + // manipulations of it before return. + auto cellManipRet = [&]() -> bool { + if (nextIf(OpNot)) return atRet(); + if (simpleCell() && simpleCmp()) return atRet(); + return atRet(); + }; + + ///////////// + + // Identity functions. + resetCursor(); + if (current == OpCGetL) { + next(); + if (atRet()) return accept("returns parameter"); + } + + // Simple property accessors. + resetCursor(); + if (current == OpCheckThis) next(); + if (cursor->op() == OpCGetM && + cursor->immVec.locationCode() == LH && + cursor->immVecM.size() == 1 && + cursor->immVecM.front() == MPT && + !mInstrHasUnknownOffsets(*cursor, func->cls())) { + next(); + // Can't currently support cellManipRet because it's usually going + // to be CGetM-prediction, which will use the frame. + if (atRet()) { + return accept("simple property accessor"); + } + } + + /* + * Functions that set an object property to a simple cell value. + * E.g. something that does $this->foo = null; + */ + resetCursor(); + if (current == OpCheckThis) next(); + if (simpleCell()) { + if (cursor->op() == OpSetM && + cursor->immVec.locationCode() == LH && + cursor->immVecM.size() == 1 && + cursor->immVecM.front() == MPT && + !mInstrHasUnknownOffsets(*cursor, func->cls())) { + next(); + if (nextIf(OpPopC) && simpleCell() && atRet()) { + return accept("simpleCell prop setter"); + } + } + } + + /* + * Anything that just puts a value on the stack with no inputs, and + * then returns it, after possibly doing some comparison with + * another such thing. + * + * E.g. String; String; Same; RetC, or Null; RetC. + */ + resetCursor(); + if (simpleCell() && cellManipRet()) { + return accept("simple returner"); + } + + // BareThis; InstanceOfD; RetC + resetCursor(); + if (nextIf(OpBareThis) && nextIf(OpInstanceOfD) && atRet()) { + return accept("$this instanceof D"); + } + + return refuse("unknown kind of function"); +} + void TranslatorX64::irTranslateFCall(const Tracelet& t, - const NormalizedInstruction& i) { - int numArgs = i.imm[0].u_IVA; + const NormalizedInstruction& i) { + auto const numArgs = i.imm[0].u_IVA; + + always_assert(!m_hhbcTrans->isInlining() && "curUnit and curFunc calls"); const Opcode* after = curUnit()->at(nextSrcKey(t, i).offset()); const Func* srcFunc = curFunc(); - - Offset callOffsetInUnit = + Offset returnBcOffset = srcFunc->unit()->offsetOf(after - srcFunc->base()); - HHIR_EMIT(FCall, numArgs, callOffsetInUnit, i.funcd); + + /* + * If we have a calleeTrace, we're going to see if we should inline + * the call. + */ + if (i.calleeTrace) { + if (!m_hhbcTrans->isInlining() && + shouldIRInline(i.funcd, *i.calleeTrace)) { + m_hhbcTrans->beginInlining(numArgs, i.funcd, returnBcOffset); + static const bool shapeStats = Stats::enabledAny() && + getenv("HHVM_STATS_INLINESHAPE"); + if (shapeStats) { + m_hhbcTrans->profileInlineFunctionShape(traceletShape(*i.calleeTrace)); + } + + for (auto* ni = i.calleeTrace->m_instrStream.first; + ni; ni = ni->next) { + m_curNI = ni; + SCOPE_EXIT { m_curNI = &i; }; + irTranslateInstr(*i.calleeTrace, *ni); + } + return; + } + + static const auto enabled = Stats::enabledAny() && + getenv("HHVM_STATS_FAILEDINL"); + if (enabled) { + m_hhbcTrans->profileFunctionEntry("FailedCandidate"); + m_hhbcTrans->profileFailedInlShape(traceletShape(*i.calleeTrace)); + } + } + + HHIR_EMIT(FCall, numArgs, returnBcOffset, i.funcd); } void @@ -1440,7 +1620,8 @@ void TranslatorX64::irTranslateInstr(const Tracelet& t, assert(!i.outLocal || i.outLocal->isLocal()); FTRACE(1, "translating: {}\n", opcodeToName(i.op())); - m_hhbcTrans->setBcOff(i.source.offset(), i.breaksTracelet); + m_hhbcTrans->setBcOff(i.source.offset(), + i.breaksTracelet && !m_hhbcTrans->isInlining()); if (!i.grouped) { emitVariantGuards(t, i); @@ -1561,6 +1742,30 @@ TranslatorX64::irTranslateTracelet(Tracelet& t, Stats::emitInc(a, Stats::Instr_TC, t.m_numOpcodes); recordBCInstr(OpTraceletGuard, a, start); m_hhbcTrans->setBcOffNextTrace(t.m_nextSk.offset()); + + // Profiling on function entry. + if (m_curTrace->m_sk.offset() == curFunc()->base()) { + m_hhbcTrans->profileFunctionEntry("Normal"); + } + + /* + * Profiling on the shapes of tracelets that are whole functions. + * (These are the things we might consider trying to support + * inlining.) + */ + [&]{ + static const bool enabled = Stats::enabledAny() && + getenv("HHVM_STATS_FUNCSHAPE"); + if (!enabled) return; + if (m_curTrace->m_sk.offset() != curFunc()->base()) return; + if (auto last = m_curTrace->m_instrStream.last) { + if (last->op() != OpRetC && last->op() != OpRetV) { + return; + } + } + m_hhbcTrans->profileSmallFunctionShape(traceletShape(*m_curTrace)); + }(); + // Translate each instruction in the tracelet for (auto* ni = t.m_instrStream.first; ni; ni = ni->next) { try { @@ -1603,7 +1808,7 @@ TranslatorX64::irTranslateTracelet(Tracelet& t, transResult = Success; hhirTraceCodeGen(bcMap); - TRACE(1, "HHIR: SUCCEEDED to generate code for Translation %d\n", + TRACE(1, "HHIR: SUCCEEDED to generate code for Translation %d\n\n\n", getCurrentTransID()); } } catch (JIT::FailedCodeGen& fcg) { @@ -1720,5 +1925,4 @@ void TranslatorX64::hhirTraceFree() { m_irFactory.reset(); } - }}} diff --git a/hphp/runtime/vm/translator/hopt/linearscan.cpp b/hphp/runtime/vm/translator/hopt/linearscan.cpp index 722aa4612..5517981f9 100644 --- a/hphp/runtime/vm/translator/hopt/linearscan.cpp +++ b/hphp/runtime/vm/translator/hopt/linearscan.cpp @@ -332,29 +332,48 @@ void LinearScan::allocRegToInstruction(InstructionList::iterator it) { for (SSATmp& dst : dsts) { for (int i = 0, n = dst.numNeededRegs(); i < n; ++i) { // LdRaw, loading a generator's embedded AR, is the only time we have a - // pointer to an AR that is not in rVmFp or rVmSp. - if (opc != LdRaw) { - if (dst.isA(Type::StkPtr)) { - assert(opc == DefSP || opc == Call || opc == SpillStack || - opc == RetAdjustStack || - opc == NewObj || opc == NewObjCached || - opc == NewObjNoCtorCached || - opc == InterpOne || opc == GenericRetDecRefs || - opc == GuardStk || opc == AssertStk || opc == CastStk || - VectorEffects::supported(opc)); - allocRegToTmp(&m_regs[int(rVmSp)], &dst, 0); - continue; - } - if (dst.isA(Type::FramePtr)) { - assert(opc == DefFP || opc == FreeActRec); - allocRegToTmp(&m_regs[int(rVmFp)], &dst, 0); - continue; - } + // pointer to an AR that is not in rVmFp. + const bool abnormalFramePtr = + (opc == LdRaw && + inst->getSrc(1)->getValInt() == RawMemSlot::ContARPtr); + + // Note that the point of StashGeneratorSP is to save a StkPtr + // somewhere other than rVmSp. (TODO(#2288359): make rbx not + // special.) + const bool abnormalStkPtr = opc == StashGeneratorSP; + + if (!abnormalStkPtr && dst.isA(Type::StkPtr)) { + assert(opc == DefSP || + opc == ReDefSP || + opc == ReDefGeneratorSP || + opc == Call || + opc == SpillStack || + opc == SpillFrame || + opc == ExceptionBarrier || + opc == RetAdjustStack || + opc == NewObj || + opc == NewObjCached || + opc == NewObjNoCtorCached || + opc == InterpOne || + opc == GenericRetDecRefs || + opc == GuardStk || + opc == AssertStk || + opc == CastStk || + VectorEffects::supported(opc)); + allocRegToTmp(&m_regs[int(rVmSp)], &dst, 0); + continue; + } + if (!abnormalFramePtr && dst.isA(Type::FramePtr)) { + assert(opc == DefFP || opc == FreeActRec || opc == DefInlineFP); + allocRegToTmp(&m_regs[int(rVmFp)], &dst, 0); + continue; } - assert(!dst.isA(Type::StkPtr) || - (opc == LdRaw && - inst->getSrc(1)->getValInt() == RawMemSlot::ContARPtr)); + // Generally speaking, StkPtrs are pretty special due to + // tracelet ABI registers. Keep track here of the allowed uses + // that don't use the above allocation. + assert(!dst.isA(Type::FramePtr) || abnormalFramePtr); + assert(!dst.isA(Type::StkPtr) || abnormalStkPtr); if (!RuntimeOption::EvalHHIRDeadCodeElim || dst.getLastUseId() != 0) { allocRegToTmp(&dst, i); diff --git a/hphp/runtime/vm/translator/hopt/memelim.cpp b/hphp/runtime/vm/translator/hopt/memelim.cpp index 1a34cef06..384f90a2b 100644 --- a/hphp/runtime/vm/translator/hopt/memelim.cpp +++ b/hphp/runtime/vm/translator/hopt/memelim.cpp @@ -745,7 +745,7 @@ void MemMap::optimizeMemoryAccesses(Trace* trace) { } } } - Simplifier::copyProp(&inst); + copyProp(&inst); processInstruction(&inst, curFunc && curFunc->isPseudoMain()); } } diff --git a/hphp/runtime/vm/translator/hopt/opt.cpp b/hphp/runtime/vm/translator/hopt/opt.cpp index 709873393..8df94c751 100644 --- a/hphp/runtime/vm/translator/hopt/opt.cpp +++ b/hphp/runtime/vm/translator/hopt/opt.cpp @@ -46,27 +46,21 @@ static void insertRefCountAsserts(IRInstruction& inst, IRFactory* factory) { /* * Insert a DbgAssertTv instruction for each stack location stored to by - * a SpillStack instruction + * a SpillStack instruction. */ static void insertSpillStackAsserts(IRInstruction& inst, IRFactory* factory) { SSATmp* sp = inst.getDst(); auto const vals = inst.getSrcs().subpiece(2); auto* block = inst.getBlock(); auto pos = block->iteratorTo(&inst); ++pos; - for (unsigned i = 0, offset = 0, n = vals.size(); i < n; ++i) { + for (unsigned i = 0, n = vals.size(); i < n; ++i) { Type t = vals[i]->getType(); - if (t == Type::ActRec) { - offset += kNumActRecCells; - i += kSpillStackActRecExtraArgs; - } else { - if (t.subtypeOf(Type::Gen)) { - IRInstruction* addr = factory->gen(LdStackAddr, Type::PtrToGen, - sp, factory->defConst(offset)); - block->insert(pos, addr); - IRInstruction* check = factory->gen(DbgAssertPtr, addr->getDst()); - block->insert(pos, check); - } - ++offset; + if (t.subtypeOf(Type::Gen)) { + IRInstruction* addr = factory->gen(LdStackAddr, Type::PtrToGen, + sp, factory->defConst(i)); + block->insert(pos, addr); + IRInstruction* check = factory->gen(DbgAssertPtr, addr->getDst()); + block->insert(pos, check); } } } @@ -107,10 +101,12 @@ void optimizeTrace(Trace* trace, TraceBuilder* traceBuilder) { optimizeMemoryAccesses(trace, irFactory); finishPass("after MemeLim"); } + if (RuntimeOption::EvalHHIRDeadCodeElim) { eliminateDeadCode(trace, irFactory); finishPass("after DCE"); } + if (RuntimeOption::EvalHHIRExtraOptPass && (RuntimeOption::EvalHHIRCse || RuntimeOption::EvalHHIRSimplification)) { @@ -124,10 +120,12 @@ void optimizeTrace(Trace* trace, TraceBuilder* traceBuilder) { finishPass("after DCE"); } } + if (RuntimeOption::EvalHHIRJumpOpts) { optimizeJumps(trace, irFactory); finishPass("jump opts"); } + if (RuntimeOption::EvalHHIRGenerateAsserts) { insertAsserts(trace, irFactory); finishPass("RefCnt asserts"); diff --git a/hphp/runtime/vm/translator/hopt/simplifier.cpp b/hphp/runtime/vm/translator/hopt/simplifier.cpp index a76dd857a..deff94d21 100644 --- a/hphp/runtime/vm/translator/hopt/simplifier.cpp +++ b/hphp/runtime/vm/translator/hopt/simplifier.cpp @@ -29,24 +29,31 @@ namespace JIT { TRACE_SET_MOD(hhir); -void Simplifier::copyProp(IRInstruction* inst) { - for (uint32_t i = 0; i < inst->getNumSrcs(); i++) { - IRInstruction* srcInst = inst->getSrc(i)->getInstruction(); - if (srcInst->getOpcode() == Mov) { - inst->setSrc(i, srcInst->getSrc(0)); - } else if (srcInst->getOpcode() == IncRef && - !isRefCounted(srcInst->getSrc(0))) { +static void copyPropSrc(IRInstruction* inst, int index) { + auto tmp = inst->getSrc(index); + auto srcInst = tmp->getInstruction(); + + switch (srcInst->getOpcode()) { + case Mov: + inst->setSrc(index, srcInst->getSrc(0)); + break; + + case IncRef: + if (!isRefCounted(srcInst->getSrc(0))) { srcInst->setOpcode(Mov); - inst->setSrc(i, srcInst->getSrc(0)); + inst->setSrc(index, srcInst->getSrc(0)); } + break; + + default: + return; } } -static void unimplementedSimplify(Opcode opc) { - // Do not assert(false), it is fine to not simplify as the default - TRACE(3, "HHIR Simplifier: unimplemented support for opcode %s\n", - opcodeName(opc)); - return; +void copyProp(IRInstruction* inst) { + for (uint32_t i = 0; i < inst->getNumSrcs(); i++) { + copyPropSrc(inst, i); + } } static bool isNotInst(SSATmp *src1, SSATmp *src2) { @@ -150,6 +157,7 @@ SSATmp* Simplifier::simplify(IRInstruction* inst) { case GuardType: return simplifyGuardType(inst); case LdCls: return simplifyLdCls(inst); + case LdThis: return simplifyLdThis(inst); case LdCtx: return simplifyLdCtx(inst); case LdClsCtx: return simplifyLdClsCtx(inst); @@ -159,7 +167,6 @@ SSATmp* Simplifier::simplify(IRInstruction* inst) { case Call: return simplifyCall(inst); default: - unimplementedSimplify(inst->getOpcode()); return nullptr; } } @@ -222,11 +229,6 @@ SSATmp* Simplifier::simplifySpillStack(IRInstruction* inst) { int64_t adjustment = spDeficit - spillCells; for (uint32_t i = 0, cellOff = 0; i < numSpillSrcs; i++) { const int64_t offset = cellOff + adjustment; - if (spillVals[i]->getType() == Type::ActRec) { - cellOff += kNumActRecCells; - i += kSpillStackActRecExtraArgs; - continue; - } auto* srcInst = spillVals[i]->getInstruction(); // If our value came from a LdStack on the same sp and offset, // we don't need to spill it. @@ -1270,6 +1272,26 @@ SSATmp* Simplifier::simplifyLdClsPropAddr(IRInstruction* inst) { inst->getSrc(2)); } +/* + * If we're in an inlined frame, use the this that we put in the + * inlined ActRec. (This could chase more intervening SpillStack + * instructions to find the SpillFrame, but for now we don't inline + * calls that will have that.) + */ +SSATmp* Simplifier::simplifyLdThis(IRInstruction* inst) { + auto fpInst = inst->getSrc(0)->getInstruction(); + if (fpInst->getOpcode() == DefInlineFP) { + auto spInst = fpInst->getSrc(0)->getInstruction(); + if (spInst->getOpcode() == SpillFrame && + spInst->getSrc(3)->isA(Type::Obj)) { + return spInst->getSrc(3); + } + return nullptr; + } + + return nullptr; +} + SSATmp* Simplifier::simplifyUnbox(IRInstruction* inst) { auto* src = inst->getSrc(0); auto type = outputType(inst); diff --git a/hphp/runtime/vm/translator/hopt/simplifier.h b/hphp/runtime/vm/translator/hopt/simplifier.h index 834c4a716..457a47abd 100644 --- a/hphp/runtime/vm/translator/hopt/simplifier.h +++ b/hphp/runtime/vm/translator/hopt/simplifier.h @@ -29,8 +29,6 @@ class TraceBuilder; struct Simplifier { explicit Simplifier(TraceBuilder* t) : m_tb(t) {} - static void copyProp(IRInstruction* tmp); - /* * Simplify performs a number of optimizations. * @@ -87,6 +85,7 @@ private: SSATmp* simplifyDecRef(IRInstruction* inst); SSATmp* simplifyIncRef(IRInstruction* inst); SSATmp* simplifyGuardType(IRInstruction* inst); + SSATmp* simplifyLdThis(IRInstruction*); SSATmp* simplifyLdCls(IRInstruction* inst); SSATmp* simplifyLdClsPropAddr(IRInstruction*); SSATmp* simplifyLdCtx(IRInstruction*); @@ -107,6 +106,14 @@ private: TraceBuilder* const m_tb; }; +/* + * Propagate very simple copies on the given instruction. + * Specifically, Movs, and also IncRefs of non-refcounted types. + * + * More complicated copy-propagation is performed in the Simplifier. + */ +void copyProp(IRInstruction*); + }}} #endif diff --git a/hphp/runtime/vm/translator/hopt/tracebuilder.cpp b/hphp/runtime/vm/translator/hopt/tracebuilder.cpp index 6e3bdef9b..a4b61d9e3 100644 --- a/hphp/runtime/vm/translator/hopt/tracebuilder.cpp +++ b/hphp/runtime/vm/translator/hopt/tracebuilder.cpp @@ -59,8 +59,10 @@ TraceBuilder::TraceBuilder(Offset initialBcOffset, m_enableCse = RuntimeOption::EvalHHIRCse; m_enableSimplification = RuntimeOption::EvalHHIRSimplification; } - genDefFP(); - genDefSP(initialSpOffsetFromFp); + + gen(DefFP); + gen(DefSP, StackOffset(initialSpOffsetFromFp), m_fpValue); + assert(m_spOffset >= 0); } @@ -76,16 +78,8 @@ SSATmp* TraceBuilder::genConcat(SSATmp* tl, SSATmp* tr) { return gen(Concat, tl, tr); } -void TraceBuilder::genDefCls(PreClass* clss, const Opcode* after) { - PUNT(DefCls); -} - -void TraceBuilder::genDefFunc(Func* func) { - gen(DefFunc, genDefConst(func)); -} - SSATmp* TraceBuilder::genLdThis(Trace* exitTrace) { - if (m_thisIsAvailable) { + if (isThisAvailable()) { return gen(LdThis, m_fpValue); } else { return gen(LdThis, getFirstBlock(exitTrace), m_fpValue); @@ -169,6 +163,7 @@ bool TraceBuilder::isValueAvailable(SSATmp* tmp) const { while (true) { if (m_refCountedMemValue == tmp) return true; if (anyLocalHasValue(tmp)) return true; + if (callerLocalHasValue(tmp)) return true; IRInstruction* srcInstr = tmp->getInstruction(); Opcode srcOpcode = srcInstr->getOpcode(); @@ -768,22 +763,6 @@ SSATmp* TraceBuilder::genNewTuple(int32_t numArgs, SSATmp* sp) { return gen(NewTuple, genDefConst(numArgs), sp); } -SSATmp* TraceBuilder::genDefActRec(SSATmp* func, - SSATmp* objOrClass, - int32_t numArgs, - const StringData* invName) { - return gen(DefActRec, - m_fpValue, - func, - objOrClass, - genDefConst(numArgs), - invName ? genDefConst(invName) : genDefInitNull()); -} - -SSATmp* TraceBuilder::genFreeActRec() { - return gen(FreeActRec, m_fpValue); -} - /* * Track down a value that was previously spilled onto the stack * The spansCall parameter tracks whether the returned value's @@ -801,10 +780,21 @@ static SSATmp* getStackValue(SSATmp* sp, case DefSP: return nullptr; + case ReDefGeneratorSP: { + auto srcInst = inst->getSrc(0)->getInstruction(); + assert(srcInst->getOpcode() == StashGeneratorSP); + return getStackValue(srcInst->getSrc(0), index, spansCall, type); + } + case ReDefSP: + return getStackValue(inst->getSrc(1), index, spansCall, type); + + case ExceptionBarrier: + return getStackValue(inst->getSrc(0), index, spansCall, type); + case AssertStk: // fallthrough case CastStk: - // fallthrough: sp = CastStk sp, offset + // fallthrough case GuardStk: { // sp = GuardStk sp, offset // We don't have a value, but we may know the type due to guarding @@ -834,18 +824,11 @@ static SSATmp* getStackValue(SSATmp* sp, type); case SpillStack: { - // sp = spillstack(stkptr, stkAdjustment, spilledtmp0, spilledtmp1, ...) int64_t numPushed = 0; int32_t numSpillSrcs = inst->getNumSrcs() - 2; for (int i = 0; i < numSpillSrcs; ++i) { SSATmp* tmp = inst->getSrc(i + 2); - if (tmp->getType() == Type::ActRec) { - numPushed += kNumActRecCells; - i += kSpillStackActRecExtraArgs; - continue; - } - if (index == numPushed) { if (tmp->getInstruction()->getOpcode() == IncRef) { tmp = tmp->getInstruction()->getSrc(0); @@ -858,8 +841,8 @@ static SSATmp* getStackValue(SSATmp* sp, ++numPushed; } - // this is not one of the values pushed onto the stack by this - // spillstack instruction, so continue searching + // This is not one of the values pushed onto the stack by this + // spillstack instruction, so continue searching. SSATmp* prevSp = inst->getSrc(0); int64_t numPopped = inst->getSrc(1)->getValInt(); return getStackValue(prevSp, @@ -870,7 +853,6 @@ static SSATmp* getStackValue(SSATmp* sp, } case InterpOne: { - // sp = InterpOne(fp, sp, bcOff, stackAdjustment, resultType) SSATmp* prevSp = inst->getSrc(1); int64_t spAdjustment = inst->getSrc(3)->getValInt(); // # popped - # pushed Type resultType = inst->getTypeParam(); @@ -881,9 +863,15 @@ static SSATmp* getStackValue(SSATmp* sp, return getStackValue(prevSp, index + spAdjustment, spansCall, type); } + case SpillFrame: + return getStackValue(inst->getSrc(0), + // pushes an ActRec + index - kNumActRecCells, + spansCall, + type); + case NewObj: case NewObjCached: - // sp = NewObj(numParams, className, sp, fp) if (index == kNumActRecCells) { // newly allocated object, which we unfortunately don't have any // kind of handle to :-( @@ -929,7 +917,14 @@ void TraceBuilder::genAssertStk(uint32_t id, Type type) { Type knownType = Type::None; bool spansCall = false; UNUSED SSATmp* tmp = getStackValue(m_spValue, id, spansCall, knownType); - assert(!tmp); + + // We may have found a value if there was an inlined call. + // AssertStk indicated that we knew the type from static analysis, + // so let's double check. + if (tmp) { + assert(tmp->isA(type)); + } + if (knownType == Type::None || type.strictSubtypeOf(knownType)) { gen(AssertStk, type, m_spValue, genDefConst(id)); } @@ -948,14 +943,6 @@ SSATmp* TraceBuilder::genCastStk(uint32_t id, Type type) { return m_spValue; } -SSATmp* TraceBuilder::genDefFP() { - return gen(DefFP); -} - -SSATmp* TraceBuilder::genDefSP(int32_t spOffset) { - return gen(DefSP, m_fpValue, genDefConst(spOffset)); -} - SSATmp* TraceBuilder::genLdStackAddr(SSATmp* sp, int64_t index) { Type type; bool spansCall; @@ -1037,11 +1024,22 @@ void TraceBuilder::genDecRefStack(Type type, uint32_t stackOff) { void TraceBuilder::genDecRefThis() { if (isThisAvailable()) { - SSATmp* thiss = genLdThis(nullptr); + auto const thiss = genLdThis(nullptr); + auto const thisInst = thiss->getInstruction(); + + if (thisInst->getOpcode() == IncRef && + callerLocalHasValue(thisInst->getSrc(0))) { + gen(DecRefNZ, thiss); + return; + } + + // It's a shame to keep a reference to the frame just to kill the + // this pointer. This is handled in optimizeActRecs. gen(DecRefKillThis, thiss, m_fpValue); - } else { - gen(DecRefThis, m_fpValue); + return; } + + gen(DecRefThis, m_fpValue); } SSATmp* TraceBuilder::genGenericRetDecRefs(SSATmp* retVal, int numLocals) { @@ -1181,7 +1179,7 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) { } switch (opc) { - case Call: { + case Call: m_spValue = inst->getDst(); // A call pops the ActRec and pushes a return value. m_spOffset -= kNumActRecCells; @@ -1190,49 +1188,57 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) { killCse(); killLocals(); break; - } - case ContEnter: { + + case ContEnter: killCse(); killLocals(); break; - } - case DefFP: { + + case DefFP: + case FreeActRec: m_fpValue = inst->getDst(); break; - } - case DefSP: { + + case ReDefGeneratorSP: + case DefSP: + case ReDefSP: m_spValue = inst->getDst(); - m_spOffset = inst->getSrc(1)->getValInt(); + m_spOffset = inst->getExtra()->offset; break; - } + case AssertStk: case CastStk: - case GuardStk: { + case GuardStk: + case ExceptionBarrier: m_spValue = inst->getDst(); break; - } + case SpillStack: { m_spValue = inst->getDst(); // Push the spilled values but adjust for the popped values int64_t stackAdjustment = inst->getSrc(1)->getValInt(); m_spOffset -= stackAdjustment; m_spOffset += spillValueCells(inst); - assert(m_spOffset >= 0); break; } + + case SpillFrame: + m_spValue = inst->getDst(); + m_spOffset += kNumActRecCells; + break; + case NewObj: - case NewObjCached: { + case NewObjCached: m_spValue = inst->getDst(); // new obj leaves the new object and an actrec on the stack - m_spOffset += (sizeof(ActRec) / sizeof(Cell)) + 1; - assert(m_spOffset >= 0); + m_spOffset += kNumActRecCells + 1; break; - } - case NewObjNoCtorCached: { + + case NewObjNoCtorCached: m_spValue = inst->getDst(); m_spOffset += 1; break; - } + case InterpOne: { m_spValue = inst->getDst(); int64_t stackAdjustment = inst->getSrc(3)->getValInt(); @@ -1246,17 +1252,15 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) { case StPropNT: // fall through to StMem; stored value is the same arg number (2) case StMem: - case StMemNT: { + case StMemNT: m_refCountedMemValue = inst->getSrc(2); break; - } case LdMem: case LdProp: - case LdRef: { + case LdRef: m_refCountedMemValue = inst->getDst(); break; - } case StRefNT: case StRef: { @@ -1269,47 +1273,47 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) { } case StLocNT: - case StLoc: { + case StLoc: setLocalValue(inst->getExtra()->locId, inst->getSrc(1)); break; - } - case LdLoc: { + + case LdLoc: setLocalValue(inst->getExtra()->locId, inst->getDst()); break; - } + case AssertLoc: - case GuardLoc: { + case GuardLoc: setLocalType(inst->getExtra()->locId, inst->getTypeParam()); break; - } - case IterInitK: { + + case IterInitK: // kill the locals to which this instruction stores iter's key and value killLocalValue(inst->getSrc(3)->getValInt()); killLocalValue(inst->getSrc(4)->getValInt()); break; - } - case IterInit: { + + case IterInit: // kill the local to which this instruction stores iter's value killLocalValue(inst->getSrc(3)->getValInt()); break; - } - case IterNextK: { + + case IterNextK: // kill the locals to which this instruction stores iter's key and value killLocalValue(inst->getSrc(2)->getValInt()); killLocalValue(inst->getSrc(3)->getValInt()); break; - } - case IterNext: { + + case IterNext: // kill the local to which this instruction stores iter's value killLocalValue(inst->getSrc(2)->getValInt()); break; - } - case LdThis: { + + case LdThis: m_thisIsAvailable = true; break; - } + default: break; } @@ -1347,6 +1351,76 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) { if (Block* target = inst->getTaken()) saveState(target); } +void TraceBuilder::beginInlining(const Func* target, + SSATmp* calleeFP, + SSATmp* calleeSP, + SSATmp* savedSP, + int32_t savedSPOff) { + // Saved tracebuilder state will include the "return" fp/sp. + // Whatever the current fpValue is is good enough, but we have to be + // passed in the StkPtr that represents the stack prior to the + // ActRec being allocated. + m_spOffset = savedSPOff; + m_spValue = savedSP; + + m_inlineSavedStates.push_back(createState()); + + /* + * Set up the callee state. + * + * We set m_thisIsAvailable to true on any object method, because we + * just don't inline calls to object methods with a null $this. + */ + m_fpValue = calleeFP; + m_spValue = calleeSP; + m_thisIsAvailable = target->cls() != nullptr; + m_curFunc = genDefConst(target); + + /* + * Keep the outer locals somewhere for isValueAvailable() to know + * about their liveness, to help with incref/decref elimination. + */ + m_callerAvailableValues.insert(m_callerAvailableValues.end(), + m_localValues.begin(), + m_localValues.end()); + + m_localValues.clear(); + m_localTypes.clear(); + m_localValues.resize(target->numLocals(), nullptr); + m_localTypes.resize(target->numLocals(), Type::None); + + gen(ReDefSP, StackOffset(target->numParams()), m_fpValue, m_spValue); +} + +void TraceBuilder::endInlining() { + auto calleeAR = m_fpValue; + gen(InlineReturn, calleeAR); + + useState(std::move(m_inlineSavedStates.back())); + m_inlineSavedStates.pop_back(); + + // See the comment in beginInlining about generator frames. + if (m_curFunc->getValFunc()->isGenerator()) { + gen(ReDefGeneratorSP, StackOffset(m_spOffset), m_spValue); + } else { + gen(ReDefSP, StackOffset(m_spOffset), m_fpValue, m_spValue); + } +} + +std::unique_ptr TraceBuilder::createState() const { + std::unique_ptr state(new State); + state->spValue = m_spValue; + state->fpValue = m_fpValue; + state->curFunc = m_curFunc; + state->spOffset = m_spOffset; + state->thisAvailable = m_thisIsAvailable; + state->localValues = m_localValues; + state->localTypes = m_localTypes; + state->callerAvailableValues = m_callerAvailableValues; + state->refCountedMemValue = m_refCountedMemValue; + return state; +} + /* * Save current state for block. If this is the first time saving state * for block, create a new snapshot. Otherwise merge the current state into @@ -1356,15 +1430,7 @@ void TraceBuilder::saveState(Block* block) { if (State* state = m_snapshots[block]) { mergeState(state); } else { - state = new State; - state->spValue = m_spValue; - state->fpValue = m_fpValue; - state->spOffset = m_spOffset; - state->thisAvailable = m_thisIsAvailable; - state->localValues = m_localValues; - state->localTypes = m_localTypes; - state->refCountedMemValue = m_refCountedMemValue; - m_snapshots[block] = state; + m_snapshots[block] = createState().release(); } } @@ -1381,6 +1447,7 @@ void TraceBuilder::mergeState(State* state) { // cannot merge fp or spOffset state, so assert they match assert(state->fpValue == m_fpValue); assert(state->spOffset == m_spOffset); + assert(state->curFunc == m_curFunc); if (state->spValue != m_spValue) { // we have two different sp definitions but we know they're equal // because spOffset matched. @@ -1408,23 +1475,33 @@ void TraceBuilder::mergeState(State* state) { if (state->refCountedMemValue != m_refCountedMemValue) { state->refCountedMemValue = nullptr; } + + // Don't attempt to continue tracking caller's available values. + state->callerAvailableValues.clear(); } -void TraceBuilder::useState(Block* block) { - assert(m_snapshots[block]); - State* state = m_snapshots[block]; - m_snapshots[block] = nullptr; +void TraceBuilder::useState(std::unique_ptr state) { m_spValue = state->spValue; m_fpValue = state->fpValue; m_spOffset = state->spOffset; + m_curFunc = state->curFunc; m_thisIsAvailable = state->thisAvailable; m_refCountedMemValue = state->refCountedMemValue; m_localValues = std::move(state->localValues); m_localTypes = std::move(state->localTypes); - delete state; + m_callerAvailableValues = std::move(state->callerAvailableValues); // If spValue is null, we merged two different but equivalent values. // Define a new sp using the known-good spOffset. - if (!m_spValue) genDefSP(m_spOffset); + if (!m_spValue) { + gen(DefSP, StackOffset(m_spOffset), m_fpValue); + } +} + +void TraceBuilder::useState(Block* block) { + assert(m_snapshots[block]); + std::unique_ptr state(m_snapshots[block]); + m_snapshots[block] = nullptr; + useState(std::move(state)); } void TraceBuilder::clearTrackedState() { @@ -1435,6 +1512,7 @@ void TraceBuilder::clearTrackedState() { for (uint32_t i = 0; i < m_localTypes.size(); i++) { m_localTypes[i] = Type::None; } + m_callerAvailableValues.clear(); m_spValue = m_fpValue = nullptr; m_spOffset = 0; m_thisIsAvailable = false; @@ -1506,7 +1584,7 @@ SSATmp* TraceBuilder::optimizeWork(IRInstruction* inst) { FTRACE(1, "{}{}\n", indent(), inst->toString()); // copy propagation on inst source operands - Simplifier::copyProp(inst); + copyProp(inst); SSATmp* result = nullptr; if (m_enableCse && inst->canCSE()) { @@ -1676,12 +1754,15 @@ void TraceBuilder::killLocalValue(uint32_t id) { } bool TraceBuilder::anyLocalHasValue(SSATmp* tmp) const { - for (size_t id = 0; id < m_localValues.size(); id++) { - if (m_localValues[id] == tmp) { - return true; - } - } - return false; + return std::find(m_localValues.begin(), + m_localValues.end(), + tmp) != m_localValues.end(); +} + +bool TraceBuilder::callerLocalHasValue(SSATmp* tmp) const { + return std::find(m_callerAvailableValues.begin(), + m_callerAvailableValues.end(), + tmp) != m_callerAvailableValues.end(); } // diff --git a/hphp/runtime/vm/translator/hopt/tracebuilder.h b/hphp/runtime/vm/translator/hopt/tracebuilder.h index 61573a495..09c7af546 100644 --- a/hphp/runtime/vm/translator/hopt/tracebuilder.h +++ b/hphp/runtime/vm/translator/hopt/tracebuilder.h @@ -39,6 +39,15 @@ public: ~TraceBuilder(); + void beginInlining(const Func* target, + SSATmp* calleeFP, + SSATmp* calleeSP, + SSATmp* prevSP, + int32_t prevSPOff); + void endInlining(); + + void setLocalValue(unsigned id, SSATmp* value); + void setEnableCse(bool val) { m_enableCse = val; } void setEnableSimplification(bool val) { m_enableSimplification = val; } @@ -57,10 +66,6 @@ public: // Run one more pass of simplification on this builder's trace. void optimizeTrace(); - SSATmp* getFP() { - return m_fpValue; - } - /* * Create an IRInstruction attached to this Trace, and allocate a * destination SSATmp for it. Uses the same argument list format as @@ -83,8 +88,6 @@ public: SSATmp* genDefCns(const StringData* cnsName, SSATmp* val); SSATmp* genConcat(SSATmp* tl, SSATmp* tr); - void genDefCls(PreClass*, const Opcode* after); - void genDefFunc(Func*); SSATmp* genLdThis(Trace* trace); SSATmp* genLdCtx(const Func* func); @@ -140,9 +143,6 @@ public: SSATmp* genNewObj(int32_t numParams, SSATmp* cls); SSATmp* genNewArray(int32_t capacity); SSATmp* genNewTuple(int32_t numArgs, SSATmp* sp); - SSATmp* genDefActRec(SSATmp* func, SSATmp* objOrClass, int32_t numArgs, - const StringData* invName); - SSATmp* genFreeActRec(); void genGuardLoc(uint32_t id, Type type, Trace* exitTrace); void genGuardStk(uint32_t id, Type type, Trace* exitTrace); void genAssertStk(uint32_t id, Type type); @@ -216,8 +216,6 @@ public: uint32_t numOpnds, SSATmp** opnds); SSATmp* genLdStack(int32_t stackOff, Type type); - SSATmp* genDefFP(); - SSATmp* genDefSP(int32_t spOffset); SSATmp* genLdStackAddr(SSATmp* sp, int64_t offset); SSATmp* genLdStackAddr(int64_t offset) { return genLdStackAddr(m_spValue, offset); @@ -411,11 +409,11 @@ private: void killCse(); void killLocals(); void killLocalValue(uint32_t id); - void setLocalValue(unsigned id, SSATmp* value); void setLocalType(uint32_t id, Type type); SSATmp* getLocalValue(unsigned id) const; - bool isValueAvailable(SSATmp* tmp) const; - bool anyLocalHasValue(SSATmp* tmp) const; + bool isValueAvailable(SSATmp*) const; + bool anyLocalHasValue(SSATmp*) const; + bool callerLocalHasValue(SSATmp*) const; void updateLocalRefValues(SSATmp* oldRef, SSATmp* newRef); void updateTrackedState(IRInstruction* inst); void clearTrackedState(); @@ -425,17 +423,23 @@ private: } void genStLocAux(uint32_t id, SSATmp* t0, bool genStoreType); - // saved state information associated with the start of a block + // Saved state information associated with the start of a block, or + // for the caller of an inlined function. struct State { - SSATmp *spValue, *fpValue; + SSATmp* spValue; + SSATmp* fpValue; + SSATmp* curFunc; int32_t spOffset; bool thisAvailable; std::vector localValues; std::vector localTypes; SSATmp* refCountedMemValue; + std::vector callerAvailableValues; // unordered list }; + std::unique_ptr createState() const; void saveState(Block*); void mergeState(State* s1); + void useState(std::unique_ptr state); void useState(Block*); /* @@ -464,9 +468,7 @@ private: * (2) m_spOffset tracks the offset of the m_spValue from m_fpValue. * * (3) m_curFunc tracks the current function containing the - * generated code; currently, this remains constant during - * tracebuilding but once we implement inlining it'll have to - * be updated to track the context of inlined functions. + * generated code. * * (4) m_cseHash is for common sub-expression elimination of non-constants. * constants are globally available and managed by IRFactory. @@ -499,8 +501,17 @@ private: SSATmp* m_refCountedMemValue; // vectors that track local values & types - std::vector m_localValues; - std::vector m_localTypes; + std::vector m_localValues; + std::vector m_localTypes; + + // Values known to be "available" for the purposes of DecRef to + // DecRefNZ transformations due to locals of the caller for an + // inlined call. + std::vector m_callerAvailableValues; + + // When we're building traces for an inlined callee, the state of + // the caller needs to be preserved here. + std::vector> m_inlineSavedStates; }; template<> diff --git a/hphp/runtime/vm/translator/hopt/vectortranslator-internal.h b/hphp/runtime/vm/translator/hopt/vectortranslator-internal.h index f72bd795f..d21f863d7 100644 --- a/hphp/runtime/vm/translator/hopt/vectortranslator-internal.h +++ b/hphp/runtime/vm/translator/hopt/vectortranslator-internal.h @@ -25,7 +25,7 @@ namespace HPHP { namespace VM { namespace JIT { -#define CTX() m_tb.genDefConst(arGetContextClass(curFrame())) +#define CTX() m_tb.genDefConst(contextClass()) static const MInstrAttr Warn = MIA_warn; static const MInstrAttr Unset = MIA_unset; diff --git a/hphp/runtime/vm/translator/hopt/vectortranslator.cpp b/hphp/runtime/vm/translator/hopt/vectortranslator.cpp index 91bca81e6..f447c5c5b 100644 --- a/hphp/runtime/vm/translator/hopt/vectortranslator.cpp +++ b/hphp/runtime/vm/translator/hopt/vectortranslator.cpp @@ -327,7 +327,8 @@ void HhbcTranslator::VectorTranslator::checkMIState() { baseType = baseType.unbox(); // CGetM or SetM with no unknown property offsets - const bool simpleProp = !mInstrHasUnknownOffsets(m_ni) && (isCGetM || isSetM); + const bool simpleProp = !mInstrHasUnknownOffsets(m_ni, contextClass()) && + (isCGetM || isSetM); // SetM with only one element const bool singlePropSet = isSingle && isSetM && @@ -366,9 +367,9 @@ void HhbcTranslator::VectorTranslator::checkMIState() { * (wantPropSpecializedWarnings) then pretty much in any case the * vector translator will do operations that can throw. * - * Currently this means we have to have a spillStack so the unwinder - * can handle it (TODO(#2162354): eventually we'll hook this into an - * unwind codepath). + * Currently this means we have to have a ExceptionBarrier so the + * unwinder can handle it (TODO(#2162354): eventually we'll hook + * this into an unwind codepath). * * We also handle one special case where we know a spillStack won't * be needed: in a simple CGetM of a single property where hphpc has @@ -377,13 +378,13 @@ void HhbcTranslator::VectorTranslator::checkMIState() { */ if (wantPropSpecializedWarnings()) { if (isCGetM && isSingle && simpleProp) { - auto info = getFinalPropertyOffset(m_ni, m_mii); + auto info = getFinalPropertyOffset(m_ni, contextClass(), m_mii); assert(info.offset != -1); if (info.hphpcType == KindOfInvalid) { - m_ht.spillStack(); + m_ht.exceptionBarrier(); } } else { - m_ht.spillStack(); + m_ht.exceptionBarrier(); } } } @@ -557,7 +558,7 @@ void HhbcTranslator::VectorTranslator::emitBaseLCR() { // Check for Uninit and warn/promote to InitNull as appropriate if (baseType.subtypeOf(Type::Uninit)) { if (mia & MIA_warn) { - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_tb.gen(RaiseUninitLoc, LocalId(base.location.offset)); } if (mia & MIA_define) { @@ -690,7 +691,7 @@ void HhbcTranslator::VectorTranslator::emitBaseG() { static const OpFunc opFuncs[] = {baseG, baseGW, baseGD, baseGWD}; OpFunc opFunc = opFuncs[mia & MIA_base]; SSATmp* gblName = getBase(); - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_base = m_tb.gen(BaseG, m_tb.genDefConst((TCA)opFunc), gblName, @@ -741,7 +742,8 @@ void HhbcTranslator::VectorTranslator::emitIntermediateOp() { void HhbcTranslator::VectorTranslator::emitProp() { const Class* knownCls = nullptr; - const auto propInfo = getPropertyOffset(m_ni, knownCls, m_mii, + const auto propInfo = getPropertyOffset(m_ni, contextClass(), + knownCls, m_mii, m_mInd, m_iInd); auto mia = m_mii.getAttr(m_ni.immVecM[m_mInd]); if (propInfo.offset == -1 || (mia & Unset)) { @@ -793,7 +795,7 @@ void HhbcTranslator::VectorTranslator::emitPropGeneric() { typedef TypedValue* (*OpFunc)(Class*, TypedValue*, TypedValue, MInstrState*); SSATmp* key = getKey(); BUILD_OPTAB(mia, m_base->isA(Type::Obj)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); if (mia & Define) { m_base = genStk(PropDX, cns((TCA)opFunc), CTX(), m_base, key, genMisPtr()); } else { @@ -841,7 +843,7 @@ SSATmp* HhbcTranslator::VectorTranslator::checkInitProp( // init_null_variant. m_tb.hint(Block::Unlikely); if (doWarn && wantPropSpecializedWarnings()) { - // We did the spillStack for this back in emitMPre. + // We did the exceptionBarrier for this back in emitMPre. m_tb.gen(RaiseUndefProp, baseAsObj, key); } if (doDefine) { @@ -858,9 +860,12 @@ SSATmp* HhbcTranslator::VectorTranslator::checkInitProp( ); } -void HhbcTranslator::VectorTranslator::emitPropSpecialized( - const MInstrAttr mia, - PropInfo propInfo) { +Class* HhbcTranslator::VectorTranslator::contextClass() const { + return m_ht.getCurFunc()->cls(); +} + +void HhbcTranslator::VectorTranslator::emitPropSpecialized(const MInstrAttr mia, + PropInfo propInfo) { assert(!(mia & MIA_warn) || !(mia & MIA_unset)); const bool doWarn = mia & MIA_warn; const bool doDefine = mia & MIA_define || mia & MIA_unset; @@ -997,7 +1002,7 @@ void HhbcTranslator::VectorTranslator::emitElem() { SSATmp* uninit = m_tb.genPtrToUninit(); Type baseType = m_base->getType().strip(); if (baseType.subtypeOf(Type::Str)) { - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_tb.gen( RaiseError, cns(StringData::GetStaticString(Strings::OP_NOT_SUPPORTED_STRING)) @@ -1013,7 +1018,7 @@ void HhbcTranslator::VectorTranslator::emitElem() { typedef TypedValue* (*OpFunc)(TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB_HOT(getKeyTypeIS(key), mia); - m_ht.spillStack(); + m_ht.exceptionBarrier(); if (define || unset) { m_base = genStk(define ? ElemDX : ElemUX, cns((TCA)opFunc), m_base, key, genMisPtr()); @@ -1135,7 +1140,7 @@ void HhbcTranslator::VectorTranslator::emitCGetProp() { assert(!m_ni.outLocal); const Class* knownCls = nullptr; - const auto propInfo = getPropertyOffset(m_ni, knownCls, + const auto propInfo = getPropertyOffset(m_ni, contextClass(), knownCls, m_mii, m_mInd, m_iInd); if (propInfo.offset != -1) { emitPropSpecialized(MIA_warn, propInfo); @@ -1148,7 +1153,7 @@ void HhbcTranslator::VectorTranslator::emitCGetProp() { typedef TypedValue (*OpFunc)(Class*, TypedValue*, TypedValue, MInstrState*); SSATmp* key = getKey(); BUILD_OPTAB_HOT(getKeyTypeS(key), m_base->isA(Type::Obj)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = m_tb.gen(CGetProp, cns((TCA)opFunc), CTX(), m_base, key, genMisPtr()); } @@ -1196,7 +1201,7 @@ void HhbcTranslator::VectorTranslator::emitVGetProp() { SSATmp* key = getKey(); typedef TypedValue (*OpFunc)(Class*, TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB_HOT(getKeyTypeS(key), m_base->isA(Type::Obj)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = genStk(VGetProp, cns((TCA)opFunc), CTX(), m_base, key, genMisPtr()); } @@ -1229,7 +1234,7 @@ void HhbcTranslator::VectorTranslator::emitIssetEmptyProp(bool isEmpty) { SSATmp* key = getKey(); typedef uint64_t (*OpFunc)(Class*, TypedValue*, TypedValue); BUILD_OPTAB(isEmpty, m_base->isA(Type::Obj)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = m_tb.gen(isEmpty ? EmptyProp : IssetProp, cns((TCA)opFunc), CTX(), m_base, key); } @@ -1269,7 +1274,7 @@ void HhbcTranslator::VectorTranslator::emitSetProp() { /* If we know the class for the current base, emit a direct property set. */ const Class* knownCls = nullptr; - const auto propInfo = getPropertyOffset(m_ni, knownCls, + const auto propInfo = getPropertyOffset(m_ni, contextClass(), knownCls, m_mii, m_mInd, m_iInd); if (propInfo.offset != -1) { emitPropSpecialized(MIA_define, propInfo); @@ -1287,7 +1292,7 @@ void HhbcTranslator::VectorTranslator::emitSetProp() { typedef TypedValue (*OpFunc)(Class*, TypedValue*, TypedValue, Cell); SSATmp* key = getKey(); BUILD_OPTAB(m_base->isA(Type::Obj)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); SSATmp* result = genStk(SetProp, cns((TCA)opFunc), CTX(), m_base, key, value); VectorEffects ve(result->getInstruction()); @@ -1334,7 +1339,7 @@ void HhbcTranslator::VectorTranslator::emitSetOpProp() { BUILD_OPTAB_ARG(SETOP_OPS, op, m_base->isA(Type::Obj)); # undef SETOP_OP m_tb.genStRaw(m_misBase, RawMemSlot::MisCtx, CTX()); - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = genStk(SetOpProp, cns((TCA)opFunc), m_base, key, value, genMisPtr()); } @@ -1372,7 +1377,7 @@ void HhbcTranslator::VectorTranslator::emitIncDecProp() { # define INCDEC_OP(op) HELPER_TABLE(FILL_ROW, op) BUILD_OPTAB_ARG(INCDEC_OPS, op, m_base->isA(Type::Obj)); # undef INCDEC_OP - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = genStk(IncDecProp, cns((TCA)opFunc), CTX(), m_base, key, genMisPtr()); } @@ -1409,7 +1414,7 @@ void HhbcTranslator::VectorTranslator::emitBindProp() { typedef void (*OpFunc)(Class*, TypedValue*, TypedValue*, RefData*, MInstrState*); BUILD_OPTAB(m_base->isA(Type::Obj)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); genStk(BindProp, cns((TCA)opFunc), CTX(), m_base, key, box, genMisPtr()); m_result = box; } @@ -1558,7 +1563,7 @@ void HhbcTranslator::VectorTranslator::emitCGetElem() { typedef TypedValue (*OpFunc)(TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB_HOT(getKeyTypeIS(key)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = m_tb.gen(CGetElem, cns((TCA)opFunc), m_base, key, genMisPtr()); } @@ -1603,7 +1608,7 @@ void HhbcTranslator::VectorTranslator::emitVGetElem() { SSATmp* key = getKey(); typedef TypedValue (*OpFunc)(TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB(getKeyTypeIS(key)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = genStk(VGetElem, cns((TCA)opFunc), m_base, key, genMisPtr()); } #undef HELPER_TABLE @@ -1643,7 +1648,7 @@ void HhbcTranslator::VectorTranslator::emitIssetEmptyElem(bool isEmpty) { typedef uint64_t (*OpFunc)(TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB_HOT(getKeyTypeIS(key), isEmpty); - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = m_tb.gen(isEmpty ? EmptyElem : IssetElem, cns((TCA)opFunc), m_base, key, genMisPtr()); } @@ -1692,7 +1697,7 @@ void HhbcTranslator::VectorTranslator::emitArrayIsset() { typedef uint64_t (*OpFunc)(ArrayData*, TypedValue*); BUILD_OPTAB(keyType, checkForInt); assert(m_base->isA(Type::Arr)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = m_tb.gen(ArrayIsset, cns((TCA)opFunc), m_base, key); } #undef HELPER_TABLE @@ -1769,9 +1774,10 @@ void HhbcTranslator::VectorTranslator::emitArraySet(SSATmp* key, typedef ArrayData* (*OpFunc)(ArrayData*, TypedValue*, TypedValue, RefData*); BUILD_OPTAB_HOT(keyType, checkForInt, setRef); - // Don't spillStack below because the helper can't throw. It may reenter to - // call destructors so it has a sync point in nativecalls.cpp, but exceptions - // are swallowed at destructor boundaries right now: #2182869. + // Don't exceptionBarrier below because the helper can't throw. It + // may reenter to call destructors so it has a sync point in + // nativecalls.cpp, but exceptions are swallowed at destructor + // boundaries right now: #2182869. if (setRef) { assert(base.location.space == Location::Local || base.location.space == Location::Stack); @@ -1836,7 +1842,7 @@ void HhbcTranslator::VectorTranslator::emitSetElem() { // Emit the appropriate helper call. typedef TypedValue (*OpFunc)(TypedValue*, TypedValue, Cell); BUILD_OPTAB_HOT(getKeyTypeIS(key)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); SSATmp* result = genStk(SetElem, cns((TCA)opFunc), m_base, key, value); VectorEffects ve(result->getInstruction()); m_result = ve.valTypeChanged ? result : value; @@ -1878,7 +1884,7 @@ void HhbcTranslator::VectorTranslator::emitSetOpElem() { # define SETOP_OP(op, bcOp) HELPER_TABLE(FILL_ROW, op) BUILD_OPTAB_ARG(SETOP_OPS, op); # undef SETOP_OP - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = genStk(SetOpElem, cns((TCA)opFunc), m_base, key, getValue(), genMisPtr()); } @@ -1913,7 +1919,7 @@ void HhbcTranslator::VectorTranslator::emitIncDecElem() { # define INCDEC_OP(op) HELPER_TABLE(FILL_ROW, op) BUILD_OPTAB_ARG(INCDEC_OPS, op); # undef INCDEC_OP - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_result = genStk(IncDecElem, cns((TCA)opFunc), m_base, key, genMisPtr()); } #undef HELPER_TABLE @@ -1931,7 +1937,7 @@ void bindElemC(TypedValue* base, TypedValue keyVal, RefData* val, void HhbcTranslator::VectorTranslator::emitBindElem() { SSATmp* key = getKey(); SSATmp* box = getValue(); - m_ht.spillStack(); + m_ht.exceptionBarrier(); genStk(BindElem, cns((TCA)VectorHelpers::bindElemC), m_base, key, box, genMisPtr()); m_result = box; @@ -1964,7 +1970,7 @@ void HhbcTranslator::VectorTranslator::emitUnsetElem() { Type baseType = m_base->getType().strip(); if (baseType.subtypeOf(Type::Str)) { - m_ht.spillStack(); + m_ht.exceptionBarrier(); m_tb.gen(RaiseError, cns(StringData::GetStaticString(Strings::CANT_UNSET_STRING))); return; @@ -1976,7 +1982,7 @@ void HhbcTranslator::VectorTranslator::emitUnsetElem() { typedef void (*OpFunc)(TypedValue*, TypedValue); BUILD_OPTAB_HOT(getKeyTypeIS(key)); - m_ht.spillStack(); + m_ht.exceptionBarrier(); genStk(UnsetElem, cns((TCA)opFunc), m_base, key); } #undef HELPER_TABLE @@ -1991,7 +1997,7 @@ void HhbcTranslator::VectorTranslator::emitVGetNewElem() { void HhbcTranslator::VectorTranslator::emitSetNewElem() { SSATmp* value = getValue(); - m_ht.spillStack(); + m_ht.exceptionBarrier(); SSATmp* result = m_tb.gen(SetNewElem, m_base, value); VectorEffects ve(result->getInstruction()); m_result = ve.valTypeChanged ? result : value; @@ -2007,7 +2013,7 @@ void HhbcTranslator::VectorTranslator::emitIncDecNewElem() { void HhbcTranslator::VectorTranslator::emitBindNewElem() { SSATmp* box = getValue(); - m_ht.spillStack(); + m_ht.exceptionBarrier(); genStk(BindNewElem, m_base, box, genMisPtr()); m_result = box; } diff --git a/hphp/runtime/vm/translator/translator-x64-vector.cpp b/hphp/runtime/vm/translator/translator-x64-vector.cpp index c0be78f51..7517c6e02 100644 --- a/hphp/runtime/vm/translator/translator-x64-vector.cpp +++ b/hphp/runtime/vm/translator/translator-x64-vector.cpp @@ -365,7 +365,7 @@ bool TranslatorX64::useTvResult(const Tracelet& t, // ArrayAccess-related destruction. For now we make the very conservative // assumption that any instruction with statically unknown offsets can // reenter. - bool result = mInstrHasUnknownOffsets(ni); + bool result = mInstrHasUnknownOffsets(ni, curFunc()->cls()); SKTRACE(2, ni.source, "%s (no stack inputs) --> %s\n", __func__, result ? "true" : "false"); return result; @@ -859,6 +859,7 @@ void TranslatorX64::emitPropGeneric(const Tracelet& t, #undef HELPER_TABLE PropInfo getPropertyOffset(const NormalizedInstruction& ni, + Class* ctx, const Class*& baseClass, const MInstrInfo& mii, unsigned mInd, unsigned iInd) { @@ -879,7 +880,6 @@ PropInfo getPropertyOffset(const NormalizedInstruction& ni, if (!name) return PropInfo(); bool accessible; - Class* ctx = curFunc()->cls(); // If we are not in repo-authoriative mode, we need to check that // baseClass cannot change in between requests if (!RuntimeOption::RepoAuthoritative || @@ -915,12 +915,13 @@ PropInfo getPropertyOffset(const NormalizedInstruction& ni, } PropInfo getFinalPropertyOffset(const NormalizedInstruction& ni, + Class* context, const MInstrInfo& mii) { unsigned mInd = ni.immVecM.size() - 1; unsigned iInd = mii.valCount() + 1 + mInd; const Class* cls = nullptr; - return getPropertyOffset(ni, cls, mii, mInd, iInd); + return getPropertyOffset(ni, context, cls, mii, mInd, iInd); } void TranslatorX64::emitPropSpecialized(MInstrAttr const mia, @@ -1041,7 +1042,8 @@ void TranslatorX64::emitProp(const MInstrInfo& mii, __func__, long(a.code.frontier), mInd, iInd); const Class* knownCls = nullptr; - const auto propInfo = getPropertyOffset(*m_curNI, knownCls, mii, + const auto propInfo = getPropertyOffset(*m_curNI, curFunc()->cls(), + knownCls, mii, mInd, iInd); if (propInfo.offset == -1) { @@ -1255,8 +1257,8 @@ void TranslatorX64::emitCGetProp(const Tracelet& t, * access. */ const Class* knownCls = nullptr; - const auto propInfo = getPropertyOffset(*m_curNI, knownCls, - mii, mInd, iInd); + const auto propInfo = getPropertyOffset(*m_curNI, curFunc()->cls(), + knownCls, mii, mInd, iInd); if (propInfo.offset != -1) { emitPropSpecialized(MIA_warn, knownCls, propInfo.offset, mInd, iInd, rBase); @@ -1672,8 +1674,8 @@ void TranslatorX64::emitSetProp(const Tracelet& t, * set. */ const Class* knownCls = nullptr; - const auto propInfo = getPropertyOffset(*m_curNI, knownCls, - mii, mInd, iInd); + const auto propInfo = getPropertyOffset(*m_curNI, curFunc()->cls(), + knownCls, mii, mInd, iInd); if (propInfo.offset != -1 && !ni.outLocal && !ni.outStack) { emitPropSpecialized(MIA_define, knownCls, propInfo.offset, mInd, iInd, rBase); @@ -2504,7 +2506,8 @@ void TranslatorX64::emitMPre(const Tracelet& t, const MInstrInfo& mii, unsigned& mInd, unsigned& iInd, LazyScratchReg& rBase) { - if (!mInstrHasUnknownOffsets(ni) && !useTvResult(t, ni, mii) && + if (!mInstrHasUnknownOffsets(ni, curFunc()->cls()) && + !useTvResult(t, ni, mii) && (ni.mInstrOp() == OpCGetM || ni.mInstrOp() == OpSetM)) { m_vecState->setNoMIState(); } @@ -2719,7 +2722,7 @@ isNormalPropertyAccess(const NormalizedInstruction& i, } bool -mInstrHasUnknownOffsets(const NormalizedInstruction& ni) { +mInstrHasUnknownOffsets(const NormalizedInstruction& ni, Class* context) { const MInstrInfo& mii = getMInstrInfo(ni.mInstrOp()); unsigned mi = 0; unsigned ii = mii.valCount() + 1; @@ -2727,7 +2730,7 @@ mInstrHasUnknownOffsets(const NormalizedInstruction& ni) { MemberCode mc = ni.immVecM[mi]; if (mcodeMaybePropName(mc)) { const Class* cls = nullptr; - if (getPropertyOffset(ni, cls, mii, mi, ii).offset == -1) { + if (getPropertyOffset(ni, context, cls, mii, mi, ii).offset == -1) { return true; } ++ii; diff --git a/hphp/runtime/vm/translator/translator-x64.cpp b/hphp/runtime/vm/translator/translator-x64.cpp index 8770bfad1..f7494e579 100644 --- a/hphp/runtime/vm/translator/translator-x64.cpp +++ b/hphp/runtime/vm/translator/translator-x64.cpp @@ -1555,6 +1555,8 @@ void TranslatorX64::unprotectCode() { void TranslatorX64::emitStackCheck(int funcDepth, Offset pc) { + funcDepth += kMaxJITInlineStackCells * sizeof(Cell); + uint64_t stackMask = cellsToBytes(RuntimeOption::EvalVMStackElms) - 1; a. mov_reg64_reg64(rVmSp, rScratch); // copy to destroy a. and_imm64_reg64(stackMask, rScratch); @@ -11640,9 +11642,22 @@ TranslatorX64::TranslatorX64() // the return stack. m_callToExit = emitServiceReq(SRFlags(SRAlign | SRJmpInsteadOfRet), REQ_EXIT, 0ull); + + /* + * Helpers for returning from a function where the ActRec was pushed + * by the interpreter. + */ m_retHelper = emitRetFromInterpretedFrame(); m_genRetHelper = emitRetFromInterpretedGeneratorFrame(); + /* + * Returning from a function where the ActRec was pushed by an + * inlined call. This is separate from m_retHelper just for + * debugability---it does the same thing. + */ + m_retInlHelper = emitRetFromInterpretedFrame(); + FTRACE(1, "retInlHelper: {}\n", (void*)m_retInlHelper); + moveToAlign(astubs); m_resumeHelperRet = astubs.code.frontier; emitPopRetIntoActRec(astubs); diff --git a/hphp/runtime/vm/translator/translator-x64.h b/hphp/runtime/vm/translator/translator-x64.h index b28ac4669..1e01fff9c 100644 --- a/hphp/runtime/vm/translator/translator-x64.h +++ b/hphp/runtime/vm/translator/translator-x64.h @@ -120,7 +120,6 @@ class TranslatorX64 : public Translator template friend class JccBlock; template friend class IfElseBlock; friend class UnlikelyIfBlock; - typedef HPHP::DataType DataType; typedef tbb::concurrent_hash_map SignalStubMap; typedef void (*sigaction_t)(int, siginfo_t*, void*); @@ -170,6 +169,7 @@ class TranslatorX64 : public Translator sigaction_t m_segvChain; TCA m_callToExit; TCA m_retHelper; + TCA m_retInlHelper; TCA m_genRetHelper; TCA m_stackOverflowHelper; TCA m_irPopRHelper; @@ -752,6 +752,10 @@ PSEUDOINSTRS return m_retHelper; } + TCA getRetFromInlinedFrame() { + return m_retInlHelper; + } + TCA getRetFromInterpretedGeneratorFrame() { return m_genRetHelper; } @@ -1162,7 +1166,9 @@ SrcKey nextSrcKey(const Tracelet& t, const NormalizedInstruction& i); bool isNormalPropertyAccess(const NormalizedInstruction& i, int propInput, int objInput); -bool mInstrHasUnknownOffsets(const NormalizedInstruction& i); + +bool mInstrHasUnknownOffsets(const NormalizedInstruction& i, + Class* contextClass); struct PropInfo { PropInfo() @@ -1179,10 +1185,12 @@ struct PropInfo { }; PropInfo getPropertyOffset(const NormalizedInstruction& ni, + Class* contextClass, const Class*& baseClass, const MInstrInfo& mii, unsigned mInd, unsigned iInd); PropInfo getFinalPropertyOffset(const NormalizedInstruction&, + Class* contextClass, const MInstrInfo&); bool isSupportedCGetM_LE(const NormalizedInstruction& i); diff --git a/hphp/runtime/vm/translator/translator.cpp b/hphp/runtime/vm/translator/translator.cpp index 68b2f424a..2d499a883 100644 --- a/hphp/runtime/vm/translator/translator.cpp +++ b/hphp/runtime/vm/translator/translator.cpp @@ -26,6 +26,8 @@ #include #include +#include "folly/Conv.h" + #include "util/trace.h" #include "util/biased_coin.h" @@ -54,6 +56,42 @@ static __thread BiasedCoin *dbgTranslateCoin; Translator* transl; Lease Translator::s_writeLease; +struct TraceletContext { + TraceletContext() = delete; + + TraceletContext(Tracelet* t, const TypeMap& initialTypes) + : m_t(t) + , m_numJmps(0) + , m_aliasTaint(false) + , m_varEnvTaint(false) + { + for (auto& kv : initialTypes) { + TRACE(1, "%s\n", + Trace::prettyNode("InitialType", kv.first, kv.second).c_str()); + m_currentMap[kv.first] = t->newDynLocation(kv.first, kv.second); + } + } + + Tracelet* m_t; + ChangeMap m_currentMap; + DepMap m_dependencies; + DepMap m_resolvedDeps; // dependencies resolved by static analysis + LocationSet m_changeSet; + LocationSet m_deletedSet; + int m_numJmps; + bool m_aliasTaint; + bool m_varEnvTaint; + + RuntimeType currentType(const Location& l) const; + DynLocation* recordRead(const InputInfo& l, bool useHHIR, + DataType staticType = KindOfInvalid); + void recordWrite(DynLocation* dl, NormalizedInstruction* source); + void recordDelete(const Location& l); + void recordJmp(); + void aliasTaint(); + void varEnvTaint(); +}; + void InstrStream::append(NormalizedInstruction* ni) { if (last) { assert(first); @@ -237,12 +275,9 @@ Translator::locPhysicalOffset(Location l, const Func* f) { return -((l.offset + 1) * iterInflator + localsToSkip); } -// liveType -- -// Return the live type of a location. If we've already plucked -// out the cell ... RuntimeType Translator::liveType(Location l, const Unit& u) { Cell *outer; - switch(l.space) { + switch (l.space) { case Location::Stack: // Stack accesses must be to addresses pushed before // translation time; if they are to addresses pushed after, @@ -280,6 +315,8 @@ RuntimeType Translator::liveType(Location l, const Unit& u) { RuntimeType Translator::liveType(const Cell* outer, const Location& l) { + always_assert(analysisDepth() == 0); + if (!outer) { // An undefined global; starts out as a variant null return RuntimeType(KindOfRef, KindOfNull); @@ -311,11 +348,18 @@ Translator::liveType(const Cell* outer, const Location& l) { } RuntimeType Translator::outThisObjectType() { - // Use the current method's context class (ctx) as a constraint. - // For instance methods, if $this is non-null, we are guaranteed - // that $this is an instance of ctx or a class derived from - // ctx. Zend allows this assumption to be violated but we have - // deliberately chosen to diverge from them here. + /* + * Use the current method's context class (ctx) as a constraint. + * For instance methods, if $this is non-null, we are guaranteed + * that $this is an instance of ctx or a class derived from + * ctx. Zend allows this assumption to be violated but we have + * deliberately chosen to diverge from them here. + * + * Note that if analysisDepth() != 0 we'll have !hasThis() here, + * because our fake ActRec has no $this, but we'll still return the + * correct object type because arGetContextClass() looks at + * ar->m_func's class for methods. + */ const Class *ctx = curFunc()->isMethod() ? arGetContextClass(curFrame()) : nullptr; if (ctx) { @@ -516,7 +560,9 @@ static const int kTooPolyRet = 6; static std::pair predictMVec(const NormalizedInstruction* ni) { - auto info = getFinalPropertyOffset(*ni, getMInstrInfo(ni->mInstrOp())); + auto info = getFinalPropertyOffset(*ni, + curFunc()->cls(), + getMInstrInfo(ni->mInstrOp())); if (info.offset != -1 && info.hphpcType != KindOfInvalid) { FTRACE(1, "prediction for CGetM prop: {}, hphpc\n", int(info.hphpcType)); @@ -2493,7 +2539,7 @@ RuntimeType TraceletContext::currentType(const Location& l) const { if (!mapGet(m_currentMap, l, &dl)) { assert(!mapContains(m_deletedSet, l)); assert(!mapContains(m_changeSet, l)); - return Translator::liveType(l, *curUnit()); + return tx64->liveType(l, *curUnit()); } return dl->rtt; } @@ -2516,7 +2562,7 @@ DynLocation* TraceletContext::recordRead(const InputInfo& ii, m_resolvedDeps[l] = dl; } } else { - RuntimeType rtt = Translator::liveType(l, *curUnit()); + RuntimeType rtt = tx64->liveType(l, *curUnit()); assert(rtt.isIter() || !rtt.isVagueValue()); // Allocate a new DynLocation to represent this and store it in the // current map. @@ -2931,6 +2977,230 @@ static bool checkTaintFuncs(StringData* name) { return name->isame(s_extract); } +static const NormalizedInstruction* +findFPushForCall(const FPIEnt* fpi, + const NormalizedInstruction* fcall) { + for (auto* ni = fcall->prev; ni; ni = ni->prev) { + if (ni->source.offset() == fpi->m_fpushOff) { + return ni; + } + } + return nullptr; +} + +/* + * Check whether the a given FCall should be analyzed for possible + * inlining or not. + */ +static bool shouldAnalyzeCallee(const NormalizedInstruction* fcall) { + auto const numArgs = fcall->imm[0].u_IVA; + auto const target = fcall->funcd; + auto const fpi = curFunc()->findFPI(fcall->source.m_offset); + auto const pushOp = curUnit()->getOpcode(fpi->m_fpushOff); + + if (!RuntimeOption::RepoAuthoritative) return false; + + // Note: the IR assumes that $this is available in all inlined object + // methods, which will need to be updated when we support + // OpFPushClsMethod here. + if (pushOp != OpFPushFuncD && pushOp != OpFPushObjMethodD) { + FTRACE(1, "analyzeCallee: push op ({}) was not supported\n", + opcodeToName(pushOp)); + return false; + } + + if (!target) { + FTRACE(1, "analyzeCallee: target func not known\n"); + return false; + } + if (target->isBuiltin()) { + FTRACE(1, "analyzeCallee: target func is a builtin\n"); + return false; + } + + constexpr int kMaxSubtraceAnalysisDepth = 2; + if (tx64->analysisDepth() + 1 >= kMaxSubtraceAnalysisDepth) { + FTRACE(1, "analyzeCallee: max inlining depth reached\n"); + return false; + } + + if (numArgs != target->numParams()) { + FTRACE(1, "analyzeCallee: param count mismatch {} != {}\n", + numArgs, target->numParams()); + return false; + } + if (numArgs != 0) { + FTRACE(1, "analyzeCallee: currently ignoring calls with parameters\n"); + return false; + } + + if (!findFPushForCall(fpi, fcall)) { + FTRACE(1, "analyzeCallee: push instruction was in a different " + "tracelet\n"); + return false; + } + + return true; +} + +void Translator::analyzeCallee(TraceletContext& tas, + Tracelet& parent, + NormalizedInstruction* fcall) { + always_assert(m_useHHIR); + if (!shouldAnalyzeCallee(fcall)) return; + + auto const numArgs = fcall->imm[0].u_IVA; + auto const target = fcall->funcd; + + /* + * Prepare a map for all the known information about the argument + * types. + * + * Also, fill out KindOfUninit for any remaining locals. The point + * here is that the subtrace can't call liveType for a local or + * stack location (since our ActRec is fake), so we need them all in + * the TraceletContext. + * + * If any of the argument types are unknown (including inner-types + * of KindOfRefs), we don't really try to analyze the callee. It + * might be possible to do this but we'll need to modify the + * analyzer to support unknown input types before there are any + * NormalizedInstructions in the Tracelet. + */ + TypeMap initialMap; + LocationSet callerArgLocs; + for (int i = 0; i < numArgs; ++i) { + auto callerLoc = Location(Location::Stack, fcall->stackOff - i - 1); + auto calleeLoc = Location(Location::Local, numArgs - i - 1); + auto type = tas.currentType(callerLoc); + + callerArgLocs.insert(callerLoc); + + if (type.isVagueValue()) { + FTRACE(1, "analyzeCallee: {} has unknown type\n", callerLoc.pretty()); + return; + } + if (type.isValue() && type.isRef() && + type.innerType() == KindOfInvalid) { + FTRACE(1, "analyzeCallee: {} has unknown inner-refdata type\n", + callerLoc.pretty()); + return; + } + + FTRACE(2, "mapping arg{} locs {} -> {} :: {}\n", + numArgs - i - 1, + callerLoc.pretty(), + calleeLoc.pretty(), + type.pretty()); + initialMap[calleeLoc] = type; + } + for (int i = numArgs; i < target->numLocals(); ++i) { + initialMap[Location(Location::Local, i)] = RuntimeType(KindOfUninit); + } + + /* + * When reentering analyze to generate a Tracelet for a callee, + * currently we handle this by creating a fake ActRec on the stack. + * + * This is mostly a compromise to deal with existing code during the + * analysis phase which pretty liberally inspects live VM state. + */ + ActRec fakeAR; + fakeAR.m_savedRbp = reinterpret_cast(curFrame()); + fakeAR.m_savedRip = 0xbaabaa; // should never be inspected + fakeAR.m_func = fcall->funcd; + fakeAR.m_soff = 0xb00b00; // should never be inspected + fakeAR.m_numArgsAndCtorFlag = numArgs; + fakeAR.m_varEnv = nullptr; + + /* + * Even when inlining an object method, we can leave the m_this as + * null. See outThisObjectType(). + */ + fakeAR.m_this = nullptr; + + FTRACE(1, "analyzing sub trace =================================\n"); + auto const oldFP = vmfp(); + auto const oldSP = vmsp(); + auto const oldPC = vmpc(); + auto const oldAnalyzeCalleeDepth = m_analysisDepth++; + vmpc() = nullptr; // should never be used + vmsp() = nullptr; // should never be used + vmfp() = reinterpret_cast(&fakeAR); + auto restoreFrame = [&]{ + vmfp() = oldFP; + vmsp() = oldSP; + vmpc() = oldPC; + m_analysisDepth = oldAnalyzeCalleeDepth; + }; + SCOPE_EXIT { + // It's ok to restoreFrame() twice---we have it in this scope + // handler to ensure it still happens if we exit via an exception. + restoreFrame(); + FTRACE(1, "finished sub trace ===================================\n"); + }; + + auto subTrace = analyze(SrcKey(target, target->base()), initialMap); + + /* + * Verify the target trace actually ended with a return, or we have + * no business doing anything based on it right now. + */ + if (!subTrace->m_instrStream.last || + (subTrace->m_instrStream.last->op() != OpRetC && + subTrace->m_instrStream.last->op() != OpRetV)) { + FTRACE(1, "analyzeCallee: callee did not end in a return\n"); + return; + } + + /* + * Disabled for now: + * + * Propagate the return type to our caller. If the return type is + * not vague, it will hold if we can inline the trace. + * + * This isn't really a sensible thing to do if we aren't also going + * to inline the callee, however, because the return type may only + * be what it is due to other output predictions (CGetMs or FCall) + * inside the callee. This means we would need to check the return + * value in the caller still as if it were a predicted return type. + */ + Location retVal(Location::Stack, 0); + auto it = subTrace->m_changes.find(retVal); + assert(it != subTrace->m_changes.end()); + FTRACE(1, "subtrace return: {}\n", it->second->pretty()); + if (false) { + if (!it->second->rtt.isVagueValue() && !it->second->rtt.isRef()) { + FTRACE(1, "changing callee's return type from {} to {}\n", + fcall->outStack->rtt.pretty(), + it->second->pretty()); + + fcall->outputPredicted = true; + fcall->outputPredictionStatic = false; + fcall->outStack = parent.newDynLocation(fcall->outStack->location, + it->second->rtt); + tas.recordWrite(fcall->outStack, fcall); + } + } + + /* + * In order for relaxDeps not to relax guards on things we may + * potentially have depended on here, we need to ensure that the + * call instruction depends on all the inputs we've used. + * + * What we probably want to do is modify getOutputUsage to be aware + * of callee-uses of parameters. + * + * For now this assert is just protecting the known breakage if we + * start doing analyzeCallee on things with parameters. + */ + restoreFrame(); + assert(callerArgLocs.empty()); + + FTRACE(1, "analyzeCallee: inline candidate\n"); + fcall->calleeTrace = std::move(subTrace); +} + /* * analyze -- * @@ -2970,7 +3240,8 @@ static bool checkTaintFuncs(StringData* name) { * we store the RuntimeTypes from the TraceletContext right after the * instruction executes into the various output fields. */ -std::unique_ptr Translator::analyze(SrcKey sk) { +std::unique_ptr Translator::analyze(SrcKey sk, + const TypeMap& initialTypes) { std::unique_ptr retval(new Tracelet()); auto& t = *retval; t.m_sk = sk; @@ -2979,9 +3250,8 @@ std::unique_ptr Translator::analyze(SrcKey sk) { DEBUG_ONLY const int lineNum = curUnit()->getLineNumber(t.m_sk.offset()); DEBUG_ONLY const char* funcName = curFunc()->fullName()->data(); - TRACE(3, "Translator::analyze %s:%d %s\n", - file, lineNum, funcName); - TraceletContext tas(&t); + TRACE(1, "Translator::analyze %s:%d %s\n", file, lineNum, funcName); + TraceletContext tas(&t, initialTypes); ImmStack immStack; int stackFrameOffset = 0; int oldStackFrameOffset = 0; @@ -3156,12 +3426,12 @@ std::unique_ptr Translator::analyze(SrcKey sk) { // agree with the inputs and outputs. assert(getStackDelta(*ni) == (stackFrameOffset - oldStackFrameOffset)); // If this instruction decreased the depth of the stack, mark the - // appropriate stack locations as "dead" + // appropriate stack locations as "dead". But we need to leave + // them in the TraceletContext until after analyzeCallee (if this + // is an FCall). if (stackFrameOffset < oldStackFrameOffset) { for (int i = stackFrameOffset; i < oldStackFrameOffset; ++i) { - Location loc(Location::Stack, i); - tas.recordDelete(loc); - ni->deadLocs.push_back(loc); + ni->deadLocs.push_back(Location(Location::Stack, i)); } } @@ -3170,8 +3440,27 @@ std::unique_ptr Translator::analyze(SrcKey sk) { t.m_instrStream.append(ni); ++t.m_numOpcodes; - annotate(ni); + /* + * The annotation step attempts to track Func*'s associated with + * given FCalls when the FPush is in a different tracelet. + * + * When we're analyzing a callee, we can't do this because we may + * have class information in some of our RuntimeTypes that is only + * true because of who the caller was. (Normally it is only there + * if it came from static analysis.) + */ + if (analysisDepth() == 0) { + annotate(ni); + } + analyzeInstr(t, *ni); + if (m_useHHIR && ni->op() == OpFCall) { + analyzeCallee(tas, t, ni); + } + + for (auto& l : ni->deadLocs) { + tas.recordDelete(l); + } if (debug) { // The interpreter has lots of nice sanity assertions in debug mode @@ -3278,6 +3567,7 @@ breakBB: --t.m_numOpcodes; } } + // Peephole optimizations may leave the bytecode stream in a state that is // inconsistent and troubles HHIR emission, so don't do it if HHIR is in use if (!m_useHHIR) { @@ -3305,10 +3595,12 @@ breakBB: return retval; } -Translator::Translator() : - m_resumeHelper(nullptr), - m_useHHIR(false), - m_createdTime(Timer::GetCurrentTimeMicros()) { +Translator::Translator() + : m_resumeHelper(nullptr) + , m_useHHIR(false) + , m_createdTime(Timer::GetCurrentTimeMicros()) + , m_analysisDepth(0) +{ initInstrInfo(); } @@ -3587,4 +3879,27 @@ const Func* lookupImmutableMethod(const Class* cls, const StringData* name, return func; } +std::string traceletShape(const Tracelet& trace) { + std::string ret; + + for (auto ni = trace.m_instrStream.first; ni; ni = ni->next) { + using folly::toAppend; + + toAppend(opcodeToName(ni->op()), &ret); + if (ni->immVec.isValid()) { + toAppend( + "<", + locationCodeString(ni->immVec.locationCode()), + &ret); + for (auto& mc : ni->immVecM) { + toAppend(" ", memberCodeString(mc), &ret); + } + toAppend(">", &ret); + } + toAppend(" ", &ret); + } + + return ret; +} + } } } diff --git a/hphp/runtime/vm/translator/translator.h b/hphp/runtime/vm/translator/translator.h index 4f4f95c97..1b734dcba 100644 --- a/hphp/runtime/vm/translator/translator.h +++ b/hphp/runtime/vm/translator/translator.h @@ -13,7 +13,6 @@ | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ - #ifndef incl_HPHP_TRANSLATOR_H_ #define incl_HPHP_TRANSLATOR_H_ @@ -158,7 +157,7 @@ typedef hphp_hash_set SrcKeySet; #define SKTRACE(level, sk, ...) \ ONTRACE(level, (sk).trace(__VA_ARGS__)) -class NormalizedInstruction; +struct NormalizedInstruction; // A DynLocation is a Location-in-execution: a location, along with // whatever is known about its runtime type. @@ -253,6 +252,9 @@ enum TXFlags { Native = MachineCode | Simple }; +struct Tracelet; +struct TraceletContext; + // A NormalizedInstruction has been decorated with its typed inputs and // outputs. class NormalizedInstruction { @@ -296,6 +298,14 @@ class NormalizedInstruction { */ std::vector immVecClasses; + /* + * On certain FCalls, we can inspect the callee and generate a + * tracelet with information about what happens over there. + * + * The HHIR translator uses this to possibly inline callees. + */ + std::unique_ptr calleeTrace; + unsigned checkedInputs; // StackOff: logical delta at *start* of this instruction to // stack at tracelet entry. @@ -391,32 +401,30 @@ class NormalizedInstruction { const Unit* unit() const; Offset offset() const; - NormalizedInstruction() : - next(nullptr), - prev(nullptr), - source(), - inputs(), - outStack(nullptr), - outLocal(nullptr), - outLocal2(nullptr), - outStack2(nullptr), - outStack3(nullptr), - deadLocs(), - checkedInputs(0), - hasConstImm(false), - invertCond(false), - ignoreInnerType(false), - skipSync(false), - grouped(false), - guardedThis(false), - guardedCls(false), - noSurprise(false), - noCtor(false), - noOp(false), - interp(false), - directCall(false), - inlineReturn(false), - m_txFlags(Interp) { + NormalizedInstruction() + : next(nullptr) + , prev(nullptr) + , outStack(nullptr) + , outLocal(nullptr) + , outLocal2(nullptr) + , outStack2(nullptr) + , outStack3(nullptr) + , checkedInputs(0) + , hasConstImm(false) + , invertCond(false) + , ignoreInnerType(false) + , skipSync(false) + , grouped(false) + , guardedThis(false) + , guardedCls(false) + , noSurprise(false) + , noCtor(false) + , noOp(false) + , interp(false) + , directCall(false) + , inlineReturn(false) + , m_txFlags(Interp) + { memset(imm, 0, sizeof(imm)); } @@ -465,6 +473,9 @@ class NormalizedInstruction { std::string toString() const; }; +// Return a summary string of the bytecode in a tracelet. +std::string traceletShape(const Tracelet&); + class TranslationFailedExc : public std::exception { public: const char* m_file; // must be static @@ -521,6 +532,7 @@ class GuardType { * and output. */ typedef hphp_hash_map ChangeMap; +typedef hphp_hash_map TypeMap; typedef ChangeMap DepMap; typedef hphp_hash_set LocationSet; typedef hphp_hash_map DynLocTypeMap; @@ -691,42 +703,6 @@ struct Tracelet : private boost::noncopyable { void print(std::ostream& out) const; }; -struct TraceletContext { - Tracelet* m_t; - ChangeMap m_currentMap; - DepMap m_dependencies; - DepMap m_resolvedDeps; // dependencies resolved by static analysis - LocationSet m_changeSet; - LocationSet m_deletedSet; - int m_numJmps; - bool m_aliasTaint; - bool m_varEnvTaint; - - TraceletContext() - : m_t(nullptr) - , m_numJmps(0) - , m_aliasTaint(false) - , m_varEnvTaint(false) - {} - TraceletContext(Tracelet* t) - : m_t(t) - , m_numJmps(0) - , m_aliasTaint(false) - , m_varEnvTaint(false) - {} - RuntimeType currentType(const Location& l) const; - DynLocation* recordRead(const InputInfo& l, bool useHHIR, - DataType staticType = KindOfInvalid); - void recordWrite(DynLocation* dl, NormalizedInstruction* source); - void recordDelete(const Location& l); - void recordJmp(); - void aliasTaint(); - void varEnvTaint(); - - private: - static bool canBeAliased(const DynLocation* dl); -}; - enum TransKind { TransNormal = 0, TransNormalIR = 1, @@ -821,6 +797,9 @@ private: friend struct TraceletContext; void analyzeSecondPass(Tracelet& t); + void analyzeCallee(TraceletContext&, + Tracelet& parent, + NormalizedInstruction* fcall); void preInputApplyMetaData(Unit::MetaHandle, NormalizedInstruction*); bool applyInputMetaData(Unit::MetaHandle&, NormalizedInstruction* ni, @@ -849,8 +828,8 @@ private: const GuardType& specType); - static RuntimeType liveType(Location l, const Unit &u); - static RuntimeType liveType(const Cell* outer, const Location& l); + RuntimeType liveType(Location l, const Unit &u); + RuntimeType liveType(const Cell* outer, const Location& l); void consumeStackEntry(Tracelet* tlet, NormalizedInstruction* ni); void produceStackEntry(Tracelet* tlet, NormalizedInstruction* ni); @@ -981,9 +960,17 @@ public: return id; } + /* + * Create a Tracelet for the given SrcKey, which must actually be + * the current VM frame. + * + * XXX The analysis pass will inspect the live state of the VM stack + * as needed to determine the current types of in-flight values. + */ + std::unique_ptr analyze(SrcKey sk, const TypeMap& = TypeMap()); + void postAnalyze(NormalizedInstruction* ni, SrcKey& sk, Tracelet& t, TraceletContext& tas); - std::unique_ptr analyze(SrcKey sk); void advance(Opcode const **instrs); static int locPhysicalOffset(Location l, const Func* f = nullptr); static Location tvToLocation(const TypedValue* tv, const TypedValue* frame); @@ -1018,6 +1005,9 @@ protected: Mutex m_dbgBlacklistLock; bool isSrcKeyInBL(const Unit* unit, const SrcKey& sk); +private: + int m_analysisDepth; + public: void clearDbgBL(); bool addDbgBLPC(PC pc); @@ -1030,6 +1020,11 @@ public: TCA getResumeHelperRet() { return m_resumeHelperRet; } + + int analysisDepth() const { + assert(m_analysisDepth >= 0); + return m_analysisDepth; + } }; int getStackDelta(const NormalizedInstruction& ni); diff --git a/hphp/runtime/vm/translator/unwind-x64.cpp b/hphp/runtime/vm/translator/unwind-x64.cpp index 01e1d2616..4407eddb2 100644 --- a/hphp/runtime/vm/translator/unwind-x64.cpp +++ b/hphp/runtime/vm/translator/unwind-x64.cpp @@ -127,6 +127,9 @@ tc_unwind_personality(int version, _Unwind_Context* context) { assert(version == 1); + FTRACE(2, "unwind: tc_unwind_personality {}\n", + int(tl_regState)); + /* * We don't do anything during the search phase---before attempting * cleanup, we want all deeper frames to have run their object diff --git a/hphp/test/quick/inline_closure_gen.php b/hphp/test/quick/closure_gen.php similarity index 100% rename from hphp/test/quick/inline_closure_gen.php rename to hphp/test/quick/closure_gen.php diff --git a/hphp/test/quick/inline_closure_gen.php.expect b/hphp/test/quick/closure_gen.php.expect similarity index 100% rename from hphp/test/quick/inline_closure_gen.php.expect rename to hphp/test/quick/closure_gen.php.expect diff --git a/hphp/test/quick/exceptions2.php.expectf b/hphp/test/quick/exceptions2.php.expectf index d18d1f4c7..e3373172f 100644 --- a/hphp/test/quick/exceptions2.php.expectf +++ b/hphp/test/quick/exceptions2.php.expectf @@ -1236,4 +1236,3 @@ C::__destruct C::__destruct C::__destruct C::__destruct -C::__destruct diff --git a/hphp/test/quick/exceptions5.php.expectf b/hphp/test/quick/exceptions5.php.expectf index d8f249768..a275b605e 100644 --- a/hphp/test/quick/exceptions5.php.expectf +++ b/hphp/test/quick/exceptions5.php.expectf @@ -1 +1 @@ -HipHop Fatal error: Stack overflow in %s on line 25 +HipHop Fatal error: Stack overflow in %s on line 21 diff --git a/hphp/test/quick/exceptions6.php.expectf b/hphp/test/quick/exceptions6.php.expectf index a275b605e..bff5ad9cf 100644 --- a/hphp/test/quick/exceptions6.php.expectf +++ b/hphp/test/quick/exceptions6.php.expectf @@ -1 +1 @@ -HipHop Fatal error: Stack overflow in %s on line 21 +HipHop Fatal error: Stack overflow in %s on line 26 diff --git a/hphp/test/quick/inline_1.php b/hphp/test/quick/inline_1.php new file mode 100644 index 000000000..9c40b0e58 --- /dev/null +++ b/hphp/test/quick/inline_1.php @@ -0,0 +1,46 @@ +foo(12); +} + +test4(new Test()); diff --git a/hphp/test/quick/inline_2.php.expect b/hphp/test/quick/inline_2.php.expect new file mode 100644 index 000000000..e69de29bb diff --git a/hphp/test/quick/inline_cgetm.php b/hphp/test/quick/inline_cgetm.php new file mode 100644 index 000000000..bd7c6bf09 --- /dev/null +++ b/hphp/test/quick/inline_cgetm.php @@ -0,0 +1,31 @@ +uniqueVar = "a string"; + } + + public function getX() { + // TODO test something that will throw, make sure stack + // materialization worked. + return $this->uniqueVar; + } + + private $uniqueVar; +} + +function test5() { + $obj = new CGetMTest(); + echo $obj->getX(); + echo "\n"; +} + +function test9() { + // $this is on the stack; can we still handle incref / decref + // elimination (not right now). + echo (new CGetMTest())->getX(); + echo "\n"; +} + +test5(); +test9(); diff --git a/hphp/test/quick/inline_cgetm.php.expect b/hphp/test/quick/inline_cgetm.php.expect new file mode 100644 index 000000000..fded72733 --- /dev/null +++ b/hphp/test/quick/inline_cgetm.php.expect @@ -0,0 +1,2 @@ +a string +a string diff --git a/hphp/test/quick/inline_cgetm_complex.php b/hphp/test/quick/inline_cgetm_complex.php new file mode 100644 index 000000000..a8aaebf25 --- /dev/null +++ b/hphp/test/quick/inline_cgetm_complex.php @@ -0,0 +1,26 @@ +val = "string"; + } +} + +class CGetM { + private $x; + + public function __construct() { + $this->x = array(new Obj); + } + + public function getVal() { + return $this->x[0]->val; + } +} + +function main(CGetM $k) { + return $k->getVal(); +} + +main(new CGetM()); diff --git a/hphp/test/quick/inline_cgetm_complex.php.expect b/hphp/test/quick/inline_cgetm_complex.php.expect new file mode 100644 index 000000000..e69de29bb diff --git a/hphp/test/quick/inline_cgetm_dynamic.php b/hphp/test/quick/inline_cgetm_dynamic.php new file mode 100644 index 000000000..b81e30779 --- /dev/null +++ b/hphp/test/quick/inline_cgetm_dynamic.php @@ -0,0 +1,16 @@ +str = "yolo"; + } + public function getString() { return $this->str; } +} + +function test6() { + $obj = new CGetMDynProp(); + echo $obj->getString(); + echo "\n"; +} + +test6(); diff --git a/hphp/test/quick/inline_cgetm_dynamic.php.expect b/hphp/test/quick/inline_cgetm_dynamic.php.expect new file mode 100644 index 000000000..bd88e01cd --- /dev/null +++ b/hphp/test/quick/inline_cgetm_dynamic.php.expect @@ -0,0 +1 @@ +yolo diff --git a/hphp/test/quick/inline_constants.php b/hphp/test/quick/inline_constants.php new file mode 100644 index 000000000..27a2523aa --- /dev/null +++ b/hphp/test/quick/inline_constants.php @@ -0,0 +1,27 @@ +asd(); + $y->k(); +} + +function const_fold() { + echo foo().bar()."\n"; +} + +main(); +const_fold(); diff --git a/hphp/test/quick/inline_constants.php.expect b/hphp/test/quick/inline_constants.php.expect new file mode 100644 index 000000000..8d151ba1f --- /dev/null +++ b/hphp/test/quick/inline_constants.php.expect @@ -0,0 +1 @@ +asdbar diff --git a/hphp/test/quick/inline_generator_body.php b/hphp/test/quick/inline_generator_body.php new file mode 100644 index 000000000..9487adbd3 --- /dev/null +++ b/hphp/test/quick/inline_generator_body.php @@ -0,0 +1,24 @@ +x = "asdasd"; + } + + function getX() { + return $this->x; + } +} + +function foo() { + $k = new CGetM(); + $z = $k->getX(); + yield $z; + yield "\n"; +} + +foreach (foo() as $x) { + echo $x; +} diff --git a/hphp/test/quick/inline_generator_body.php.expect b/hphp/test/quick/inline_generator_body.php.expect new file mode 100644 index 000000000..bf7e89904 --- /dev/null +++ b/hphp/test/quick/inline_generator_body.php.expect @@ -0,0 +1 @@ +asdasd diff --git a/hphp/test/quick/inline_getter_internal.php b/hphp/test/quick/inline_getter_internal.php new file mode 100644 index 000000000..6131c0fa0 --- /dev/null +++ b/hphp/test/quick/inline_getter_internal.php @@ -0,0 +1,24 @@ +foo = "asd"; + } + + public function doit() { + return $this->getFoo() . "asd"; + } + + public function getFoo() { + return $this->foo; + } +} + +function test10() { + $k = new GetterInternal(); + $k->doit(); +} + +test10(); diff --git a/hphp/test/quick/inline_getter_internal.php.expect b/hphp/test/quick/inline_getter_internal.php.expect new file mode 100644 index 000000000..e69de29bb diff --git a/hphp/test/quick/inline_issetm.php b/hphp/test/quick/inline_issetm.php new file mode 100644 index 000000000..474460584 --- /dev/null +++ b/hphp/test/quick/inline_issetm.php @@ -0,0 +1,15 @@ +x); + } +} + +function main() { + $k = new IssetM(); + $v = $k->hasX(); + var_dump($v); +} + +main(); diff --git a/hphp/test/quick/inline_issetm.php.expect b/hphp/test/quick/inline_issetm.php.expect new file mode 100644 index 000000000..5c0c0681d --- /dev/null +++ b/hphp/test/quick/inline_issetm.php.expect @@ -0,0 +1 @@ +bool(false) diff --git a/hphp/test/quick/inline_setm.php b/hphp/test/quick/inline_setm.php new file mode 100644 index 000000000..a06201d98 --- /dev/null +++ b/hphp/test/quick/inline_setm.php @@ -0,0 +1,20 @@ +x = "asdasd"; + } + + public function clearX() { + $this->x = null; + } +} + +function main() { + $x = new SetM(); + $x->clearX(); +} + +main(); diff --git a/hphp/test/quick/inline_setm.php.expect b/hphp/test/quick/inline_setm.php.expect new file mode 100644 index 000000000..e69de29bb diff --git a/hphp/test/quick/inline_unset.php b/hphp/test/quick/inline_unset.php new file mode 100644 index 000000000..513f82184 --- /dev/null +++ b/hphp/test/quick/inline_unset.php @@ -0,0 +1,31 @@ +x); + } + public function getX() { + return $this->x; + } +} + +// TODO: this will need a hopt to enable throw +function thrower() { + //var_dump(debug_backtrace()); + throw new Exception("Yo"); +} + +function test7() { + set_error_handler('thrower'); + try { + $obj = new NonExistProp(); + $obj->unsetIt(); + $k = new Dtor(); + echo $obj->getX(); + echo "\n"; + } catch (Exception $x) {} +} + +test7(); diff --git a/hphp/test/quick/inline_unset.php.expectf b/hphp/test/quick/inline_unset.php.expectf new file mode 100644 index 000000000..b4e7da5f3 --- /dev/null +++ b/hphp/test/quick/inline_unset.php.expectf @@ -0,0 +1 @@ +HipHop Fatal error: Class undefined: Dtor in %s on line 25 diff --git a/hphp/test/quick/inline_weird_getter.php b/hphp/test/quick/inline_weird_getter.php new file mode 100644 index 000000000..b607822b2 --- /dev/null +++ b/hphp/test/quick/inline_weird_getter.php @@ -0,0 +1,21 @@ +x = "some string"; + } + + public function getX($ignored, $arg, $list, $lol) { + return $this->x; + } +} + +function test8() { + $k = new WeirdGetter(); + echo "blah" . $k->getX("string", "a", "string", "a"); + echo "\n"; +} + +test8(); diff --git a/hphp/test/quick/inline_weird_getter.php.expect b/hphp/test/quick/inline_weird_getter.php.expect new file mode 100644 index 000000000..4345afb71 --- /dev/null +++ b/hphp/test/quick/inline_weird_getter.php.expect @@ -0,0 +1 @@ +blahsome string diff --git a/hphp/test/quick/regpressure_bug.php b/hphp/test/quick/regpressure_bug.php new file mode 100644 index 000000000..0dc9771d0 --- /dev/null +++ b/hphp/test/quick/regpressure_bug.php @@ -0,0 +1,49 @@ +id = $id; + $this->binary = "asdasd"; + } + + public function __destruct() { + echo "dtor" . $this->id . "\n"; + } + + public function toBinary() { + return $this->binary; + } + + public final function inSubnetWithMask(IPAddressIsh $subnet, string $mask) { + // Test case for inlining from FB's IPAddress class, removed real + // logic. + echo "pre1\n"; + return ($this->toBinary() & $mask) === ($subnet->toBinary() & $mask); + } + + public function foobar() { + echo $this->binary; + echo "\n"; + } +} + +function main() { + $x = new IPAddressIsh('x'); + $y = new IPAddressIsh('y'); + echo $x->inSubnetWithMask($y, "213"); + // Use after and ensure refcounts are still real. + echo "after1\n"; + $x->foobar(); + echo "after2\n"; + $y->foobar(); + echo "after3\n"; +} +main(); diff --git a/hphp/test/quick/regpressure_bug.php.expect b/hphp/test/quick/regpressure_bug.php.expect new file mode 100644 index 000000000..60346c04b --- /dev/null +++ b/hphp/test/quick/regpressure_bug.php.expect @@ -0,0 +1,8 @@ +pre1 +1after1 +asdasd +after2 +asdasd +after3 +dtory +dtorx diff --git a/hphp/test/quick/inline_surprise_throw.php b/hphp/test/quick/surprise_throw.php similarity index 100% rename from hphp/test/quick/inline_surprise_throw.php rename to hphp/test/quick/surprise_throw.php diff --git a/hphp/test/quick/inline_surprise_throw.php.expect b/hphp/test/quick/surprise_throw.php.expect similarity index 100% rename from hphp/test/quick/inline_surprise_throw.php.expect rename to hphp/test/quick/surprise_throw.php.expect