Arquivos
hhvm/hphp/runtime/vm/jit/hhbctranslator.cpp
T
Mirek Klimos 5a98277b6c Replace ContSend by CGetL+CUnsetL
The behavior of ContSend is equivalent to CGetL+CUnsetL. Let's kill this
unnecessary opcode. Let's remove this logic from ContRaise opcode as
well.
2013-07-02 11:46:25 -07:00

3942 linhas
118 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/vm/jit/hhbctranslator.h"
#include "hphp/util/trace.h"
#include "hphp/runtime/ext/ext_closure.h"
#include "hphp/runtime/ext/ext_continuation.h"
#include "hphp/runtime/vm/jit/translator-runtime.h"
#include "hphp/runtime/vm/jit/translator-x64.h"
#include "hphp/runtime/base/stats.h"
#include "hphp/runtime/vm/unit.h"
#include "hphp/runtime/vm/runtime.h"
#include "hphp/runtime/vm/jit/irfactory.h"
#include "hphp/runtime/vm/jit/codegen.h" // ArrayIdx helpers
// Include last to localize effects to this file
#include "hphp/util/assert_throw.h"
namespace HPHP {
namespace JIT {
TRACE_SET_MOD(hhir);
using namespace HPHP::Transl;
//////////////////////////////////////////////////////////////////////
namespace {
bool classIsUnique(const Class* cls) {
return RuntimeOption::RepoAuthoritative &&
cls &&
(cls->attrs() & AttrUnique);
}
bool classIsPersistent(const Class* cls) {
return RuntimeOption::RepoAuthoritative &&
cls &&
(cls->attrs() & AttrPersistent);
}
bool classIsUniqueNormalClass(const Class* cls) {
return classIsUnique(cls) &&
!(cls->attrs() & (AttrInterface | AttrTrait));
}
}
//////////////////////////////////////////////////////////////////////
HhbcTranslator::HhbcTranslator(IRFactory& irFactory,
Offset startOffset,
uint32_t initialSpOffsetFromFp,
const Func* func)
: m_irFactory(irFactory)
, m_tb(new TraceBuilder(startOffset,
initialSpOffsetFromFp,
m_irFactory,
func))
, m_bcStateStack {BcState(startOffset, func)}
, m_startBcOff(startOffset)
, m_lastBcOff(false)
, m_hasExit(false)
, m_stackDeficit(0)
{
updateMarker();
auto const fp = gen(DefFP);
gen(DefSP, StackOffset(initialSpOffsetFromFp), fp);
}
bool HhbcTranslator::classIsUniqueOrCtxParent(const Class* cls) const {
if (!cls) return false;
if (classIsUnique(cls)) return true;
if (!curClass()) return false;
return curClass()->classof(cls);
}
bool HhbcTranslator::classIsPersistentOrCtxParent(const Class* cls) const {
if (!cls) return false;
if (classIsPersistent(cls)) return true;
if (!curClass()) return false;
return curClass()->classof(cls);
}
ArrayData* HhbcTranslator::lookupArrayId(int arrId) {
return curUnit()->lookupArrayId(arrId);
}
StringData* HhbcTranslator::lookupStringId(int strId) {
return curUnit()->lookupLitstrId(strId);
}
Func* HhbcTranslator::lookupFuncId(int funcId) {
return curUnit()->lookupFuncId(funcId);
}
PreClass* HhbcTranslator::lookupPreClassId(int preClassId) {
return curUnit()->lookupPreClassId(preClassId);
}
const NamedEntityPair& HhbcTranslator::lookupNamedEntityPairId(int id) {
return curUnit()->lookupNamedEntityPairId(id);
}
const NamedEntity* HhbcTranslator::lookupNamedEntityId(int id) {
return curUnit()->lookupNamedEntityId(id);
}
SSATmp* HhbcTranslator::push(SSATmp* tmp) {
assert(tmp);
m_evalStack.push(tmp);
return tmp;
}
void HhbcTranslator::refineType(SSATmp* tmp, Type type) {
// If type is more refined than tmp's type, reset tmp's type to type
IRInstruction* inst = tmp->inst();
if (type.strictSubtypeOf(tmp->type())) {
// If tmp is incref or move, then chase down its src
Opcode opc = inst->op();
if (opc == Mov || opc == IncRef) {
refineType(inst->src(0), type);
tmp->setType(outputType(inst));
} else if (tmp->type().isNull() && type.isNull()) {
// Refining Null to Uninit or InitNull is supported
tmp->setType(type);
} else {
// At this point, we have no business refining the type of any
// instructions other than the following, which all control
// their destination type via a type parameter.
//
// FIXME: I think most of these shouldn't be possible still
// (except LdStack?).
assert(opc == LdLoc || opc == LdStack ||
opc == LdMem || opc == LdProp ||
opc == LdRef);
inst->setTypeParam(type);
tmp->setType(type);
assert(outputType(inst) == type);
}
}
}
SSATmp* HhbcTranslator::pop(Type type) {
SSATmp* opnd = m_evalStack.pop();
if (opnd == nullptr) {
uint32_t stackOff = m_stackDeficit;
m_stackDeficit++;
return gen(LdStack, type, StackOffset(stackOff), m_tb->sp());
}
// Refine the type of the temp given the information we have from
// `type'. This case can occur if we did an extendStack() and
// didn't know the type of the intermediate values yet (see below).
refineType(opnd, type);
return opnd;
}
void HhbcTranslator::discard(unsigned n) {
for (unsigned i = 0; i < n; ++i) {
pop(Type::Gen | Type::Cls);
}
}
// type is the type expected on the stack.
void HhbcTranslator::popDecRef(Type type) {
if (SSATmp* src = m_evalStack.pop()) {
gen(DecRef, src);
return;
}
gen(DecRefStack, StackOffset(m_stackDeficit), type, m_tb->sp());
m_stackDeficit++;
}
// We don't know what type description to expect for the stack
// locations before index, so we use a generic type when popping the
// intermediate values. If it ends up creating a new LdStack,
// refineType during a later pop() or top() will fix up the type to
// the known type.
void HhbcTranslator::extendStack(uint32_t index, Type type) {
if (index == 0) {
push(pop(type));
return;
}
SSATmp* tmp = pop(Type::Gen | Type::Cls);
extendStack(index - 1, type);
push(tmp);
}
SSATmp* HhbcTranslator::top(Type type, uint32_t index) {
SSATmp* tmp = m_evalStack.top(index);
if (!tmp) {
extendStack(index, type);
tmp = m_evalStack.top(index);
}
assert(tmp);
refineType(tmp, type);
return tmp;
}
void HhbcTranslator::replace(uint32_t index, SSATmp* tmp) {
m_evalStack.replace(index, tmp);
}
Type HhbcTranslator::topType(uint32_t idx) const {
if (idx < m_evalStack.size()) {
return m_evalStack.top(idx)->type();
} else {
auto stkVal = getStackValue(m_tb->sp(),
idx - m_evalStack.size() + m_stackDeficit);
if (stkVal.knownType.equals(Type::None)) return Type::Gen;
return stkVal.knownType;
}
}
/*
* When doing gen-time inlining, we set up a series of IR instructions
* that looks like this:
*
* fp0 = DefFP
* sp0 = DefSP<offset>
*
* // ... normal stuff happens ...
* // sp_pre = some SpillStack, or maybe the DefSP
*
* // FPI region:
* sp1 = SpillStack sp_pre, ...
* sp2 = SpillFrame sp1, ...
* // ... possibly more spillstacks due to argument expressions
* sp3 = SpillStack sp2, -argCount
* fp2 = DefInlineFP<func,retBC,retSP> sp2 sp1
* sp4 = ReDefSP<numLocals> fp2
*
* // ... callee body ...
*
* = InlineReturn fp2
*
* sp5 = ReDefSP<returnOffset> fp0 sp1
*
* The rest of the code then depends on sp5, and not any of the StkPtr
* tree going through the callee body. The sp5 tmp has the same view
* of the stack as sp1 did, which represents what the stack looks like
* before the return address is pushed but after the activation record
* is popped.
*
* In DCE we attempt to remove the SpillFrame/InlineReturn/DefInlineFP
* instructions if they aren't needed.
*/
void HhbcTranslator::beginInlining(unsigned numParams,
const Func* target,
Offset returnBcOffset) {
assert(!m_fpiStack.empty() &&
"Inlining does not support calls with the FPush* in a different Tracelet");
assert(!target->isGenerator() && "Generator stack handling not implemented");
FTRACE(1, "[[[ begin inlining: {}\n", target->fullName()->data());
SSATmp* params[numParams];
for (unsigned i = 0; i < numParams; ++i) {
params[numParams - i - 1] = popF();
}
auto const prevSP = m_fpiStack.top().first;
auto const prevSPOff = m_fpiStack.top().second;
auto const calleeSP = spillStack();
DefInlineFPData data;
data.target = target;
data.retBCOff = returnBcOffset;
data.retSPOff = prevSPOff;
auto const calleeFP = gen(DefInlineFP, data, calleeSP, prevSP);
m_bcStateStack.emplace_back(target->base(), target);
gen(ReDefSP, StackOffset(target->numLocals()), m_tb->fp(), m_tb->sp());
profileFunctionEntry("Inline");
for (unsigned i = 0; i < numParams; ++i) {
gen(StLoc, LocalId(i), calleeFP, params[i]);
}
for (unsigned i = numParams; i < target->numLocals(); ++i) {
/*
* Here we need to be generating hopefully-dead stores to
* initialize non-parameter locals to KindOfUnknownin case we have
* to leave the trace.
*/
always_assert(0 && "unimplemented");
gen(StLoc, LocalId(i), calleeFP, m_tb->genDefUninit());
}
updateMarker();
}
bool HhbcTranslator::isInlining() const {
return m_bcStateStack.size() > 1;
}
BCMarker HhbcTranslator::makeMarker(Offset bcOff) {
int32_t stackOff = m_tb->spOffset() +
m_evalStack.numCells() - m_stackDeficit;
FTRACE(2, "makeMarker: bc {} sp {} fn {}\n",
bcOff, stackOff, curFunc()->fullName()->data());
return BCMarker{ curFunc(), bcOff, stackOff };
}
void HhbcTranslator::updateMarker() {
m_tb->setMarker(makeMarker(bcOff()));
}
void HhbcTranslator::profileFunctionEntry(const char* category) {
static const bool enabled = Stats::enabledAny() &&
getenv("HHVM_STATS_FUNCENTRY");
if (!enabled) return;
gen(
IncStatGrouped,
cns(StringData::GetStaticString("FunctionEntry")),
cns(StringData::GetStaticString(category)),
cns(1)
);
}
void HhbcTranslator::profileInlineFunctionShape(const std::string& str) {
gen(
IncStatGrouped,
cns(StringData::GetStaticString("InlineShape")),
cns(StringData::GetStaticString(str)),
cns(1)
);
}
void HhbcTranslator::profileSmallFunctionShape(const std::string& str) {
gen(
IncStatGrouped,
cns(StringData::GetStaticString("SmallFunctions")),
cns(StringData::GetStaticString(str)),
cns(1)
);
}
void HhbcTranslator::profileFailedInlShape(const std::string& str) {
gen(
IncStatGrouped,
cns(StringData::GetStaticString("FailedInl")),
cns(StringData::GetStaticString(str)),
cns(1)
);
}
void HhbcTranslator::setBcOff(Offset newOff, bool lastBcOff) {
if (isInlining()) assert(!lastBcOff);
if (newOff != bcOff()) {
m_bcStateStack.back().bcOff = newOff;
updateMarker();
}
m_lastBcOff = lastBcOff;
}
void HhbcTranslator::emitPrint() {
Type type = topC()->type();
if (type.subtypeOfAny(Type::Int, Type::Bool, Type::Null, Type::Str)) {
auto const cell = popC();
Opcode op;
if (type.isString()) {
op = PrintStr;
} else if (type.subtypeOf(Type::Int)) {
op = PrintInt;
} else if (type.subtypeOf(Type::Bool)) {
op = PrintBool;
} else {
assert(type.isNull());
op = Nop;
}
// the print helpers decref their arg, so don't decref pop'ed value
if (op != Nop) {
gen(op, cell);
}
push(cns(1));
} else {
emitInterpOne(Type::Int, 1);
}
}
void HhbcTranslator::emitUnboxRAux() {
Block* exit = getExitTrace()->front();
SSATmp* srcBox = popR();
SSATmp* unboxed = gen(Unbox, exit, srcBox);
if (unboxed == srcBox) {
// If the Unbox ended up being a noop, don't bother refcounting
push(unboxed);
} else {
pushIncRef(unboxed);
gen(DecRef, srcBox);
}
}
void HhbcTranslator::emitUnboxR() {
emitUnboxRAux();
}
void HhbcTranslator::emitThis() {
if (!curClass()) {
emitInterpOne(Type::Obj, 0); // will throw a fatal
return;
}
pushIncRef(gen(LdThis, getExitSlowTrace(), m_tb->fp()));
}
void HhbcTranslator::emitCheckThis() {
if (!curClass()) {
emitInterpOne(Type::None, 0); // will throw a fatal
return;
}
gen(LdThis, getExitSlowTrace(), m_tb->fp());
}
void HhbcTranslator::emitBareThis(int notice) {
// We just exit the trace in the case $this is null. Before exiting
// the trace, we could also push null onto the stack and raise a
// notice if the notice argument is set. By exiting the trace when
// $this is null, we can be sure in the rest of the trace that we
// have the this object on top of the stack, and we can eliminate
// further null checks of this.
if (!curClass()) {
emitInterpOne(Type::InitNull, 0); // will raise notice and push null
return;
}
pushIncRef(gen(LdThis, getExitSlowTrace(), m_tb->fp()));
}
void HhbcTranslator::emitArray(int arrayId) {
push(cns(lookupArrayId(arrayId)));
}
void HhbcTranslator::emitNewArray(int capacity) {
if (capacity == 0) {
push(cns(HphpArray::GetStaticEmptyArray()));
} else {
push(gen(NewArray, cns(capacity)));
}
}
void HhbcTranslator::emitNewTuple(int numArgs) {
// The new_tuple helper function needs array values passed to it
// via the stack. We use spillStack() to flush the eval stack and
// obtain a pointer to the topmost item; if over-flushing becomes
// a problem then we should refactor the NewTuple opcode to take
// its values directly as SSA operands.
SSATmp* sp = spillStack();
for (int i = 0; i < numArgs; i++) popC();
push(gen(NewTuple, cns(numArgs), sp));
}
void HhbcTranslator::emitArrayAdd() {
Type type1 = topC(0)->type();
Type type2 = topC(1)->type();
if (!type1.isArray() || !type2.isArray()) {
// This happens when we have a prior spillstack that optimizes away
// its spilled values because they were already on the stack. This
// prevents us from getting to type of the SSATmps popped from the
// eval stack. Most likely we had an interpone before this
// instruction.
emitInterpOne(Type::Arr, 2);
return;
}
SSATmp* tr = popC();
SSATmp* tl = popC();
// The ArrayAdd helper decrefs its args, so don't decref pop'ed values.
push(gen(ArrayAdd, tl, tr));
}
void HhbcTranslator::emitAddElemC() {
// The AddElem* instructions decref their args, so don't decref
// pop'ed values.
auto kt = topC(1)->type();
Opcode op;
if (kt.subtypeOf(Type::Int)) {
op = AddElemIntKey;
} else if (kt.isString()) {
op = AddElemStrKey;
} else {
emitInterpOne(Type::Arr, 3);
return;
}
auto const val = popC();
auto const key = popC();
auto const arr = popC();
push(gen(op, arr, key, val));
}
void HhbcTranslator::emitAddNewElemC() {
if (!topC(1)->isA(Type::Arr)) {
return emitInterpOne(Type::Arr, 2);
}
auto const val = popC();
auto const arr = popC();
// The AddNewElem helper decrefs its args, so don't decref pop'ed values.
push(gen(AddNewElem, arr, val));
}
void HhbcTranslator::emitNewCol(int type, int numElems) {
emitInterpOne(Type::Obj, 0);
}
void HhbcTranslator::emitColAddElemC() {
emitInterpOne(Type::Obj, 3);
}
void HhbcTranslator::emitColAddNewElemC() {
emitInterpOne(Type::Obj, 2);
}
void HhbcTranslator::emitCns(uint32_t id) {
StringData* name = curUnit()->lookupLitstrId(id);
SSATmp* cnsNameTmp = cns(name);
const TypedValue* tv = Unit::lookupPersistentCns(name);
SSATmp* result = nullptr;
Type cnsType = Type::Cell;
if (tv) {
result =
// KindOfUninit is a dynamic system constant. always a slow
// lookup.
tv->m_type == KindOfUninit
? gen(LookupCns, cnsType, cnsNameTmp)
: staticTVCns(tv);
} else {
SSATmp* c1 = gen(LdCns, cnsType, cnsNameTmp);
result = m_tb->cond(
curFunc(),
[&] (Block* taken) { // branch
gen(CheckInit, taken, c1);
},
[&] { // Next: LdCns hit in TC
return c1;
},
[&] { // Taken: miss in TC, do lookup & init
m_tb->hint(Block::Hint::Unlikely);
return gen(LookupCns, getCatchTrace(), cnsType, cnsNameTmp);
}
);
}
push(result);
}
void HhbcTranslator::emitCnsE(uint32_t id) {
PUNT(CnsE);
}
void HhbcTranslator::emitCnsU(uint32_t id) {
PUNT(CnsU);
}
void HhbcTranslator::emitDefCns(uint32_t id) {
emitInterpOne(Type::Bool, 1);
}
void HhbcTranslator::emitConcat() {
SSATmp* tr = popC();
SSATmp* tl = popC();
// the concat helpers decref their args, so don't decref pop'ed values
push(gen(Concat, tl, tr));
}
void HhbcTranslator::emitDefCls(int cid, Offset after) {
emitInterpOne(Type::None, 0);
}
void HhbcTranslator::emitDefFunc(int fid) {
emitInterpOne(Type::None, 0);
}
void HhbcTranslator::emitLateBoundCls() {
Class* clss = curClass();
if (!clss) {
// no static context class, so this will raise an error
emitInterpOne(Type::Cls, 0);
return;
}
auto const ctx = gen(LdCtx, m_tb->fp(), cns(curFunc()));
push(gen(LdClsCtx, ctx));
}
void HhbcTranslator::emitSelf() {
Class* clss = curClass();
if (clss == nullptr) {
emitInterpOne(Type::Cls, 0);
} else {
push(cns(clss));
}
}
void HhbcTranslator::emitParent() {
auto const clss = curClass();
if (clss == nullptr || clss->parent() == nullptr) {
emitInterpOne(Type::Cls, 0);
} else {
push(cns(clss->parent()));
}
}
void HhbcTranslator::emitString(int strId) {
push(cns(lookupStringId(strId)));
}
void HhbcTranslator::emitInt(int64_t val) {
push(cns(val));
}
void HhbcTranslator::emitDouble(double val) {
push(cns(val));
}
void HhbcTranslator::emitNullUninit() {
push(m_tb->genDefUninit());
}
void HhbcTranslator::emitNull() {
push(m_tb->genDefInitNull());
}
void HhbcTranslator::emitTrue() {
push(cns(true));
}
void HhbcTranslator::emitFalse() {
push(cns(false));
}
void HhbcTranslator::emitInitThisLoc(int32_t id) {
if (!curClass()) {
// Do nothing if this is null
return;
}
SSATmp* tmpThis = gen(LdThis, getExitSlowTrace(), m_tb->fp());
gen(StLoc, LocalId(id), m_tb->fp(), gen(IncRef, tmpThis));
}
void HhbcTranslator::emitCGetL(int32_t id) {
IRTrace* exitTrace = getExitTrace();
pushIncRef(ldLocInnerWarn(id, exitTrace));
}
void HhbcTranslator::emitCGetL2(int32_t id) {
IRTrace* exitTrace = getExitTrace();
IRTrace* catchTrace = getCatchTrace();
SSATmp* oldTop = pop(Type::Gen);
pushIncRef(ldLocInnerWarn(id, exitTrace, catchTrace));
push(oldTop);
}
void HhbcTranslator::emitVGetL(int32_t id) {
auto value = ldLoc(id);
if (!value->type().isBoxed()) {
if (value->isA(Type::Uninit)) {
value = m_tb->genDefInitNull();
}
value = gen(Box, value);
gen(StLoc, LocalId(id), m_tb->fp(), value);
}
pushIncRef(value);
}
void HhbcTranslator::emitUnsetN() {
// No reason to punt, translator-x64 does emitInterpOne as well
emitInterpOne(Type::None, 1);
}
void HhbcTranslator::emitUnsetG(const StringData* gblName) {
// No reason to punt, translator-x64 does emitInterpOne as well
emitInterpOne(Type::None, 1);
}
void HhbcTranslator::emitUnsetL(int32_t id) {
auto const prev = ldLoc(id);
gen(StLoc, LocalId(id), m_tb->fp(), m_tb->genDefUninit());
gen(DecRef, prev);
}
void HhbcTranslator::emitBindL(int32_t id) {
auto const newValue = popV();
// Note that the IncRef must happen first, for correctness in a
// pseudo-main: the destructor could decref the value again after
// we've stored it into the local.
pushIncRef(newValue);
auto const oldValue = ldLoc(id);
gen(StLoc, LocalId(id), m_tb->fp(), newValue);
gen(DecRef, oldValue);
}
void HhbcTranslator::emitSetL(int32_t id) {
auto const exitTrace = getExitTrace();
auto const src = popC();
push(stLoc(id, exitTrace, src));
}
void HhbcTranslator::emitIncDecL(bool pre, bool inc, uint32_t id) {
IRTrace* exitTrace = getExitTrace();
auto const src = ldLocInner(id, exitTrace);
// Inc/Dec of a bool is a no-op.
if (src->isA(Type::Bool)) {
push(src);
return;
}
auto const res = emitIncDec(pre, inc, src);
stLoc(id, exitTrace, res);
}
// only handles integer or double inc/dec
SSATmp* HhbcTranslator::emitIncDec(bool pre, bool inc, SSATmp* src) {
assert(src->isA(Type::Int) || src->isA(Type::Dbl));
SSATmp* one = src->isA(Type::Int) ? cns(1) : cns(1.0);
SSATmp* res = inc ? gen(OpAdd, src, one) : gen(OpSub, src, one);
// no incref necessary on push since result is an int
push(pre ? res : src);
return res;
}
void HhbcTranslator::emitIncDecMem(bool pre,
bool inc,
SSATmp* propAddr,
IRTrace* exitTrace) {
// Handle only integer inc/dec for now
SSATmp* src = gen(LdMem, Type::Int, exitTrace, propAddr, cns(0));
// do the add and store back
SSATmp* res = emitIncDec(pre, inc, src);
// don't gen a dec ref or type store
gen(StMemNT, propAddr, cns(0), res);
}
static bool areBinaryArithTypesSupported(Opcode opc, Type t1, Type t2) {
switch (opc) {
case OpAdd:
case OpSub:
case OpMul: return t1.subtypeOfAny(Type::Int, Type::Bool, Type::Dbl) &&
t2.subtypeOfAny(Type::Int, Type::Bool, Type::Dbl);
case OpBitAnd:
case OpBitOr:
case OpBitXor:
return t1.subtypeOfAny(Type::Int, Type::Bool) &&
t2.subtypeOfAny(Type::Int, Type::Bool);
default:
not_reached();
}
}
void HhbcTranslator::emitSetOpL(Opcode subOpc, uint32_t id) {
auto const exitTrace = getExitTrace();
auto const loc = ldLocInnerWarn(id, exitTrace);
if (subOpc == Concat) {
/*
* The concat helpers decref their args, so don't decref pop'ed values
* and don't decref the old value held in the local. The concat helpers
* also incref their results, which will be consumed by the stloc. We
* need an extra incref for the push onto the stack.
*/
auto const val = popC();
auto const result = gen(Concat, loc, val);
pushIncRef(stLocNRC(id, exitTrace, result));
return;
}
if (areBinaryArithTypesSupported(subOpc, loc->type(), topC()->type())) {
auto const val = popC();
auto const result = gen(
subOpc,
loc->isA(Type::Bool) ? gen(ConvBoolToInt, loc) : loc,
val->isA(Type::Bool) ? gen(ConvBoolToInt, val) : val
);
push(stLoc(id, exitTrace, result));
return;
}
PUNT(SetOpL);
}
void HhbcTranslator::emitClassExists(const StringData* clsName) {
emitInterpOne(Type::Bool, 2);
}
void HhbcTranslator::emitInterfaceExists(const StringData* ifaceName) {
emitClassExists(ifaceName);
}
void HhbcTranslator::emitTraitExists(const StringData* traitName) {
emitClassExists(traitName);
}
void HhbcTranslator::emitStaticLocInit(uint32_t locId, uint32_t litStrId) {
const StringData* name = lookupStringId(litStrId);
SSATmp* value = popC();
SSATmp* box;
// Closures and generators from closures don't satisfy the "one static per
// source location" rule that the inline fastpath requires
if (curFunc()->isClosureBody() || curFunc()->isGeneratorFromClosure()) {
box = gen(StaticLocInit, cns(name), m_tb->fp(), value);
} else {
SSATmp* ch = cns(TargetCache::allocStatic(), Type::CacheHandle);
SSATmp* cachedBox = nullptr;
box = m_tb->cond(curFunc(),
[&](Block* taken) {
// Careful: cachedBox is only ok to use in the 'next' branch.
cachedBox = gen(LdStaticLocCached, taken, ch);
},
[&] { // next: The local is already initialized
return gen(IncRef, cachedBox);
},
[&] { // taken: We missed in the cache
m_tb->hint(Block::Hint::Unlikely);
return gen(StaticLocInitCached,
cns(name), m_tb->fp(), value, ch);
}
);
}
gen(StLoc, LocalId(locId), m_tb->fp(), box);
gen(DecRef, value);
}
void HhbcTranslator::emitReqDoc(const StringData* name) {
PUNT(ReqDoc);
}
template<class Lambda>
SSATmp* HhbcTranslator::emitIterInitCommon(int offset, Lambda genFunc) {
SSATmp* src = popC();
Type type = src->type();
if (!type.isArray() && type != Type::Obj) {
PUNT(IterInit);
}
SSATmp* res = genFunc(src);
return emitJmpCondHelper(offset, true, res);
}
void HhbcTranslator::emitIterInit(uint32_t iterId,
int offset,
uint32_t valLocalId) {
emitIterInitCommon(offset, [=] (SSATmp* src) {
return gen(
IterInit,
Type::Bool,
src,
m_tb->fp(),
cns(iterId),
cns(valLocalId)
);
});
}
void HhbcTranslator::emitIterInitK(uint32_t iterId,
int offset,
uint32_t valLocalId,
uint32_t keyLocalId) {
emitIterInitCommon(offset, [=] (SSATmp* src) {
return gen(
IterInitK,
Type::Bool,
src,
m_tb->fp(),
cns(iterId),
cns(valLocalId),
cns(keyLocalId)
);
});
}
void HhbcTranslator::emitIterNext(uint32_t iterId,
int offset,
uint32_t valLocalId) {
SSATmp* res = gen(
IterNext,
Type::Bool,
m_tb->fp(),
cns(iterId),
cns(valLocalId)
);
emitJmpCondHelper(offset, false, res);
}
void HhbcTranslator::emitIterNextK(uint32_t iterId,
int offset,
uint32_t valLocalId,
uint32_t keyLocalId) {
SSATmp* res = gen(
IterNextK,
Type::Bool,
m_tb->fp(),
cns(iterId),
cns(valLocalId),
cns(keyLocalId)
);
emitJmpCondHelper(offset, false, res);
}
void HhbcTranslator::emitWIterInit(uint32_t iterId,
int offset,
uint32_t valLocalId) {
emitIterInitCommon(
offset, [=] (SSATmp* src) {
return gen(
WIterInit,
Type::Bool,
src,
m_tb->fp(),
cns(iterId),
cns(valLocalId)
);
}
);
}
void HhbcTranslator::emitWIterInitK(uint32_t iterId,
int offset,
uint32_t valLocalId,
uint32_t keyLocalId) {
emitIterInitCommon(
offset, [=] (SSATmp* src) {
return gen(
WIterInitK,
Type::Bool,
src,
m_tb->fp(),
cns(iterId),
cns(valLocalId),
cns(keyLocalId)
);
}
);
}
void HhbcTranslator::emitWIterNext(uint32_t iterId,
int offset,
uint32_t valLocalId) {
SSATmp* res = gen(
WIterNext,
Type::Bool,
m_tb->fp(),
cns(iterId),
cns(valLocalId)
);
emitJmpCondHelper(offset, false, res);
}
void HhbcTranslator::emitWIterNextK(uint32_t iterId,
int offset,
uint32_t valLocalId,
uint32_t keyLocalId) {
SSATmp* res = gen(
WIterNextK,
Type::Bool,
m_tb->fp(),
cns(iterId),
cns(valLocalId),
cns(keyLocalId)
);
emitJmpCondHelper(offset, false, res);
}
void HhbcTranslator::emitIterFree(uint32_t iterId) {
gen(IterFree, IterId(iterId), m_tb->fp());
}
void HhbcTranslator::emitDecodeCufIter(uint32_t iterId, int offset) {
SSATmp* src = popC();
Type type = src->type();
if (type.subtypeOfAny(Type::Arr, Type::Str, Type::Obj)) {
SSATmp* res = gen(DecodeCufIter, Type::Bool,
IterId(iterId), src, m_tb->fp());
gen(DecRef, src);
emitJmpCondHelper(offset, true, res);
} else {
gen(DecRef, src);
emitJmp(offset, true, false);
}
}
void HhbcTranslator::emitCIterFree(uint32_t iterId) {
gen(CIterFree, IterId(iterId), m_tb->fp());
}
typedef std::map<int, int> ContParamMap;
/*
* mapContParams builds a mapping between named locals in origFunc and
* corresponding named locals in genFunc. If this step succeeds and
* there's no VarEnv at runtime, the continuation's variables can be
* filled completely inline in the TC (assuming there aren't too
* many).
*/
static
void mapContParams(ContParamMap& map,
const Func* origFunc, const Func* genFunc) {
const StringData* const* varNames = origFunc->localNames();
for (Id i = 0; i < origFunc->numNamedLocals(); ++i) {
Id id = genFunc->lookupVarId(varNames[i]);
assert(id != kInvalidId);
map[i] = id;
}
}
void HhbcTranslator::emitCreateCont(Id funNameStrId) {
gen(ExitOnVarEnv, getExitSlowTrace()->front(), m_tb->fp());
auto const genName = lookupStringId(funNameStrId);
auto const origFunc = curFunc();
auto const genFunc = origFunc->getGeneratorBody(genName);
auto const origLocals = origFunc->numLocals();
auto const cont = origFunc->isMethod()
? gen(
CreateContMeth,
cns(origFunc),
cns(genFunc),
gen(LdCtx, m_tb->fp(), cns(curFunc()))
)
: gen(
CreateContFunc,
cns(origFunc),
cns(genFunc)
);
ContParamMap params;
mapContParams(params, origFunc, genFunc);
static auto const thisStr = StringData::GetStaticString("this");
Id thisId = kInvalidId;
const bool fillThis = origFunc->isMethod() &&
!origFunc->isStatic() &&
((thisId = genFunc->lookupVarId(thisStr)) != kInvalidId) &&
(origFunc->lookupVarId(thisStr) == kInvalidId);
SSATmp* contAR = gen(
LdRaw, Type::PtrToGen, cont, cns(RawMemSlot::ContARPtr));
for (int i = 0; i < origLocals; ++i) {
// We must generate an AssertLoc because we don't have tracelet
// guards on the object type in these outer generator functions.
gen(AssertLoc, Type::Gen, LocalId(i), m_tb->fp());
// Copy the value of the local to the cont object and set the
// local to uninit so that we don't need to change refcounts.
gen(StMem, contAR, cns(-cellsToBytes(params[i] + 1)), ldLoc(i));
gen(StLoc, LocalId(i), m_tb->fp(), m_tb->genDefUninit());
}
if (fillThis) {
assert(thisId != kInvalidId);
auto const thisObj = gen(IncRef, gen(LdThis, m_tb->fp()));
gen(StMem, contAR, cns(-cellsToBytes(thisId + 1)), thisObj);
}
push(cont);
}
void HhbcTranslator::emitContEnter(int32_t returnBcOffset) {
// make sure the value to be sent is on the actual stack
spillStack();
assert(curClass());
SSATmp* cont = gen(LdThis, m_tb->fp());
SSATmp* contAR = gen(
LdRaw, Type::FramePtr, cont, cns(RawMemSlot::ContARPtr)
);
SSATmp* func = gen(LdARFuncPtr, contAR, cns(0));
SSATmp* funcBody = gen(
LdRaw, Type::TCA, func, cns(RawMemSlot::ContEntry)
);
gen(
ContEnter,
contAR,
funcBody,
cns(returnBcOffset),
m_tb->fp()
);
assert(m_stackDeficit == 0);
}
void HhbcTranslator::emitContReturnControl() {
auto const retAddr = gen(LdRetAddr, m_tb->fp());
auto const fp = gen(FreeActRec, m_tb->fp());
auto const sp = spillStack();
gen(RetCtrl, sp, fp, retAddr);
m_hasExit = true;
}
void HhbcTranslator::emitUnpackCont() {
gen(AssertLoc, Type::Obj, LocalId(0), m_tb->fp());
auto const cont = ldLoc(0);
push(gen(LdRaw, Type::Int, cont, cns(RawMemSlot::ContLabel)));
}
void HhbcTranslator::emitContSuspend(int64_t labelId) {
gen(ExitWhenSurprised, getExitSlowTrace());
gen(AssertLoc, Type::Obj, LocalId(0), m_tb->fp());
auto const cont = ldLoc(0);
auto const newVal = popC();
auto const oldValue = gen(LdProp, Type::Cell, cont, cns(CONTOFF(m_value)));
gen(StProp, cont, cns(CONTOFF(m_value)), newVal);
gen(DecRef, oldValue);
// take a fast path if this generator has no yield k => v;
if (curFunc()->isPairGenerator()) {
// this needs optimization
auto const idx = gen(LdRaw, Type::Int, cont, cns(RawMemSlot::ContIndex));
auto const newIdx = gen(OpAdd, idx, cns(1));
gen(StRaw, cont, cns(RawMemSlot::ContIndex), newIdx);
auto const oldKey = gen(LdProp, Type::Cell, cont, cns(CONTOFF(m_key)));
gen(StProp, cont, cns(CONTOFF(m_key)), newIdx);
gen(DecRef, oldKey);
} else {
// we're guaranteed that the key is an int
gen(ContIncKey, cont);
}
gen(StRaw, cont, cns(RawMemSlot::ContLabel), cns(labelId));
// transfer control
emitContReturnControl();
}
void HhbcTranslator::emitContSuspendK(int64_t labelId) {
gen(ExitWhenSurprised, getExitSlowTrace());
gen(AssertLoc, Type::Obj, LocalId(0), m_tb->fp());
auto const cont = ldLoc(0);
auto const newVal = popC();
auto const oldValue = gen(LdProp, Type::Cell, cont, cns(CONTOFF(m_value)));
gen(StProp, cont, cns(CONTOFF(m_value)), newVal);
gen(DecRef, oldValue);
auto const newKey = popC();
auto const oldKey = gen(LdProp, Type::Cell, cont, cns(CONTOFF(m_key)));
gen(StProp, cont, cns(CONTOFF(m_key)), newKey);
gen(DecRef, oldKey);
auto const keyType = newKey->type();
if (keyType.subtypeOf(Type::Int)) {
gen(ContUpdateIdx, cont, newKey);
}
gen(StRaw, cont, cns(RawMemSlot::ContLabel), cns(labelId));
// transfer control
emitContReturnControl();
}
void HhbcTranslator::emitContRetC() {
gen(AssertLoc, Type::Obj, LocalId(0), m_tb->fp());
auto const cont = ldLoc(0);
gen(ExitWhenSurprised, getExitSlowTrace());
gen(ContDone, cont);
auto const newVal = popC();
auto const oldVal = gen(LdProp, Type::Cell, cont, cns(CONTOFF(m_value)));
gen(StProp, cont, cns(CONTOFF(m_value)), newVal);
gen(DecRef, oldVal);
// transfer control
emitContReturnControl();
}
void HhbcTranslator::emitContCheck(bool checkStarted) {
assert(curClass());
SSATmp* cont = gen(LdThis, m_tb->fp());
if (checkStarted) {
gen(ContStartedCheck, getExitSlowTrace(), cont);
}
gen(ContPreNext, getExitSlowTrace(), cont);
}
void HhbcTranslator::emitContRaise() {
assert(curClass());
SSATmp* cont = gen(LdThis, m_tb->fp());
SSATmp* label = gen(LdRaw, Type::Int, cont, cns(RawMemSlot::ContLabel));
label = gen(OpSub, label, cns(1));
gen(StRaw, cont, cns(RawMemSlot::ContLabel), label);
}
void HhbcTranslator::emitContValid() {
assert(curClass());
SSATmp* cont = gen(LdThis, m_tb->fp());
push(gen(ContValid, cont));
}
void HhbcTranslator::emitContKey() {
assert(curClass());
SSATmp* cont = gen(LdThis, m_tb->fp());
gen(ContStartedCheck, getExitSlowTrace(), cont);
SSATmp* offset = cns(CONTOFF(m_key));
SSATmp* value = gen(LdProp, Type::Cell, cont, offset);
value = gen(IncRef, value);
push(value);
}
void HhbcTranslator::emitContCurrent() {
assert(curClass());
SSATmp* cont = gen(LdThis, m_tb->fp());
gen(ContStartedCheck, getExitSlowTrace(), cont);
SSATmp* offset = cns(CONTOFF(m_value));
SSATmp* value = gen(LdProp, Type::Cell, cont, offset);
value = gen(IncRef, value);
push(value);
}
void HhbcTranslator::emitContStopped() {
assert(curClass());
SSATmp* cont = gen(LdThis, m_tb->fp());
gen(ContSetRunning, cont, cns(false));
}
void HhbcTranslator::emitContHandle() {
emitInterpOneCF(1);
}
void HhbcTranslator::emitStrlen() {
Type inType = topC()->type();
if (inType.isString()) {
SSATmp* input = popC();
if (input->isConst()) {
// static string; fold its strlen operation
push(cns(input->getValStr()->size()));
} else {
push(gen(LdRaw, Type::Int, input, cns(RawMemSlot::StrLen)));
gen(DecRef, input);
}
} else if (inType.isNull()) {
popC();
push(cns(0));
} else if (inType == Type::Bool) {
// strlen(true) == 1, strlen(false) == 0.
push(gen(ConvBoolToInt, popC()));
} else {
emitInterpOne(Type::Int | Type::InitNull, 1);
}
}
void HhbcTranslator::emitIncStat(int32_t counter, int32_t value, bool force) {
if (Stats::enabled() || force) {
gen(IncStat, cns(counter), cns(value), cns(force));
}
}
void HhbcTranslator::emitArrayIdx() {
Type arrType = topC(1)->type();
Type keyType = topC(2)->type();
if (UNLIKELY(!arrType.subtypeOf(Type::Arr))) {
// raise fatal
emitInterpOne(Type::Cell, 3);
return;
}
if (UNLIKELY(keyType.subtypeOf(Type::Null))) {
SSATmp* def = popC();
SSATmp* arr = popC();
SSATmp* key = popC();
// if the key is null it will not be found so just return the default
push(def);
gen(DecRef, arr);
gen(DecRef, key);
return;
}
if (UNLIKELY(
!(keyType.subtypeOf(Type::Int) || keyType.subtypeOf(Type::Str)))) {
emitInterpOne(Type::Cell, 3);
return;
}
SSATmp* def = popC();
SSATmp* arr = popC();
SSATmp* key = popC();
KeyType arrayKeyType;
bool checkForInt;
checkStrictlyInteger(key, arrayKeyType, checkForInt);
TCA opFunc;
if (checkForInt) {
opFunc = (TCA)&arrayIdxSi;
} else if (KeyType::Int == arrayKeyType) {
opFunc = (TCA)&arrayIdxI;
} else {
assert(KeyType::Str == arrayKeyType);
opFunc = (TCA)&arrayIdxS;
}
push(gen(ArrayIdx, cns(opFunc), arr, key, def));
gen(DecRef, arr);
gen(DecRef, key);
}
void HhbcTranslator::emitIncTransCounter() {
m_tb->gen(IncTransCounter);
}
SSATmp* HhbcTranslator::getStrName(const StringData* knownName) {
SSATmp* name = popC();
assert(name->isA(Type::Str) || knownName);
if (!name->isConst() || !name->isA(Type::Str)) {
if (knownName) {
// The SSATmp on the evaluation stack was not a string constant,
// but the bytecode translator somehow knew the name statically.
name = cns(knownName);
}
} else {
assert(!knownName || knownName->same(name->getValStr()));
}
return name;
}
SSATmp* HhbcTranslator::emitLdClsPropAddrCached(const StringData* propName,
Block* block) {
SSATmp* cls = popA();
const StringData* clsName = findClassName(cls);
assert(clsName);
SSATmp* prop = getStrName(propName);
SSATmp* addr = gen(LdClsPropAddrCached,
block,
cls,
prop,
cns(clsName),
cns(curClass()));
return addr;
}
SSATmp* HhbcTranslator::emitLdClsPropAddrOrExit(const StringData* propName,
Block* block) {
if (canUseSPropCache(m_evalStack.top(), propName, curClass())) {
return emitLdClsPropAddrCached(propName, block);
}
if (!block) block = getCatchTrace()->front();
SSATmp* clsTmp = popA();
SSATmp* prop = getStrName(propName);
SSATmp* addr = gen(LdClsPropAddr,
block,
clsTmp,
prop,
cns(curClass()));
gen(DecRef, prop); // safe to do early because prop is a string
return addr;
}
bool HhbcTranslator::checkSupportedClsProp(const StringData* propName,
Type resultType,
int stkIndex) {
if (topC(stkIndex + 1)->isA(Type::Str) || propName) {
return true;
}
emitInterpOne(resultType, stkIndex + 2);
return false;
}
bool HhbcTranslator::checkSupportedGblName(const StringData* gblName,
Type resultType,
int stkIndex) {
if (topC(stkIndex)->isA(Type::Str) || gblName) {
return true;
}
emitInterpOne(resultType, stkIndex + 1);
return false;
}
SSATmp* HhbcTranslator::emitLdGblAddr(const StringData* gblName, Block* block) {
SSATmp* name = getStrName(gblName);
// Note: Once we use control flow to implement IssetG/EmptyG, we can
// use a LdGblAddr helper that decrefs name for us
SSATmp* addr = gen(LdGblAddr, block, name);
gen(DecRef, name);
return addr;
}
SSATmp* HhbcTranslator::emitLdGblAddrDef(const StringData* gblName) {
return gen(LdGblAddrDef, getStrName(gblName));
}
void HhbcTranslator::emitIncDecS(bool pre, bool inc) {
if (!checkSupportedClsProp(nullptr, Type::Cell, 0)) return;
IRTrace* exit = getExitSlowTrace();
emitIncDecMem(pre, inc, emitLdClsPropAddr(nullptr), exit);
}
void HhbcTranslator::emitMInstr(const NormalizedInstruction& ni) {
VectorTranslator(ni, *this).emit();
}
/*
* IssetH: return true if var is not uninit and !is_null(var)
* Unboxes var if necessary when var is not uninit.
*/
void HhbcTranslator::emitIssetL(int32_t id) {
auto const exitTrace = getExitTrace();
auto const ld = ldLocInner(id, exitTrace);
push(gen(IsNType, Type::Null, ld));
}
void HhbcTranslator::emitIssetG(const StringData* gblName) {
emitIsset(gblName,
&HhbcTranslator::checkSupportedGblName,
&HhbcTranslator::emitLdGblAddr);
}
void HhbcTranslator::emitIssetS(const StringData* propName) {
emitIsset(propName,
&HhbcTranslator::checkSupportedClsProp,
&HhbcTranslator::emitLdClsPropAddrOrExit);
}
void HhbcTranslator::emitEmptyL(int32_t id) {
auto const exitTrace = getExitTrace();
auto const ld = ldLocInner(id, exitTrace);
push(gen(OpNot, gen(ConvCellToBool, ld)));
}
void HhbcTranslator::emitEmptyG(const StringData* gblName) {
emitEmpty(gblName,
&HhbcTranslator::checkSupportedGblName,
&HhbcTranslator::emitLdGblAddr);
}
void HhbcTranslator::emitEmptyS(const StringData* propName) {
emitEmpty(propName,
&HhbcTranslator::checkSupportedClsProp,
&HhbcTranslator::emitLdClsPropAddrOrExit);
}
void HhbcTranslator::emitIsTypeC(Type t) {
SSATmp* src = popC();
push(gen(IsType, t, src));
gen(DecRef, src);
}
void HhbcTranslator::emitIsTypeL(Type t, int id) {
IRTrace* exitTrace = getExitTrace();
push(gen(IsType, t, ldLocInnerWarn(id, exitTrace)));
}
void HhbcTranslator::emitIsNullL(int id) { emitIsTypeL(Type::Null, id);}
void HhbcTranslator::emitIsArrayL(int id) { emitIsTypeL(Type::Arr, id); }
void HhbcTranslator::emitIsStringL(int id) { emitIsTypeL(Type::Str, id); }
void HhbcTranslator::emitIsObjectL(int id) { emitIsTypeL(Type::Obj, id); }
void HhbcTranslator::emitIsIntL(int id) { emitIsTypeL(Type::Int, id); }
void HhbcTranslator::emitIsBoolL(int id) { emitIsTypeL(Type::Bool, id);}
void HhbcTranslator::emitIsDoubleL(int id) { emitIsTypeL(Type::Dbl, id); }
void HhbcTranslator::emitIsNullC() { emitIsTypeC(Type::Null);}
void HhbcTranslator::emitIsArrayC() { emitIsTypeC(Type::Arr); }
void HhbcTranslator::emitIsStringC() { emitIsTypeC(Type::Str); }
void HhbcTranslator::emitIsObjectC() { emitIsTypeC(Type::Obj); }
void HhbcTranslator::emitIsIntC() { emitIsTypeC(Type::Int); }
void HhbcTranslator::emitIsBoolC() { emitIsTypeC(Type::Bool);}
void HhbcTranslator::emitIsDoubleC() { emitIsTypeC(Type::Dbl); }
void HhbcTranslator::emitPopC() {
popDecRef(Type::Cell);
}
void HhbcTranslator::emitPopV() {
popDecRef(Type::BoxedCell);
}
void HhbcTranslator::emitPopR() {
popDecRef(Type::Gen);
}
void HhbcTranslator::emitDup() {
pushIncRef(topC());
}
void HhbcTranslator::emitJmp(int32_t offset,
bool breakTracelet,
bool noSurprise) {
// If surprise flags are set, exit trace and handle surprise
bool backward = (offset - (int32_t)bcOff()) < 0;
if (backward && !noSurprise) {
gen(ExitWhenSurprised, getExitSlowTrace());
}
if (!breakTracelet) return;
gen(Jmp_, getExitTrace(offset));
}
SSATmp* HhbcTranslator::emitJmpCondHelper(int32_t offset,
bool negate,
SSATmp* src) {
// Spill everything on main trace if all paths will exit.
if (m_lastBcOff) spillStack();
auto const target = getExitTrace(offset);
auto const boolSrc = gen(ConvCellToBool, src);
gen(DecRef, src);
return gen(negate ? JmpZero : JmpNZero, target, boolSrc);
}
void HhbcTranslator::emitJmpZ(Offset taken) {
auto const src = popC();
emitJmpCondHelper(taken, true, src);
}
void HhbcTranslator::emitJmpNZ(Offset taken) {
auto const src = popC();
emitJmpCondHelper(taken, false, src);
}
void HhbcTranslator::emitCmp(Opcode opc) {
IRTrace* catchTrace = nullptr;
if (cmpOpTypesMayReenter(opc, topC(0)->type(), topC(1)->type())) {
catchTrace = getCatchTrace();
}
// src2 opc src1
SSATmp* src1 = popC();
SSATmp* src2 = popC();
push(gen(opc, catchTrace, src2, src1));
gen(DecRef, src2);
gen(DecRef, src1);
}
// Return a constant SSATmp representing a static value held in a
// TypedValue. The TypedValue may be a non-scalar, but it must have a
// static value.
SSATmp* HhbcTranslator::staticTVCns(const TypedValue* tv) {
switch (tv->m_type) {
case KindOfNull: return m_tb->genDefInitNull();
case KindOfBoolean: return cns(!!tv->m_data.num);
case KindOfInt64: return cns(tv->m_data.num);
case KindOfString:
case KindOfStaticString: return cns(tv->m_data.pstr);
case KindOfDouble: return cns(tv->m_data.dbl);
case KindOfArray: return cns(tv->m_data.parr);
default: always_assert(0);
}
}
void HhbcTranslator::emitClsCnsD(int32_t cnsNameId, int32_t clsNameId,
Type outPred) {
auto const clsNameStr = lookupStringId(clsNameId);
auto const cnsNameStr = lookupStringId(cnsNameId);
auto const clsCnsName = ClsCnsName { clsNameStr, cnsNameStr };
// If we have to side exit, do the target cache lookup before
// chaining to another Tracelet so forward progress still happens.
auto const sideExit = makeSideExit(
nextBcOff(),
[&] {
return gen(LookupClsCns, Type::Cell, clsCnsName);
}
);
/*
* If the class is already defined in this request, and this
* constant is a scalar constant, we can just compile it to a
* literal.
*
* We need to guard at runtime that the class is defined in this
* request and has the Class* we expect. If the class is persistent
* or a parent of the current context, we don't need the guard.
*/
if (auto const cls = Unit::lookupClass(clsNameStr)) {
Slot ignore;
auto const tv = cls->cnsNameToTV(cnsNameStr, ignore);
if (tv && tv->m_type != KindOfUninit) {
if (!classIsPersistentOrCtxParent(cls)) {
gen(CheckDefinedClsEq, CheckDefinedClsData{clsNameStr, cls}, sideExit);
}
push(staticTVCns(tv));
return;
}
}
auto guardType = Type::UncountedInit;
if (outPred.strictSubtypeOf(guardType)) guardType = outPred;
auto const cns = gen(LdClsCns, sideExit, clsCnsName, guardType);
push(cns);
}
void HhbcTranslator::emitAKExists() {
SSATmp* arr = popC();
SSATmp* key = popC();
if (!arr->isA(Type::Arr) && !arr->isA(Type::Obj)) {
PUNT(AKExists_badArray);
}
if (!key->isString() && !key->isA(Type::Int) && !key->isA(Type::Null)) {
PUNT(AKExists_badKey);
}
push(gen(AKExists, arr, key));
gen(DecRef, arr);
gen(DecRef, key);
}
void HhbcTranslator::emitFPassR() {
emitUnboxRAux();
}
void HhbcTranslator::emitFPassCOp() {
}
void HhbcTranslator::emitFPassV() {
Block* exit = getExitTrace()->front();
SSATmp* tmp = popV();
pushIncRef(gen(Unbox, exit, tmp));
gen(DecRef, tmp);
}
void HhbcTranslator::emitFPushCufIter(int32_t numParams,
int32_t itId) {
auto sp = spillStack();
m_fpiStack.emplace(sp, m_tb->spOffset());
gen(CufIterSpillFrame,
FPushCufData(numParams, itId),
sp, m_tb->fp());
}
void HhbcTranslator::emitFPushCufOp(Op op, Class* cls, StringData* invName,
const Func* callee, int numArgs) {
const Func* curFunc = this->curFunc();
const bool safe = op == OpFPushCufSafe;
const bool forward = op == OpFPushCufF;
if (!callee) {
SSATmp* callable = topC(safe ? 1 : 0);
// The most common type for the callable in this case is Arr. We
// can't really do better than the interpreter here, so punt.
SPUNT(StringData::GetStaticString(
folly::format("FPushCuf-{}",
callable->type().toString()).str())
->data());
}
SSATmp* ctx;
SSATmp* safeFlag = cns(true); // This is always true until the slow exits
// below are implemented
SSATmp* func = cns(callee);
if (cls) {
if (forward) {
ctx = gen(LdCtx, m_tb->fp(), cns(curFunc));
ctx = gen(GetCtxFwdCall, ctx, cns(callee));
} else {
ctx = genClsMethodCtx(callee, cls);
}
if (!TargetCache::isPersistentHandle(cls->m_cachedOffset)) {
// The miss path is complicated and rare. Punt for now.
gen(LdClsCachedSafe, getExitSlowTrace(), cns(cls->name()));
}
} else {
ctx = m_tb->genDefInitNull();
if (!TargetCache::isPersistentHandle(callee->getCachedOffset())) {
// The miss path is complicated and rare. Punt for now.
func = gen(LdFuncCachedSafe, getExitSlowTrace(),
cns(callee->name()));
}
}
SSATmp* defaultVal = safe ? popC() : nullptr;
popDecRef(Type::Cell); // callable
if (safe) {
push(defaultVal);
push(safeFlag);
}
emitFPushActRec(func, ctx, numArgs, invName);
}
void HhbcTranslator::emitNativeImpl() {
gen(NativeImpl, cns(curFunc()), m_tb->fp());
SSATmp* sp = gen(RetAdjustStack, m_tb->fp());
SSATmp* retAddr = gen(LdRetAddr, m_tb->fp());
SSATmp* fp = gen(FreeActRec, m_tb->fp());
gen(RetCtrl, sp, fp, retAddr);
// Flag that this trace has a Ret instruction so no ExitTrace is needed
m_hasExit = true;
}
void HhbcTranslator::emitFPushActRec(SSATmp* func,
SSATmp* objOrClass,
int32_t numArgs,
const StringData* invName) {
/*
* Before allocating an ActRec, we do a spillStack so we'll have a
* StkPtr that represents what the stack will look like after the
* ActRec is popped.
*/
auto actualStack = spillStack();
auto returnSp = actualStack;
/*
* XXX. In a generator, we can't use ReDefSP to restore the stack
* pointer from the frame pointer if we inline the callee. (This is
* because we don't really pay attention to usedefs for allocating
* registers to stack pointers, and rVmFp and rVmSp are not related
* to each other in a generator frame.)
*
* Instead, save it somewhere so we can move it back after. This
* instruction will be dce'd if we don't inline the callee.
*
* TODO(#2288359): freeing up the special-ness of %rbx should
* allow us to avoid this sort of thing.
*/
if (curFunc()->isGenerator()) {
returnSp = gen(StashGeneratorSP, m_tb->sp());
}
m_fpiStack.emplace(returnSp, m_tb->spOffset());
ActRecInfo info;
info.numArgs = numArgs;
info.invName = invName;
gen(
SpillFrame,
info,
// Using actualStack instead of returnSp so SpillFrame still gets
// the src in rVmSp. (TODO(#2288359).)
actualStack,
m_tb->fp(),
func,
objOrClass
);
assert(m_stackDeficit == 0);
}
void HhbcTranslator::emitFPushCtorCommon(SSATmp* cls,
SSATmp* obj,
const Func* func,
int32_t numParams,
IRTrace* catchTrace) {
push(obj);
SSATmp* fn = nullptr;
if (func) {
fn = cns(func);
} else {
fn = gen(LdClsCtor, catchTrace, cls);
}
SSATmp* obj2 = gen(IncRef, obj);
int32_t numArgsAndCtorFlag = ActRec::encodeNumArgs(numParams, true);
emitFPushActRec(fn, obj2, numArgsAndCtorFlag, nullptr);
}
void HhbcTranslator::emitFPushCtor(int32_t numParams) {
IRTrace* catchTrace = getCatchTrace();
SSATmp* cls = popA();
SSATmp* obj = gen(IncRef, gen(AllocObj, cls));
emitFPushCtorCommon(cls, obj, nullptr, numParams, catchTrace);
}
static bool canInstantiateClass(const Class* cls) {
return cls &&
!(cls->attrs() & (AttrAbstract | AttrInterface | AttrTrait));
}
void HhbcTranslator::emitFPushCtorD(int32_t numParams, int32_t classNameStrId) {
const StringData* className = lookupStringId(classNameStrId);
// The code generated for the catch trace depends on the environment at the
// call so we can't share them between instructions.
IRTrace* catchTrace1 = getCatchTrace();
IRTrace* catchTrace2 = getCatchTrace();
const Class* cls = Unit::lookupUniqueClass(className);
bool uniqueCls = classIsUnique(cls);
bool persistentCls = TargetCache::classIsPersistent(cls);
bool canInstantiate = canInstantiateClass(cls);
bool fastAlloc = !RuntimeOption::EnableObjDestructCall &&
persistentCls && canInstantiate;
const Func* func = uniqueCls ? cls->getCtor() : nullptr;
if (func && !(func->attrs() & AttrPublic)) {
Class* ctx = arGetContextClass(curFrame());
if (!ctx) {
func = nullptr;
} else if (ctx != cls) {
if ((func->attrs() & AttrPrivate) ||
!(ctx->classof(cls) || cls->classof(ctx))) {
func = nullptr;
}
}
}
SSATmp* clss = nullptr;
if (persistentCls) {
clss = cns(cls);
} else {
clss = gen(LdClsCached, catchTrace1, cns(className));
}
SSATmp* obj = nullptr;
if (fastAlloc) {
obj = gen(IncRef, gen(AllocObjFast, clss));
} else {
obj = gen(IncRef, gen(AllocObj, clss));
}
emitFPushCtorCommon(clss, obj, func, numParams, catchTrace2);
}
/*
* The CreateCl opcode is specified as not being allowed before the
* class it creates exists, and closure classes are always unique.
*
* This means even if we're not in RepoAuthoritative mode, as long as
* this code is reachable it will always use the same closure Class*,
* so we can just burn it into the TC without using TargetCache.
*/
void HhbcTranslator::emitCreateCl(int32_t numParams, int32_t funNameStrId) {
auto const sp = spillStack();
auto const cls = Unit::lookupUniqueClass(lookupStringId(funNameStrId));
assert(cls && (cls->attrs() & AttrUnique));
auto const closure = gen(
CreateCl,
cns(cls),
cns(numParams),
m_tb->fp(),
sp
);
discard(numParams);
push(closure);
}
void HhbcTranslator::emitFPushFuncD(int32_t numParams, int32_t funcId) {
const NamedEntityPair& nep = lookupNamedEntityPairId(funcId);
const StringData* name = nep.first;
const Func* func = Unit::lookupFunc(nep.second);
if (!func) {
// function lookup failed so just do the same as FPushFunc
emitFPushFunc(numParams, cns(name));
return;
}
func->validate();
const bool immutable = func->isNameBindingImmutable(curUnit());
IRTrace* catchTrace = nullptr;
if (!immutable) {
catchTrace = getCatchTrace(); // LdFuncCached can throw
}
SSATmp* ssaFunc = immutable ? cns(func)
: gen(LdFuncCached, catchTrace, cns(name));
emitFPushActRec(ssaFunc,
m_tb->genDefInitNull(),
numParams,
nullptr);
}
void HhbcTranslator::emitFPushFuncU(int32_t numParams,
int32_t funcId,
int32_t fallbackFuncId) {
PUNT(FPushFuncU);
}
void HhbcTranslator::emitFPushFunc(int32_t numParams) {
// input must be a string or an object implementing __invoke();
// otherwise fatal
SSATmp* funcName = popC();
if (!funcName->isString()) {
PUNT(FPushFunc_not_Str);
}
emitFPushFunc(numParams, funcName);
}
void HhbcTranslator::emitFPushFunc(int32_t numParams, SSATmp* funcName) {
emitFPushActRec(gen(LdFunc, getCatchTrace(), funcName),
m_tb->genDefInitNull(),
numParams,
nullptr);
}
void HhbcTranslator::emitFPushObjMethodD(int32_t numParams,
int32_t methodNameStrId,
const Class* baseClass) {
const StringData* methodName = lookupStringId(methodNameStrId);
bool magicCall = false;
const Func* func = HPHP::Transl::lookupImmutableMethod(baseClass,
methodName,
magicCall,
/* staticLookup: */
false);
SSATmp* obj = popC();
SSATmp* objOrCls = obj;
if (!func) {
if (baseClass && !(baseClass->attrs() & AttrInterface)) {
MethodLookup::LookupResult res =
g_vmContext->lookupObjMethod(func, baseClass, methodName, false);
if ((res == MethodLookup::LookupResult::MethodFoundWithThis ||
res == MethodLookup::LookupResult::MethodFoundNoThis) &&
!func->isAbstract()) {
/*
* If we found the func in baseClass, then either:
* a) its private, and this is always going to be the
* called function. This case is handled further down.
* OR
* b) any derived class must have a func that matches in staticness
* and is at least as accessible (and in particular, you can't
* override a public/protected method with a private method).
* In this case, we emit code to dynamically lookup the method
* given the Object and the method slot, which is the same as func's.
*/
if (!(func->attrs() & AttrPrivate)) {
SSATmp* clsTmp = gen(LdObjClass, obj);
SSATmp* funcTmp = gen(
LdClsMethod, clsTmp, cns(func->methodSlot())
);
if (res == MethodLookup::LookupResult::MethodFoundNoThis) {
gen(DecRef, obj);
objOrCls = clsTmp;
}
emitFPushActRec(funcTmp, objOrCls, numParams,
magicCall ? methodName : nullptr);
return;
}
} else {
// method lookup did not find anything
func = nullptr; // force lookup
}
}
}
if (func != nullptr) {
if (func->attrs() & AttrStatic) {
assert(baseClass); // This assert may be too strong, but be aggressive
// static function: store base class into this slot instead of obj
// and decref the obj that was pushed as the this pointer since
// the obj won't be in the actrec and thus MethodCache::lookup won't
// decref it
gen(DecRef, obj);
objOrCls = cns(baseClass);
}
emitFPushActRec(cns(func),
objOrCls,
numParams,
magicCall ? methodName : nullptr);
} else {
emitFPushActRec(m_tb->genDefNull(),
obj,
numParams,
nullptr);
auto const actRec = spillStack();
auto const objCls = gen(LdObjClass, obj);
// This is special. We need to move the stackpointer incase LdObjMethod
// calls a destructor. Otherwise it would clobber the ActRec we just pushed.
updateMarker();
gen(LdObjMethod,
objCls,
cns(methodName),
actRec);
}
}
SSATmp* HhbcTranslator::genClsMethodCtx(const Func* callee, const Class* cls) {
bool mightNotBeStatic = false;
assert(callee);
if (!(callee->attrs() & AttrStatic) &&
!(curFunc()->attrs() & AttrStatic) &&
curClass() &&
curClass()->classof(cls)) {
mightNotBeStatic = true;
}
if (!mightNotBeStatic) {
// static function: ctx is just the Class*. LdCls will simplify to a
// DefConst or LdClsCached.
return gen(LdCls, cns(cls->name()), cns(curClass()));
}
if (m_tb->isThisAvailable()) {
// might not be a static call and $this is available, so we know it's
// definitely not static
assert(curClass());
return gen(IncRef, gen(LdThis, m_tb->fp()));
}
// might be a non-static call. we have to inspect the func at runtime
PUNT(getClsMethodCtx-MightNotBeStatic);
}
void HhbcTranslator::emitFPushClsMethodD(int32_t numParams,
int32_t methodNameStrId,
int32_t clssNamedEntityPairId) {
const StringData* methodName = lookupStringId(methodNameStrId);
const NamedEntityPair& np = lookupNamedEntityPairId(clssNamedEntityPairId);
const StringData* className = np.first;
const Class* baseClass = Unit::lookupUniqueClass(np.second);
bool magicCall = false;
const Func* func = HPHP::Transl::lookupImmutableMethod(baseClass,
methodName,
magicCall,
/* staticLookup: */
true);
if (func) {
SSATmp* objOrCls = genClsMethodCtx(func, baseClass);
emitFPushActRec(cns(func),
objOrCls,
numParams,
func && magicCall ? methodName : nullptr);
} else {
// lookup static method & class in the target cache
SSATmp* stack = spillStack();
IRTrace* exitTrace = getExitSlowTrace();
SSATmp* funcClassTmp =
gen(LdClsMethodCache,
exitTrace,
cns(className),
cns(methodName),
cns(np.second),
m_tb->fp(),
stack);
emitFPushActRec(funcClassTmp,
m_tb->genDefInitNull(),
numParams,
nullptr);
}
}
void HhbcTranslator::emitFPushClsMethodF(int32_t numParams,
const Class* cls,
const StringData* methName) {
assert(cls);
assert(methName && methName->isStatic());
Block* exitBlock = getExitSlowTrace()->front();
UNUSED SSATmp* clsVal = popC();
UNUSED SSATmp* methVal = popC();
bool magicCall = false;
const Func* func = lookupImmutableMethod(cls, methName, magicCall,
true /* staticLookup */);
SSATmp* curCtxTmp = gen(LdCtx, m_tb->fp(), cns(curFunc()));
if (func) {
SSATmp* funcTmp = cns(func);
SSATmp* newCtxTmp = gen(GetCtxFwdCall, curCtxTmp, funcTmp);
emitFPushActRec(funcTmp, newCtxTmp, numParams,
(magicCall ? methName : nullptr));
} else {
SSATmp* funcCtxTmp = gen(LdClsMethodFCache, exitBlock,
cns(cls),
cns(methName),
curCtxTmp,
m_tb->fp());
emitFPushActRec(funcCtxTmp,
m_tb->genDefInitNull(),
numParams,
(magicCall ? methName : nullptr));
}
}
void HhbcTranslator::emitFCallArray(const Offset pcOffset,
const Offset after) {
SSATmp* stack = spillStack();
gen(CallArray, CallArrayData(pcOffset, after), stack);
}
void HhbcTranslator::emitFCall(uint32_t numParams,
Offset returnBcOffset,
const Func* callee) {
SSATmp* params[numParams + 3];
std::memset(params, 0, sizeof params);
for (uint32_t i = 0; i < numParams; i++) {
params[numParams + 3 - i - 1] = popF();
}
params[0] = spillStack();
params[1] = cns(returnBcOffset);
params[2] = callee ? cns(callee) : m_tb->genDefNull();
SSATmp** decayedPtr = params;
gen(Call, std::make_pair(numParams + 3, decayedPtr));
if (!m_fpiStack.empty()) {
m_fpiStack.pop();
}
}
void HhbcTranslator::emitFCallBuiltin(uint32_t numArgs,
uint32_t numNonDefault,
int32_t funcId) {
const NamedEntity* ne = lookupNamedEntityId(funcId);
const Func* callee = Unit::lookupFunc(ne);
callee->validate();
// spill args to stack. We need to spill these for two resons:
// 1. some of the arguments may be passed by reference, for which
// case we will pass a stack address.
// 2. type conversions of the arguments (using tvCast* helpers)
// may throw an exception, so we either need to have the VM stack
// in a clean state at that point or give each helper a catch
// trace. Since we have to spillstack anyway, the catch trace
// would be overkill.
spillStack();
bool zendParamMode = callee->info()->attribute & ClassInfo::ZendParamMode;
// Convert types if needed.
for (int i = 0; i < numNonDefault; i++) {
const Func::ParamInfo& pi = callee->params()[i];
switch (pi.builtinType()) {
case KindOfBoolean:
case KindOfInt64:
case KindOfArray:
case KindOfObject:
case KindOfString:
if (zendParamMode) {
gen(
CoerceStk,
Type::fromDataType(pi.builtinType(), KindOfInvalid),
StackOffset(numArgs - i - 1),
getExitSlowTrace(),
m_tb->sp()
);
} else {
gen(
CastStk,
Type::fromDataType(pi.builtinType(), KindOfInvalid),
StackOffset(numArgs - i - 1),
m_tb->sp()
);
}
break;
case KindOfDouble: not_reached();
case KindOfUnknown: break;
default: not_reached();
}
}
// Pass arguments for CallBuiltin.
const int argsSize = numArgs + 2;
SSATmp* args[argsSize];
args[0] = cns(callee);
args[1] = m_tb->sp();
for (int i = numArgs - 1; i >= 0; i--) {
const Func::ParamInfo& pi = callee->params()[i];
switch (pi.builtinType()) {
case KindOfBoolean:
case KindOfInt64:
args[i + 2] = top(Type::fromDataType(pi.builtinType(), KindOfInvalid),
numArgs - i - 1);
break;
case KindOfDouble: assert(false);
default:
args[i + 2] = ldStackAddr(numArgs - i - 1);
break;
}
}
// Generate call and set return type
auto const ret = gen(
CallBuiltin,
Type::fromDataTypeWithRef(callee->returnType(),
(callee->attrs() & ClassInfo::IsReference)),
std::make_pair(argsSize, (SSATmp**)&args)
);
// Decref and free args
for (int i = 0; i < numArgs; i++) {
auto const arg = popR();
if (i >= numArgs - numNonDefault) {
gen(DecRef, arg);
}
}
push(ret);
}
void HhbcTranslator::emitRetFromInlined(Type type) {
SSATmp* retVal = pop(type);
assert(!(curFunc()->attrs() & AttrMayUseVV));
assert(!curFunc()->isPseudoMain());
assert(!m_fpiStack.empty());
emitDecRefLocalsInline(retVal);
/*
* Pop the ActRec and restore the stack and frame pointers. It's
* important that this does endInlining before pushing the return
* value so stack offsets are properly tracked.
*/
gen(InlineReturn, m_tb->fp());
// Return to the caller function. Careful between here and the
// updateMarker() below, where the caller state isn't entirely set up.
m_bcStateStack.pop_back();
m_fpiStack.pop();
// See the comment in beginInlining about generator frames.
if (curFunc()->isGenerator()) {
gen(ReDefGeneratorSP, StackOffset(m_tb->spOffset()), m_tb->sp());
} else {
gen(ReDefSP,
StackOffset(m_tb->spOffset()), m_tb->fp(), m_tb->sp());
}
/*
* After the end of inlining, we are restoring to a previously
* defined stack that we know is entirely materialized. TODO:
* explain this better.
*
* The push of the return value below is not yet materialized.
*/
assert(m_evalStack.numCells() == 0);
m_stackDeficit = 0;
FTRACE(1, "]]] end inlining: {}\n", curFunc()->fullName()->data());
push(retVal);
updateMarker();
}
SSATmp* HhbcTranslator::emitDecRefLocalsInline(SSATmp* retVal) {
SSATmp* retValSrcLoc = nullptr;
Opcode retValSrcOpc = Nop; // Nop flags the ref-count opt is impossible
IRInstruction* retValSrcInstr = retVal->inst();
const Func* curFunc = this->curFunc();
/*
* In case retVal comes from a local, the logic below tweaks the code
* so that retVal is DecRef'd and the corresponding local's SSATmp is
* returned. This enables the ref-count optimization to eliminate the
* IncRef/DecRef pair in the main trace.
*/
if (retValSrcInstr->op() == IncRef) {
retValSrcLoc = retValSrcInstr->src(0);
retValSrcOpc = retValSrcLoc->inst()->op();
if (retValSrcOpc != LdLoc && retValSrcOpc != LdThis) {
retValSrcLoc = nullptr;
retValSrcOpc = Nop;
}
}
if (curFunc->mayHaveThis()) {
if (retValSrcLoc && retValSrcOpc == LdThis) {
gen(DecRef, retVal);
} else {
gen(DecRefThis, m_tb->fp());
}
}
/*
* Note: this is currently off for isInlining() because the shuffle
* was preventing a decref elimination due to ordering. Currently
* we don't inline anything with parameters, though, so it doesn't
* matter. This will need to be revisted then.
*/
int retValLocId = (!isInlining() && retValSrcLoc && retValSrcOpc == LdLoc) ?
retValSrcLoc->inst()->extra<LocalId>()->locId : -1;
for (int id = curFunc->numLocals() - 1; id >= 0; --id) {
if (retValLocId == id) {
gen(DecRef, retVal);
continue;
}
gen(DecRefLoc, Type::Gen, LocalId(id), m_tb->fp());
}
return retValSrcLoc ? retValSrcLoc : retVal;
}
void HhbcTranslator::emitRet(Type type, bool freeInline) {
if (isInlining()) {
return emitRetFromInlined(type);
}
const Func* curFunc = this->curFunc();
bool mayUseVV = (curFunc->attrs() & AttrMayUseVV);
gen(ExitWhenSurprised, getExitSlowTrace());
if (mayUseVV) {
// Note: this has to be the first thing, because we cannot bail after
// we start decRefing locs because then there'll be no corresponding
// bytecode boundaries until the end of RetC
gen(ReleaseVVOrExit, getExitSlowTrace(), m_tb->fp());
}
SSATmp* retVal = pop(type);
SSATmp* sp;
if (freeInline) {
SSATmp* useRet = emitDecRefLocalsInline(retVal);
gen(StRetVal, m_tb->fp(), useRet);
sp = gen(RetAdjustStack, m_tb->fp());
} else {
if (curFunc->mayHaveThis()) {
gen(DecRefThis, m_tb->fp());
}
sp = gen(GenericRetDecRefs, m_tb->fp(), cns(curFunc->numLocals()));
gen(StRetVal, m_tb->fp(), retVal);
}
// Free ActRec, and return control to caller.
SSATmp* retAddr = gen(LdRetAddr, m_tb->fp());
SSATmp* fp = gen(FreeActRec, m_tb->fp());
gen(RetCtrl, sp, fp, retAddr);
// Flag that this trace has a Ret instruction, so that no ExitTrace is needed
m_hasExit = true;
}
void HhbcTranslator::emitSwitch(const ImmVector& iv,
int64_t base,
bool bounded) {
int nTargets = bounded ? iv.size() - 2 : iv.size();
SSATmp* const switchVal = popC();
Type type = switchVal->type();
assert(IMPLIES(!type.equals(Type::Int), bounded));
assert(IMPLIES(bounded, iv.size() > 2));
SSATmp* index;
SSATmp* ssabase = cns(base);
SSATmp* ssatargets = cns(nTargets);
Offset defaultOff = bcOff() + iv.vec32()[iv.size() - 1];
Offset zeroOff = 0;
if (base <= 0 && (base + nTargets) > 0) {
zeroOff = bcOff() + iv.vec32()[0 - base];
} else {
zeroOff = defaultOff;
}
if (type.subtypeOf(Type::Null)) {
gen(Jmp_, getExitTrace(zeroOff));
return;
} else if (type.subtypeOf(Type::Bool)) {
Offset nonZeroOff = bcOff() + iv.vec32()[iv.size() - 2];
gen(JmpNZero, getExitTrace(nonZeroOff), switchVal);
gen(Jmp_, getExitTrace(zeroOff));
return;
} else if (type.subtypeOf(Type::Int)) {
// No special treatment needed
index = switchVal;
} else if (type.subtypeOf(Type::Dbl)) {
// switch(Double|String|Obj)Helper do bounds-checking for us, so
// we need to make sure the default case is in the jump table,
// and don't emit our own bounds-checking code
bounded = false;
index = gen(LdSwitchDblIndex,
switchVal, ssabase, ssatargets);
} else if (type.subtypeOf(Type::Str)) {
bounded = false;
index = gen(LdSwitchStrIndex,
switchVal, ssabase, ssatargets);
} else if (type.subtypeOf(Type::Obj)) {
// switchObjHelper can throw exceptions and reenter the VM
IRTrace* catchTrace = nullptr;
if (type.subtypeOf(Type::Obj)) {
catchTrace = getCatchTrace();
}
bounded = false;
index = gen(LdSwitchObjIndex, catchTrace, switchVal, ssabase, ssatargets);
} else if (type.subtypeOf(Type::Arr)) {
gen(DecRef, switchVal);
gen(Jmp_, getExitTrace(defaultOff));
return;
} else {
PUNT(Switch-UnknownType);
}
std::vector<Offset> targets(iv.size());
for (int i = 0; i < iv.size(); i++) {
targets[i] = bcOff() + iv.vec32()[i];
}
JmpSwitchData data;
data.func = curFunc();
data.base = base;
data.bounded = bounded;
data.cases = iv.size();
data.defaultOff = defaultOff;
data.targets = &targets[0];
auto const stack = spillStack();
gen(SyncABIRegs, m_tb->fp(), stack);
gen(JmpSwitchDest, data, index);
m_hasExit = true;
}
void HhbcTranslator::emitSSwitch(const ImmVector& iv) {
const int numCases = iv.size() - 1;
/*
* We use a fast path translation with a hashtable if none of the
* cases are numeric strings and if the input is actually a string.
*
* Otherwise we do a linear search through the cases calling string
* conversion routines.
*/
const bool fastPath =
topC()->isA(Type::Str) &&
std::none_of(iv.strvec(), iv.strvec() + numCases,
[&](const StrVecItem& item) {
return curUnit()->lookupLitstrId(item.str)->isNumeric();
}
);
IRTrace* catchTrace = nullptr;
// The slow path can throw exceptions and reenter the VM.
if (!fastPath) catchTrace = getCatchTrace();
auto const testVal = popC();
std::vector<LdSSwitchData::Elm> cases(numCases);
for (int i = 0; i < numCases; ++i) {
auto const& kv = iv.strvec()[i];
cases[i].str = curUnit()->lookupLitstrId(kv.str);
cases[i].dest = bcOff() + kv.dest;
}
LdSSwitchData data;
data.func = curFunc();
data.numCases = numCases;
data.cases = &cases[0];
data.defaultOff = bcOff() + iv.strvec()[iv.size() - 1].dest;
SSATmp* dest = gen(fastPath ? LdSSwitchDestFast
: LdSSwitchDestSlow,
catchTrace,
data,
testVal);
gen(DecRef, testVal);
auto const stack = spillStack();
gen(SyncABIRegs, m_tb->fp(), stack);
gen(JmpIndirect, dest);
m_hasExit = true;
}
void HhbcTranslator::emitRetC(bool freeInline) {
emitRet(Type::Cell, freeInline);
}
void HhbcTranslator::emitRetV(bool freeInline) {
emitRet(Type::BoxedCell, freeInline);
}
void HhbcTranslator::setThisAvailable() {
m_tb->setThisAvailable();
}
void HhbcTranslator::guardTypeLocal(uint32_t locId, Type type) {
gen(GuardLoc, type, LocalId(locId), m_tb->fp());
}
void HhbcTranslator::guardTypeLocation(const Location& loc, Type type) {
assert(type.subtypeOf(Type::Gen | Type::Cls));
if (loc.isStack()) {
guardTypeStack(loc.offset, type);
} else if (loc.isLocal()) {
assert(type.not(Type::Cls));
guardTypeLocal(loc.offset, type);
} else {
not_reached();
}
}
void HhbcTranslator::checkTypeLocal(uint32_t locId, Type type,
Offset dest /* = -1 */) {
gen(CheckLoc, type, LocalId(locId), getExitTrace(dest), m_tb->fp());
}
void HhbcTranslator::assertTypeLocal(uint32_t locId, Type type) {
gen(AssertLoc, type, LocalId(locId), m_tb->fp());
}
void HhbcTranslator::overrideTypeLocal(uint32_t locId, Type type) {
gen(OverrideLoc, type, LocalId(locId), m_tb->fp());
}
void HhbcTranslator::checkTypeLocation(const Location& loc, Type type,
Offset dest) {
assert(type.subtypeOf(Type::Gen));
if (loc.isStack()) {
checkTypeStack(loc.offset, type, dest);
} else if (loc.isLocal()) {
checkTypeLocal(loc.offset, type, dest);
} else {
not_reached();
}
}
void HhbcTranslator::assertTypeLocation(const Location& loc, Type type) {
assert(type.subtypeOf(Type::Gen | Type::Cls));
if (loc.isStack()) {
assertTypeStack(loc.offset, type);
} else if (loc.isLocal()) {
assert(type.not(Type::Cls));
assertTypeLocal(loc.offset, type);
} else {
not_reached();
}
}
void HhbcTranslator::guardTypeStack(uint32_t stackIndex, Type type) {
// Should not generate guards for class; instead assert their type
if (type.subtypeOf(Type::Cls)) {
assertTypeStack(stackIndex, type);
return;
}
assert(m_evalStack.size() == 0);
assert(m_stackDeficit == 0); // This should only be called at the beginning
// of a trace, with a clean stack.
gen(GuardStk, type, StackOffset(stackIndex), m_tb->sp());
}
void HhbcTranslator::checkTypeStack(uint32_t idx, Type type, Offset dest) {
auto exitTrace = getExitTrace(dest);
if (idx < m_evalStack.size()) {
FTRACE(1, "checkTypeStack(){}: generating CheckType for {}\n",
idx, type.toString());
SSATmp* tmp = m_evalStack.top(idx);
assert(tmp);
m_evalStack.replace(idx, gen(CheckType, type, exitTrace, tmp));
} else {
FTRACE(1, "checkTypeStack({}): no tmp: {}\n", idx, type.toString());
gen(CheckStk, type, exitTrace,
StackOffset(idx - m_evalStack.size () + m_stackDeficit), m_tb->sp());
}
}
void HhbcTranslator::checkTypeTopOfStack(Type type, Offset nextByteCode) {
checkTypeStack(0, type, nextByteCode);
}
void HhbcTranslator::assertTypeStack(uint32_t idx, Type type) {
if (idx < m_evalStack.size()) {
SSATmp* tmp = m_evalStack.top(idx);
assert(tmp);
m_evalStack.replace(idx, gen(AssertType, type, tmp));
} else {
gen(AssertStk, type,
StackOffset(idx - m_evalStack.size() + m_stackDeficit),
m_tb->sp());
}
}
void HhbcTranslator::assertString(const Location& loc, const StringData* str) {
auto idx = loc.offset;
if (loc.isStack()) {
if (idx < m_evalStack.size()) {
DEBUG_ONLY SSATmp* oldStr = m_evalStack.top(idx);
assert(oldStr->type().maybe(Type::Str));
m_evalStack.replace(idx, cns(str));
} else {
gen(AssertStkVal, StackOffset(idx - m_evalStack.size() + m_stackDeficit),
m_tb->sp(), cns(str));
}
} else if (loc.isLocal()) {
assert(m_tb->getLocalType(loc.offset).maybe(Type::Str));
m_tb->setLocalValue(idx, cns(str));
} else {
not_reached();
}
}
/*
* Creates a RuntimeType struct from a program location. This needs access to
* more than just the location's type because RuntimeType includes known
* constant values.
*/
RuntimeType HhbcTranslator::rttFromLocation(const Location& loc) {
Type t;
SSATmp* val = nullptr;
switch (loc.space) {
case Location::Stack: {
auto i = loc.offset;
assert(i >= 0);
if (i < m_evalStack.size()) {
val = m_evalStack.top(i);
t = val->type();
} else {
auto stackVal = getStackValue(m_tb->sp(),
i - m_evalStack.size() + m_stackDeficit);
val = stackVal.value;
t = stackVal.knownType;
}
} break;
case Location::Local: {
auto l = loc.offset;
val = m_tb->getLocalValue(l);
t = val ? val->type() : m_tb->getLocalType(l);
} break;
case Location::Litstr:
return RuntimeType(curUnit()->lookupLitstrId(loc.offset));
case Location::Litint:
return RuntimeType(loc.offset);
case Location::This:
return RuntimeType(KindOfObject, KindOfInvalid, curFunc()->cls());
case Location::Invalid:
case Location::Iter:
not_reached();
}
assert(IMPLIES(val, val->type().equals(t)));
if (val && val->isConst()) {
// RuntimeType holds constant Bool, Int, Str, and Cls.
if (val->type().isBool()) return RuntimeType(val->getValBool());
if (val->type().isInt()) return RuntimeType(val->getValInt());
if (val->type().isString()) return RuntimeType(val->getValStr());
if (val->type().isCls()) return RuntimeType(val->getValClass());
}
return t.toRuntimeType();
}
static uint64_t packBitVec(const vector<bool>& bits, unsigned i) {
uint64_t retval = 0;
assert(i % 64 == 0);
assert(i < bits.size());
while (i < bits.size()) {
retval |= bits[i] << (i % 64);
if ((++i % 64) == 0) {
break;
}
}
return retval;
}
void HhbcTranslator::guardRefs(int64_t entryArDelta,
const vector<bool>& mask,
const vector<bool>& vals) {
int32_t actRecOff = cellsToBytes(entryArDelta);
SSATmp* funcPtr = gen(LdARFuncPtr, m_tb->sp(), cns(actRecOff));
SSATmp* nParams = nullptr;
for (unsigned i = 0; i < mask.size(); i += 64) {
assert(i < vals.size());
uint64_t mask64 = packBitVec(mask, i);
if (mask64 == 0) {
continue;
}
uint64_t vals64 = packBitVec(vals, i);
if (i == 0) {
nParams = cns(64);
} else if (i == 64) {
nParams = gen(
LdRaw, Type::Int, funcPtr, cns(RawMemSlot::FuncNumParams)
);
}
SSATmp* maskTmp = !(mask64>>32) ? cns(mask64) : m_tb->genLdConst(mask64);
SSATmp* valsTmp = !(vals64>>32) ? cns(vals64) : m_tb->genLdConst(vals64);
gen(
GuardRefs,
funcPtr,
nParams,
cns(i),
maskTmp,
valsTmp
);
}
}
void HhbcTranslator::emitVerifyParamType(int32_t paramId) {
const Func* func = curFunc();
const TypeConstraint& tc = func->params()[paramId].typeConstraint();
auto locVal = ldLoc(paramId);
Type locType = locVal->type().unbox();
assert(locType.isKnownDataType());
if (tc.nullable() && locType.isNull()) {
return;
}
if (tc.isCallable()) {
locVal = gen(Unbox, getExitTrace(), locVal);
gen(VerifyParamCallable, getCatchTrace(), locVal, cns(paramId));
return;
}
// For non-object guards, we rely on what we know from the tracelet
// guards and never have to do runtime checks.
if (!tc.isObjectOrTypedef()) {
if (locVal->type().isBoxed()) {
locVal = gen(LdRef, locVal->type().innerType(), getExitTrace(), locVal);
}
if (!tc.checkPrimitive(locType.toDataType())) {
gen(VerifyParamFail, getCatchTrace(), cns(paramId));
return;
}
return;
}
/*
* If the parameter is an object, we check the object in one of
* various ways (similar to instance of). If the parameter is not
* an object, it still might pass the VerifyParamType if the
* constraint is a typedef.
*
* For now we just interp that case.
*/
if (!locType.isObj()) {
emitInterpOne(Type::None, 0);
return;
}
const StringData* clsName;
const Class* knownConstraint = nullptr;
if (!tc.isSelf() && !tc.isParent()) {
clsName = tc.typeName();
knownConstraint = Unit::lookupClass(clsName);
} else {
if (tc.isSelf()) {
tc.selfToClass(curFunc(), &knownConstraint);
} else if (tc.isParent()) {
tc.parentToClass(curFunc(), &knownConstraint);
}
if (knownConstraint) {
clsName = knownConstraint->preClass()->name();
} else {
// The hint was self or parent and there's no corresponding
// class for the current func. This typehint will always fail.
gen(VerifyParamFail, getCatchTrace(), cns(paramId));
return;
}
}
assert(clsName);
// We can only burn in the Class* if it's unique or in the
// inheritance hierarchy of our context. It's ok if the class isn't
// defined yet - all paths below are tolerant of a null constraint.
if (!classIsUniqueOrCtxParent(knownConstraint)) knownConstraint = nullptr;
Class::initInstanceBits();
bool haveBit = Class::haveInstanceBit(clsName);
SSATmp* constraint = knownConstraint ? cns(knownConstraint)
: gen(LdClsCachedSafe, cns(clsName));
locVal = gen(Unbox, getExitTrace(), locVal);
SSATmp* objClass = gen(LdObjClass, locVal);
if (haveBit || classIsUniqueNormalClass(knownConstraint)) {
SSATmp* isInstance = haveBit
? gen(InstanceOfBitmask, objClass, cns(clsName))
: gen(ExtendsClass, objClass, constraint);
m_tb->ifThen(curFunc(),
[&](Block* taken) {
gen(JmpZero, taken, isInstance);
},
[&] { // taken: the param type does not match
m_tb->hint(Block::Hint::Unlikely);
gen(VerifyParamFail, getCatchTrace(), cns(paramId));
}
);
} else {
gen(VerifyParamCls,
getCatchTrace(),
objClass,
constraint,
cns(paramId),
cns(uintptr_t(&tc)));
}
}
void HhbcTranslator::emitInstanceOfD(int classNameStrId) {
const StringData* className = lookupStringId(classNameStrId);
SSATmp* src = popC();
/*
* InstanceOfD is always false if it's not an object.
*
* We're prepared to generate translations for known non-object
* types, but if it's Gen/Cell we're going to PUNT because it's
* natural to translate that case with control flow TODO(#2020251)
*/
if (Type::Obj.strictSubtypeOf(src->type())) {
PUNT(InstanceOfD_MaybeObj);
}
if (!src->isA(Type::Obj)) {
bool res = (src->isA(Type::Arr) && interface_supports_array(className));
push(cns(res));
gen(DecRef, src);
return;
}
SSATmp* objClass = gen(LdObjClass, src);
SSATmp* ssaClassName = cns(className);
Class::initInstanceBits();
const bool haveBit = Class::haveInstanceBit(className);
Class* const maybeCls = Unit::lookupUniqueClass(className);
const bool isNormalClass = classIsUniqueNormalClass(maybeCls);
const bool isUnique = classIsUnique(maybeCls);
/*
* If the class is unique or a parent of the current context, we
* don't need to load it out of target cache because it must
* already exist and be defined.
*
* Otherwise, we only use LdClsCachedSafe---instanceof with an
* undefined class doesn't invoke autoload.
*/
SSATmp* checkClass =
isUnique || (maybeCls && curClass() && curClass()->classof(maybeCls))
? cns(maybeCls)
: gen(LdClsCachedSafe, ssaClassName);
push(
haveBit ? gen(InstanceOfBitmask,
objClass,
ssaClassName) :
isUnique && isNormalClass ? gen(ExtendsClass,
objClass,
checkClass) :
gen(InstanceOf,
objClass,
checkClass,
cns(maybeCls && !isNormalClass))
);
gen(DecRef, src);
}
void HhbcTranslator::emitCastArray() {
// Turns the castArray BC operation into a type specialized
// IR operation. The IR operation might end up being simplified
// into a constant, but if not, it simply turns into a helper
// call when translated to machine code. The main benefit from
// separate IR instructions is that they can have different flags,
// principally to distinguish the instructions that (may) hold on to a
// reference to argument, from instructions that do not.
// In the future, if this instruction occurs in a hot trace,
// it might be better to expand it into a series of primitive
// IR instructions so that the object allocation is exposed to
// the optimizer and becomes eligible for removal if it does not
// escape the trace.
SSATmp* src = popC();
Type fromType = src->type();
if (fromType.isArray()) {
push(src);
} else if (fromType.isNull()) {
push(cns(HphpArray::GetStaticEmptyArray()));
} else if (fromType.isBool()) {
push(gen(ConvBoolToArr, src));
} else if (fromType.isDbl()) {
push(gen(ConvDblToArr, src));
} else if (fromType.isInt()) {
push(gen(ConvIntToArr, src));
} else if (fromType.isString()) {
push(gen(ConvStrToArr, src));
} else if (fromType.isObj()) {
push(gen(ConvObjToArr, src));
} else {
push(gen(ConvCellToArr, src));
}
}
void HhbcTranslator::emitCastBool() {
auto const src = popC();
push(gen(ConvCellToBool, src));
gen(DecRef, src);
}
void HhbcTranslator::emitCastDouble() {
IRTrace* catchTrace = getCatchTrace();
SSATmp* src = popC();
push(gen(ConvCellToDbl, catchTrace, src));
gen(DecRef, src);
}
void HhbcTranslator::emitCastInt() {
IRTrace* catchTrace = getCatchTrace();
SSATmp* src = popC();
push(gen(ConvCellToInt, catchTrace, src));
gen(DecRef, src);
}
void HhbcTranslator::emitCastObject() {
SSATmp* src = popC();
Type srcType = src->type();
if (srcType.isObj()) {
push(src);
} else {
push(gen(ConvCellToObj, src));
}
}
void HhbcTranslator::emitCastString() {
IRTrace* catchTrace = getCatchTrace();
SSATmp* src = popC();
Type fromType = src->type();
if (fromType.isString()) {
push(src);
} else if (fromType.isNull()) {
push(cns(StringData::GetStaticString("")));
} else if (fromType.isArray()) {
push(cns(StringData::GetStaticString("Array")));
gen(DecRef, src);
} else if (fromType.isBool()) {
push(gen(ConvBoolToStr, src));
} else if (fromType.isDbl()) {
push(gen(ConvDblToStr, src));
} else if (fromType.isInt()) {
push(gen(ConvIntToStr, src));
} else if (fromType.isObj()) {
push(gen(ConvObjToStr, catchTrace, src));
} else {
push(gen(ConvCellToStr, catchTrace, src));
}
}
static
bool isSupportedAGet(SSATmp* classSrc, const StringData* clsName) {
return (classSrc->isA(Type::Obj) || classSrc->isA(Type::Str) || clsName);
}
void HhbcTranslator::emitAGet(SSATmp* classSrc, const StringData* clsName) {
if (classSrc->isA(Type::Str)) {
push(gen(LdCls, classSrc, cns(curClass())));
} else if (classSrc->isA(Type::Obj)) {
push(gen(LdObjClass, classSrc));
} else if (clsName) {
push(gen(LdCls, cns(clsName), cns(curClass())));
} else {
not_reached();
}
}
void HhbcTranslator::emitAGetC(const StringData* clsName) {
if (isSupportedAGet(topC(), clsName)) {
SSATmp* src = popC();
emitAGet(src, clsName);
gen(DecRef, src);
} else {
emitInterpOne(Type::Cls, 1);
}
}
void HhbcTranslator::emitAGetL(int id, const StringData* clsName) {
auto const src = ldLocInner(id, getExitTrace());
if (isSupportedAGet(src, clsName)) {
emitAGet(src, clsName);
} else {
PUNT(AGetL); // need to teach interpone about local uses
}
}
void HhbcTranslator::emitBindMem(SSATmp* ptr, SSATmp* src) {
SSATmp* prevValue = gen(LdMem, ptr->type().deref(), ptr, cns(0));
pushIncRef(src);
gen(StMem, ptr, cns(0), src);
if (isRefCounted(src) && src->type().canRunDtor()) {
Block* exitBlock = getExitTrace(nextBcOff())->front();
exitBlock->prepend(m_irFactory.gen(DecRef, makeMarker(nextBcOff()),
prevValue));
gen(DecRefNZOrBranch, exitBlock, prevValue);
} else {
gen(DecRef, prevValue);
}
}
template<class CheckSupportedFun, class EmitLdAddrFun>
void HhbcTranslator::emitBind(const StringData* name,
CheckSupportedFun checkSupported,
EmitLdAddrFun emitLdAddr) {
if (!(this->*checkSupported)(name, topV(0)->type(), 1)) return;
SSATmp* src = popV();
emitBindMem((this->*emitLdAddr)(name), src);
}
void HhbcTranslator::emitSetMem(SSATmp* ptr, SSATmp* src) {
emitBindMem(gen(UnboxPtr, ptr), src);
}
template<class CheckSupportedFun, class EmitLdAddrFun>
void HhbcTranslator::emitSet(const StringData* name,
CheckSupportedFun checkSupported,
EmitLdAddrFun emitLdAddr) {
if (!(this->*checkSupported)(name, topC(0)->type(), 1)) return;
SSATmp* src = popC();
emitSetMem((this->*emitLdAddr)(name), src);
}
void HhbcTranslator::emitVGetMem(SSATmp* ptr) {
pushIncRef(
gen(LdMem, Type::BoxedCell, gen(BoxPtr, ptr), cns(0))
);
}
template<class CheckSupportedFun, class EmitLdAddrFun>
void HhbcTranslator::emitVGet(const StringData* name,
CheckSupportedFun checkSupported,
EmitLdAddrFun emitLdAddr) {
if (!(this->*checkSupported)(name, Type::BoxedCell, 0)) return;
emitVGetMem((this->*emitLdAddr)(name));
}
template<class CheckSupportedFun, class EmitLdAddrFun>
void HhbcTranslator::emitIsset(const StringData* name,
CheckSupportedFun checkSupported,
EmitLdAddrFun emitLdAddr) {
if (!(this->*checkSupported)(name, Type::Bool, 0)) return;
SSATmp* ptr = nullptr;
SSATmp* result = m_tb->cond(curFunc(),
[&] (Block* taken) { // branch
ptr = (this->*emitLdAddr)(name, taken);
},
[&] { // Next: property or global is defined
return gen(IsNTypeMem, Type::Null,
gen(UnboxPtr, ptr));
},
[&] { // Taken
return cns(false);
}
);
push(result);
}
void HhbcTranslator::emitEmptyMem(SSATmp* ptr) {
SSATmp* ld = gen(LdMem, Type::Cell, gen(UnboxPtr, ptr), cns(0));
push(gen(OpNot, gen(ConvCellToBool, ld)));
}
template<class CheckSupportedFun, class EmitLdAddrFun>
void HhbcTranslator::emitEmpty(const StringData* name,
CheckSupportedFun checkSupported,
EmitLdAddrFun emitLdAddr) {
if (!(this->*checkSupported)(name, Type::Bool, 0)) return;
SSATmp* ptr = nullptr;
SSATmp* result = m_tb->cond(curFunc(),
[&] (Block* taken) {
ptr = (this->*emitLdAddr)(name, taken);
},
[&] { // Next: property or global is defined
SSATmp* ld = gen(
LdMem,
Type::Cell,
gen(UnboxPtr, ptr),
cns(0)
);
return gen(OpNot, gen(ConvCellToBool, ld));
},
[&] { // Taken
return cns(true);
}
);
push(result);
}
void HhbcTranslator::emitBindG(const StringData* gblName) {
emitBind(gblName,
&HhbcTranslator::checkSupportedGblName,
&HhbcTranslator::emitLdGblAddrDef);
}
void HhbcTranslator::emitBindS(const StringData* propName) {
emitBind(propName,
&HhbcTranslator::checkSupportedClsProp,
&HhbcTranslator::emitLdClsPropAddr);
}
void HhbcTranslator::emitVGetG(const StringData* gblName) {
emitVGet(gblName,
&HhbcTranslator::checkSupportedGblName,
&HhbcTranslator::emitLdGblAddrDef);
}
void HhbcTranslator::emitVGetS(const StringData* propName) {
emitVGet(propName,
&HhbcTranslator::checkSupportedClsProp,
&HhbcTranslator::emitLdClsPropAddr);
}
void HhbcTranslator::emitSetG(const StringData* gblName) {
emitSet(gblName,
&HhbcTranslator::checkSupportedGblName,
&HhbcTranslator::emitLdGblAddrDef);
}
void HhbcTranslator::emitSetS(const StringData* propName) {
emitSet(propName,
&HhbcTranslator::checkSupportedClsProp,
&HhbcTranslator::emitLdClsPropAddr);
}
static Type getResultType(Type resultType, bool isInferedType) {
assert(!isInferedType || resultType.isKnownUnboxedDataType());
if (resultType.equals(Type::None)) {
// result type neither predicted nor inferred
return Type::Cell;
}
assert(resultType.isKnownUnboxedDataType());
return resultType;
}
template<class CheckSupportedFun, class EmitLdAddrFun>
void HhbcTranslator::emitCGet(const StringData* name,
Type resultType,
bool isInferedType,
bool exitOnFailure,
CheckSupportedFun checkSupported,
EmitLdAddrFun emitLdAddr) {
resultType = getResultType(resultType, isInferedType);
if (!(this->*checkSupported)(name, resultType, 0)) return;
IRTrace* exit = (isInferedType || resultType.equals(Type::Cell))
? nullptr : getExitSlowTrace();
SSATmp* ptr = (this->*emitLdAddr)(name,
exitOnFailure
? getExitSlowTrace()->front()
: nullptr);
if (!isInferedType) ptr = gen(UnboxPtr, ptr);
pushIncRef(gen(LdMem, resultType, exit, ptr, cns(0)));
}
void HhbcTranslator::emitCGetG(const StringData* gblName,
Type resultType,
bool isInferedType) {
emitCGet(gblName, resultType, isInferedType, true,
&HhbcTranslator::checkSupportedGblName,
&HhbcTranslator::emitLdGblAddr);
}
void HhbcTranslator::emitCGetS(const StringData* propName,
Type resultType,
bool isInferedType) {
emitCGet(propName, resultType, isInferedType, false,
&HhbcTranslator::checkSupportedClsProp,
&HhbcTranslator::emitLdClsPropAddrOrExit);
}
void HhbcTranslator::emitBinaryArith(Opcode opc) {
bool isBitOp = (opc == OpBitAnd || opc == OpBitOr || opc == OpBitXor);
Type type1 = topC(0)->type();
Type type2 = topC(1)->type();
if (areBinaryArithTypesSupported(opc, type1, type2)) {
SSATmp* tr = popC();
SSATmp* tl = popC();
tr = (tr->isA(Type::Bool) ? gen(ConvBoolToInt, tr) : tr);
tl = (tl->isA(Type::Bool) ? gen(ConvBoolToInt, tl) : tl);
push(gen(opc, tl, tr));
} else {
Type type = Type::Int;
if (isBitOp) {
if (type1.isString() && type2.isString()) {
type = Type::Str;
} else if ((type1.needsReg() && (type2.needsReg() || type2.isString()))
|| (type2.needsReg() && type1.isString())) {
// both types might be strings, but can't tell
type = Type::Cell;
} else {
type = Type::Int;
}
} else {
// either an int or a dbl, but can't tell
type = Type::Cell;
}
emitInterpOne(type, 2);
}
}
void HhbcTranslator::emitNot() {
SSATmp* src = popC();
push(gen(OpNot, gen(ConvCellToBool, src)));
gen(DecRef, src);
}
#define BINOP(Opp) \
void HhbcTranslator::emit ## Opp() { \
emitBinaryArith(Op ## Opp); \
}
BINOP(Add)
BINOP(Sub)
BINOP(Mul)
BINOP(BitAnd)
BINOP(BitOr)
BINOP(BitXor)
#undef BINOP
void HhbcTranslator::emitDiv() {
emitInterpOne(Type::Cell, 2);
}
void HhbcTranslator::emitMod() {
IRTrace* catchTrace = getCatchTrace();
SSATmp* btr = popC();
SSATmp* btl = popC();
SSATmp* tr = gen(ConvCellToInt, catchTrace, btr);
SSATmp* tl = gen(ConvCellToInt, catchTrace, btl);
gen(DecRef, btr);
gen(DecRef, btl);
// Exit path spills an additional false
auto exitSpillValues = peekSpillValues();
exitSpillValues.push_back(cns(false));
// Generate an exit for the rare case that r is zero. Interpreting
// will raise a notice and produce the boolean false. Punch out
// here and resume after the Mod instruction; this should be rare.
auto const exit = getExitTraceWarn(
nextBcOff(),
exitSpillValues,
StringData::GetStaticString(Strings::DIVISION_BY_ZERO)
);
gen(JmpZero, exit, tr);
// We unfortunately need to special-case r = -1 here. In two's
// complement, trying to divide INT_MIN by -1 will cause an integer
// overflow.
if (tr->isConst()) {
// This crap only exists so m_tb->cond doesn't get mad when one
// of the branches gets optimized out due to constant folding.
if (tr->getValInt() == -1LL) {
push(cns(0));
} else {
push(gen(OpMod, tl, tr));
}
return;
}
// check for -1 (dynamic version)
SSATmp *res = m_tb->cond(
curFunc(),
[&] (Block* taken) {
SSATmp* negone = gen(OpEq, tr, cns(-1));
gen(JmpNZero, taken, negone);
},
[&] {
return gen(OpMod, tl, tr);
},
[&] {
m_tb->hint(Block::Hint::Unlikely);
return cns(0);
}
);
push(res);
}
void HhbcTranslator::emitBitNot() {
Type srcType = topC()->type();
if (srcType.subtypeOf(Type::Int)) {
SSATmp* src = popC();
push(gen(OpBitNot, src));
} else {
Type resultType = Type::Int;
if (srcType.isString()) {
resultType = Type::Str;
} else if (srcType.needsReg()) {
resultType = Type::Cell;
}
emitInterpOne(resultType, 1);
}
}
void HhbcTranslator::emitXor() {
SSATmp* btr = popC();
SSATmp* btl = popC();
SSATmp* tr = gen(ConvCellToBool, btr);
SSATmp* tl = gen(ConvCellToBool, btl);
push(gen(ConvCellToBool, gen(OpLogicXor, tl, tr)));
gen(DecRef, btl);
gen(DecRef, btr);
}
namespace {
Type arithOpResult(Type t1, Type t2) {
if (!t1.isKnownDataType() || !t2.isKnownDataType()) {
return Type::Cell;
}
auto both = t1 | t2;
if (both.maybe(Type::Dbl)) return Type::Dbl;
if (both.maybe(Type::Arr)) return Type::Arr;
if (both.maybe(Type::Str)) return Type::Cell;
return Type::Int;
}
Type bitOpResult(Type t1, Type t2) {
if (!t1.isKnownDataType() || !t2.isKnownDataType()) {
return Type::Cell;
}
auto both = t1 | t2;
if (both.subtypeOf(Type::Str)) return Type::Str;
return Type::Int;
}
Type setOpResult(Type locType, Type valType, SetOpOp op) {
switch (op) {
case SetOpPlusEqual:
case SetOpMinusEqual:
case SetOpMulEqual: return arithOpResult(locType.unbox(), valType);
case SetOpConcatEqual: return Type::Str;
case SetOpDivEqual:
case SetOpModEqual: return Type::Cell;
case SetOpAndEqual:
case SetOpOrEqual:
case SetOpXorEqual: return bitOpResult(locType.unbox(), valType);
case SetOpSlEqual:
case SetOpSrEqual: return Type::Int;
case SetOp_invalid: not_reached();
}
not_reached();
}
uint32_t localOutputId(const NormalizedInstruction& inst) {
switch (inst.op()) {
case OpUnpackCont:
case OpContSuspend:
case OpContRetC:
return 0;
case OpSetWithRefLM:
case OpFPassL:
return inst.imm[1].u_IVA;
default:
return inst.imm[0].u_IVA;
}
}
}
Type HhbcTranslator::interpOutputType(const NormalizedInstruction& inst) const {
using namespace Transl::InstrFlags;
auto localType = [&]{
auto locId = localOutputId(inst);
assert(locId >= 0 && locId < curFunc()->numLocals());
auto t = m_tb->getLocalType(locId);
return t.equals(Type::None) ? Type::Gen : t;
};
auto cell = [](Type t) {
return t.unbox();
};
auto boxed = [](Type t) {
if (t.equals(Type::Gen)) return t;
assert(t.isBoxed() || t.notBoxed());
return t.isBoxed() ? t : boxType(t);
};
auto outFlag = getInstrInfo(inst.op()).type;
if (outFlag == OutFInputL) {
outFlag = inst.preppedByRef ? OutVInputL : OutCInputL;
} else if (outFlag == OutFInputR) {
outFlag = inst.preppedByRef ? OutVInput : OutCInput;
}
switch (outFlag) {
case OutNull: return Type::InitNull;
case OutNullUninit: return Type::Uninit;
case OutString: return Type::Str;
case OutStringImm: return Type::StaticStr;
case OutDouble: return Type::Dbl;
case OutBoolean:
case OutBooleanImm: return Type::Bool;
case OutInt64: return Type::Int;
case OutArray: return Type::Arr;
case OutArrayImm: return Type::Arr; // Should be StaticArr: t2124292
case OutObject:
case OutThisObject: return Type::Obj;
case OutFDesc: return Type::None;
case OutUnknown: return Type::Gen;
case OutPred: return inst.outPred;
case OutCns: return Type::Cell;
case OutVUnknown: return Type::BoxedCell;
case OutSameAsInput: return topType(0);
case OutCInput: return cell(topType(0));
case OutVInput: return boxed(topType(0));
case OutCInputL: return cell(localType());
case OutVInputL: return boxed(localType());
case OutFInputL:
case OutFInputR: not_reached();
case OutArith: return arithOpResult(topType(0), topType(1));
case OutBitOp:
return bitOpResult(topType(0),
inst.op() == HPHP::OpBitNot ? Type::Bottom
: topType(1));
case OutSetOp: return setOpResult(localType(), topType(0),
SetOpOp(inst.imm[1].u_OA));
case OutIncDec: return localType().unbox().isInt() ? Type::Int
: Type::Cell;
case OutStrlen: return topType(0).isString() ? Type::Int : Type::Cell;
case OutClassRef: return Type::Cls;
case OutSetM: return Type::Cell; // Imprecise but we can translate
// all cases that matter.
case OutNone: return Type::None;
}
not_reached();
}
void HhbcTranslator::interpOutputLocals(const NormalizedInstruction& inst) {
using namespace Transl::InstrFlags;
if (!(getInstrInfo(inst.op()).out & Local)) return;
auto setImmLocType = [&](uint32_t id, Type t = Type::Gen) {
gen(OverrideLoc, t, LocalId(inst.imm[id].u_HA), m_tb->fp());
};
switch (inst.op()) {
case OpSetN:
case OpSetOpN:
case OpIncDecN:
case OpBindN:
case OpUnsetN:
gen(SmashLocals, m_tb->fp());
break;
case OpSetOpL:
case OpIncDecL: {
auto locType = m_tb->getLocalType(inst.imm[0].u_HA);
auto stackType = topType(0);
setImmLocType(0, locType.isBoxed() ? stackType.box() : stackType);
break;
}
case OpStaticLocInit:
setImmLocType(0, Type::BoxedCell);
break;
case OpInitThisLoc:
setImmLocType(0, Type::Gen);
break;
case OpSetL:
case OpBindL:
setImmLocType(0, topType(0));
break;
case OpUnsetL:
setImmLocType(0, Type::Uninit);
break;
case OpSetM:
case OpSetOpM:
case OpBindM:
case OpVGetM:
case OpSetWithRefLM:
case OpSetWithRefRM:
switch (inst.immVec.locationCode()) {
case LL: {
auto const& mii = getMInstrInfo(inst.mInstrOp());
auto const& base = inst.inputs[mii.valCount()]->location;
assert(base.space == Location::Local);
Type baseType = m_tb->getLocalType(base.offset);
assert(baseType.isBoxed() || baseType.notBoxed() ||
baseType.equals(Type::Gen));
const bool baseBoxed = baseType.isBoxed();
baseType = baseType.strip();
// This is a simple, conservative approximation of the real type flow
// logic that happens in VectorTranslator.
if (baseType.maybe(Type::Str | Type::Bool | Type::Null)) {
auto promoted = mcodeMaybePropName(inst.immVecM[0]) ? Type::Obj
: Type::Arr;
if (!baseType.isNull()) {
// Promotion isn't guaranteed to happen so the base might keep
// its original type.
promoted = promoted | baseType;
}
if (baseBoxed) promoted = boxType(promoted);
gen(OverrideLoc, promoted, LocalId(base.offset), m_tb->fp());
}
break;
}
case LNL:
case LNC:
gen(SmashLocals, m_tb->fp());
break;
default:
break;
}
break;
case OpMIterInitK:
case OpMIterNextK:
setImmLocType(3, Type::Cell);
case OpMIterInit:
case OpMIterNext:
setImmLocType(2, Type::BoxedCell);
break;
case OpIterInitK:
case OpWIterInitK:
case OpIterNextK:
case OpWIterNextK:
setImmLocType(3, Type::Cell);
case OpIterInit:
case OpWIterInit:
case OpIterNext:
case OpWIterNext:
setImmLocType(2, Type::Gen);
break;
default:
not_reached();
}
}
void HhbcTranslator::emitInterpOne(const NormalizedInstruction& inst) {
auto stackType = interpOutputType(inst);
auto popped = getStackPopped(inst);
auto pushed = getStackPushed(inst);
FTRACE(1, "emitting InterpOne for {}, result = {}, popped {}, pushed {}\n",
inst.toString(), stackType.toString(), popped, pushed);
emitInterpOne(stackType, popped, pushed);
interpOutputLocals(inst);
}
void HhbcTranslator::emitInterpOne(Type outType, int popped) {
emitInterpOne(outType, popped, outType.equals(Type::None) ? 0 : 1);
}
void HhbcTranslator::emitInterpOne(Type outType, int popped, int pushed) {
auto sp = spillStack();
gen(InterpOne, outType, InterpOneData(bcOff(), popped, pushed),
m_tb->fp(), sp);
assert(m_stackDeficit == 0);
}
void HhbcTranslator::emitInterpOneCF(int numPopped) {
// We're calling into the interpreter so we want the stack synced to memory.
SSATmp* sp = spillStack();
// discard the top elements of the stack, which are consumed by this instr
discard(numPopped);
assert(numPopped == m_stackDeficit);
gen(InterpOneCF, m_tb->fp(), sp, cns(bcOff()));
m_stackDeficit = 0;
m_hasExit = true;
}
std::string HhbcTranslator::showStack() const {
if (isInlining()) {
return folly::format("{:*^60}\n",
" I don't understand inlining stacks yet ").str();
}
std::ostringstream out;
auto header = [&](const std::string& str) {
out << folly::format("+{:-^62}+\n", str);
};
const int32_t frameCells = curFunc()->numLocals() +
curFunc()->numIterators() * kNumIterCells;
const int32_t stackDepth =
m_tb->spOffset() + m_evalStack.size() - m_stackDeficit -
(curFunc()->isGenerator() ? 0 : frameCells);
auto spOffset = stackDepth;
auto elem = [&](const std::string& str) {
out << folly::format("| {:<60} |\n",
folly::format("{:>2}: {}",
stackDepth - spOffset, str));
assert(spOffset > 0);
--spOffset;
};
auto fpiStack = m_fpiStack;
auto checkFpi = [&]() {
if (!fpiStack.empty() &&
spOffset - kNumActRecCells == fpiStack.top().second) {
for (unsigned i = 0; i < kNumActRecCells; ++i) elem("ActRec");
fpiStack.pop();
return true;
}
return false;
};
header(folly::format(" {} stack element(s); m_evalStack: ",
stackDepth).str());
for (unsigned i = 0; i < m_evalStack.size(); ++i) {
while (checkFpi());
SSATmp* value = m_evalStack.top(i);
elem(value->inst()->toString());
}
header(" in-memory ");
for (unsigned i = m_stackDeficit; spOffset > 0; ) {
assert(i < curFunc()->maxStackCells());
if (checkFpi()) {
i += kNumActRecCells;
continue;
}
auto stkVal = getStackValue(m_tb->sp(), i);
std::ostringstream elemStr;
if (stkVal.knownType.equals(Type::None)) elem("unknown");
else if (stkVal.value) elem(stkVal.value->inst()->toString());
else elem(stkVal.knownType.toString());
++i;
}
header("");
return out.str();
}
/*
* Get SSATmps representing all the information on the virtual eval
* stack in preparation for a spill or exit trace. Top of stack will
* be at index 0.
*
* Doesn't actually remove these values from the eval stack.
*/
std::vector<SSATmp*> HhbcTranslator::peekSpillValues() const {
std::vector<SSATmp*> ret;
ret.reserve(m_evalStack.size());
for (int i = 0; i < m_evalStack.size(); ++i) {
SSATmp* elem = m_evalStack.top(i);
ret.push_back(elem);
}
return ret;
}
IRTrace* HhbcTranslator::getExitTrace(Offset targetBcOff /* = -1 */) {
auto spillValues = peekSpillValues();
return getExitTrace(targetBcOff, spillValues);
}
IRTrace* HhbcTranslator::getExitTrace(Offset targetBcOff,
std::vector<SSATmp*>& spillValues) {
if (targetBcOff == -1) targetBcOff = bcOff();
return getExitTraceImpl(targetBcOff, ExitFlag::None, spillValues,
CustomExit{});
}
IRTrace* HhbcTranslator::getExitTraceWarn(Offset targetBcOff,
std::vector<SSATmp*>& spillValues,
const StringData* warning) {
assert(targetBcOff != -1);
return getExitTraceImpl(targetBcOff, ExitFlag::None, spillValues,
[&]() -> SSATmp* {
gen(RaiseWarning, cns(warning));
return nullptr;
}
);
}
template<class ExitLambda>
IRTrace* HhbcTranslator::makeSideExit(Offset targetBcOff, ExitLambda exit) {
auto spillValues = peekSpillValues();
return getExitTraceImpl(targetBcOff,
ExitFlag::DelayedMarker,
spillValues,
exit);
}
IRTrace* HhbcTranslator::getExitSlowTrace() {
auto spillValues = peekSpillValues();
return getExitTraceImpl(bcOff(), ExitFlag::NoIR, spillValues,
CustomExit{});
}
IRTrace* HhbcTranslator::getExitTraceImpl(Offset targetBcOff,
ExitFlag flag,
std::vector<SSATmp*>& stackValues,
const CustomExit& customFn) {
BCMarker exitMarker;
exitMarker.bcOff = targetBcOff;
exitMarker.spOff = m_tb->spOffset() + stackValues.size() - m_stackDeficit;
exitMarker.func = curFunc();
BCMarker currentMarker = makeMarker(bcOff());
auto const exit = m_tb->makeExitTrace(targetBcOff);
TracePusher tp(*m_tb, exit,
flag == ExitFlag::DelayedMarker ? currentMarker : exitMarker);
// The value we use for stack is going to depend on whether we have
// to spillstack or what.
auto stack = m_tb->sp();
// TODO(#2404447) move this conditional to the simplifier?
if (m_stackDeficit != 0 || !stackValues.empty()) {
stackValues.insert(
stackValues.begin(),
{ m_tb->sp(), cns(int64_t(m_stackDeficit)) }
);
stack = gen(SpillStack, std::make_pair(stackValues.size(), &stackValues[0])
);
}
if (customFn) {
stack = gen(ExceptionBarrier, stack);
auto const customTmp = customFn();
if (customTmp) {
SSATmp* spill2[] = { stack, cns(0), customTmp };
stack = gen(SpillStack,
std::make_pair(sizeof spill2 / sizeof spill2[0], spill2)
);
exitMarker.spOff += 1;
}
}
if (flag == ExitFlag::DelayedMarker) {
m_tb->setMarker(exitMarker);
}
gen(SyncABIRegs, m_tb->fp(), stack);
if (flag == ExitFlag::NoIR) {
gen(targetBcOff == m_startBcOff ? ReqRetranslateNoIR : ReqBindJmpNoIR,
BCOffset(targetBcOff));
return exit;
}
if (bcOff() == m_startBcOff && targetBcOff == m_startBcOff) {
gen(ReqRetranslate);
} else {
gen(ReqBindJmp, BCOffset(targetBcOff));
}
return exit;
}
/*
* Create a catch trace for the current state of the eval stack. This is a
* trace intended to be invoked by the unwinder while unwinding a frame
* containing a call to C++ from translated code. When attached to an
* instruction as its taken field, code will be generated and the trace will be
* registered with the unwinder automatically.
*/
IRTrace* HhbcTranslator::getCatchTrace() {
auto exit = m_tb->makeExitTrace(bcOff());
assert(exit->blocks().size() == 1);
TracePusher tp(*m_tb, exit, makeMarker(bcOff()));
gen(BeginCatch);
auto sp = emitSpillStack(m_tb->sp(), peekSpillValues());
gen(EndCatch, sp);
assert(exit->blocks().size() == 1);
return exit;
}
SSATmp* HhbcTranslator::emitSpillStack(SSATmp* sp,
const std::vector<SSATmp*>& spillVals) {
std::vector<SSATmp*> ssaArgs{ sp, cns(int64_t(m_stackDeficit)) };
ssaArgs.insert(ssaArgs.end(), spillVals.begin(), spillVals.end());
auto args = std::make_pair(ssaArgs.size(), &ssaArgs[0]);
return gen(SpillStack, args);
}
SSATmp* HhbcTranslator::spillStack() {
auto newSp = emitSpillStack(m_tb->sp(), peekSpillValues());
m_evalStack.clear();
m_stackDeficit = 0;
return newSp;
}
void HhbcTranslator::exceptionBarrier() {
auto const sp = spillStack();
gen(ExceptionBarrier, sp);
}
SSATmp* HhbcTranslator::ldStackAddr(int32_t offset) {
// You're almost certainly doing it wrong if you want to get the address of a
// stack cell that's in m_evalStack.
assert(offset >= (int32_t)m_evalStack.numCells());
return gen(
LdStackAddr,
Type::PtrToGen,
StackOffset(offset + m_stackDeficit - m_evalStack.numCells()),
m_tb->sp()
);
}
SSATmp* HhbcTranslator::ldLoc(uint32_t locId) {
return gen(
LdLoc,
Type::Gen,
LocalId(locId),
m_tb->fp()
);
}
SSATmp* HhbcTranslator::ldLocAddr(uint32_t locId) {
return gen(
LdLocAddr,
Type::PtrToGen,
LocalId(locId),
m_tb->fp()
);
}
/*
* Load a local, and if it's boxed dereference to get the inner cell.
*
* Note: For boxed values, this will generate a LdRef instruction which
* takes the given exit trace in case the inner type doesn't match
* the tracked type for this local. This check may be optimized away
* if we can determine that the inner type must match the tracked type.
*/
SSATmp* HhbcTranslator::ldLocInner(uint32_t locId, IRTrace* exitTrace) {
auto loc = ldLoc(locId);
assert((loc->type().isBoxed() || loc->type().notBoxed()) &&
"Currently we don't handle traces where locals are maybeBoxed");
return loc->type().isBoxed()
? gen(LdRef, loc->type().innerType(), exitTrace, loc)
: loc;
}
/*
* This is a wrapper to ldLocInner that also emits the RaiseUninitLoc if the
* local is uninitialized. The catchTrace argument may be provided if the
* caller requires the catch trace to be generated at a point earlier than when
* it calls this function.
*/
SSATmp* HhbcTranslator::ldLocInnerWarn(uint32_t id, IRTrace* target,
IRTrace* catchTrace /* = nullptr */) {
if (!catchTrace) catchTrace = getCatchTrace();
auto const locVal = ldLocInner(id, target);
if (locVal->type().subtypeOf(Type::Uninit)) {
gen(RaiseUninitLoc, catchTrace, cns(curFunc()->localVarName(id)));
return m_tb->genDefInitNull();
}
return locVal;
}
/*
* Store to a local, if it's boxed set the value on the inner cell.
*
* Returns the value that was stored to the local, after incrementing
* its reference count.
*
* Pre: !newVal->type().isBoxed() && !newVal->type().maybeBoxed()
* Pre: exitTrace != nullptr if the local may be boxed
*/
SSATmp* HhbcTranslator::stLocImpl(uint32_t id,
IRTrace* exitTrace,
SSATmp* newVal,
bool doRefCount) {
assert(!newVal->type().maybeBoxed());
auto const oldLoc = ldLoc(id);
if (!(oldLoc->type().isBoxed() || oldLoc->type().notBoxed())) {
PUNT(stLocImpl-maybeBoxedValue);
}
if (oldLoc->type().notBoxed()) {
gen(StLoc, LocalId(id), m_tb->fp(), newVal);
auto const ret = doRefCount ? gen(IncRef, newVal) : newVal;
if (doRefCount) {
gen(DecRef, oldLoc);
}
return ret;
}
// It's important that the IncRef happens after the LdRef, since the
// LdRef is also a guard on the inner type and may side-exit.
assert(exitTrace);
auto const innerCell = gen(
LdRef, oldLoc->type().innerType(), exitTrace, oldLoc
);
auto const ret = doRefCount ? gen(IncRef, newVal) : newVal;
gen(StRef, oldLoc, newVal);
if (doRefCount) {
gen(DecRef, innerCell);
}
return ret;
}
SSATmp* HhbcTranslator::stLoc(uint32_t id, IRTrace* exit, SSATmp* newVal) {
const bool doRefCount = true;
return stLocImpl(id, exit, newVal, doRefCount);
}
SSATmp* HhbcTranslator::stLocNRC(uint32_t id, IRTrace* exit, SSATmp* newVal) {
const bool doRefCount = false;
return stLocImpl(id, exit, newVal, doRefCount);
}
void HhbcTranslator::end() {
if (m_hasExit) return;
auto const nextSk = curSrcKey().advanced(curUnit());
auto const nextPc = nextSk.offset();
if (nextPc >= curFunc()->past()) {
// We have fallen off the end of the func's bytecodes. This happens
// when the function's bytecodes end with an unconditional
// backwards jump so that nextPc is out of bounds and causes an
// assertion failure in unit.cpp. The common case for this comes
// from the default value funclets, which are placed after the end
// of the function, with an unconditional branch back to the start
// of the function. So you should see this in any function with
// default params.
return;
}
setBcOff(nextPc, true);
auto const sp = spillStack();
gen(SyncABIRegs, m_tb->fp(), sp);
gen(ReqBindJmp, BCOffset(nextPc));
}
void HhbcTranslator::checkStrictlyInteger(
SSATmp*& key, KeyType& keyType, bool& checkForInt) {
checkForInt = false;
if (key->isA(Type::Int)) {
keyType = KeyType::Int;
} else {
assert(key->isA(Type::Str));
keyType = KeyType::Str;
if (key->isConst()) {
int64_t i;
if (key->getValStr()->isStrictlyInteger(i)) {
keyType = KeyType::Int;
key = cns(i);
}
} else {
checkForInt = true;
}
}
}
}} // namespace HPHP::JIT