Arquivos
hhvm/hphp/runtime/vm/translator/translator-x64-vector.cpp
T
mwilliams 6df0912bc0 Get rid of baseStrOff
It was unnecessary book keeping, and caused us to differ
from zend's behavior
2013-04-18 12:19:35 -07:00

3279 linhas
126 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- 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 <runtime/base/complex_types.h>
#include <util/asm-x64.h>
#include <runtime/vm/translator/translator-deps.h>
#include <runtime/vm/translator/translator-inline.h>
#include <runtime/vm/translator/translator-runtime.h>
#include <runtime/vm/translator/translator-x64.h>
#include <runtime/vm/member_operations.h>
#include <runtime/vm/stats.h>
#include <runtime/base/shared/shared_map.h>
#include <runtime/vm/translator/translator-x64-internal.h>
#include <memory>
namespace HPHP {
namespace VM {
namespace Transl {
/*
* Translator for vector instructions.
*/
struct MVecTransState {
MVecTransState()
: baseType(KindOfUninit)
, needsMIS(true)
{}
bool isKnown() { return baseType != KindOfUninit; }
bool isObj() { return baseType == KindOfObject; }
void setObj() { baseType = KindOfObject; }
void resetBase() { baseType = KindOfUninit; }
bool needsMIState() { return needsMIS; }
void setNoMIState() { needsMIS = false; }
private:
/* This stores the type of the current value in rBase. If it's !=
* KindOfUninit then the register will hold the value itself instead of a
* TypedValue* */
DataType baseType;
/* Many vector instructions can be executed without using an MInstrState
* struct - so we avoid the memory traffic in those cases. */
bool needsMIS;
};
/* To ensure that we don't emit code to access an MInstrState struct when we
* won't have one on the stack, access to rsp and offsets within MInstrState is
* always done through these macros. */
template<typename T>
static inline T misAssertHelper(T value, bool condition) {
assert(condition);
return value;
}
#undef MISOFF
#define MISOFF(memb) \
misAssertHelper(offsetof(MInstrState, memb), m_vecState->needsMIState())
static const PhysReg unsafe_rsp = rsp;
#define rsp
#define mis_rsp misAssertHelper(unsafe_rsp, m_vecState->needsMIState())
/* The next group of macros are used for passing a DynLocation as an argument
* to a function (look for EMIT_CALL or EMIT_RCALL to see usage). Literals,
* non-literal ints, non-literal strings, and generic TypedValues are all
* supported. */
// TypedValue member location: fully generic
#define TVML(dl) \
(Stats::emitInc(a, Stats::Tx64_MTVKey), A(dl.location))
// Literal member location: takes a statement to use if the loc isn't a literal
#define LITML(dl, argElse) \
IE(dl.isLiteral(), \
(Stats::emitInc(a, Stats::Tx64_MLitKey), IMM(dl.rtt.valueGeneric())), \
argElse)
// Takes a location and a condition. If the condition is true the value of the
// location will be passed in a register
#define VALML(dl, cond) \
IE((cond) && !dl.isRef(), \
(Stats::emitInc(a, Stats::Tx64_MRegKey), \
m_regMap.allocInputReg(dl), \
V(dl.location)), \
TVML(dl))
// Literals and non-literal strings
#define SML(dl) \
LITML(dl, VALML(dl, IS_STRING_TYPE(dl.valueType())))
// Literals and non-literal integers
#define IML(dl) \
LITML(dl, VALML(dl, dl.valueType() == KindOfInt64))
// Literals and non-literals ints and strings
#define ISML(dl) \
LITML(dl, VALML(dl, dl.valueType() == KindOfInt64 || \
IS_STRING_TYPE(dl.valueType())))
#define PREP_CTX(pr) Class* ctx = arGetContextClass(curFrame());
#define CTX() IMM(uintptr_t(ctx))
#define PREP_RESULT(useTvR) \
if (!useTvR) { \
m_regMap.cleanSmashLoc(result.location); \
ScratchReg rScratch(m_regMap); \
a. lea_reg64_disp_reg64(rVmSp, vstackOffset(ni, mResultStackOffset(ni)), \
r(rScratch)); \
emitStoreUninitNull(a, 0, r(rScratch)); \
}
#define RESULT(useTvR) \
IE((useTvR), \
RPLUS(mis_rsp, MISOFF(tvResult)), \
A(result.location))
#define PREP_VAL(useRVal, pr) \
LazyScratchReg rVal(m_regMap); \
if (useRVal) { \
rVal.alloc(m_regMap.regIsFree(pr) ? pr : InvalidReg); \
PhysReg reg; \
int disp; \
locToRegDisp(val.location, &reg, &disp); \
a. loadq(reg[disp + TVOFF(m_data)], r(rVal)); \
if (auto offset = RefData::tvOffset()) a.addq(offset, r(rVal)); \
}
#define VAL(useRVal) \
IE((useRVal), \
R(rVal), \
A(val.location))
static const MInstrAttr Warn = MIA_warn;
static const MInstrAttr Unset = MIA_unset;
static const MInstrAttr Reffy = MIA_reffy;
static const MInstrAttr Define = MIA_define;
static const MInstrAttr None = MIA_none;
static const MInstrAttr WarnDefine = MInstrAttr(Warn | Define);
static const MInstrAttr DefineReffy = MInstrAttr(Define | Reffy);
static const MInstrAttr WarnDefineReffy = MInstrAttr(Warn | Define | Reffy);
#define WDU(attrs) attrs & Warn, attrs & Define, attrs & Unset
#define WDRU(attrs) attrs & Warn, attrs & Define, attrs & Reffy, attrs & Unset
/* The next bunch of macros and functions are used to build up tables of helper
* function pointers and determine which helper should be called based on a
* variable number of bool and enum arguments. */
template<typename T> constexpr unsigned bitWidth() {
static_assert(IncDec_invalid == 4,
"IncDecOp enum must fit in 2 bits");
static_assert(SetOp_invalid == 11,
"SetOpOp enum must fit in 4 bits");
return std::is_same<T, bool>::value ? 1
: std::is_same<T, KeyType>::value ? 2
: std::is_same<T, MInstrAttr>::value ? 4
: std::is_same<T, IncDecOp>::value ? 2
: std::is_same<T, SetOpOp>::value ? 4
: sizeof(T) * CHAR_BIT;
}
// Determines the width in bits of all of its arguments
template<typename... T> unsigned multiBitWidth();
template<typename T, typename... Args>
inline unsigned multiBitWidth(T t, Args... args) {
return bitWidth<T>() + multiBitWidth<Args...>(args...);
}
template<>
inline unsigned multiBitWidth() {
return 0;
}
// Given the same arguments as multiBitWidth, buildBitmask will determine which
// index in the table corresponds to the provided parameters.
template<unsigned bit>
inline unsigned buildBitmask() {
static_assert(bit < (sizeof(unsigned) * CHAR_BIT - 1), "Too many bits");
return 0;
}
template<unsigned bit = 0, typename T, typename... Args>
inline unsigned buildBitmask(T c, Args... args) {
unsigned bits = (unsigned)c & ((1u << bitWidth<T>()) - 1);
return buildBitmask<bit + bitWidth<T>()>(args...) | bits << bit;
}
#define FILL_ROW(nm, ...) do { \
OpFunc* dest = &optab[buildBitmask(__VA_ARGS__)]; \
assert(*dest == nullptr); \
*dest = nm; \
} while (false);
#define FILL_ROWH(nm, hot, ...) FILL_ROW(nm, __VA_ARGS__)
#define BUILD_OPTAB(...) BUILD_OPTAB_ARG(HELPER_TABLE(FILL_ROW), __VA_ARGS__)
#define BUILD_OPTABH(...) BUILD_OPTAB_ARG(HELPER_TABLE(FILL_ROWH), __VA_ARGS__)
#define BUILD_OPTAB_ARG(FILL_TABLE, ...) \
static OpFunc* optab = nullptr; \
if (!optab) { \
optab = (OpFunc*)calloc(1 << multiBitWidth(__VA_ARGS__), sizeof(OpFunc)); \
FILL_TABLE \
} \
unsigned idx = buildBitmask(__VA_ARGS__); \
OpFunc opFunc = optab[idx]; \
always_assert(opFunc);
// The getKeyType family of functions determine the KeyType to be used as a
// template argument to helper functions. S, IS, or I at the end of the
// function names signals that the caller supports non-literal strings, int, or
// both, respectively.
static KeyType getKeyType(const DynLocation& dl, bool nonLitStr,
bool nonLitInt) {
if (dl.isRef()) {
// Variants can change types at arbitrary times, so don't try to
// pass them in registers.
return AnyKey;
}
if ((dl.isLiteral() || nonLitStr) && IS_STRING_TYPE(dl.valueType())) {
return StrKey;
} else if ((dl.isLiteral() || nonLitInt) && dl.valueType() == KindOfInt64) {
return IntKey;
} else {
assert(dl.isStack() || dl.isLocal());
return AnyKey;
}
}
inline static KeyType getKeyType(const DynLocation& dl) {
return getKeyType(dl, false, false);
}
inline static KeyType getKeyTypeS(const DynLocation& dl) {
return getKeyType(dl, true, false);
}
inline static KeyType getKeyTypeIS(const DynLocation& dl) {
return getKeyType(dl, true, true);
}
int TranslatorX64::mResultStackOffset(const NormalizedInstruction& ni) const {
int stackDest = 0 - int(sizeof(Cell));
for (unsigned i = 0; i < ni.inputs.size(); ++i) {
const DynLocation& input = *ni.inputs[i];
if (input.location.isStack()) {
stackDest += int(sizeof(Cell));
}
}
return stackDest;
}
bool TranslatorX64::generateMVal(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) const {
if (mii.valCount() == 1) {
const DynLocation& input = *ni.inputs[0];
// Some instruction sequences, e.g. CGetL..SetM..PopC are optimized during
// analysis to avoid the push/pop, in which case the input is a local
// instead of a stack, and no val results from executing the VM instruction.
if (input.isStack() && ni.outStack != nullptr) {
return true;
}
} else if (mii.instr() == MI_IncDecM) {
if (ni.outStack != nullptr) {
return true;
}
}
return false;
}
bool TranslatorX64::inputIsLiveForFinalOp(const NormalizedInstruction& ni,
unsigned i,
const MInstrInfo& mii) const {
// It might be live if it's the final input (the last key)
if (i == ni.inputs.size() - 1) {
return true;
}
// Or if this is a SetOp and it's the first input
if (mii.instr() == MI_SetOpM && i == 0) {
return true;
}
return false;
}
int TranslatorX64::firstDecrefInput(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) const {
return logicalTeleportMVal(t, ni, mii) ? 1 : 0;
}
bool TranslatorX64::logicalTeleportMVal(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) const {
return ((mii.instr() == MI_SetM || mii.instr() == MI_BindM)
&& generateMVal(t, ni, mii));
}
bool TranslatorX64::teleportMVal(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) const {
if (logicalTeleportMVal(t, ni, mii)) {
const DynLocation& input = *ni.inputs[0];
const DynLocation& output = *ni.outStack;
// No teleport is required if the only stack input is the result, because
// the result ends up in the same place as the input.
if (input.location != output.location) {
return true;
}
}
return false;
}
bool TranslatorX64::useTvResult(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) const {
// Some of the *M instructions store a result to the VM stack, which may be
// at the same location as an input. If an input and an output are overlaid,
// it is possible to write the result directly to its final location if the
// overlaid input is not refcounted (won't be decref'ed by emitMPost()).
// Otherwise the result must be temporarily stored to tvResult, then copied
// to its final location after inputs have been discarded.
const DynLocation& output = *ni.outStack;
if (&output == nullptr) {
SKTRACE(2, ni.source, "%s (no stack output) --> false\n", __func__);
return false;
}
assert(output.location.isStack());
if (mii.instr() == MI_SetM || mii.instr() == MI_BindM) {
SKTRACE(2, ni.source, "%s (%s val) --> false\n",
__func__, mii.instr() == MI_SetM ? "SetM" : "BindM");
return false;
}
int bottomI = 0;
const DynLocation* bottomStack = nullptr;
for (unsigned i = firstDecrefInput(t, ni, mii); i < ni.inputs.size(); ++i) {
const DynLocation& input = *ni.inputs[i];
// Only the bottommost stack input can possibly overlay the
// output, but that might not be the first stack input in
// ni.inputs. Walk through all stack inputs to find the bottommost
// one.
if (input.location.isStack() &&
(!bottomStack ||
input.location.offset < bottomStack->location.offset)) {
bottomStack = &input;
bottomI = i;
}
}
if (bottomStack) {
assert(bottomStack->location == output.location);
// Use tvResult if the overlaid input is refcounted, or if it
// might be used during the final operation (in which case it may
// still be in use at the time the result is written).
bool result = IS_REFCOUNTED_TYPE(bottomStack->outerType())
|| inputIsLiveForFinalOp(ni, bottomI, mii);
SKTRACE(2, ni.source, "%s input %u/[0..%u) --> %s\n",
__func__, bottomI, ni.inputs.size(), result ? "true" : "false");
return result;
}
// No stack inputs. If we were to write the result directly to the VM stack,
// it would potentially be clobbered by reentry during cleanup, e.g. for
// ArrayAccess-related destruction. For now we make the very conservative
// assumption that any instruction with statically unknown offsets can
// reenter.
bool result = mInstrHasUnknownOffsets(ni);
SKTRACE(2, ni.source, "%s (no stack inputs) --> %s\n",
__func__, result ? "true" : "false");
return result;
}
void TranslatorX64::emitBaseLCR(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned iInd,
LazyScratchReg& rBase) {
LocationCode lCode = ni.immVec.locationCode();
const MInstrAttr& mia = mii.getAttr(lCode);
SKTRACE(2, ni.source, "%s %#lx %s%s\n", __func__, long(a.code.frontier),
(mia & MIA_warn) ? "W" : "", (mia & MIA_define) ? "D" : "");
const DynLocation& base = *ni.inputs[iInd];
if (mia & MIA_warn) {
if (base.rtt.isUninit()) {
const StringData* name = local_name(base.location);
EMIT_RCALL(a, ni, raiseUndefVariable, IMM((uintptr_t)name));
}
}
if (mia & MIA_define) {
if (base.rtt.isUninit()) {
emitStoreNull(a, base.location);
}
}
const bool canEnregisterObj =
base.isObject() &&
mcodeMaybePropName(ni.immVecM[0]);
if (canEnregisterObj) {
m_regMap.allocInputReg(ni, iInd);
auto curReg = getReg(base);
m_regMap.cleanSmashReg(curReg);
rBase.alloc(curReg);
m_vecState->setObj();
} else {
PhysReg pr;
int disp;
locToRegDisp(base.location, &pr, &disp);
rBase.alloc();
m_regMap.cleanSmashLoc(base.location);
if (base.isRef()) {
// Get inner value.
a. loadq(pr[disp + TVOFF(m_data)], r(rBase));
if (auto offset = RefData::tvOffset()) a.addq (offset, r(rBase));
} else {
a. lea_reg64_disp_reg64(pr, disp, r(rBase));
}
}
}
void TranslatorX64::emitBaseH(unsigned iInd, LazyScratchReg& rBase) {
m_regMap.allocInputReg(*m_curNI, iInd);
const Location& l = m_curNI->inputs[iInd]->location;
PhysReg r = getReg(l);
m_regMap.smashReg(r);
rBase.alloc(r);
m_vecState->setObj();
}
template <bool warn, bool define>
static inline TypedValue* baseNImpl(TypedValue* key,
MInstrState* mis,
ActRec* fp) {
TypedValue* base;
StringData* name = prepareKey(key);
const Func* func = fp->m_func;
Id id = func->lookupVarId(name);
if (id != kInvalidId) {
base = frame_local(fp, id);
} else {
assert(!fp->hasInvName());
if (define) {
if (fp->m_varEnv == nullptr) {
fp->m_varEnv = VarEnv::createLazyAttach(fp);
}
base = fp->m_varEnv->lookup(name);
if (base == nullptr) {
if (warn) {
raise_notice(Strings::UNDEFINED_VARIABLE, name->data());
}
TypedValue tv;
tvWriteNull(&tv);
fp->m_varEnv->set(name, &tv);
base = fp->m_varEnv->lookup(name);
}
} else {
if (fp->m_varEnv == nullptr || (base = fp->m_varEnv->lookup(name)) == nullptr) {
if (warn) {
raise_notice(Strings::UNDEFINED_VARIABLE, name->data());
}
tvWriteNull(&mis->tvScratch);
base = &mis->tvScratch;
}
}
}
decRefStr(name);
if (base->m_type == KindOfRef) {
base = base->m_data.pref->tv();
}
return base;
}
static TypedValue* baseN(TypedValue* key, MInstrState* mis) {
DECLARE_FRAME_POINTER(framePtr);
return baseNImpl<false, false>(key, mis, (ActRec*)framePtr->m_savedRbp);
}
static TypedValue* baseNW(TypedValue* key, MInstrState* mis) {
DECLARE_FRAME_POINTER(framePtr);
return baseNImpl<true, false>(key, mis, (ActRec*)framePtr->m_savedRbp);
}
static TypedValue* baseND(TypedValue* key, MInstrState* mis) {
DECLARE_FRAME_POINTER(framePtr);
return baseNImpl<false, true>(key, mis, (ActRec*)framePtr->m_savedRbp);
}
static TypedValue* baseNWD(TypedValue* key, MInstrState* mis) {
DECLARE_FRAME_POINTER(framePtr);
return baseNImpl<true, true>(key, mis, (ActRec*)framePtr->m_savedRbp);
}
void TranslatorX64::emitBaseN(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned iInd,
LazyScratchReg& rBase) {
LocationCode lCode = ni.immVec.locationCode();
const MInstrAttr& mia = mii.getAttr(lCode);
SKTRACE(2, ni.source, "%s %#lx %s%s\n",
__func__, long(a.code.frontier),
(mia & MIA_warn) ? "W" : "", (mia & MIA_define) ? "D" : "");
typedef TypedValue* (*BaseNOp)(TypedValue*, MInstrState*);
static_assert(MIA_warn == 0x1 && MIA_define == 0x2,
"MIA_* bitmask values were not as expected");
static const BaseNOp baseNOps[] = {baseN, baseNW, baseND, baseNWD};
assert((mia & MIA_base) < sizeof(baseNOps)/sizeof(BaseNOp));
BaseNOp baseNOp = baseNOps[mia & MIA_base];
const DynLocation& base = *ni.inputs[iInd];
m_regMap.cleanSmashLoc(base.location);
EMIT_RCALL(a, ni, baseNOp, A(base.location), R(mis_rsp));
rBase.alloc(rax);
}
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 != nullptr);
base = varEnv->lookup(name);
if (base == nullptr) {
if (warn) {
raise_notice(Strings::UNDEFINED_VARIABLE, name->data());
}
if (define) {
TypedValue tv;
tvWriteNull(&tv);
varEnv->set(name, &tv);
base = varEnv->lookup(name);
} else {
tvWriteNull(&mis->tvScratch);
base = &mis->tvScratch;
}
}
decRefStr(name);
if (base->m_type == KindOfRef) {
base = base->m_data.pref->tv();
}
return base;
}
static TypedValue* baseG(TypedValue* key, MInstrState* mis) {
return baseGImpl<false, false>(key, mis);
}
static TypedValue* baseGW(TypedValue* key, MInstrState* mis) {
return baseGImpl<true, false>(key, mis);
}
static TypedValue* baseGD(TypedValue* key, MInstrState* mis) {
return baseGImpl<false, true>(key, mis);
}
static TypedValue* baseGWD(TypedValue* key, MInstrState* mis) {
return baseGImpl<true, true>(key, mis);
}
void TranslatorX64::emitBaseG(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned iInd,
LazyScratchReg& rBase) {
LocationCode lCode = ni.immVec.locationCode();
const MInstrAttr& mia = mii.getAttr(lCode);
SKTRACE(2, ni.source, "%s %#lx %s%s\n", __func__, long(a.code.frontier),
(mia & MIA_warn) ? "W" : "", (mia & MIA_define) ? "D" : "");
typedef TypedValue* (*BaseGOp)(TypedValue*, MInstrState*);
static_assert(MIA_warn == 0x1 && MIA_define == 0x2,
"MIA_* bitmask values were not as expected");
static const BaseGOp baseGOps[] = {baseG, baseGW, baseGD, baseGWD};
assert((mia & MIA_base) < sizeof(baseGOps)/sizeof(BaseGOp));
BaseGOp baseGOp = baseGOps[mia & MIA_base];
const DynLocation& base = *ni.inputs[iInd];
m_regMap.cleanSmashLoc(base.location);
EMIT_RCALL(a, ni, baseGOp, A(base.location), R(mis_rsp));
rBase.alloc(rax);
}
HOT_FUNC_VM
static TypedValue* baseS(Class* ctx, TypedValue* key, const Class* cls,
MInstrState* mis) {
TypedValue* base;
StringData* name = prepareKey(key);
bool visible, accessible;
base = cls->getSProp(ctx, name, visible, accessible);
if (!(visible && accessible)) {
raise_error("Invalid static property access: %s::%s",
cls->name()->data(), name->data());
}
decRefStr(name);
if (base->m_type == KindOfRef) {
base = base->m_data.pref->tv();
}
return base;
}
HOT_FUNC_VM
static TypedValue* baseSClsRef(Class* ctx, TypedValue* key,
TypedValue* clsRef,
MInstrState* mis) {
assert(clsRef->m_type == KindOfClass);
const Class* cls = clsRef->m_data.pcls;
return baseS(ctx, key, cls, mis);
}
void TranslatorX64::emitBaseS(const Tracelet& t,
const NormalizedInstruction& ni, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx\n", __func__, long(a.code.frontier));
const DynLocation& key = *ni.inputs[iInd];
m_regMap.cleanSmashLoc(key.location);
const int kClassIdx = ni.inputs.size() - 1;
const DynLocation& clsRef = *ni.inputs[kClassIdx];
assert(clsRef.valueType() == KindOfClass);
const Class* cls = clsRef.rtt.valueClass();
const bool uniqueKnownClass =
cls != nullptr &&
(cls->preClass()->attrs() & AttrUnique);
const bool canUseCache =
cls &&
key.isString() &&
key.rtt.valueString() &&
curFunc()->cls() == cls;
// Emit the appropriate helper call.
if (canUseCache) {
SKTRACE(3, ni.source, "%s using SPropCache\n", __func__);
rBase.alloc();
emitStaticPropInlineLookup(*m_curNI, kClassIdx, key, r(rBase));
} else if (uniqueKnownClass && RuntimeOption::RepoAuthoritative) {
SKTRACE(3, ni.source, "%s using uniqueKnownClass\n", __func__);
PREP_CTX(argNumToRegName[0]);
EMIT_RCALL(a, ni, baseS,
CTX(),
A(key.location),
IMM(uintptr_t(cls)),
R(mis_rsp));
rBase.alloc(rax);
} else {
SKTRACE(3, ni.source, "%s using generic\n", __func__);
PREP_CTX(argNumToRegName[0]);
m_regMap.cleanSmashLoc(clsRef.location);
EMIT_RCALL(a, ni, baseSClsRef,
CTX(),
A(key.location),
A(clsRef.location),
R(mis_rsp));
rBase.alloc(rax);
}
}
void TranslatorX64::emitBaseOp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned iInd,
LazyScratchReg& rBase) {
LocationCode lCode = ni.immVec.locationCode();
switch (lCode) {
case LL: case LC: case LR: emitBaseLCR(t, ni, mii, iInd, rBase); break;
case LH: emitBaseH(iInd, rBase); break;
case LGL: case LGC: emitBaseG(t, ni, mii, iInd, rBase); break;
case LNL: case LNC: emitBaseN(t, ni, mii, iInd, rBase); break;
case LSL: case LSC: emitBaseS(t, ni, iInd, rBase); break;
default: not_reached();
}
}
template<KeyType kt, bool isRef>
static inline TypedValue* unbox(TypedValue* k) {
if (isRef) {
if (kt == AnyKey) {
assert(k->m_type == KindOfRef);
k = k->m_data.pref->tv();
assert(k->m_type != KindOfRef);
} else {
assert(k->m_type == keyDataType(kt) ||
(IS_STRING_TYPE(k->m_type) && IS_STRING_TYPE(keyDataType(kt))));
return reinterpret_cast<TypedValue*>(k->m_data.num);
}
} else if (kt == AnyKey) {
assert(k->m_type != KindOfRef);
}
return k;
}
template <KeyType keyType, bool unboxKey, bool warn, bool define, bool reffy,
bool unset>
static inline TypedValue* elemImpl(TypedValue* base, TypedValue* key,
MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
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 key unboxKey attrs */ \
m(elemC, , AnyKey, false, None) \
m(elemCD, , AnyKey, false, Define) \
m(elemCDR, , AnyKey, false, DefineReffy) \
m(elemCU, , AnyKey, false, Unset) \
m(elemCW, , AnyKey, false, Warn) \
m(elemCWD, , AnyKey, false, WarnDefine) \
m(elemCWDR, , AnyKey, false, WarnDefineReffy) \
m(elemI, , IntKey, false, None) \
m(elemID, HOT_FUNC_VM, IntKey, false, Define) \
m(elemIDR, , IntKey, false, DefineReffy) \
m(elemIU, , IntKey, false, Unset) \
m(elemIW, , IntKey, false, Warn) \
m(elemIWD, , IntKey, false, WarnDefine) \
m(elemIWDR, , IntKey, false, WarnDefineReffy) \
m(elemL, , AnyKey, true, None) \
m(elemLD, , AnyKey, true, Define) \
m(elemLDR, , AnyKey, true, DefineReffy) \
m(elemLU, , AnyKey, true, Unset) \
m(elemLW, , AnyKey, true, Warn) \
m(elemLWD, , AnyKey, true, WarnDefine) \
m(elemLWDR, , AnyKey, true, WarnDefineReffy) \
m(elemLI, , IntKey, true, None) \
m(elemLID, , IntKey, true, Define) \
m(elemLIDR, , IntKey, true, DefineReffy) \
m(elemLIU, , IntKey, true, Unset) \
m(elemLIW, , IntKey, true, Warn) \
m(elemLIWD, , IntKey, true, WarnDefine) \
m(elemLIWDR, , IntKey, true, WarnDefineReffy) \
m(elemLS, , StrKey, true, None) \
m(elemLSD, , StrKey, true, Define) \
m(elemLSDR, , StrKey, true, DefineReffy) \
m(elemLSU, , StrKey, true, Unset) \
m(elemLSW, , StrKey, true, Warn) \
m(elemLSWD, , StrKey, true, WarnDefine) \
m(elemLSWDR, , StrKey, true, WarnDefineReffy) \
m(elemS, HOT_FUNC_VM, StrKey, false, None) \
m(elemSD, HOT_FUNC_VM, StrKey, false, Define) \
m(elemSDR, , StrKey, false, DefineReffy) \
m(elemSU, , StrKey, false, Unset) \
m(elemSW, HOT_FUNC_VM, StrKey, false, Warn) \
m(elemSWD, , StrKey, false, WarnDefine) \
m(elemSWDR, , StrKey, false, WarnDefineReffy)
#define ELEM(nm, hot, keyType, unboxKey, attrs) \
hot \
static TypedValue* nm(TypedValue* base, TypedValue* key, \
MInstrState* mis) { \
return elemImpl<keyType, unboxKey, WDRU(attrs)>(base, key, mis); \
}
HELPER_TABLE(ELEM)
#undef ELEM
void TranslatorX64::emitElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, LazyScratchReg& rBase) {
MemberCode mCode = ni.immVecM[mInd];
MInstrAttr mia = MInstrAttr(mii.getAttr(mCode) & MIA_intermediate);
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u flags %x\n",
__func__, long(a.code.frontier), mInd, iInd, mia);
const DynLocation& memb = *ni.inputs[iInd];
typedef TypedValue* (*OpFunc)(TypedValue*, TypedValue*,
MInstrState*);
BUILD_OPTABH(getKeyTypeIS(memb), memb.isRef(), mia);
EMIT_RCALL(a, ni, opFunc, R(rBase), ISML(memb), R(mis_rsp));
rBase.realloc(rax);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, MInstrAttr attrs, bool isObj>
static inline TypedValue* propImpl(Class* ctx, TypedValue* base,
TypedValue* key,
MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
return Prop<WDU(attrs), isObj, keyType>(
mis->tvScratch, mis->tvRef, ctx, base, key);
}
#define HELPER_TABLE(m) \
/* name key unboxKey attrs isObj */ \
m(propC, AnyKey, false, None, false) \
m(propCD, AnyKey, false, Define, false) \
m(propCDO, AnyKey, false, Define, true) \
m(propCO, AnyKey, false, None, true) \
m(propCU, AnyKey, false, Unset, false) \
m(propCUO, AnyKey, false, Unset, true) \
m(propCW, AnyKey, false, Warn, false) \
m(propCWD, AnyKey, false, WarnDefine, false) \
m(propCWDO, AnyKey, false, WarnDefine, true) \
m(propCWO, AnyKey, false, Warn, true) \
m(propL, AnyKey, true, None, false) \
m(propLD, AnyKey, true, Define, false) \
m(propLDO, AnyKey, true, Define, true) \
m(propLO, AnyKey, true, None, true) \
m(propLU, AnyKey, true, Unset, false) \
m(propLUO, AnyKey, true, Unset, true) \
m(propLW, AnyKey, true, Warn, false) \
m(propLWD, AnyKey, true, WarnDefine, false) \
m(propLWDO, AnyKey, true, WarnDefine, true) \
m(propLWO, AnyKey, true, Warn, true) \
m(propLS, StrKey, true, None, false) \
m(propLSD, StrKey, true, Define, false) \
m(propLSDO, StrKey, true, Define, true) \
m(propLSO, StrKey, true, None, true) \
m(propLSU, StrKey, true, Unset, false) \
m(propLSUO, StrKey, true, Unset, true) \
m(propLSW, StrKey, true, Warn, false) \
m(propLSWD, StrKey, true, WarnDefine, false) \
m(propLSWDO, StrKey, true, WarnDefine, true) \
m(propLSWO, StrKey, true, Warn, true) \
m(propS, StrKey, false, None, false) \
m(propSD, StrKey, false, Define, false) \
m(propSDO, StrKey, false, Define, true) \
m(propSO, StrKey, false, None, true) \
m(propSU, StrKey, false, Unset, false) \
m(propSUO, StrKey, false, Unset, true) \
m(propSW, StrKey, false, Warn, false) \
m(propSWD, StrKey, false, WarnDefine, false) \
m(propSWDO, StrKey, false, WarnDefine, true) \
m(propSWO, StrKey, false, Warn, true)
#define PROP(nm, ...) \
static TypedValue* nm(Class* ctx, TypedValue* base, TypedValue* key, \
MInstrState* mis) { \
return propImpl<__VA_ARGS__>(ctx, base, key, mis); \
}
HELPER_TABLE(PROP)
#undef PROP
void TranslatorX64::emitPropGeneric(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
MemberCode mCode = ni.immVecM[mInd];
MInstrAttr mia = MInstrAttr(mii.getAttr(mCode) & MIA_intermediate_prop);
const DynLocation& memb = *ni.inputs[iInd];
PREP_CTX(argNumToRegName[0]);
// Emit the appropriate helper call.
Stats::emitInc(a, Stats::PropAsm_Generic);
typedef TypedValue* (*OpFunc)(Class*, TypedValue*, TypedValue*,
MInstrState*);
BUILD_OPTAB(getKeyTypeS(memb), memb.isRef(), mia, m_vecState->isObj());
EMIT_RCALL(a, ni, opFunc, CTX(),
R(rBase),
SML(memb),
R(mis_rsp));
rBase.realloc(rax);
m_vecState->resetBase();
}
#undef HELPER_TABLE
PropInfo getPropertyOffset(const NormalizedInstruction& ni,
const Class*& baseClass,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd) {
if (mInd == 0) {
auto const baseIndex = mii.valCount();
baseClass = ni.inputs[baseIndex]->rtt.isObject()
? ni.inputs[baseIndex]->rtt.valueClass()
: nullptr;
} else {
baseClass = ni.immVecClasses[mInd - 1];
}
if (!baseClass) return PropInfo();
if (!ni.inputs[iInd]->rtt.isString()) {
return PropInfo();
}
auto* const name = ni.inputs[iInd]->rtt.valueString();
if (!name) return PropInfo();
bool accessible;
Class* ctx = curFunc()->cls();
// If we are not in repo-authoriative mode, we need to check that
// baseClass cannot change in between requests
if (!RuntimeOption::RepoAuthoritative ||
!(baseClass->preClass()->attrs() & AttrUnique)) {
if (!ctx) return PropInfo();
if (!ctx->classof(baseClass)) {
if (baseClass->classof(ctx)) {
// baseClass can change on us in between requests, but since
// ctx is an ancestor of baseClass we can make the weaker
// assumption that the object is an instance of ctx
baseClass = ctx;
} else {
// baseClass can change on us in between requests and it is
// not related to ctx, so bail out
return PropInfo();
}
}
}
// Lookup the index of the property based on ctx and baseClass
Slot idx = baseClass->getDeclPropIndex(ctx, name, accessible);
// If we couldn't find a property that is accessible in the current
// context, bail out
if (idx == kInvalidSlot || !accessible) {
return PropInfo();
}
// If it's a declared property we're good to go: even if a subclass
// redefines an accessible property with the same name it's guaranteed
// to be at the same offset
return PropInfo(
baseClass->declPropOffset(idx),
baseClass->declPropHphpcType(idx)
);
}
PropInfo getFinalPropertyOffset(const NormalizedInstruction& ni,
const MInstrInfo& mii) {
unsigned mInd = ni.immVecM.size() - 1;
unsigned iInd = mii.valCount() + 1 + mInd;
const Class* cls = nullptr;
return getPropertyOffset(ni, cls, mii, mInd, iInd);
}
void TranslatorX64::emitPropSpecialized(MInstrAttr const mia,
const Class* baseClass,
int propOffset,
unsigned mInd,
unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, m_curNI->source, "%s class=%s offset=%d\n",
__func__, baseClass->nameRef()->data(), propOffset);
assert(!(mia & MIA_warn) || !(mia & MIA_unset));
const bool doWarn = mia & MIA_warn;
const bool doDefine = mia & MIA_define || mia & MIA_unset;
Stats::emitInc(a, Stats::PropAsm_Specialized);
/*
* Type-inference from hphpc only tells us that this 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 mInd == 0, 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.
*/
std::unique_ptr<DiamondReturn> nonObjectRet;
bool isObj = m_vecState->isObj();
if (!isObj) {
emitTypeCheck(a, KindOfObject, r(rBase), 0);
{
nonObjectRet.reset(new DiamondReturn());
UnlikelyIfBlock ifNotObject(CC_NZ, a, astubs, nonObjectRet.get());
if (doWarn) {
EMIT_RCALL(astubs, *m_curNI, raisePropertyOnNonObject);
}
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)
*/
EMIT_RCALL(astubs, *m_curNI, throw_null_object_prop);
} else {
/*
* This case is supposed to evaluate to null and let the vector
* operation continue. However, the control flow to make that work
* properly would be quite messy and this never happens in practice, so
* throw a fatal instead of crashing.
*/
EMIT_RCALL(astubs, *m_curNI, throw_null_get_object_prop);
}
}
}
ScratchReg rScratch(m_regMap);
if (!isObj) {
emitDeref(a, r(rBase), r(rBase));
}
a. lea_reg64_disp_reg64(r(rBase), propOffset, r(rScratch));
if (doWarn || doDefine) {
emitCmpTVType(a, KindOfUninit, r(rScratch)[TVOFF(m_type)]);
{
UnlikelyIfBlock ifUninit(CC_Z, a, astubs);
if (doWarn) {
EMIT_RCALL(
astubs, *m_curNI, raiseUndefProp,
R(rBase),
IMM(uintptr_t(m_curNI->inputs[iInd]->rtt.valueString()))
);
}
if (doDefine) {
emitStoreTVType(astubs, KindOfNull, r(rScratch)[TVOFF(m_type)]);
} else {
emitImmReg(astubs, uintptr_t(&init_null_variant), r(rScratch));
}
}
}
// We have to prevent ~ScratchReg from deallocating this Scratch, so
// unbind it before doing the realloc.
auto usedScratch = r(rScratch);
rScratch.dealloc();
rBase.realloc(usedScratch);
m_vecState->resetBase();
// nonObjectRet returns here.
}
void TranslatorX64::emitProp(const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, m_curNI->source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const Class* knownCls = nullptr;
const auto propInfo = getPropertyOffset(*m_curNI, knownCls, mii,
mInd, iInd);
if (propInfo.offset == -1) {
emitPropGeneric(*m_curTrace, *m_curNI, mii, mInd, iInd, rBase);
} else {
auto attrs = mii.getAttr(m_curNI->immVecM[mInd]);
emitPropSpecialized(attrs, knownCls, propInfo.offset, mInd, iInd, rBase);
}
}
static TypedValue* newElem(TypedValue* base, MInstrState* mis) {
return NewElem(mis->tvScratch, mis->tvRef, base);
}
void TranslatorX64::emitNewElem(const Tracelet& t,
const NormalizedInstruction& ni,
unsigned mInd, LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u\n",
__func__, long(a.code.frontier), mInd);
EMIT_RCALL(a, ni, newElem, R(rBase), R(mis_rsp));
rBase.realloc(rax);
}
void TranslatorX64::emitIntermediateOp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned& iInd,
LazyScratchReg& rBase) {
switch (ni.immVecM[mInd]) {
case MEC: case MEL: case MET: case MEI: {
assert(!m_vecState->isKnown());
emitElem(t, ni, mii, mInd, iInd, rBase);
++iInd;
break;
}
case MPC: case MPL: case MPT:
emitProp(mii, mInd, iInd, rBase);
++iInd;
break;
case MW:
assert(!m_vecState->isKnown());
assert(mii.newElem());
emitNewElem(t, ni, mInd, rBase);
break;
default: not_reached();
}
/* If we consumed a key we're done with it now */
if (ni.immVecM[mInd] != MW) {
const DynLocation& key = *ni.inputs[iInd - 1];
m_regMap.cleanSmashLoc(key.location);
}
}
bool TranslatorX64::needFirstRatchet(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) const {
if (ni.inputs[mii.valCount()]->valueType() == KindOfArray) {
switch (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 TranslatorX64::needFinalRatchet(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) const {
return mii.finalGet();
}
unsigned TranslatorX64::nLogicalRatchets(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) const {
// 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
// If we've proven elsewhere that we don't need an MInstrState struct, we
// know this translation won't need any ratchets
if (!m_vecState->needsMIState()) return 0;
unsigned ratchets = ni.immVecM.size();
if (!needFirstRatchet(t, ni, mii)) --ratchets;
if (!needFinalRatchet(t, ni, mii)) --ratchets;
return ratchets;
}
int TranslatorX64::ratchetInd(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd) const {
return needFirstRatchet(t, ni, mii) ? int(mInd) : int(mInd) - 1;
}
void TranslatorX64::emitRatchetRefs(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd,
PhysReg rBase) {
if (ratchetInd(t, ni, mii, mInd) < 0
|| ratchetInd(t, ni, mii, mInd) >= int(nLogicalRatchets(t, ni, mii))) {
return;
}
SKTRACE(2, ni.source, "%s %#lx mInd=%u, ratchetInd=%d/[0..%u)\n",
__func__, long(a.code.frontier), mInd, ratchetInd(t, ni, mii, mInd),
nLogicalRatchets(t, ni, mii));
{
UnlessUninit uu(a, mis_rsp, MISOFF(tvRef));
// Clean up tvRef2 before overwriting it.
if (ratchetInd(t, ni, mii, mInd) > 0) {
emitDecRefGeneric(ni, mis_rsp, MISOFF(tvRef2));
}
// Copy tvRef to tvRef2.
{
ScratchReg rScratch(m_regMap);
assert(sizeof(TypedValue) % 8 == 0);
for (size_t off = 0; off < sizeof(TypedValue); off += 8) {
a.load_reg64_disp_reg64(mis_rsp, MISOFF(tvRef) + off, r(rScratch));
a.store_reg64_disp_reg64(r(rScratch), MISOFF(tvRef2) + off, mis_rsp);
}
}
// Reset tvRef.
emitStoreUninitNull(a, MISOFF(tvRef), mis_rsp);
// Adjust base pointer.
a. lea_reg64_disp_reg64(mis_rsp, MISOFF(tvRef2), rBase);
}
}
template <KeyType keyType, bool unboxKey, bool isObj>
static inline void cGetPropImpl(Class* ctx, TypedValue* base, TypedValue* key,
TypedValue* result,
MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
base = Prop<true, false, false, isObj, keyType>(
*result, mis->tvRef, ctx, base, key);
if (base != result) {
// Save a copy of the result.
tvDup(base, result);
}
if (result->m_type == KindOfRef) {
tvUnbox(result);
}
}
#define HELPER_TABLE(m) \
/* name hot key unboxKey isObj */ \
m(cGetPropC, , AnyKey, false, false) \
m(cGetPropCO, , AnyKey, false, true) \
m(cGetPropL, , AnyKey, true, false) \
m(cGetPropLO, , AnyKey, true, true) \
m(cGetPropLS, , StrKey, true, false) \
m(cGetPropLSO, , StrKey, true, true) \
m(cGetPropS, , StrKey, false, false) \
m(cGetPropSO, HOT_FUNC_VM, StrKey, false, true)
#define PROP(nm, hot, ...) \
hot \
static void nm(Class* ctx, TypedValue* base, TypedValue* key, \
TypedValue* result, \
MInstrState* mis) { \
cGetPropImpl<__VA_ARGS__>(ctx, base, key, result, mis); \
}
HELPER_TABLE(PROP)
#undef PROP
void TranslatorX64::emitCGetProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
assert(!ni.outLocal);
/*
* If we know the class for the current base, emit a direct property
* access.
*/
const Class* knownCls = nullptr;
const auto propInfo = getPropertyOffset(*m_curNI, knownCls,
mii, mInd, iInd);
if (propInfo.offset != -1) {
emitPropSpecialized(MIA_warn, knownCls, propInfo.offset,
mInd, iInd, rBase);
emitDerefIfVariant(a, r(rBase));
emitIncRefGeneric(r(rBase), 0);
PhysReg stackOutReg;
int stackOutDisp;
if (useTvResult(t, ni, mii)) {
stackOutReg = mis_rsp;
stackOutDisp = MISOFF(tvResult);
} else {
locToRegDisp(ni.outStack->location, &stackOutReg, &stackOutDisp);
invalidateOutStack(ni);
}
emitCopyToAligned(a, r(rBase), 0, stackOutReg, stackOutDisp);
return;
}
Stats::emitInc(a, Stats::PropAsm_GenFinal);
const DynLocation& memb = *ni.inputs[iInd];
const DynLocation& result = *ni.outStack;
PREP_CTX(argNumToRegName[0]);
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
// Emit the appropriate helper call.
typedef void (*OpFunc)(Class*, TypedValue*, TypedValue*, TypedValue*,
MInstrState*);
BUILD_OPTABH(getKeyTypeS(memb), memb.isRef(), m_vecState->isObj());
EMIT_RCALL(a, ni, opFunc,
CTX(), R(rBase), SML(memb), RESULT(useTvR), R(mis_rsp));
if (!useTvR) invalidateOutStack(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey>
static inline void vGetElemImpl(TypedValue* base, TypedValue* key,
TypedValue* result,
MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
base = ElemD<false, true, keyType>(mis->tvScratch, mis->tvRef, base, key);
if (base == &mis->tvScratch && base->m_type == KindOfUninit) {
// Error (no result was set).
tvWriteNull(result);
tvBox(result);
} else {
if (base->m_type != KindOfRef) {
tvBox(base);
}
tvDupVar(base, result);
}
}
#define HELPER_TABLE(m) \
/* name hot keyType unboxKey */ \
m(vGetElemC, , AnyKey, false) \
m(vGetElemI, HOT_FUNC_VM, IntKey, false) \
m(vGetElemL, , AnyKey, true) \
m(vGetElemLI, , IntKey, true) \
m(vGetElemLS, , StrKey, true) \
m(vGetElemS, , StrKey, false)
#define ELEM(nm, hot, ...) \
hot \
static void nm(TypedValue* base, TypedValue* key, TypedValue* result, \
MInstrState* mis) { \
vGetElemImpl<__VA_ARGS__>(base, key, result, mis); \
}
HELPER_TABLE(ELEM)
#undef ELEM
void TranslatorX64::emitVGetElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const DynLocation& memb = *ni.inputs[iInd];
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
typedef void (*OpFunc)(TypedValue*, TypedValue*, TypedValue*, MInstrState*);
BUILD_OPTABH(getKeyTypeIS(memb), memb.isRef());
cleanOutLocal(ni);
EMIT_RCALL(a, ni, opFunc, R(rBase), ISML(memb), RESULT(useTvR), R(mis_rsp));
invalidateOutLocal(ni);
if (!useTvR) invalidateOutStack(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, bool isObj>
static inline void vGetPropImpl(Class* ctx, TypedValue* base, TypedValue* key,
TypedValue* result,
MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
base = Prop<false, true, false, isObj, keyType>(mis->tvScratch, mis->tvRef,
ctx, base, key);
if (base == &mis->tvScratch && base->m_type == KindOfUninit) {
// Error (no result was set).
tvWriteNull(result);
tvBox(result);
} else {
if (base->m_type != KindOfRef) {
tvBox(base);
}
tvDupVar(base, result);
}
}
#define HELPER_TABLE(m) \
/* name hot key unboxKey isObj */ \
m(vGetPropC, , AnyKey, false, false) \
m(vGetPropCO, , AnyKey, false, true) \
m(vGetPropL, , AnyKey, true, false) \
m(vGetPropLO, , AnyKey, true, true) \
m(vGetPropLS, , StrKey, true, false) \
m(vGetPropLSO, , StrKey, true, true) \
m(vGetPropS, , StrKey, false, false) \
m(vGetPropSO, HOT_FUNC_VM, StrKey, false, true)
#define PROP(nm, hot, ...) \
hot \
static void nm(Class* ctx, TypedValue* base, TypedValue* key, \
TypedValue* result, \
MInstrState* mis) { \
vGetPropImpl<__VA_ARGS__>(ctx, base, key, result, mis); \
}
HELPER_TABLE(PROP)
#undef PROP
void TranslatorX64::emitVGetProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const DynLocation& memb = *ni.inputs[iInd];
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
PREP_CTX(argNumToRegName[0]);
// Emit the appropriate helper call.
typedef void (*OpFunc)(Class*, TypedValue*, TypedValue*, TypedValue*,
MInstrState*);
BUILD_OPTABH(getKeyTypeS(memb), memb.isRef(), m_vecState->isObj());
cleanOutLocal(ni);
EMIT_RCALL(a, ni, opFunc,
CTX(), R(rBase), SML(memb), RESULT(useTvR), R(mis_rsp));
invalidateOutLocal(ni);
if (!useTvR) invalidateOutStack(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, bool useEmpty>
static inline bool issetEmptyElemImpl(TypedValue* base, TypedValue* key,
MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
return IssetEmptyElem<useEmpty, false, keyType>(mis->tvScratch, mis->tvRef,
base, key);
}
#define HELPER_TABLE(m) \
/* name hot keyType unboxKey useEmpty */ \
m(issetElemC, , AnyKey, false, false) \
m(issetElemCE, , AnyKey, false, true) \
m(issetElemI, HOT_FUNC_VM, IntKey, false, false) \
m(issetElemIE, , IntKey, false, true) \
m(issetElemL, , AnyKey, true, false) \
m(issetElemLE, , AnyKey, true, true) \
m(issetElemLI, , IntKey, true, false) \
m(issetElemLIE, , IntKey, true, true) \
m(issetElemLS, , StrKey, true, false) \
m(issetElemLSE, , StrKey, true, true) \
m(issetElemS, HOT_FUNC_VM, StrKey, false, false) \
m(issetElemSE, HOT_FUNC_VM, StrKey, false, true)
#define ISSET(nm, hot, ...) \
hot \
static bool nm(TypedValue* base, TypedValue* key, \
MInstrState* mis) { \
return issetEmptyElemImpl<__VA_ARGS__>(base, key, mis); \
}
HELPER_TABLE(ISSET)
#undef ISSET
template <bool useEmpty>
void TranslatorX64::emitIssetEmptyElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
const DynLocation& memb = *ni.inputs[iInd];
typedef bool (*OpFunc)(TypedValue*, TypedValue*, MInstrState*);
BUILD_OPTABH(getKeyTypeIS(memb), memb.isRef(), useEmpty);
EMIT_RCALL(a, ni, opFunc, R(rBase), ISML(memb), R(mis_rsp));
a. and_imm32_reg64(1, rax); // Mask garbage bits.
ScratchReg rIssetEmpty(m_regMap, rax);
assert(!ni.outLocal);
if (useTvResult(t, ni, mii)) {
emitStoreTypedValue(a, KindOfBoolean, r(rIssetEmpty), MISOFF(tvResult),
mis_rsp);
} else {
m_regMap.bindScratch(rIssetEmpty, ni.outStack->location,
KindOfBoolean, RegInfo::DIRTY);
}
}
#undef HELPER_TABLE
void TranslatorX64::emitIssetElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
emitIssetEmptyElem<false>(t, ni, mii, mInd, iInd, rBase);
}
void TranslatorX64::emitEmptyElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
emitIssetEmptyElem<true>(t, ni, mii, mInd, iInd, rBase);
}
template <KeyType keyType, bool unboxKey, bool useEmpty, bool isObj>
static inline bool issetEmptyPropImpl(Class* ctx, TypedValue* base,
TypedValue* key,
MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
return IssetEmptyProp<useEmpty, isObj, keyType>(ctx, base, key);
}
#define HELPER_TABLE(m) \
/* name hot key unboxKey useEmpty isObj */ \
m(issetPropC, , AnyKey, false, false, false) \
m(issetPropCE, , AnyKey, false, true, false) \
m(issetPropCEO, , AnyKey, false, true, true) \
m(issetPropCO, , AnyKey, false, false, true) \
m(issetPropL, , AnyKey, true, false, false) \
m(issetPropLE, , AnyKey, true, true, false) \
m(issetPropLEO, , AnyKey, true, true, true) \
m(issetPropLO, , AnyKey, true, false, true) \
m(issetPropLS, , StrKey, true, false, false) \
m(issetPropLSE, , StrKey, true, true, false) \
m(issetPropLSEO, , StrKey, true, true, true) \
m(issetPropLSO, , StrKey, true, false, true) \
m(issetPropS, , StrKey, false, false, false) \
m(issetPropSE, , StrKey, false, true, false) \
m(issetPropSEO, , StrKey, false, true, true) \
m(issetPropSO, HOT_FUNC_VM, StrKey, false, false, true)
#define ISSET(nm, hot, ...) \
hot \
static bool nm(Class* ctx, TypedValue* base, TypedValue* key, \
MInstrState* mis) { \
return issetEmptyPropImpl<__VA_ARGS__>(ctx, base, key, mis); \
}
HELPER_TABLE(ISSET)
#undef ISSET
template <bool useEmpty>
void TranslatorX64::emitIssetEmptyProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
PhysReg rBase) {
const DynLocation& memb = *ni.inputs[iInd];
PREP_CTX(argNumToRegName[0]);
// Emit the appropriate helper call.
typedef bool (*OpFunc)(Class* ctx, TypedValue*, TypedValue*, MInstrState*);
BUILD_OPTABH(getKeyTypeS(memb), memb.isRef(), useEmpty,
m_vecState->isObj());
EMIT_RCALL(a, ni, opFunc, CTX(), R(rBase), SML(memb));
a. andq(1, rax); // Mask garbage bits.
ScratchReg rIssetEmpty(m_regMap, rax);
assert(!ni.outLocal);
if (useTvResult(t, ni, mii)) {
emitStoreTypedValue(a, KindOfBoolean, r(rIssetEmpty), MISOFF(tvResult),
mis_rsp);
} else {
m_regMap.bindScratch(rIssetEmpty, ni.outStack->location, KindOfBoolean,
RegInfo::DIRTY);
}
}
#undef HELPER_TABLE
void TranslatorX64::emitIssetProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
emitIssetEmptyProp<false>(t, ni, mii, mInd, iInd, r(rBase));
}
void TranslatorX64::emitEmptyProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
emitIssetEmptyProp<true>(t, ni, mii, mInd, iInd, r(rBase));
}
template <KeyType keyType, bool unboxKey, bool setResult>
static inline void setElemImpl(TypedValue* base, TypedValue* key, Cell* val) {
key = unbox<keyType, unboxKey>(key);
SetElem<setResult, keyType>(base, key, val);
}
#define HELPER_TABLE(m) \
/* name hot key unboxKey setResult */ \
m(setElemC, , AnyKey, false, false) \
m(setElemCR, , AnyKey, false, true) \
m(setElemI, , IntKey, false, false) \
m(setElemIR, , IntKey, false, true) \
m(setElemL, , AnyKey, true, false) \
m(setElemLR, , AnyKey, true, true) \
m(setElemLI, , IntKey, true, false) \
m(setElemLIR, , IntKey, true, true) \
m(setElemLS, , StrKey, true, false) \
m(setElemLSR, , StrKey, true, true) \
m(setElemS, HOT_FUNC_VM, StrKey, false, false) \
m(setElemSR, , StrKey, false, true)
#define ELEM(nm, hot, ...) \
hot \
static void nm(TypedValue* base, TypedValue* key, Cell* val) { \
setElemImpl<__VA_ARGS__>(base, key, val); \
}
HELPER_TABLE(ELEM)
#undef ELEM
void TranslatorX64::emitSetElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const DynLocation& key = *ni.inputs[iInd];
const DynLocation& val = *ni.inputs[0];
bool setResult = generateMVal(t, ni, mii);
SKTRACE(2, ni.source, "%s setResult=%s\n",
__func__, setResult ? "true" : "false");
m_regMap.cleanSmashLoc(val.location);
bool useRVal = (!forceMValIncDec(t, ni, mii) && val.isRef());
PREP_VAL(useRVal, argNumToRegName[2]);
// Emit the appropriate helper call.
typedef void (*OpFunc)(TypedValue*, TypedValue*, Cell*);
BUILD_OPTABH(getKeyTypeIS(key), key.isRef(), setResult);
cleanOutLocal(ni);
EMIT_RCALL(a, ni, opFunc,
R(rBase),
ISML(key),
IE(forceMValIncDec(t, ni, mii),
RPLUS(mis_rsp, MISOFF(tvVal)),
VAL(useRVal)));
invalidateOutLocal(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, bool setResult, bool isObj>
static inline void setPropImpl(Class* ctx, TypedValue* base, TypedValue* key,
Cell* val) {
key = unbox<keyType, unboxKey>(key);
SetProp<setResult, isObj, keyType>(ctx, base, key, val);
}
#define HELPER_TABLE(m) \
/* name key unboxKey setResult isObj */ \
m(setPropC, AnyKey, false, false, false) \
m(setPropCO, AnyKey, false, false, true) \
m(setPropCR, AnyKey, false, true, false) \
m(setPropCRO, AnyKey, false, true, true) \
m(setPropL, AnyKey, true, false, false) \
m(setPropLO, AnyKey, true, false, true) \
m(setPropLR, AnyKey, true, true, false) \
m(setPropLRO, AnyKey, true, true, true) \
m(setPropLS, StrKey, true, false, false) \
m(setPropLSO, StrKey, true, false, true) \
m(setPropLSR, StrKey, true, true, false) \
m(setPropLSRO, StrKey, true, true, true) \
m(setPropS, StrKey, false, false, false) \
m(setPropSO, StrKey, false, false, true) \
m(setPropSR, StrKey, false, true, false) \
m(setPropSRO, StrKey, false, true, true)
#define PROP(nm, ...) \
static void nm(Class* ctx, TypedValue* base, TypedValue* key, Cell* val) { \
setPropImpl<__VA_ARGS__>(ctx, base, key, val); \
}
HELPER_TABLE(PROP)
#undef PROP
void TranslatorX64::emitSetProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const int kRhsIdx = 0;
const DynLocation& val = *ni.inputs[kRhsIdx];
/*
* If we know the class for the current base, emit a direct property
* set.
*/
const Class* knownCls = nullptr;
const auto propInfo = getPropertyOffset(*m_curNI, knownCls,
mii, mInd, iInd);
if (propInfo.offset != -1 && !ni.outLocal && !ni.outStack) {
emitPropSpecialized(MIA_define, knownCls, propInfo.offset,
mInd, iInd, rBase);
m_regMap.allocInputReg(*m_curNI, kRhsIdx);
PhysReg rhsReg = getReg(val.location);
LazyScratchReg tmp(m_regMap);
if (val.isRef() && !IS_NULL_TYPE(val.rtt.valueType())) {
tmp.alloc();
emitDerefRef(a, rhsReg, r(tmp));
rhsReg = r(tmp);
}
const bool incRef = true;
emitTvSet(*m_curNI, rhsReg, val.rtt.valueType(), r(rBase), 0, incRef);
return;
}
Stats::emitInc(a, Stats::PropAsm_GenFinal);
const bool setResult = generateMVal(t, ni, mii);
SKTRACE(2, ni.source, "%s setResult=%s\n",
__func__, setResult ? "true" : "false");
const DynLocation& key = *ni.inputs[iInd];
m_regMap.cleanSmashLoc(val.location);
PREP_CTX(argNumToRegName[0]);
bool useRVal = val.isRef();
PREP_VAL(useRVal, argNumToRegName[3]);
// Emit the appropriate helper call.
typedef void (*OpFunc)(Class*, TypedValue*, TypedValue*, Cell*);
cleanOutLocal(ni);
BUILD_OPTAB(getKeyTypeS(key), key.isRef(), setResult,
m_vecState->isObj());
EMIT_RCALL(a, ni, opFunc, CTX(), R(rBase), SML(key), VAL(useRVal));
invalidateOutLocal(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, SetOpOp op, bool setResult>
static inline void setOpElemImpl(TypedValue* base, TypedValue* key, Cell* val,
MInstrState* mis,
TypedValue* tvRes=nullptr) {
key = unbox<keyType, unboxKey>(key);
TypedValue* result = SetOpElem<keyType>(mis->tvScratch, mis->tvRef, op, base,
key, val);
if (setResult) {
tvReadCell(result, tvRes);
}
}
#define OPELEM_TABLE(m, nm, op) \
/* name keyType unboxKey op setResult */ \
m(nm##op##ElemC, AnyKey, false, op, false) \
m(nm##op##ElemCR, AnyKey, false, op, true) \
m(nm##op##ElemI, IntKey, false, op, false) \
m(nm##op##ElemIR, IntKey, false, op, true) \
m(nm##op##ElemL, AnyKey, true, op, false) \
m(nm##op##ElemLR, AnyKey, true, op, true) \
m(nm##op##ElemLI, IntKey, true, op, false) \
m(nm##op##ElemLIR, IntKey, true, op, true) \
m(nm##op##ElemLS, StrKey, true, op, false) \
m(nm##op##ElemLSR, StrKey, true, op, true) \
m(nm##op##ElemS, StrKey, false, op, false) \
m(nm##op##ElemSR, StrKey, false, op, true)
#define HELPER_TABLE(m, op) OPELEM_TABLE(m, setOp, SetOp##op)
#define SETOP(nm, ...) \
static void nm(TypedValue* base, TypedValue* key, Cell* val, \
MInstrState* mis, \
TypedValue* tvRes) { \
setOpElemImpl<__VA_ARGS__>(base, key, val, mis, tvRes); \
}
#define SETOP_OP(op, bcOp) HELPER_TABLE(SETOP, op)
SETOP_OPS
#undef SETOP_OP
#undef SETOP
void TranslatorX64::emitSetOpElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
SetOpOp op = SetOpOp(ni.imm[0].u_OA);
const DynLocation& key = *ni.inputs[iInd];
const DynLocation& val = *ni.inputs[0];
bool setResult = generateMVal(t, ni, mii);
SKTRACE(2, ni.source, "%s setResult=%s\n",
__func__, setResult ? "true" : "false");
m_regMap.cleanSmashLoc(val.location);
bool useRVal = (!forceMValIncDec(t, ni, mii) && val.isRef());
PREP_VAL(useRVal, argNumToRegName[2]);
typedef void (*OpFunc)(TypedValue*, TypedValue*, Cell*, MInstrState*,
TypedValue*);
# define SETOP_OP(op, bcOp) HELPER_TABLE(FILL_ROW, op)
BUILD_OPTAB_ARG(SETOP_OPS, getKeyTypeIS(key), key.isRef(), op, setResult);
# undef SETOP_OP
// Emit the appropriate helper call.
cleanOutLocal(ni);
if (setResult) {
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
EMIT_RCALL(a, ni, opFunc,
R(rBase),
ISML(key),
IE(forceMValIncDec(t, ni, mii),
RPLUS(mis_rsp, MISOFF(tvVal)),
VAL(useRVal)),
R(mis_rsp),
RESULT(useTvR));
if (!useTvR) invalidateOutStack(ni);
} else {
EMIT_RCALL(a, ni, opFunc,
R(rBase),
ISML(key),
IE(forceMValIncDec(t, ni, mii),
RPLUS(mis_rsp, MISOFF(tvVal)),
VAL(useRVal)),
R(mis_rsp));
}
invalidateOutLocal(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, SetOpOp op, bool setResult,
bool isObj>
static inline void setOpPropImpl(Class* ctx, TypedValue* base, TypedValue* key,
Cell* val, MInstrState* mis,
TypedValue* tvRes=nullptr) {
key = unbox<keyType, unboxKey>(key);
TypedValue* result = SetOpProp<isObj, keyType>(mis->tvScratch, mis->tvRef,
ctx, op, base, key, val);
if (setResult) {
tvReadCell(result, tvRes);
}
}
#define OPPROP_TABLE(m, nm, op) \
/* name keyType unboxKey op setResult isObj */ \
m(nm##op##PropC, AnyKey, false, op, false, false) \
m(nm##op##PropCO, AnyKey, false, op, false, true) \
m(nm##op##PropCR, AnyKey, false, op, true, false) \
m(nm##op##PropCRO, AnyKey, false, op, true, true) \
m(nm##op##PropL, AnyKey, true, op, false, false) \
m(nm##op##PropLO, AnyKey, true, op, false, true) \
m(nm##op##PropLR, AnyKey, true, op, true, false) \
m(nm##op##PropLRO, AnyKey, true, op, true, true) \
m(nm##op##PropLS, StrKey, true, op, false, false) \
m(nm##op##PropLSO, StrKey, true, op, false, true) \
m(nm##op##PropLSR, StrKey, true, op, true, false) \
m(nm##op##PropLSRO, StrKey, true, op, true, true) \
m(nm##op##PropS, StrKey, false, op, false, false) \
m(nm##op##PropSO, StrKey, false, op, false, true) \
m(nm##op##PropSR, StrKey, false, op, true, false) \
m(nm##op##PropSRO, StrKey, false, op, true, true)
#define HELPER_TABLE(m, op) OPPROP_TABLE(m, setOp, SetOp##op)
#define SETOP(nm, ...) \
static void nm(Class* ctx, TypedValue* base, TypedValue* key, \
Cell* val, MInstrState* mis, \
TypedValue* tvRes) { \
setOpPropImpl<__VA_ARGS__>(ctx, base, key, val, mis, tvRes); \
}
#define SETOP_OP(op, bcOp) HELPER_TABLE(SETOP, op)
SETOP_OPS
#undef SETOP_OP
#undef SETOP
void TranslatorX64::emitSetOpProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
SetOpOp op = SetOpOp(ni.imm[0].u_OA);
const DynLocation& key = *ni.inputs[iInd];
const DynLocation& val = *ni.inputs[0];
bool setResult = generateMVal(t, ni, mii);
SKTRACE(2, ni.source, "%s setResult=%s\n",
__func__, setResult ? "true" : "false");
m_regMap.cleanSmashLoc(val.location);
PREP_CTX(argNumToRegName[0]);
bool useRVal = val.isRef();
bool isObj = m_vecState->isObj();
PREP_VAL(useRVal, argNumToRegName[3]);
typedef void (*OpFunc)(Class*, TypedValue*, TypedValue*, Cell*, MInstrState*,
TypedValue*);
# define SETOP_OP(op, bcOp) HELPER_TABLE(FILL_ROW, op)
BUILD_OPTAB_ARG(SETOP_OPS,
getKeyTypeS(key), key.isRef(), op, setResult, isObj);
# undef SETOP_OP
// Emit the appropriate helper call.
cleanOutLocal(ni);
if (setResult) {
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
EMIT_RCALL(a, ni, opFunc,
CTX(),
R(rBase),
SML(key),
VAL(useRVal),
R(mis_rsp),
RESULT(useTvR));
if (!useTvR) invalidateOutStack(ni);
} else {
EMIT_RCALL(a, ni, opFunc,
CTX(), R(rBase), SML(key), VAL(useRVal), R(mis_rsp));
}
invalidateOutLocal(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, IncDecOp op, bool setResult>
static inline void incDecElemImpl(TypedValue* base, TypedValue* key,
MInstrState* mis,
TypedValue* tvRes) {
key = unbox<keyType, unboxKey>(key);
IncDecElem<setResult, keyType>(mis->tvScratch, mis->tvRef, op, base, key,
*tvRes);
}
#define HELPER_TABLE(m, op) OPELEM_TABLE(m, incDec, op)
#define INCDEC(nm, ...) \
static void nm(TypedValue* base, TypedValue* key, \
MInstrState* mis, \
TypedValue* tvRes) { \
incDecElemImpl<__VA_ARGS__>(base, key, mis, tvRes); \
}
#define INCDEC_OP(op) HELPER_TABLE(INCDEC, op)
INCDEC_OPS
#undef INCDEC_OP
#undef INCDEC
void TranslatorX64::emitIncDecElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
IncDecOp op = IncDecOp(ni.imm[0].u_OA);
const DynLocation& key = *ni.inputs[iInd];
bool setResult = generateMVal(t, ni, mii);
SKTRACE(2, ni.source, "%s setResult=%s\n",
__func__, setResult ? "true" : "false");
typedef void (*OpFunc)(TypedValue*, TypedValue*, MInstrState*, TypedValue*);
# define INCDEC_OP(op) HELPER_TABLE(FILL_ROW, op)
BUILD_OPTAB_ARG(INCDEC_OPS, getKeyTypeIS(key), key.isRef(), op, setResult);
# undef INCDEC_OP
// Emit the appropriate helper call.
cleanOutLocal(ni);
if (setResult) {
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
EMIT_RCALL(a, ni, opFunc, R(rBase), ISML(key), R(mis_rsp), RESULT(useTvR));
if (!useTvR) invalidateOutStack(ni);
} else {
EMIT_RCALL(a, ni, opFunc, R(rBase), ISML(key), R(mis_rsp));
}
invalidateOutLocal(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, IncDecOp op, bool setResult,
bool isObj>
static inline void incDecPropImpl(Class* ctx, TypedValue* base, TypedValue* key,
MInstrState* mis,
TypedValue* tvRes) {
key = unbox<keyType, unboxKey>(key);
IncDecProp<setResult, isObj, keyType>(mis->tvScratch, mis->tvRef, ctx, op,
base, key, *tvRes);
}
#define HELPER_TABLE(m, op) OPPROP_TABLE(m, incDec, op)
#define INCDEC(nm, ...) \
static void nm(Class* ctx, TypedValue* base, TypedValue* key, \
MInstrState* mis, TypedValue* tvRes) { \
incDecPropImpl<__VA_ARGS__>(ctx, base, key, mis, tvRes); \
}
#define INCDEC_OP(op) HELPER_TABLE(INCDEC, op)
INCDEC_OPS
#undef INCDEC_OP
#undef INCDEC
void TranslatorX64::emitIncDecProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
IncDecOp op = IncDecOp(ni.imm[0].u_OA);
const DynLocation& key = *ni.inputs[iInd];
bool setResult = generateMVal(t, ni, mii);
SKTRACE(2, ni.source, "%s setResult=%s\n",
__func__, setResult ? "true" : "false");
PREP_CTX(argNumToRegName[0]);
bool isObj = m_vecState->isObj();
typedef void (*OpFunc)(Class*, TypedValue*, TypedValue*, MInstrState*,
TypedValue*);
# define INCDEC_OP(op) HELPER_TABLE(FILL_ROW, op)
BUILD_OPTAB_ARG(INCDEC_OPS,
getKeyTypeS(key), key.isRef(), op, setResult, isObj);
# undef INCDEC_OP
// Emit the appropriate helper call.
cleanOutLocal(ni);
if (setResult) {
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
EMIT_RCALL(a, ni, opFunc,
CTX(),
R(rBase),
SML(key),
R(mis_rsp),
RESULT(useTvR));
if (!useTvR) invalidateOutStack(ni);
} else {
EMIT_RCALL(a, ni, opFunc,
CTX(), R(rBase), SML(key), R(mis_rsp));
}
invalidateOutLocal(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey>
static inline void bindElemImpl(TypedValue* base, TypedValue* key, TypedValue* val,
MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
base = ElemD<false, true, keyType>(mis->tvScratch, mis->tvRef, base, key);
assert(val->m_type == KindOfRef);
if (!(base == &mis->tvScratch && base->m_type == KindOfUninit)) {
tvBind(val, base);
}
}
#define HELPER_TABLE(m) \
/* name keyType unboxKey */ \
m(bindElemC, AnyKey, false) \
m(bindElemI, IntKey, false) \
m(bindElemL, AnyKey, true) \
m(bindElemLI, IntKey, true) \
m(bindElemLS, StrKey, true) \
m(bindElemS, StrKey, false)
#define ELEM(nm, ...) \
static void nm(TypedValue* base, TypedValue* key, TypedValue* val, \
MInstrState* mis) { \
bindElemImpl<__VA_ARGS__>(base, key, val, mis); \
}
HELPER_TABLE(ELEM)
#undef ELEM
void TranslatorX64::emitBindElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const DynLocation& key = *ni.inputs[iInd];
const DynLocation& val = *ni.inputs[0];
assert(generateMVal(t, ni, mii));
m_regMap.cleanSmashLoc(val.location);
cleanOutLocal(ni);
assert(!forceMValIncDec(t, ni, mii));
assert(val.isRef());
typedef void (*OpFunc)(TypedValue*, TypedValue*, TypedValue*, MInstrState*);
BUILD_OPTAB(getKeyTypeIS(key), key.isRef());
EMIT_RCALL(a, ni, opFunc, R(rBase), ISML(key), A(val.location), R(mis_rsp));
invalidateOutLocal(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, bool isObj>
static inline void bindPropImpl(Class* ctx, TypedValue* base, TypedValue* key,
TypedValue* val, MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
base = Prop<false, true, false, isObj, keyType>(mis->tvScratch, mis->tvRef,
ctx, base, key);
assert(val->m_type == KindOfRef);
if (!(base == &mis->tvScratch && base->m_type == KindOfUninit)) {
tvBind(val, base);
}
}
#define HELPER_TABLE(m) \
/* name key unboxKey isObj */ \
m(bindPropC, AnyKey, false, false) \
m(bindPropCO, AnyKey, false, true) \
m(bindPropL, AnyKey, true, false) \
m(bindPropLO, AnyKey, true, true) \
m(bindPropLS, StrKey, true, false) \
m(bindPropLSO, StrKey, true, true) \
m(bindPropS, StrKey, false, false) \
m(bindPropSO, StrKey, false, true)
#define PROP(nm, ...) \
static inline void nm(Class* ctx, TypedValue* base, TypedValue* key, \
TypedValue* val, \
MInstrState* mis) { \
bindPropImpl<__VA_ARGS__>(ctx, base, key, val, mis); \
}
HELPER_TABLE(PROP)
#undef PROP
void TranslatorX64::emitBindProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const DynLocation& val = *ni.inputs[0];
m_regMap.cleanSmashLoc(val.location);
assert(val.isRef());
assert(generateMVal(t, ni, mii));
const DynLocation& key = *ni.inputs[iInd];
cleanOutLocal(ni);
PREP_CTX(argNumToRegName[0]);
// Emit the appropriate helper call.
assert(!forceMValIncDec(t, ni, mii));
typedef void (*OpFunc)(Class*, TypedValue*, TypedValue*, TypedValue*,
MInstrState*);
BUILD_OPTAB(getKeyTypeS(key), key.isRef(), m_vecState->isObj());
EMIT_RCALL(a, ni, opFunc,
CTX(), R(rBase), SML(key), A(val.location), R(mis_rsp));
invalidateOutLocal(ni);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey>
static inline void unsetElemImpl(TypedValue* base, TypedValue* key) {
key = unbox<keyType, unboxKey>(key);
UnsetElem<keyType>(base, key);
}
#define HELPER_TABLE(m) \
/* name hot keyType unboxKey */ \
m(unsetElemC, , AnyKey, false) \
m(unsetElemI, HOT_FUNC_VM, IntKey, false) \
m(unsetElemL, , AnyKey, true) \
m(unsetElemLI, , IntKey, true) \
m(unsetElemLS, , StrKey, true) \
m(unsetElemS, HOT_FUNC_VM, StrKey, false)
#define ELEM(nm, hot, ...) \
hot \
static void nm(TypedValue* base, TypedValue* key) { \
unsetElemImpl<__VA_ARGS__>(base, key); \
}
HELPER_TABLE(ELEM)
#undef ELEM
void TranslatorX64::emitUnsetElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const DynLocation& key = *ni.inputs[iInd];
const DynLocation& val = *ni.inputs[0];
m_regMap.cleanSmashLoc(key.location);
m_regMap.cleanSmashLoc(val.location);
typedef void (*OpFunc)(TypedValue*, TypedValue*);
BUILD_OPTABH(getKeyTypeIS(key), key.isRef());
EMIT_RCALL(a, ni, opFunc, R(rBase), ISML(key));
assert(!ni.outLocal && !ni.outStack);
}
#undef HELPER_TABLE
template <KeyType keyType, bool unboxKey, bool isObj>
static inline void unsetPropImpl(Class* ctx, TypedValue* base,
TypedValue* key) {
key = unbox<keyType, unboxKey>(key);
UnsetProp<isObj, keyType>(ctx, base, key);
}
#define HELPER_TABLE(m) \
/* name key unboxKey isObj */ \
m(unsetPropC, AnyKey, false, false) \
m(unsetPropCO, AnyKey, false, true) \
m(unsetPropL, AnyKey, true, false) \
m(unsetPropLO, AnyKey, true, true) \
m(unsetPropLS, StrKey, true, false) \
m(unsetPropLSO, StrKey, true, true) \
m(unsetPropS, StrKey, false, false) \
m(unsetPropSO, StrKey, false, true)
#define PROP(nm, ...) \
static void nm(Class* ctx, TypedValue* base, TypedValue* key) { \
unsetPropImpl<__VA_ARGS__>(ctx, base, key); \
}
HELPER_TABLE(PROP)
#undef PROP
void TranslatorX64::emitUnsetProp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd, unsigned iInd,
LazyScratchReg& rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const DynLocation& key = *ni.inputs[iInd];
const DynLocation& val = *ni.inputs[0];
m_regMap.cleanSmashLoc(key.location);
m_regMap.cleanSmashLoc(val.location);
PREP_CTX(argNumToRegName[0]);
// Emit the appropriate helper call.
typedef void (*OpFunc)(Class*, TypedValue*, TypedValue*);
BUILD_OPTAB(getKeyTypeS(key), key.isRef(), m_vecState->isObj());
EMIT_RCALL(a, ni, opFunc, CTX(), R(rBase), SML(key));
assert(!ni.outLocal && !ni.outStack);
}
#undef HELPER_TABLE
static inline void vGetNewElem(TypedValue* base, TypedValue* result,
MInstrState* mis) {
base = NewElem(mis->tvScratch, mis->tvRef, base);
if (base == &mis->tvScratch && base->m_type == KindOfUninit) {
// Error (no result was set).
tvWriteNull(result);
tvBox(result);
} else {
if (base->m_type != KindOfRef) {
tvBox(base);
}
tvDupVar(base, result);
}
}
void TranslatorX64::emitVGetNewElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
void (*vGetNewElemOp)(TypedValue*, TypedValue*, MInstrState*) = vGetNewElem;
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
EMIT_RCALL(a, ni, vGetNewElemOp,
R(rBase),
RESULT(useTvR),
R(mis_rsp));
}
static void setNewElemR(TypedValue* base, Cell* val) {
SetNewElem<true>(base, val);
}
HOT_FUNC_VM
static void setNewElemNR(TypedValue* base, Cell* val) {
SetNewElem<false>(base, val);
}
void TranslatorX64::emitSetNewElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
bool setResult = generateMVal(t, ni, mii);
SKTRACE(2, ni.source, "%s setResult=%s\n",
__func__, setResult ? "true" : "false");
const DynLocation& val = *ni.inputs[0];
m_regMap.cleanSmashLoc(val.location);
// Emit the appropriate helper call.
void (*setNewElemOp)(TypedValue*, Cell*) =
setResult ? setNewElemR : setNewElemNR;
bool useRVal = val.isRef();
PREP_VAL(useRVal, argNumToRegName[1]);
EMIT_RCALL(a, ni, setNewElemOp,
R(rBase),
VAL(useRVal));
}
template <unsigned char op, bool setResult>
static inline void setOpNewElemImpl(TypedValue* base, Cell* val,
MInstrState* mis,
TypedValue* tvRes=nullptr) {
TypedValue* result = SetOpNewElem(mis->tvScratch, mis->tvRef, op, base, val);
if (setResult) {
if (result->m_type == KindOfRef) {
tvUnbox(result);
}
tvDup(result, tvRes); // tvRes may or may not be &mis->tvResult.
}
}
#define SETOP_OP(op, bcOp) \
static void setOp##op##NewElemR(TypedValue* base, Cell* val, \
MInstrState* mis, \
TypedValue* tvRes) { \
setOpNewElemImpl<SetOp##op, true>(base, val, mis, tvRes); \
} \
static void setOp##op##NewElem(TypedValue* base, Cell* val, \
MInstrState* mis) { \
setOpNewElemImpl<SetOp##op, false>(base, val, mis); \
}
SETOP_OPS
#undef SETOP_OP
void TranslatorX64::emitSetOpNewElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
unsigned char op = ni.imm[0].u_OA;
const DynLocation& val = *ni.inputs[0];
bool setResult = generateMVal(t, ni, mii);
SKTRACE(2, ni.source, "%s setResult=%s\n",
__func__, setResult ? "true" : "false");
m_regMap.cleanSmashLoc(val.location);
bool useRVal = val.isRef();
PREP_VAL(useRVal, argNumToRegName[3]);
// Emit the appropriate helper call.
if (setResult) {
void (*setOpNewElemOp)(TypedValue*, Cell*, MInstrState*, TypedValue*);
switch (op) {
#define SETOP_OP(op, bcOp) \
case SetOp##op: setOpNewElemOp = setOp##op##NewElemR; break;
SETOP_OPS
#undef SETOP_OP
default: not_reached();
}
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
EMIT_RCALL(a, ni, setOpNewElemOp,
R(rBase),
VAL(useRVal),
R(mis_rsp),
RESULT(useTvR));
} else {
void (*setOpNewElemOp)(TypedValue*, Cell*, MInstrState*);
switch (op) {
#define SETOP_OP(op, bcOp) \
case SetOp##op: setOpNewElemOp = setOp##op##NewElem; break;
SETOP_OPS
#undef SETOP_OP
default: not_reached();
}
EMIT_RCALL(a, ni, setOpNewElemOp,
R(rBase),
VAL(useRVal),
R(mis_rsp));
}
}
template <unsigned char op, bool setResult>
static inline void incDecNewElemImpl(TypedValue* base,
MInstrState* mis,
TypedValue* tvRes) {
IncDecNewElem<setResult>(mis->tvScratch, mis->tvRef, op, base, *tvRes);
}
#define INCDEC_OP(op) \
static void incDec##op##NewElemR(TypedValue* base, \
MInstrState* mis, \
TypedValue* tvRes) { \
incDecNewElemImpl<op, true>(base, mis, tvRes); \
} \
static void incDec##op##NewElem(TypedValue* base, \
MInstrState* mis) { \
TypedValue tvRes; /* Not used; no need to initialize. */ \
incDecNewElemImpl<op, false>(base, mis, &tvRes); \
}
INCDEC_OPS
#undef INCDEC_OP
void TranslatorX64::emitIncDecNewElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
unsigned char op = ni.imm[0].u_OA;
bool setResult = generateMVal(t, ni, mii);
SKTRACE(2, ni.source, "%s setResult=%s\n",
__func__, setResult ? "true" : "false");
// Emit the appropriate helper call.
if (setResult) {
void (*incDecNewElemOp)(TypedValue*, MInstrState*, TypedValue*);
switch (op) {
#define INCDEC_OP(op) \
case op: incDecNewElemOp = incDec##op##NewElemR; break;
INCDEC_OPS
#undef INCDEC_OP
default: not_reached();
}
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
EMIT_RCALL(a, ni, incDecNewElemOp,
R(rBase),
R(mis_rsp),
RESULT(useTvR));
} else {
void (*incDecNewElemOp)(TypedValue*, MInstrState*);
switch (op) {
#define INCDEC_OP(op) \
case op: incDecNewElemOp = incDec##op##NewElem; break;
INCDEC_OPS
#undef INCDEC_OP
default: not_reached();
}
EMIT_RCALL(a, ni, incDecNewElemOp, R(rBase), R(mis_rsp));
}
}
static inline void bindNewElem(TypedValue* base, TypedValue* val,
MInstrState* mis) {
base = NewElem(mis->tvScratch, mis->tvRef, base);
assert(val->m_type == KindOfRef);
if (!(base == &mis->tvScratch && base->m_type == KindOfUninit)) {
tvBind(val, base);
}
}
void TranslatorX64::emitBindNewElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
assert(generateMVal(t, ni, mii));
const DynLocation& val = *ni.inputs[0];
m_regMap.cleanSmashLoc(val.location);
assert(val.isRef());
EMIT_RCALL(a, ni, bindNewElem, R(rBase), A(val.location), R(mis_rsp));
}
void TranslatorX64::emitNotSuppNewElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
not_reached();
}
void TranslatorX64::emitFinalMOp(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned mInd,
unsigned iInd,
LazyScratchReg& rBase) {
typedef void (TranslatorX64::*RegOp)(const Tracelet&,
const NormalizedInstruction&,
const MInstrInfo&, unsigned, unsigned,
PhysReg rBase);
typedef void (TranslatorX64::*ScratchOp)(const Tracelet&,
const NormalizedInstruction&,
const MInstrInfo&, unsigned,
unsigned, LazyScratchReg& rBase);
switch (ni.immVecM[mInd]) {
case MEC: case MEL: case MET: case MEI:
assert(!m_vecState->isKnown());
static RegOp elemOps[] = {
# define MII(instr, ...) &TranslatorX64::emit##instr##Elem,
MINSTRS
# undef MII
};
(this->*elemOps[mii.instr()])(t, ni, mii, mInd, iInd, r(rBase));
break;
case MPC: case MPL: case MPT:
static ScratchOp propOps[] = {
# define MII(instr, ...) &TranslatorX64::emit##instr##Prop,
MINSTRS
# undef MII
};
(this->*propOps[mii.instr()])(t, ni, mii, mInd, iInd, rBase);
break;
case MW:
assert(!m_vecState->isKnown());
assert(mii.getAttr(MW) & MIA_final);
static RegOp newOp[] = {
# define MII(instr, attrs, bS, iS, vC, fN) &TranslatorX64::emit##fN,
MINSTRS
# undef MII
};
(this->*newOp[mii.instr()])(t, ni, mii, mInd, iInd, r(rBase));
break;
default: not_reached();
}
}
bool TranslatorX64::needMInstrCtx(const Tracelet& t,
const NormalizedInstruction& ni) const {
// Return true if the context will actually be used; otherwise return false
// in order to avoid emission of getMInstrCtx() calls.
switch (ni.immVec.locationCode()) {
case LL: case LC: case LR: case LH:
case LGL: case LGC:
case LNL: case LNC: break;
case LSL: case LSC: {
SKTRACE(2, ni.source, "%s (location uses context) --> true\n", __func__);
return true;
}
default: not_reached();
}
for (unsigned m = 0; m < ni.immVecM.size(); ++m) {
switch (ni.immVecM[m]) {
case MEC: case MEL:
case MET: case MEI:
case MW: break;
case MPC: case MPL: case MPT: {
SKTRACE(2, ni.source, "%s (member %u uses context) --> true\n",
__func__, m);
return true;
}
default: not_reached();
}
}
SKTRACE(2, ni.source, "%s --> false\n", __func__);
return false;
}
void TranslatorX64::emitMPre(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii,
unsigned& mInd, unsigned& iInd,
LazyScratchReg& rBase) {
if (!mInstrHasUnknownOffsets(ni) && !useTvResult(t, ni, mii) &&
(ni.mInstrOp() == OpCGetM || ni.mInstrOp() == OpSetM)) {
m_vecState->setNoMIState();
}
SKTRACE(2, ni.source, "%s %#lx\n", __func__, long(a.code.frontier));
if (m_vecState->needsMIState()) {
if (debug) {
emitStoreInvalid(a, MISOFF(tvScratch), mis_rsp);
}
SKTRACE(2, ni.source, "%s nLogicalRatchets=%u\n",
__func__, nLogicalRatchets(t, ni, mii));
if (nLogicalRatchets(t, ni, mii) > 0) {
emitStoreUninitNull(a, MISOFF(tvRef), mis_rsp);
emitStoreUninitNull(a, MISOFF(tvRef2), mis_rsp);
} else if (debug) {
emitStoreInvalid(a, MISOFF(tvRef), mis_rsp);
emitStoreInvalid(a, MISOFF(tvRef2), mis_rsp);
}
if (useTvResult(t, ni, mii)) {
emitStoreUninitNull(a, MISOFF(tvResult), mis_rsp);
} else if (debug) {
emitStoreInvalid(a, MISOFF(tvResult), mis_rsp);
}
}
SKTRACE(2, ni.source, "%s\n", __func__);
if (debug && m_vecState->needsMIState()) {
a. store_imm64_disp_reg64(0xfacefacefacefaceULL, MISOFF(ctx), mis_rsp);
}
// Check if val incref/decref is forced as a side effect of analysis
// converting the val from a stack input to a local input.
if (forceMValIncDec(t, ni, mii)) {
SKTRACE(2, ni.source, "%s %#lx force val incref\n",
__func__, long(a.code.frontier));
const DynLocation& val = *ni.inputs[0];
LazyScratchReg tmp(m_regMap);
PhysReg rVal = m_regMap.hasReg(val.location)
? m_regMap.getReg(val.location)
: m_regMap.allocReg(val.location, val.outerType(),
RegInfo::CLEAN);
if (val.isRef()) {
tmp.alloc();
emitDerefRef(a, rVal, r(tmp));
rVal = r(tmp);
}
// Copy val to tvVal for later assignment and decref.
assert(val.valueType() == KindOfArray);
emitStoreTypedValue(a, KindOfArray, rVal, MISOFF(tvVal), mis_rsp);
// Incref, offset by decref in emitMPost().
emitIncRef(rVal, KindOfArray);
}
// The base location is input 0 or 1, and the location code is stored
// separately from 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.
iInd = mii.valCount();
emitBaseOp(t, ni, mii, iInd, rBase);
++iInd;
// Iterate over all but the last member, which is consumed by a final
// operation.
for (mInd = 0; mInd < ni.immVecM.size() - 1; ++mInd) {
emitIntermediateOp(t, ni, mii, mInd, iInd, rBase);
emitRatchetRefs(t, ni, mii, mInd, r(rBase));
}
}
void TranslatorX64::emitMPost(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) {
SKTRACE(2, ni.source, "%s %#lx\n", __func__, long(a.code.frontier));
// Decref stack inputs. Some instructions push their topmost input as their
// final result; skip input 0 if it is a result.
for (unsigned i = firstDecrefInput(t, ni, mii); i < ni.inputs.size(); ++i) {
const DynLocation& input = *ni.inputs[i];
switch (input.location.space) {
case Location::Stack: {
DataType dt = input.outerType();
if (IS_REFCOUNTED_TYPE(dt)) {
SKTRACE(2, ni.source, "%s %#lx decref stack input %u, type %s\n",
__func__, long(a.code.frontier), i, tname(dt).c_str());
PhysReg pr = m_regMap.allocReg(input.location, dt, RegInfo::CLEAN);
emitDecRef(ni, pr, dt);
// XXX: can't this go away since we're about to invalidate
// popped stack locations after this call?
m_regMap.cleanSmashLoc(input.location);
}
break;
}
case Location::Local:
case Location::Litstr:
case Location::Litint:
case Location::This: {
// Do nothing.
SKTRACE(2, ni.source, "%s (no decref needed) input %u, loc(%s, %lld)\n",
__func__, i, input.location.spaceName(), input.location.offset);
break;
}
default: not_reached();
}
}
// Decref the val if incref/decref is forced as a side effect of analysis
// converting the val from a stack input to a local input.
if (forceMValIncDec(t, ni, mii)) {
SKTRACE(2, ni.source, "%s %#lx force val decref\n",
__func__, long(a.code.frontier));
ScratchReg rScratch(m_regMap);
a. load_reg64_disp_reg64(mis_rsp, MISOFF(tvVal) + TVOFF(m_data),
r(rScratch));
emitDecRef(ni, r(rScratch), KindOfArray);
}
// Clean up ratchet.
if (nLogicalRatchets(t, ni, mii) > 1) {
SKTRACE(2, ni.source, "%s %#lx decref tvRef2\n",
__func__, long(a.code.frontier));
emitDecRefGeneric(ni, mis_rsp, MISOFF(tvRef2));
}
if (nLogicalRatchets(t, ni, mii) > 0) {
SKTRACE(2, ni.source, "%s %#lx decref tvRef\n",
__func__, long(a.code.frontier));
emitDecRefGeneric(ni, mis_rsp, MISOFF(tvRef));
}
// Copy tvResult to final location if it was used.
if (useTvResult(t, ni, mii)) {
SKTRACE(2, ni.source, "%s %#lx copy tvResult\n",
__func__, long(a.code.frontier));
emitCopyToAligned(a, mis_rsp, MISOFF(tvResult),
rVmSp, vstackOffset(ni, mResultStackOffset(ni)));
invalidateOutStack(ni);
}
// Teleport val to final location if this instruction generates val.
if (teleportMVal(t, ni, mii)) {
assert(!useTvResult(t, ni, mii));
SKTRACE(2, ni.source, "%s %#lx teleport val\n",
__func__, long(a.code.frontier));
const DynLocation& val = *ni.inputs[0];
m_regMap.cleanSmashLoc(val.location);
PhysReg prVal;
int dispVal;
locToRegDisp(val.location, &prVal, &dispVal);
emitCopyToAligned(a, prVal, dispVal,
rVmSp, vstackOffset(ni, mResultStackOffset(ni)));
invalidateOutStack(ni);
}
}
template <KeyType keyType, bool unboxKey>
static inline void cGetElemImpl(TypedValue* base, TypedValue* key,
TypedValue* result,
MInstrState* mis) {
key = unbox<keyType, unboxKey>(key);
base = Elem<true, keyType>(*result, mis->tvRef, base, key);
if (base != result) {
// Save a copy of the result.
tvDup(base, result);
}
if (result->m_type == KindOfRef) {
tvUnbox(result);
}
}
#define HELPER_TABLE(m) \
/* name hot key unboxKey */ \
m(cGetElemC, , AnyKey, false) \
m(cGetElemI, , IntKey, false) \
m(cGetElemL, HOT_FUNC_VM, AnyKey, true) \
m(cGetElemLI, HOT_FUNC_VM, IntKey, true) \
m(cGetElemLS, , StrKey, true) \
m(cGetElemS, HOT_FUNC_VM, StrKey, false)
#define ELEM(nm, hot, ...) \
hot \
static void nm(TypedValue* base, TypedValue* key, \
TypedValue* result, \
MInstrState* mis) { \
cGetElemImpl<__VA_ARGS__>(base, key, result, mis); \
}
HELPER_TABLE(ELEM)
#undef ELEM
void TranslatorX64::emitCGetElem(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii, unsigned mInd,
unsigned iInd, PhysReg rBase) {
SKTRACE(2, ni.source, "%s %#lx mInd=%u, iInd=%u\n",
__func__, long(a.code.frontier), mInd, iInd);
const DynLocation& memb = *ni.inputs[iInd];
m_regMap.cleanSmashLoc(memb.location);
const DynLocation& result = *ni.outStack;
bool useTvR = useTvResult(t, ni, mii);
PREP_RESULT(useTvR);
typedef void (*OpFunc)(TypedValue*, TypedValue*, TypedValue*, MInstrState*);
BUILD_OPTABH(getKeyTypeIS(memb), memb.isRef());
EMIT_RCALL(a, ni, opFunc, R(rBase), ISML(memb), RESULT(useTvR), R(mis_rsp));
assert(!ni.outLocal);
invalidateOutStack(ni);
}
#undef HELPER_TABLE
bool
isNormalPropertyAccess(const NormalizedInstruction& i,
int propInput,
int objInput) {
const LocationCode lcode = i.immVec.locationCode();
return
i.immVecM.size() == 1 &&
(lcode == LC || lcode == LL || lcode == LR || lcode == LH) &&
mcodeMaybePropName(i.immVecM[0]) &&
i.inputs[propInput]->isString() &&
i.inputs[objInput]->valueType() == KindOfObject;
}
bool
mInstrHasUnknownOffsets(const NormalizedInstruction& ni) {
const MInstrInfo& mii = getMInstrInfo(ni.mInstrOp());
unsigned mi = 0;
unsigned ii = mii.valCount() + 1;
for (; mi < ni.immVecM.size(); ++mi) {
MemberCode mc = ni.immVecM[mi];
if (mcodeMaybePropName(mc)) {
const Class* cls = nullptr;
if (getPropertyOffset(ni, cls, mii, mi, ii).offset == -1) {
return true;
}
++ii;
} else {
return true;
}
}
return false;
}
bool
isSupportedCGetM_LE(const NormalizedInstruction& i) {
if (i.inputs.size() > 2) return false;
// Return true iff this is a CGetM <H E> instruction where
// the base is an array and the key is an int or string
return
i.immVec.locationCode() == LL &&
i.immVecM.size() == 1 &&
mcodeMaybeArrayKey(i.immVecM[0]) &&
i.inputs[0]->valueType() == KindOfArray && // base
(i.inputs[1]->isInt() || i.inputs[1]->isString()); // key
}
bool
isSupportedCGetM_RE(const NormalizedInstruction& i) {
if (i.inputs.size() > 2) return false;
return
i.immVec.locationCode() == LR &&
i.immVecM.size() == 1 &&
mcodeMaybeArrayKey(i.immVecM[0]) &&
i.inputs[0]->valueType() == KindOfArray && // base
(i.inputs[1]->isInt() || i.inputs[1]->isString()); // key;
}
void
TranslatorX64::analyzeCGetM(Tracelet& t, NormalizedInstruction& ni) {
if (!RuntimeOption::EvalJitMGeneric) {
ni.m_txFlags = supportedPlan(isSupportedCGetM_LE(ni) ||
isSupportedCGetM_RE(ni));
return;
}
ni.m_txFlags = Supported;
ni.manuallyAllocInputs = true;
}
void
TranslatorX64::emitArrayElem(const NormalizedInstruction& i,
const DynLocation* baseInput,
PhysReg baseReg,
const DynLocation* keyIn,
const Location& outLoc) {
// Let the array helpers handle refcounting logic: key down,
// return value up.
SKTRACE(1, i.source, "emitCGetM: committed to unary load\n");
bool decRefBase = baseInput->isStack();
PhysReg decRefReg = InvalidReg;
bool stackSavedDecRef = false;
if (decRefBase) {
// We'll need to decref the base after the call. Make sure we hold
// on to the value if it's a variant or from a global.
if (baseInput->isRef()) {
decRefReg = getReg(baseInput->location);
}
if (decRefReg != noreg && kCallerSaved.contains(decRefReg)) {
stackSavedDecRef = true;
a. push(decRefReg);
a. sub_imm32_reg64(8, unsafe_rsp);
}
}
int flags = 0;
if (keyIn->isString()) {
if (keyIn->isStack()) flags |= DecRefKey;
if (!i.hasConstImm) flags |= CheckInts;
}
if (false) { // type-check
ArrayData *a = nullptr;
TypedValue tv;
array_getm_i(a, 1, &tv);
StringData *sd = nullptr;
array_getm_s(a, sd, &tv, 0);
}
if (keyIn->isInt()) {
EMIT_CALL(a, (void*)array_getm_i, R(baseReg), V(keyIn->location),
A(outLoc));
} else {
EMIT_CALL(a, (void*)array_getm_s, R(baseReg), V(keyIn->location),
A(outLoc), IMM(flags));
}
recordReentrantCall(i);
m_regMap.invalidate(outLoc);
if (decRefBase) {
// For convenience of decRefs, the helpers return the ArrayData*.
PhysReg base = rax;
// but if it was boxed or from a global, we need to get the
// original address back...
if (decRefReg != noreg) {
if (stackSavedDecRef) {
a. add_imm32_reg64(8, unsafe_rsp);
a. pop(rax);
} else {
base = decRefReg;
}
}
emitDecRef(i, base, KindOfArray);
}
}
void
TranslatorX64::translateCGetM(const Tracelet& t,
const NormalizedInstruction& i) {
assert(i.inputs.size() >= 2);
assert(i.outStack);
if (isSupportedCGetM_LE(i) || isSupportedCGetM_RE(i)) {
const DynLocation& base = *i.inputs[0];
const DynLocation& key = *i.inputs[1];
int args[2];
args[0] = base.isRef() ? ArgAnyReg : 0;
args[1] = 1;
allocInputsForCall(i, args);
PhysReg baseReg = getReg(base.location);
LazyScratchReg baseScratch(m_regMap);
if (base.isRef()) {
baseScratch.alloc();
emitDerefRef(a, baseReg, r(baseScratch));
baseReg = r(baseScratch);
}
Stats::emitInc(a, Stats::Tx64_CGetMArray);
emitArrayElem(i, &base, baseReg, &key, i.outStack->location);
return;
}
Stats::emitInc(a, Stats::Tx64_CGetMGeneric);
translateMInstr(OpCGetM);
}
void TranslatorX64::analyzeVGetM(Tracelet& t, NormalizedInstruction& ni) {
if (!RuntimeOption::EvalJitMGeneric) {
ni.m_txFlags = Interp;
return;
}
ni.m_txFlags = Supported;
ni.manuallyAllocInputs = true;
}
void TranslatorX64::translateVGetM(const Tracelet& t,
const NormalizedInstruction& ni) {
translateMInstr(OpVGetM);
}
static bool isSupportedIssetMFast(const NormalizedInstruction& ni) {
return ni.inputs.size() == 2 &&
ni.immVec.locationCode() == LL &&
ni.immVecM.size() == 1 &&
mcodeMaybeArrayKey(ni.immVecM[0]) &&
ni.inputs[0]->valueType() == KindOfArray &&
(ni.inputs[1]->isString() || ni.inputs[1]->isInt());
}
void
TranslatorX64::analyzeIssetM(Tracelet& t, NormalizedInstruction& ni) {
bool fast = isSupportedIssetMFast(ni);
if (!RuntimeOption::EvalJitMGeneric) {
ni.m_txFlags = supportedPlan(fast);
return;
}
ni.m_txFlags = Supported;
if (!fast) {
assert(ni.isSupported());
ni.manuallyAllocInputs = true;
}
}
void TranslatorX64::translateIssetMFast(const Tracelet& t,
const NormalizedInstruction& ni) {
const DynLocation& base = *ni.inputs[0];
const DynLocation& key = *ni.inputs[1];
PhysReg arrReg = getReg(base.location);
LazyScratchReg scratch(m_regMap);
if (base.isRef()) {
scratch.alloc();
emitDerefRef(a, arrReg, r(scratch));
arrReg = r(scratch);
SKTRACE(1, ni.source, "loaded variant\n");
}
typedef uint64_t (*HelperFunc)(const void* arr, StringData* sd);
HelperFunc helper = nullptr;
if (key.isInt()) {
helper = (HelperFunc)array_issetm_i;
} else {
bool doIntCheck = !ni.hasConstImm;
if (key.isStack()) {
// on the stack, we need to decref strings.
helper = doIntCheck ? array_issetm_s : array_issetm_s_fast;
} else {
assert(key.isLocal() || key.location.isLiteral());
helper = doIntCheck ? array_issetm_s0 : array_issetm_s0_fast;
}
}
assert(helper);
// The array helpers can reenter; need to sync state.
EMIT_RCALL(a, ni, helper, R(arrReg), V(key.location));
// We didn't bother allocating the single output reg above;
// it lives in rax now.
assert(ni.outStack && !ni.outLocal);
assert(ni.outStack->outerType() == KindOfBoolean);
m_regMap.bind(rax, ni.outStack->location, KindOfBoolean,
RegInfo::DIRTY);
}
void TranslatorX64::translateIssetM(const Tracelet& t,
const NormalizedInstruction& ni) {
if (isSupportedIssetMFast(ni)) {
translateIssetMFast(t, ni);
} else {
assert(ni.isSupported());
translateMInstr(OpIssetM);
}
}
void TranslatorX64::analyzeEmptyM(Tracelet& t, NormalizedInstruction& ni) {
if (!RuntimeOption::EvalJitMGeneric) {
ni.m_txFlags = Interp;
return;
}
ni.m_txFlags = Supported;
ni.manuallyAllocInputs = true;
}
void TranslatorX64::translateEmptyM(const Tracelet& t,
const NormalizedInstruction& ni) {
translateMInstr(OpEmptyM);
}
/*
* SetM. We care about two cases in particular: objects and arrays.
*/
static inline bool
isSupportedSetMArray(const NormalizedInstruction& i) {
const ImmVector& iv = i.immVec;
const LocationCode lcode = iv.locationCode();
return
i.immVecM.size() == 1 && lcode == LL &&
mcodeMaybeArrayKey(i.immVecM[0]) &&
i.inputs.size() == 3 &&
(i.inputs[0]->isStack() || i.grouped) && // rhs
i.inputs[1]->valueType() == KindOfArray && // base
(i.inputs[2]->isInt() || i.inputs[2]->isString()); // key
}
void
TranslatorX64::analyzeSetM(Tracelet& t, NormalizedInstruction& i) {
// TODO: We could be more aggressive in the translation plans when the
// lefthand side isn't refcounted.
if (!RuntimeOption::EvalJitMGeneric) {
i.m_txFlags = supportedPlan(isSupportedSetMArray(i));
if (i.m_txFlags) {
i.manuallyAllocInputs = true;
}
return;
}
i.m_txFlags = Supported;
i.manuallyAllocInputs = true;
}
// Check if val incref/decref is forced as a side effect of analysis
// converting the val from a stack input to a local input. The conversion from
// stack input to local input is beneficial both because it avoids push/pop
// overhead and because it avoids incref/decref, but if the val is an array,
// the incref/decref omission is safe only if the val can't possibly alias the
// base or any intermediate results.
//
// This method handles only the single-dim case.
bool TranslatorX64::forceMValIncDec(const NormalizedInstruction& ni,
const DynLocation& base,
const DynLocation& val) const {
assert(base.valueType() == KindOfArray || val.valueType() == KindOfArray);
if (val.isLocal() &&
(val.location == base.location ||
(val.isRef() && base.isRef() &&
val.valueType() == base.valueType()))) {
// We don't allow a local input unless we also folded a following pop.
assert(!ni.outStack);
// We can't avoid the inc/dec on val in the case of:
// $a[...] = $a;
return true;
}
return false;
}
// Same semantics as above, but this method handles the general case.
bool TranslatorX64::forceMValIncDec(const Tracelet& t,
const NormalizedInstruction& ni,
const MInstrInfo& mii) const {
if (mii.valCount() == 1) {
const DynLocation& val = *ni.inputs[0];
if (val.valueType() != KindOfArray) {
SKTRACE(2, ni.source, "%s false (val is non-array)\n", __func__);
return false;
}
if (ni.immVecM.size() == 1) {
const DynLocation& base = *ni.inputs[1];
SKTRACE(2, ni.source, "%s %s\n",
__func__, forceMValIncDec(ni, base, val) ? "true" : "false");
return forceMValIncDec(ni, base, val);
}
assert(ni.immVecM.size() > 1);
if (val.isLocal()) {
SKTRACE(2, ni.source, "%s true (val may alias one or more bases)\n",
__func__, long(a.code.frontier));
return true;
}
assert(val.isStack());
SKTRACE(2, ni.source, "%s false (val is on stack)\n",
__func__, long(a.code.frontier));
return false;
}
SKTRACE(2, ni.source, "%s false (no val)\n", __func__);
return false;
}
void
TranslatorX64::translateSetMArray(const Tracelet& t,
const NormalizedInstruction& i) {
assert(i.inputs.size() == 3);
const DynLocation& val = *i.inputs[0];
const DynLocation& arr = *i.inputs[1];
const DynLocation& key = *i.inputs[2];
assert(arr.isLocal());
assert(val.isLocal() || !val.isRef());
assert(!i.outStack || !val.isLocal());
bool forceIncDec = forceMValIncDec(i, arr, val);
const Location valLoc = val.location;
const Location arrLoc = arr.location;
const Location keyLoc = key.location;
// The array_setm helpers will decRef any old value that is
// overwritten if appropriate. If copy-on-write occurs, it will also
// incRef the new array and decRef the old array for us. Finally,
// some of the array_setm helpers will decRef the key if it is a
// string (for cases where the key is not a local), while others do
// not (for cases where the key is a local).
bool useBoxedForm = arr.isRef();
void* fptr;
if (false) { // helper type-checks
RefData* ref = nullptr;
ArrayData* arr = nullptr;
TypedValue* rhs = nullptr;
StringData* strKey = nullptr;
UNUSED ArrayData* ret;
ret = array_setm_ik1_v(ref, arr, 12, rhs);
ret = array_setm_sk1_v(ref, arr, strKey, rhs);
ret = array_setm_sk1_v0(ref, arr, strKey, rhs);
ret = array_setm_s0k1_v(ref, arr, strKey, rhs);
ret = array_setm_s0k1_v0(ref, arr, strKey, rhs);
ret = array_setm_s0k1nc_v(ref, arr, strKey, rhs);
ret = array_setm_s0k1nc_v0(ref, arr, strKey, rhs);
}
/*
* The value can be passed as A or V according to:
*
* | val.isRef() | !val.isRef()
* ----------------------+-----------------+-----------------------------
* value | V(valLoc) | A(valLoc)
*/
bool valIsRef = val.isRef();
int args[3];
args[0] = valIsRef ? 3 : ArgDontAllocate;
args[1] = useBoxedForm ? 0 : 1;
args[2] = 2;
allocInputsForCall(i, args);
bool decRefKey = key.rtt.isString() && keyLoc.isStack();
bool decRefValue = forceIncDec ||
(!i.outStack && !val.isLocal() &&
IS_REFCOUNTED_TYPE(val.rtt.valueType()));
fptr = decRefValue ?
(key.rtt.isString() ?
(decRefKey ? (void*)array_setm_sk1_v0 :
(i.hasConstImm ? (void*)array_setm_s0k1nc_v0 :
(void*)array_setm_s0k1_v0)) :
(void*)array_setm_ik1_v0) :
(key.rtt.isString() ?
(decRefKey ? (void*)array_setm_sk1_v :
(i.hasConstImm ? (void*)array_setm_s0k1nc_v :
(void*)array_setm_s0k1_v)) :
(void*)array_setm_ik1_v);
if (forceIncDec) {
LazyScratchReg tmp(m_regMap);
PhysReg rhsReg = getReg(valLoc);
if (valIsRef) {
tmp.alloc();
emitDerefRef(a, rhsReg, r(tmp));
rhsReg = r(tmp);
}
emitIncRef(rhsReg, KindOfArray);
}
EMIT_CALL(a, fptr,
useBoxedForm ? V(arrLoc) : IMM(0),
useBoxedForm ? DEREF(arrLoc) : V(arrLoc),
V(keyLoc),
valIsRef ? VREF(valLoc) : A(valLoc));
recordReentrantCall(i);
// If we did not used boxed form, we need to tell the register allocator
// to associate rax with arrLoc.
if (!useBoxedForm) {
// The array_setm helper returns the up-to-date array pointer in rax.
// Therefore, we can bind rax to arrLoc and mark it as dirty.
m_regMap.markAsClean(arrLoc);
m_regMap.bind(rax, arrLoc, KindOfArray, RegInfo::DIRTY);
}
if (i.outStack) {
assert(!val.isRef());
// Bring the output value into its correct location on the stack.
// Note that this has to happen after all of the above, since it
// needs to read the key from this location.
m_regMap.allocInputReg(i, 0);
m_regMap.bind(getReg(valLoc), i.outStack->location,
val.outerType(), RegInfo::DIRTY);
}
}
void TranslatorX64::translateMInstr(Op op) {
assert(RuntimeOption::EvalJitMGeneric);
const MInstrInfo& mii = getMInstrInfo(op);
const Tracelet& t = *m_curTrace;
const NormalizedInstruction& ni = *m_curNI;
SKTRACE(2, ni.source, "translate%sM\n", mii.name());
m_vecState = new MVecTransState();
Deleter<MVecTransState> stateDeleter(&m_vecState);
unsigned mInd, iInd;
LazyScratchReg rBase(m_regMap);
emitMPre(t, ni, mii, mInd, iInd, rBase);
emitFinalMOp(t, ni, mii, mInd, iInd, rBase);
emitMPost(t, ni, mii);
}
void
TranslatorX64::translateSetM(const Tracelet& t,
const NormalizedInstruction& i) {
if (isSupportedSetMArray(i)) {
translateSetMArray(t, i);
return;
}
translateMInstr(OpSetM);
}
void TranslatorX64::analyzeSetOpM(Tracelet& t, NormalizedInstruction& ni) {
if (!RuntimeOption::EvalJitMGeneric) {
ni.m_txFlags = Interp;
return;
}
ni.m_txFlags = Supported;
ni.manuallyAllocInputs = true;
}
void TranslatorX64::translateSetOpM(const Tracelet& t,
const NormalizedInstruction& ni) {
translateMInstr(OpSetOpM);
}
void TranslatorX64::analyzeIncDecM(Tracelet& t, NormalizedInstruction& ni) {
if (!RuntimeOption::EvalJitMGeneric) {
ni.m_txFlags = Interp;
return;
}
ni.m_txFlags = Supported;
ni.manuallyAllocInputs = true;
}
void TranslatorX64::translateIncDecM(const Tracelet& t,
const NormalizedInstruction& ni) {
translateMInstr(OpIncDecM);
}
void
TranslatorX64::analyzeUnsetM(Tracelet& t, NormalizedInstruction& ni) {
if (!RuntimeOption::EvalJitMGeneric) {
ni.m_txFlags = Interp;
return;
}
ni.m_txFlags = Supported;
ni.manuallyAllocInputs = true;
}
void
TranslatorX64::translateUnsetM(const Tracelet& t,
const NormalizedInstruction& ni) {
translateMInstr(OpUnsetM);
}
void TranslatorX64::analyzeBindM(Tracelet& t, NormalizedInstruction& ni) {
if (!RuntimeOption::EvalJitMGeneric) {
ni.m_txFlags = Interp;
return;
}
ni.m_txFlags = Supported;
ni.manuallyAllocInputs = true;
}
void TranslatorX64::translateBindM(const Tracelet& t,
const NormalizedInstruction& ni) {
translateMInstr(OpBindM);
}
void
TranslatorX64::analyzeFPassM(Tracelet& t, NormalizedInstruction& ni) {
if (!RuntimeOption::EvalJitMGeneric) {
if (!ni.preppedByRef) {
analyzeCGetM(t, ni);
} else {
analyzeVGetM(t, ni);
}
return;
}
ni.m_txFlags = Supported;
ni.manuallyAllocInputs = true;
}
void
TranslatorX64::translateFPassM(const Tracelet& t,
const NormalizedInstruction& ni) {
assert(ni.inputs.size() >= 1);
assert(ni.outStack && !ni.outLocal);
if (!ni.preppedByRef) {
translateCGetM(t, ni);
} else {
translateVGetM(t, ni);
}
}
}}}