Handle parameter reffiness in translateRegion
This diff stops the region translator from punting on instructions that may be prepped by ref (FPass*) or Tracelets with reffiness guards. It increased coverage and exposed a few more bugs. The only significant one was that some Tracelets depend on type prediction for their length, so I had to hook it into translateRegion to avoid generating worse code (or punting).
Esse commit está contido em:
@@ -151,6 +151,11 @@ class map : public std::map<
|
||||
Allocator<std::pair<const Key,T>>
|
||||
> {};
|
||||
|
||||
template <class T, class Compare = std::less<T>>
|
||||
class set : public std::set<
|
||||
T, Compare, Allocator<T>
|
||||
> {};
|
||||
|
||||
template <class T>
|
||||
class deque : public std::deque<T,Allocator<T>> {};
|
||||
|
||||
|
||||
@@ -803,9 +803,29 @@ inline bool isFCallStar(Op opcode) {
|
||||
return opcode == OpFCall || opcode == OpFCallArray;
|
||||
}
|
||||
|
||||
inline bool isSwitch(Op op) {
|
||||
return op == OpSwitch || op == OpSSwitch;
|
||||
inline bool isFPassStar(Op opcode) {
|
||||
switch (opcode) {
|
||||
case OpFPassC:
|
||||
case OpFPassCW:
|
||||
case OpFPassCE:
|
||||
case OpFPassV:
|
||||
case OpFPassR:
|
||||
case OpFPassL:
|
||||
case OpFPassN:
|
||||
case OpFPassG:
|
||||
case OpFPassS:
|
||||
case OpFPassM:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isSwitch(Op op) {
|
||||
return op == Op::Switch || op == Op::SSwitch;
|
||||
}
|
||||
|
||||
inline bool isSwitch(Opcode op) {
|
||||
return isSwitch(toOp(op));
|
||||
}
|
||||
|
||||
@@ -1776,6 +1776,7 @@ void CodeGenerator::emitTypeTest(Type type, Loc1 typeSrc, Loc2 dataSrc,
|
||||
// nothing to check
|
||||
return;
|
||||
} else {
|
||||
assert(type.isKnownDataType());
|
||||
DataType dataType = type.toDataType();
|
||||
assert(dataType == KindOfRef ||
|
||||
(dataType >= KindOfUninit && dataType <= KindOfObject));
|
||||
|
||||
@@ -1543,7 +1543,8 @@ SSATmp* HhbcTranslator::staticTVCns(const TypedValue* tv) {
|
||||
}
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitClsCnsD(int32_t cnsNameId, int32_t clsNameId) {
|
||||
void HhbcTranslator::emitClsCnsD(int32_t cnsNameId, int32_t clsNameId,
|
||||
Type outPred) {
|
||||
auto const clsNameStr = lookupStringId(clsNameId);
|
||||
auto const cnsNameStr = lookupStringId(cnsNameId);
|
||||
auto const clsCnsName = ClsCnsName { clsNameStr, cnsNameStr };
|
||||
@@ -1578,8 +1579,9 @@ void HhbcTranslator::emitClsCnsD(int32_t cnsNameId, int32_t clsNameId) {
|
||||
}
|
||||
}
|
||||
|
||||
auto const cns = gen(LdClsCns, clsCnsName, Type::Uncounted);
|
||||
gen(CheckInit, sideExit, cns);
|
||||
auto guardType = Type::UncountedInit;
|
||||
if (outPred.strictSubtypeOf(guardType)) guardType = outPred;
|
||||
auto const cns = gen(LdClsCns, sideExit, clsCnsName, guardType);
|
||||
push(cns);
|
||||
}
|
||||
|
||||
|
||||
@@ -280,7 +280,7 @@ struct HhbcTranslator {
|
||||
const Func* callee);
|
||||
void emitFCallBuiltin(uint32_t numArgs, uint32_t numNonDefault,
|
||||
int32_t funcId);
|
||||
void emitClsCnsD(int32_t cnsNameStrId, int32_t clsNameStrId);
|
||||
void emitClsCnsD(int32_t cnsNameStrId, int32_t clsNameStrId, Type outPred);
|
||||
void emitClsCns(int32_t cnsNameStrId);
|
||||
void emitAKExists();
|
||||
void emitAGetC(const StringData* clsName);
|
||||
|
||||
@@ -393,7 +393,7 @@ Translator::translateDefCns(const NormalizedInstruction& i) {
|
||||
|
||||
void
|
||||
Translator::translateClsCnsD(const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(ClsCnsD, (i.imm[0].u_SA), (i.imm[1].u_SA));
|
||||
HHIR_EMIT(ClsCnsD, (i.imm[0].u_SA), (i.imm[1].u_SA), i.outPred);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -152,46 +152,6 @@ void optimizePredictions(IRTrace* const trace, IRFactory* const irFactory) {
|
||||
return true;
|
||||
};
|
||||
|
||||
/*
|
||||
* When we have a type prediction for a LdClsCns that is followed
|
||||
* immediately by CheckInit, we can merge both checks into the
|
||||
* LdClsCns and change it to exit to the same location the CheckInit
|
||||
* would've exited to.
|
||||
*/
|
||||
auto optLdClsCns = [&] (IRInstruction* checkType,
|
||||
IRInstruction* lastMarker) -> bool {
|
||||
auto const ldClsCns = checkType->src(0)->inst();
|
||||
if (ldClsCns->op() != LdClsCns) return false;
|
||||
if (ldClsCns->taken()) return false;
|
||||
|
||||
auto const mainBlock = ldClsCns->block();
|
||||
auto const nextIt = boost::next(mainBlock->iteratorTo(ldClsCns));
|
||||
if (nextIt == mainBlock->end()) return false;
|
||||
auto const checkInit = &*nextIt;
|
||||
if (checkInit->op() != CheckInit) return false;
|
||||
auto const exit = checkInit->taken();
|
||||
if (exit->numPreds() != 1) return false;
|
||||
|
||||
FTRACE(5, "candidate: {}\n", ldClsCns->toString());
|
||||
|
||||
// Change the LdClsCns to do the check on the more refined type,
|
||||
// exiting to the trace we would've exited to, and get rid of the
|
||||
// CheckInit.
|
||||
checkInit->setTaken(nullptr);
|
||||
mainBlock->erase(mainBlock->iteratorTo(checkInit));
|
||||
ldClsCns->setTaken(exit);
|
||||
ldClsCns->setTypeParam(checkType->typeParam());
|
||||
|
||||
// We don't need the checkType anymore.
|
||||
irFactory->replace(
|
||||
checkType,
|
||||
Mov,
|
||||
ldClsCns->dst()
|
||||
);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/*
|
||||
* The main loop for this pass.
|
||||
*
|
||||
@@ -213,8 +173,7 @@ void optimizePredictions(IRTrace* const trace, IRFactory* const irFactory) {
|
||||
if (inst.op() == CheckType &&
|
||||
inst.src(0)->type().equals(Type::Cell)) {
|
||||
assert(lastMarker);
|
||||
if (optLdMem(&inst, lastMarker) ||
|
||||
optLdClsCns(&inst, lastMarker)) {
|
||||
if (optLdMem(&inst, lastMarker)) {
|
||||
needsReflow = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "folly/Conv.h"
|
||||
|
||||
#include "hphp/util/assertions.h"
|
||||
#include "hphp/util/map_walker.h"
|
||||
#include "hphp/runtime/base/runtime_option.h"
|
||||
#include "hphp/runtime/vm/jit/translator.h"
|
||||
|
||||
@@ -62,10 +63,18 @@ 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::upper_bound(m_predTypes.begin(), m_predTypes.end(),
|
||||
newElem, typePredListCmp);
|
||||
m_predTypes.insert(it, newElem);
|
||||
m_typePreds.insert(std::make_pair(sk, pred));
|
||||
checkInvariants();
|
||||
}
|
||||
|
||||
void RegionDesc::Block::setParamByRef(SrcKey sk, ParamByRef byRef) {
|
||||
assert(m_byRefs.find(sk) == m_byRefs.end());
|
||||
m_byRefs.insert(std::make_pair(sk, byRef));
|
||||
checkInvariants();
|
||||
}
|
||||
|
||||
void RegionDesc::Block::addReffinessPred(SrcKey sk, const ReffinessPred& pred) {
|
||||
m_refPreds.insert(std::make_pair(sk, pred));
|
||||
checkInvariants();
|
||||
}
|
||||
|
||||
@@ -76,23 +85,27 @@ void RegionDesc::Block::addPredicted(SrcKey sk, TypePred pred) {
|
||||
* non-fallthrough instructions mid-block and no control flow (not
|
||||
* counting calls as control flow).
|
||||
*
|
||||
* 2. The type prediction list is ordered by increasing SrcKey.
|
||||
* 2. Each SrcKey in m_typePreds, m_byRefs, and m_refPreds is within the bounds
|
||||
* of the block.
|
||||
*
|
||||
* 3. Each prediction in the type prediction list is inside the range
|
||||
* of this block.
|
||||
* 3. Each local id referred to in the type prediction list is valid.
|
||||
*
|
||||
* 4. Each local id referred to in the type prediction list is valid.
|
||||
*
|
||||
* 5. (Unchecked) each stack offset in the type prediction list is
|
||||
* 4. (Unchecked) each stack offset in the type prediction list is
|
||||
* valid.
|
||||
*/
|
||||
void RegionDesc::Block::checkInvariants() const {
|
||||
smart::vector<SrcKey> keysInRange;
|
||||
keysInRange.reserve(length());
|
||||
keysInRange.push_back(start());
|
||||
if (!debug || length() == 0) return;
|
||||
|
||||
smart::set<SrcKey> keysInRange;
|
||||
auto firstKey = [&] { return *keysInRange.begin(); };
|
||||
auto lastKey = [&] {
|
||||
assert(!keysInRange.empty());
|
||||
return *--keysInRange.end();
|
||||
};
|
||||
keysInRange.insert(start());
|
||||
for (int i = 1; i < length(); ++i) {
|
||||
if (i != length() - 1) {
|
||||
auto const pc = unit()->at(keysInRange.back().offset());
|
||||
auto const pc = unit()->at(lastKey().offset());
|
||||
if (instrFlags(toOp(*pc)) & TF) {
|
||||
FTRACE(1, "Bad block: {}\n", show(*this));
|
||||
assert(!"Block may not contain non-fallthrough instruction unless "
|
||||
@@ -104,20 +117,20 @@ void RegionDesc::Block::checkInvariants() const {
|
||||
"they are last");
|
||||
}
|
||||
}
|
||||
keysInRange.push_back(keysInRange.back().advanced(unit()));
|
||||
keysInRange.insert(lastKey().advanced(unit()));
|
||||
}
|
||||
assert(keysInRange.size() == length());
|
||||
|
||||
assert(std::is_sorted(m_predTypes.begin(), m_predTypes.end(),
|
||||
typePredListCmp));
|
||||
assert(std::is_sorted(keysInRange.begin(), keysInRange.end()));
|
||||
|
||||
auto rangeIt = keysInRange.begin();
|
||||
for (auto& tpred : m_predTypes) {
|
||||
while (rangeIt != keysInRange.end() && *rangeIt < tpred.first) ++rangeIt;
|
||||
assert(rangeIt != keysInRange.end() && tpred.first == *rangeIt &&
|
||||
"RegionDesc::Block contained an out-of-range prediction");
|
||||
|
||||
auto rangeCheck = [&](const char* type, SrcKey sk) {
|
||||
if (!keysInRange.count(sk)) {
|
||||
std::cerr << folly::format("{} at {} outside range [{}, {}]\n",
|
||||
type, show(sk),
|
||||
show(firstKey()), show(lastKey()));
|
||||
assert(!"Region::Block contained out-of-range metadata");
|
||||
}
|
||||
};
|
||||
for (auto& tpred : m_typePreds) {
|
||||
rangeCheck("type prediction", tpred.first);
|
||||
auto& loc = tpred.second.location;
|
||||
switch (loc.tag()) {
|
||||
case Location::Tag::Local: assert(loc.localId() < m_func->numLocals());
|
||||
@@ -126,37 +139,45 @@ void RegionDesc::Block::checkInvariants() const {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RegionDesc::Block::typePredListCmp(TypePredList::const_reference a,
|
||||
TypePredList::const_reference b) {
|
||||
return a.first < b.first;
|
||||
for (auto& byRef : m_byRefs) {
|
||||
rangeCheck("parameter reference flag", byRef.first);
|
||||
}
|
||||
for (auto& refPred : m_refPreds) {
|
||||
rangeCheck("reffiness prediction", refPred.first);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
RegionDescPtr createRegion(const Transl::Tracelet& tlet) {
|
||||
if (tlet.m_refDeps.size()) {
|
||||
// We don't support reffiness guards yet.
|
||||
return nullptr;
|
||||
}
|
||||
typedef Transl::NormalizedInstruction NI;
|
||||
typedef RegionDesc::Block Block;
|
||||
|
||||
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;
|
||||
Block* curBlock;
|
||||
auto newBlock = [&] {
|
||||
region->blocks.push_back(
|
||||
smart::make_unique<Block>(tlet.m_func, sk.offset(), 0));
|
||||
curBlock = region->blocks.back().get();
|
||||
};
|
||||
newBlock();
|
||||
|
||||
for (auto ni = tlet.m_instrStream.first; ni; ni = ni->next) {
|
||||
assert(sk == ni->source);
|
||||
assert(ni->unit() == unit);
|
||||
|
||||
++numInstrs;
|
||||
curBlock->addInstruction();
|
||||
if (!ni->noOp && isFPassStar(ni->op())) {
|
||||
curBlock->setParamByRef(
|
||||
sk, ni->preppedByRef ? RegionDesc::ParamByRef::Yes
|
||||
: RegionDesc::ParamByRef::No);
|
||||
}
|
||||
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
|
||||
@@ -165,22 +186,14 @@ RegionDescPtr createRegion(const Transl::Tracelet& tlet) {
|
||||
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;
|
||||
// The Jmp terminates this block.
|
||||
newBlock();
|
||||
} 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));
|
||||
auto& frontBlock = *region->blocks.front();
|
||||
|
||||
// Add tracelet guards as predictions on the first instruction. Predictions
|
||||
// and known types from static analysis will be applied by
|
||||
@@ -192,7 +205,7 @@ RegionDescPtr createRegion(const Transl::Tracelet& tlet) {
|
||||
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});
|
||||
frontBlock.addPredicted(tlet.m_sk, {loc, type});
|
||||
};
|
||||
|
||||
switch (dep.first.space) {
|
||||
@@ -208,6 +221,14 @@ RegionDescPtr createRegion(const Transl::Tracelet& tlet) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add reffiness dependencies as predictions on the first instruction.
|
||||
for (auto const& dep : tlet.m_refDeps.m_arMap) {
|
||||
RegionDesc::ReffinessPred pred{dep.second.m_mask,
|
||||
dep.second.m_vals,
|
||||
dep.first};
|
||||
frontBlock.addReffinessPred(tlet.m_sk, pred);
|
||||
}
|
||||
|
||||
FTRACE(2, "Converted Tracelet:\n{}\nInto RegionDesc:\n{}\n",
|
||||
tlet.toString(), show(*region));
|
||||
return region;
|
||||
@@ -283,6 +304,23 @@ std::string show(RegionDesc::TypePred ta) {
|
||||
).str();
|
||||
}
|
||||
|
||||
std::string show(const RegionDesc::ReffinessPred& pred) {
|
||||
std::ostringstream out;
|
||||
out << "offset: " << pred.arSpOffset << " mask: ";
|
||||
for (auto const bit : pred.mask) out << (bit ? '1' : '0');
|
||||
out << " vals: ";
|
||||
for (auto const bit : pred.vals) out << (bit ? '1' : '0');
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string show(RegionDesc::ParamByRef byRef) {
|
||||
switch (byRef) {
|
||||
case RegionDesc::ParamByRef::Yes: return "by value";
|
||||
case RegionDesc::ParamByRef::No: return "by reference";
|
||||
}
|
||||
not_reached();
|
||||
}
|
||||
|
||||
std::string show(RegionContext::LiveType ta) {
|
||||
return folly::format(
|
||||
"{} :: {}",
|
||||
@@ -308,29 +346,34 @@ std::string show(const RegionDesc::Block& b) {
|
||||
&ret
|
||||
);
|
||||
|
||||
auto const& tpRange = b.typePreds();
|
||||
auto tpIter = begin(tpRange);
|
||||
auto typePreds = makeMapWalker(b.typePreds());
|
||||
auto byRefs = makeMapWalker(b.paramByRefs());
|
||||
auto refPreds = makeMapWalker(b.reffinessPreds());
|
||||
|
||||
auto skIter = b.start();
|
||||
for (int i = 0; i < b.length(); ++i) {
|
||||
while (tpIter != end(tpRange) && tpIter->first < skIter) {
|
||||
++tpIter;
|
||||
while (typePreds.hasNext(skIter)) {
|
||||
folly::toAppend(" predict: ", show(typePreds.next()), "\n", &ret);
|
||||
}
|
||||
while (tpIter != end(tpRange) && tpIter->first == skIter) {
|
||||
folly::toAppend(" predict: ", show(tpIter->second), "\n", &ret);
|
||||
++tpIter;
|
||||
while (refPreds.hasNext(skIter)) {
|
||||
folly::toAppend(" predict reffiness: ", show(refPreds.next()), "\n",
|
||||
&ret);
|
||||
}
|
||||
std::string byRef;
|
||||
if (byRefs.hasNext(skIter)) {
|
||||
byRef = folly::format(" (passed {})", show(byRefs.next())).str();
|
||||
}
|
||||
folly::toAppend(
|
||||
" ",
|
||||
skIter.offset(),
|
||||
" ",
|
||||
instrToString((Op*)b.unit()->at(skIter.offset()), b.unit()),
|
||||
'\n',
|
||||
byRef,
|
||||
"\n",
|
||||
&ret
|
||||
);
|
||||
skIter.advance(b.unit());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <boost/container/flat_map.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#include "folly/Format.h"
|
||||
@@ -32,6 +33,9 @@ struct Tracelet;
|
||||
}
|
||||
namespace JIT {
|
||||
|
||||
using boost::container::flat_map;
|
||||
using boost::container::flat_multimap;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
@@ -47,7 +51,12 @@ struct RegionDesc {
|
||||
struct Block;
|
||||
struct Location;
|
||||
struct TypePred;
|
||||
struct ReffinessPred;
|
||||
typedef smart::unique_ptr<Block>::type BlockPtr;
|
||||
enum class ParamByRef : uint8_t {
|
||||
Yes,
|
||||
No,
|
||||
};
|
||||
|
||||
smart::vector<BlockPtr> blocks;
|
||||
};
|
||||
@@ -107,12 +116,30 @@ struct RegionDesc::TypePred {
|
||||
Type type;
|
||||
};
|
||||
|
||||
/*
|
||||
* A prediction for the argument reffiness of the Func for a pre-live ActRec.
|
||||
*
|
||||
* mask is a bitmask of all 1's, with one bit for each parameter being passed.
|
||||
*
|
||||
* vals is a bitmask of the same length as mask, with a 1 representing a
|
||||
* parameter that will be passed by reference and a 0 for for value.
|
||||
*
|
||||
* arSpOffset is the offset from rVmSp to the ActRec.
|
||||
*/
|
||||
struct RegionDesc::ReffinessPred {
|
||||
std::vector<bool> mask;
|
||||
std::vector<bool> vals;
|
||||
int64_t arSpOffset;
|
||||
};
|
||||
|
||||
/*
|
||||
* A basic block in the region, with type predictions for conditions
|
||||
* at various execution points, including at entry to the block.
|
||||
*/
|
||||
class RegionDesc::Block {
|
||||
typedef smart::vector<std::pair<SrcKey,TypePred>> TypePredList;
|
||||
typedef flat_multimap<SrcKey, TypePred> TypePredMap;
|
||||
typedef flat_map<SrcKey, ParamByRef> ParamByRefMap;
|
||||
typedef flat_multimap<SrcKey, ReffinessPred> RefPredMap;
|
||||
|
||||
public:
|
||||
explicit Block(const Func* func, Offset start, int length)
|
||||
@@ -135,6 +162,14 @@ public:
|
||||
SrcKey start() const { return SrcKey { m_func, m_start }; }
|
||||
int length() const { return m_length; }
|
||||
|
||||
/*
|
||||
* Increase the length of the Block by 1.
|
||||
*/
|
||||
void addInstruction() {
|
||||
++m_length;
|
||||
checkInvariants();
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a predicted type to this block.
|
||||
*
|
||||
@@ -143,25 +178,36 @@ public:
|
||||
void addPredicted(SrcKey sk, TypePred);
|
||||
|
||||
/*
|
||||
* Obtain a range for the type predictions on this block. The
|
||||
* elements in the range are listed in ascending SrcKey order.
|
||||
*
|
||||
* The caller should assume it is a SinglePassReadableRange.
|
||||
* Add information about parameter reffiness to this block.
|
||||
*/
|
||||
boost::iterator_range<TypePredList::const_iterator> typePreds() const {
|
||||
return boost::make_iterator_range(m_predTypes.begin(), m_predTypes.end());
|
||||
}
|
||||
void setParamByRef(SrcKey sk, ParamByRef);
|
||||
|
||||
/*
|
||||
* Add a reffiness prediction about a pre-live ActRec.
|
||||
*/
|
||||
void addReffinessPred(SrcKey, const ReffinessPred&);
|
||||
|
||||
|
||||
/*
|
||||
* The following getters return references to the metadata maps holding the
|
||||
* information added using the add* and set* methods above. The best way to
|
||||
* iterate over the information is using a MapWalker, since they're all
|
||||
* backed by a sorted map.
|
||||
*/
|
||||
const TypePredMap& typePreds() const { return m_typePreds; }
|
||||
const ParamByRefMap& paramByRefs() const { return m_byRefs; }
|
||||
const RefPredMap& reffinessPreds() const { return m_refPreds; }
|
||||
|
||||
private:
|
||||
void checkInvariants() const;
|
||||
static bool typePredListCmp(TypePredList::const_reference,
|
||||
TypePredList::const_reference);
|
||||
|
||||
private:
|
||||
const Func* m_func;
|
||||
const Offset m_start;
|
||||
const int m_length;
|
||||
TypePredList m_predTypes;
|
||||
int m_length;
|
||||
TypePredMap m_typePreds;
|
||||
ParamByRefMap m_byRefs;
|
||||
RefPredMap m_refPreds;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -211,7 +257,7 @@ struct RegionContext::PreLiveAR {
|
||||
*
|
||||
* May return nullptr.
|
||||
*
|
||||
* For now this is hooked up in TranslatorX64::createTranslation, and
|
||||
* For now this is hooked up in TranslatorX64::translateWork, and
|
||||
* returning nullptr causes it to use the current level 0 tracelet
|
||||
* analyzer. Eventually we'd like analyze to occur underneath this as
|
||||
* well.
|
||||
@@ -223,6 +269,8 @@ RegionDescPtr selectRegion(const RegionContext&, const Transl::Tracelet*);
|
||||
*/
|
||||
std::string show(RegionDesc::Location);
|
||||
std::string show(RegionDesc::TypePred);
|
||||
std::string show(const RegionDesc::ReffinessPred&);
|
||||
std::string show(RegionDesc::ParamByRef);
|
||||
std::string show(RegionContext::LiveType);
|
||||
std::string show(RegionContext::PreLiveAR);
|
||||
std::string show(const RegionDesc::Block&);
|
||||
|
||||
@@ -3272,6 +3272,8 @@ TranslatorX64::translateWork(const TranslArgs& args) {
|
||||
if (!region || result == Failure) {
|
||||
FTRACE(1, "trying irTranslateTracelet\n");
|
||||
assertCleanState();
|
||||
static const bool requireRegion = getenv("HHVM_REQUIRE_REGION");
|
||||
assert(!requireRegion);
|
||||
result = irTranslateTracelet(*tp);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
#include "hphp/util/trace.h"
|
||||
#include "hphp/util/biased_coin.h"
|
||||
|
||||
#include "hphp/util/map_walker.h"
|
||||
#include "hphp/runtime/base/file_repository.h"
|
||||
#include "hphp/runtime/base/runtime_option.h"
|
||||
#include "hphp/runtime/base/stats.h"
|
||||
@@ -646,13 +646,12 @@ predictMVec(const NormalizedInstruction* ni) {
|
||||
* Provide a best guess for the output type of this instruction.
|
||||
*/
|
||||
static DataType
|
||||
predictOutputs(const Tracelet& t,
|
||||
NormalizedInstruction* ni) {
|
||||
predictOutputs(SrcKey startSk,
|
||||
const NormalizedInstruction* ni) {
|
||||
if (!RuntimeOption::EvalJitTypePrediction) return KindOfInvalid;
|
||||
|
||||
if (RuntimeOption::EvalJitStressTypePredPercent &&
|
||||
RuntimeOption::EvalJitStressTypePredPercent > int(get_random() % 100)) {
|
||||
ni->outputPredicted = true;
|
||||
int dt;
|
||||
while (true) {
|
||||
dt = get_random() % (KindOfRef + 1);
|
||||
@@ -698,7 +697,6 @@ predictOutputs(const Tracelet& t,
|
||||
if (cls) {
|
||||
DataType dt = cls->clsCnsType(cnsName);
|
||||
if (dt != KindOfUninit) {
|
||||
ni->outputPredicted = true;
|
||||
TRACE(1, "clscnsd: %s:%s prediction type %d\n",
|
||||
cne.first->data(), cnsName->data(), dt);
|
||||
return dt;
|
||||
@@ -712,7 +710,7 @@ predictOutputs(const Tracelet& t,
|
||||
// them combinatorially explode if they bring in precondtions that vary a
|
||||
// lot. Get more conservative as evidence mounts that this is a
|
||||
// polymorphic tracelet.
|
||||
if (tx64->numTranslations(t.m_sk) >= kTooPolyPred) return KindOfInvalid;
|
||||
if (tx64->numTranslations(startSk) >= kTooPolyPred) return KindOfInvalid;
|
||||
if (ni->op() == OpCGetS) {
|
||||
const StringData* propName = ni->inputs[1]->rtt.valueStringOrNull();
|
||||
if (propName) {
|
||||
@@ -736,7 +734,6 @@ predictOutputs(const Tracelet& t,
|
||||
}
|
||||
}
|
||||
if (pred.second >= kAccept) {
|
||||
ni->outputPredicted = true;
|
||||
TRACE(1, "accepting prediction of type %d\n", pred.first);
|
||||
assert(pred.first != KindOfUninit);
|
||||
return pred.first;
|
||||
@@ -805,7 +802,11 @@ getDynLocType(const vector<DynLocation*>& inputs,
|
||||
CS(OutArray, KindOfArray);
|
||||
CS(OutObject, KindOfObject);
|
||||
#undef CS
|
||||
case OutPred: return RuntimeType(predictOutputs(t, ni));
|
||||
case OutPred: {
|
||||
auto dt = predictOutputs(t.m_sk, ni);
|
||||
if (dt != KindOfInvalid) ni->outputPredicted = true;
|
||||
return RuntimeType(dt);
|
||||
}
|
||||
|
||||
case OutClassRef: {
|
||||
Op op = Op(ni->op());
|
||||
@@ -3281,7 +3282,6 @@ std::unique_ptr<Tracelet> Translator::analyze(SrcKey sk,
|
||||
ni->funcd = (t.m_arState.getCurrentState() == ActRecState::State::KNOWN) ?
|
||||
t.m_arState.getCurrentFunc() : nullptr;
|
||||
ni->m_unit = unit;
|
||||
ni->preppedByRef = false;
|
||||
ni->breaksTracelet = false;
|
||||
ni->changesPC = opcodeChangesPC(ni->op());
|
||||
ni->fuseBranch = false;
|
||||
@@ -3319,8 +3319,7 @@ std::unique_ptr<Tracelet> Translator::analyze(SrcKey sk,
|
||||
// relative to the sp at the beginning of the tracelet, so we adjust
|
||||
// by subtracting ni->stackOff
|
||||
int entryArDelta = instrSpToArDelta((Op*)ni->pc()) - ni->stackOffset;
|
||||
ni->preppedByRef = t.m_arState.getReffiness(argNum,
|
||||
entryArDelta,
|
||||
ni->preppedByRef = t.m_arState.getReffiness(argNum, entryArDelta,
|
||||
&t.m_refDeps);
|
||||
SKTRACE(1, sk, "passing arg%d by %s\n", argNum,
|
||||
ni->preppedByRef ? "reference" : "value");
|
||||
@@ -3440,6 +3439,11 @@ std::unique_ptr<Tracelet> Translator::analyze(SrcKey sk,
|
||||
}
|
||||
}
|
||||
|
||||
if (ni->outputPredicted) {
|
||||
assert(ni->outStack);
|
||||
ni->outPred = Type::fromDynLocation(ni->outStack);
|
||||
}
|
||||
|
||||
t.m_stackChange += getStackDelta(*ni);
|
||||
|
||||
t.m_instrStream.append(ni);
|
||||
@@ -3777,21 +3781,23 @@ static Location toLocation(const RegionDesc::Location& loc) {
|
||||
|
||||
Translator::TranslateResult
|
||||
Translator::translateRegion(const RegionDesc& region) {
|
||||
typedef JIT::RegionDesc::Block Block;
|
||||
FTRACE(1, "translateRegion starting with:\n{}\n", show(region));
|
||||
assert(!region.blocks.empty());
|
||||
const SrcKey startSk = region.blocks.front()->start();
|
||||
|
||||
for (auto const& block : region.blocks) {
|
||||
const SrcKey startSk = block->start();
|
||||
SrcKey sk = startSk;
|
||||
auto predIt = block->typePreds().begin();
|
||||
auto const predEnd = block->typePreds().end();
|
||||
SrcKey sk = block->start();
|
||||
auto typePreds = makeMapWalker(block->typePreds());
|
||||
auto byRefs = makeMapWalker(block->paramByRefs());
|
||||
auto refPreds = makeMapWalker(block->reffinessPreds());
|
||||
|
||||
for (unsigned i = 0; i < block->length(); ++i, sk.advance(block->unit())) {
|
||||
// Emit prediction guards. If this is the first instruction in the
|
||||
// region the guards will go to a retranslate request. Otherwise, they'll
|
||||
// go to a side exit.
|
||||
for (; predIt != predEnd && predIt->first == sk; ++predIt) {
|
||||
auto const& pred = predIt->second;
|
||||
while (typePreds.hasNext(sk)) {
|
||||
auto const& pred = typePreds.next();
|
||||
if (block == region.blocks.front() && i == 0) {
|
||||
m_hhbcTrans->guardTypeLocation(toLocation(pred.location), pred.type);
|
||||
} else {
|
||||
@@ -3800,11 +3806,18 @@ Translator::translateRegion(const RegionDesc& region) {
|
||||
}
|
||||
}
|
||||
|
||||
// Emit reffiness guards. For now, we only support reffiness guards at
|
||||
// the beginning of the region.
|
||||
while (refPreds.hasNext(sk)) {
|
||||
assert(sk == startSk);
|
||||
auto const& pred = refPreds.next();
|
||||
m_hhbcTrans->guardRefs(pred.arSpOffset, pred.mask, pred.vals);
|
||||
}
|
||||
|
||||
// Create and initialize the instruction.
|
||||
NormalizedInstruction inst;
|
||||
inst.source = sk;
|
||||
inst.m_unit = block->unit();
|
||||
inst.preppedByRef = false;
|
||||
inst.breaksTracelet =
|
||||
i == block->length() - 1 && block == region.blocks.back();
|
||||
inst.changesPC = opcodeChangesPC(inst.op());
|
||||
@@ -3825,10 +3838,6 @@ 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;
|
||||
}
|
||||
@@ -3851,11 +3860,44 @@ Translator::translateRegion(const RegionDesc& region) {
|
||||
// Apply the remaining metadata. This may change the types of some of
|
||||
// inst's inputs.
|
||||
readMetaData(metaHand, inst);
|
||||
if (!inst.noOp && inputInfos.needsRefCheck) {
|
||||
assert(byRefs.hasNext(sk));
|
||||
auto byRef = byRefs.next();
|
||||
inst.preppedByRef = byRef == RegionDesc::ParamByRef::Yes;
|
||||
}
|
||||
|
||||
// Check for a type prediction. Put it in the NormalizedInstruction so
|
||||
// the emit* method can use it if needed.
|
||||
auto const& iInfo = instrInfo[inst.op()];
|
||||
auto doPrediction = iInfo.type == OutPred && !inst.breaksTracelet;
|
||||
if (doPrediction) {
|
||||
// All OutPred ops have a single stack output for now.
|
||||
assert(iInfo.out == Stack1);
|
||||
auto dt = predictOutputs(startSk, &inst);
|
||||
if (dt != KindOfInvalid) {
|
||||
inst.outPred = Type::fromDataType(dt);
|
||||
} else {
|
||||
doPrediction = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Emit IR for the body of the instruction.
|
||||
Util::Nuller<NormalizedInstruction> niNuller(&m_curNI);
|
||||
m_curNI = &inst;
|
||||
translateInstr(inst);
|
||||
|
||||
// Check the prediction. If the predicted type is less specific than what
|
||||
// is currently on the eval stack, checkTypeLocation won't emit any code.
|
||||
if (doPrediction) {
|
||||
m_hhbcTrans->checkTypeLocation(Location(Location::Stack, 0),
|
||||
inst.outPred,
|
||||
sk.advanced(block->unit()).offset());
|
||||
}
|
||||
}
|
||||
|
||||
assert(!typePreds.hasNext());
|
||||
assert(!byRefs.hasNext());
|
||||
assert(!refPreds.hasNext());
|
||||
}
|
||||
|
||||
traceEnd();
|
||||
|
||||
@@ -202,6 +202,7 @@ class NormalizedInstruction {
|
||||
// MIterNextK
|
||||
DynLocation* outStack2; // Used for CGetL2
|
||||
DynLocation* outStack3; // Used for CGetL3
|
||||
Type outPred;
|
||||
vector<Location> deadLocs; // locations that die at the end of this
|
||||
// instruction
|
||||
ArgUnion imm[4];
|
||||
@@ -238,7 +239,7 @@ class NormalizedInstruction {
|
||||
bool breaksTracelet:1;
|
||||
bool changesPC:1;
|
||||
bool fuseBranch:1;
|
||||
bool preppedByRef:1; // For FPass*; indicates parameter reffiness
|
||||
bool preppedByRef:1;
|
||||
bool outputPredicted:1;
|
||||
bool outputPredictionStatic:1;
|
||||
bool ignoreInnerType:1;
|
||||
@@ -300,6 +301,7 @@ class NormalizedInstruction {
|
||||
, outLocal2(nullptr)
|
||||
, outStack2(nullptr)
|
||||
, outStack3(nullptr)
|
||||
, outPred(Type::Gen)
|
||||
, checkedInputs(0)
|
||||
, ignoreInnerType(false)
|
||||
, guardedThis(false)
|
||||
|
||||
@@ -82,7 +82,7 @@ using Transl::RuntimeType;
|
||||
|
||||
// Gen, Counted, PtrToGen, and PtrToCounted are here instead of
|
||||
// IRT_PHP_UNIONS because boxing them (e.g., BoxedGen, PtrToBoxedGen)
|
||||
// would be nonsense types.
|
||||
// would yield nonsense types.
|
||||
#define IRT_SPECIAL \
|
||||
IRT(Bottom, 0) \
|
||||
IRT(Top, 0xffffffffffffffffULL) \
|
||||
|
||||
@@ -125,6 +125,10 @@ struct SrcKey::Hasher {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
inline std::string show(SrcKey sk) {
|
||||
return folly::format("{}@{}", sk.getFuncId(), sk.offset()).str();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_HPHP_UTIL_MAPWALKER_H_
|
||||
#define incl_HPHP_UTIL_MAPWALKER_H_
|
||||
|
||||
namespace HPHP {
|
||||
|
||||
/*
|
||||
* MapWalker is designed to iterate over an ordered map and an ordered list of
|
||||
* keys in parallel. Once created with a reference to a map, hasNext(key) will
|
||||
* return true iff the next item in the map matches the given key. If it
|
||||
* returns true, next() will return a reference to that element and advance its
|
||||
* internal iterator. hasNext() can be used to check if the internal iterator
|
||||
* is at the end of the map.
|
||||
*/
|
||||
template<typename Map>
|
||||
struct MapWalker {
|
||||
typedef typename Map::key_type Key;
|
||||
typedef typename Map::mapped_type Value;
|
||||
typedef typename Map::const_iterator ConstIter;
|
||||
|
||||
explicit MapWalker(const Map& m)
|
||||
: m_map(m)
|
||||
, m_iter(m.begin())
|
||||
{}
|
||||
|
||||
bool hasNext() const {
|
||||
return m_iter != m_map.end();
|
||||
}
|
||||
|
||||
bool hasNext(const Key& key) const {
|
||||
assert(m_iter == m_map.end() || key <= m_iter->first);
|
||||
return m_iter != m_map.end() && m_iter->first == key;
|
||||
}
|
||||
|
||||
const Value& next() {
|
||||
assert(m_iter != m_map.end());
|
||||
auto const& val = m_iter->second;
|
||||
++m_iter;
|
||||
return val;
|
||||
}
|
||||
|
||||
private:
|
||||
const Map& m_map;
|
||||
ConstIter m_iter;
|
||||
};
|
||||
|
||||
template<typename Map>
|
||||
MapWalker<Map> makeMapWalker(const Map& m) {
|
||||
return MapWalker<Map>(m);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Referência em uma Nova Issue
Bloquear um usuário