From d99c6b0c58a8f576f2cef0a5f2bcd4eeadf751cc Mon Sep 17 00:00:00 2001 From: bsimmers Date: Tue, 18 Jun 2013 12:13:23 -0700 Subject: [PATCH] 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). --- hphp/runtime/base/memory/smart_containers.h | 5 + hphp/runtime/vm/hhbc.h | 24 ++- hphp/runtime/vm/jit/codegen.cpp | 1 + hphp/runtime/vm/jit/hhbctranslator.cpp | 8 +- hphp/runtime/vm/jit/hhbctranslator.h | 2 +- hphp/runtime/vm/jit/irtranslator.cpp | 2 +- hphp/runtime/vm/jit/predictionopts.cpp | 43 +----- hphp/runtime/vm/jit/region_selection.cpp | 161 +++++++++++++------- hphp/runtime/vm/jit/region_selection.h | 74 +++++++-- hphp/runtime/vm/jit/translator-x64.cpp | 2 + hphp/runtime/vm/jit/translator.cpp | 86 ++++++++--- hphp/runtime/vm/jit/translator.h | 4 +- hphp/runtime/vm/jit/type.h | 2 +- hphp/runtime/vm/srckey.h | 4 + hphp/util/map_walker.h | 69 +++++++++ 15 files changed, 342 insertions(+), 145 deletions(-) create mode 100644 hphp/util/map_walker.h diff --git a/hphp/runtime/base/memory/smart_containers.h b/hphp/runtime/base/memory/smart_containers.h index 6c137997f..4b5e0ed31 100644 --- a/hphp/runtime/base/memory/smart_containers.h +++ b/hphp/runtime/base/memory/smart_containers.h @@ -151,6 +151,11 @@ class map : public std::map< Allocator> > {}; +template > +class set : public std::set< + T, Compare, Allocator +> {}; + template class deque : public std::deque> {}; diff --git a/hphp/runtime/vm/hhbc.h b/hphp/runtime/vm/hhbc.h index 2c1efed0b..1f5885735 100644 --- a/hphp/runtime/vm/hhbc.h +++ b/hphp/runtime/vm/hhbc.h @@ -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)); } diff --git a/hphp/runtime/vm/jit/codegen.cpp b/hphp/runtime/vm/jit/codegen.cpp index f2eaa9f8a..721f9c0e3 100644 --- a/hphp/runtime/vm/jit/codegen.cpp +++ b/hphp/runtime/vm/jit/codegen.cpp @@ -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)); diff --git a/hphp/runtime/vm/jit/hhbctranslator.cpp b/hphp/runtime/vm/jit/hhbctranslator.cpp index 4f8dc3755..00005158a 100644 --- a/hphp/runtime/vm/jit/hhbctranslator.cpp +++ b/hphp/runtime/vm/jit/hhbctranslator.cpp @@ -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); } diff --git a/hphp/runtime/vm/jit/hhbctranslator.h b/hphp/runtime/vm/jit/hhbctranslator.h index cf04e1090..b6d60e1b2 100644 --- a/hphp/runtime/vm/jit/hhbctranslator.h +++ b/hphp/runtime/vm/jit/hhbctranslator.h @@ -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); diff --git a/hphp/runtime/vm/jit/irtranslator.cpp b/hphp/runtime/vm/jit/irtranslator.cpp index 560f9012f..c49d26542 100644 --- a/hphp/runtime/vm/jit/irtranslator.cpp +++ b/hphp/runtime/vm/jit/irtranslator.cpp @@ -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 diff --git a/hphp/runtime/vm/jit/predictionopts.cpp b/hphp/runtime/vm/jit/predictionopts.cpp index 6f0d86904..f1b950103 100644 --- a/hphp/runtime/vm/jit/predictionopts.cpp +++ b/hphp/runtime/vm/jit/predictionopts.cpp @@ -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; } diff --git a/hphp/runtime/vm/jit/region_selection.cpp b/hphp/runtime/vm/jit/region_selection.cpp index 413834cfe..23736d88b 100644 --- a/hphp/runtime/vm/jit/region_selection.cpp +++ b/hphp/runtime/vm/jit/region_selection.cpp @@ -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 keysInRange; - keysInRange.reserve(length()); - keysInRange.push_back(start()); + if (!debug || length() == 0) return; + + smart::set 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(); 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(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( - 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( - 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; } diff --git a/hphp/runtime/vm/jit/region_selection.h b/hphp/runtime/vm/jit/region_selection.h index 4e1d2f20a..7a914ceb5 100644 --- a/hphp/runtime/vm/jit/region_selection.h +++ b/hphp/runtime/vm/jit/region_selection.h @@ -18,6 +18,7 @@ #include #include +#include #include #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::type BlockPtr; + enum class ParamByRef : uint8_t { + Yes, + No, + }; smart::vector 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 mask; + std::vector 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> TypePredList; + typedef flat_multimap TypePredMap; + typedef flat_map ParamByRefMap; + typedef flat_multimap 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 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&); diff --git a/hphp/runtime/vm/jit/translator-x64.cpp b/hphp/runtime/vm/jit/translator-x64.cpp index 196fc25ab..4fc946946 100644 --- a/hphp/runtime/vm/jit/translator-x64.cpp +++ b/hphp/runtime/vm/jit/translator-x64.cpp @@ -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); } diff --git a/hphp/runtime/vm/jit/translator.cpp b/hphp/runtime/vm/jit/translator.cpp index 40a73df76..0d8b83d41 100644 --- a/hphp/runtime/vm/jit/translator.cpp +++ b/hphp/runtime/vm/jit/translator.cpp @@ -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& 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 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 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 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 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(); diff --git a/hphp/runtime/vm/jit/translator.h b/hphp/runtime/vm/jit/translator.h index a22d5d505..6a9252982 100644 --- a/hphp/runtime/vm/jit/translator.h +++ b/hphp/runtime/vm/jit/translator.h @@ -202,6 +202,7 @@ class NormalizedInstruction { // MIterNextK DynLocation* outStack2; // Used for CGetL2 DynLocation* outStack3; // Used for CGetL3 + Type outPred; vector 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) diff --git a/hphp/runtime/vm/jit/type.h b/hphp/runtime/vm/jit/type.h index 7e2ec1fa7..b3d2b31d9 100644 --- a/hphp/runtime/vm/jit/type.h +++ b/hphp/runtime/vm/jit/type.h @@ -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) \ diff --git a/hphp/runtime/vm/srckey.h b/hphp/runtime/vm/srckey.h index a6da615fd..d7c599c82 100644 --- a/hphp/runtime/vm/srckey.h +++ b/hphp/runtime/vm/srckey.h @@ -125,6 +125,10 @@ struct SrcKey::Hasher { ////////////////////////////////////////////////////////////////////// +inline std::string show(SrcKey sk) { + return folly::format("{}@{}", sk.getFuncId(), sk.offset()).str(); +} + } #endif diff --git a/hphp/util/map_walker.h b/hphp/util/map_walker.h new file mode 100644 index 000000000..d39855680 --- /dev/null +++ b/hphp/util/map_walker.h @@ -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 +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 +MapWalker makeMapWalker(const Map& m) { + return MapWalker(m); +} + +} + +#endif