From c561feb82c2e864bf95f17ed0cff7ddda1350a8e Mon Sep 17 00:00:00 2001 From: Brett Simmers Date: Thu, 11 Dec 2014 10:19:15 -0800 Subject: [PATCH] Delete interpreter type-profiling code Summary: This isn't doing anything now that we PGO everything, and it added a fair bit of complexity to some jit functions. This doesn't touch the "runtime type profiler" which is something unrelated to type prediction in the jit, and the non-type warmup profiling for Func hotness and InstanceBits is still around. Reviewed By: @alexmalyshev, @jdelong Differential Revision: D1732733 Signature: t1:1732733:1418259277:acffb558bef3ccc0d1b1ca24bee8918b5e5a9197 --- hphp/runtime/base/runtime-option.h | 3 - hphp/runtime/vm/bytecode.cpp | 20 +- hphp/runtime/vm/jit/extra-data.h | 4 +- hphp/runtime/vm/jit/irgen-cns.cpp | 4 +- hphp/runtime/vm/jit/irgen-inlining.cpp | 13 +- hphp/runtime/vm/jit/irgen-interpone.cpp | 10 +- hphp/runtime/vm/jit/irgen.h | 3 +- hphp/runtime/vm/jit/normalized-instruction.cpp | 2 - hphp/runtime/vm/jit/normalized-instruction.h | 2 - hphp/runtime/vm/jit/region-tracelet.cpp | 14 +- hphp/runtime/vm/jit/translator.cpp | 378 +-------------------- hphp/runtime/vm/jit/translator.h | 8 - hphp/runtime/vm/type-profile.cpp | 258 +------------- hphp/runtime/vm/type-profile.h | 20 -- hphp/test/slow/program_functions/ini_binding.php | 1 - .../slow/program_functions/ini_binding.php.expect | 1 - .../slow/program_functions/ini_binding.php.ini | 1 - 17 files changed, 34 insertions(+), 708 deletions(-) diff --git a/hphp/runtime/base/runtime-option.h b/hphp/runtime/base/runtime-option.h index ed3c30a0b49..2ffaf5ef3c0 100644 --- a/hphp/runtime/base/runtime-option.h +++ b/hphp/runtime/base/runtime-option.h @@ -437,9 +437,6 @@ public: F(uint32_t, JitMaxTranslations, 12) \ F(uint64_t, JitGlobalTranslationLimit, -1) \ F(uint32_t, JitMaxRegionInstrs, 1000) \ - F(string, JitProfilePath, std::string("")) \ - F(bool, JitTypePrediction, true) \ - F(int32_t, JitStressTypePredPercent, 0) \ F(uint32_t, JitProfileInterpRequests, kDefaultProfileInterpRequests) \ F(bool, JitProfileWarmupRequests, false) \ F(uint32_t, NumSingleJitRequests, nsjrDefault()) \ diff --git a/hphp/runtime/vm/bytecode.cpp b/hphp/runtime/vm/bytecode.cpp index 5caaf2a5fd8..e6eb7e70070 100644 --- a/hphp/runtime/vm/bytecode.cpp +++ b/hphp/runtime/vm/bytecode.cpp @@ -4404,13 +4404,7 @@ OPTBLD_INLINE void ExecutionContext::ret(IOP_ARGS) { } if (isProfileRequest()) { - auto const f = vmfp()->func(); - profileIncrementFuncCounter(f); - if (!(f->isPseudoMain() || f->isClosureBody() || f->isMagic() || - Func::isSpecial(f->name()))) { - recordType(TypeProfileKey(TypeProfileKey::MethodName, f->name()), - retval.m_type); - } + profileIncrementFuncCounter(vmfp()->func()); } // Type profile return value. @@ -4650,27 +4644,15 @@ OPTBLD_INLINE void ExecutionContext::iopCGetG(IOP_ARGS) { OPTBLD_INLINE void ExecutionContext::iopCGetS(IOP_ARGS) { StringData* name; GETS(false); - if (isProfileRequest() && name && name->isStatic()) { - recordType(TypeProfileKey(TypeProfileKey::StaticPropName, name), - vmStack().top()->m_type); - } } OPTBLD_INLINE void ExecutionContext::iopCGetM(IOP_ARGS) { - PC oldPC = pc; NEXT(); DECLARE_GETHELPER_ARGS getHelper(GETHELPER_ARGS); if (tvRet->m_type == KindOfRef) { tvUnbox(tvRet); } - assert(hasImmVector(*reinterpret_cast(oldPC))); - const ImmVector& immVec = ImmVector::createFromStream(oldPC + 1); - StringData* name; - MemberCode mc; - if (immVec.decodeLastMember(vmfp()->unit(), name, mc)) { - recordType(TypeProfileKey(mc, name), vmStack().top()->m_type); - } } static inline void vgetl_body(TypedValue* fr, TypedValue* to) { diff --git a/hphp/runtime/vm/jit/extra-data.h b/hphp/runtime/vm/jit/extra-data.h index 3e2e127ede2..1208d5b3531 100644 --- a/hphp/runtime/vm/jit/extra-data.h +++ b/hphp/runtime/vm/jit/extra-data.h @@ -409,15 +409,13 @@ struct PropByteOffset : IRExtraData { struct DefInlineFPData : IRExtraData { std::string show() const { return folly::to( - target->fullName()->data(), "(),", retBCOff, ',', retSPOff, - retTypePred < Type::Gen ? (',' + retTypePred.toString()) : "" + target->fullName()->data(), "(),", retBCOff, ',', retSPOff ); } const Func* target; Offset retBCOff; Offset retSPOff; - Type retTypePred; }; struct CallArrayData : IRExtraData { diff --git a/hphp/runtime/vm/jit/irgen-cns.cpp b/hphp/runtime/vm/jit/irgen-cns.cpp index 1c428d213e1..1f6b6ae8835 100644 --- a/hphp/runtime/vm/jit/irgen-cns.cpp +++ b/hphp/runtime/vm/jit/irgen-cns.cpp @@ -126,7 +126,6 @@ void emitCnsU(HTS& env, void emitClsCnsD(HTS& env, const StringData* cnsNameStr, const StringData* clsNameStr) { - auto const outPred = env.currentNormalizedInstruction->outPred; // TODO: rm auto const clsCnsName = ClsCnsName { clsNameStr, cnsNameStr }; /* @@ -156,8 +155,7 @@ void emitClsCnsD(HTS& env, RDSHandleData { link.handle() }, Type::Cell.ptr(Ptr::ClsCns) ); - auto const guardType = outPred < Type::UncountedInit ? outPred - : Type::UncountedInit; + auto const guardType = Type::UncountedInit; env.irb->ifThen( [&] (Block* taken) { diff --git a/hphp/runtime/vm/jit/irgen-inlining.cpp b/hphp/runtime/vm/jit/irgen-inlining.cpp index bfe3cb021c1..1c73be9b7c9 100644 --- a/hphp/runtime/vm/jit/irgen-inlining.cpp +++ b/hphp/runtime/vm/jit/irgen-inlining.cpp @@ -68,8 +68,7 @@ bool isInlining(const HTS& env) { return env.bcStateStack.size() > 1; } void beginInlining(HTS& env, unsigned numParams, const Func* target, - Offset returnBcOffset, - Type retTypePred) { + Offset returnBcOffset) { assert(!env.fpiStack.empty() && "Inlining does not support calls with the FPush* in a different Tracelet"); assert(returnBcOffset >= 0 && "returnBcOffset before beginning of caller"); @@ -91,7 +90,6 @@ void beginInlining(HTS& env, data.target = target; data.retBCOff = returnBcOffset; data.retSPOff = prevSPOff; - data.retTypePred = retTypePred; // Push state and update the marker before emitting any instructions so // they're all given markers in the callee. @@ -189,17 +187,8 @@ void endInlinedCommon(HTS& env) { void retFromInlined(HTS& env, Type type) { auto const retVal = pop(env, type, DataTypeGeneric); - // Before we leave the inlined frame, grab a type prediction from - // our DefInlineFP. - auto const retPred = fp(env)->inst()->extra()->retTypePred; endInlinedCommon(env); push(env, retVal); - if (retPred < retVal->type()) { // TODO: this if statement shouldn't be here, - // because check type resolves to the - // intersection of the two types - // If we had a predicted output type that's useful, check that here. - checkTypeStack(env, 0, retPred, curSrcKey(env).advanced().offset()); - } } ////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/vm/jit/irgen-interpone.cpp b/hphp/runtime/vm/jit/irgen-interpone.cpp index 0528f0a2153..99f24cede6b 100644 --- a/hphp/runtime/vm/jit/irgen-interpone.cpp +++ b/hphp/runtime/vm/jit/irgen-interpone.cpp @@ -110,8 +110,6 @@ folly::Optional interpOutputType(HTS& env, return Type::BoxedInitCell; }; - if (inst.outputPredicted) return Type::Gen; - auto outFlag = getInstrInfo(inst.op()).type; if (outFlag == OutFInputL) { outFlag = inst.preppedByRef ? OutVInputL : OutCInputL; @@ -139,12 +137,6 @@ folly::Optional interpOutputType(HTS& env, case OutFDesc: return folly::none; case OutUnknown: return Type::Gen; - case OutPred: - checkTypeType = inst.outPred; - // Returning inst.outPred from this function would turn the CheckStk - // after the InterpOne into a nop. - return Type::Gen; - case OutCns: return Type::Cell; case OutVUnknown: return Type::BoxedInitCell; @@ -240,7 +232,7 @@ interpOutputLocals(HTS& env, auto locType = env.irb->localType(localInputId(inst), DataTypeSpecific); assert(locType < Type::Gen || curFunc(env)->isPseudoMain()); - auto stackType = inst.outputPredicted ? inst.outPred : pushedType.value(); + auto stackType = pushedType.value(); setImmLocType(0, handleBoxiness(locType, stackType)); break; } diff --git a/hphp/runtime/vm/jit/irgen.h b/hphp/runtime/vm/jit/irgen.h index 42cb785cc87..129a2703092 100644 --- a/hphp/runtime/vm/jit/irgen.h +++ b/hphp/runtime/vm/jit/irgen.h @@ -144,8 +144,7 @@ void endBlock(HTS&, Offset next, bool nextIsMerge); void beginInlining(HTS&, unsigned numParams, const Func* target, - Offset returnBcOffset, - Type retTypePred); + Offset returnBcOffset); /* * Returns whether the HTS is currently inlining or not. diff --git a/hphp/runtime/vm/jit/normalized-instruction.cpp b/hphp/runtime/vm/jit/normalized-instruction.cpp index f7773b29f40..6829a82db95 100644 --- a/hphp/runtime/vm/jit/normalized-instruction.cpp +++ b/hphp/runtime/vm/jit/normalized-instruction.cpp @@ -52,13 +52,11 @@ NormalizedInstruction::NormalizedInstruction(SrcKey sk, const Unit* u) : source(sk) , funcd(nullptr) , m_unit(u) - , outPred(Type::Gen) , immVec() , nextOffset(kInvalidOffset) , endsRegion(false) , nextIsMerge(false) , preppedByRef(false) - , outputPredicted(false) , ignoreInnerType(false) , interp(false) { diff --git a/hphp/runtime/vm/jit/normalized-instruction.h b/hphp/runtime/vm/jit/normalized-instruction.h index 1a062f1bf82..6da0cbdf01a 100644 --- a/hphp/runtime/vm/jit/normalized-instruction.h +++ b/hphp/runtime/vm/jit/normalized-instruction.h @@ -44,7 +44,6 @@ struct NormalizedInstruction { const Unit* m_unit; std::vector inputs; - Type outPred; ArgUnion imm[4]; ImmVector immVec; // vector immediate; will have !isValid() if the // instruction has no vector immediate @@ -57,7 +56,6 @@ struct NormalizedInstruction { bool endsRegion:1; bool nextIsMerge:1; bool preppedByRef:1; - bool outputPredicted:1; bool ignoreInnerType:1; /* diff --git a/hphp/runtime/vm/jit/region-tracelet.cpp b/hphp/runtime/vm/jit/region-tracelet.cpp index f3d2d478a75..1294a7b9e8f 100644 --- a/hphp/runtime/vm/jit/region-tracelet.cpp +++ b/hphp/runtime/vm/jit/region-tracelet.cpp @@ -157,8 +157,6 @@ RegionDescPtr RegionFormer::go() { m_curBlock->setKnownFunc(m_sk, m_inst.funcd); m_inst.interp = m_interp.count(m_sk); - auto const doPrediction = - m_profiling ? false : outputIsPredicted(m_inst); uint32_t calleeInstrSize; if (tryInline(calleeInstrSize)) { @@ -176,8 +174,7 @@ RegionDescPtr RegionFormer::go() { m_arStates.back().pop(); m_arStates.emplace_back(); m_curBlock->setInlinedCallee(callee); - irgen::beginInlining(m_hts, m_inst.imm[0].u_IVA, callee, returnFuncOff, - doPrediction ? m_inst.outPred : Type::Gen); + irgen::beginInlining(m_hts, m_inst.imm[0].u_IVA, callee, returnFuncOff); m_sk = irgen::curSrcKey(m_hts); m_blockFinished = true; @@ -221,15 +218,6 @@ RegionDescPtr RegionFormer::go() { } if (isFCallStar(m_inst.op())) m_arStates.back().pop(); - - // Since the current instruction is over, advance sk before emitting the - // prediction (if any). - if (doPrediction && - // TODO(#5710339): would be nice to remove the following check - irgen::publicTopType(m_hts, 0).maybe(m_inst.outPred)) { - irgen::prepareForNextHHBC(m_hts, &m_inst, m_sk.offset(), false); - irgen::checkTypeStack(m_hts, 0, m_inst.outPred, m_sk.offset()); - } } // If we failed while trying to inline, trigger retry without inlining. diff --git a/hphp/runtime/vm/jit/translator.cpp b/hphp/runtime/vm/jit/translator.cpp index eab1e6286ee..6f479ff3670 100644 --- a/hphp/runtime/vm/jit/translator.cpp +++ b/hphp/runtime/vm/jit/translator.cpp @@ -82,16 +82,6 @@ int locPhysicalOffset(Location l, const Func* f) { return -((l.offset + 1) * iterInflator + localsToSkip); } -static uint32_t m_w = 1; /* must not be zero */ -static uint32_t m_z = 1; /* must not be zero */ - -static uint32_t get_random() -{ - m_z = 36969 * (m_z & 65535) + (m_z >> 16); - m_w = 18000 * (m_w & 65535) + (m_w >> 16); - return (m_z << 16) + m_w; /* 32-bit result */ -} - PropInfo getPropertyOffset(const NormalizedInstruction& ni, const Class* ctx, const Class*& baseClass, const MInstrInfo& mii, @@ -152,292 +142,6 @@ PropInfo getFinalPropertyOffset(const NormalizedInstruction& ni, return getPropertyOffset(ni, ctx, cls, mii, mInd, iInd); } - -/////////////////////////////////////////////////////////////////////////////// -// Type predictions. - -namespace { - -/* - * Pair of (predicted type, confidence). - * - * A folly::none prediction means mixed/unknown. - */ -using TypePred = std::pair; - -MaybeDataType predictionForRepoAuthType(RepoAuthType repoTy) { - using T = RepoAuthType::Tag; - switch (repoTy.tag()) { - case T::OptBool: return KindOfBoolean; - case T::OptInt: return KindOfInt64; - case T::OptDbl: return KindOfDouble; - case T::OptRes: return KindOfResource; - - case T::OptSArr: - case T::OptArr: - return KindOfArray; - - case T::OptStr: - case T::OptSStr: - return KindOfString; - - case T::OptSubObj: - case T::OptExactObj: - case T::OptObj: - return KindOfObject; - - case T::Bool: - case T::Uninit: - case T::InitNull: - case T::Int: - case T::Dbl: - case T::Res: - case T::Str: - case T::Arr: - case T::Obj: - case T::Null: - case T::SStr: - case T::SArr: - case T::SubObj: - case T::ExactObj: - case T::Cell: - case T::Ref: - case T::InitUnc: - case T::Unc: - case T::InitCell: - case T::InitGen: - case T::Gen: - return folly::none; - } - not_reached(); -} - -TypePred predictMVec(const NormalizedInstruction* ni) { - auto info = getFinalPropertyOffset(*ni, - ni->func()->cls(), - getMInstrInfo(ni->mInstrOp())); - if (info.offset != -1) { - auto const predTy = predictionForRepoAuthType(info.repoAuthType); - if (predTy) { - FTRACE(1, "prediction for CGetM prop: {}, hphpc\n", - static_cast(*predTy)); - return std::make_pair(predTy, 1.0); - } - // If the RepoAuthType converts to an exact data type, there's no - // point in having a prediction because we know its type with 100% - // accuracy. Disable it in that case here. - if (convertToDataType(info.repoAuthType)) { - return std::make_pair(folly::none, 0.0); - } - } - - auto& immVec = ni->immVec; - StringData* name; - MemberCode mc; - if (immVec.decodeLastMember(ni->m_unit, name, mc)) { - auto pred = predictType(TypeProfileKey(mc, name)); - TRACE(1, "prediction for CGetM %s named %s: %d, %f\n", - mc == MET ? "elt" : "prop", - name->data(), - pred.first ? *pred.first : -1, - pred.second); - return pred; - } - - return std::make_pair(folly::none, 0.0); -} - -/* - * Provide a best guess for the output type of this instruction. - */ -MaybeDataType predictOutputs(const NormalizedInstruction* ni) { - if (!RuntimeOption::EvalJitTypePrediction) return folly::none; - - if (RuntimeOption::EvalJitStressTypePredPercent && - RuntimeOption::EvalJitStressTypePredPercent > int(get_random() % 100)) { - int dt; - while (true) { - dt = getDataTypeValue(get_random() % (kMaxDataType + 1)); - switch (dt) { - case KindOfNull: - case KindOfBoolean: - case KindOfInt64: - case KindOfDouble: - case KindOfString: - case KindOfArray: - case KindOfObject: - case KindOfResource: - break; - - // KindOfRef and KindOfUninit can't happen for lots of predicted types. - case KindOfUninit: - case KindOfStaticString: - case KindOfRef: - continue; - - case KindOfClass: - not_reached(); - } - break; - } - return DataType(dt); - } - - if (ni->op() == OpCns || - ni->op() == OpCnsE || - ni->op() == OpCnsU) { - StringData* sd = ni->m_unit->lookupLitstrId(ni->imm[0].u_SA); - auto const tv = Unit::lookupCns(sd); - if (tv) return tv->m_type; - } - - if (ni->op() == OpMod) { - // x % 0 returns boolean false, so we don't know for certain, but it's - // probably an int. - return KindOfInt64; - } - - if (ni->op() == OpPow) { - // int ** int => int, unless result > 2 ** 52, then it's a double - // anything ** double => double - // double ** anything => double - // anything ** anything => int - auto lhs = ni->inputs[0]->rtt; - auto rhs = ni->inputs[1]->rtt; - - if (lhs <= Type::Int && rhs <= Type::Int) { - // Best guess, since overflowing isn't common - return KindOfInt64; - } - - if (lhs <= Type::Dbl || rhs <= Type::Dbl) { - return KindOfDouble; - } - - return KindOfInt64; - } - - if (ni->op() == OpSqrt) { - // sqrt returns a double, unless you pass something nasty to it. - return KindOfDouble; - } - - if (ni->op() == OpDiv) { - // Integers can produce integers if there's no residue, but $i / $j in - // general produces a double. $i / 0 produces boolean false, so we have - // actually check the result. - return KindOfDouble; - } - - if (ni->op() == OpAbs) { - if (ni->inputs[0]->rtt <= Type::Dbl) { - return KindOfDouble; - } - - // some types can't be converted to integers and will return false here - if (ni->inputs[0]->rtt <= Type::Arr) { - return KindOfBoolean; - } - - // If the type is not numeric we need to convert it to a numeric type, - // a string can be converted to an Int64 or a Double but most other types - // will end up being integral. - return KindOfInt64; - } - - if (ni->op() == OpClsCnsD) { - const NamedEntityPair& cne = - ni->unit()->lookupNamedEntityPairId(ni->imm[1].u_SA); - StringData* cnsName = ni->m_unit->lookupLitstrId(ni->imm[0].u_SA); - Class* cls = cne.second->getCachedClass(); - if (cls) { - DataType dt = cls->clsCnsType(cnsName); - if (dt != KindOfUninit) { - TRACE(1, "clscnsd: %s:%s prediction type %d\n", - cne.first->data(), cnsName->data(), dt); - return dt; - } - } - } - - if (ni->op() == OpSetM) { - /* - * SetM pushes null for certain rare combinations of input types, a string - * if the base was a string, or (most commonly) its first stack input. We - * mark the output as predicted here and do a very rough approximation of - * what really happens; most of the time the prediction will be a noop - * since MInstrTranslator side exits in all uncommon cases. - */ - - auto inType = ni->inputs[0]->rtt; - auto const inDt = inType.isKnownDataType() - ? MaybeDataType(inType.toDataType()) - : folly::none; - // If the base is a string, the output is probably a string. Unless the - // member code is MW, then we're either going to fatal or promote the - // string to an array. - Type baseType; - switch (ni->immVec.locationCode()) { - case LGL: case LGC: - case LNL: case LNC: - case LSL: case LSC: - baseType = Type::Gen; - break; - - default: - baseType = ni->inputs[1]->rtt; - } - if (baseType <= Type::Str && ni->immVecM.size() == 1) { - return ni->immVecM[0] == MW ? inDt : KindOfString; - } - - // Otherwise, it's probably the input type. - return inDt; - } - - auto const op = ni->op(); - static const double kAccept = 1.0; - - std::pair pred = std::make_pair(folly::none, 0.0); - - if (op == OpCGetS) { - auto nameType = ni->inputs[1]->rtt; - if (nameType.isConst(Type::Str)) { - auto propName = nameType.strVal(); - pred = predictType(TypeProfileKey(TypeProfileKey::StaticPropName, - propName)); - TRACE(1, "prediction for static fields named %s: %d, %f\n", - propName->data(), - pred.first ? *pred.first : -1, - pred.second); - } - } else if (op == OpCGetM) { - pred = predictMVec(ni); - } - if (pred.second < kAccept) { - const StringData* const invName - = ni->op() == Op::FCallD - ? ni->m_unit->lookupLitstrId(ni->imm[2].u_SA) - : nullptr; - if (invName) { - pred = predictType(TypeProfileKey(TypeProfileKey::MethodName, invName)); - FTRACE(1, "prediction for methods named {}: {}, {:.2}\n", - invName->data(), - pred.first ? *pred.first : -1, - pred.second); - } - } - if (pred.second >= kAccept) { - FTRACE(1, "accepting prediction of type {}\n", - pred.first ? *pred.first : -1); - assert(!pred.first || *pred.first != KindOfUninit); - return pred.first; - } - return folly::none; -} - -} - /////////////////////////////////////////////////////////////////////////////// /* @@ -499,7 +203,7 @@ static const struct { { OpCnsE, {None, Stack1, OutCns, 1 }}, { OpCnsU, {None, Stack1, OutCns, 1 }}, { OpClsCns, {Stack1, Stack1, OutUnknown, 0 }}, - { OpClsCnsD, {None, Stack1, OutPred, 1 }}, + { OpClsCnsD, {None, Stack1, OutUnknown, 1 }}, { OpFile, {None, Stack1, OutString, 1 }}, { OpDir, {None, Stack1, OutString, 1 }}, { OpNameA, {Stack1, Stack1, OutString, 0 }}, @@ -510,7 +214,7 @@ static const struct { { OpConcat, {StackTop2, Stack1, OutString, -1 }}, { OpConcatN, {StackN, Stack1, OutString, 0 }}, /* Arithmetic ops */ - { OpAbs, {Stack1, Stack1, OutPred, 0 }}, + { OpAbs, {Stack1, Stack1, OutUnknown, 0 }}, { OpAdd, {StackTop2, Stack1, OutArith, -1 }}, { OpSub, {StackTop2, Stack1, OutArith, -1 }}, { OpMul, {StackTop2, Stack1, OutArith, -1 }}, @@ -519,10 +223,10 @@ static const struct { { OpSubO, {StackTop2, Stack1, OutArithO, -1 }}, { OpMulO, {StackTop2, Stack1, OutArithO, -1 }}, /* Div and mod might return boolean false. Sigh. */ - { OpDiv, {StackTop2, Stack1, OutPred, -1 }}, - { OpMod, {StackTop2, Stack1, OutPred, -1 }}, - { OpPow, {StackTop2, Stack1, OutPred, -1 }}, - { OpSqrt, {Stack1, Stack1, OutPred, 0 }}, + { OpDiv, {StackTop2, Stack1, OutUnknown, -1 }}, + { OpMod, {StackTop2, Stack1, OutUnknown, -1 }}, + { OpPow, {StackTop2, Stack1, OutUnknown, -1 }}, + { OpSqrt, {Stack1, Stack1, OutUnknown, 0 }}, /* Logical ops */ { OpXor, {StackTop2, Stack1, OutBoolean, -1 }}, { OpNot, {Stack1, Stack1, OutBoolean, 0 }}, @@ -585,8 +289,8 @@ static const struct { { OpPushL, {Local, Stack1|Local, OutCInputL, 1 }}, { OpCGetN, {Stack1, Stack1, OutUnknown, 0 }}, { OpCGetG, {Stack1, Stack1, OutUnknown, 0 }}, - { OpCGetS, {StackTop2, Stack1, OutPred, -1 }}, - { OpCGetM, {MVector, Stack1, OutPred, 1 }}, + { OpCGetS, {StackTop2, Stack1, OutUnknown, -1 }}, + { OpCGetM, {MVector, Stack1, OutUnknown, 1 }}, { OpVGetL, {Local, Stack1|Local, OutVInputL, 1 }}, { OpVGetN, {Stack1, Stack1|Local, OutVUnknown, 0 }}, // TODO: In pseudo-main, the VGetG instruction invalidates what we know @@ -622,7 +326,7 @@ static const struct { { OpSetN, {StackTop2, Stack1|Local, OutSameAsInput, -1 }}, { OpSetG, {StackTop2, Stack1, OutSameAsInput, -1 }}, { OpSetS, {StackTop3, Stack1, OutSameAsInput, -2 }}, - { OpSetM, {MVector|Stack1, Stack1|Local, OutPred, 0 }}, + { OpSetM, {MVector|Stack1, Stack1|Local, OutUnknown, 0 }}, { OpSetWithRefLM,{MVector|Local , Local, OutNone, 0 }}, { OpSetWithRefRM,{MVector|Stack1, Local, OutNone, -1 }}, { OpSetOpL, {Stack1|Local, Stack1|Local, OutSetOp, 0 }}, @@ -696,13 +400,12 @@ static const struct { * FCall is special. Like the Ret* instructions, its manipulation of the * runtime stack are outside the boundaries of the tracelet abstraction. */ - { OpFCall, {FStack, Stack1, OutPred, 0 }}, - { OpFCallD, {FStack, Stack1, OutPred, 0 }}, - { OpFCallUnpack, {FStack, Stack1, OutPred, 0 }}, - { OpFCallArray, {FStack, Stack1, OutPred, + { OpFCall, {FStack, Stack1, OutUnknown, 0 }}, + { OpFCallD, {FStack, Stack1, OutUnknown, 0 }}, + { OpFCallUnpack, {FStack, Stack1, OutUnknown, 0 }}, + { OpFCallArray, {FStack, Stack1, OutUnknown, -(int)kNumActRecCells }}, - // TODO: output type is known - { OpFCallBuiltin,{BStackN, Stack1, OutPred, 0 }}, + { OpFCallBuiltin,{BStackN, Stack1, OutUnknown, 0 }}, { OpCufSafeArray,{StackTop3|DontGuardAny, Stack1, OutArray, -2 }}, { OpCufSafeReturn,{StackTop3|DontGuardAny, @@ -921,27 +624,6 @@ int getStackDelta(const NormalizedInstruction& ni) { return delta; } -// Task #3449943: This returns true even if there's meta-data telling -// that the value was inferred. -bool outputIsPredicted(NormalizedInstruction& inst) { - auto const& iInfo = getInstrInfo(inst.op()); - auto doPrediction = - (iInfo.type == OutPred || iInfo.type == OutCns) && !inst.endsRegion; - if (doPrediction) { - // All OutPred ops except for SetM have a single stack output for now. - assert(iInfo.out == Stack1 || inst.op() == OpSetM); - auto dt = predictOutputs(&inst); - if (dt) { - inst.outPred = *dt == KindOfRef ? Type(*dt, KindOfAny{}) : Type(*dt); - inst.outputPredicted = true; - } else { - doPrediction = false; - } - } - - return doPrediction; -} - bool isAlwaysNop(Op op) { switch (op) { case Op::BoxRNop: @@ -1522,7 +1204,6 @@ bool callDestroysLocals(const NormalizedInstruction& inst, bool instrBreaksProfileBB(const NormalizedInstruction* inst) { if (instrIsNonCallControlFlow(inst->op()) || - inst->outputPredicted || inst->op() == OpAwait || // may branch to scheduler and suspend execution inst->op() == OpClsCnsD) { // side exits if misses in the RDS return true; @@ -1950,8 +1631,6 @@ void translateInstr(HTS& hts, const NormalizedInstruction& ni) { FTRACE(1, "\n{:-^60}\n", folly::format("Translating {}: {} with stack:\n{}", ni.offset(), ni.toString(), show(hts))); - // When profiling, we disable type predictions to avoid side exits - assert(IMPLIES(mcg->tx().mode() == TransKind::Profile, !ni.outputPredicted)); irgen::ringbuffer(hts, Trace::RBTypeBytecodeStart, ni.source, 2); irgen::emitIncStat(hts, Stats::Instr_TC, 1); @@ -2156,20 +1835,6 @@ TranslateResult translateRegion(HTS& hts, inst.preppedByRef = byRefs.next(); } - /* - * Check for a type prediction. Put it in the NormalizedInstruction so - * the emit* method can use it if needed. In PGO mode, we don't really - * need the values coming from the interpreter type profiler. - * TransKind::Profile translations end whenever there's a side-exit, and - * type predictions incur side-exits. And when we stitch multiple - * TransKind::Profile translations together to form a larger region (in - * TransKind::Optimize mode), the guard for the top of the stack - * essentially does the role of type prediction. And, if the value is - * also inferred, then the guard is omitted. - */ - auto const doPrediction = mcg->tx().mode() == TransKind::Live && - outputIsPredicted(inst); - // If this block ends with an inlined FCall, we don't emit anything for // the FCall and instead set up HhbcTranslator for inlining. Blocks from // the callee will be next in the region. @@ -2185,8 +1850,7 @@ TranslateResult translateRegion(HTS& hts, show(hts)); auto returnSk = inst.nextSk(); auto returnFuncOff = returnSk.offset() - block->func()->base(); - irgen::beginInlining(hts, inst.imm[0].u_IVA, callee, returnFuncOff, - doPrediction ? inst.outPred : Type::Gen); + irgen::beginInlining(hts, inst.imm[0].u_IVA, callee, returnFuncOff); // "Fallthrough" into the callee's first block irgen::endBlock(hts, blocks[b + 1]->start().offset(), inst.nextIsMerge); continue; @@ -2277,21 +1941,13 @@ TranslateResult translateRegion(HTS& hts, } else if (b < blocks.size() - 1 && (isRet(inst.op()) || inst.op() == OpNativeImpl)) { // "Fallthrough" from inlined return to the next block - irgen::endBlock(hts, blocks[b + 1]->start().offset(), inst.nextIsMerge); + irgen::endBlock(hts, blocks[b + 1]->start().offset(), + inst.nextIsMerge); } if (region.isExit(blockId)) { irgen::endRegion(hts); } } - - // Check the prediction. If the predicted type is less specific than what - // is currently on the eval stack, checkType won't emit any code. - if (doPrediction && - // TODO(#5710339): would be nice to remove the following check - irgen::publicTopType(hts, 0).maybe(inst.outPred)) { - irgen::checkTypeStack(hts, 0, inst.outPred, - sk.advanced(block->unit()).offset()); - } } if (hts.mode == IRGenMode::Trace) { diff --git a/hphp/runtime/vm/jit/translator.h b/hphp/runtime/vm/jit/translator.h index ff0cd26bf20..ca5bf4ec712 100644 --- a/hphp/runtime/vm/jit/translator.h +++ b/hphp/runtime/vm/jit/translator.h @@ -488,7 +488,6 @@ enum OutTypeConstraints { OutFDesc, // Blows away the current function desc OutUnknown, // Not known at tracelet compile-time - OutPred, // Unknown, but give prediction a whirl. OutPredBool, // Boolean value predicted to be True or False OutCns, // Constant; may be known at compile-time OutVUnknown, // type is V(unknown) @@ -573,13 +572,6 @@ struct InstrInfo { const InstrInfo& getInstrInfo(Op op); /* - * Is the output of `inst' predicted? - * - * Flags on `inst' may be updated. - */ -bool outputIsPredicted(NormalizedInstruction& inst); - -/* * If this returns true, we dont generate guards for any of the inputs to this * instruction. * diff --git a/hphp/runtime/vm/type-profile.cpp b/hphp/runtime/vm/type-profile.cpp index 22e97f3de2a..3abf49bc919 100644 --- a/hphp/runtime/vm/type-profile.cpp +++ b/hphp/runtime/vm/type-profile.cpp @@ -16,28 +16,24 @@ #include "hphp/runtime/vm/type-profile.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include -#include - -#include "hphp/util/atomic-vector.h" #include "hphp/util/lock.h" #include "hphp/util/logger.h" #include "hphp/util/trace.h" #include "hphp/runtime/base/runtime-option.h" -#include "hphp/runtime/vm/func.h" #include "hphp/runtime/base/stats.h" -#include "hphp/runtime/vm/jit/translator.h" +#include "hphp/runtime/base/thread-info.h" +#include "hphp/runtime/vm/func.h" #include "hphp/runtime/vm/jit/write-lease.h" #include "hphp/runtime/vm/treadmill.h" +#include "hphp/util/atomic-vector.h" namespace HPHP { @@ -45,123 +41,7 @@ TRACE_SET_MOD(typeProfile); ////////////////////////////////////////////////////////////////////// -/* - * It is useful at translation time to have a hunch as to the types a given - * instruction is likely to produce. This is a probabilistic data structure - * that tries to balance: cost of reading and writing; memory overhead; - * prediction recall (percentage of time we make a prediction); prediction - * accuracy (fidelity relative to reality); and prediction precision - * (fidelity relative to ourselves). - */ - -typedef uint16_t Counter; -static const Counter kMaxCounter = USHRT_MAX; - -namespace { - -struct ValueProfile { - uint32_t m_tag; - // All of these saturate at 255. - Counter m_totalSamples; - Counter m_samples[kNumDataTypes]; - void dump() { - for (int i = 0; i < kNumDataTypes; i++) { - TRACE(0, "t%3d: %4d\n", i, m_samples[getDataTypeValue(i)]); - } - } -}; - -} - -/* - * Magic tunables. - */ - -/* - * kNumEntries - * - * Tradeoff: size vs. accuracy. - * - * ~256K entries. - * - * Size: (sizeof(ValueProfile) == 16) B -> 4MB - * - * Accuracy: If we collide further than kLineSize, we toss out perfectly - * good evidence. www seems to use about 12000 method names at this - * writing, so we have a decent pad for collisions. - */ - -static const int kNumEntries = 1 << 18; -static const int kLineSizeLog2 = 2; -static const int kLineSize = 1 << kLineSizeLog2; -static const int kNumLines = kNumEntries / kLineSize; -static const int kNumLinesMask = kNumLines - 1; - -/* - * kMinInstances - * - * Tradeoff: recall vs. accuracy. This is the minimum number of examples - * before we'll report any information at all. - * - * Recall: If we set this too high, we'll never be able to predict - * anything. - * - * Accuracy: If we set this too low, we'll be "jumpy", eagerly predicting - * types on the basis of weak evidence. - */ -static const double kMinInstances = 99.0; - -typedef ValueProfile ValueProfileLine[kLineSize]; -static ValueProfileLine* profiles; - -static ValueProfileLine* -profileInitMmap() { - const std::string& path = RuntimeOption::EvalJitProfilePath; - if (path.empty()) { - return nullptr; - } - - TRACE(1, "profileInit: path %s\n", path.c_str()); - int fd = open(path.c_str(), O_RDWR | O_CREAT, 0600); - if (fd < 0) { - TRACE(0, "profileInit: open %s failed: %s\n", path.c_str(), - folly::errnoStr(errno).c_str()); - perror("open"); - return nullptr; - } - - size_t len = sizeof(ValueProfileLine) * kNumLines; - int retval = ftruncate(fd, len); - if (retval < 0) { - perror("truncate"); - TRACE(0, "profileInit: truncate %s failed: %s\n", path.c_str(), - folly::errnoStr(errno).c_str()); - return nullptr; - } - - int flags = PROT_READ | - (RuntimeOption::EvalJitProfileRecord ? PROT_WRITE : 0); - void* mmapRet = mmap(0, len, flags, MAP_SHARED, // Yes, shared. - fd, 0); - if (mmapRet == MAP_FAILED) { - perror("mmap"); - TRACE(0, "profileInit: mmap %s failed: %s\n", path.c_str(), - folly::errnoStr(errno).c_str()); - return nullptr; - } - return (ValueProfileLine*)mmapRet; -} - -void -profileInit() { - if (!profiles) { - profiles = profileInitMmap(); - if (!profiles) { - TRACE(1, "profileInit: anonymous memory.\n"); - profiles = (ValueProfileLine*)calloc(sizeof(ValueProfileLine), kNumLines); - assert(profiles); - } - } +void profileInit() { } /* @@ -265,9 +145,8 @@ static void setHotFuncAttr() { void profileIncrementFuncCounter(const Func* f) { FuncProfileCounters::accessor acc; auto const value = FuncProfileCounters::value_type(f->getFuncId(), 0); - if (!s_func_counters.insert(acc, value)) { - __sync_fetch_and_add(&acc->second, 1); - } + s_func_counters.insert(acc, value); + ++acc->second; } int64_t requestCount() { @@ -329,121 +208,4 @@ void profileRequestEnd() { } } -enum class KeyToVPMode { - Read, Write -}; - -uint64_t -TypeProfileKey::hash() const { - return hash_int64_pair(m_kind, m_name->hash()); -} - -static inline ValueProfile* -keyToVP(TypeProfileKey key, KeyToVPMode mode) { - assert(profiles); - uint64_t h = key.hash(); - // Use the low-order kLineSizeLog2 bits to as tag bits to distinguish - // within the line, rather than to choose a line. Without the shift, all - // the tags in the line would have the same low-order bits, making - // collisions kLineSize times more likely. - int hidx = (h >> kLineSizeLog2) & kNumLinesMask; - ValueProfileLine& l = profiles[hidx]; - int replaceCandidate = 0; - int minCount = 255; - for (int i = 0; i < kLineSize; i++) { - if (l[i].m_tag == uint32_t(h)) { - TRACE(2, "Found %d for %s -> %d\n", - l[i].m_tag, key.m_name->data(), uint32_t(h)); - return &l[i]; - } - if (mode == KeyToVPMode::Write && l[i].m_totalSamples < minCount) { - replaceCandidate = i; - minCount = l[i].m_totalSamples; - } - } - if (mode == KeyToVPMode::Write) { - assert(replaceCandidate >= 0 && replaceCandidate < kLineSize); - ValueProfile& vp = l[replaceCandidate]; - Stats::inc(Stats::TypePred_Evict, vp.m_totalSamples != 0); - Stats::inc(Stats::TypePred_Insert); - TRACE(1, "Killing %d in favor of %s -> %d\n", - vp.m_tag, key.m_name ? key.m_name->data() : "NULL", uint32_t(h)); - vp.m_totalSamples = 0; - memset(&vp.m_samples, 0, sizeof(vp.m_samples)); - // Zero first, then claim. It seems safer to temporarily zero out some - // other function's values than to have this new function using the - // possibly-non-trivial prediction from an unrelated function. - compiler_membar(); - vp.m_tag = uint32_t(h); - return &l[replaceCandidate]; - } - return nullptr; -} - -void recordType(TypeProfileKey key, DataType dt) { - if (!profiles) return; - if (!isProfileRequest()) return; - assert(dt != KindOfUninit); - // Normalize strings to KindOfString. - if (dt == KindOfStaticString) dt = KindOfString; - TRACE(1, "recordType lookup: %s -> %d\n", key.m_name->data(), dt); - ValueProfile *prof = keyToVP(key, KeyToVPMode::Write); - if (prof->m_totalSamples != kMaxCounter) { - prof->m_totalSamples++; - // NB: we can't quite assert that we have fewer than kMaxCounter samples, - // because other threads are updating this structure without locks. - int dtIndex = getDataTypeIndex(dt); - if (prof->m_samples[dtIndex] < kMaxCounter) { - prof->m_samples[dtIndex]++; - } - } - ONTRACE(2, prof->dump()); -} - -/* - * Pair of (predicted type, confidence). - * - * A folly::none prediction means mixed/unknown. - */ -typedef std::pair PredVal; - -PredVal predictType(TypeProfileKey key) { - PredVal kNullPred = std::make_pair(KindOfUninit, 0.0); - if (!profiles) return kNullPred; - const ValueProfile *prof = keyToVP(key, KeyToVPMode::Read); - if (!prof) { - TRACE(2, "predictType lookup: %s -> MISS\n", key.m_name->data()); - Stats::inc(Stats::TypePred_Miss); - return kNullPred; - } - double total = prof->m_totalSamples; - if (total < kMinInstances) { - Stats::inc(Stats::TypePred_MissTooFew); - TRACE(2, "TypePred: hit %s but too few samples numSamples %d\n", - key.m_name->data(), prof->m_totalSamples); - return kNullPred; - } - double maxProb = 0.0; - DataType pred = KindOfUninit; - // If we have fewer than kMinInstances predictions, consider it too - // little data to be actionable. - for (int i = 0; i < kNumDataTypes; ++i) { - double prob = (1.0 * prof->m_samples[i]) / total; - if (prob > maxProb) { - maxProb = prob; - pred = getDataTypeValue(i); - } - if (prob >= 1.0) break; - } - Stats::inc(Stats::TypePred_Hit, maxProb >= 1.0); - Stats::inc(Stats::TypePred_MissTooWeak, maxProb < 1.0); - TRACE(2, "TypePred: hit %s numSamples %d pred %d prob %g\n", - key.m_name->data(), prof->m_totalSamples, pred, maxProb); - // Probabilities over 1.0 are possible due to racy updates. - if (maxProb > 1.0) maxProb = 1.0; - return std::make_pair(pred, maxProb); -} - -////////////////////////////////////////////////////////////////////// - } diff --git a/hphp/runtime/vm/type-profile.h b/hphp/runtime/vm/type-profile.h index 6abc8133e83..67df9114f5f 100644 --- a/hphp/runtime/vm/type-profile.h +++ b/hphp/runtime/vm/type-profile.h @@ -25,36 +25,16 @@ namespace HPHP { ////////////////////////////////////////////////////////////////////// -struct StringData; struct Func; ////////////////////////////////////////////////////////////////////// -struct TypeProfileKey { - enum KeyType { - MethodName, - PropName, - EltName, - StaticPropName - } m_kind; - const StringData* m_name; - - TypeProfileKey(KeyType kind, const StringData* sd) : - m_kind(kind), m_name(sd) { } - - TypeProfileKey(MemberCode mc, const StringData* sd) : - m_kind(mc == MET ? EltName : PropName), m_name(sd) { } - uint64_t hash() const; -}; - // These are both best-effort, and return noisy results. void profileInit(); void profileWarmupStart(); void profileWarmupEnd(); void profileRequestStart(); void profileRequestEnd(); -void recordType(TypeProfileKey sk, DataType dt); -std::pair predictType(TypeProfileKey key); int64_t requestCount(); /* diff --git a/hphp/test/slow/program_functions/ini_binding.php b/hphp/test/slow/program_functions/ini_binding.php index 3030f31cb65..f375c837455 100644 --- a/hphp/test/slow/program_functions/ini_binding.php +++ b/hphp/test/slow/program_functions/ini_binding.php @@ -3,7 +3,6 @@ var_dump(ini_get("hhvm.allow_hhas")); var_dump(ini_get("hhvm.jit_timer")); var_dump(ini_get("hhvm.simulate_arm")); -var_dump(ini_get("hhvm.jit_type_prediction")); var_dump(ini_get("hhvm.gdb_sync_chunks")); var_dump(ini_get("hhvm.server.apc.ttl_limit")); diff --git a/hphp/test/slow/program_functions/ini_binding.php.expect b/hphp/test/slow/program_functions/ini_binding.php.expect index c22dfdb4bfa..0b1b1f6111f 100644 --- a/hphp/test/slow/program_functions/ini_binding.php.expect +++ b/hphp/test/slow/program_functions/ini_binding.php.expect @@ -1,7 +1,6 @@ string(1) "1" string(0) "" string(1) "1" -string(0) "" string(3) "256" string(3) "100" string(4) "4100" diff --git a/hphp/test/slow/program_functions/ini_binding.php.ini b/hphp/test/slow/program_functions/ini_binding.php.ini index f179ff1af1d..67a14d2deed 100644 --- a/hphp/test/slow/program_functions/ini_binding.php.ini +++ b/hphp/test/slow/program_functions/ini_binding.php.ini @@ -2,7 +2,6 @@ hhvm.allow_hhas=true hhvm.jit_timer=false hhvm.simulate_arm=true -hhvm.jit_type_prediction=false hhvm.this_should_not_work=128 hhvm.gdb_sync_chunks=256 hhvm.server.apc.ttl_limit=100 -- 2.11.4.GIT