/* +----------------------------------------------------------------------+ | 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. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/base/strings.h" #include "hphp/runtime/vm/member_operations.h" #include "hphp/runtime/vm/jit/hhbc-translator.h" #include "hphp/runtime/vm/jit/ir.h" #include "hphp/runtime/vm/jit/ir-instruction.h" #include "hphp/runtime/vm/jit/translator-x64.h" // These files do ugly things with macros so include them last #include "hphp/util/assert_throw.h" #include "hphp/runtime/vm/jit/vector-translator-internal.h" namespace HPHP { namespace JIT { TRACE_SET_MOD(hhir); using HPHP::Transl::Location; static bool wantPropSpecializedWarnings() { return !RuntimeOption::RepoAuthoritative || !RuntimeOption::EvalDisableSomeRepoAuthNotices; } bool VectorEffects::supported(Opcode op) { return opcodeHasFlags(op, VectorProp | VectorElem); } bool VectorEffects::supported(const IRInstruction* inst) { return supported(inst->op()); } void VectorEffects::get(const IRInstruction* inst, StoreLocFunc storeLocalValue, SetLocTypeFunc setLocalType) { // If the base for this instruction is a local address, the // helper call might have side effects on the local's value SSATmp* base = inst->src(vectorBaseIdx(inst)); IRInstruction* locInstr = base->inst(); if (locInstr->op() == LdLocAddr) { UNUSED Type baseType = locInstr->dst()->type(); assert(baseType.equals(base->type())); assert(baseType.isPtr() || baseType.isKnownDataType()); int loc = locInstr->extra()->locId; VectorEffects ve(inst); if (ve.baseTypeChanged || ve.baseValChanged) { storeLocalValue(loc, nullptr); setLocalType(loc, ve.baseType.derefIfPtr()); } } } namespace { // Reduce baseType to a canonical unboxed, non-pointer form, returning (in // basePtr and baseBoxed) whether the original type was a pointer or boxed, // respectively. Whether or not the type is a pointer and/or boxed must be // statically known, unless it's exactly equal to Gen. void stripBase(Type& baseType, bool& basePtr, bool& baseBoxed) { assert_not_implemented(baseType.isPtr() || baseType.notPtr()); basePtr = baseType.isPtr(); baseType = basePtr ? baseType.deref() : baseType; assert_not_implemented( baseType.equals(Type::Gen) || baseType.isBoxed() || baseType.notBoxed()); baseBoxed = baseType.isBoxed(); baseType = baseBoxed ? baseType.innerType() : baseType; } Opcode canonicalOp(Opcode op) { if (op == ElemUX || op == ElemUXStk || op == UnsetElem || op == UnsetElemStk) { return UnsetElem; } if (op == SetWithRefElem || op == SetWithRefElemStk || op == SetWithRefNewElem || op == SetWithRefNewElemStk) { return SetWithRefElem; } return opcodeHasFlags(op, VectorProp) ? SetProp : opcodeHasFlags(op, VectorElem) || op == ArraySet ? SetElem : bad_value(); } } VectorEffects::VectorEffects(const IRInstruction* inst) { int keyIdx = vectorKeyIdx(inst); int valIdx = vectorValIdx(inst); init(inst->op(), inst->src(vectorBaseIdx(inst))->type(), keyIdx == -1 ? Type::None : inst->src(keyIdx)->type(), valIdx == -1 ? Type::None : inst->src(valIdx)->type()); } VectorEffects::VectorEffects(Opcode op, Type base, Type key, Type val) { init(op, base, key, val); } VectorEffects::VectorEffects(Opcode op, SSATmp* base, SSATmp* key, SSATmp* val) { auto typeOrNone = [](SSATmp* val){ return val ? val->type() : Type::None; }; init(op, typeOrNone(base), typeOrNone(key), typeOrNone(val)); } VectorEffects::VectorEffects(Opcode opc, const std::vector& srcs) { int keyIdx = vectorKeyIdx(opc); int valIdx = vectorValIdx(opc); init(opc, srcs[vectorBaseIdx(opc)]->type(), keyIdx == -1 ? Type::None : srcs[keyIdx]->type(), valIdx == -1 ? Type::None : srcs[valIdx]->type()); } void VectorEffects::init(const Opcode rawOp, const Type origBase, const Type key, const Type origVal) { baseType = origBase; bool basePtr, baseBoxed; stripBase(baseType, basePtr, baseBoxed); // Only certain types of bases are supported now but this list may expand in // the future. assert_not_implemented(basePtr || baseType.subtypeOfAny(Type::Obj, Type::Arr)); baseTypeChanged = baseValChanged = false; // Canonicalize the op to SetProp/SetElem/UnsetElem/SetWithRefElem auto const op = canonicalOp(rawOp); /* Although it should work fine in practice, if we have a key it should * either be a known DataType or Cell for now. */ assert(key.equals(Type::None) || key.isKnownDataType() || key.equals(Type::Cell)); // Deal with possible promotion to stdClass or array if ((op == SetElem || op == SetProp || op == SetWithRefElem) && baseType.maybe(Type::Null | Type::Bool | Type::Str)) { auto newBase = op == SetProp ? Type::Obj : Type::Arr; if (baseBoxed) { /* We always guard when loading from a Boxed type, so we don't *have* to * fix the type when the base is boxed. But it's still a good idea to do * something with it, since the inner type is used as a hint by * LdRef. And since the next load of the base will be guarded anyway we * can be optimistic and assume no promotion for string bases and * promotion in other cases. */ baseType = baseType.isString() ? Type::Str : newBase; } else if (baseType.isString() && (rawOp == SetElem || rawOp == SetElemStk)) { /* If the base is known to be a string and the operation is exactly * SetElem, we're guaranteed that either the base will end as a * CountedStr or the instruction will throw an exception and side * exit. */ baseType = Type::CountedStr; } else { /* Regardless of whether or not promotion happens, we know the base * cannot be Null after the operation. If the base was a subtype of Null * this will give newBase. */ baseType = (baseType - Type::Null) | newBase; } baseValChanged = true; } if ((op == SetElem || op == UnsetElem || op == SetWithRefElem) && baseType.maybe(Type::Arr | Type::Str)) { /* Modifying an array or string element, even when COW doesn't kick in, * produces a new SSATmp for the base. StaticArr/StaticStr may be promoted * to CountedArr/CountedStr. */ baseValChanged = true; if (baseType.maybe(Type::StaticArr)) baseType |= Type::CountedArr; if (baseType.maybe(Type::StaticStr)) baseType |= Type::CountedStr; } // The final baseType should be a pointer/box iff the input was baseType = baseBoxed ? baseType.box() : baseType; baseType = basePtr ? baseType.ptr() : baseType; baseTypeChanged = baseTypeChanged || baseType != origBase; /* Boxed bases may have their inner value changed but the value of the box * will never change. */ baseValChanged = !baseBoxed && (baseValChanged || baseTypeChanged); } // vectorBaseIdx returns the src index for inst's base operand. int vectorBaseIdx(Opcode opc) { return opc == SetNewElem || opc == SetNewElemStk ? 0 : opc == BindNewElem || opc == BindNewElemStk ? 0 : opc == ArraySet ? 1 : opc == SetOpProp || opc == SetOpPropStk ? 1 : opcodeHasFlags(opc, VectorProp) ? 2 : opcodeHasFlags(opc, VectorElem) ? 1 : bad_value(); } int vectorBaseIdx(const IRInstruction* inst) { return vectorBaseIdx(inst->op()); } // vectorKeyIdx returns the src index for inst's key operand. int vectorKeyIdx(Opcode opc) { return opc == SetNewElem || opc == SetNewElemStk ? -1 : opc == SetWithRefNewElem || opc == SetWithRefNewElemStk ? -1 : opc == BindNewElem || opc == BindNewElem ? -1 : opc == ArraySet ? 2 : opc == SetOpProp || opc == SetOpPropStk ? 2 : opcodeHasFlags(opc, VectorProp) ? 3 : opcodeHasFlags(opc, VectorElem) ? 2 : bad_value(); } int vectorKeyIdx(const IRInstruction* inst) { return vectorKeyIdx(inst->op()); } // vectorValIdx returns the src index for inst's value operand. int vectorValIdx(Opcode opc) { switch (opc) { case VGetProp: case VGetPropStk: case IncDecProp: case IncDecPropStk: case PropDX: case PropDXStk: case VGetElem: case VGetElemStk: case UnsetElem: case UnsetElemStk: case IncDecElem: case IncDecElemStk: case ElemDX: case ElemDXStk: case ElemUX: case ElemUXStk: return -1; case ArraySet: return 3; case SetNewElem: case SetNewElemStk: return 1; case SetWithRefNewElem: case SetWithRefNewElemStk: return 2; case BindNewElem: case BindNewElemStk: return 1; case SetOpProp: case SetOpPropStk: return 3; default: return opcodeHasFlags(opc, VectorProp) ? 4 : opcodeHasFlags(opc, VectorElem) ? 3 : bad_value(); } } int vectorValIdx(const IRInstruction* inst) { return vectorValIdx(inst->op()); } HhbcTranslator::VectorTranslator::VectorTranslator( const NormalizedInstruction& ni, HhbcTranslator& ht) : m_ni(ni) , m_ht(ht) , m_tb(*m_ht.m_tb) , m_irf(m_ht.m_irFactory) , m_mii(getMInstrInfo(ni.mInstrOp())) , m_marker(ht.makeMarker(ht.bcOff())) , m_needMIS(true) , m_misBase(nullptr) , m_base(nullptr) , m_result(nullptr) , m_strTestResult(nullptr) , m_failedSetTrace(nullptr) { } template SSATmp* HhbcTranslator::VectorTranslator::genStk(Opcode opc, IRTrace* taken, Srcs... srcs) { assert(opcodeHasFlags(opc, HasStackVersion)); assert(!opcodeHasFlags(opc, ModifiesStack)); std::vector srcVec({srcs...}); SSATmp* base = srcVec[vectorBaseIdx(opc)]; /* If the base is a pointer to a stack cell and the operation might change * its type and/or value, use the version of the opcode that returns a new * StkPtr. */ if (base->inst()->op() == LdStackAddr) { VectorEffects ve(opc, srcVec); if (ve.baseTypeChanged || ve.baseValChanged) { opc = getStackModifyingOpcode(opc); } } return gen(opc, taken, srcs...); } void HhbcTranslator::VectorTranslator::emit() { // Assign stack slots to our stack inputs numberStackInputs(); // Emit the base and every intermediate op emitMPre(); // Emit the final operation emitFinalMOp(); // Cleanup: decref inputs and scratch values emitMPost(); } // Returns a pointer to the base of the current MInstrState struct, or // a null pointer if it's not needed. SSATmp* HhbcTranslator::VectorTranslator::genMisPtr() { if (m_needMIS) { return gen(LdAddr, m_misBase, cns(kReservedRSPSpillSpace)); } else { return gen(DefConst, Type::PtrToCell, ConstData(nullptr)); } } // Inspect the instruction we're about to translate and determine if // it can be executed without using an MInstrState struct. void HhbcTranslator::VectorTranslator::checkMIState() { auto const& baseRtt = m_ni.inputs[m_mii.valCount()]->rtt; Type baseType = Type::fromRuntimeType(baseRtt); const bool isCGetM = m_ni.mInstrOp() == OpCGetM; const bool isSetM = m_ni.mInstrOp() == OpSetM; const bool isIssetM = m_ni.mInstrOp() == OpIssetM; const bool isUnsetM = m_ni.mInstrOp() == OpUnsetM; const bool isSingle = m_ni.immVecM.size() == 1; assert(baseType.isBoxed() || baseType.notBoxed()); baseType = baseType.unbox(); // CGetM or SetM with no unknown property offsets const bool simpleProp = !mInstrHasUnknownOffsets(m_ni, contextClass()) && (isCGetM || isSetM); // SetM with only one element const bool singlePropSet = isSingle && isSetM && mcodeMaybePropName(m_ni.immVecM[0]); // Array access with one element in the vector const bool singleElem = isSingle && mcodeMaybeArrayOrMapKey(m_ni.immVecM[0]); // SetM with one vector array element const bool simpleArraySet = isSetM && singleElem; // IssetM with one vector array element and an Arr base const bool simpleArrayIsset = isIssetM && singleElem && baseType.subtypeOf(Type::Arr); // IssetM with one vector array element and a collection type const bool simpleCollectionIsset = isIssetM && singleElem && baseType.strictSubtypeOf(Type::Obj) && isOptimizableCollectionClass(baseType.getClass()); // UnsetM on an array with one vector element const bool simpleArrayUnset = isUnsetM && singleElem; // UnsetM on a non-standard base. Always a noop or fatal. const bool badUnset = isUnsetM && baseType.not(Type::Arr | Type::Obj); // CGetM on an array with a base that won't use MInstrState. Str // will use tvScratch and Obj will fatal or use tvRef. const bool simpleArrayGet = isCGetM && singleElem && baseType.not(Type::Str | Type::Obj); const bool simpleCollectionGet = isCGetM && singleElem && baseType.strictSubtypeOf(Type::Obj) && isOptimizableCollectionClass(baseType.getClass()); if (simpleProp || singlePropSet || simpleArraySet || simpleArrayGet || simpleCollectionGet || simpleArrayUnset || badUnset || simpleCollectionIsset || simpleArrayIsset) { setNoMIState(); } } void HhbcTranslator::VectorTranslator::emitMPre() { checkMIState(); if (HPHP::Trace::moduleEnabled(HPHP::Trace::minstr, 1)) { emitMTrace(); } if (m_needMIS) { m_misBase = gen(DefMIStateBase); SSATmp* uninit = m_tb.genDefUninit(); if (nLogicalRatchets() > 0) { gen(StMem, m_misBase, cns(HHIR_MISOFF(tvRef)), uninit); gen(StMem, m_misBase, cns(HHIR_MISOFF(tvRef2)), uninit); } } // The base location is input 0 or 1, and the location code is stored // separately from m_ni.immVecM, so input indices (iInd) and member indices // (mInd) commonly differ. Additionally, W members have no corresponding // inputs, so it is necessary to track the two indices separately. m_iInd = m_mii.valCount(); emitBaseOp(); ++m_iInd; // Iterate over all but the last member, which is consumed by a final // operation. for (m_mInd = 0; m_mInd < m_ni.immVecM.size() - 1; ++m_mInd) { emitIntermediateOp(); emitRatchetRefs(); } } void HhbcTranslator::VectorTranslator::emitMTrace() { auto rttStr = [this](int i) { return Type::fromRuntimeType(m_ni.inputs[i]->rtt).unbox().toString(); }; std::ostringstream shape; int iInd = m_mii.valCount(); const char* separator = ""; shape << opcodeToName(m_ni.mInstrOp()) << " <"; auto baseLoc = m_ni.immVec.locationCode(); shape << folly::format("{}:{} ", locationCodeString(baseLoc), rttStr(iInd)); ++iInd; for (int mInd = 0; mInd < m_ni.immVecM.size(); ++mInd) { auto mcode = m_ni.immVecM[mInd]; shape << separator; if (mcode == MW) { shape << "MW"; } else if (mcodeMaybeArrayOrMapKey(mcode)) { shape << "ME:" << rttStr(iInd); } else if (mcodeMaybePropName(mcode)) { shape << "MP:" << rttStr(iInd); } else { not_reached(); } if (mcode != MW) ++iInd; separator = " "; } shape << '>'; gen(IncStatGrouped, cns(StringData::GetStaticString("vector instructions")), cns(StringData::GetStaticString(shape.str())), cns(1)); } // Build a map from (stack) input index to stack index. void HhbcTranslator::VectorTranslator::numberStackInputs() { // Stack inputs are pushed in the order they appear in the vector // from left to right, so earlier elements in the vector are at // higher offsets in the stack. m_mii.valCount() tells us how many // rvals the instruction takes on the stack; they're pushed after // any vector elements and we want to ignore them here. bool stackRhs = m_mii.valCount() && m_ni.inputs[0]->location.space == Location::Stack; int stackIdx = (int)stackRhs + m_ni.immVec.numStackValues() - 1; for (unsigned i = m_mii.valCount(); i < m_ni.inputs.size(); ++i) { const Location& l = m_ni.inputs[i]->location; switch (l.space) { case Location::Stack: assert(stackIdx >= 0); m_stackInputs[i] = stackIdx--; break; default: break; } } assert(stackIdx == (stackRhs ? 0 : -1)); if (stackRhs) { // If this instruction does have an RHS, it will be input 0 at // stack offset 0. assert(m_mii.valCount() == 1); m_stackInputs[0] = 0; } } SSATmp* HhbcTranslator::VectorTranslator::getBase() { assert(m_iInd == m_mii.valCount()); return getInput(m_iInd); } SSATmp* HhbcTranslator::VectorTranslator::getKey() { SSATmp* key = getInput(m_iInd); auto keyType = key->type(); assert(keyType.isBoxed() || keyType.notBoxed()); if (keyType.isBoxed()) { key = gen(LdRef, Type::Cell, key); } return key; } SSATmp* HhbcTranslator::VectorTranslator::getValue() { // If an instruction takes an rhs, it's always input 0. assert(m_mii.valCount() == 1); const int kValIdx = 0; return getInput(kValIdx); } SSATmp* HhbcTranslator::VectorTranslator::getValAddr() { assert(m_mii.valCount() == 1); const DynLocation& dl = *m_ni.inputs[0]; const Location& l = dl.location; if (l.space == Location::Local) { assert(!mapContains(m_stackInputs, 0)); return m_ht.ldLocAddr(l.offset); } else { assert(l.space == Location::Stack); assert(mapContains(m_stackInputs, 0)); m_ht.spillStack(); return m_ht.ldStackAddr(m_stackInputs[0]); } } SSATmp* HhbcTranslator::VectorTranslator::getInput(unsigned i) { const DynLocation& dl = *m_ni.inputs[i]; const Location& l = dl.location; assert(mapContains(m_stackInputs, i) == (l.space == Location::Stack)); switch (l.space) { case Location::Stack: { SSATmp* val = m_ht.top(Type::Gen | Type::Cls, m_stackInputs[i]); // Check if the type on our eval stack is at least as specific as what // Transl::Translator came up with. We allow boxed types with differing // inner types because of the different ways the two systems deal with // reference types. auto t = Type::fromRuntimeType(dl.rtt); if (!val->isA(t) && !(val->isBoxed() && t.isBoxed())) { FTRACE(1, "{}: hhir stack has a {} where Translator had a {}\n", __func__, val->type().toString(), t.toString()); // Normally here we would make sure that Translator gave us a sensical // type, but since we could be getting inputs from type-predicted // instructions, we can't actually be sure that the types are going to // match up. refineType is just going to ignore the Translator type // if the runtime type is completely unrelated. m_ht.refineType(val, t); } return val; } case Location::Local: return m_ht.ldLoc(l.offset); case Location::Litstr: return cns(m_ht.lookupStringId(l.offset)); case Location::Litint: return cns(l.offset); case Location::This: return gen(LdThis, m_tb.fp()); default: not_reached(); } } void HhbcTranslator::VectorTranslator::emitBaseLCR() { const MInstrAttr& mia = m_mii.getAttr(m_ni.immVec.locationCode()); const DynLocation& base = *m_ni.inputs[m_iInd]; auto baseType = Type::fromRuntimeType(base.rtt); assert(baseType.isKnownDataType()); if (base.location.isLocal()) { // Check for Uninit and warn/promote to InitNull as appropriate if (baseType.subtypeOf(Type::Uninit)) { if (mia & MIA_warn) { gen(RaiseUninitLoc, getEmptyCatchTrace(), LocalId(base.location.offset)); } if (mia & MIA_define) { gen( StLoc, LocalId(base.location.offset), m_tb.fp(), m_tb.genDefInitNull() ); baseType = Type::InitNull; } } } // If the base is a box with a type that's changed, we need to bail out of // the tracelet and retranslate. Doing an exit here is a little sketchy since // we may have already emitted instructions with memory effects to initialize // the MInstrState. These particular stores are harmless though, and the // worst outcome here is that we'll end up doing the stores twice, once for // this instruction and once at the beginning of the retranslation. IRTrace* failedRef = baseType.isBoxed() ? m_ht.getExitTrace() : nullptr; if ((baseType.subtypeOfAny(Type::Obj, Type::BoxedObj) && mcodeMaybePropName(m_ni.immVecM[0])) || isSimpleCollectionOp() != SimpleOp::None) { // In these cases we can pass the base by value, after unboxing if needed. m_base = gen(Unbox, failedRef, getBase()); assert(m_base->isA(baseType.unbox())); } else { // Everything else is passed by reference. We don't have to worry about // unboxing here, since all the generic helpers understand boxed bases. if (baseType.isBoxed()) { SSATmp* box = getBase(); assert(box->isA(Type::BoxedCell)); // Guard that the inner type hasn't changed gen(LdRef, baseType.innerType(), failedRef, box); } if (base.location.space == Location::Local) { m_base = m_ht.ldLocAddr(base.location.offset); } else { assert(base.location.space == Location::Stack); // Make sure the stack is clean before getting a pointer to one of its // elements. m_ht.spillStack(); assert(m_stackInputs.count(m_iInd)); m_base = m_ht.ldStackAddr(m_stackInputs[m_iInd]); } assert(m_base->type().isPtr()); } } // Is the current instruction a 1-element simple collection (includes Array), // operation? HhbcTranslator::VectorTranslator::SimpleOp HhbcTranslator::VectorTranslator::isSimpleCollectionOp() { SSATmp* base = getInput(m_mii.valCount()); auto baseType = base->type().unbox(); HPHP::Op op = m_ni.mInstrOp(); if ((op == OpSetM || op == OpCGetM || op == OpIssetM) && isSimpleBase() && isSingleMember()) { if (baseType.subtypeOf(Type::Arr)) { if (mcodeMaybeArrayOrMapKey(m_ni.immVecM[0])) { SSATmp* key = getInput(m_mii.valCount() + 1); if (key->isA(Type::Int) || key->isA(Type::Str)) { return SimpleOp::Array; } } } else if (baseType.strictSubtypeOf(Type::Obj)) { const Class* klass = baseType.getClass(); if (klass == c_Vector::s_cls || klass == c_Pair::s_cls) { if (mcodeMaybeVectorKey(m_ni.immVecM[0])) { SSATmp* key = getInput(m_mii.valCount() + 1); if (key->isA(Type::Int)) { return (klass == c_Vector::s_cls) ? SimpleOp::Vector : SimpleOp::Pair; } } } else if (klass == c_Map::s_cls || klass == c_StableMap::s_cls) { if (mcodeMaybeArrayOrMapKey(m_ni.immVecM[0])) { SSATmp* key = getInput(m_mii.valCount() + 1); if (key->isA(Type::Int) || key->isA(Type::Str)) { return (klass == c_Map::s_cls) ? SimpleOp::Map : SimpleOp::StableMap; } } } } } return SimpleOp::None; } // "Simple" bases are stack cells and locals. bool HhbcTranslator::VectorTranslator::isSimpleBase() { LocationCode loc = m_ni.immVec.locationCode(); return loc == LL || loc == LC || loc == LR; } bool HhbcTranslator::VectorTranslator::isSingleMember() { return m_ni.immVecM.size() == 1; } void HhbcTranslator::VectorTranslator::emitBaseH() { m_base = gen(LdThis, m_tb.fp()); } void HhbcTranslator::VectorTranslator::emitBaseN() { PUNT(emitBaseN); } template static inline TypedValue* baseGImpl(TypedValue *key, MInstrState* mis) { TypedValue* base; StringData* name = prepareKey(key); VarEnv* varEnv = g_vmContext->m_globalVarEnv; assert(varEnv != NULL); base = varEnv->lookup(name); if (base == NULL) { if (warn) { raise_notice(Strings::UNDEFINED_VARIABLE, name->data()); } if (define) { TypedValue tv; tvWriteNull(&tv); varEnv->set(name, &tv); base = varEnv->lookup(name); } else { return const_cast(init_null_variant.asTypedValue()); } } decRefStr(name); if (base->m_type == KindOfRef) { base = base->m_data.pref->tv(); } return base; } namespace VectorHelpers { TypedValue* baseG(TypedValue key, MInstrState* mis) { return baseGImpl(&key, mis); } TypedValue* baseGW(TypedValue key, MInstrState* mis) { return baseGImpl(&key, mis); } TypedValue* baseGD(TypedValue key, MInstrState* mis) { return baseGImpl(&key, mis); } TypedValue* baseGWD(TypedValue key, MInstrState* mis) { return baseGImpl(&key, mis); } } void HhbcTranslator::VectorTranslator::emitBaseG() { const MInstrAttr& mia = m_mii.getAttr(m_ni.immVec.locationCode()); typedef TypedValue* (*OpFunc)(TypedValue, MInstrState*); using namespace VectorHelpers; static const OpFunc opFuncs[] = {baseG, baseGW, baseGD, baseGWD}; OpFunc opFunc = opFuncs[mia & MIA_base]; SSATmp* gblName = getBase(); m_base = gen(BaseG, getEmptyCatchTrace(), cns(reinterpret_cast(opFunc)), gblName, genMisPtr()); } void HhbcTranslator::VectorTranslator::emitBaseS() { const int kClassIdx = m_ni.inputs.size() - 1; SSATmp* key = getKey(); SSATmp* clsRef = getInput(kClassIdx); m_base = gen(LdClsPropAddr, clsRef, key, CTX()); } void HhbcTranslator::VectorTranslator::emitBaseOp() { LocationCode lCode = m_ni.immVec.locationCode(); switch (lCode) { case LL: case LC: case LR: emitBaseLCR(); break; case LH: emitBaseH(); break; case LGL: case LGC: emitBaseG(); break; case LNL: case LNC: emitBaseN(); break; case LSL: case LSC: emitBaseS(); break; default: not_reached(); } } void HhbcTranslator::VectorTranslator::emitIntermediateOp() { switch (m_ni.immVecM[m_mInd]) { case MEC: case MEL: case MET: case MEI: { emitElem(); ++m_iInd; break; } case MPC: case MPL: case MPT: emitProp(); ++m_iInd; break; case MW: assert(m_mii.newElem()); emitNewElem(); break; default: not_reached(); } } void HhbcTranslator::VectorTranslator::emitProp() { const Class* knownCls = nullptr; const auto propInfo = getPropertyOffset(m_ni, contextClass(), knownCls, m_mii, m_mInd, m_iInd); auto mia = m_mii.getAttr(m_ni.immVecM[m_mInd]); if (propInfo.offset == -1 || (mia & Unset)) { emitPropGeneric(); } else { emitPropSpecialized(mia, propInfo); } } template static inline TypedValue* propImpl(Class* ctx, TypedValue* base, TypedValue keyVal, MInstrState* mis) { return Prop( mis->tvScratch, mis->tvRef, ctx, base, &keyVal); } #define HELPER_TABLE(m) \ /* name attrs isObj */ \ m(propC, None, false) \ m(propCD, Define, false) \ m(propCDO, Define, true) \ m(propCO, None, true) \ m(propCU, Unset, false) \ m(propCUO, Unset, true) \ m(propCW, Warn, false) \ m(propCWD, WarnDefine, false) \ m(propCWDO, WarnDefine, true) \ m(propCWO, Warn, true) #define PROP(nm, ...) \ TypedValue* nm(Class* ctx, TypedValue* base, TypedValue key, \ MInstrState* mis) { \ return propImpl<__VA_ARGS__>(ctx, base, key, mis); \ } namespace VectorHelpers { HELPER_TABLE(PROP) } #undef PROP void HhbcTranslator::VectorTranslator::emitPropGeneric() { MemberCode mCode = m_ni.immVecM[m_mInd]; MInstrAttr mia = MInstrAttr(m_mii.getAttr(mCode) & MIA_intermediate_prop); if ((mia & Unset) && m_base->type().strip().not(Type::Obj)) { m_base = m_tb.genPtrToInitNull(); return; } typedef TypedValue* (*OpFunc)(Class*, TypedValue*, TypedValue, MInstrState*); SSATmp* key = getKey(); BUILD_OPTAB(mia, m_base->isA(Type::Obj)); if (mia & Define) { m_base = genStk(PropDX, getCatchTrace(), cns((TCA)opFunc), CTX(), m_base, key, genMisPtr()); } else { m_base = gen(PropX, getCatchTrace(), cns((TCA)opFunc), CTX(), m_base, key, genMisPtr()); } } #undef HELPER_TABLE /* * Helper for emitPropSpecialized to check if a property is Uninit. It * returns a pointer to the property's address, or init_null_variant * if the property was Uninit and doDefine is false. * * We can omit the uninit check for properties that we know may not be * uninit due to the frontend's type inference. */ SSATmp* HhbcTranslator::VectorTranslator::checkInitProp( SSATmp* baseAsObj, SSATmp* propAddr, PropInfo propInfo, bool doWarn, bool doDefine) { SSATmp* key = getKey(); assert(key->isA(Type::StaticStr)); assert(baseAsObj->isA(Type::Obj)); assert(propAddr->type().isPtr()); auto const needsCheck = propInfo.hphpcType == KindOfInvalid && // The m_mInd check is to avoid initializing a property to // InitNull right before it's going to be set to something else. (doWarn || (doDefine && m_mInd < m_ni.immVecM.size() - 1)); if (!needsCheck) return propAddr; return m_tb.cond(m_ht.curFunc(), [&] (Block* taken) { gen(CheckInitMem, taken, propAddr, cns(0)); }, [&] { // Next: Property isn't Uninit. Do nothing. return propAddr; }, [&] { // Taken: Property is Uninit. Raise a warning and return // a pointer to InitNull, either in the object or // init_null_variant. m_tb.hint(Block::Hint::Unlikely); if (doWarn && wantPropSpecializedWarnings()) { gen(RaiseUndefProp, m_ht.getCatchTrace(), baseAsObj, key); } if (doDefine) { gen( StProp, baseAsObj, cns(propInfo.offset), m_tb.genDefInitNull() ); return propAddr; } return cns((const TypedValue*)&init_null_variant); } ); } Class* HhbcTranslator::VectorTranslator::contextClass() const { return m_ht.curFunc()->cls(); } void HhbcTranslator::VectorTranslator::emitPropSpecialized(const MInstrAttr mia, PropInfo propInfo) { assert(!(mia & MIA_warn) || !(mia & MIA_unset)); const bool doWarn = mia & MIA_warn; const bool doDefine = mia & MIA_define || mia & MIA_unset; SSATmp* initNull = cns((const TypedValue*)&init_null_variant); /* * Type-inference from hphpc only tells us that this is either an object of a * given class type or null. If it's not an object, it has to be a null type * based on type inference. (It could be KindOfRef with an object inside, * except that this isn't inferred for object properties so we're fine not * checking KindOfRef in that case.) * * On the other hand, if m_base->isA(Type::Obj), we're operating on the base * which was already guarded by tracelet guards (and may have been KindOfRef, * but the Base* op already handled this). So we only need to do a type * check against null here in the intermediate cases. */ if (m_base->isA(Type::Obj)) { SSATmp* propAddr = gen(LdPropAddr, m_base, cns(propInfo.offset)); m_base = checkInitProp(m_base, propAddr, propInfo, doWarn, doDefine); } else { SSATmp* baseAsObj = nullptr; m_base = m_tb.cond(m_ht.curFunc(), [&] (Block* taken) { // baseAsObj is only available in the Next branch baseAsObj = gen(LdMem, Type::Obj, taken, m_base, cns(0)); }, [&] { // Next: Base is an object. Load property address and // check for uninit return checkInitProp(baseAsObj, gen(LdPropAddr, baseAsObj, cns(propInfo.offset)), propInfo, doWarn, doDefine); }, [&] { // Taken: Base is Null. Raise warnings/errors and return InitNull. m_tb.hint(Block::Hint::Unlikely); if (doWarn) { gen(WarnNonObjProp); } if (doDefine) { /* * NOTE: * * This case logically is supposed to do a stdClass promotion. It * should ideally not be possible (since we have a known class type), * except that the static compiler doesn't correctly infer object * class types in some edge cases involving stdClass promotion. * * This is impossible to handle "correctly" if we're in the middle of * a multi-dim property expression, because things further along may * also have type inference telling them that object properties are * at a given slot, but the object could actually be a stdClass * instead of the knownCls type if we were to promote here. * * So, we throw a fatal error, which is what hphpc's generated C++ * would do in this case too. * * Relevant TODOs: * #1789661 (this can cause bugs if bytecode.cpp promotes) * #1124706 (we want to get rid of stdClass promotion in general) */ gen(ThrowNonObjProp); } return initNull; } ); } // At this point m_base is either a pointer to init_null_variant or // a property in the object that we've verified isn't uninit. assert(m_base->type().isPtr()); } template static inline TypedValue* elemImpl(TypedValue* base, TypedValue keyVal, MInstrState* mis) { TypedValue* key = keyPtr(keyVal); if (unset) { return ElemU(mis->tvScratch, mis->tvRef, base, key); } else if (define) { return ElemD(mis->tvScratch, mis->tvRef, base, key); } else { return Elem(mis->tvScratch, mis->tvRef, base, key); } } #define HELPER_TABLE(m) \ /* name hot keyType attrs */ \ m(elemC, , KeyType::Any, None) \ m(elemCD, , KeyType::Any, Define) \ m(elemCDR, , KeyType::Any, DefineReffy) \ m(elemCU, , KeyType::Any, Unset) \ m(elemCW, , KeyType::Any, Warn) \ m(elemCWD, , KeyType::Any, WarnDefine) \ m(elemCWDR, , KeyType::Any, WarnDefineReffy) \ m(elemI, , KeyType::Int, None) \ m(elemID, HOT_FUNC_VM, KeyType::Int, Define) \ m(elemIDR, , KeyType::Int, DefineReffy) \ m(elemIU, , KeyType::Int, Unset) \ m(elemIW, , KeyType::Int, Warn) \ m(elemIWD, , KeyType::Int, WarnDefine) \ m(elemIWDR, , KeyType::Int, WarnDefineReffy) \ m(elemS, HOT_FUNC_VM, KeyType::Str, None) \ m(elemSD, HOT_FUNC_VM, KeyType::Str, Define) \ m(elemSDR, , KeyType::Str, DefineReffy) \ m(elemSU, , KeyType::Str, Unset) \ m(elemSW, HOT_FUNC_VM, KeyType::Str, Warn) \ m(elemSWD, , KeyType::Str, WarnDefine) \ m(elemSWDR, , KeyType::Str, WarnDefineReffy) #define ELEM(nm, hot, keyType, attrs) \ hot \ TypedValue* nm(TypedValue* base, TypedValue key, MInstrState* mis) { \ return elemImpl(base, key, mis); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitElem() { MemberCode mCode = m_ni.immVecM[m_mInd]; MInstrAttr mia = MInstrAttr(m_mii.getAttr(mCode) & MIA_intermediate); SSATmp* key = getKey(); const bool unset = mia & Unset; const bool define = mia & Define; assert(!(define && unset)); if (unset) { SSATmp* uninit = m_tb.genPtrToUninit(); Type baseType = m_base->type().strip(); if (baseType.subtypeOf(Type::Str)) { m_ht.exceptionBarrier(); gen( RaiseError, cns(StringData::GetStaticString(Strings::OP_NOT_SUPPORTED_STRING)) ); m_base = uninit; return; } if (baseType.not(Type::Arr | Type::Obj)) { m_base = uninit; return; } } typedef TypedValue* (*OpFunc)(TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB_HOT(getKeyTypeIS(key), mia); if (define || unset) { m_base = genStk(define ? ElemDX : ElemUX, getCatchTrace(), cns((TCA)opFunc), m_base, key, genMisPtr()); } else { m_base = gen(ElemX, getCatchTrace(), cns((TCA)opFunc), m_base, key, genMisPtr()); } } #undef HELPER_TABLE void HhbcTranslator::VectorTranslator::emitNewElem() { PUNT(emitNewElem); } void HhbcTranslator::VectorTranslator::emitRatchetRefs() { if (ratchetInd() < 0 || ratchetInd() >= int(nLogicalRatchets())) { return; } m_base = m_tb.cond(m_ht.curFunc(), [&] (Block* taken) { gen(CheckInitMem, taken, m_misBase, cns(HHIR_MISOFF(tvRef))); }, [&] { // Next: tvRef isn't Uninit. Ratchet the refs // Clean up tvRef2 before overwriting it. if (ratchetInd() > 0) { gen(DecRefMem, Type::Gen, m_misBase, cns(HHIR_MISOFF(tvRef2))); } // Copy tvRef to tvRef2. Use mmx at some point SSATmp* tvRef = gen( LdMem, Type::Gen, m_misBase, cns(HHIR_MISOFF(tvRef)) ); gen(StMem, m_misBase, cns(HHIR_MISOFF(tvRef2)), tvRef); // Reset tvRef. gen(StMem, m_misBase, cns(HHIR_MISOFF(tvRef)), m_tb.genDefUninit()); // Adjust base pointer. assert(m_base->type().isPtr()); return gen(LdAddr, m_misBase, cns(HHIR_MISOFF(tvRef2))); }, [&] { // Taken: tvRef is Uninit. Do nothing. return m_base; } ); } void HhbcTranslator::VectorTranslator::emitFinalMOp() { typedef void (HhbcTranslator::VectorTranslator::*MemFun)(); switch (m_ni.immVecM[m_mInd]) { case MEC: case MEL: case MET: case MEI: static MemFun elemOps[] = { # define MII(instr, ...) &HhbcTranslator::VectorTranslator::emit##instr##Elem, MINSTRS # undef MII }; (this->*elemOps[m_mii.instr()])(); break; case MPC: case MPL: case MPT: static MemFun propOps[] = { # define MII(instr, ...) &HhbcTranslator::VectorTranslator::emit##instr##Prop, MINSTRS # undef MII }; (this->*propOps[m_mii.instr()])(); break; case MW: assert(m_mii.getAttr(MW) & MIA_final); static MemFun newOps[] = { # define MII(instr, attrs, bS, iS, vC, fN) \ &HhbcTranslator::VectorTranslator::emit##fN, MINSTRS # undef MII }; (this->*newOps[m_mii.instr()])(); break; default: not_reached(); } } template static inline TypedValue cGetPropImpl(Class* ctx, TypedValue* base, TypedValue keyVal, MInstrState* mis) { TypedValue* key = keyPtr(keyVal); TypedValue scratch; TypedValue* result = Prop( scratch, mis->tvRef, ctx, base, key); if (result->m_type == KindOfRef) { result = result->m_data.pref->tv(); } tvRefcountedIncRef(result); return *result; } #define HELPER_TABLE(m) \ /* name hot keyType isObj */ \ m(cGetPropC, , KeyType::Any, false) \ m(cGetPropCO, , KeyType::Any, true) \ m(cGetPropS, , KeyType::Str, false) \ m(cGetPropSO, HOT_FUNC_VM, KeyType::Str, true) #define PROP(nm, hot, ...) \ hot \ TypedValue nm(Class* ctx, TypedValue* base, TypedValue key, \ MInstrState* mis) { \ return cGetPropImpl<__VA_ARGS__>(ctx, base, key, mis); \ } namespace VectorHelpers { HELPER_TABLE(PROP) } #undef PROP void HhbcTranslator::VectorTranslator::emitCGetProp() { assert(!m_ni.outLocal); const Class* knownCls = nullptr; const auto propInfo = getPropertyOffset(m_ni, contextClass(), knownCls, m_mii, m_mInd, m_iInd); if (propInfo.offset != -1) { emitPropSpecialized(MIA_warn, propInfo); SSATmp* cellPtr = gen(UnboxPtr, m_base); SSATmp* propVal = gen(LdMem, Type::Cell, cellPtr, cns(0)); m_result = gen(IncRef, propVal); return; } typedef TypedValue (*OpFunc)(Class*, TypedValue*, TypedValue, MInstrState*); SSATmp* key = getKey(); BUILD_OPTAB_HOT(getKeyTypeS(key), m_base->isA(Type::Obj)); m_result = gen(CGetProp, getCatchTrace(), cns((TCA)opFunc), CTX(), m_base, key, genMisPtr()); } #undef HELPER_TABLE template static inline RefData* vGetPropImpl(Class* ctx, TypedValue* base, TypedValue keyVal, MInstrState* mis) { TypedValue* key = keyPtr(keyVal); TypedValue* result = HPHP::Prop( mis->tvScratch, mis->tvRef, ctx, base, key); if (result->m_type != KindOfRef) { tvBox(result); } RefData* ref = result->m_data.pref; ref->incRefCount(); return ref; } #define HELPER_TABLE(m) \ /* name hot keyType isObj */\ m(vGetPropC, , KeyType::Any, false) \ m(vGetPropCO, , KeyType::Any, true) \ m(vGetPropS, , KeyType::Str, false) \ m(vGetPropSO, HOT_FUNC_VM, KeyType::Str, true) #define PROP(nm, hot, ...) \ hot \ RefData* nm(Class* ctx, TypedValue* base, TypedValue key, \ MInstrState* mis) { \ return vGetPropImpl<__VA_ARGS__>(ctx, base, key, mis); \ } namespace VectorHelpers { HELPER_TABLE(PROP) } #undef PROP void HhbcTranslator::VectorTranslator::emitVGetProp() { SSATmp* key = getKey(); typedef RefData* (*OpFunc)(Class*, TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB_HOT(getKeyTypeS(key), m_base->isA(Type::Obj)); m_result = genStk(VGetProp, getCatchTrace(), cns((TCA)opFunc), CTX(), m_base, key, genMisPtr()); } #undef HELPER_TABLE template static inline bool issetEmptyPropImpl(Class* ctx, TypedValue* base, TypedValue keyVal) { return HPHP::IssetEmptyProp(ctx, base, &keyVal); } #define HELPER_TABLE(m) \ /* name useEmpty isObj */ \ m(issetPropC, false, false) \ m(issetPropCE, true, false) \ m(issetPropCEO, true, true) \ m(issetPropCO, false, true) #define ISSET(nm, ...) \ /* This returns int64_t to ensure all 64 bits of rax are valid */ \ uint64_t nm(Class* ctx, TypedValue* base, TypedValue key) { \ return issetEmptyPropImpl<__VA_ARGS__>(ctx, base, key); \ } namespace VectorHelpers { HELPER_TABLE(ISSET) } #undef ISSET void HhbcTranslator::VectorTranslator::emitIssetEmptyProp(bool isEmpty) { SSATmp* key = getKey(); typedef uint64_t (*OpFunc)(Class*, TypedValue*, TypedValue); BUILD_OPTAB(isEmpty, m_base->isA(Type::Obj)); m_result = gen(isEmpty ? EmptyProp : IssetProp, getCatchTrace(), cns((TCA)opFunc), CTX(), m_base, key); } #undef HELPER_TABLE void HhbcTranslator::VectorTranslator::emitIssetProp() { emitIssetEmptyProp(false); } void HhbcTranslator::VectorTranslator::emitEmptyProp() { emitIssetEmptyProp(true); } template static inline void setPropImpl(Class* ctx, TypedValue* base, TypedValue keyVal, Cell val) { HPHP::SetProp(ctx, base, &keyVal, &val); } #define HELPER_TABLE(m) \ /* name isObj */ \ m(setPropC, false) \ m(setPropCO, true) #define PROP(nm, ...) \ void nm(Class* ctx, TypedValue* base, TypedValue key, Cell val) { \ setPropImpl<__VA_ARGS__>(ctx, base, key, val); \ } namespace VectorHelpers { HELPER_TABLE(PROP) } #undef PROP void HhbcTranslator::VectorTranslator::emitSetProp() { SSATmp* value = getValue(); /* If we know the class for the current base, emit a direct property set. */ const Class* knownCls = nullptr; const auto propInfo = getPropertyOffset(m_ni, contextClass(), knownCls, m_mii, m_mInd, m_iInd); if (propInfo.offset != -1) { emitPropSpecialized(MIA_define, propInfo); SSATmp* cellPtr = gen(UnboxPtr, m_base); SSATmp* oldVal = gen(LdMem, Type::Cell, cellPtr, cns(0)); // The object owns a reference now SSATmp* increffed = gen(IncRef, value); gen(StMem, cellPtr, cns(0), value); gen(DecRef, oldVal); m_result = increffed; return; } // Emit the appropriate helper call. typedef void (*OpFunc)(Class*, TypedValue*, TypedValue, Cell); SSATmp* key = getKey(); BUILD_OPTAB(m_base->isA(Type::Obj)); genStk(SetProp, getCatchSetTrace(), cns((TCA)opFunc), CTX(), m_base, key, value); m_result = value; } #undef HELPER_TABLE template static inline TypedValue setOpPropImpl(TypedValue* base, TypedValue keyVal, Cell val, MInstrState* mis, SetOpOp op) { TypedValue* result = HPHP::SetOpProp( mis->tvScratch, mis->tvRef, mis->ctx, op, base, &keyVal, &val); TypedValue ret; tvReadCell(result, &ret); return ret; } #define HELPER_TABLE(m) \ /* name isObj */ \ m(setOpPropC, false) \ m(setOpPropCO, true) #define SETOP(nm, ...) \ TypedValue nm(TypedValue* base, TypedValue key, \ Cell val, MInstrState* mis, SetOpOp op) { \ return setOpPropImpl<__VA_ARGS__>(base, key, val, mis, op); \ } namespace VectorHelpers { HELPER_TABLE(SETOP) } #undef SETOP void HhbcTranslator::VectorTranslator::emitSetOpProp() { SetOpOp op = SetOpOp(m_ni.imm[0].u_OA); SSATmp* key = getKey(); SSATmp* value = getValue(); typedef TypedValue (*OpFunc)(TypedValue*, TypedValue, Cell, MInstrState*, SetOpOp); BUILD_OPTAB(m_base->isA(Type::Obj)); m_tb.gen(StRaw, m_misBase, cns(RawMemSlot::MisCtx), CTX()); m_result = genStk(SetOpProp, getCatchTrace(), cns((TCA)opFunc), m_base, key, value, genMisPtr(), cns(op)); } #undef HELPER_TABLE template static inline TypedValue incDecPropImpl(TypedValue* base, TypedValue keyVal, MInstrState* mis, IncDecOp op) { TypedValue result; result.m_type = KindOfUninit; HPHP::IncDecProp( mis->tvScratch, mis->tvRef, mis->ctx, op, base, &keyVal, result); assert(result.m_type != KindOfRef); return result; } #define HELPER_TABLE(m) \ /* name isObj */ \ m(incDecPropC, false) \ m(incDecPropCO, true) #define INCDEC(nm, ...) \ TypedValue nm(TypedValue* base, TypedValue key, \ MInstrState* mis, IncDecOp op) { \ return incDecPropImpl<__VA_ARGS__>(base, key, mis, op); \ } namespace VectorHelpers { HELPER_TABLE(INCDEC) } #undef INCDEC void HhbcTranslator::VectorTranslator::emitIncDecProp() { IncDecOp op = IncDecOp(m_ni.imm[0].u_OA); SSATmp* key = getKey(); typedef TypedValue (*OpFunc)(TypedValue*, TypedValue, MInstrState*, IncDecOp); BUILD_OPTAB(m_base->isA(Type::Obj)); m_tb.gen(StRaw, m_misBase, cns(RawMemSlot::MisCtx), CTX()); m_result = genStk(IncDecProp, getCatchTrace(), cns((TCA)opFunc), m_base, key, genMisPtr(), cns(op)); } #undef HELPER_TABLE template static inline void bindPropImpl(Class* ctx, TypedValue* base, TypedValue keyVal, RefData* val, MInstrState* mis) { TypedValue* prop = HPHP::Prop( mis->tvScratch, mis->tvRef, ctx, base, &keyVal); if (!(prop == &mis->tvScratch && prop->m_type == KindOfUninit)) { tvBindRef(val, prop); } } #define HELPER_TABLE(m) \ /* name isObj */ \ m(bindPropC, false) \ m(bindPropCO, true) #define PROP(nm, ...) \ void nm(Class* ctx, TypedValue* base, TypedValue key, \ RefData* val, MInstrState* mis) { \ bindPropImpl<__VA_ARGS__>(ctx, base, key, val, mis); \ } namespace VectorHelpers { HELPER_TABLE(PROP) } #undef PROP void HhbcTranslator::VectorTranslator::emitBindProp() { SSATmp* key = getKey(); SSATmp* box = getValue(); typedef void (*OpFunc)(Class*, TypedValue*, TypedValue, RefData*, MInstrState*); BUILD_OPTAB(m_base->isA(Type::Obj)); genStk(BindProp, getCatchTrace(), cns((TCA)opFunc), CTX(), m_base, key, box, genMisPtr()); m_result = box; } #undef HELPER_TABLE template static inline void unsetPropImpl(Class* ctx, TypedValue* base, TypedValue keyVal) { HPHP::UnsetProp(ctx, base, &keyVal); } #define HELPER_TABLE(m) \ /* name isObj */ \ m(unsetPropC, false) \ m(unsetPropCO, true) #define PROP(nm, ...) \ static void nm(Class* ctx, TypedValue* base, TypedValue key) { \ unsetPropImpl<__VA_ARGS__>(ctx, base, key); \ } namespace VectorHelpers { HELPER_TABLE(PROP) } #undef PROP void HhbcTranslator::VectorTranslator::emitUnsetProp() { SSATmp* key = getKey(); if (m_base->type().strip().not(Type::Obj)) { // Noop return; } typedef void (*OpFunc)(Class*, TypedValue*, TypedValue); BUILD_OPTAB(m_base->isA(Type::Obj)); gen(UnsetProp, cns((TCA)opFunc), CTX(), m_base, key); } #undef HELPER_TABLE // Keep these error handlers in sync with ArrayData::getNotFound(); NEVER_INLINE static TypedValue arrayGetNotFound(int64_t k) { raise_notice("Undefined index: %" PRId64, k); TypedValue v; tvWriteNull(&v); return v; } NEVER_INLINE static TypedValue arrayGetNotFound(const StringData* k) { raise_notice("Undefined index: %s", k->data()); TypedValue v; tvWriteNull(&v); return v; } static inline TypedValue* checkedGet(ArrayData* a, StringData* key) { int64_t i; return UNLIKELY(key->isStrictlyInteger(i)) ? a->nvGet(i) : a->nvGet(key); } static inline TypedValue* checkedGet(ArrayData* a, int64_t key) { not_reached(); } template static inline TypedValue arrayGetImpl( ArrayData* a, typename KeyTypeTraits::rawType key) { TypedValue* ret = checkForInt ? checkedGet(a, key) : a->nvGet(key); if (ret) { ret = tvToCell(ret); tvRefcountedIncRef(ret); return *ret; } return arrayGetNotFound(key); } #define HELPER_TABLE(m) \ /* name hot keyType checkForInt */\ m(arrayGetS, HOT_FUNC_VM, KeyType::Str, false) \ m(arrayGetSi, HOT_FUNC_VM, KeyType::Str, true) \ m(arrayGetI, HOT_FUNC_VM, KeyType::Int, false) #define ELEM(nm, hot, keyType, checkForInt) \ hot \ TypedValue nm(ArrayData* a, TypedValue* key) { \ return arrayGetImpl(a, keyAsRaw(key)); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitArrayGet(SSATmp* key) { KeyType keyType; bool checkForInt; m_ht.checkStrictlyInteger(key, keyType, checkForInt); typedef TypedValue (*OpFunc)(ArrayData*, TypedValue*); BUILD_OPTAB_HOT(keyType, checkForInt); assert(m_base->isA(Type::Arr)); m_result = gen(ArrayGet, cns((TCA)opFunc), m_base, key); } #undef HELPER_TABLE namespace VectorHelpers { TypedValue vectorGet(c_Vector* vec, int64_t key) { TypedValue* ret = vec->at(key); return *ret; } } void HhbcTranslator::VectorTranslator::emitVectorGet(SSATmp* key) { SSATmp* value = gen(VectorGet, getCatchTrace(), cns((TCA)VectorHelpers::vectorGet), m_base, key); m_result = gen(IncRef, value); } namespace VectorHelpers { TypedValue pairGet(c_Pair* pair, int64_t key) { TypedValue* ret = pair->at(key); return *ret; } } void HhbcTranslator::VectorTranslator::emitPairGet(SSATmp* key) { SSATmp* value = gen(PairGet, getCatchTrace(), cns((TCA)VectorHelpers::pairGet), m_base, key); m_result = gen(IncRef, value); } template static inline TypedValue mapGetImpl( c_Map* map, typename KeyTypeTraits::rawType key) { TypedValue* ret = map->at(key); return *ret; } #define HELPER_TABLE(m) \ /* name hot keyType */ \ m(mapGetS, HOT_FUNC_VM, KeyType::Str) \ m(mapGetI, HOT_FUNC_VM, KeyType::Int) #define ELEM(nm, hot, keyType) \ hot \ TypedValue nm(c_Map* map, TypedValue* key) { \ return mapGetImpl(map, keyAsRaw(key)); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitMapGet(SSATmp* key) { assert(key->isA(Type::Int) || key->isA(Type::Str)); KeyType keyType = key->isA(Type::Int) ? KeyType::Int : KeyType::Str; typedef TypedValue (*OpFunc)(c_Map*, TypedValue*); BUILD_OPTAB_HOT(keyType); SSATmp* value = gen(MapGet, getCatchTrace(), cns((TCA)opFunc), m_base, key); m_result = gen(IncRef, value); } #undef HELPER_TABLE template static inline TypedValue stableMapGetImpl( c_StableMap* map, typename KeyTypeTraits::rawType key) { TypedValue* ret = map->at(key); return *ret; } #define HELPER_TABLE(m) \ /* name hot keyType */ \ m(stableMapGetS, HOT_FUNC_VM, KeyType::Str) \ m(stableMapGetI, HOT_FUNC_VM, KeyType::Int) #define ELEM(nm, hot, keyType) \ hot \ TypedValue nm(c_StableMap* map, TypedValue* key) { \ return stableMapGetImpl(map, keyAsRaw(key)); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitStableMapGet(SSATmp* key) { assert(key->isA(Type::Int) || key->isA(Type::Str)); KeyType keyType = key->isA(Type::Int) ? KeyType::Int : KeyType::Str; typedef TypedValue (*OpFunc)(c_StableMap*, TypedValue*); BUILD_OPTAB_HOT(keyType); SSATmp* value = gen(StableMapGet, getCatchTrace(), cns((TCA)opFunc), m_base, key); m_result = gen(IncRef, value); } #undef HELPER_TABLE template static inline TypedValue cGetElemImpl(TypedValue* base, TypedValue keyVal, MInstrState* mis) { TypedValue* key = keyPtr(keyVal); TypedValue scratch; TypedValue* result = Elem(scratch, mis->tvRef, base, key); if (result->m_type == KindOfRef) { result = result->m_data.pref->tv(); } tvRefcountedIncRef(result); return *result; } #define HELPER_TABLE(m) \ /* name hot key */ \ m(cGetElemC, , KeyType::Any) \ m(cGetElemI, , KeyType::Int) \ m(cGetElemS, HOT_FUNC_VM, KeyType::Str) #define ELEM(nm, hot, ...) \ hot \ TypedValue nm(TypedValue* base, TypedValue key, MInstrState* mis) { \ return cGetElemImpl<__VA_ARGS__>(base, key, mis); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitCGetElem() { SSATmp* key = getKey(); SimpleOp simpleOpType = isSimpleCollectionOp(); switch (simpleOpType) { case SimpleOp::Array: emitArrayGet(key); break; case SimpleOp::Vector: emitVectorGet(key); break; case SimpleOp::Pair: emitPairGet(key); break; case SimpleOp::Map: emitMapGet(key); break; case SimpleOp::StableMap: emitStableMapGet(key); break; default: typedef TypedValue (*OpFunc)(TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB_HOT(getKeyTypeIS(key)); m_result = gen(CGetElem, getCatchTrace(), cns((TCA)opFunc), m_base, key, genMisPtr()); break; } } #undef HELPER_TABLE template static inline RefData* vGetElemImpl(TypedValue* base, TypedValue keyVal, MInstrState* mis) { TypedValue* key = keyPtr(keyVal); TypedValue* result = HPHP::ElemD( mis->tvScratch, mis->tvRef, base, key); if (result->m_type != KindOfRef) { tvBox(result); } RefData* ref = result->m_data.pref; ref->incRefCount(); return ref; } #define HELPER_TABLE(m) \ /* name keyType */ \ m(vGetElemC, KeyType::Any) \ m(vGetElemI, KeyType::Int) \ m(vGetElemS, KeyType::Str) #define ELEM(nm, ...) \ RefData* nm(TypedValue* base, TypedValue key, MInstrState* mis) { \ return vGetElemImpl<__VA_ARGS__>(base, key, mis); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitVGetElem() { SSATmp* key = getKey(); typedef RefData* (*OpFunc)(TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB(getKeyTypeIS(key)); m_result = genStk(VGetElem, getCatchTrace(), cns((TCA)opFunc), m_base, key, genMisPtr()); } #undef HELPER_TABLE template static inline bool issetEmptyElemImpl(TypedValue* base, TypedValue keyVal, MInstrState* mis) { TypedValue* key = keyPtr(keyVal); // mis == nullptr if we proved that it won't be used. mis->tvScratch and // mis->tvRef are ok because those params are passed by // reference. return HPHP::IssetEmptyElem( mis->tvScratch, mis->tvRef, base, key); } #define HELPER_TABLE(m) \ /* name hot keyType isEmpty */\ m(issetElemC, , KeyType::Any, false) \ m(issetElemCE, , KeyType::Any, true) \ m(issetElemI, HOT_FUNC_VM, KeyType::Int, false) \ m(issetElemIE, , KeyType::Int, true) \ m(issetElemS, HOT_FUNC_VM, KeyType::Str, false) \ m(issetElemSE, , KeyType::Str, true) #define ISSET(nm, hot, ...) \ hot \ uint64_t nm(TypedValue* base, TypedValue key, MInstrState* mis) { \ return issetEmptyElemImpl<__VA_ARGS__>(base, key, mis); \ } namespace VectorHelpers { HELPER_TABLE(ISSET) } #undef ISSET void HhbcTranslator::VectorTranslator::emitIssetEmptyElem(bool isEmpty) { SSATmp* key = getKey(); typedef uint64_t (*OpFunc)(TypedValue*, TypedValue, MInstrState*); BUILD_OPTAB_HOT(getKeyTypeIS(key), isEmpty); m_result = gen(isEmpty ? EmptyElem : IssetElem, getCatchTrace(), cns((TCA)opFunc), m_base, key, genMisPtr()); } #undef HELPER_TABLE template static inline uint64_t arrayIssetImpl( ArrayData* a, typename KeyTypeTraits::rawType key) { TypedValue* value = checkForInt ? checkedGet(a, key) : a->nvGet(key); Variant* var = &tvAsVariant(value); return var && !var->isNull(); } #define HELPER_TABLE(m) \ /* name keyType checkForInt */\ m(arrayIssetS, KeyType::Str, false) \ m(arrayIssetSi, KeyType::Str, true) \ m(arrayIssetI, KeyType::Int, false) #define ISSET(nm, keyType, checkForInt) \ uint64_t nm(ArrayData* a, TypedValue* key) { \ return arrayIssetImpl(a, keyAsRaw(key)); \ } namespace VectorHelpers { HELPER_TABLE(ISSET) } #undef ISSET void HhbcTranslator::VectorTranslator::emitArrayIsset() { SSATmp* key = getKey(); KeyType keyType; bool checkForInt; m_ht.checkStrictlyInteger(key, keyType, checkForInt); typedef uint64_t (*OpFunc)(ArrayData*, TypedValue*); BUILD_OPTAB(keyType, checkForInt); assert(m_base->isA(Type::Arr)); m_result = gen(ArrayIsset, getCatchTrace(), cns((TCA)opFunc), m_base, key); } #undef HELPER_TABLE namespace VectorHelpers { uint64_t vectorIsset(c_Vector* vec, int64_t index) { return vec->get(index) != nullptr; } } void HhbcTranslator::VectorTranslator::emitVectorIsset() { SSATmp* key = getKey(); assert(key->isA(Type::Int)); m_result = gen(VectorIsset, cns((TCA)VectorHelpers::vectorIsset), m_base, key); } namespace VectorHelpers { uint64_t pairIsset(c_Pair* pair, int64_t index) { return pair->get(index) != nullptr; } } void HhbcTranslator::VectorTranslator::emitPairIsset() { SSATmp* key = getKey(); assert(key->isA(Type::Int)); m_result = gen(PairIsset, cns((TCA)VectorHelpers::pairIsset), m_base, key); } template static inline uint64_t mapIssetImpl( c_Map* map, typename KeyTypeTraits::rawType key) { return map->get(key) != nullptr; } #define HELPER_TABLE(m) \ /* name hot keyType */ \ m(mapIssetS, HOT_FUNC_VM, KeyType::Str) \ m(mapIssetI, HOT_FUNC_VM, KeyType::Int) #define ELEM(nm, hot, keyType) \ hot \ uint64_t nm(c_Map* map, TypedValue* key) { \ return mapIssetImpl(map, keyAsRaw(key)); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitMapIsset() { SSATmp* key = getKey(); assert(key->isA(Type::Int) || key->isA(Type::Str)); KeyType keyType = key->isA(Type::Int) ? KeyType::Int : KeyType::Str; typedef TypedValue (*OpFunc)(c_Map*, TypedValue*); BUILD_OPTAB_HOT(keyType); m_result = gen(MapIsset, cns((TCA)opFunc), m_base, key); } #undef HELPER_TABLE template static inline uint64_t stableMapIssetImpl( c_StableMap* map, typename KeyTypeTraits::rawType key) { return map->get(key) != nullptr; } #define HELPER_TABLE(m) \ /* name hot keyType */ \ m(stableMapIssetS, HOT_FUNC_VM, KeyType::Str) \ m(stableMapIssetI, HOT_FUNC_VM, KeyType::Int) #define ELEM(nm, hot, keyType) \ hot \ uint64_t nm(c_StableMap* map, TypedValue* key) { \ return stableMapIssetImpl(map, keyAsRaw(key)); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitStableMapIsset() { SSATmp* key = getKey(); assert(key->isA(Type::Int) || key->isA(Type::Str)); KeyType keyType = key->isA(Type::Int) ? KeyType::Int : KeyType::Str; typedef TypedValue (*OpFunc)(c_StableMap*, TypedValue*); BUILD_OPTAB_HOT(keyType); m_result = gen(StableMapIsset, cns((TCA)opFunc), m_base, key); } #undef HELPER_TABLE void HhbcTranslator::VectorTranslator::emitIssetElem() { SimpleOp simpleOpType = isSimpleCollectionOp(); switch (simpleOpType) { case SimpleOp::Array: emitArrayIsset(); break; case SimpleOp::Vector: emitVectorIsset(); break; case SimpleOp::Pair: emitPairIsset(); break; case SimpleOp::Map: emitMapIsset(); break; case SimpleOp::StableMap: emitStableMapIsset(); break; default: emitIssetEmptyElem(false); break; } } void HhbcTranslator::VectorTranslator::emitEmptyElem() { emitIssetEmptyElem(true); } static inline ArrayData* checkedSet(ArrayData* a, StringData* key, CVarRef value, bool copy) { int64_t i; return UNLIKELY(key->isStrictlyInteger(i)) ? a->set(i, value, copy) : a->set(key, value, copy); } static inline ArrayData* checkedSet(ArrayData* a, int64_t key, CVarRef value, bool copy) { not_reached(); } template static inline typename ShuffleReturn::return_type arraySetImpl( ArrayData* a, typename KeyTypeTraits::rawType key, CVarRef value, RefData* ref) { static_assert(keyType != KeyType::Any, "KeyType::Any is not supported in arraySetMImpl"); const bool copy = a->getCount() > 1; ArrayData* ret = checkForInt ? checkedSet(a, key, value, copy) : a->set(key, value, copy); return arrayRefShuffle(a, ret, setRef ? ref->tv() : nullptr); } #define HELPER_TABLE(m) \ /* name hot keyType checkForInt setRef */ \ m(arraySetS, HOT_FUNC_VM, KeyType::Str, false, false) \ m(arraySetSi, HOT_FUNC_VM, KeyType::Str, true, false) \ m(arraySetI, HOT_FUNC_VM, KeyType::Int, false, false) \ m(arraySetSR, , KeyType::Str, false, true) \ m(arraySetSiR, , KeyType::Str, true, true) \ m(arraySetIR, , KeyType::Int, false, true) #define ELEM(nm, hot, keyType, checkForInt, setRef) \ hot \ typename ShuffleReturn::return_type \ nm(ArrayData* a, TypedValue* key, TypedValue value, RefData* ref) { \ return arraySetImpl( \ a, keyAsRaw(key), tvAsCVarRef(&value), ref); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitArraySet(SSATmp* key, SSATmp* value) { assert(m_iInd == m_mii.valCount() + 1); const int baseStkIdx = m_mii.valCount(); assert(key->type().notBoxed()); assert(value->type().notBoxed()); KeyType keyType; bool checkForInt; m_ht.checkStrictlyInteger(key, keyType, checkForInt); const DynLocation& base = *m_ni.inputs[m_mii.valCount()]; bool setRef = base.outerType() == KindOfRef; typedef ArrayData* (*OpFunc)(ArrayData*, TypedValue*, TypedValue, RefData*); BUILD_OPTAB_HOT(keyType, checkForInt, setRef); // No catch trace below because the helper can't throw. It may reenter to // call destructors so it has a sync point in nativecalls.cpp, but exceptions // are swallowed at destructor boundaries right now: #2182869. if (setRef) { assert(base.location.space == Location::Local || base.location.space == Location::Stack); SSATmp* box = getInput(baseStkIdx); gen(ArraySetRef, cns((TCA)opFunc), m_base, key, value, box); // Unlike the non-ref case, we don't need to do anything to the stack // because any load of the box will be guarded. } else { SSATmp* newArr = gen(ArraySet, cns((TCA)opFunc), m_base, key, value); // Update the base's value with the new array if (base.location.space == Location::Local) { // We know it's not boxed (setRef above handles that), and // newArr has already been incref'd in the helper. gen(StLoc, LocalId(base.location.offset), m_tb.fp(), newArr); } else if (base.location.space == Location::Stack) { VectorEffects ve(newArr->inst()); assert(ve.baseValChanged); assert(ve.baseType.subtypeOf(Type::Arr)); m_ht.extendStack(baseStkIdx, Type::Gen); m_ht.replace(baseStkIdx, newArr); } else { not_reached(); } } m_result = value; } #undef HELPER_TABLE namespace VectorHelpers { void setWithRefElemC(TypedValue* base, TypedValue keyVal, TypedValue* val, MInstrState* mis) { base = HPHP::ElemD(mis->tvScratch, mis->tvRef, base, &keyVal); if (base != &mis->tvScratch) { tvDup(*val, *base); } else { assert(base->m_type == KindOfUninit); } } void setWithRefNewElem(TypedValue* base, TypedValue* val, MInstrState* mis) { base = NewElem(mis->tvScratch, mis->tvRef, base); if (base != &mis->tvScratch) { tvDup(*val, *base); } else { assert(base->m_type == KindOfUninit); } } } void HhbcTranslator::VectorTranslator::emitSetWithRefLElem() { SSATmp* key = getKey(); SSATmp* locAddr = getValAddr(); if (m_base->type().strip().subtypeOf(Type::Arr) && !locAddr->type().deref().maybeBoxed()) { emitSetElem(); assert(m_strTestResult == nullptr); } else { genStk(SetWithRefElem, getCatchTrace(), cns((TCA)VectorHelpers::setWithRefElemC), m_base, key, locAddr, genMisPtr()); } m_result = nullptr; } void HhbcTranslator::VectorTranslator::emitSetWithRefLProp() { SPUNT(__func__); } void HhbcTranslator::VectorTranslator::emitSetWithRefRElem() { emitSetWithRefLElem(); } void HhbcTranslator::VectorTranslator::emitSetWithRefRProp() { emitSetWithRefLProp(); } void HhbcTranslator::VectorTranslator::emitSetWithRefNewElem() { if (m_base->type().strip().subtypeOf(Type::Arr) && getValue()->type().notBoxed()) { emitSetNewElem(); } else { genStk(SetWithRefNewElem, getCatchTrace(), cns((TCA)VectorHelpers::setWithRefNewElem), m_base, getValAddr(), genMisPtr()); } m_result = nullptr; } namespace VectorHelpers { void vectorSet(c_Vector* vec, int64_t key, Cell value) { vec->set(key, &value); } } void HhbcTranslator::VectorTranslator::emitVectorSet( SSATmp* key, SSATmp* value) { gen(VectorSet, getCatchTrace(), cns((TCA)VectorHelpers::vectorSet), m_base, key, value); m_result = value; } template static inline void mapSetImpl( c_Map* map, typename KeyTypeTraits::rawType key, Cell value) { map->set(key, &value); } #define HELPER_TABLE(m) \ /* name hot keyType */ \ m(mapSetS, HOT_FUNC_VM, KeyType::Str) \ m(mapSetI, HOT_FUNC_VM, KeyType::Int) #define ELEM(nm, hot, keyType) \ hot \ void nm(c_Map* map, TypedValue* key, Cell value) { \ mapSetImpl(map, keyAsRaw(key), value); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitMapSet( SSATmp* key, SSATmp* value) { assert(key->isA(Type::Int) || key->isA(Type::Str)); KeyType keyType = key->isA(Type::Int) ? KeyType::Int : KeyType::Str; typedef TypedValue (*OpFunc)(c_Map*, TypedValue*, TypedValue*); BUILD_OPTAB_HOT(keyType); gen(MapSet, getCatchTrace(), cns((TCA)opFunc), m_base, key, value); m_result = value; } #undef HELPER_TABLE template static inline void stableMapSetImpl( c_StableMap* map, typename KeyTypeTraits::rawType key, Cell value) { map->set(key, &value); } #define HELPER_TABLE(m) \ /* name hot keyType */ \ m(stableMapSetS, HOT_FUNC_VM, KeyType::Str) \ m(stableMapSetI, HOT_FUNC_VM, KeyType::Int) #define ELEM(nm, hot, keyType) \ hot \ void nm(c_StableMap* map, TypedValue* key, Cell value) { \ stableMapSetImpl(map, keyAsRaw(key), value); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitStableMapSet( SSATmp* key, SSATmp* value) { assert(key->isA(Type::Int) || key->isA(Type::Str)); KeyType keyType = key->isA(Type::Int) ? KeyType::Int : KeyType::Str; typedef TypedValue (*OpFunc)(c_StableMap*, TypedValue*, TypedValue*); BUILD_OPTAB_HOT(keyType); gen(StableMapSet, getCatchTrace(), cns((TCA)opFunc), m_base, key, value); m_result = value; } #undef HELPER_TABLE template static inline StringData* setElemImpl(TypedValue* base, TypedValue keyVal, Cell val) { TypedValue* key = keyPtr(keyVal); return HPHP::SetElem(base, key, &val); } #define HELPER_TABLE(m) \ /* name hot keyType */ \ m(setElemC, , KeyType::Any) \ m(setElemI, , KeyType::Int) \ m(setElemS, HOT_FUNC_VM, KeyType::Str) #define ELEM(nm, hot, ...) \ hot \ StringData* nm(TypedValue* base, TypedValue key, Cell val) { \ return setElemImpl<__VA_ARGS__>(base, key, val); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitSetElem() { SSATmp* value = getValue(); SSATmp* key = getKey(); SimpleOp simpleOpType = isSimpleCollectionOp(); switch (simpleOpType) { case SimpleOp::Array: emitArraySet(key, value); break; case SimpleOp::Vector: emitVectorSet(key, value); break; case SimpleOp::Map: emitMapSet(key, value); break; case SimpleOp::StableMap: emitStableMapSet(key, value); break; default: // Emit the appropriate helper call. typedef StringData* (*OpFunc)(TypedValue*, TypedValue, Cell); BUILD_OPTAB_HOT(getKeyTypeIS(key)); m_failedSetTrace = getCatchSetTrace(); SSATmp* result = genStk(SetElem, m_failedSetTrace, cns((TCA)opFunc), m_base, key, value); auto t = result->type(); if (t.equals(Type::Nullptr)) { // Base is not a string. Result is always value. m_result = value; } else if (t.equals(Type::CountedStr)) { // Base is a string. Stack result is a new string so we're responsible for // decreffing value. m_result = result; gen(DecRef, value); } else { assert(t.equals(Type::CountedStr | Type::Nullptr)); // Base might be a string. Assume the result is value, then inform // emitMPost that it needs to test the actual result. m_result = value; m_strTestResult = result; } break; } } #undef HELPER_TABLE template static inline TypedValue setOpElemImpl(TypedValue* base, TypedValue keyVal, Cell val, MInstrState* mis) { TypedValue* result = HPHP::SetOpElem(mis->tvScratch, mis->tvRef, op, base, &keyVal, &val); TypedValue ret; tvReadCell(result, &ret); return ret; } #define OPELEM_TABLE(m, nm, op) \ /* name op */ \ m(nm##op##ElemC, op) #define HELPER_TABLE(m, op) OPELEM_TABLE(m, setOp, SetOp##op) #define SETOP(nm, ...) \ TypedValue nm(TypedValue* base, TypedValue key, Cell val, \ MInstrState* mis) { \ return setOpElemImpl<__VA_ARGS__>(base, key, val, mis); \ } #define SETOP_OP(op, bcOp) HELPER_TABLE(SETOP, op) namespace VectorHelpers { SETOP_OPS } #undef SETOP_OP #undef SETOP void HhbcTranslator::VectorTranslator::emitSetOpElem() { SetOpOp op = SetOpOp(m_ni.imm[0].u_OA); SSATmp* key = getKey(); typedef TypedValue (*OpFunc)(TypedValue*, TypedValue, Cell, MInstrState*); # define SETOP_OP(op, bcOp) HELPER_TABLE(FILL_ROW, op) BUILD_OPTAB_ARG(SETOP_OPS, op); # undef SETOP_OP m_result = genStk(SetOpElem, getCatchTrace(), cns((TCA)opFunc), m_base, key, getValue(), genMisPtr()); } #undef HELPER_TABLE template static inline TypedValue incDecElemImpl(TypedValue* base, TypedValue keyVal, MInstrState* mis) { TypedValue result; HPHP::IncDecElem( mis->tvScratch, mis->tvRef, op, base, &keyVal, result); assert(result.m_type != KindOfRef); return result; } #define HELPER_TABLE(m, op) OPELEM_TABLE(m, incDec, op) #define INCDEC(nm, ...) \ TypedValue nm(TypedValue* base, TypedValue key, MInstrState* mis) { \ return incDecElemImpl<__VA_ARGS__>(base, key, mis); \ } #define INCDEC_OP(op) HELPER_TABLE(INCDEC, op) namespace VectorHelpers { INCDEC_OPS } #undef INCDEC_OP #undef INCDEC void HhbcTranslator::VectorTranslator::emitIncDecElem() { IncDecOp op = IncDecOp(m_ni.imm[0].u_OA); SSATmp* key = getKey(); typedef TypedValue (*OpFunc)(TypedValue*, TypedValue, MInstrState*); # define INCDEC_OP(op) HELPER_TABLE(FILL_ROW, op) BUILD_OPTAB_ARG(INCDEC_OPS, op); # undef INCDEC_OP m_result = genStk(IncDecElem, getCatchTrace(), cns((TCA)opFunc), m_base, key, genMisPtr()); } #undef HELPER_TABLE namespace VectorHelpers { void bindElemC(TypedValue* base, TypedValue keyVal, RefData* val, MInstrState* mis) { base = HPHP::ElemD(mis->tvScratch, mis->tvRef, base, &keyVal); if (!(base == &mis->tvScratch && base->m_type == KindOfUninit)) { tvBindRef(val, base); } } } void HhbcTranslator::VectorTranslator::emitBindElem() { SSATmp* key = getKey(); SSATmp* box = getValue(); genStk(BindElem, getCatchTrace(), cns((TCA)VectorHelpers::bindElemC), m_base, key, box, genMisPtr()); m_result = box; } template static inline void unsetElemImpl(TypedValue* base, TypedValue keyVal) { TypedValue* key = keyPtr(keyVal); HPHP::UnsetElem(base, key); } #define HELPER_TABLE(m) \ /* name hot keyType */ \ m(unsetElemC, , KeyType::Any) \ m(unsetElemI, , KeyType::Int) \ m(unsetElemS, HOT_FUNC_VM, KeyType::Str) #define ELEM(nm, hot, ...) \ hot \ void nm(TypedValue* base, TypedValue key) { \ unsetElemImpl<__VA_ARGS__>(base, key); \ } namespace VectorHelpers { HELPER_TABLE(ELEM) } #undef ELEM void HhbcTranslator::VectorTranslator::emitUnsetElem() { SSATmp* key = getKey(); Type baseType = m_base->type().strip(); if (baseType.subtypeOf(Type::Str)) { m_ht.exceptionBarrier(); gen(RaiseError, cns(StringData::GetStaticString(Strings::CANT_UNSET_STRING))); return; } if (baseType.not(Type::Arr | Type::Obj)) { // Noop return; } typedef void (*OpFunc)(TypedValue*, TypedValue); BUILD_OPTAB_HOT(getKeyTypeIS(key)); genStk(UnsetElem, getCatchTrace(), cns((TCA)opFunc), m_base, key); } #undef HELPER_TABLE void HhbcTranslator::VectorTranslator::emitNotSuppNewElem() { not_reached(); } void HhbcTranslator::VectorTranslator::emitVGetNewElem() { SPUNT(__func__); } void HhbcTranslator::VectorTranslator::emitSetNewElem() { SSATmp* value = getValue(); gen(SetNewElem, getCatchSetTrace(), m_base, value); m_result = value; } void HhbcTranslator::VectorTranslator::emitSetOpNewElem() { SPUNT(__func__); } void HhbcTranslator::VectorTranslator::emitIncDecNewElem() { SPUNT(__func__); } void HhbcTranslator::VectorTranslator::emitBindNewElem() { SSATmp* box = getValue(); genStk(BindNewElem, getCatchTrace(), m_base, box, genMisPtr()); m_result = box; } void HhbcTranslator::VectorTranslator::emitMPost() { SSATmp* catchSp = nullptr; if (m_failedSetTrace) { catchSp = m_failedSetTrace->back()->back()->src(0); assert(catchSp->isA(Type::StkPtr)); } // Decref stack inputs. If we're translating a SetM or BindM, then input 0 is // both our input and output so leave its refcount alone. If m_failedSetTrace // is non-null, the final helper call may throw an InvalidSetMException. We // need to add instructions to m_failedSetTrace to finish the vector // instruction in case this happens, so any DecRefs emitted here are also // added to m_failedSetTrace. unsigned nStack = (m_ni.mInstrOp() == OpSetM || m_ni.mInstrOp() == OpBindM) ? 1 : 0; for (unsigned i = nStack; i < m_ni.inputs.size(); ++i) { const DynLocation& input = *m_ni.inputs[i]; switch (input.location.space) { case Location::Stack: { ++nStack; auto input = getInput(i); if (input->isA(Type::Gen)) { gen(DecRef, input); if (m_failedSetTrace) { TracePusher tp(m_tb, m_failedSetTrace, m_marker); gen(DecRefStack, StackOffset(m_stackInputs[i]), Type::Cell, catchSp); } } break; } case Location::Local: case Location::Litstr: case Location::Litint: case Location::This: { // Do nothing. break; } default: not_reached(); } } // Pop off all stack inputs m_ht.discard(nStack); // Push result, if one was produced. If we have a predicted result use that // instead of the real result; its validity will be guarded later in this // function. if (m_result) { m_ht.push(m_result); } else { assert(m_ni.mInstrOp() == OpUnsetM || m_ni.mInstrOp() == OpSetWithRefLM || m_ni.mInstrOp() == OpSetWithRefRM); } // Clean up tvRef(2): during exception handling any objects required only // during vector expansion need to be DecRef'd. There may be either one // or two such scratch objects, in the case of a Set the first of which will // always be tvRef2, in all other cases if only one scratch value is present // it will be stored in tvRef. static const size_t refOffs[] = { HHIR_MISOFF(tvRef), HHIR_MISOFF(tvRef2) }; for (unsigned i = 0; i < std::min(nLogicalRatchets(), 2U); ++i) { IRInstruction* inst = m_irf.gen(DecRefMem, m_marker, Type::Gen, m_misBase, cns(refOffs[m_failedSetTrace ? 1 - i : i])); m_tb.add(inst); prependToTraces(inst); } emitSideExits(catchSp, nStack); } void HhbcTranslator::VectorTranslator::emitSideExits(SSATmp* catchSp, int nStack) { auto const nextOff = m_ht.nextBcOff(); auto const op = m_ni.mInstrOp(); const bool isSetWithRef = op == OpSetWithRefLM || op == OpSetWithRefRM; if (m_failedSetTrace) { assert(bool(m_result) ^ isSetWithRef); // This catch trace currently ends with an EndCatch that will fall through // if an InvalidSetMException was thrown. We need to emit code to clean up // if that happens. If we're translating a SetWithRef* bytecode we don't // have to do anything special to the stack since they have no stack // output. Otherwise we need to pop our input value and push the value from // the exception to the stack (done with a DecRefStack followed by a // SpillStack). std::vector args{ catchSp, // sp from the previous SpillStack cns(nStack), // cells popped since the last SpillStack }; TracePusher tp(m_tb, m_failedSetTrace, m_marker); if (!isSetWithRef) { gen(DecRefStack, StackOffset(0), Type::Cell, catchSp); args.push_back(m_ht.gen(LdUnwinderValue, Type::Cell)); } SSATmp* sp = gen(SpillStack, std::make_pair(args.size(), &args[0])); gen(DeleteUnwinderException); gen(SyncABIRegs, m_tb.fp(), sp); gen(ReqBindJmp, BCOffset(nextOff)); } if (m_strTestResult) { assert(!isSetWithRef); // We expected SetElem's base to not be a Str but might be wrong. Make an // exit trace to side exit to the next instruction, replacing our guess // with the correct stack output. auto toSpill = m_ht.peekSpillValues(); assert(toSpill.size()); assert(toSpill[0] == m_result); SSATmp* str = m_irf.gen(AssertNonNull, m_marker, m_strTestResult)->dst(); toSpill[0] = str; auto exitTrace = m_ht.getExitTrace(nextOff, toSpill); { TracePusher tp(m_tb, exitTrace, m_marker, exitTrace->back(), exitTrace->back()->skipHeader()); gen(IncStat, cns(Stats::TC_SetMStrGuess_Miss), cns(1), cns(false)); gen(DecRef, m_result); m_tb.add(str->inst()); } gen(CheckType, Type::Nullptr, exitTrace, m_strTestResult); gen(IncStat, cns(Stats::TC_SetMStrGuess_Hit), cns(1), cns(false)); } } bool HhbcTranslator::VectorTranslator::needFirstRatchet() const { if (m_ni.inputs[m_mii.valCount()]->valueType() == KindOfArray) { switch (m_ni.immVecM[0]) { case MEC: case MEL: case MET: case MEI: return false; case MPC: case MPL: case MPT: case MW: return true; default: not_reached(); } } return true; } bool HhbcTranslator::VectorTranslator::needFinalRatchet() const { return m_mii.finalGet(); } // Ratchet operations occur after each intermediate operation, except // possibly the first and last (see need{First,Final}Ratchet()). No actual // ratchet occurs after the final operation, but this means that both tvRef // and tvRef2 can contain references just after the final operation. Here we // pretend that a ratchet occurs after the final operation, i.e. a "logical" // ratchet. The reason for counting logical ratchets as part of the total is // the following case, in which the logical count is 0: // // (base is array) // BaseL // IssetElemL // no logical ratchet // // Following are a few more examples to make the algorithm clear: // // (base is array) (base is object) (base is object) // BaseL BaseL BaseL // ElemL ElemL CGetPropL // no ratchet ratchet logical ratchet // ElemL PropL // ratchet ratchet // ElemL CGetElemL // ratchet logical ratchet // IssetElemL // logical ratchet // // (base is array) // BaseL // ElemL // no ratchet // ElemL // ratchet // ElemL // logical ratchet // SetElemL // no ratchet unsigned HhbcTranslator::VectorTranslator::nLogicalRatchets() const { // If we've proven elsewhere that we don't need an MInstrState struct, we // know this translation won't need any ratchets if (!m_needMIS) return 0; unsigned ratchets = m_ni.immVecM.size(); if (!needFirstRatchet()) --ratchets; if (!needFinalRatchet()) --ratchets; return ratchets; } int HhbcTranslator::VectorTranslator::ratchetInd() const { return needFirstRatchet() ? int(m_mInd) : int(m_mInd) - 1; } } }