Transl::Tracelet -> JIT::RegionDesc converter
This converter enables much more thorough testing of the region translator. It currently passes all tests, though it does punt on one or more Tracelets in roughly 1/6th of them. The two big unimplemented features for the whole pipeline are interpOne (should be pretty easy) and parameter reffiness checks (probably nontrivial). I'll attack those in separate diffs next.
Esse commit está contido em:
@@ -327,7 +327,13 @@ Instruction set
|
||||
D:T = CheckType<T> S0:{Gen|Nullptr} -> L
|
||||
|
||||
Check that the type of the src S0 is T, and if so copy it to D. If
|
||||
S0 is not type T, branch to the label L.
|
||||
S0 is not type T, branch to the label L. If T is Nullptr, the
|
||||
instruction is being used to check for a C++ nullptr, usually from a
|
||||
type like {Str|Nullptr}.
|
||||
|
||||
D:T = AssertType<T> S0:{Gen|Nullptr}
|
||||
|
||||
Assert that the type of S0 is T, copying it to D.
|
||||
|
||||
CheckTypeMem<T> S0:PtrToGen -> L
|
||||
|
||||
@@ -354,7 +360,8 @@ AssertLoc<T,localId> S0:FramePtr
|
||||
OverrideLoc<T,localId> S0:FramePtr
|
||||
|
||||
Overrides tracked information about the type of a local in frame S0.
|
||||
This is used to fix tracked state after InterpOne instructions.
|
||||
T doesn't have to be related to the local's current type, so this
|
||||
may be used to update tracked state after InterpOne instructions.
|
||||
|
||||
D:StkPtr = GuardStk<T,offset> S0:StkPtr
|
||||
|
||||
|
||||
@@ -406,6 +406,7 @@ public:
|
||||
F(uint64_t, JitGlobalTranslationLimit, -1) \
|
||||
F(bool, JitTrampolines, true) \
|
||||
F(string, JitProfilePath, string("")) \
|
||||
F(bool, JitTypePrediction, true) \
|
||||
F(int32_t, JitStressTypePredPercent, 0) \
|
||||
F(uint32_t, JitWarmupRequests, kDefaultWarmupRequests) \
|
||||
F(bool, JitProfileRecord, false) \
|
||||
|
||||
@@ -3998,6 +3998,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopExit(PC& pc) {
|
||||
echo(tvCellAsVariant(c1).toString());
|
||||
}
|
||||
m_stack.popC();
|
||||
m_stack.pushNull();
|
||||
throw ExitException(exitCode);
|
||||
}
|
||||
|
||||
|
||||
@@ -806,6 +806,13 @@ void CodeGenerator::cgAssertNonNull(IRInstruction* inst) {
|
||||
emitMovRegReg(m_as, srcReg, dstReg);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgAssertType(IRInstruction* inst) {
|
||||
auto const& srcRegs = m_regs[inst->src(0)];
|
||||
auto const& dstRegs = m_regs[inst->dst()];
|
||||
shuffle2(m_as, srcRegs.reg(0), srcRegs.reg(1),
|
||||
dstRegs.reg(0), dstRegs.reg(1));
|
||||
}
|
||||
|
||||
void CodeGenerator::cgLdUnwinderValue(IRInstruction* inst) {
|
||||
cgLoad(rVmTl, TargetCache::kUnwinderTvOff, inst);
|
||||
}
|
||||
|
||||
@@ -471,7 +471,7 @@ void optimizeActRecs(IRTrace* trace, DceState& state, IRFactory* factory,
|
||||
|
||||
// Assuming that the 'consumer' instruction consumes 'src', trace back through
|
||||
// src's instruction to the real origin of the value. Currently this traces
|
||||
// through CheckType and DefLabel.
|
||||
// through CheckType, AssertType and DefLabel.
|
||||
void consumeIncRef(const IRInstruction* consumer, const SSATmp* src,
|
||||
DceState& state, SSACache& ssas, SSASet visitedSrcs) {
|
||||
assert(!visitedSrcs.count(src) && "Cycle detected in dataflow graph");
|
||||
@@ -486,11 +486,11 @@ void consumeIncRef(const IRInstruction* consumer, const SSATmp* src,
|
||||
|
||||
const IRInstruction* srcInst = src->inst();
|
||||
visitedSrcs.insert(src);
|
||||
if (srcInst->op() == CheckType &&
|
||||
if ((srcInst->op() == CheckType || srcInst->op() == AssertType) &&
|
||||
srcInst->typeParam().maybeCounted()) {
|
||||
// srcInst is a CheckType that guards to a refcounted type. We need to
|
||||
// trace through to its source. If the CheckType guards to a non-refcounted
|
||||
// type then the reference is consumed by CheckType itself.
|
||||
// srcInst is a CheckType/AsserType that guards to a refcounted type. We
|
||||
// need to trace through to its source. If the instruciton guards to a
|
||||
// non-refcounted type then the reference is consumed by CheckType itself.
|
||||
consumeIncRef(consumer, srcInst->src(0), state, ssas, visitedSrcs);
|
||||
} else if (srcInst->op() == DefLabel) {
|
||||
// srcInst is a DefLabel that may be a join node. We need to find
|
||||
|
||||
@@ -2384,9 +2384,12 @@ void HhbcTranslator::guardTypeLocal(uint32_t locId, Type type) {
|
||||
}
|
||||
|
||||
void HhbcTranslator::guardTypeLocation(const Location& loc, Type type) {
|
||||
assert(type.subtypeOf(Type::Gen | Type::Cls));
|
||||
|
||||
if (loc.isStack()) {
|
||||
guardTypeStack(loc.offset, type);
|
||||
} else if (loc.isLocal()) {
|
||||
assert(type.not(Type::Cls));
|
||||
guardTypeLocal(loc.offset, type);
|
||||
} else {
|
||||
not_reached();
|
||||
@@ -2408,6 +2411,8 @@ void HhbcTranslator::overrideTypeLocal(uint32_t locId, Type type) {
|
||||
|
||||
void HhbcTranslator::checkTypeLocation(const Location& loc, Type type,
|
||||
Offset dest) {
|
||||
assert(type.subtypeOf(Type::Gen));
|
||||
|
||||
if (loc.isStack()) {
|
||||
checkTypeStack(loc.offset, type, dest);
|
||||
} else if (loc.isLocal()) {
|
||||
@@ -2418,9 +2423,12 @@ void HhbcTranslator::checkTypeLocation(const Location& loc, Type type,
|
||||
}
|
||||
|
||||
void HhbcTranslator::assertTypeLocation(const Location& loc, Type type) {
|
||||
assert(type.subtypeOf(Type::Gen | Type::Cls));
|
||||
|
||||
if (loc.isStack()) {
|
||||
assertTypeStack(loc.offset, type);
|
||||
} else if (loc.isLocal()) {
|
||||
assert(type.not(Type::Cls));
|
||||
assertTypeLocal(loc.offset, type);
|
||||
} else {
|
||||
not_reached();
|
||||
@@ -2434,20 +2442,24 @@ void HhbcTranslator::guardTypeStack(uint32_t stackIndex, Type type) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(m_evalStack.size() == 0);
|
||||
assert(m_stackDeficit == 0); // This should only be called at the beginning
|
||||
// of a trace, with a clean stack.
|
||||
gen(GuardStk, type, StackOffset(stackIndex), m_tb->sp());
|
||||
}
|
||||
|
||||
void HhbcTranslator::checkTypeStack(uint32_t idx, Type type, Offset dest) {
|
||||
auto exitTrace = getExitTrace(dest);
|
||||
if (idx >= m_evalStack.size()) {
|
||||
FTRACE(1, "checkTypeStack({}): no tmp: {}\n", idx, type.toString());
|
||||
gen(CheckStk, type, exitTrace, StackOffset(idx), m_tb->sp());
|
||||
} else {
|
||||
if (idx < m_evalStack.size()) {
|
||||
FTRACE(1, "checkTypeStack(){}: generating CheckType for {}\n",
|
||||
idx, type.toString());
|
||||
SSATmp* tmp = m_evalStack.top(idx);
|
||||
assert(tmp);
|
||||
m_evalStack.replace(idx, gen(CheckType, type, exitTrace, tmp));
|
||||
} else {
|
||||
FTRACE(1, "checkTypeStack({}): no tmp: {}\n", idx, type.toString());
|
||||
gen(CheckStk, type, exitTrace,
|
||||
StackOffset(idx - m_evalStack.size () + m_stackDeficit), m_tb->sp());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2455,32 +2467,29 @@ void HhbcTranslator::checkTypeTopOfStack(Type type, Offset nextByteCode) {
|
||||
checkTypeStack(0, type, nextByteCode);
|
||||
}
|
||||
|
||||
void HhbcTranslator::assertTypeStack(uint32_t stackIndex, Type type) {
|
||||
SSATmp* tmp = m_evalStack.top(stackIndex);
|
||||
if (!tmp) {
|
||||
gen(AssertStk, type, StackOffset(stackIndex), m_tb->sp());
|
||||
return;
|
||||
void HhbcTranslator::assertTypeStack(uint32_t idx, Type type) {
|
||||
if (idx < m_evalStack.size()) {
|
||||
SSATmp* tmp = m_evalStack.top(idx);
|
||||
assert(tmp);
|
||||
m_evalStack.replace(idx, gen(AssertType, type, tmp));
|
||||
} else {
|
||||
gen(AssertStk, type,
|
||||
StackOffset(idx - m_evalStack.size() + m_stackDeficit),
|
||||
m_tb->sp());
|
||||
}
|
||||
|
||||
/*
|
||||
* We already had a value in flight---refine the type in case it
|
||||
* allows generating better code. This is safe because in this path
|
||||
* we know the value is *actually* this type due to static analysis
|
||||
* (not based on guards).
|
||||
*/
|
||||
refineType(tmp, type);
|
||||
}
|
||||
|
||||
void HhbcTranslator::assertString(const Location& loc, const StringData* str) {
|
||||
auto idx = loc.offset;
|
||||
|
||||
if (loc.isStack()) {
|
||||
if (idx >= m_evalStack.size()) {
|
||||
gen(AssertStkVal, StackOffset(idx), cns(str));
|
||||
} else {
|
||||
if (idx < m_evalStack.size()) {
|
||||
DEBUG_ONLY SSATmp* oldStr = m_evalStack.top(idx);
|
||||
assert(oldStr->type().maybe(Type::Str));
|
||||
m_evalStack.replace(idx, cns(str));
|
||||
} else {
|
||||
gen(AssertStkVal, StackOffset(idx - m_evalStack.size() + m_stackDeficit),
|
||||
m_tb->sp(), cns(str));
|
||||
}
|
||||
} else if (loc.isLocal()) {
|
||||
assert(m_tb->getLocalType(loc.offset).maybe(Type::Str));
|
||||
@@ -2506,7 +2515,8 @@ RuntimeType HhbcTranslator::rttFromLocation(const Location& loc) {
|
||||
val = m_evalStack.top(i);
|
||||
t = val->type();
|
||||
} else {
|
||||
auto stackVal = getStackValue(m_tb->sp(), i);
|
||||
auto stackVal = getStackValue(m_tb->sp(),
|
||||
i - m_evalStack.size() + m_stackDeficit);
|
||||
val = stackVal.value;
|
||||
t = stackVal.knownType;
|
||||
}
|
||||
@@ -3235,6 +3245,66 @@ void HhbcTranslator::emitInterpOneCF(int numPopped) {
|
||||
m_hasExit = true;
|
||||
}
|
||||
|
||||
std::string HhbcTranslator::showStack() const {
|
||||
if (isInlining()) {
|
||||
return folly::format("{:*^60}\n",
|
||||
" I don't understand inlining stacks yet ").str();
|
||||
}
|
||||
std::ostringstream out;
|
||||
auto header = [&](const std::string& str) {
|
||||
out << folly::format("+{:-^62}+\n", str);
|
||||
};
|
||||
|
||||
const int32_t stackDepth = m_tb->spOffset() - curFunc()->numLocals() +
|
||||
m_evalStack.size() - m_stackDeficit;
|
||||
auto spOffset = stackDepth;
|
||||
auto elem = [&](const std::string& str) {
|
||||
out << folly::format("| {:<60} |\n",
|
||||
folly::format("{:>2}: {}",
|
||||
stackDepth - spOffset, str));
|
||||
assert(spOffset > 0);
|
||||
--spOffset;
|
||||
};
|
||||
auto fpiStack = m_fpiStack;
|
||||
auto checkFpi = [&]() {
|
||||
if (!fpiStack.empty() &&
|
||||
spOffset - kNumActRecCells == fpiStack.top().second) {
|
||||
for (unsigned i = 0; i < kNumActRecCells; ++i) elem("ActRec");
|
||||
fpiStack.pop();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
header(folly::format(" {} stack element(s); m_evalStack: ",
|
||||
stackDepth).str());
|
||||
for (unsigned i = 0; i < m_evalStack.size(); ++i) {
|
||||
while (checkFpi());
|
||||
SSATmp* value = m_evalStack.top(i);
|
||||
elem(value->inst()->toString());
|
||||
}
|
||||
|
||||
header(" in-memory ");
|
||||
for (unsigned i = m_stackDeficit; spOffset > 0; ) {
|
||||
assert(i < curFunc()->maxStackCells());
|
||||
if (checkFpi()) {
|
||||
i += kNumActRecCells;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto stkVal = getStackValue(m_tb->sp(), i);
|
||||
std::ostringstream elemStr;
|
||||
if (stkVal.knownType.equals(Type::None)) elem("unknown");
|
||||
else if (stkVal.value) elem(stkVal.value->inst()->toString());
|
||||
else elem(stkVal.knownType.toString());
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
header("");
|
||||
return out.str();
|
||||
}
|
||||
|
||||
/*
|
||||
* Get SSATmps representing all the information on the virtual eval
|
||||
* stack in preparation for a spill or exit trace. Top of stack will
|
||||
|
||||
@@ -153,6 +153,7 @@ struct HhbcTranslator {
|
||||
void setThisAvailable();
|
||||
void emitInterpOne(Type type, int numPopped, int numExtraPushed = 0);
|
||||
void emitInterpOneCF(int numPopped);
|
||||
std::string showStack() const;
|
||||
|
||||
/*
|
||||
* An emit* function for each HHBC opcode.
|
||||
|
||||
@@ -358,10 +358,11 @@ bool IRInstruction::hasMemEffects() const {
|
||||
bool IRInstruction::canCSE() const {
|
||||
auto canCSE = opcodeHasFlags(op(), CanCSE);
|
||||
// Make sure that instructions that are CSE'able can't produce a reference
|
||||
// count or consume reference counts. CheckType is special because it can
|
||||
// refine a maybeCounted type to a notCounted type, so it logically consumes
|
||||
// and produces a reference without doing any work.
|
||||
assert(!canCSE || !consumesReferences() || m_op == CheckType);
|
||||
// count or consume reference counts. CheckType/AssertType are special
|
||||
// because they can refine a maybeCounted type to a notCounted type, so they
|
||||
// logically consume and produce a reference without doing any work.
|
||||
assert(!canCSE || !consumesReferences() ||
|
||||
m_op == CheckType || m_op == AssertType);
|
||||
return canCSE && !mayReenterHelper();
|
||||
}
|
||||
|
||||
@@ -373,9 +374,9 @@ bool IRInstruction::consumesReference(int srcNo) const {
|
||||
if (!consumesReferences()) {
|
||||
return false;
|
||||
}
|
||||
// CheckType consumes a reference if we're guarding from a maybeCounted type
|
||||
// to a notCounted type.
|
||||
if (m_op == CheckType) {
|
||||
// CheckType/AssertType consume a reference if we're guarding from a
|
||||
// maybeCounted type to a notCounted type.
|
||||
if (m_op == CheckType || m_op == AssertType) {
|
||||
assert(srcNo == 0);
|
||||
return src(0)->type().maybeCounted() && typeParam().notCounted();
|
||||
}
|
||||
@@ -512,7 +513,9 @@ bool IRInstruction::storesCell(uint32_t srcIdx) const {
|
||||
|
||||
SSATmp* IRInstruction::getPassthroughValue() const {
|
||||
assert(isPassthrough());
|
||||
assert(m_op == IncRef || m_op == CheckType || m_op == Mov);
|
||||
assert(m_op == IncRef ||
|
||||
m_op == CheckType || m_op == AssertType ||
|
||||
m_op == Mov);
|
||||
return src(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,9 @@
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/intrusive/list.hpp>
|
||||
|
||||
#include "folly/Range.h"
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/Format.h"
|
||||
#include "folly/Range.h"
|
||||
|
||||
#include "hphp/util/asm-x64.h"
|
||||
#include "hphp/util/trace.h"
|
||||
@@ -64,13 +65,19 @@ struct SSATmp;
|
||||
struct Block;
|
||||
struct IRTrace;
|
||||
|
||||
class FailedIRGen : public std::exception {
|
||||
class FailedIRGen : public std::runtime_error {
|
||||
public:
|
||||
const char* file;
|
||||
const int line;
|
||||
const char* func;
|
||||
FailedIRGen(const char* _file, int _line, const char* _func) :
|
||||
file(_file), line(_line), func(_func) { }
|
||||
const char* const file;
|
||||
const int line;
|
||||
const char* const func;
|
||||
|
||||
FailedIRGen(const char* _file, int _line, const char* _func)
|
||||
: std::runtime_error(folly::format("FailedIRGen @ {}:{} in {}",
|
||||
_file, _line, _func).str())
|
||||
, file(_file)
|
||||
, line(_line)
|
||||
, func(_func)
|
||||
{}
|
||||
};
|
||||
|
||||
#define SPUNT(instr) do { \
|
||||
@@ -155,6 +162,7 @@ class FailedIRGen : public std::exception {
|
||||
#define IR_OPCODES \
|
||||
/* name dstinfo srcinfo flags */ \
|
||||
O(CheckType, DParam, S(Gen,Nullptr), C|E|CRc|PRc|P) \
|
||||
O(AssertType, DParam, S(Gen,Nullptr), C|E|CRc|PRc|P) \
|
||||
O(CheckTypeMem, ND, S(PtrToGen), E) \
|
||||
O(GuardLoc, ND, S(FramePtr), E) \
|
||||
O(GuardStk, D(StkPtr), S(StkPtr), E) \
|
||||
|
||||
@@ -105,7 +105,7 @@ TranslatorX64::irCheckType(X64Assembler& a,
|
||||
SrcRec& fail) {
|
||||
// We can get invalid inputs as a side effect of reading invalid
|
||||
// items out of BBs we truncate; they don't need guards.
|
||||
if (rtt.isVagueValue()) return;
|
||||
assert(!rtt.isVagueValue());
|
||||
|
||||
switch (l.space) {
|
||||
case Location::Stack:
|
||||
@@ -125,7 +125,7 @@ TranslatorX64::irCheckType(X64Assembler& a,
|
||||
case Location::Litstr:
|
||||
case Location::Litint:
|
||||
case Location::This:
|
||||
assert(false); // should not happen
|
||||
not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,8 @@ Translator::translateUnboxR(const NormalizedInstruction& i) {
|
||||
if (i.noOp) {
|
||||
// statically proved to be unboxed -- just pass that info to the IR
|
||||
TRACE(1, "HHIR: translateUnboxR: output inferred to be Cell\n");
|
||||
m_hhbcTrans->assertTypeStack(0, JIT::Type::Cell);
|
||||
m_hhbcTrans->assertTypeLocation(Location(Location::Stack, 0),
|
||||
JIT::Type::Cell);
|
||||
} else {
|
||||
HHIR_EMIT(UnboxR);
|
||||
}
|
||||
@@ -1585,7 +1586,9 @@ void Translator::interpretInstr(const NormalizedInstruction& i) {
|
||||
}
|
||||
|
||||
void Translator::translateInstr(const NormalizedInstruction& i) {
|
||||
FTRACE(1, "translating: {}\n", opcodeToName(i.op()));
|
||||
FTRACE(1, "\n{:-^60}\n", folly::format("translating {} with stack:\n{}",
|
||||
i.toString(),
|
||||
m_hhbcTrans->showStack()));
|
||||
|
||||
m_hhbcTrans->setBcOff(i.source.offset(),
|
||||
i.breaksTracelet && !m_hhbcTrans->isInlining());
|
||||
@@ -1674,20 +1677,11 @@ static bool supportedInterpOne(const NormalizedInstruction* i) {
|
||||
}
|
||||
|
||||
TranslatorX64::TranslateResult
|
||||
TranslatorX64::irTranslateTracelet(Tracelet& t,
|
||||
const TCA start,
|
||||
const TCA stubStart,
|
||||
vector<TransBCMapping>* bcMap) {
|
||||
auto transResult = Failure;
|
||||
TranslatorX64::irTranslateTracelet(Tracelet& t) {
|
||||
const SrcKey &sk = t.m_sk;
|
||||
SrcRec& srcRec = *getSrcRec(sk);
|
||||
assert(srcRec.inProgressTailJumps().size() == 0);
|
||||
try {
|
||||
// Don't translate if we have already reached the maximum # of
|
||||
// translations for this tracelet
|
||||
HHIR_UNIMPLEMENTED_WHEN(checkTranslationLimit(t.m_sk, srcRec),
|
||||
TOO_MANY_TRANSLATIONS);
|
||||
|
||||
irEmitResolvedDeps(t.m_resolvedDeps);
|
||||
emitGuardChecks(a, sk, t.m_dependencies, t.m_refDeps, srcRec);
|
||||
|
||||
@@ -1700,7 +1694,6 @@ TranslatorX64::irTranslateTracelet(Tracelet& t,
|
||||
|
||||
emitRB(a, RBTypeTraceletBody, t.m_sk);
|
||||
Stats::emitInc(a, Stats::Instr_TC, t.m_numOpcodes);
|
||||
recordBCInstr(OpTraceletGuard, a, start);
|
||||
|
||||
// Profiling on function entry.
|
||||
if (m_curTrace->m_sk.offset() == curFunc()->base()) {
|
||||
@@ -1737,45 +1730,41 @@ TranslatorX64::irTranslateTracelet(Tracelet& t,
|
||||
// and retry
|
||||
if (!ni->interp && supportedInterpOne(ni)) {
|
||||
ni->interp = true;
|
||||
transResult = Retry;
|
||||
return Retry;
|
||||
} else {
|
||||
throw fcg;
|
||||
}
|
||||
break;
|
||||
}
|
||||
assert(ni->source.offset() >= curFunc()->base());
|
||||
// We sometimes leave the tail of a truncated tracelet in place to aid
|
||||
// analysis, but breaksTracelet is authoritative.
|
||||
if (ni->breaksTracelet) break;
|
||||
}
|
||||
|
||||
traceEnd();
|
||||
if (transResult != Retry) {
|
||||
try {
|
||||
transResult = Success;
|
||||
traceCodeGen(bcMap);
|
||||
} catch (JIT::FailedCodeGen& fcg) {
|
||||
// Code-gen failed. Search for the bytecode instruction that caused the
|
||||
// problem, flag it to be interpreted, and retranslate the tracelet.
|
||||
NormalizedInstruction *ni;
|
||||
for (ni = t.m_instrStream.first; ni; ni = ni->next) {
|
||||
if (ni->source.offset() == fcg.bcOff) break;
|
||||
}
|
||||
if (ni && !ni->interp && supportedInterpOne(ni)) {
|
||||
ni->interp = true;
|
||||
transResult = Retry;
|
||||
TRACE(1, "HHIR: RETRY Translation %d: will interpOne BC instr %s "
|
||||
"after failing to code-gen \n\n",
|
||||
getCurrentTransID(), ni->toString().c_str());
|
||||
} else {
|
||||
throw fcg;
|
||||
|
||||
try {
|
||||
traceCodeGen();
|
||||
TRACE(1, "HHIR: SUCCEEDED to generate code for Translation %d\n\n\n",
|
||||
getCurrentTransID());
|
||||
return Success;
|
||||
} catch (JIT::FailedCodeGen& fcg) {
|
||||
// Code-gen failed. Search for the bytecode instruction that caused the
|
||||
// problem, flag it to be interpreted, and retranslate the tracelet.
|
||||
for (auto ni = t.m_instrStream.first; ni; ni = ni->next) {
|
||||
if (ni->source.offset() == fcg.bcOff) {
|
||||
if (!ni->interp && supportedInterpOne(ni)) {
|
||||
ni->interp = true;
|
||||
TRACE(1, "HHIR: RETRY Translation %d: will interpOne BC instr %s "
|
||||
"after failing to code-gen \n\n",
|
||||
getCurrentTransID(), ni->toString().c_str());
|
||||
return Retry;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (transResult == Success) {
|
||||
TRACE(1, "HHIR: SUCCEEDED to generate code for Translation %d\n\n\n",
|
||||
getCurrentTransID());
|
||||
}
|
||||
throw fcg;
|
||||
}
|
||||
} catch (JIT::FailedCodeGen& fcg) {
|
||||
transResult = Failure;
|
||||
TRACE(1, "HHIR: FAILED to generate code for Translation %d "
|
||||
"@ %s:%d (%s)\n", getCurrentTransID(),
|
||||
fcg.file, fcg.line, fcg.func);
|
||||
@@ -1783,11 +1772,8 @@ TranslatorX64::irTranslateTracelet(Tracelet& t,
|
||||
TRACE(1, "HHIR: FAILED to translate @ %s:%d (%s)\n",
|
||||
fcg.file, fcg.line, fcg.func);
|
||||
} catch (JIT::FailedIRGen& x) {
|
||||
transResult = Failure;
|
||||
TRACE(1, "HHIR: FAILED to translate @ %s:%d (%s)\n",
|
||||
x.file, x.line, x.func);
|
||||
} catch (TranslationFailedExc& tfe) {
|
||||
not_reached();
|
||||
} catch (const FailedAssertion& fa) {
|
||||
fa.print();
|
||||
StackTraceNoHeap::AddExtraLogging(
|
||||
@@ -1796,11 +1782,10 @@ TranslatorX64::irTranslateTracelet(Tracelet& t,
|
||||
fa.summary, m_hhbcTrans->trace()->toString()).str());
|
||||
abort();
|
||||
} catch (const std::exception& e) {
|
||||
transResult = Failure;
|
||||
FTRACE(1, "HHIR: FAILED with exception: {}\n", e.what());
|
||||
assert(0);
|
||||
}
|
||||
return transResult;
|
||||
return Failure;
|
||||
}
|
||||
|
||||
void Translator::traceStart(Offset bcStartOffset) {
|
||||
@@ -1828,7 +1813,7 @@ void Translator::traceEnd() {
|
||||
color(ANSI_COLOR_END));
|
||||
}
|
||||
|
||||
void TranslatorX64::traceCodeGen(vector<TransBCMapping>* bcMap) {
|
||||
void TranslatorX64::traceCodeGen() {
|
||||
using namespace JIT;
|
||||
|
||||
HPHP::JIT::IRTrace* trace = m_hhbcTrans->trace();
|
||||
@@ -1844,13 +1829,14 @@ void TranslatorX64::traceCodeGen(vector<TransBCMapping>* bcMap) {
|
||||
finishPass(" after optimizing ", kOptLevel);
|
||||
|
||||
auto* factory = m_irFactory.get();
|
||||
recordBCInstr(OpTraceletGuard, a, a.code.frontier);
|
||||
if (dumpIREnabled() || RuntimeOption::EvalJitCompareHHIR) {
|
||||
LifetimeInfo lifetime(factory);
|
||||
RegAllocInfo regs = allocRegsForTrace(trace, factory, &lifetime);
|
||||
finishPass(" after reg alloc ", kRegAllocLevel, ®s, &lifetime);
|
||||
assert(checkRegisters(trace, *factory, regs));
|
||||
AsmInfo ai(factory);
|
||||
genCodeForTrace(trace, a, astubs, factory, bcMap, this, regs,
|
||||
genCodeForTrace(trace, a, astubs, factory, &m_bcMap, this, regs,
|
||||
&lifetime, &ai);
|
||||
if (RuntimeOption::EvalJitCompareHHIR) {
|
||||
std::ostringstream out;
|
||||
@@ -1863,11 +1849,10 @@ void TranslatorX64::traceCodeGen(vector<TransBCMapping>* bcMap) {
|
||||
RegAllocInfo regs = allocRegsForTrace(trace, factory);
|
||||
finishPass(" after reg alloc ", kRegAllocLevel);
|
||||
assert(checkRegisters(trace, *factory, regs));
|
||||
genCodeForTrace(trace, a, astubs, factory, bcMap, this, regs);
|
||||
genCodeForTrace(trace, a, astubs, factory, &m_bcMap, this, regs);
|
||||
}
|
||||
|
||||
m_numHHIRTrans++;
|
||||
traceFree();
|
||||
}
|
||||
|
||||
void Translator::traceFree() {
|
||||
|
||||
@@ -232,10 +232,17 @@ static SSATmp* canonicalize(SSATmp* tmp) {
|
||||
// The dst of IncRef, Mov, StRef, and StRefNT has the same value
|
||||
// as the src.
|
||||
// We follow these instructions to canonicalize an SSATmp.
|
||||
if (opc != IncRef && opc != Mov && opc != StRef && opc != StRefNT) {
|
||||
return tmp;
|
||||
switch (opc) {
|
||||
case IncRef:
|
||||
case Mov:
|
||||
case StRef:
|
||||
case StRefNT:
|
||||
tmp = inst->src(0);
|
||||
break;
|
||||
|
||||
default:
|
||||
return tmp;
|
||||
}
|
||||
tmp = inst->src(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,6 +541,9 @@ int LinearScan::allocRegToTmp(SSATmp* ssaTmp, uint32_t index) {
|
||||
if (targetRegNo == reg::noreg) {
|
||||
targetRegNo = getJmpPreColor(orig, index, orig != ssaTmp);
|
||||
}
|
||||
if (targetRegNo == reg::noreg && ssaTmp->inst()->op() == AssertType) {
|
||||
targetRegNo = m_allocInfo[ssaTmp->inst()->src(0)].reg(index);
|
||||
}
|
||||
if (targetRegNo != reg::noreg) {
|
||||
reg = getReg(&m_regs[int(targetRegNo)]);
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@
|
||||
#include "hphp/runtime/vm/jit/region_selection.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/range/adaptors.hpp>
|
||||
|
||||
#include "folly/Memory.h"
|
||||
#include "folly/Conv.h"
|
||||
|
||||
#include "hphp/util/assertions.h"
|
||||
#include "hphp/runtime/base/runtime_option.h"
|
||||
#include "hphp/runtime/vm/jit/translator.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
@@ -40,6 +42,7 @@ enum class RegionMode {
|
||||
None,
|
||||
OneBC,
|
||||
Method,
|
||||
Tracelet,
|
||||
};
|
||||
|
||||
RegionMode regionMode() {
|
||||
@@ -47,6 +50,7 @@ RegionMode regionMode() {
|
||||
if (s == "") return RegionMode::None;
|
||||
if (s == "onebc") return RegionMode::OneBC;
|
||||
if (s == "method") return RegionMode::Method;
|
||||
if (s == "tracelet") return RegionMode::Tracelet;
|
||||
FTRACE(1, "unknown region mode {}: using none\n", s);
|
||||
if (debug) abort();
|
||||
return RegionMode::None;
|
||||
@@ -57,8 +61,9 @@ RegionMode regionMode() {
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void RegionDesc::Block::addPredicted(SrcKey sk, TypePred pred) {
|
||||
assert(pred.type.subtypeOf(Type::Gen | Type::Cls));
|
||||
auto const newElem = std::make_pair(sk, pred);
|
||||
auto const it = std::lower_bound(m_predTypes.begin(), m_predTypes.end(),
|
||||
auto const it = std::upper_bound(m_predTypes.begin(), m_predTypes.end(),
|
||||
newElem, typePredListCmp);
|
||||
m_predTypes.insert(it, newElem);
|
||||
checkInvariants();
|
||||
@@ -130,7 +135,87 @@ bool RegionDesc::Block::typePredListCmp(TypePredList::const_reference a,
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
RegionDescPtr selectRegion(const RegionContext& context) {
|
||||
namespace {
|
||||
RegionDescPtr createRegion(const Transl::Tracelet& tlet) {
|
||||
if (tlet.m_refDeps.size()) {
|
||||
// We don't support reffiness guards yet.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto region = smart::make_unique<RegionDesc>();
|
||||
SrcKey sk(tlet.m_sk);
|
||||
assert(sk == tlet.m_instrStream.first->source);
|
||||
auto unit = tlet.m_instrStream.first->unit();
|
||||
|
||||
// Boundaries for the current RegionDesc::Block, which is created when we
|
||||
// finish the Tracelet or start a new basic block.
|
||||
SrcKey startSk(sk);
|
||||
int numInstrs = 0;
|
||||
|
||||
for (auto ni = tlet.m_instrStream.first; ni; ni = ni->next) {
|
||||
assert(sk == ni->source);
|
||||
assert(ni->unit() == unit);
|
||||
|
||||
++numInstrs;
|
||||
if (ni->op() == OpJmp && ni->next) {
|
||||
// A Jmp that isn't the final instruction in a Tracelet means we traced
|
||||
// through a forward jump in analyze. Update sk to point to the next NI
|
||||
// in the stream.
|
||||
auto dest = ni->offset() + ni->imm[0].u_BA;
|
||||
assert(dest > sk.offset()); // We only trace for forward Jmps for now.
|
||||
sk.setOffset(dest);
|
||||
|
||||
// The Jmp terminates this block, so append it and start a new one.
|
||||
region->blocks.push_back(
|
||||
smart::make_unique<RegionDesc::Block>(
|
||||
tlet.m_func, startSk.offset(), numInstrs));
|
||||
numInstrs = 0;
|
||||
startSk = sk;
|
||||
} else {
|
||||
sk.advance(unit);
|
||||
}
|
||||
}
|
||||
|
||||
// Append the final block. This will be the only block for Tracelets that
|
||||
// didn't include a Jmp.
|
||||
region->blocks.push_back(
|
||||
smart::make_unique<RegionDesc::Block>(
|
||||
tlet.m_func, startSk.offset(), numInstrs));
|
||||
|
||||
// Add tracelet guards as predictions on the first instruction. Predictions
|
||||
// and known types from static analysis will be applied by
|
||||
// Translator::translateRegion.
|
||||
for (auto const& dep : tlet.m_dependencies) {
|
||||
if (dep.second->rtt.isVagueValue() ||
|
||||
dep.second->location.isThis()) continue;
|
||||
|
||||
typedef RegionDesc R;
|
||||
auto addPred = [&](const R::Location& loc) {
|
||||
auto type = Type::fromRuntimeType(dep.second->rtt);
|
||||
region->blocks.front()->addPredicted(tlet.m_sk, {loc, type});
|
||||
};
|
||||
|
||||
switch (dep.first.space) {
|
||||
case Transl::Location::Stack:
|
||||
addPred(R::Location::Stack{uint32_t(-dep.first.offset - 1)});
|
||||
break;
|
||||
|
||||
case Transl::Location::Local:
|
||||
addPred(R::Location::Local{uint32_t(dep.first.offset)});
|
||||
break;
|
||||
|
||||
default: not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
FTRACE(2, "Converted Tracelet:\n{}\nInto RegionDesc:\n{}\n",
|
||||
tlet.toString(), show(*region));
|
||||
return region;
|
||||
}
|
||||
}
|
||||
|
||||
RegionDescPtr selectRegion(const RegionContext& context,
|
||||
const Transl::Tracelet* t) {
|
||||
auto const mode = regionMode();
|
||||
|
||||
FTRACE(1,
|
||||
@@ -160,6 +245,7 @@ RegionDescPtr selectRegion(const RegionContext& context) {
|
||||
case RegionMode::None: return RegionDescPtr{nullptr};
|
||||
case RegionMode::OneBC: return regionOneBC(context);
|
||||
case RegionMode::Method: return regionMethod(context);
|
||||
case RegionMode::Tracelet: always_assert(t); return createRegion(*t);
|
||||
}
|
||||
not_reached();
|
||||
} catch (const std::exception& e) {
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
#include "hphp/runtime/vm/srckey.h"
|
||||
#include "hphp/runtime/vm/jit/type.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
namespace HPHP {
|
||||
namespace Transl {
|
||||
struct Tracelet;
|
||||
}
|
||||
namespace JIT {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -212,7 +216,7 @@ struct RegionContext::PreLiveAR {
|
||||
* analyzer. Eventually we'd like analyze to occur underneath this as
|
||||
* well.
|
||||
*/
|
||||
RegionDescPtr selectRegion(const RegionContext&);
|
||||
RegionDescPtr selectRegion(const RegionContext&, const Transl::Tracelet*);
|
||||
|
||||
/*
|
||||
* Debug stringification for various things.
|
||||
|
||||
@@ -268,7 +268,8 @@ bool RuntimeType::isRef() const {
|
||||
}
|
||||
|
||||
bool RuntimeType::isVagueValue() const {
|
||||
return m_kind == VALUE && outerType() == KindOfInvalid;
|
||||
return m_kind == VALUE && (outerType() == KindOfInvalid ||
|
||||
outerType() == KindOfAny);
|
||||
}
|
||||
|
||||
bool RuntimeType::isRefCounted() const {
|
||||
|
||||
@@ -329,7 +329,9 @@ SSATmp* Simplifier::simplify(IRInstruction* inst) {
|
||||
case DecRefNZOrBranch:
|
||||
case DecRefNZ: return simplifyDecRef(inst);
|
||||
case IncRef: return simplifyIncRef(inst);
|
||||
case CheckType: return simplifyCheckType(inst);
|
||||
case CheckType:
|
||||
case AssertType: return simplifyCheckType(inst);
|
||||
case CheckStk: return simplifyCheckStk(inst);
|
||||
case AssertNonNull:return simplifyAssertNonNull(inst);
|
||||
|
||||
case LdCls: return simplifyLdCls(inst);
|
||||
@@ -515,6 +517,20 @@ SSATmp* Simplifier::simplifyCheckType(IRInstruction* inst) {
|
||||
return src;
|
||||
}
|
||||
|
||||
SSATmp* Simplifier::simplifyCheckStk(IRInstruction* inst) {
|
||||
auto type = inst->typeParam();
|
||||
auto sp = inst->src(0);
|
||||
auto offset = inst->extra<CheckStk>()->offset;
|
||||
|
||||
auto stkVal = getStackValue(sp, offset);
|
||||
if (stkVal.knownType.equals(Type::None)) return nullptr;
|
||||
|
||||
if (stkVal.knownType.subtypeOf(type)) {
|
||||
return sp;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SSATmp* Simplifier::simplifyQueryJmp(IRInstruction* inst) {
|
||||
SSATmp* src1 = inst->src(0);
|
||||
SSATmp* src2 = inst->src(1);
|
||||
@@ -1666,21 +1682,21 @@ SSATmp* Simplifier::simplifyLdStackAddr(IRInstruction* inst) {
|
||||
}
|
||||
|
||||
SSATmp* Simplifier::simplifyDecRefStack(IRInstruction* inst) {
|
||||
if (inst->typeParam().notCounted()) {
|
||||
inst->convertToNop();
|
||||
return nullptr;
|
||||
}
|
||||
auto const info = getStackValue(inst->src(0),
|
||||
inst->extra<StackOffset>()->offset);
|
||||
if (info.value && !info.spansCall) {
|
||||
inst->convertToNop();
|
||||
return gen(DecRef, info.knownType, info.value);
|
||||
return gen(DecRef, info.value);
|
||||
}
|
||||
if (!info.knownType.equals(Type::None)) {
|
||||
inst->setTypeParam(
|
||||
Type::mostRefined(inst->typeParam(), info.knownType)
|
||||
);
|
||||
}
|
||||
if (inst->typeParam().notCounted()) {
|
||||
inst->convertToNop();
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ private:
|
||||
SSATmp* simplifyDecRef(IRInstruction* inst);
|
||||
SSATmp* simplifyIncRef(IRInstruction* inst);
|
||||
SSATmp* simplifyCheckType(IRInstruction* inst);
|
||||
SSATmp* simplifyCheckStk(IRInstruction* inst);
|
||||
SSATmp* simplifyLdThis(IRInstruction*);
|
||||
SSATmp* simplifyLdCls(IRInstruction* inst);
|
||||
SSATmp* simplifyLdClsPropAddr(IRInstruction*);
|
||||
|
||||
@@ -37,15 +37,15 @@ namespace HPHP {
|
||||
* Note that these do not assert anything about tl_regState; use
|
||||
* carefully.
|
||||
*/
|
||||
static inline Cell*& vmsp() { return (Cell*&)g_vmContext->m_stack.top(); }
|
||||
static inline Cell*& vmfp() { return (Cell*&)g_vmContext->m_fp; }
|
||||
static inline const uchar*& vmpc() { return g_vmContext->m_pc; }
|
||||
static inline ActRec*& vmFirstAR() { return g_vmContext->m_firstAR; }
|
||||
inline Cell*& vmsp() { return (Cell*&)g_vmContext->m_stack.top(); }
|
||||
inline Cell*& vmfp() { return (Cell*&)g_vmContext->m_fp; }
|
||||
inline const uchar*& vmpc() { return g_vmContext->m_pc; }
|
||||
inline ActRec*& vmFirstAR() { return g_vmContext->m_firstAR; }
|
||||
|
||||
static inline ActRec* curFrame() { return (ActRec*)vmfp(); }
|
||||
static inline const Func* curFunc() { return curFrame()->m_func; }
|
||||
static inline const Unit* curUnit() { return curFunc()->unit(); }
|
||||
static inline Class* curClass() {
|
||||
inline ActRec* curFrame() { return (ActRec*)vmfp(); }
|
||||
inline const Func* curFunc() { return curFrame()->m_func; }
|
||||
inline const Unit* curUnit() { return curFunc()->unit(); }
|
||||
inline Class* curClass() {
|
||||
const auto* func = curFunc();
|
||||
auto* cls = func->cls();
|
||||
if (func->isPseudoMain() || func->isTraitMethod() || cls == nullptr) {
|
||||
|
||||
@@ -777,6 +777,7 @@ static void populateLiveContext(JIT::RegionContext& ctx) {
|
||||
objOrCls
|
||||
}
|
||||
);
|
||||
FTRACE(2, "added prelive ActRec {}\n", show(ctx.preLiveARs.back()));
|
||||
|
||||
stackOff += kNumActRecCells;
|
||||
},
|
||||
@@ -784,6 +785,7 @@ static void populateLiveContext(JIT::RegionContext& ctx) {
|
||||
ctx.liveTypes.push_back(
|
||||
{ L::Stack{stackOff++}, JIT::liveTVType(tv) }
|
||||
);
|
||||
FTRACE(2, "added live type {}\n", show(ctx.liveTypes.back()));
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -3277,74 +3279,75 @@ TranslatorX64::translateWork(const TranslArgs& args) {
|
||||
TCA counterStart = 0;
|
||||
uint8_t counterLen = 0;
|
||||
SrcRec& srcRec = *getSrcRec(sk);
|
||||
vector<TransBCMapping> bcMapping;
|
||||
TransKind transKind = TransInterp;
|
||||
|
||||
if (!args.m_interp) {
|
||||
auto resetState = [&] {
|
||||
a.code.frontier = start;
|
||||
astubs.code.frontier = stubStart;
|
||||
m_pendingFixups.clear();
|
||||
m_bcMap.clear();
|
||||
srcRec.clearInProgressTailJumps();
|
||||
};
|
||||
|
||||
auto assertCleanState = [&] {
|
||||
assert(a.code.frontier == start);
|
||||
assert(astubs.code.frontier == stubStart);
|
||||
assert(m_pendingFixups.empty());
|
||||
assert(m_bcMap.empty());
|
||||
assert(srcRec.inProgressTailJumps().empty());
|
||||
};
|
||||
|
||||
if (!args.m_interp && !checkTranslationLimit(t.m_sk, srcRec)) {
|
||||
// Attempt to create a region at this SrcKey
|
||||
JIT::RegionContext rContext { curFunc(), args.m_sk.offset() };
|
||||
FTRACE(2, "populating live context for region\n");
|
||||
populateLiveContext(rContext);
|
||||
auto region = JIT::selectRegion(rContext);
|
||||
auto region = JIT::selectRegion(rContext, &t);
|
||||
|
||||
TranslateResult result = Retry;
|
||||
while (result == Retry) {
|
||||
assert(a.code.frontier == start);
|
||||
assert(astubs.code.frontier == stubStart);
|
||||
|
||||
traceStart(sk.offset());
|
||||
|
||||
// Try translating a region if we have one, then fall back to using the
|
||||
// Tracelet.
|
||||
if (region) {
|
||||
try {
|
||||
result = translateRegion(*region, &bcMapping);
|
||||
FTRACE(2, "translateRegion succeeded\n");
|
||||
assertCleanState();
|
||||
result = translateRegion(*region);
|
||||
FTRACE(2, "translateRegion finished with result {}\n",
|
||||
translateResultName(result));
|
||||
} catch (const std::exception& e) {
|
||||
FTRACE(1, "translateRegion failed with '{}'\n", e.what());
|
||||
traceEnd();
|
||||
result = Failure;
|
||||
}
|
||||
if (result == Failure) {
|
||||
traceFree();
|
||||
traceStart(sk.offset());
|
||||
resetState();
|
||||
}
|
||||
}
|
||||
if (!region || result == Failure) {
|
||||
FTRACE(1, "trying irTranslateTracelet\n");
|
||||
result = irTranslateTracelet(*tp, start, stubStart, &bcMapping);
|
||||
assertCleanState();
|
||||
result = irTranslateTracelet(*tp);
|
||||
}
|
||||
|
||||
if (result != Success) {
|
||||
// The whole translation failed; give up on this SrcKey. Since it is not
|
||||
// linked into srcDB yet, it is guaranteed not to be reachable.
|
||||
// Free IR resources for this trace, rollback the Translation cache
|
||||
// frontiers, and discard any pending fixups.
|
||||
traceFree();
|
||||
a.code.frontier = start;
|
||||
astubs.code.frontier = stubStart;
|
||||
m_pendingFixups.clear();
|
||||
// Reset additions to list of addresses which need to be patched
|
||||
srcRec.clearInProgressTailJumps();
|
||||
// Translation failed. Free resources for this trace, rollback the
|
||||
// translation cache frontiers, and discard any pending fixups.
|
||||
resetState();
|
||||
}
|
||||
traceFree();
|
||||
}
|
||||
|
||||
if (result == Success) {
|
||||
m_irAUsage += (a.code.frontier - start);
|
||||
m_irAstubsUsage += (astubs.code.frontier - stubStart);
|
||||
// Translation succeeded. Mark it as such.
|
||||
transKind = TransNormalIR;
|
||||
}
|
||||
}
|
||||
|
||||
if (transKind == TransInterp) {
|
||||
assert(m_pendingFixups.size() == 0);
|
||||
assert(srcRec.inProgressTailJumps().size() == 0);
|
||||
bcMapping.clear();
|
||||
|
||||
// The whole translation failed; give up on this BB. Since it is not
|
||||
// linked into srcDB yet, it is guaranteed not to be reachable.
|
||||
// Permanent reset; nothing is reachable yet.
|
||||
a.code.frontier = start;
|
||||
astubs.code.frontier = stubStart;
|
||||
bcMapping.clear();
|
||||
// Discard any pending fixups.
|
||||
m_pendingFixups.clear();
|
||||
srcRec.clearInProgressTailJumps();
|
||||
assertCleanState();
|
||||
TRACE(1,
|
||||
"emitting %d-instr interp request for failed translation\n",
|
||||
int(t.m_numOpcodes));
|
||||
@@ -3368,7 +3371,8 @@ TranslatorX64::translateWork(const TranslArgs& args) {
|
||||
a.code.frontier - start, stubStart,
|
||||
astubs.code.frontier - stubStart,
|
||||
counterStart, counterLen,
|
||||
bcMapping));
|
||||
m_bcMap));
|
||||
m_bcMap.clear();
|
||||
|
||||
recordGdbTranslation(sk, curFunc(), a, start,
|
||||
false, false);
|
||||
@@ -3465,8 +3469,6 @@ TranslatorX64::TranslatorX64()
|
||||
m_trampolineSize(0),
|
||||
m_defClsHelper(0),
|
||||
m_funcPrologueRedispatch(0),
|
||||
m_irAUsage(0),
|
||||
m_irAstubsUsage(0),
|
||||
m_numHHIRTrans(0),
|
||||
m_catchTraceMap(128)
|
||||
{
|
||||
@@ -3953,16 +3955,12 @@ std::string TranslatorX64::getUsage() {
|
||||
"tx64: %9zd bytes (%" PRId64 "%%) in ahot.code\n"
|
||||
"tx64: %9zd bytes (%" PRId64 "%%) in a.code\n"
|
||||
"tx64: %9zd bytes (%" PRId64 "%%) in astubs.code\n"
|
||||
"tx64: %9zd bytes (%" PRId64 "%%) in a.code from ir\n"
|
||||
"tx64: %9zd bytes (%" PRId64 "%%) in astubs.code from ir\n"
|
||||
"tx64: %9zd bytes (%" PRId64 "%%) in m_globalData\n"
|
||||
"tx64: %9zd bytes (%" PRId64 "%%) in targetCache\n"
|
||||
"tx64: %9zd bytes (%" PRId64 "%%) in persistentCache\n",
|
||||
aHotUsage, 100 * aHotUsage / ahot.code.size,
|
||||
aUsage, 100 * aUsage / a.code.size,
|
||||
stubsUsage, 100 * stubsUsage / astubs.code.size,
|
||||
m_irAUsage, 100 * m_irAUsage / a.code.size,
|
||||
m_irAstubsUsage, 100 * m_irAstubsUsage / astubs.code.size,
|
||||
dataUsage, 100 * dataUsage / m_globalData.size,
|
||||
tcUsage,
|
||||
400 * tcUsage / RuntimeOption::EvalJitTargetCacheSize / 3,
|
||||
|
||||
@@ -186,17 +186,16 @@ class TranslatorX64 : public Translator
|
||||
TCA m_freeLocalsHelpers[kNumFreeLocalsHelpers];
|
||||
|
||||
DataBlock m_globalData;
|
||||
size_t m_irAUsage;
|
||||
size_t m_irAstubsUsage;
|
||||
|
||||
// Data structures for HHIR-based translation
|
||||
uint64_t m_numHHIRTrans;
|
||||
|
||||
virtual void traceCodeGen(std::vector<TransBCMapping>*);
|
||||
virtual void traceCodeGen();
|
||||
|
||||
FixupMap m_fixupMap;
|
||||
UnwindInfoHandle m_unwindRegistrar;
|
||||
CatchTraceMap m_catchTraceMap;
|
||||
std::vector<TransBCMapping> m_bcMap;
|
||||
|
||||
private:
|
||||
int64_t m_createdTime;
|
||||
@@ -372,10 +371,7 @@ public:
|
||||
bool freeRequestStub(TCA stub);
|
||||
TCA getFreeStub();
|
||||
bool checkTranslationLimit(SrcKey, const SrcRec&) const;
|
||||
TranslateResult irTranslateTracelet(Tracelet& t,
|
||||
const TCA start,
|
||||
const TCA stubStart,
|
||||
vector<TransBCMapping>* bcMap);
|
||||
TranslateResult irTranslateTracelet(Tracelet& t);
|
||||
|
||||
void irAssertType(const Location& l, const RuntimeType& rtt);
|
||||
void checkType(Asm&, const Location& l, const RuntimeType& rtt,
|
||||
|
||||
@@ -128,46 +128,6 @@ void InstrStream::remove(NormalizedInstruction* ni) {
|
||||
ni->next = nullptr;
|
||||
}
|
||||
|
||||
void Tracelet::constructLiveRanges() {
|
||||
// Helper function.
|
||||
auto considerLoc = [this](DynLocation* dloc,
|
||||
const NormalizedInstruction* ni,
|
||||
bool output) {
|
||||
if (!dloc) return;
|
||||
Location loc = dloc->location;
|
||||
m_liveEnd[loc] = ni->sequenceNum;
|
||||
if (output) m_liveDirtyEnd[loc] = ni->sequenceNum;
|
||||
};
|
||||
// We assign each instruction a sequence number. We do this here, rather
|
||||
// than when creating the instruction, to allow splicing and removing
|
||||
// instructions
|
||||
int sequenceNum = 0;
|
||||
for (auto ni = m_instrStream.first; ni; ni = ni->next) {
|
||||
ni->sequenceNum = sequenceNum++;
|
||||
considerLoc(ni->outLocal, ni, true);
|
||||
considerLoc(ni->outStack3, ni, true);
|
||||
considerLoc(ni->outStack2, ni, true);
|
||||
considerLoc(ni->outStack, ni, true);
|
||||
for (auto inp : ni->inputs) {
|
||||
considerLoc(inp, ni, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Tracelet::isLiveAfterInstr(Location l,
|
||||
const NormalizedInstruction& ni) const {
|
||||
const auto end = m_liveEnd.find(l);
|
||||
assert(end != m_liveEnd.end());
|
||||
return ni.sequenceNum < end->second;
|
||||
}
|
||||
|
||||
bool Tracelet::isWrittenAfterInstr(Location l,
|
||||
const NormalizedInstruction& ni) const {
|
||||
const auto end = m_liveDirtyEnd.find(l);
|
||||
if (end == m_liveDirtyEnd.end()) return false;
|
||||
return ni.sequenceNum < end->second;
|
||||
}
|
||||
|
||||
NormalizedInstruction* Tracelet::newNormalizedInstruction() {
|
||||
NormalizedInstruction* ni = new NormalizedInstruction();
|
||||
m_instrs.push_back(ni);
|
||||
@@ -210,6 +170,12 @@ void Tracelet::print(std::ostream& out) const {
|
||||
}
|
||||
}
|
||||
|
||||
std::string Tracelet::toString() const {
|
||||
std::ostringstream out;
|
||||
print(out);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
void sktrace(SrcKey sk, const char *fmt, ...) {
|
||||
if (!Trace::enabled) {
|
||||
return;
|
||||
@@ -679,6 +645,8 @@ predictMVec(const NormalizedInstruction* ni) {
|
||||
static DataType
|
||||
predictOutputs(const Tracelet& t,
|
||||
NormalizedInstruction* ni) {
|
||||
if (!RuntimeOption::EvalJitTypePrediction) return KindOfInvalid;
|
||||
|
||||
if (RuntimeOption::EvalJitStressTypePredPercent &&
|
||||
RuntimeOption::EvalJitStressTypePredPercent > int(get_random() % 100)) {
|
||||
ni->outputPredicted = true;
|
||||
@@ -3277,6 +3245,7 @@ std::unique_ptr<Tracelet> Translator::analyze(SrcKey sk,
|
||||
std::unique_ptr<Tracelet> retval(new Tracelet());
|
||||
auto& t = *retval;
|
||||
t.m_sk = sk;
|
||||
t.m_func = curFunc();
|
||||
|
||||
DEBUG_ONLY const char* file = curUnit()->filepath()->data();
|
||||
DEBUG_ONLY const int lineNum = curUnit()->getLineNumber(t.m_sk.offset());
|
||||
@@ -3561,8 +3530,6 @@ breakBB:
|
||||
t.m_changes[*it] = tas.m_currentMap[*it];
|
||||
}
|
||||
|
||||
t.constructLiveRanges();
|
||||
|
||||
TRACE(1, "Tracelet done: stack delta %d\n", t.m_stackChange);
|
||||
return retval;
|
||||
}
|
||||
@@ -3637,6 +3604,15 @@ void Translator::populateImmediates(NormalizedInstruction& inst) {
|
||||
}
|
||||
}
|
||||
|
||||
const char* Translator::translateResultName(TranslateResult r) {
|
||||
static const char* const names[] = {
|
||||
"Failure",
|
||||
"Retry",
|
||||
"Success",
|
||||
};
|
||||
return names[r];
|
||||
}
|
||||
|
||||
/*
|
||||
* Similar to applyInputMetaData, but designed to be used during ir
|
||||
* generation. Reads and writes types of values using m_hhbcTrans. This will
|
||||
@@ -3683,8 +3659,8 @@ void Translator::readMetaData(Unit::MetaHandle& handle,
|
||||
|
||||
int arg = info.m_arg & Unit::MetaInfo::VectorArg ?
|
||||
base + (info.m_arg & ~Unit::MetaInfo::VectorArg) : info.m_arg;
|
||||
auto& input = *inst.inputs[arg];
|
||||
auto updateType = [&]{
|
||||
auto& input = *inst.inputs[arg];
|
||||
input.rtt = m_hhbcTrans->rttFromLocation(input.location);
|
||||
};
|
||||
|
||||
@@ -3699,20 +3675,27 @@ void Translator::readMetaData(Unit::MetaHandle& handle,
|
||||
inst.imm[0].u_IVA = info.m_data;
|
||||
break;
|
||||
case Unit::MetaInfo::DataTypePredicted: {
|
||||
m_hhbcTrans->checkTypeLocation(
|
||||
input.location, Type::fromDataType(DataType(info.m_data)),
|
||||
inst.source.offset());
|
||||
auto const& loc = inst.inputs[arg]->location;
|
||||
auto const t = Type::fromDataType(DataType(info.m_data));
|
||||
auto const offset = inst.source.offset();
|
||||
|
||||
// These 'predictions' mean the type is InitNull or the predicted type,
|
||||
// so we assert InitNull | t, then guard t. This allows certain
|
||||
// optimizations in the IR.
|
||||
m_hhbcTrans->assertTypeLocation(loc, Type::InitNull | t);
|
||||
m_hhbcTrans->checkTypeLocation(loc, t, offset);
|
||||
updateType();
|
||||
break;
|
||||
}
|
||||
case Unit::MetaInfo::DataTypeInferred: {
|
||||
m_hhbcTrans->assertTypeLocation(
|
||||
input.location, Type::fromDataType(DataType(info.m_data)));
|
||||
inst.inputs[arg]->location,
|
||||
Type::fromDataType(DataType(info.m_data)));
|
||||
updateType();
|
||||
break;
|
||||
}
|
||||
case Unit::MetaInfo::String: {
|
||||
m_hhbcTrans->assertString(input.location,
|
||||
m_hhbcTrans->assertString(inst.inputs[arg]->location,
|
||||
inst.unit()->lookupLitstrId(info.m_data));
|
||||
updateType();
|
||||
break;
|
||||
@@ -3782,8 +3765,7 @@ static Location toLocation(const RegionDesc::Location& loc) {
|
||||
}
|
||||
|
||||
Translator::TranslateResult
|
||||
Translator::translateRegion(const RegionDesc& region,
|
||||
vector<TransBCMapping>* bcMap) {
|
||||
Translator::translateRegion(const RegionDesc& region) {
|
||||
FTRACE(1, "translateRegion starting with:\n{}\n", show(region));
|
||||
assert(!region.blocks.empty());
|
||||
|
||||
@@ -3812,7 +3794,8 @@ Translator::translateRegion(const RegionDesc& region,
|
||||
inst.source = sk;
|
||||
inst.m_unit = block->unit();
|
||||
inst.preppedByRef = false;
|
||||
inst.breaksTracelet = false;
|
||||
inst.breaksTracelet =
|
||||
i == block->length() - 1 && block == region.blocks.back();
|
||||
inst.changesPC = opcodeChangesPC(inst.op());
|
||||
populateImmediates(inst);
|
||||
|
||||
@@ -3831,6 +3814,10 @@ Translator::translateRegion(const RegionDesc& region,
|
||||
getInputs(startSk, &inst, stackOff, inputInfos, [&](int i) {
|
||||
return m_hhbcTrans->traceBuilder()->getLocalType(i);
|
||||
});
|
||||
if (inputInfos.needsRefCheck) {
|
||||
// Not supported yet.
|
||||
return Failure;
|
||||
}
|
||||
for (auto& info : inputInfos) {
|
||||
if (info.loc.isStack()) info.loc.offset = -info.loc.offset;
|
||||
}
|
||||
@@ -3860,7 +3847,8 @@ Translator::translateRegion(const RegionDesc& region,
|
||||
}
|
||||
}
|
||||
|
||||
traceCodeGen(bcMap);
|
||||
traceEnd();
|
||||
traceCodeGen();
|
||||
return Success;
|
||||
}
|
||||
|
||||
|
||||
@@ -494,8 +494,8 @@ struct Tracelet : private boost::noncopyable {
|
||||
InstrStream m_instrStream;
|
||||
int m_stackChange;
|
||||
|
||||
// SrcKey for the start of the Tracelet. This might be different
|
||||
// from m_instrStream.first->source.
|
||||
// SrcKey for the start of the Tracelet. This will be the same as
|
||||
// m_instrStream.first->source.
|
||||
SrcKey m_sk;
|
||||
|
||||
// numOpcodes is the number of raw opcode instructions, before optimization.
|
||||
@@ -510,15 +510,8 @@ struct Tracelet : private boost::noncopyable {
|
||||
ActRecState m_arState;
|
||||
RefDeps m_refDeps;
|
||||
|
||||
// Live range suport.
|
||||
//
|
||||
// Maintain a per-location last-read and last-written map. We don't need
|
||||
// to remember the start of the live range, since we implicitly discover it
|
||||
// at translation time. The entries in these maps are the sequence number
|
||||
// of the instruction after which the location is no longer read/written.
|
||||
typedef hphp_hash_map<Location, int, Location> RangeMap;
|
||||
RangeMap m_liveEnd;
|
||||
RangeMap m_liveDirtyEnd;
|
||||
// The function this Tracelet was generated from.
|
||||
const Func* m_func;
|
||||
|
||||
/*
|
||||
* If we were unable to make sense of the instruction stream (e.g., it
|
||||
@@ -547,10 +540,6 @@ struct Tracelet : private boost::noncopyable {
|
||||
m_analysisFailed(false),
|
||||
m_inliningFailed(false){ }
|
||||
|
||||
void constructLiveRanges();
|
||||
bool isLiveAfterInstr(Location l, const NormalizedInstruction& i) const;
|
||||
bool isWrittenAfterInstr(Location l, const NormalizedInstruction& i) const;
|
||||
|
||||
NormalizedInstruction* newNormalizedInstruction();
|
||||
DynLocation* newDynLocation(Location l, DataType t);
|
||||
DynLocation* newDynLocation(Location l, RuntimeType t);
|
||||
@@ -560,6 +549,7 @@ struct Tracelet : private boost::noncopyable {
|
||||
* to make gdb happy. */
|
||||
void print() const;
|
||||
void print(std::ostream& out) const;
|
||||
std::string toString() const;
|
||||
|
||||
SrcKey nextSk() const;
|
||||
};
|
||||
@@ -906,9 +896,10 @@ protected:
|
||||
Retry,
|
||||
Success
|
||||
};
|
||||
static const char* translateResultName(TranslateResult r);
|
||||
void translateInstr(const NormalizedInstruction& i);
|
||||
void traceStart(Offset bcStartOffset);
|
||||
virtual void traceCodeGen(vector<TransBCMapping>* bcMap) = 0;
|
||||
virtual void traceCodeGen() = 0;
|
||||
void traceEnd();
|
||||
void traceFree();
|
||||
|
||||
@@ -934,8 +925,7 @@ protected:
|
||||
void requestResetHighLevelTranslator();
|
||||
|
||||
void populateImmediates(NormalizedInstruction&);
|
||||
TranslateResult translateRegion(const RegionDesc& region,
|
||||
vector<TransBCMapping>* bcMap);
|
||||
TranslateResult translateRegion(const RegionDesc& region);
|
||||
|
||||
TCA m_resumeHelper;
|
||||
TCA m_resumeHelperRet;
|
||||
|
||||
@@ -92,7 +92,7 @@ Type Type::fromDataType(DataType outerType,
|
||||
case KindOfUncounted : return Uncounted;
|
||||
case KindOfAny : return Gen;
|
||||
case KindOfRef: {
|
||||
if (innerType == KindOfInvalid) {
|
||||
if (innerType == KindOfInvalid || innerType == KindOfAny) {
|
||||
return BoxedCell;
|
||||
} else {
|
||||
return fromDataType(innerType).box();
|
||||
@@ -152,7 +152,7 @@ Type builtinReturn(const IRInstruction* inst) {
|
||||
return t;
|
||||
}
|
||||
if (t.isReferenceType() || t.equals(Type::BoxedCell)) {
|
||||
return (t | Type::Null);
|
||||
return t | Type::InitNull;
|
||||
}
|
||||
not_reached();
|
||||
}
|
||||
|
||||
@@ -73,6 +73,10 @@ struct SrcKey : private boost::totally_ordered<SrcKey> {
|
||||
return m_funcId;
|
||||
}
|
||||
|
||||
void setOffset(Offset o) {
|
||||
m_offset = o;
|
||||
}
|
||||
|
||||
int offset() const {
|
||||
return m_offset;
|
||||
}
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário