Basic support for inlining functions with parameters, inline some setters
Adds support for some functions with parameters. Parameters work by doing StLocs to the inlined frame, which can be eliminated if the frame can be eliminated. (Non-parameter locals will eventually work similarly.) This diff also moves all the state tracking to updateTrackedState instead of beginInlining/endInlining functions on TraceBuilder---the second TraceBuilder pass currently tracks state incorrectly if the inlined frame wasn't eliminated. This diff doesn't inline setters that have parameter type hints because they could hold on to the frame. Seems to be in the noise in perflab, although contrived setter microbenchmarks definitely reflect the change.
Esse commit está contido em:
@@ -864,11 +864,17 @@ D:FramePtr = FreeActRec S0:FramePtr
|
||||
Load the saved frame pointer from the activation record pointed to
|
||||
by S0 into D.
|
||||
|
||||
D:FramePtr = DefInlineFP<returnBcOffset> S0:StkPtr
|
||||
D:FramePtr = DefInlineFP<func,retBCOff,retSPOff> S0:StkPtr S1: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).
|
||||
been popped). S1 is a StkPtr that represents what the inlined
|
||||
function will return to (i.e. it points to the stack after the
|
||||
ActRec in S0 is popped).
|
||||
|
||||
`func' is the function being inlined. `retBCOff' and `retSPOff'
|
||||
represent what the bytecode and stack offsets should be after the
|
||||
FCall instruction in ther caller.
|
||||
|
||||
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
|
||||
|
||||
@@ -2339,7 +2339,7 @@ void CodeGenerator::cgLdSSwitchDestSlow(IRInstruction* inst) {
|
||||
void CodeGenerator::cgDefInlineFP(IRInstruction* inst) {
|
||||
auto const fp = m_regs[inst->getSrc(0)].getReg();
|
||||
auto const fakeRet = m_tx64->getRetFromInlinedFrame();
|
||||
auto const retBCOff = inst->getExtra<DefInlineFP>()->offset;
|
||||
auto const retBCOff = inst->getExtra<DefInlineFP>()->retBCOff;
|
||||
|
||||
m_as. storeq (fakeRet, fp[AROFF(m_savedRip)]);
|
||||
m_as. storel (retBCOff, fp[AROFF(m_soff)]);
|
||||
|
||||
@@ -62,9 +62,7 @@ struct DceFlags {
|
||||
*/
|
||||
void incWeakUse() {
|
||||
if (m_weakUseCount + 1 > kMaxWeakUseCount) {
|
||||
always_assert(!"currently there's only two instructions "
|
||||
"using this machinery, so this shouldn't "
|
||||
"happen ... ");
|
||||
// Too many weak uses for us to know we can optimize it away.
|
||||
return;
|
||||
}
|
||||
++m_weakUseCount;
|
||||
@@ -392,6 +390,17 @@ void optimizeActRecs(Trace* trace, DceState& state, IRFactory* factory,
|
||||
}
|
||||
break;
|
||||
|
||||
// We don't need to generate stores to a frame if it can be
|
||||
// eliminated.
|
||||
case StLoc:
|
||||
{
|
||||
auto const frameInst = inst->getSrc(0)->inst();
|
||||
if (frameInst->op() == DefInlineFP) {
|
||||
state[frameInst].incWeakUse();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case InlineReturn:
|
||||
{
|
||||
auto frameUses = uses[inst->getSrc(0)];
|
||||
@@ -446,11 +455,13 @@ void optimizeActRecs(Trace* trace, DceState& state, IRFactory* factory,
|
||||
}
|
||||
break;
|
||||
|
||||
case InlineReturn:
|
||||
case StLoc: case InlineReturn:
|
||||
{
|
||||
auto const fp = inst->getSrc(0);
|
||||
if (state[fp->inst()].isDead()) {
|
||||
FTRACE(5, "InlineReturn ({}) setDead\n", inst->getId());
|
||||
FTRACE(5, "{} ({}) setDead\n",
|
||||
opcodeName(inst->op()),
|
||||
inst->getId());
|
||||
state[inst].setDead();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ void HhbcTranslator::replace(uint32_t index, SSATmp* tmp) {
|
||||
* sp2 = SpillFrame sp1, ...
|
||||
* // ... possibly more spillstacks due to argument expressions
|
||||
* sp3 = SpillStack sp2, -argCount
|
||||
* fp2 = DefInlineFP<returnBc> sp2
|
||||
* fp2 = DefInlineFP<func,retBC,retSP> sp2 sp1
|
||||
* sp4 = ReDefSP<numLocals> fp2
|
||||
*
|
||||
* // ... callee body ...
|
||||
@@ -206,35 +206,28 @@ void HhbcTranslator::beginInlining(unsigned numParams,
|
||||
|
||||
FTRACE(1, "[[[ begin inlining: {}\n", target->fullName()->data());
|
||||
|
||||
{
|
||||
static const bool enabled = Stats::enabledAny() &&
|
||||
getenv("HHVM_STATS_INLINEFUNC");
|
||||
if (enabled) {
|
||||
gen(
|
||||
IncStatGrouped,
|
||||
cns(StringData::GetStaticString("HHIRInline")),
|
||||
cns(target->fullName()),
|
||||
cns(1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SSATmp* params[numParams];
|
||||
for (unsigned i = 0; i < numParams; ++i) {
|
||||
params[numParams - i - 1] = popF();
|
||||
}
|
||||
|
||||
auto prevSP = m_fpiStack.top().first;
|
||||
auto prevSPOff = m_fpiStack.top().second;
|
||||
auto calleeSP = spillStack();
|
||||
auto calleeFP = gen(DefInlineFP, BCOffset(returnBcOffset), calleeSP);
|
||||
auto const prevSP = m_fpiStack.top().first;
|
||||
auto const prevSPOff = m_fpiStack.top().second;
|
||||
auto const calleeSP = spillStack();
|
||||
|
||||
DefInlineFPData data;
|
||||
data.target = target;
|
||||
data.retBCOff = returnBcOffset;
|
||||
data.retSPOff = prevSPOff;
|
||||
auto const calleeFP = gen(DefInlineFP, data, calleeSP, prevSP);
|
||||
|
||||
m_bcStateStack.emplace_back(target->base(), target);
|
||||
m_tb->beginInlining(target, calleeFP, calleeSP, prevSP, prevSPOff);
|
||||
gen(ReDefSP, StackOffset(target->numLocals()), m_tb->getFp(), m_tb->getSp());
|
||||
|
||||
profileFunctionEntry("Inline");
|
||||
|
||||
for (unsigned i = 0; i < numParams; ++i) {
|
||||
m_tb->setLocalValue(i, params[i]);
|
||||
gen(StLoc, LocalId(i), calleeFP, params[i]);
|
||||
}
|
||||
for (unsigned i = numParams; i < target->numLocals(); ++i) {
|
||||
/*
|
||||
@@ -243,7 +236,7 @@ void HhbcTranslator::beginInlining(unsigned numParams,
|
||||
* to leave the trace.
|
||||
*/
|
||||
always_assert(0 && "unimplemented");
|
||||
m_tb->setLocalValue(i, m_tb->genDefUninit());
|
||||
gen(StLoc, LocalId(i), calleeFP, m_tb->genDefUninit());
|
||||
}
|
||||
|
||||
emitMarker();
|
||||
@@ -2005,14 +1998,32 @@ void HhbcTranslator::emitRetFromInlined(Type type) {
|
||||
* important that this does endInlining before pushing the return
|
||||
* value so stack offsets are properly tracked.
|
||||
*/
|
||||
m_tb->endInlining();
|
||||
// after end of inlining, m_stackDeficit should be set to
|
||||
// 0, and eval stack should be empty
|
||||
assert(m_evalStack.numCells() == 0);
|
||||
m_stackDeficit = 0;
|
||||
FTRACE(1, "]]] end inlining: {}\n", getCurFunc()->fullName()->data());
|
||||
gen(InlineReturn, m_tb->getFp());
|
||||
|
||||
// Return to the caller function. Careful between here and the
|
||||
// emitMarker() below, where the caller state isn't entirely set up.
|
||||
m_bcStateStack.pop_back();
|
||||
m_fpiStack.pop();
|
||||
|
||||
// See the comment in beginInlining about generator frames.
|
||||
if (getCurFunc()->isGenerator()) {
|
||||
gen(ReDefGeneratorSP, StackOffset(m_tb->getSpOffset()), m_tb->getSp());
|
||||
} else {
|
||||
gen(ReDefSP,
|
||||
StackOffset(m_tb->getSpOffset()), m_tb->getFp(), m_tb->getSp());
|
||||
}
|
||||
|
||||
/*
|
||||
* After the end of inlining, we are restoring to a previously
|
||||
* defined stack that we know is entirely materialized. TODO:
|
||||
* explain this better.
|
||||
*
|
||||
* The push of the return value below is not yet materialized.
|
||||
*/
|
||||
assert(m_evalStack.numCells() == 0);
|
||||
m_stackDeficit = 0;
|
||||
|
||||
FTRACE(1, "]]] end inlining: {}\n", getCurFunc()->fullName()->data());
|
||||
push(retVal);
|
||||
|
||||
emitMarker();
|
||||
|
||||
@@ -378,7 +378,7 @@ 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(DefInlineFP, D(FramePtr), S(StkPtr), NF) \
|
||||
O(DefInlineFP, D(FramePtr), S(StkPtr) S(StkPtr), NF) \
|
||||
O(InlineReturn, ND, S(FramePtr), E) \
|
||||
O(DefFP, D(FramePtr), NA, E) \
|
||||
O(DefSP, D(StkPtr), S(FramePtr), E) \
|
||||
@@ -780,12 +780,20 @@ struct StackOffset : IRExtraData {
|
||||
};
|
||||
|
||||
/*
|
||||
* Bytecode offsets.
|
||||
* DefInlineFP is present when we need to create a frame for inlining.
|
||||
* This instruction also carries some metadata used by tracebuilder to
|
||||
* track state during an inlined call.
|
||||
*/
|
||||
struct BCOffset : IRExtraData {
|
||||
explicit BCOffset(Offset offset) : offset(offset) {}
|
||||
std::string show() const { return folly::to<std::string>(offset); }
|
||||
Offset offset;
|
||||
struct DefInlineFPData : IRExtraData {
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(
|
||||
target->fullName()->data(), "(),", retSPOff, ',', retBCOff
|
||||
);
|
||||
}
|
||||
|
||||
const Func* target;
|
||||
Offset retBCOff;
|
||||
Offset retSPOff;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -837,7 +845,7 @@ X(DefSP, StackOffset);
|
||||
X(LdStack, StackOffset);
|
||||
X(LdStackAddr, StackOffset);
|
||||
X(DecRefStack, StackOffset);
|
||||
X(DefInlineFP, BCOffset);
|
||||
X(DefInlineFP, DefInlineFPData);
|
||||
X(InlineCreateCont, CreateContData);
|
||||
X(CallArray, CallArrayData);
|
||||
|
||||
|
||||
@@ -1107,9 +1107,9 @@ TranslatorX64::irTranslateFCallBuiltin(const Tracelet& t,
|
||||
HHIR_EMIT(FCallBuiltin, numArgs, numNonDefault, funcId);
|
||||
}
|
||||
|
||||
static bool shouldIRInline(const Func* curFunc,
|
||||
const Func* func,
|
||||
const Tracelet& callee) {
|
||||
bool shouldIRInline(const Func* curFunc,
|
||||
const Func* func,
|
||||
const Tracelet& callee) {
|
||||
if (!RuntimeOption::EvalHHIREnableGenTimeInlining) {
|
||||
return false;
|
||||
}
|
||||
@@ -1126,7 +1126,7 @@ static bool shouldIRInline(const Func* curFunc,
|
||||
};
|
||||
|
||||
if (func->numLocals() != func->numParams()) {
|
||||
return refuse("locals");
|
||||
return refuse("more locals than params (unsupported)");
|
||||
}
|
||||
if (func->numIterators() != 0) {
|
||||
return refuse("iterators");
|
||||
@@ -1136,11 +1136,7 @@ static bool shouldIRInline(const Func* curFunc,
|
||||
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;
|
||||
@@ -1162,12 +1158,16 @@ static bool shouldIRInline(const Func* curFunc,
|
||||
};
|
||||
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.
|
||||
// Simple operations that just put a Cell on the stack. There must
|
||||
// either be no inputs, or a single local as an input. For now
|
||||
// avoid CreateCont because it depends on the frame.
|
||||
auto simpleCell = [&]() -> bool {
|
||||
if (cursor->outStack && cursor->inputs.empty() &&
|
||||
current != OpCreateCont) {
|
||||
if (current == OpCreateCont) return false;
|
||||
if (cursor->outStack && cursor->inputs.empty()) {
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
if (current == OpCGetL || current == OpVGetL) {
|
||||
next();
|
||||
return true;
|
||||
}
|
||||
@@ -1198,14 +1198,9 @@ static bool shouldIRInline(const Func* curFunc,
|
||||
return atRet();
|
||||
};
|
||||
|
||||
/////////////
|
||||
|
||||
// Identity functions.
|
||||
resetCursor();
|
||||
if (current == OpCGetL) {
|
||||
next();
|
||||
if (atRet()) return accept("returns parameter");
|
||||
}
|
||||
|
||||
////////////
|
||||
|
||||
// Simple property accessors.
|
||||
resetCursor();
|
||||
@@ -1246,7 +1241,8 @@ static bool shouldIRInline(const Func* curFunc,
|
||||
* Continuation allocation functions that take no arguments.
|
||||
*/
|
||||
resetCursor();
|
||||
if (current == OpCreateCont && cursor->imm[0].u_IVA == 0) {
|
||||
if (current == OpCreateCont && cursor->imm[0].u_IVA == 0 &&
|
||||
!func->numParams()) {
|
||||
next();
|
||||
if (atRet()) return accept("zero-arg continuation creator");
|
||||
}
|
||||
@@ -1288,8 +1284,9 @@ TranslatorX64::irTranslateFCall(const Tracelet& t,
|
||||
* the call.
|
||||
*/
|
||||
if (i.calleeTrace) {
|
||||
if (!m_hhbcTrans->isInlining() &&
|
||||
shouldIRInline(curFunc(), i.funcd, *i.calleeTrace)) {
|
||||
if (!m_hhbcTrans->isInlining()) {
|
||||
assert(shouldIRInline(curFunc(), i.funcd, *i.calleeTrace));
|
||||
|
||||
m_hhbcTrans->beginInlining(numArgs, i.funcd, returnBcOffset);
|
||||
static const bool shapeStats = Stats::enabledAny() &&
|
||||
getenv("HHVM_STATS_INLINESHAPE");
|
||||
|
||||
@@ -861,6 +861,8 @@ void LinearScan::numberInstructions(const BlockList& blocks) {
|
||||
|
||||
void LinearScan::genSpillStats(Trace* trace, int numSpillLocs) {
|
||||
if (!moduleEnabled(HPHP::Trace::statgroups, 1)) return;
|
||||
static bool enabled = getenv("HHVM_STATS_SPILLS");
|
||||
if (!enabled) return;
|
||||
|
||||
int numMainSpills = 0;
|
||||
int numExitSpills = 0;
|
||||
|
||||
@@ -221,6 +221,52 @@ SSATmp* TraceBuilder::genDefNone() {
|
||||
return gen(DefConst, Type::None, ConstData(0));
|
||||
}
|
||||
|
||||
void TraceBuilder::trackDefInlineFP(IRInstruction* inst) {
|
||||
auto const target = inst->getExtra<DefInlineFP>()->target;
|
||||
auto const savedSPOff = inst->getExtra<DefInlineFP>()->retSPOff;
|
||||
auto const calleeFP = inst->getDst();
|
||||
auto const calleeSP = inst->getSrc(0);
|
||||
auto const savedSP = inst->getSrc(1);
|
||||
|
||||
// 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 = cns(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);
|
||||
}
|
||||
|
||||
void TraceBuilder::trackInlineReturn(IRInstruction* inst) {
|
||||
useState(std::move(m_inlineSavedStates.back()));
|
||||
m_inlineSavedStates.pop_back();
|
||||
}
|
||||
|
||||
void TraceBuilder::updateTrackedState(IRInstruction* inst) {
|
||||
Opcode opc = inst->op();
|
||||
// Update tracked state of local values/types, stack/frame pointer, CSE, etc.
|
||||
@@ -231,147 +277,150 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) {
|
||||
}
|
||||
|
||||
switch (opc) {
|
||||
case Call:
|
||||
m_spValue = inst->getDst();
|
||||
// A call pops the ActRec and pushes a return value.
|
||||
m_spOffset -= kNumActRecCells;
|
||||
m_spOffset += 1;
|
||||
assert(m_spOffset >= 0);
|
||||
killCse();
|
||||
killLocals();
|
||||
break;
|
||||
case DefInlineFP: trackDefInlineFP(inst); break;
|
||||
case InlineReturn: trackInlineReturn(inst); break;
|
||||
|
||||
case CallArray:
|
||||
m_spValue = inst->getDst();
|
||||
// A CallArray pops the ActRec an array arg and pushes a return value.
|
||||
m_spOffset -= kNumActRecCells;
|
||||
assert(m_spOffset >= 0);
|
||||
killCse();
|
||||
killLocals();
|
||||
break;
|
||||
case Call:
|
||||
m_spValue = inst->getDst();
|
||||
// A call pops the ActRec and pushes a return value.
|
||||
m_spOffset -= kNumActRecCells;
|
||||
m_spOffset += 1;
|
||||
assert(m_spOffset >= 0);
|
||||
killCse();
|
||||
killLocals();
|
||||
break;
|
||||
|
||||
case ContEnter:
|
||||
killCse();
|
||||
killLocals();
|
||||
break;
|
||||
case CallArray:
|
||||
m_spValue = inst->getDst();
|
||||
// A CallArray pops the ActRec an array arg and pushes a return value.
|
||||
m_spOffset -= kNumActRecCells;
|
||||
assert(m_spOffset >= 0);
|
||||
killCse();
|
||||
killLocals();
|
||||
break;
|
||||
|
||||
case DefFP:
|
||||
case FreeActRec:
|
||||
m_fpValue = inst->getDst();
|
||||
break;
|
||||
case ContEnter:
|
||||
killCse();
|
||||
killLocals();
|
||||
break;
|
||||
|
||||
case ReDefGeneratorSP:
|
||||
case DefSP:
|
||||
case ReDefSP:
|
||||
m_spValue = inst->getDst();
|
||||
m_spOffset = inst->getExtra<StackOffset>()->offset;
|
||||
break;
|
||||
case DefFP:
|
||||
case FreeActRec:
|
||||
m_fpValue = inst->getDst();
|
||||
break;
|
||||
|
||||
case AssertStk:
|
||||
case CastStk:
|
||||
case GuardStk:
|
||||
case ExceptionBarrier:
|
||||
m_spValue = inst->getDst();
|
||||
break;
|
||||
case ReDefGeneratorSP:
|
||||
case DefSP:
|
||||
case ReDefSP:
|
||||
m_spValue = inst->getDst();
|
||||
m_spOffset = inst->getExtra<StackOffset>()->offset;
|
||||
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);
|
||||
break;
|
||||
case AssertStk:
|
||||
case CastStk:
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
||||
case SpillFrame:
|
||||
m_spValue = inst->getDst();
|
||||
m_spOffset += kNumActRecCells;
|
||||
break;
|
||||
|
||||
case InterpOne: {
|
||||
m_spValue = inst->getDst();
|
||||
int64_t stackAdjustment = inst->getSrc(3)->getValInt();
|
||||
Type resultType = inst->getTypeParam();
|
||||
// push the return value if any and adjust for the popped values
|
||||
m_spOffset -= stackAdjustment;
|
||||
break;
|
||||
}
|
||||
|
||||
case StProp:
|
||||
case StPropNT:
|
||||
// fall through to StMem; stored value is the same arg number (2)
|
||||
case StMem:
|
||||
case StMemNT:
|
||||
m_refCountedMemValue = inst->getSrc(2);
|
||||
break;
|
||||
|
||||
case LdMem:
|
||||
case LdProp:
|
||||
case LdRef:
|
||||
m_refCountedMemValue = inst->getDst();
|
||||
break;
|
||||
|
||||
case StRefNT:
|
||||
case StRef: {
|
||||
m_refCountedMemValue = inst->getSrc(2);
|
||||
SSATmp* newRef = inst->getDst();
|
||||
SSATmp* prevRef = inst->getSrc(0);
|
||||
// update other tracked locals that also contain prevRef
|
||||
updateLocalRefValues(prevRef, newRef);
|
||||
break;
|
||||
}
|
||||
|
||||
case StLocNT:
|
||||
case StLoc:
|
||||
setLocalValue(inst->getExtra<LocalId>()->locId,
|
||||
inst->getSrc(1));
|
||||
break;
|
||||
|
||||
case LdLoc:
|
||||
setLocalValue(inst->getExtra<LdLoc>()->locId, inst->getDst());
|
||||
break;
|
||||
|
||||
case OverrideLoc:
|
||||
// If changing the inner type of a boxed local, also drop the
|
||||
// information about inner types for any other boxed locals.
|
||||
if (inst->getTypeParam().isBoxed()) {
|
||||
dropLocalRefsInnerTypes();
|
||||
}
|
||||
// fallthrough
|
||||
case AssertLoc:
|
||||
case GuardLoc:
|
||||
setLocalType(inst->getExtra<LocalId>()->locId,
|
||||
inst->getTypeParam());
|
||||
break;
|
||||
|
||||
case SpillFrame:
|
||||
m_spValue = inst->getDst();
|
||||
m_spOffset += kNumActRecCells;
|
||||
break;
|
||||
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 InterpOne: {
|
||||
m_spValue = inst->getDst();
|
||||
int64_t stackAdjustment = inst->getSrc(3)->getValInt();
|
||||
Type resultType = inst->getTypeParam();
|
||||
// push the return value if any and adjust for the popped values
|
||||
m_spOffset -= stackAdjustment;
|
||||
break;
|
||||
}
|
||||
case IterInit:
|
||||
// kill the local to which this instruction stores iter's value
|
||||
killLocalValue(inst->getSrc(3)->getValInt());
|
||||
break;
|
||||
|
||||
case StProp:
|
||||
case StPropNT:
|
||||
// fall through to StMem; stored value is the same arg number (2)
|
||||
case StMem:
|
||||
case StMemNT:
|
||||
m_refCountedMemValue = inst->getSrc(2);
|
||||
break;
|
||||
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 LdMem:
|
||||
case LdProp:
|
||||
case LdRef:
|
||||
m_refCountedMemValue = inst->getDst();
|
||||
break;
|
||||
case IterNext:
|
||||
// kill the local to which this instruction stores iter's value
|
||||
killLocalValue(inst->getSrc(2)->getValInt());
|
||||
break;
|
||||
|
||||
case StRefNT:
|
||||
case StRef: {
|
||||
m_refCountedMemValue = inst->getSrc(2);
|
||||
SSATmp* newRef = inst->getDst();
|
||||
SSATmp* prevRef = inst->getSrc(0);
|
||||
// update other tracked locals that also contain prevRef
|
||||
updateLocalRefValues(prevRef, newRef);
|
||||
break;
|
||||
}
|
||||
case LdThis:
|
||||
m_thisIsAvailable = true;
|
||||
break;
|
||||
|
||||
case StLocNT:
|
||||
case StLoc:
|
||||
setLocalValue(inst->getExtra<LocalId>()->locId,
|
||||
inst->getSrc(1));
|
||||
break;
|
||||
|
||||
case LdLoc:
|
||||
setLocalValue(inst->getExtra<LdLoc>()->locId, inst->getDst());
|
||||
break;
|
||||
|
||||
case OverrideLoc:
|
||||
// If changing the inner type of a boxed local, also drop the
|
||||
// information about inner types for any other boxed locals.
|
||||
if (inst->getTypeParam().isBoxed()) {
|
||||
dropLocalRefsInnerTypes();
|
||||
}
|
||||
// fallthrough
|
||||
case AssertLoc:
|
||||
case GuardLoc:
|
||||
setLocalType(inst->getExtra<LocalId>()->locId,
|
||||
inst->getTypeParam());
|
||||
break;
|
||||
|
||||
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:
|
||||
// kill the local to which this instruction stores iter's value
|
||||
killLocalValue(inst->getSrc(3)->getValInt());
|
||||
break;
|
||||
|
||||
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:
|
||||
// kill the local to which this instruction stores iter's value
|
||||
killLocalValue(inst->getSrc(2)->getValInt());
|
||||
break;
|
||||
|
||||
case LdThis:
|
||||
m_thisIsAvailable = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (VectorEffects::supported(inst)) {
|
||||
@@ -407,62 +456,6 @@ 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 = cns(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;
|
||||
|
||||
@@ -94,16 +94,6 @@ struct TraceBuilder {
|
||||
const Func* func);
|
||||
~TraceBuilder();
|
||||
|
||||
void beginInlining(const Func* target,
|
||||
SSATmp* calleeFP,
|
||||
SSATmp* calleeSP,
|
||||
SSATmp* prevSP,
|
||||
int32_t prevSPOff);
|
||||
void endInlining();
|
||||
|
||||
// XXX: Accessible for inlining, for now.
|
||||
void setLocalValue(unsigned id, SSATmp* value);
|
||||
|
||||
void setEnableCse(bool val) { m_enableCse = val; }
|
||||
void setEnableSimplification(bool val) { m_enableSimplification = val; }
|
||||
|
||||
@@ -362,11 +352,14 @@ private:
|
||||
void killLocalValue(uint32_t id);
|
||||
void setLocalType(uint32_t id, Type type);
|
||||
Type getLocalType(unsigned id) const;
|
||||
void setLocalValue(unsigned id, SSATmp* value);
|
||||
SSATmp* getLocalValue(unsigned id) const;
|
||||
bool isValueAvailable(SSATmp*) const;
|
||||
bool anyLocalHasValue(SSATmp*) const;
|
||||
bool callerLocalHasValue(SSATmp*) const;
|
||||
void updateLocalRefValues(SSATmp* oldRef, SSATmp* newRef);
|
||||
void trackDefInlineFP(IRInstruction* inst);
|
||||
void trackInlineReturn(IRInstruction* inst);
|
||||
void updateTrackedState(IRInstruction* inst);
|
||||
void clearTrackedState();
|
||||
void dropLocalRefsInnerTypes();
|
||||
|
||||
@@ -2929,7 +2929,7 @@ void Translator::reanalizeConsumers(Tracelet& tclet, DynLocation* depDynLoc) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
/*
|
||||
* This method looks at all the uses of the tracelet dependencies in the
|
||||
* instruction stream and tries to relax the type associated with each location.
|
||||
*/
|
||||
@@ -2949,7 +2949,8 @@ void Translator::relaxDeps(Tracelet& tclet, TraceletContext& tctxt) {
|
||||
}
|
||||
}
|
||||
|
||||
// Process the instruction stream, constraining the relaxed types along the way
|
||||
// Process the instruction stream, constraining the relaxed types
|
||||
// along the way
|
||||
for (NormalizedInstruction* instr = tclet.m_instrStream.first; instr;
|
||||
instr = instr->next) {
|
||||
for (size_t i = 0; i < instr->inputs.size(); i++) {
|
||||
@@ -2962,11 +2963,11 @@ void Translator::relaxDeps(Tracelet& tclet, TraceletContext& tctxt) {
|
||||
}
|
||||
}
|
||||
|
||||
// For each dependency, if we found a more relaxed type for it, use such type.
|
||||
for (auto mapIt = locRelxTypeMap.begin(); mapIt != locRelxTypeMap.end();
|
||||
mapIt++) {
|
||||
DynLocation* loc = mapIt->first;
|
||||
const GuardType& relxType = mapIt->second;
|
||||
// For each dependency, if we found a more relaxed type for it, use
|
||||
// such type.
|
||||
for (auto& kv : locRelxTypeMap) {
|
||||
DynLocation* loc = kv.first;
|
||||
const GuardType& relxType = kv.second;
|
||||
if (relxType.isRelaxed()) {
|
||||
TRACE(1, "relaxDeps: Loc: %s oldType: %s => newType: %s\n",
|
||||
loc->location.pretty().c_str(),
|
||||
@@ -2988,17 +2989,6 @@ 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.
|
||||
@@ -3041,28 +3031,41 @@ static bool shouldAnalyzeCallee(const NormalizedInstruction* fcall) {
|
||||
numArgs, target->numParams());
|
||||
return false;
|
||||
}
|
||||
if (numArgs != 0) {
|
||||
FTRACE(1, "analyzeCallee: currently ignoring calls with parameters\n");
|
||||
if (target->numLocals() != target->numParams()) {
|
||||
FTRACE(1, "analyzeCallee: not inlining functions with more locals "
|
||||
"than params\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!findFPushForCall(fpi, fcall)) {
|
||||
FTRACE(1, "analyzeCallee: push instruction was in a different "
|
||||
"tracelet\n");
|
||||
return false;
|
||||
// Find the fpush and ensure it's in this tracelet---refuse to
|
||||
// inline if there are any calls in order to prepare arguments.
|
||||
for (auto* ni = fcall->prev; ni; ni = ni->prev) {
|
||||
if (ni->source.offset() == fpi->m_fpushOff) {
|
||||
return true;
|
||||
}
|
||||
if (isFCallStar(ni->op()) || ni->op() == OpFCallBuiltin) {
|
||||
FTRACE(1, "analyzeCallee: fpi region contained other calls\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
FTRACE(1, "analyzeCallee: push instruction was in a different "
|
||||
"tracelet\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
extern bool shouldIRInline(const Func* curFunc,
|
||||
const Func* func,
|
||||
const Tracelet& callee);
|
||||
|
||||
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;
|
||||
auto const numArgs = fcall->imm[0].u_IVA;
|
||||
auto const target = fcall->funcd;
|
||||
auto const callerFunc = curFunc();
|
||||
|
||||
/*
|
||||
* Prepare a map for all the known information about the argument
|
||||
@@ -3165,6 +3168,16 @@ void Translator::analyzeCallee(TraceletContext& tas,
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the IR can't inline this, give up now. Below we're going to
|
||||
* start making changes to the traclet that is making the call
|
||||
* (potentially increasing the specificity of guards), and we don't
|
||||
* want to do that unnecessarily.
|
||||
*/
|
||||
if (!shouldIRInline(callerFunc, target, *subTrace)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Disabled for now:
|
||||
*
|
||||
@@ -3196,18 +3209,17 @@ void Translator::analyzeCallee(TraceletContext& tas,
|
||||
}
|
||||
|
||||
/*
|
||||
* In order for relaxDeps not to relax guards on things we may
|
||||
* In order for relaxDeps not to relax guards on some 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.
|
||||
* (We could do better by letting relaxDeps look through the
|
||||
* callee.)
|
||||
*/
|
||||
restoreFrame();
|
||||
assert(callerArgLocs.empty());
|
||||
for (auto& loc : callerArgLocs) {
|
||||
fcall->inputs.push_back(tas.recordRead(InputInfo(loc), m_useHHIR));
|
||||
}
|
||||
|
||||
FTRACE(1, "analyzeCallee: inline candidate\n");
|
||||
fcall->calleeTrace = std::move(subTrace);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
function wat(string $x) {
|
||||
return 12;
|
||||
}
|
||||
|
||||
function main() {
|
||||
try {
|
||||
wat(12);
|
||||
} catch (Exception $x) { echo "ok\n"; }
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Argument 1 passed to wat() must be an instance of string, int given in %s on line 5
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
../hiphop.hphp_opts
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
../hiphop.opts
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
class FluentObj {
|
||||
private
|
||||
$first,
|
||||
$second,
|
||||
$third,
|
||||
$fourth,
|
||||
$fifth;
|
||||
|
||||
public function __construct($first) {
|
||||
$this->first = $first;
|
||||
}
|
||||
|
||||
public function setSecond($x) {
|
||||
$this->second = $x;
|
||||
return $this;
|
||||
}
|
||||
public function setThird($x) {
|
||||
$this->third = $x;
|
||||
return $this;
|
||||
}
|
||||
public function setFourth($x) {
|
||||
$this->fourth = $x;
|
||||
return $this;
|
||||
}
|
||||
public function setFifth($x) {
|
||||
$this->fifth = $x;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValue() {
|
||||
return $this->first;
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
$k = (new FluentObj('a'))
|
||||
->setSecond('b')
|
||||
->setThird('c')
|
||||
->setFourth('d')
|
||||
->setFifth('e')
|
||||
->getValue();
|
||||
echo $k;
|
||||
echo "\n";
|
||||
}
|
||||
main();
|
||||
@@ -0,0 +1 @@
|
||||
a
|
||||
@@ -0,0 +1 @@
|
||||
a
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
class SecurityLogger {
|
||||
private $x, $y;
|
||||
|
||||
public function setResource($x) {
|
||||
$this->x = $x;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setExtra($x) {
|
||||
$this->y = $x;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function log() {
|
||||
echo "logging\n";
|
||||
}
|
||||
}
|
||||
|
||||
function SecurityLogger($x, $y) {
|
||||
return new SecurityLogger();
|
||||
}
|
||||
|
||||
function unique_function() {
|
||||
mt_rand();
|
||||
return array('1');
|
||||
}
|
||||
|
||||
function foo() {
|
||||
$resource_arr = array('a' => 'b');
|
||||
// Inlining something which calls a function inside a generator
|
||||
// doesn't work. This test shouldn't inline right now (if it does
|
||||
// it crashes).
|
||||
SecurityLogger(12, 12)
|
||||
->setResource((array)unique_function())
|
||||
->log();
|
||||
yield 12;
|
||||
}
|
||||
|
||||
function main() {
|
||||
foreach (foo() as $k) {}
|
||||
}
|
||||
main();
|
||||
@@ -0,0 +1 @@
|
||||
logging
|
||||
@@ -0,0 +1,28 @@
|
||||
<?hh
|
||||
|
||||
final class HTTPHeaders {
|
||||
private $headers;
|
||||
|
||||
public function __construct() {
|
||||
$this->headers = Map {
|
||||
'foo' => 'bar',
|
||||
'baz' => 'quux',
|
||||
};
|
||||
}
|
||||
|
||||
public function getAllHeaders(): Map<string,string> {
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function getHeader(string $name, ?string $default = null): ?string {
|
||||
return idx($this->getAllHeaders(), strtolower($name), $default);
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
$h = new HTTPHeaders();
|
||||
$val = $h->getHeader('blah', 'wat');
|
||||
echo $val;
|
||||
echo "\n";
|
||||
}
|
||||
main();
|
||||
@@ -0,0 +1 @@
|
||||
wat
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
../hiphop.hphp_opts
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
../hiphop.opts
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
class Bar{}
|
||||
|
||||
class Foo {
|
||||
private $x;
|
||||
private $y;
|
||||
|
||||
public function __construct($x) {
|
||||
$this->x = $x;
|
||||
}
|
||||
|
||||
public function setX($x) {
|
||||
$this->x = $x;
|
||||
}
|
||||
|
||||
public function getX() {
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
public function setXVerified(string $str) {
|
||||
$this->x = $str;
|
||||
}
|
||||
|
||||
public function setY(Bar $k) {
|
||||
$this->y = $k;
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
$k = new Foo("something");
|
||||
echo $k->getX();
|
||||
echo "\n";
|
||||
$k->setX("foo");
|
||||
echo $k->getX();
|
||||
echo "\n";
|
||||
|
||||
$k->setXVerified("string");
|
||||
echo $k->getX();
|
||||
echo "\n";
|
||||
|
||||
$k->setY(new Bar);
|
||||
}
|
||||
main();
|
||||
@@ -0,0 +1,3 @@
|
||||
something
|
||||
foo
|
||||
string
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
../hiphop.hphp_opts
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
../hiphop.opts
|
||||
Referência em uma Nova Issue
Bloquear um usuário