Initial support for inlining small functions
Sets up the translator analyze pass to create a Tracelet for the callee at every statically-known FCall. If the callee has an appropriate shape (in this diff, it must be a function consisting of "return $this->foo" for a declared property), we can inline it in HHIR. Restructures the IR relating to frames some so we can eliminate the stores relating to ActRec in this simple case (see the comments in dce.cpp and hhbctranslator.cpp for details). Includes partial support for inlining callees with locals, but it's disabled for now because they will keep the frame live.
Esse commit está contido em:
+73
-21
@@ -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<numArgs,invName> 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<returnBcOffset> 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<stackOff> 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<offset> 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<offset> 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
|
||||
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<DefInlineFP>()->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<ReDefSP>()->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<SpillFrame>()->invName;
|
||||
auto const nArgs = inst->getExtra<SpillFrame>()->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<uintptr_t>(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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -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<class CheckSupportedFun, class EmitLdAddrFun>
|
||||
void emitVGet(const StringData* name, CheckSupportedFun, EmitLdAddrFun);
|
||||
|
||||
void emitBindMem(SSATmp* ptr, SSATmp* src);
|
||||
|
||||
template<class CheckSupportedFun, class EmitLdAddrFun>
|
||||
void emitBind(const StringData* name, CheckSupportedFun, EmitLdAddrFun);
|
||||
|
||||
void emitSetMem(SSATmp* ptr, SSATmp* src);
|
||||
|
||||
template<class CheckSupportedFun, class EmitLdAddrFun>
|
||||
void emitSet(const StringData* name, CheckSupportedFun, EmitLdAddrFun);
|
||||
|
||||
template<class CheckSupportedFun, class EmitLdAddrFun>
|
||||
void emitIsset(const StringData* name, CheckSupportedFun, EmitLdAddrFun);
|
||||
|
||||
void emitEmptyMem(SSATmp* ptr);
|
||||
|
||||
template<class CheckSupportedFun, class EmitLdAddrFun>
|
||||
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<class Lambda>
|
||||
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<SSATmp*> 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<TraceBuilder>
|
||||
m_tb;
|
||||
const Func* m_curFunc;
|
||||
Offset m_bcOff;
|
||||
std::vector<BcState>
|
||||
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<std::pair<SSATmp*,int32_t>>
|
||||
m_fpiStack;
|
||||
|
||||
vector<TypeGuard> m_typeGuards;
|
||||
Trace* const m_exitGuardFailureTrace;
|
||||
};
|
||||
|
||||
@@ -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<Marker>();
|
||||
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()
|
||||
? "<systemlib>"
|
||||
: func->unit()->filepath()->data();
|
||||
out << folly::format("{}() @{} ({})\n",
|
||||
func->fullName()->data(),
|
||||
trace->getBcOff(),
|
||||
unitName);
|
||||
trace->print(out, asmInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<std::string>(numArgs, invName ? " M" : "");
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Stack and bytecode offsets.
|
||||
*/
|
||||
struct StackOffset : IRExtraData {
|
||||
explicit StackOffset(int32_t offset) : offset(offset) {}
|
||||
|
||||
std::string show() const { return folly::to<std::string>(offset); }
|
||||
|
||||
int32_t offset;
|
||||
};
|
||||
|
||||
struct BCOffset : IRExtraData {
|
||||
explicit BCOffset(Offset offset) : offset(offset) {}
|
||||
|
||||
std::string show() const { return folly::to<std::string>(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:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <strings.h>
|
||||
|
||||
#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 {} <reason: {}>\n",
|
||||
func->fullName()->data(), why);
|
||||
return false;
|
||||
};
|
||||
auto accept = [&](const char* kind) -> bool {
|
||||
FTRACE(1, "shouldIRInline: inlining {} <kind: {}>\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();
|
||||
}
|
||||
|
||||
|
||||
}}}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -745,7 +745,7 @@ void MemMap::optimizeMemoryAccesses(Trace* trace) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Simplifier::copyProp(&inst);
|
||||
copyProp(&inst);
|
||||
processInstruction(&inst, curFunc && curFunc->isPseudoMain());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<const Func*>(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<int64_t>(numArgs), sp);
|
||||
}
|
||||
|
||||
SSATmp* TraceBuilder::genDefActRec(SSATmp* func,
|
||||
SSATmp* objOrClass,
|
||||
int32_t numArgs,
|
||||
const StringData* invName) {
|
||||
return gen(DefActRec,
|
||||
m_fpValue,
|
||||
func,
|
||||
objOrClass,
|
||||
genDefConst<int64_t>(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<T> sp, offset
|
||||
// fallthrough
|
||||
case GuardStk: {
|
||||
// sp = GuardStk<T> 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<int64_t>(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<StackOffset>()->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<LocalId>()->locId,
|
||||
inst->getSrc(1));
|
||||
break;
|
||||
}
|
||||
case LdLoc: {
|
||||
|
||||
case LdLoc:
|
||||
setLocalValue(inst->getExtra<LdLoc>()->locId, inst->getDst());
|
||||
break;
|
||||
}
|
||||
|
||||
case AssertLoc:
|
||||
case GuardLoc: {
|
||||
case GuardLoc:
|
||||
setLocalType(inst->getExtra<LocalId>()->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::State> TraceBuilder::createState() const {
|
||||
std::unique_ptr<State> 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> 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> 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();
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -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<SSATmp*> localValues;
|
||||
std::vector<Type> localTypes;
|
||||
SSATmp* refCountedMemValue;
|
||||
std::vector<SSATmp*> callerAvailableValues; // unordered list
|
||||
};
|
||||
std::unique_ptr<State> createState() const;
|
||||
void saveState(Block*);
|
||||
void mergeState(State* s1);
|
||||
void useState(std::unique_ptr<State> 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<SSATmp*> m_localValues;
|
||||
std::vector<Type> m_localTypes;
|
||||
std::vector<SSATmp*> m_localValues;
|
||||
std::vector<Type> 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<SSATmp*> m_callerAvailableValues;
|
||||
|
||||
// When we're building traces for an inlined callee, the state of
|
||||
// the caller needs to be preserved here.
|
||||
std::vector<std::unique_ptr<State>> m_inlineSavedStates;
|
||||
};
|
||||
|
||||
template<>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -120,7 +120,6 @@ class TranslatorX64 : public Translator
|
||||
template<ConditionCode, typename smasher> friend class JccBlock;
|
||||
template<ConditionCode> friend class IfElseBlock;
|
||||
friend class UnlikelyIfBlock;
|
||||
typedef HPHP::DataType DataType;
|
||||
|
||||
typedef tbb::concurrent_hash_map<TCA, TCA> 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);
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#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<DataType,double>
|
||||
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<uintptr_t>(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<Cell*>(&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<Tracelet> Translator::analyze(SrcKey sk) {
|
||||
std::unique_ptr<Tracelet> Translator::analyze(SrcKey sk,
|
||||
const TypeMap& initialTypes) {
|
||||
std::unique_ptr<Tracelet> retval(new Tracelet());
|
||||
auto& t = *retval;
|
||||
t.m_sk = sk;
|
||||
@@ -2979,9 +3250,8 @@ std::unique_ptr<Tracelet> 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<Tracelet> 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<Tracelet> 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;
|
||||
}
|
||||
|
||||
} } }
|
||||
|
||||
@@ -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<SrcKey, SrcKey> 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<Class*> 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<Tracelet> 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<Location, DynLocation*, Location> ChangeMap;
|
||||
typedef hphp_hash_map<Location,RuntimeType,Location> TypeMap;
|
||||
typedef ChangeMap DepMap;
|
||||
typedef hphp_hash_set<Location, Location> LocationSet;
|
||||
typedef hphp_hash_map<DynLocation*, GuardType> 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<Tracelet> analyze(SrcKey sk, const TypeMap& = TypeMap());
|
||||
|
||||
void postAnalyze(NormalizedInstruction* ni, SrcKey& sk,
|
||||
Tracelet& t, TraceletContext& tas);
|
||||
std::unique_ptr<Tracelet> 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1236,4 +1236,3 @@ C::__destruct
|
||||
C::__destruct
|
||||
C::__destruct
|
||||
C::__destruct
|
||||
C::__destruct
|
||||
|
||||
@@ -1 +1 @@
|
||||
HipHop Fatal error: Stack overflow in %s on line 25
|
||||
HipHop Fatal error: Stack overflow in %s on line 21
|
||||
|
||||
@@ -1 +1 @@
|
||||
HipHop Fatal error: Stack overflow in %s on line 21
|
||||
HipHop Fatal error: Stack overflow in %s on line 26
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
class Dtor { public function __destruct() { echo "dtor\n"; } }
|
||||
|
||||
function id($x) {
|
||||
return $x;
|
||||
}
|
||||
|
||||
function test1() {
|
||||
$k = new Dtor();
|
||||
id($k);
|
||||
}
|
||||
|
||||
function test2() {
|
||||
id(new Dtor());
|
||||
}
|
||||
|
||||
function test3() {
|
||||
echo id("haha");
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
function printer($x, $y) {
|
||||
echo $x;
|
||||
echo $y;
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
function test31() {
|
||||
printer("asd ", id("foo"));
|
||||
}
|
||||
|
||||
function test32() {
|
||||
echo id(id("foo"));
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
test1();
|
||||
test2();
|
||||
test3();
|
||||
test31();
|
||||
test32();
|
||||
@@ -0,0 +1,5 @@
|
||||
dtor
|
||||
dtor
|
||||
haha
|
||||
asd foo
|
||||
foo
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
class Test {
|
||||
public function foo($x) {
|
||||
return $x;
|
||||
}
|
||||
}
|
||||
|
||||
function test4(Test $x) {
|
||||
$x->foo(12);
|
||||
}
|
||||
|
||||
test4(new Test());
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
class CGetMTest {
|
||||
public function __construct() {
|
||||
$this->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();
|
||||
@@ -0,0 +1,2 @@
|
||||
a string
|
||||
a string
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
class Obj {
|
||||
public $val;
|
||||
public function __construct() {
|
||||
$this->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());
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
class CGetMDynProp {
|
||||
public function __construct() {
|
||||
$this->str = "yolo";
|
||||
}
|
||||
public function getString() { return $this->str; }
|
||||
}
|
||||
|
||||
function test6() {
|
||||
$obj = new CGetMDynProp();
|
||||
echo $obj->getString();
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
test6();
|
||||
@@ -0,0 +1 @@
|
||||
yolo
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
function foo() { return "asd"; }
|
||||
function bar() { return "bar"; }
|
||||
|
||||
class Bar {
|
||||
public function asd() { return $this; }
|
||||
}
|
||||
|
||||
class Baz {
|
||||
public function k() { return 12; }
|
||||
}
|
||||
|
||||
function main() {
|
||||
$k = new Bar;
|
||||
$y = new Baz;
|
||||
foo();
|
||||
$k->asd();
|
||||
$y->k();
|
||||
}
|
||||
|
||||
function const_fold() {
|
||||
echo foo().bar()."\n";
|
||||
}
|
||||
|
||||
main();
|
||||
const_fold();
|
||||
@@ -0,0 +1 @@
|
||||
asdbar
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
class CGetM {
|
||||
private $x;
|
||||
|
||||
public function __construct() {
|
||||
$this->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;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
asdasd
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
class GetterInternal {
|
||||
private $foo;
|
||||
|
||||
public function __construct() {
|
||||
$this->foo = "asd";
|
||||
}
|
||||
|
||||
public function doit() {
|
||||
return $this->getFoo() . "asd";
|
||||
}
|
||||
|
||||
public function getFoo() {
|
||||
return $this->foo;
|
||||
}
|
||||
}
|
||||
|
||||
function test10() {
|
||||
$k = new GetterInternal();
|
||||
$k->doit();
|
||||
}
|
||||
|
||||
test10();
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
class IssetM {
|
||||
public function hasX() {
|
||||
return isset($this->x);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
$k = new IssetM();
|
||||
$v = $k->hasX();
|
||||
var_dump($v);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1 @@
|
||||
bool(false)
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
class SetM {
|
||||
private $x;
|
||||
|
||||
public function __construct() {
|
||||
$this->x = "asdasd";
|
||||
}
|
||||
|
||||
public function clearX() {
|
||||
$this->x = null;
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
$x = new SetM();
|
||||
$x->clearX();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
class NonExistProp {
|
||||
private $x;
|
||||
public function __construct() { $x = "str"; }
|
||||
public function unsetIt() {
|
||||
unset($this->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();
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Class undefined: Dtor in %s on line 25
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
class WeirdGetter {
|
||||
private $x;
|
||||
|
||||
public function __construct() {
|
||||
$this->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();
|
||||
@@ -0,0 +1 @@
|
||||
blahsome string
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This test emulates a regpressure case that occurred with inlining
|
||||
* (but probably won't occur later as we improve linearscan).
|
||||
*/
|
||||
|
||||
class IPAddressIsh {
|
||||
private $binary;
|
||||
private $id;
|
||||
|
||||
public function __construct($id) {
|
||||
$this->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();
|
||||
@@ -0,0 +1,8 @@
|
||||
pre1
|
||||
1after1
|
||||
asdasd
|
||||
after2
|
||||
asdasd
|
||||
after3
|
||||
dtory
|
||||
dtorx
|
||||
Referência em uma Nova Issue
Bloquear um usuário