244d4da93a
Makes things more consistent, at least within this directory.
2607 linhas
87 KiB
C++
2607 linhas
87 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| 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<LdLocAddr>()->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<Opcode>();
|
|
}
|
|
}
|
|
|
|
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<SSATmp*>& 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>();
|
|
}
|
|
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>();
|
|
}
|
|
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>();
|
|
}
|
|
}
|
|
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<typename... Srcs>
|
|
SSATmp* HhbcTranslator::VectorTranslator::genStk(Opcode opc, IRTrace* taken,
|
|
Srcs... srcs) {
|
|
assert(opcodeHasFlags(opc, HasStackVersion));
|
|
assert(!opcodeHasFlags(opc, ModifiesStack));
|
|
std::vector<SSATmp*> 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 <bool warn, bool define>
|
|
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<TypedValue*>(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<false, false>(&key, mis);
|
|
}
|
|
|
|
TypedValue* baseGW(TypedValue key, MInstrState* mis) {
|
|
return baseGImpl<true, false>(&key, mis);
|
|
}
|
|
|
|
TypedValue* baseGD(TypedValue key, MInstrState* mis) {
|
|
return baseGImpl<false, true>(&key, mis);
|
|
}
|
|
|
|
TypedValue* baseGWD(TypedValue key, MInstrState* mis) {
|
|
return baseGImpl<true, true>(&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<TCA>(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 <MInstrAttr attrs, bool isObj>
|
|
static inline TypedValue* propImpl(Class* ctx, TypedValue* base,
|
|
TypedValue keyVal, MInstrState* mis) {
|
|
return Prop<WDU(attrs), isObj>(
|
|
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 <KeyType keyType, bool warn, bool define, bool reffy,
|
|
bool unset>
|
|
static inline TypedValue* elemImpl(TypedValue* base, TypedValue keyVal,
|
|
MInstrState* mis) {
|
|
TypedValue* key = keyPtr<keyType>(keyVal);
|
|
if (unset) {
|
|
return ElemU<keyType>(mis->tvScratch, mis->tvRef, base, key);
|
|
} else if (define) {
|
|
return ElemD<warn, reffy, keyType>(mis->tvScratch, mis->tvRef, base, key);
|
|
} else {
|
|
return Elem<warn, keyType>(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<keyType, WDRU(attrs)>(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 <KeyType keyType, bool isObj>
|
|
static inline TypedValue cGetPropImpl(Class* ctx, TypedValue* base,
|
|
TypedValue keyVal, MInstrState* mis) {
|
|
TypedValue* key = keyPtr<keyType>(keyVal);
|
|
TypedValue scratch;
|
|
TypedValue* result = Prop<true, false, false, isObj, keyType>(
|
|
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 <KeyType keyType, bool isObj>
|
|
static inline RefData* vGetPropImpl(Class* ctx, TypedValue* base,
|
|
TypedValue keyVal, MInstrState* mis) {
|
|
TypedValue* key = keyPtr<keyType>(keyVal);
|
|
TypedValue* result = HPHP::Prop<false, true, false, isObj, keyType>(
|
|
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 <bool useEmpty, bool isObj>
|
|
static inline bool issetEmptyPropImpl(Class* ctx, TypedValue* base,
|
|
TypedValue keyVal) {
|
|
return HPHP::IssetEmptyProp<useEmpty, isObj>(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 <bool isObj>
|
|
static inline void setPropImpl(Class* ctx, TypedValue* base,
|
|
TypedValue keyVal, Cell val) {
|
|
HPHP::SetProp<false, isObj>(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 <bool isObj>
|
|
static inline TypedValue setOpPropImpl(TypedValue* base, TypedValue keyVal,
|
|
Cell val, MInstrState* mis, SetOpOp op) {
|
|
TypedValue* result = HPHP::SetOpProp<isObj>(
|
|
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 <bool isObj>
|
|
static inline TypedValue incDecPropImpl(TypedValue* base, TypedValue keyVal,
|
|
MInstrState* mis, IncDecOp op) {
|
|
TypedValue result;
|
|
result.m_type = KindOfUninit;
|
|
HPHP::IncDecProp<true, isObj>(
|
|
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 <bool isObj>
|
|
static inline void bindPropImpl(Class* ctx, TypedValue* base, TypedValue keyVal,
|
|
RefData* val, MInstrState* mis) {
|
|
TypedValue* prop = HPHP::Prop<false, true, false, isObj>(
|
|
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 <bool isObj>
|
|
static inline void unsetPropImpl(Class* ctx, TypedValue* base,
|
|
TypedValue keyVal) {
|
|
HPHP::UnsetProp<isObj>(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<KeyType keyType, bool checkForInt>
|
|
static inline TypedValue arrayGetImpl(
|
|
ArrayData* a, typename KeyTypeTraits<keyType>::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<keyType, checkForInt>(a, keyAsRaw<keyType>(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<KeyType keyType>
|
|
static inline TypedValue mapGetImpl(
|
|
c_Map* map, typename KeyTypeTraits<keyType>::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<keyType>(map, keyAsRaw<keyType>(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<KeyType keyType>
|
|
static inline TypedValue stableMapGetImpl(
|
|
c_StableMap* map, typename KeyTypeTraits<keyType>::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<keyType>(map, keyAsRaw<keyType>(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 <KeyType keyType>
|
|
static inline TypedValue cGetElemImpl(TypedValue* base, TypedValue keyVal,
|
|
MInstrState* mis) {
|
|
TypedValue* key = keyPtr<keyType>(keyVal);
|
|
TypedValue scratch;
|
|
TypedValue* result = Elem<true, keyType>(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 <KeyType keyType>
|
|
static inline RefData* vGetElemImpl(TypedValue* base, TypedValue keyVal,
|
|
MInstrState* mis) {
|
|
TypedValue* key = keyPtr<keyType>(keyVal);
|
|
TypedValue* result = HPHP::ElemD<false, true, keyType>(
|
|
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 <KeyType keyType, bool isEmpty>
|
|
static inline bool issetEmptyElemImpl(TypedValue* base, TypedValue keyVal,
|
|
MInstrState* mis) {
|
|
TypedValue* key = keyPtr<keyType>(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<isEmpty, false, keyType>(
|
|
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<KeyType keyType, bool checkForInt>
|
|
static inline uint64_t arrayIssetImpl(
|
|
ArrayData* a, typename KeyTypeTraits<keyType>::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<keyType, checkForInt>(a, keyAsRaw<keyType>(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<KeyType keyType>
|
|
static inline uint64_t mapIssetImpl(
|
|
c_Map* map, typename KeyTypeTraits<keyType>::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<keyType>(map, keyAsRaw<keyType>(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<KeyType keyType>
|
|
static inline uint64_t stableMapIssetImpl(
|
|
c_StableMap* map, typename KeyTypeTraits<keyType>::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<keyType>(map, keyAsRaw<keyType>(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<KeyType keyType, bool checkForInt, bool setRef>
|
|
static inline typename ShuffleReturn<setRef>::return_type arraySetImpl(
|
|
ArrayData* a, typename KeyTypeTraits<keyType>::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<setRef>(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<setRef>::return_type \
|
|
nm(ArrayData* a, TypedValue* key, TypedValue value, RefData* ref) { \
|
|
return arraySetImpl<keyType, checkForInt, setRef>( \
|
|
a, keyAsRaw<keyType>(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<false, false>(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<KeyType keyType>
|
|
static inline void mapSetImpl(
|
|
c_Map* map,
|
|
typename KeyTypeTraits<keyType>::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<keyType>(map, keyAsRaw<keyType>(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<KeyType keyType>
|
|
static inline void stableMapSetImpl(
|
|
c_StableMap* map,
|
|
typename KeyTypeTraits<keyType>::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<keyType>(map, keyAsRaw<keyType>(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 <KeyType keyType>
|
|
static inline StringData* setElemImpl(TypedValue* base, TypedValue keyVal,
|
|
Cell val) {
|
|
TypedValue* key = keyPtr<keyType>(keyVal);
|
|
return HPHP::SetElem<false, keyType>(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 <SetOpOp op>
|
|
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 <IncDecOp op>
|
|
static inline TypedValue incDecElemImpl(TypedValue* base, TypedValue keyVal,
|
|
MInstrState* mis) {
|
|
TypedValue result;
|
|
HPHP::IncDecElem<true>(
|
|
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<false, true>(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 <KeyType keyType>
|
|
static inline void unsetElemImpl(TypedValue* base, TypedValue keyVal) {
|
|
TypedValue* key = keyPtr<keyType>(keyVal);
|
|
HPHP::UnsetElem<keyType>(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<SSATmp*> 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;
|
|
}
|
|
|
|
} }
|