d6fd9de236
InterpOne sucks. Let's see if we can do this right.
4302 linhas
150 KiB
C++
4302 linhas
150 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/translator.h"
|
|
|
|
// Translator front-end: parse instruction stream into basic blocks, decode
|
|
// and normalize instructions. Propagate run-time type info to instructions
|
|
// to annotate their inputs and outputs with types.
|
|
#include <cinttypes>
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdarg.h>
|
|
|
|
#include <vector>
|
|
#include <string>
|
|
|
|
#include "folly/Conv.h"
|
|
|
|
#include "hphp/util/trace.h"
|
|
#include "hphp/util/biased_coin.h"
|
|
#include "hphp/util/map_walker.h"
|
|
#include "hphp/runtime/base/file_repository.h"
|
|
#include "hphp/runtime/base/runtime_option.h"
|
|
#include "hphp/runtime/base/stats.h"
|
|
#include "hphp/runtime/base/types.h"
|
|
#include "hphp/runtime/ext/ext_continuation.h"
|
|
#include "hphp/runtime/ext/ext_collections.h"
|
|
#include "hphp/runtime/vm/hhbc.h"
|
|
#include "hphp/runtime/vm/bytecode.h"
|
|
#include "hphp/runtime/vm/jit/annotation.h"
|
|
#include "hphp/runtime/vm/jit/hhbctranslator.h"
|
|
#include "hphp/runtime/vm/jit/irfactory.h"
|
|
#include "hphp/runtime/vm/jit/region_selection.h"
|
|
#include "hphp/runtime/vm/jit/targetcache.h"
|
|
#include "hphp/runtime/vm/jit/translator-inline.h"
|
|
#include "hphp/runtime/vm/jit/translator-x64.h"
|
|
#include "hphp/runtime/vm/jit/type.h"
|
|
#include "hphp/runtime/vm/pendq.h"
|
|
#include "hphp/runtime/vm/treadmill.h"
|
|
#include "hphp/runtime/vm/type_profile.h"
|
|
#include "hphp/runtime/vm/runtime.h"
|
|
|
|
namespace HPHP {
|
|
namespace Transl {
|
|
|
|
using namespace HPHP;
|
|
using HPHP::JIT::Type;
|
|
using HPHP::JIT::HhbcTranslator;
|
|
|
|
TRACE_SET_MOD(trans)
|
|
|
|
static __thread BiasedCoin *dbgTranslateCoin;
|
|
Translator* transl;
|
|
Lease Translator::s_writeLease;
|
|
|
|
struct TraceletContext {
|
|
TraceletContext() = delete;
|
|
|
|
TraceletContext(Tracelet* t, const TypeMap& initialTypes)
|
|
: m_t(t)
|
|
, m_numJmps(0)
|
|
, m_aliasTaint(false)
|
|
, m_varEnvTaint(false)
|
|
{
|
|
for (auto& kv : initialTypes) {
|
|
TRACE(1, "%s\n",
|
|
Trace::prettyNode("InitialType", kv.first, kv.second).c_str());
|
|
m_currentMap[kv.first] = t->newDynLocation(kv.first, kv.second);
|
|
}
|
|
}
|
|
|
|
Tracelet* m_t;
|
|
ChangeMap m_currentMap;
|
|
DepMap m_dependencies;
|
|
DepMap m_resolvedDeps; // dependencies resolved by static analysis
|
|
LocationSet m_changeSet;
|
|
LocationSet m_deletedSet;
|
|
int m_numJmps;
|
|
bool m_aliasTaint;
|
|
bool m_varEnvTaint;
|
|
|
|
RuntimeType currentType(const Location& l) const;
|
|
DynLocation* recordRead(const InputInfo& l, bool useHHIR,
|
|
DataType staticType = KindOfInvalid);
|
|
void recordWrite(DynLocation* dl);
|
|
void recordDelete(const Location& l);
|
|
void recordJmp();
|
|
void aliasTaint();
|
|
void varEnvTaint();
|
|
};
|
|
|
|
void InstrStream::append(NormalizedInstruction* ni) {
|
|
if (last) {
|
|
assert(first);
|
|
last->next = ni;
|
|
ni->prev = last;
|
|
ni->next = nullptr;
|
|
last = ni;
|
|
return;
|
|
}
|
|
assert(!first);
|
|
first = ni;
|
|
last = ni;
|
|
ni->prev = nullptr;
|
|
ni->next = nullptr;
|
|
}
|
|
|
|
void InstrStream::remove(NormalizedInstruction* ni) {
|
|
if (ni->prev) {
|
|
ni->prev->next = ni->next;
|
|
} else {
|
|
first = ni->next;
|
|
}
|
|
if (ni->next) {
|
|
ni->next->prev = ni->prev;
|
|
} else {
|
|
last = ni->prev;
|
|
}
|
|
ni->prev = nullptr;
|
|
ni->next = nullptr;
|
|
}
|
|
|
|
NormalizedInstruction* Tracelet::newNormalizedInstruction() {
|
|
NormalizedInstruction* ni = new NormalizedInstruction();
|
|
m_instrs.push_back(ni);
|
|
return ni;
|
|
}
|
|
|
|
DynLocation* Tracelet::newDynLocation(Location l, DataType t) {
|
|
DynLocation* dl = new DynLocation(l, t);
|
|
m_dynlocs.push_back(dl);
|
|
return dl;
|
|
}
|
|
|
|
DynLocation* Tracelet::newDynLocation(Location l, RuntimeType t) {
|
|
DynLocation* dl = new DynLocation(l, t);
|
|
m_dynlocs.push_back(dl);
|
|
return dl;
|
|
}
|
|
|
|
DynLocation* Tracelet::newDynLocation() {
|
|
DynLocation* dl = new DynLocation();
|
|
m_dynlocs.push_back(dl);
|
|
return dl;
|
|
}
|
|
|
|
void Tracelet::print() const {
|
|
print(std::cerr);
|
|
}
|
|
|
|
void Tracelet::print(std::ostream& out) const {
|
|
const NormalizedInstruction* i = m_instrStream.first;
|
|
if (i == nullptr) {
|
|
out << "<empty>\n";
|
|
return;
|
|
}
|
|
|
|
out << i->unit()->filepath()->data() << ':'
|
|
<< i->unit()->getLineNumber(i->offset()) << std::endl;
|
|
for (; i; i = i->next) {
|
|
out << " " << i->offset() << ": " << i->toString() << std::endl;
|
|
}
|
|
}
|
|
|
|
std::string Tracelet::toString() const {
|
|
std::ostringstream out;
|
|
print(out);
|
|
return out.str();
|
|
}
|
|
|
|
void sktrace(SrcKey sk, const char *fmt, ...) {
|
|
if (!Trace::enabled) {
|
|
return;
|
|
}
|
|
// We don't want to print string literals, so don't pass the unit
|
|
string s = instrToString((Op*)curUnit()->at(sk.offset()));
|
|
const char *filepath = "*anonFile*";
|
|
if (curUnit()->filepath()->data() &&
|
|
strlen(curUnit()->filepath()->data()) > 0)
|
|
filepath = curUnit()->filepath()->data();
|
|
Trace::trace("%s:%llx %6d: %20s ",
|
|
filepath, (unsigned long long)sk.getFuncId(),
|
|
sk.offset(), s.c_str());
|
|
va_list a;
|
|
va_start(a, fmt);
|
|
Trace::vtrace(fmt, a);
|
|
va_end(a);
|
|
}
|
|
|
|
SrcKey Tracelet::nextSk() const {
|
|
return m_instrStream.last->source.advanced(curUnit());
|
|
}
|
|
|
|
/*
|
|
* locPhysicalOffset --
|
|
*
|
|
* Return offset, in cells, of this location from its base
|
|
* pointer. It needs a function descriptor to see how many locals
|
|
* to skip for iterators; if the current frame pointer is not the context
|
|
* you're looking for, be sure to pass in a non-default f.
|
|
*/
|
|
int
|
|
Translator::locPhysicalOffset(Location l, const Func* f) {
|
|
f = f ? f : curFunc();
|
|
assert_not_implemented(l.space == Location::Stack ||
|
|
l.space == Location::Local ||
|
|
l.space == Location::Iter);
|
|
int localsToSkip = l.space == Location::Iter ? f->numLocals() : 0;
|
|
int iterInflator = l.space == Location::Iter ? kNumIterCells : 1;
|
|
return -((l.offset + 1) * iterInflator + localsToSkip);
|
|
}
|
|
|
|
RuntimeType Translator::liveType(Location l,
|
|
const Unit& u,
|
|
bool specialize) {
|
|
Cell *outer;
|
|
switch (l.space) {
|
|
case Location::Stack:
|
|
// Stack accesses must be to addresses pushed before
|
|
// translation time; if they are to addresses pushed after,
|
|
// they should be hitting in the changemap.
|
|
assert(locPhysicalOffset(l) >= 0);
|
|
// fallthru
|
|
case Location::Local: {
|
|
Cell *base;
|
|
int offset = locPhysicalOffset(l);
|
|
base = l.space == Location::Stack ? vmsp() : vmfp();
|
|
outer = &base[offset];
|
|
} break;
|
|
case Location::Iter: {
|
|
const Iter *it = frame_iter(curFrame(), l.offset);
|
|
TRACE(1, "Iter input: fp %p, iter %p, offset %" PRId64 "\n", vmfp(),
|
|
it, l.offset);
|
|
return RuntimeType(it);
|
|
} break;
|
|
case Location::Litstr: {
|
|
return RuntimeType(u.lookupLitstrId(l.offset));
|
|
} break;
|
|
case Location::Litint: {
|
|
return RuntimeType(l.offset);
|
|
} break;
|
|
case Location::This: {
|
|
return outThisObjectType();
|
|
} break;
|
|
default: {
|
|
not_reached();
|
|
}
|
|
}
|
|
assert(IS_REAL_TYPE(outer->m_type));
|
|
return liveType(outer, l, specialize);
|
|
}
|
|
|
|
RuntimeType
|
|
Translator::liveType(const Cell* outer,
|
|
const Location& l,
|
|
bool specialize) {
|
|
always_assert(analysisDepth() == 0);
|
|
|
|
if (!outer) {
|
|
// An undefined global; starts out as a variant null
|
|
return RuntimeType(KindOfRef, KindOfNull);
|
|
}
|
|
DataType outerType = (DataType)outer->m_type;
|
|
assert(IS_REAL_TYPE(outerType));
|
|
DataType valueType = outerType;
|
|
DataType innerType = KindOfInvalid;
|
|
const Cell* valCell = outer;
|
|
if (outerType == KindOfRef) {
|
|
// Variant. Pick up the inner type, too.
|
|
valCell = outer->m_data.pref->tv();
|
|
innerType = valCell->m_type;
|
|
assert(IS_REAL_TYPE(innerType));
|
|
valueType = innerType;
|
|
assert(innerType != KindOfRef);
|
|
TRACE(2, "liveType Var -> %d\n", innerType);
|
|
} else {
|
|
TRACE(2, "liveType %d\n", outerType);
|
|
}
|
|
const Class *klass = nullptr;
|
|
if (valueType == KindOfObject) {
|
|
// Only infer the class if specialization requested
|
|
if (specialize) {
|
|
klass = valCell->m_data.pobj->getVMClass();
|
|
}
|
|
}
|
|
RuntimeType retval = RuntimeType(outerType, innerType);
|
|
if (klass != nullptr) {
|
|
retval = retval.setKnownClass(klass);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
RuntimeType Translator::outThisObjectType() {
|
|
/*
|
|
* Use the current method's context class (ctx) as a constraint.
|
|
* For instance methods, if $this is non-null, we are guaranteed
|
|
* that $this is an instance of ctx or a class derived from
|
|
* ctx. Zend allows this assumption to be violated but we have
|
|
* deliberately chosen to diverge from them here.
|
|
*
|
|
* Note that if analysisDepth() != 0 we'll have !hasThis() here,
|
|
* because our fake ActRec has no $this, but we'll still return the
|
|
* correct object type because arGetContextClass() looks at
|
|
* ar->m_func's class for methods.
|
|
*/
|
|
const Class *ctx = curFunc()->isMethod() ?
|
|
arGetContextClass(curFrame()) : nullptr;
|
|
if (ctx) {
|
|
assert(!curFrame()->hasThis() ||
|
|
curFrame()->getThis()->getVMClass()->classof(ctx));
|
|
TRACE(2, "OutThisObject: derived from Class \"%s\"\n",
|
|
ctx->name()->data());
|
|
return RuntimeType(KindOfObject, KindOfInvalid, ctx);
|
|
}
|
|
return RuntimeType(KindOfObject, KindOfInvalid);
|
|
}
|
|
|
|
bool Translator::liveFrameIsPseudoMain() {
|
|
ActRec* ar = (ActRec*)vmfp();
|
|
return ar->hasVarEnv() && ar->getVarEnv()->isGlobalScope();
|
|
}
|
|
|
|
Location
|
|
Translator::tvToLocation(const TypedValue* tv, const TypedValue* frame) {
|
|
const Cell *arg0 = frame + locPhysicalOffset(Location(Location::Local, 0));
|
|
// Physical stack offsets grow downwards from the frame pointer. See
|
|
// locPhysicalOffset.
|
|
int offset = -(tv - arg0);
|
|
assert(offset >= 0);
|
|
assert(offset < ((ActRec*)frame)->m_func->numLocals());
|
|
TRACE(2, "tvToLocation: %p -> L:%d\n", tv, offset);
|
|
return Location(Location::Local, offset);
|
|
}
|
|
|
|
static int64_t typeToMask(DataType t) {
|
|
return (t == KindOfInvalid) ? 1 : (1 << (1 + getDataTypeIndex(t)));
|
|
}
|
|
|
|
struct InferenceRule {
|
|
int64_t mask;
|
|
DataType result;
|
|
};
|
|
|
|
static DataType inferType(const InferenceRule* rules,
|
|
const vector<DynLocation*>& inputs) {
|
|
int inputMask = 0;
|
|
// We generate the inputMask by ORing together the mask for each input's
|
|
// type.
|
|
for (unsigned int i = 0; i < inputs.size(); ++i) {
|
|
DataType inType = inputs[i]->rtt.valueType();
|
|
inputMask |= typeToMask(inType);
|
|
}
|
|
// This loop checks each rule in order, looking for the first rule that
|
|
// applies. Note that we assume there's a "catch-all" at the end.
|
|
for (unsigned int i = 0; ; ++i) {
|
|
if (rules[i].mask == 0 || (rules[i].mask & inputMask) != 0) {
|
|
return rules[i].result;
|
|
}
|
|
}
|
|
// We return KindOfInvalid by default if none of the rules applied.
|
|
return KindOfInvalid;
|
|
}
|
|
|
|
/*
|
|
* Inference rules used for OutArith. These are applied in order
|
|
* row-by-row.
|
|
*/
|
|
|
|
#define TYPE_MASK(name) \
|
|
static const int64_t name ## Mask = typeToMask(KindOf ## name);
|
|
TYPE_MASK(Invalid);
|
|
TYPE_MASK(Uninit);
|
|
TYPE_MASK(Null);
|
|
TYPE_MASK(Boolean);
|
|
static const int64_t IntMask = typeToMask(KindOfInt64);
|
|
TYPE_MASK(Double);
|
|
static const int64_t StringMask = typeToMask(KindOfString) |
|
|
typeToMask(KindOfStaticString);
|
|
TYPE_MASK(Array);
|
|
TYPE_MASK(Object);
|
|
|
|
static const InferenceRule ArithRules[] = {
|
|
{ DoubleMask, KindOfDouble },
|
|
{ ArrayMask, KindOfArray },
|
|
// If one of the inputs is known to be a String or if one of the input
|
|
// types is unknown, the output type is Unknown
|
|
{ StringMask | InvalidMask, KindOfInvalid },
|
|
// Default to Int64
|
|
{ 0, KindOfInt64 },
|
|
};
|
|
|
|
static const int NumArithRules = sizeof(ArithRules) / sizeof(InferenceRule);
|
|
|
|
/**
|
|
* Returns the type of the output of a bitwise operator on the two
|
|
* DynLocs. The only case that doesn't result in KindOfInt64 is String
|
|
* op String.
|
|
*/
|
|
static const InferenceRule BitOpRules[] = {
|
|
{ UninitMask | NullMask | BooleanMask |
|
|
IntMask | DoubleMask | ArrayMask | ObjectMask,
|
|
KindOfInt64 },
|
|
{ StringMask, KindOfString },
|
|
{ 0, KindOfInvalid },
|
|
};
|
|
|
|
static RuntimeType bitOpType(DynLocation* a, DynLocation* b) {
|
|
vector<DynLocation*> ins;
|
|
ins.push_back(a);
|
|
if (b) ins.push_back(b);
|
|
return RuntimeType(inferType(BitOpRules, ins));
|
|
}
|
|
|
|
static uint32_t m_w = 1; /* must not be zero */
|
|
static uint32_t m_z = 1; /* must not be zero */
|
|
|
|
static uint32_t get_random()
|
|
{
|
|
m_z = 36969 * (m_z & 65535) + (m_z >> 16);
|
|
m_w = 18000 * (m_w & 65535) + (m_w >> 16);
|
|
return (m_z << 16) + m_w; /* 32-bit result */
|
|
}
|
|
|
|
static const int kTooPolyPred = 2;
|
|
static const int kTooPolyRet = 6;
|
|
|
|
bool
|
|
isNormalPropertyAccess(const NormalizedInstruction& i,
|
|
int propInput,
|
|
int objInput) {
|
|
const LocationCode lcode = i.immVec.locationCode();
|
|
return
|
|
i.immVecM.size() == 1 &&
|
|
(lcode == LC || lcode == LL || lcode == LR || lcode == LH) &&
|
|
mcodeMaybePropName(i.immVecM[0]) &&
|
|
i.inputs[propInput]->isString() &&
|
|
i.inputs[objInput]->valueType() == KindOfObject;
|
|
}
|
|
|
|
bool
|
|
mInstrHasUnknownOffsets(const NormalizedInstruction& ni, Class* context) {
|
|
const MInstrInfo& mii = getMInstrInfo(ni.mInstrOp());
|
|
unsigned mi = 0;
|
|
unsigned ii = mii.valCount() + 1;
|
|
for (; mi < ni.immVecM.size(); ++mi) {
|
|
MemberCode mc = ni.immVecM[mi];
|
|
if (mcodeMaybePropName(mc)) {
|
|
const Class* cls = nullptr;
|
|
if (getPropertyOffset(ni, context, cls, mii, mi, ii).offset == -1) {
|
|
return true;
|
|
}
|
|
++ii;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
PropInfo getPropertyOffset(const NormalizedInstruction& ni,
|
|
Class* ctx,
|
|
const Class*& baseClass,
|
|
const MInstrInfo& mii,
|
|
unsigned mInd, unsigned iInd) {
|
|
if (mInd == 0) {
|
|
auto const baseIndex = mii.valCount();
|
|
baseClass = ni.inputs[baseIndex]->rtt.isObject()
|
|
? ni.inputs[baseIndex]->rtt.valueClass()
|
|
: nullptr;
|
|
} else {
|
|
baseClass = ni.immVecClasses[mInd - 1];
|
|
}
|
|
if (!baseClass) return PropInfo();
|
|
|
|
if (!ni.inputs[iInd]->rtt.isString()) {
|
|
return PropInfo();
|
|
}
|
|
auto* const name = ni.inputs[iInd]->rtt.valueString();
|
|
if (!name) return PropInfo();
|
|
|
|
bool accessible;
|
|
// If we are not in repo-authoriative mode, we need to check that
|
|
// baseClass cannot change in between requests
|
|
if (!RuntimeOption::RepoAuthoritative ||
|
|
!(baseClass->preClass()->attrs() & AttrUnique)) {
|
|
if (!ctx) return PropInfo();
|
|
if (!ctx->classof(baseClass)) {
|
|
if (baseClass->classof(ctx)) {
|
|
// baseClass can change on us in between requests, but since
|
|
// ctx is an ancestor of baseClass we can make the weaker
|
|
// assumption that the object is an instance of ctx
|
|
baseClass = ctx;
|
|
} else {
|
|
// baseClass can change on us in between requests and it is
|
|
// not related to ctx, so bail out
|
|
return PropInfo();
|
|
}
|
|
}
|
|
}
|
|
// Lookup the index of the property based on ctx and baseClass
|
|
Slot idx = baseClass->getDeclPropIndex(ctx, name, accessible);
|
|
// If we couldn't find a property that is accessible in the current
|
|
// context, bail out
|
|
if (idx == kInvalidSlot || !accessible) {
|
|
return PropInfo();
|
|
}
|
|
// If it's a declared property we're good to go: even if a subclass
|
|
// redefines an accessible property with the same name it's guaranteed
|
|
// to be at the same offset
|
|
return PropInfo(
|
|
baseClass->declPropOffset(idx),
|
|
baseClass->declPropHphpcType(idx)
|
|
);
|
|
}
|
|
|
|
PropInfo getFinalPropertyOffset(const NormalizedInstruction& ni,
|
|
Class* context,
|
|
const MInstrInfo& mii) {
|
|
unsigned mInd = ni.immVecM.size() - 1;
|
|
unsigned iInd = mii.valCount() + 1 + mInd;
|
|
|
|
const Class* cls = nullptr;
|
|
return getPropertyOffset(ni, context, cls, mii, mInd, iInd);
|
|
}
|
|
|
|
static std::pair<DataType,double>
|
|
predictMVec(const NormalizedInstruction* ni) {
|
|
auto info = getFinalPropertyOffset(*ni,
|
|
curFunc()->cls(),
|
|
getMInstrInfo(ni->mInstrOp()));
|
|
if (info.offset != -1 && info.hphpcType != KindOfInvalid) {
|
|
FTRACE(1, "prediction for CGetM prop: {}, hphpc\n",
|
|
int(info.hphpcType));
|
|
return std::make_pair(info.hphpcType, 1.0);
|
|
}
|
|
|
|
auto& immVec = ni->immVec;
|
|
StringData* name;
|
|
MemberCode mc;
|
|
if (immVec.decodeLastMember(curUnit(), name, mc)) {
|
|
auto pred = predictType(TypeProfileKey(mc, name));
|
|
TRACE(1, "prediction for CGetM %s named %s: %d, %f\n",
|
|
mc == MET ? "elt" : "prop",
|
|
name->data(),
|
|
pred.first,
|
|
pred.second);
|
|
return pred;
|
|
}
|
|
|
|
return std::make_pair(KindOfInvalid, 0.0);
|
|
}
|
|
|
|
/*
|
|
* predictOutputs --
|
|
*
|
|
* Provide a best guess for the output type of this instruction.
|
|
*/
|
|
static DataType
|
|
predictOutputs(SrcKey startSk,
|
|
const NormalizedInstruction* ni) {
|
|
if (!RuntimeOption::EvalJitTypePrediction) return KindOfInvalid;
|
|
|
|
if (RuntimeOption::EvalJitStressTypePredPercent &&
|
|
RuntimeOption::EvalJitStressTypePredPercent > int(get_random() % 100)) {
|
|
int dt;
|
|
while (true) {
|
|
dt = get_random() % (KindOfRef + 1);
|
|
switch (dt) {
|
|
case KindOfNull:
|
|
case KindOfBoolean:
|
|
case KindOfInt64:
|
|
case KindOfDouble:
|
|
case KindOfString:
|
|
case KindOfArray:
|
|
case KindOfObject:
|
|
break;
|
|
// KindOfRef and KindOfUninit can't happen for lots of predicted
|
|
// types.
|
|
case KindOfRef:
|
|
case KindOfUninit:
|
|
default:
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return DataType(dt);
|
|
}
|
|
|
|
if (ni->op() == OpMod) {
|
|
// x % 0 returns boolean false, so we don't know for certain, but it's
|
|
// probably an int.
|
|
return KindOfInt64;
|
|
}
|
|
|
|
if (ni->op() == OpDiv) {
|
|
// Integers can produce integers if there's no residue, but $i / $j in
|
|
// general produces a double. $i / 0 produces boolean false, so we have
|
|
// actually check the result.
|
|
return KindOfDouble;
|
|
}
|
|
|
|
if (ni->op() == OpClsCnsD) {
|
|
const NamedEntityPair& cne =
|
|
curFrame()->m_func->unit()->lookupNamedEntityPairId(ni->imm[1].u_SA);
|
|
StringData* cnsName = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
|
|
Class* cls = cne.second->getCachedClass();
|
|
if (cls) {
|
|
DataType dt = cls->clsCnsType(cnsName);
|
|
if (dt != KindOfUninit) {
|
|
TRACE(1, "clscnsd: %s:%s prediction type %d\n",
|
|
cne.first->data(), cnsName->data(), dt);
|
|
return dt;
|
|
}
|
|
}
|
|
}
|
|
|
|
static const double kAccept = 1.0;
|
|
std::pair<DataType, double> pred = std::make_pair(KindOfInvalid, 0.0);
|
|
// Type predictions grow tracelets, and can have a side effect of making
|
|
// them combinatorially explode if they bring in precondtions that vary a
|
|
// lot. Get more conservative as evidence mounts that this is a
|
|
// polymorphic tracelet.
|
|
if (tx64->numTranslations(startSk) >= kTooPolyPred) return KindOfInvalid;
|
|
if (ni->op() == OpCGetS) {
|
|
const StringData* propName = ni->inputs[1]->rtt.valueStringOrNull();
|
|
if (propName) {
|
|
pred = predictType(TypeProfileKey(TypeProfileKey::StaticPropName,
|
|
propName));
|
|
TRACE(1, "prediction for static fields named %s: %d, %f\n",
|
|
propName->data(),
|
|
pred.first,
|
|
pred.second);
|
|
}
|
|
} else if (hasImmVector(ni->op())) {
|
|
pred = predictMVec(ni);
|
|
}
|
|
if (debug && pred.second < kAccept) {
|
|
if (const StringData* invName = fcallToFuncName(ni)) {
|
|
pred = predictType(TypeProfileKey(TypeProfileKey::MethodName, invName));
|
|
TRACE(1, "prediction for methods named %s: %d, %f\n",
|
|
invName->data(),
|
|
pred.first,
|
|
pred.second);
|
|
}
|
|
}
|
|
if (pred.second >= kAccept) {
|
|
TRACE(1, "accepting prediction of type %d\n", pred.first);
|
|
assert(pred.first != KindOfUninit);
|
|
return pred.first;
|
|
}
|
|
return KindOfInvalid;
|
|
}
|
|
|
|
/**
|
|
* Returns the type of the value a SetOpL will store into the local.
|
|
*/
|
|
static RuntimeType setOpOutputType(NormalizedInstruction* ni,
|
|
const vector<DynLocation*>& inputs) {
|
|
assert(inputs.size() == 2);
|
|
const int kValIdx = 0;
|
|
const int kLocIdx = 1;
|
|
unsigned char op = ni->imm[1].u_OA;
|
|
DynLocation locLocation(inputs[kLocIdx]->location,
|
|
inputs[kLocIdx]->rtt.unbox());
|
|
assert(inputs[kLocIdx]->location.isLocal());
|
|
switch (op) {
|
|
case SetOpPlusEqual:
|
|
case SetOpMinusEqual:
|
|
case SetOpMulEqual: {
|
|
// Same as OutArith, except we have to fiddle with inputs a bit.
|
|
vector<DynLocation*> arithInputs;
|
|
arithInputs.push_back(&locLocation);
|
|
arithInputs.push_back(inputs[kValIdx]);
|
|
return RuntimeType(inferType(ArithRules, arithInputs));
|
|
}
|
|
case SetOpConcatEqual: return RuntimeType(KindOfString);
|
|
case SetOpDivEqual:
|
|
case SetOpModEqual: return RuntimeType(KindOfInvalid);
|
|
case SetOpAndEqual:
|
|
case SetOpOrEqual:
|
|
case SetOpXorEqual: return bitOpType(&locLocation, inputs[kValIdx]);
|
|
case SetOpSlEqual:
|
|
case SetOpSrEqual: return RuntimeType(KindOfInt64);
|
|
default:
|
|
not_reached();
|
|
}
|
|
}
|
|
|
|
static RuntimeType
|
|
getDynLocType(const SrcKey startSk,
|
|
NormalizedInstruction* ni,
|
|
InstrFlags::OutTypeConstraints constraint) {
|
|
using namespace InstrFlags;
|
|
auto const& inputs = ni->inputs;
|
|
assert(constraint != OutFInputL);
|
|
|
|
switch (constraint) {
|
|
#define CS(OutXLike, KindOfX) \
|
|
case OutXLike: \
|
|
return RuntimeType(KindOfX);
|
|
CS(OutInt64, KindOfInt64);
|
|
CS(OutBoolean, KindOfBoolean);
|
|
CS(OutDouble, KindOfDouble);
|
|
CS(OutString, KindOfString);
|
|
CS(OutNull, KindOfNull);
|
|
CS(OutUnknown, KindOfInvalid); // Subtle interaction with BB-breaking.
|
|
CS(OutFDesc, KindOfInvalid); // Unclear if OutFDesc has a purpose.
|
|
CS(OutArray, KindOfArray);
|
|
CS(OutObject, KindOfObject);
|
|
#undef CS
|
|
case OutPred: {
|
|
auto dt = predictOutputs(startSk, ni);
|
|
if (dt != KindOfInvalid) ni->outputPredicted = true;
|
|
return RuntimeType(dt);
|
|
}
|
|
|
|
case OutClassRef: {
|
|
Op op = Op(ni->op());
|
|
if ((op == OpAGetC && inputs[0]->isString())) {
|
|
const StringData *sd = inputs[0]->rtt.valueString();
|
|
if (sd) {
|
|
Class *klass = Unit::lookupUniqueClass(sd);
|
|
TRACE(3, "KindOfClass: derived class \"%s\" from string literal\n",
|
|
klass ? klass->preClass()->name()->data() : "NULL");
|
|
return RuntimeType(klass);
|
|
}
|
|
} else if (op == OpSelf) {
|
|
return RuntimeType(curClass());
|
|
} else if (op == OpParent) {
|
|
Class* clss = curClass();
|
|
if (clss != nullptr)
|
|
return RuntimeType(clss->parent());
|
|
}
|
|
return RuntimeType(KindOfClass);
|
|
}
|
|
|
|
case OutCns: {
|
|
// If it's a system constant, burn in its type. Otherwise we have
|
|
// to accept prediction; use the translation-time value, or fall back
|
|
// to the targetcache if none exists.
|
|
StringData *sd = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
|
|
assert(sd);
|
|
const TypedValue* tv = Unit::lookupPersistentCns(sd);
|
|
if (tv) {
|
|
return RuntimeType(tv->m_type);
|
|
}
|
|
tv = Unit::lookupCns(sd);
|
|
if (tv) {
|
|
ni->outputPredicted = true;
|
|
TRACE(1, "CNS %s: guessing runtime type %d\n", sd->data(), tv->m_type);
|
|
return RuntimeType(tv->m_type);
|
|
}
|
|
return RuntimeType(KindOfInvalid);
|
|
}
|
|
|
|
case OutNullUninit: {
|
|
assert(ni->op() == OpNullUninit);
|
|
return RuntimeType(KindOfUninit);
|
|
}
|
|
|
|
case OutStringImm: {
|
|
assert(ni->op() == OpString);
|
|
StringData *sd = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
|
|
assert(sd);
|
|
return RuntimeType(sd);
|
|
}
|
|
|
|
case OutArrayImm: {
|
|
assert(ni->op() == OpArray);
|
|
ArrayData *ad = curUnit()->lookupArrayId(ni->imm[0].u_AA);
|
|
assert(ad);
|
|
return RuntimeType(ad);
|
|
}
|
|
|
|
case OutBooleanImm: {
|
|
assert(ni->op() == OpTrue || ni->op() == OpFalse);
|
|
return RuntimeType(ni->op() == OpTrue);
|
|
}
|
|
|
|
case OutThisObject: {
|
|
return Translator::outThisObjectType();
|
|
}
|
|
|
|
case OutVUnknown: {
|
|
return RuntimeType(KindOfRef, KindOfInvalid);
|
|
}
|
|
|
|
case OutArith: {
|
|
return RuntimeType(inferType(ArithRules, inputs));
|
|
}
|
|
|
|
case OutSameAsInput: {
|
|
/*
|
|
* Relies closely on the order that inputs are pushed in
|
|
* getInputs(). (Pushing top of stack first for multi-stack
|
|
* consumers, stack elements before M-vectors and locals, etc.)
|
|
*/
|
|
assert(inputs.size() >= 1);
|
|
auto op = ni->op();
|
|
ASSERT_NOT_IMPLEMENTED(
|
|
// Sets and binds that take multiple arguments have the rhs
|
|
// pushed first. In the case of the M-vector versions, the
|
|
// rhs comes before the M-vector elements.
|
|
op == OpSetL || op == OpSetN || op == OpSetG || op == OpSetS ||
|
|
op == OpBindL || op == OpBindG || op == OpBindS || op == OpBindN ||
|
|
op == OpBindM ||
|
|
// Dup takes a single element.
|
|
op == OpDup
|
|
);
|
|
|
|
const int idx = 0; // all currently supported cases.
|
|
|
|
if (debug) {
|
|
if (!inputs[idx]->rtt.isVagueValue()) {
|
|
if (op == OpBindG || op == OpBindN || op == OpBindS ||
|
|
op == OpBindM || op == OpBindL) {
|
|
assert(inputs[idx]->rtt.isRef() && !inputs[idx]->isLocal());
|
|
} else {
|
|
assert(inputs[idx]->rtt.valueType() ==
|
|
inputs[idx]->rtt.outerType());
|
|
}
|
|
}
|
|
}
|
|
return inputs[idx]->rtt;
|
|
}
|
|
|
|
case OutSetM: {
|
|
/*
|
|
* SetM returns null for "invalid" inputs, or a string if the base was a
|
|
* string. VectorTranslator ensures that invalid inputs or a string
|
|
* output when we weren't expecting it will cause a side exit, so we can
|
|
* keep this fairly simple.
|
|
*/
|
|
|
|
if (ni->immVecM.size() > 1) {
|
|
// We don't know the type of the base for the final operation so we
|
|
// can't assume anything about the output
|
|
// type.
|
|
return RuntimeType(KindOfAny);
|
|
}
|
|
|
|
// For single-element vectors, we can determine the output type from the
|
|
// base.
|
|
Type baseType;
|
|
switch (ni->immVec.locationCode()) {
|
|
case LGL: case LGC:
|
|
case LNL: case LNC:
|
|
case LSL: case LSC:
|
|
baseType = Type::Gen;
|
|
break;
|
|
|
|
default:
|
|
baseType = Type::fromRuntimeType(inputs[1]->rtt);
|
|
}
|
|
|
|
const bool setElem = mcodeMaybeArrayOrMapKey(ni->immVecM[0]);
|
|
const Type valType = Type::fromRuntimeType(inputs[0]->rtt);
|
|
if (setElem && baseType.maybe(Type::Str)) {
|
|
if (baseType.isString()) {
|
|
// The base is a string so our output is a string.
|
|
return RuntimeType(KindOfString);
|
|
} else if (!valType.isString()) {
|
|
// The base might be a string and our value isn't known to
|
|
// be a string. The output type could be Str or valType.
|
|
return RuntimeType(KindOfAny);
|
|
}
|
|
}
|
|
|
|
return inputs[0]->rtt;
|
|
}
|
|
|
|
case OutCInputL: {
|
|
assert(inputs.size() >= 1);
|
|
const DynLocation* in = inputs[inputs.size() - 1];
|
|
RuntimeType retval;
|
|
if (in->rtt.outerType() == KindOfUninit) {
|
|
// Locals can be KindOfUninit, so we need to convert
|
|
// this to KindOfNull
|
|
retval = RuntimeType(KindOfNull);
|
|
} else {
|
|
retval = in->rtt.unbox();
|
|
}
|
|
TRACE(2, "Input (%d, %d) -> (%d, %d)\n",
|
|
in->rtt.outerType(), in->rtt.innerType(),
|
|
retval.outerType(), retval.innerType());
|
|
return retval;
|
|
}
|
|
|
|
case OutIncDec: {
|
|
const RuntimeType &inRtt = ni->inputs[0]->rtt;
|
|
// TODO: instead of KindOfInvalid this should track the actual
|
|
// type we will get from interping a non-int IncDec.
|
|
return RuntimeType(IS_INT_TYPE(inRtt.valueType()) ?
|
|
KindOfInt64 : KindOfInvalid);
|
|
}
|
|
|
|
case OutStrlen: {
|
|
auto const& rtt = ni->inputs[0]->rtt;
|
|
return RuntimeType(rtt.isString() ? KindOfInt64 : KindOfInvalid);
|
|
}
|
|
|
|
case OutCInput: {
|
|
assert(inputs.size() >= 1);
|
|
const DynLocation* in = inputs[inputs.size() - 1];
|
|
if (in->rtt.outerType() == KindOfRef) {
|
|
return in->rtt.unbox();
|
|
}
|
|
return in->rtt;
|
|
}
|
|
|
|
case OutBitOp: {
|
|
assert(inputs.size() == 2 ||
|
|
(inputs.size() == 1 && ni->op() == OpBitNot));
|
|
if (inputs.size() == 2) {
|
|
return bitOpType(inputs[0], inputs[1]);
|
|
} else {
|
|
return bitOpType(inputs[0], nullptr);
|
|
}
|
|
}
|
|
|
|
case OutSetOp: {
|
|
return setOpOutputType(ni, inputs);
|
|
}
|
|
|
|
case OutNone:
|
|
default:
|
|
return RuntimeType(KindOfInvalid);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* NB: this opcode structure is sparse; it cannot just be indexed by
|
|
* opcode.
|
|
*/
|
|
using namespace InstrFlags;
|
|
static const struct {
|
|
Op op;
|
|
InstrInfo info;
|
|
} instrInfoSparse [] = {
|
|
|
|
// Op Inputs Outputs OutputTypes Stack delta
|
|
// -- ------ ------- ----------- -----------
|
|
|
|
/*** 1. Basic instructions ***/
|
|
|
|
{ OpNop, {None, None, OutNone, 0 }},
|
|
{ OpPopC, {Stack1|
|
|
DontGuardStack1, None, OutNone, -1 }},
|
|
{ OpPopV, {Stack1|
|
|
DontGuardStack1|
|
|
IgnoreInnerType, None, OutNone, -1 }},
|
|
{ OpPopR, {Stack1|
|
|
DontGuardStack1|
|
|
IgnoreInnerType, None, OutNone, -1 }},
|
|
{ OpDup, {Stack1, StackTop2, OutSameAsInput, 1 }},
|
|
{ OpBox, {Stack1, Stack1, OutVInput, 0 }},
|
|
{ OpUnbox, {Stack1, Stack1, OutCInput, 0 }},
|
|
{ OpBoxR, {Stack1, Stack1, OutVInput, 0 }},
|
|
{ OpUnboxR, {Stack1, Stack1, OutCInput, 0 }},
|
|
|
|
/*** 2. Literal and constant instructions ***/
|
|
|
|
{ OpNull, {None, Stack1, OutNull, 1 }},
|
|
{ OpNullUninit, {None, Stack1, OutNullUninit, 1 }},
|
|
{ OpTrue, {None, Stack1, OutBooleanImm, 1 }},
|
|
{ OpFalse, {None, Stack1, OutBooleanImm, 1 }},
|
|
{ OpInt, {None, Stack1, OutInt64, 1 }},
|
|
{ OpDouble, {None, Stack1, OutDouble, 1 }},
|
|
{ OpString, {None, Stack1, OutStringImm, 1 }},
|
|
{ OpArray, {None, Stack1, OutArrayImm, 1 }},
|
|
{ OpNewArray, {None, Stack1, OutArray, 1 }},
|
|
{ OpNewTuple, {StackN, Stack1, OutArray, 0 }},
|
|
{ OpAddElemC, {StackTop3, Stack1, OutArray, -2 }},
|
|
{ OpAddElemV, {StackTop3, Stack1, OutArray, -2 }},
|
|
{ OpAddNewElemC, {StackTop2, Stack1, OutArray, -1 }},
|
|
{ OpAddNewElemV, {StackTop2, Stack1, OutArray, -1 }},
|
|
{ OpNewCol, {None, Stack1, OutObject, 1 }},
|
|
{ OpColAddElemC, {StackTop3, Stack1, OutObject, -2 }},
|
|
{ OpColAddNewElemC, {StackTop2, Stack1, OutObject, -1 }},
|
|
{ OpCns, {None, Stack1, OutCns, 1 }},
|
|
{ OpCnsE, {None, Stack1, OutCns, 1 }},
|
|
{ OpCnsU, {None, Stack1, OutCns, 1 }},
|
|
{ OpClsCns, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpClsCnsD, {None, Stack1, OutPred, 1 }},
|
|
{ OpFile, {None, Stack1, OutString, 1 }},
|
|
{ OpDir, {None, Stack1, OutString, 1 }},
|
|
|
|
/*** 3. Operator instructions ***/
|
|
|
|
/* Binary string */
|
|
{ OpConcat, {StackTop2, Stack1, OutString, -1 }},
|
|
/* Arithmetic ops */
|
|
{ OpAdd, {StackTop2, Stack1, OutArith, -1 }},
|
|
{ OpSub, {StackTop2, Stack1, OutArith, -1 }},
|
|
{ OpMul, {StackTop2, Stack1, OutArith, -1 }},
|
|
/* Div and mod might return boolean false. Sigh. */
|
|
{ OpDiv, {StackTop2, Stack1, OutUnknown, -1 }},
|
|
{ OpMod, {StackTop2, Stack1, OutPred, -1 }},
|
|
/* Logical ops */
|
|
{ OpXor, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpNot, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpSame, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpNSame, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpEq, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpNeq, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpLt, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpLte, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpGt, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpGte, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
/* Bitwise ops */
|
|
{ OpBitAnd, {StackTop2, Stack1, OutBitOp, -1 }},
|
|
{ OpBitOr, {StackTop2, Stack1, OutBitOp, -1 }},
|
|
{ OpBitXor, {StackTop2, Stack1, OutBitOp, -1 }},
|
|
{ OpBitNot, {Stack1, Stack1, OutBitOp, 0 }},
|
|
{ OpShl, {StackTop2, Stack1, OutInt64, -1 }},
|
|
{ OpShr, {StackTop2, Stack1, OutInt64, -1 }},
|
|
/* Cast instructions */
|
|
{ OpCastBool, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpCastInt, {Stack1, Stack1, OutInt64, 0 }},
|
|
{ OpCastDouble, {Stack1, Stack1, OutDouble, 0 }},
|
|
{ OpCastString, {Stack1, Stack1, OutString, 0 }},
|
|
{ OpCastArray, {Stack1, Stack1, OutArray, 0 }},
|
|
{ OpCastObject, {Stack1, Stack1, OutObject, 0 }},
|
|
{ OpInstanceOf, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpInstanceOfD, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpPrint, {Stack1, Stack1, OutInt64, 0 }},
|
|
{ OpClone, {Stack1, Stack1, OutObject, 0 }},
|
|
{ OpExit, {Stack1, None, OutNone, -1 }},
|
|
{ OpFatal, {Stack1, None, OutNone, -1 }},
|
|
|
|
/*** 4. Control flow instructions ***/
|
|
|
|
{ OpJmp, {None, None, OutNone, 0 }},
|
|
{ OpJmpZ, {Stack1, None, OutNone, -1 }},
|
|
{ OpJmpNZ, {Stack1, None, OutNone, -1 }},
|
|
{ OpSwitch, {Stack1, None, OutNone, -1 }},
|
|
{ OpSSwitch, {Stack1, None, OutNone, -1 }},
|
|
/*
|
|
* RetC and RetV are special. Their manipulation of the runtime stack are
|
|
* outside the boundaries of the tracelet abstraction; since they always end
|
|
* a basic block, they behave more like "glue" between BBs than the
|
|
* instructions in the body of a BB.
|
|
*
|
|
* RetC and RetV consume a value from the stack, and this value's type needs
|
|
* to be known at compile-time.
|
|
*/
|
|
{ OpRetC, {AllLocals, None, OutNone, 0 }},
|
|
{ OpRetV, {AllLocals, None, OutNone, 0 }},
|
|
{ OpThrow, {Stack1, None, OutNone, -1 }},
|
|
{ OpUnwind, {None, None, OutNone, 0 }},
|
|
|
|
/*** 5. Get instructions ***/
|
|
|
|
{ OpCGetL, {Local, Stack1, OutCInputL, 1 }},
|
|
{ OpCGetL2, {Stack1|Local, StackIns1, OutCInputL, 1 }},
|
|
{ OpCGetL3, {StackTop2|Local, StackIns2, OutCInputL, 1 }},
|
|
{ OpCGetN, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpCGetG, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpCGetS, {StackTop2, Stack1, OutPred, -1 }},
|
|
{ OpCGetM, {MVector, Stack1, OutPred, 1 }},
|
|
{ OpVGetL, {Local, Stack1, OutVInputL, 1 }},
|
|
{ OpVGetN, {Stack1, Stack1, OutVUnknown, 0 }},
|
|
// TODO: In pseudo-main, the VGetG instruction invalidates what we know
|
|
// about the types of the locals because it could cause any one of the
|
|
// local variables to become "boxed". We need to add logic to tracelet
|
|
// analysis to deal with this properly.
|
|
{ OpVGetG, {Stack1, Stack1, OutVUnknown, 0 }},
|
|
{ OpVGetS, {StackTop2, Stack1, OutVUnknown, -1 }},
|
|
{ OpVGetM, {MVector, Stack1|Local, OutVUnknown, 1 }},
|
|
{ OpAGetC, {Stack1, Stack1, OutClassRef, 0 }},
|
|
{ OpAGetL, {Local, Stack1, OutClassRef, 1 }},
|
|
|
|
/*** 6. Isset, Empty, and type querying instructions ***/
|
|
|
|
{ OpAKExists, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpIssetL, {Local, Stack1, OutBoolean, 1 }},
|
|
{ OpIssetN, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpIssetG, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpIssetS, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpIssetM, {MVector, Stack1, OutBoolean, 1 }},
|
|
{ OpEmptyL, {Local, Stack1, OutBoolean, 1 }},
|
|
{ OpEmptyN, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpEmptyG, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpEmptyS, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpEmptyM, {MVector, Stack1, OutBoolean, 1 }},
|
|
{ OpIsNullC, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpIsBoolC, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpIsIntC, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpIsDoubleC, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpIsStringC, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpIsArrayC, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpIsObjectC, {Stack1, Stack1, OutBoolean, 0 }},
|
|
{ OpIsNullL, {Local, Stack1, OutBoolean, 1 }},
|
|
{ OpIsBoolL, {Local, Stack1, OutBoolean, 1 }},
|
|
{ OpIsIntL, {Local, Stack1, OutBoolean, 1 }},
|
|
{ OpIsDoubleL, {Local, Stack1, OutBoolean, 1 }},
|
|
{ OpIsStringL, {Local, Stack1, OutBoolean, 1 }},
|
|
{ OpIsArrayL, {Local, Stack1, OutBoolean, 1 }},
|
|
{ OpIsObjectL, {Local, Stack1, OutBoolean, 1 }},
|
|
|
|
/*** 7. Mutator instructions ***/
|
|
|
|
{ OpSetL, {Stack1|Local, Stack1|Local, OutSameAsInput, 0 }},
|
|
{ OpSetN, {StackTop2, Stack1|Local, OutSameAsInput, -1 }},
|
|
{ OpSetG, {StackTop2, Stack1, OutSameAsInput, -1 }},
|
|
{ OpSetS, {StackTop3, Stack1, OutSameAsInput, -2 }},
|
|
{ OpSetM, {MVector|Stack1, Stack1|Local, OutSetM, 0 }},
|
|
{ OpSetWithRefLM,{MVector|Local , Local, OutNone, 0 }},
|
|
{ OpSetWithRefRM,{MVector|Stack1, Local, OutNone, -1 }},
|
|
{ OpSetOpL, {Stack1|Local, Stack1|Local, OutSetOp, 0 }},
|
|
{ OpSetOpN, {StackTop2, Stack1|Local, OutUnknown, -1 }},
|
|
{ OpSetOpG, {StackTop2, Stack1, OutUnknown, -1 }},
|
|
{ OpSetOpS, {StackTop3, Stack1, OutUnknown, -2 }},
|
|
{ OpSetOpM, {MVector|Stack1, Stack1|Local, OutUnknown, 0 }},
|
|
{ OpIncDecL, {Local, Stack1|Local, OutIncDec, 1 }},
|
|
{ OpIncDecN, {Stack1, Stack1|Local, OutUnknown, 0 }},
|
|
{ OpIncDecG, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpIncDecS, {StackTop2, Stack1, OutUnknown, -1 }},
|
|
{ OpIncDecM, {MVector, Stack1, OutUnknown, 1 }},
|
|
{ OpBindL, {Stack1|Local|
|
|
IgnoreInnerType, Stack1|Local, OutSameAsInput, 0 }},
|
|
{ OpBindN, {StackTop2, Stack1|Local, OutSameAsInput, -1 }},
|
|
{ OpBindG, {StackTop2, Stack1, OutSameAsInput, -1 }},
|
|
{ OpBindS, {StackTop3, Stack1, OutSameAsInput, -2 }},
|
|
{ OpBindM, {MVector|Stack1, Stack1|Local, OutSameAsInput, 0 }},
|
|
{ OpUnsetL, {Local, Local, OutNone, 0 }},
|
|
{ OpUnsetN, {Stack1, Local, OutNone, -1 }},
|
|
{ OpUnsetG, {Stack1, None, OutNone, -1 }},
|
|
{ OpUnsetM, {MVector, None, OutNone, 0 }},
|
|
|
|
/*** 8. Call instructions ***/
|
|
|
|
{ OpFPushFunc, {Stack1, FStack, OutFDesc,
|
|
kNumActRecCells - 1 }},
|
|
{ OpFPushFuncD, {None, FStack, OutFDesc,
|
|
kNumActRecCells }},
|
|
{ OpFPushFuncU, {None, FStack, OutFDesc,
|
|
kNumActRecCells }},
|
|
{ OpFPushObjMethod,
|
|
{StackTop2, FStack, OutFDesc,
|
|
kNumActRecCells - 2 }},
|
|
{ OpFPushObjMethodD,
|
|
{Stack1, FStack, OutFDesc,
|
|
kNumActRecCells - 1 }},
|
|
{ OpFPushClsMethod,
|
|
{StackTop2, FStack, OutFDesc,
|
|
kNumActRecCells - 2 }},
|
|
{ OpFPushClsMethodF,
|
|
{StackTop2, FStack, OutFDesc,
|
|
kNumActRecCells - 2 }},
|
|
{ OpFPushClsMethodD,
|
|
{None, FStack, OutFDesc,
|
|
kNumActRecCells }},
|
|
{ OpFPushCtor, {Stack1, Stack1|FStack,OutObject,
|
|
kNumActRecCells }},
|
|
{ OpFPushCtorD, {None, Stack1|FStack,OutObject,
|
|
kNumActRecCells + 1 }},
|
|
{ OpFPushCufIter,{None, FStack, OutFDesc,
|
|
kNumActRecCells }},
|
|
{ OpFPushCuf, {Stack1, FStack, OutFDesc,
|
|
kNumActRecCells - 1 }},
|
|
{ OpFPushCufF, {Stack1, FStack, OutFDesc,
|
|
kNumActRecCells - 1 }},
|
|
{ OpFPushCufSafe,{StackTop2|DontGuardAny,
|
|
StackCufSafe, OutFDesc,
|
|
kNumActRecCells }},
|
|
{ OpFPassC, {FuncdRef, None, OutSameAsInput, 0 }},
|
|
{ OpFPassCW, {FuncdRef, None, OutSameAsInput, 0 }},
|
|
{ OpFPassCE, {FuncdRef, None, OutSameAsInput, 0 }},
|
|
{ OpFPassV, {Stack1|FuncdRef, Stack1, OutUnknown, 0 }},
|
|
{ OpFPassR, {Stack1|FuncdRef, Stack1, OutFInputR, 0 }},
|
|
{ OpFPassL, {Local|FuncdRef, Stack1, OutFInputL, 1 }},
|
|
{ OpFPassN, {Stack1|FuncdRef, Stack1, OutUnknown, 0 }},
|
|
{ OpFPassG, {Stack1|FuncdRef, Stack1, OutFInputR, 0 }},
|
|
{ OpFPassS, {StackTop2|FuncdRef,
|
|
Stack1, OutUnknown, -1 }},
|
|
{ OpFPassM, {MVector|FuncdRef, Stack1, OutUnknown, 1 }},
|
|
/*
|
|
* FCall is special. Like the Ret* instructions, its manipulation of the
|
|
* runtime stack are outside the boundaries of the tracelet abstraction.
|
|
*/
|
|
{ OpFCall, {FStack, Stack1, OutPred, 0 }},
|
|
{ OpFCallArray, {FStack, Stack1, OutPred,
|
|
-(int)kNumActRecCells }},
|
|
// TODO: output type is known
|
|
{ OpFCallBuiltin,{BStackN, Stack1, OutPred, 0 }},
|
|
{ OpCufSafeArray,{StackTop3|DontGuardAny,
|
|
Stack1, OutArray, -2 }},
|
|
{ OpCufSafeReturn,{StackTop3|DontGuardAny,
|
|
Stack1, OutUnknown, -2 }},
|
|
{ OpDecodeCufIter,{Stack1, None, OutNone, -1 }},
|
|
|
|
/*** 11. Iterator instructions ***/
|
|
|
|
{ OpIterInit, {Stack1, Local, OutUnknown, -1 }},
|
|
{ OpMIterInit, {Stack1, Local, OutUnknown, -1 }},
|
|
{ OpWIterInit, {Stack1, Local, OutUnknown, -1 }},
|
|
{ OpIterInitK, {Stack1, Local, OutUnknown, -1 }},
|
|
{ OpMIterInitK, {Stack1, Local, OutUnknown, -1 }},
|
|
{ OpWIterInitK, {Stack1, Local, OutUnknown, -1 }},
|
|
{ OpIterNext, {None, Local, OutUnknown, 0 }},
|
|
{ OpMIterNext, {None, Local, OutUnknown, 0 }},
|
|
{ OpWIterNext, {None, Local, OutUnknown, 0 }},
|
|
{ OpIterNextK, {None, Local, OutUnknown, 0 }},
|
|
{ OpMIterNextK, {None, Local, OutUnknown, 0 }},
|
|
{ OpWIterNextK, {None, Local, OutUnknown, 0 }},
|
|
{ OpIterFree, {None, None, OutNone, 0 }},
|
|
{ OpMIterFree, {None, None, OutNone, 0 }},
|
|
{ OpCIterFree, {None, None, OutNone, 0 }},
|
|
|
|
/*** 12. Include, eval, and define instructions ***/
|
|
|
|
{ OpIncl, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpInclOnce, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpReq, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpReqOnce, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpReqDoc, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpEval, {Stack1, Stack1, OutUnknown, 0 }},
|
|
{ OpDefFunc, {None, None, OutNone, 0 }},
|
|
{ OpDefTypedef, {None, None, OutNone, 0 }},
|
|
{ OpDefCls, {None, None, OutNone, 0 }},
|
|
{ OpDefCns, {Stack1, Stack1, OutBoolean, 0 }},
|
|
|
|
/*** 13. Miscellaneous instructions ***/
|
|
|
|
{ OpThis, {None, Stack1, OutThisObject, 1 }},
|
|
{ OpBareThis, {None, Stack1, OutUnknown, 1 }},
|
|
{ OpCheckThis, {This, None, OutNone, 0 }},
|
|
{ OpInitThisLoc,
|
|
{None, Local, OutUnknown, 0 }},
|
|
{ OpStaticLoc,
|
|
{None, Stack1, OutBoolean, 1 }},
|
|
{ OpStaticLocInit,
|
|
{Stack1, Local, OutVUnknown, -1 }},
|
|
{ OpCatch, {None, Stack1, OutObject, 1 }},
|
|
{ OpVerifyParamType,
|
|
{Local, None, OutNone, 0 }},
|
|
{ OpClassExists, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpInterfaceExists,
|
|
{StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpTraitExists, {StackTop2, Stack1, OutBoolean, -1 }},
|
|
{ OpSelf, {None, Stack1, OutClassRef, 1 }},
|
|
{ OpParent, {None, Stack1, OutClassRef, 1 }},
|
|
{ OpLateBoundCls,{None, Stack1, OutClassRef, 1 }},
|
|
{ OpNativeImpl, {None, None, OutNone, 0 }},
|
|
{ OpCreateCl, {BStackN, Stack1, OutObject, 1 }},
|
|
{ OpStrlen, {Stack1, Stack1, OutStrlen, 0 }},
|
|
{ OpIncStat, {None, None, OutNone, 0 }},
|
|
{ OpArrayIdx, {StackTop3, Stack1, OutUnknown, -2 }},
|
|
|
|
/*** 14. Continuation instructions ***/
|
|
|
|
{ OpCreateCont, {None, Stack1, OutObject, 1 }},
|
|
{ OpContEnter, {Stack1, None, OutNone, -1 }},
|
|
{ OpUnpackCont, {Local, StackTop2, OutInt64, 2 }},
|
|
{ OpContSuspend, {Local|Stack1, None, OutNone, -1 }},
|
|
{ OpContRetC, {Local|Stack1, None, OutNone, -1 }},
|
|
{ OpContCheck, {None, None, OutNone, 0 }},
|
|
{ OpContSend, {Local, Stack1, OutUnknown, 1 }},
|
|
{ OpContRaise, {Local, Stack1, OutUnknown, 1 }},
|
|
{ OpContValid, {None, Stack1, OutBoolean, 1 }},
|
|
{ OpContCurrent, {None, Stack1, OutUnknown, 1 }},
|
|
{ OpContStopped, {None, None, OutNone, 0 }},
|
|
{ OpContHandle, {Stack1, None, OutNone, -1 }},
|
|
};
|
|
|
|
static hphp_hash_map<Op, InstrInfo> instrInfo;
|
|
static bool instrInfoInited;
|
|
static void initInstrInfo() {
|
|
if (!instrInfoInited) {
|
|
for (size_t i = 0; i < sizeof(instrInfoSparse) / sizeof(instrInfoSparse[0]);
|
|
i++) {
|
|
instrInfo[instrInfoSparse[i].op] = instrInfoSparse[i].info;
|
|
}
|
|
|
|
instrInfoInited = true;
|
|
}
|
|
}
|
|
|
|
const InstrInfo& getInstrInfo(Op op) {
|
|
assert(instrInfoInited);
|
|
return instrInfo[op];
|
|
}
|
|
|
|
static int numHiddenStackInputs(const NormalizedInstruction& ni) {
|
|
assert(ni.immVec.isValid());
|
|
return ni.immVec.numStackValues();
|
|
}
|
|
|
|
namespace {
|
|
int64_t countOperands(uint64_t mask) {
|
|
const uint64_t ignore = FuncdRef | Local | Iter | AllLocals |
|
|
DontGuardLocal | DontGuardStack1 | DontBreakLocal | DontBreakStack1 |
|
|
IgnoreInnerType | DontGuardAny | This;
|
|
mask &= ~ignore;
|
|
|
|
static const uint64_t counts[][2] = {
|
|
{Stack3, 1},
|
|
{Stack2, 1},
|
|
{Stack1, 1},
|
|
{StackIns1, 2},
|
|
{StackIns2, 3},
|
|
{FStack, kNumActRecCells},
|
|
};
|
|
|
|
int64_t count = 0;
|
|
for (auto const& pair : counts) {
|
|
if (mask & pair[0]) {
|
|
count += pair[1];
|
|
mask &= ~pair[0];
|
|
}
|
|
}
|
|
assert(mask == 0);
|
|
return count;
|
|
}
|
|
}
|
|
|
|
int64_t getStackPopped(const NormalizedInstruction& ni) {
|
|
switch (ni.op()) {
|
|
case OpFCall: return ni.imm[0].u_IVA + kNumActRecCells;
|
|
case OpFCallArray: return kNumActRecCells + 1;
|
|
|
|
case OpFCallBuiltin:
|
|
case OpNewTuple:
|
|
case OpCreateCl: return ni.imm[0].u_IVA;
|
|
|
|
default: break;
|
|
}
|
|
|
|
uint64_t mask = getInstrInfo(ni.op()).in;
|
|
int64_t count = 0;
|
|
|
|
if (mask & MVector) {
|
|
count += ni.immVec.numStackValues();
|
|
mask &= ~MVector;
|
|
}
|
|
if (mask & (StackN | BStackN)) {
|
|
count += ni.imm[0].u_IVA;
|
|
mask &= ~(StackN | BStackN);
|
|
}
|
|
|
|
return count + countOperands(mask);
|
|
}
|
|
|
|
int64_t getStackPushed(const NormalizedInstruction& ni) {
|
|
switch (ni.op()) {
|
|
case OpFPushCufSafe: return kNumActRecCells + 2;
|
|
|
|
default: break;
|
|
}
|
|
|
|
return countOperands(getInstrInfo(ni.op()).out);
|
|
}
|
|
|
|
int getStackDelta(const NormalizedInstruction& ni) {
|
|
int hiddenStackInputs = 0;
|
|
initInstrInfo();
|
|
auto op = ni.op();
|
|
switch (op) {
|
|
case OpFCall: {
|
|
int numArgs = ni.imm[0].u_IVA;
|
|
return 1 - numArgs - kNumActRecCells;
|
|
}
|
|
|
|
case OpFCallBuiltin:
|
|
case OpNewTuple:
|
|
case OpCreateCl:
|
|
return 1 - ni.imm[0].u_IVA;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
const InstrInfo& info = instrInfo[op];
|
|
if (info.in & MVector) {
|
|
hiddenStackInputs = numHiddenStackInputs(ni);
|
|
SKTRACE(2, ni.source, "Has %d hidden stack inputs\n", hiddenStackInputs);
|
|
}
|
|
int delta = instrInfo[op].numPushed - hiddenStackInputs;
|
|
return delta;
|
|
}
|
|
|
|
static NormalizedInstruction* findInputSrc(NormalizedInstruction* ni,
|
|
DynLocation* dl) {
|
|
while (ni != nullptr) {
|
|
if (ni->outStack == dl ||
|
|
ni->outLocal == dl ||
|
|
ni->outLocal2 == dl ||
|
|
ni->outStack2 == dl ||
|
|
ni->outStack3 == dl) {
|
|
break;
|
|
}
|
|
ni = ni->prev;
|
|
}
|
|
return ni;
|
|
}
|
|
|
|
/*
|
|
* For MetaData information that affects whether we want to even put a
|
|
* value in the ni->inputs, we need to look at it before we call
|
|
* getInputs(), so this is separate from applyInputMetaData.
|
|
*
|
|
* We also check GuardedThis here, since RetC is short-circuited in
|
|
* applyInputMetaData.
|
|
*/
|
|
void Translator::preInputApplyMetaData(Unit::MetaHandle metaHand,
|
|
NormalizedInstruction* ni) {
|
|
if (!metaHand.findMeta(ni->unit(), ni->offset())) return;
|
|
|
|
Unit::MetaInfo info;
|
|
while (metaHand.nextArg(info)) {
|
|
switch (info.m_kind) {
|
|
case Unit::MetaInfo::Kind::NonRefCounted:
|
|
ni->nonRefCountedLocals.resize(curFunc()->numLocals());
|
|
ni->nonRefCountedLocals[info.m_data] = 1;
|
|
break;
|
|
case Unit::MetaInfo::Kind::GuardedThis:
|
|
ni->guardedThis = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Translator::applyInputMetaData(Unit::MetaHandle& metaHand,
|
|
NormalizedInstruction* ni,
|
|
TraceletContext& tas,
|
|
InputInfos &inputInfos) {
|
|
if (!metaHand.findMeta(ni->unit(), ni->offset())) return false;
|
|
|
|
Unit::MetaInfo info;
|
|
if (!metaHand.nextArg(info)) return false;
|
|
if (info.m_kind == Unit::MetaInfo::Kind::NopOut) {
|
|
ni->noOp = true;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* We need to adjust the indexes in MetaInfo::m_arg if this
|
|
* instruction takes other stack arguments than those related to the
|
|
* MVector. (For example, the rhs of an assignment.)
|
|
*/
|
|
const InstrInfo& iInfo = instrInfo[ni->op()];
|
|
if (iInfo.in & AllLocals) {
|
|
/*
|
|
* RetC/RetV dont care about their stack input, but it may have
|
|
* been annotated. Skip it (because RetC/RetV pretend they dont
|
|
* have a stack input).
|
|
*/
|
|
return false;
|
|
}
|
|
if (iInfo.in == FuncdRef) {
|
|
/*
|
|
* FPassC* pretend to have no inputs
|
|
*/
|
|
return false;
|
|
}
|
|
const int base = !(iInfo.in & MVector) ? 0 :
|
|
!(iInfo.in & Stack1) ? 0 :
|
|
!(iInfo.in & Stack2) ? 1 :
|
|
!(iInfo.in & Stack3) ? 2 : 3;
|
|
|
|
do {
|
|
SKTRACE(3, ni->source, "considering MetaInfo of kind %d\n", info.m_kind);
|
|
|
|
int arg = info.m_arg & Unit::MetaInfo::VectorArg ?
|
|
base + (info.m_arg & ~Unit::MetaInfo::VectorArg) : info.m_arg;
|
|
|
|
switch (info.m_kind) {
|
|
case Unit::MetaInfo::Kind::NoSurprise:
|
|
ni->noSurprise = true;
|
|
break;
|
|
case Unit::MetaInfo::Kind::GuardedCls:
|
|
ni->guardedCls = true;
|
|
break;
|
|
case Unit::MetaInfo::Kind::ArrayCapacity:
|
|
ni->imm[0].u_IVA = info.m_data;
|
|
break;
|
|
case Unit::MetaInfo::Kind::DataTypePredicted: {
|
|
// If the original type was invalid or predicted, then use the
|
|
// prediction in the meta-data.
|
|
assert((unsigned) arg < inputInfos.size());
|
|
|
|
SKTRACE(1, ni->source, "MetaInfo DataTypePredicted for input %d; "
|
|
"newType = %d\n", arg, DataType(info.m_data));
|
|
InputInfo& ii = inputInfos[arg];
|
|
DynLocation* dl = tas.recordRead(ii, false, KindOfInvalid);
|
|
NormalizedInstruction* src = findInputSrc(tas.m_t->m_instrStream.last,
|
|
dl);
|
|
if (src) {
|
|
// Update the rtt and mark src's output as predicted if either:
|
|
// a) we don't have type information yet (ie, it's KindOfInvalid), or
|
|
// b) src's output was predicted. This is assuming that the
|
|
// front-end's prediction is more accurate.
|
|
if (dl->rtt.outerType() == KindOfInvalid || src->outputPredicted) {
|
|
SKTRACE(1, ni->source, "MetaInfo DataTypePredicted for input %d; "
|
|
"replacing oldType = %d with newType = %d\n", arg,
|
|
dl->rtt.outerType(), DataType(info.m_data));
|
|
dl->rtt = RuntimeType((DataType)info.m_data);
|
|
src->outputPredicted = true;
|
|
src->outputPredictionStatic = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Unit::MetaInfo::Kind::DataTypeInferred: {
|
|
assert((unsigned)arg < inputInfos.size());
|
|
SKTRACE(1, ni->source, "MetaInfo DataTypeInferred for input %d; "
|
|
"newType = %d\n", arg, DataType(info.m_data));
|
|
InputInfo& ii = inputInfos[arg];
|
|
ii.dontGuard = true;
|
|
DynLocation* dl = tas.recordRead(ii, true, (DataType)info.m_data);
|
|
if (dl->rtt.outerType() != info.m_data &&
|
|
(!dl->isString() || info.m_data != KindOfString)) {
|
|
if (dl->rtt.outerType() != KindOfInvalid) {
|
|
// Either static analysis is wrong, or
|
|
// this was mis-predicted by the type
|
|
// profiler, or this code is unreachable,
|
|
// and there's an earlier bytecode in the tracelet
|
|
// thats going to fatal
|
|
NormalizedInstruction *src = nullptr;
|
|
if (mapContains(tas.m_changeSet, dl->location)) {
|
|
src = findInputSrc(tas.m_t->m_instrStream.last, dl);
|
|
if (src && src->outputPredicted) {
|
|
src->outputPredicted = false;
|
|
} else {
|
|
src = nullptr;
|
|
}
|
|
}
|
|
if (!src) {
|
|
// Not a type-profiler mis-predict
|
|
if (tas.m_t->m_instrStream.first) {
|
|
// We're not the first instruction, so punt
|
|
// If this bytecode /is/ reachable, we'll
|
|
// get here again, and that time, we will
|
|
// be the first instruction
|
|
punt();
|
|
}
|
|
not_reached();
|
|
}
|
|
}
|
|
dl->rtt = RuntimeType((DataType)info.m_data);
|
|
ni->markInputInferred(arg);
|
|
} else {
|
|
/*
|
|
* Static inference confirmed the expected type
|
|
* but if the expected type was provided by the type
|
|
* profiler we want to clear outputPredicted to
|
|
* avoid unneeded guards
|
|
*/
|
|
if (mapContains(tas.m_changeSet, dl->location)) {
|
|
NormalizedInstruction *src =
|
|
findInputSrc(tas.m_t->m_instrStream.last, dl);
|
|
if (src->outputPredicted) {
|
|
src->outputPredicted = false;
|
|
ni->markInputInferred(arg);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Unit::MetaInfo::Kind::String: {
|
|
const StringData* sd = ni->unit()->lookupLitstrId(info.m_data);
|
|
assert((unsigned)arg < inputInfos.size());
|
|
InputInfo& ii = inputInfos[arg];
|
|
ii.dontGuard = true;
|
|
DynLocation* dl = tas.recordRead(ii, true, KindOfString);
|
|
assert(!dl->rtt.isString() || !dl->rtt.valueString() ||
|
|
dl->rtt.valueString() == sd);
|
|
SKTRACE(1, ni->source, "MetaInfo on input %d; old type = %s\n",
|
|
arg, dl->pretty().c_str());
|
|
dl->rtt = RuntimeType(sd);
|
|
break;
|
|
}
|
|
|
|
case Unit::MetaInfo::Kind::Class: {
|
|
assert((unsigned)arg < inputInfos.size());
|
|
InputInfo& ii = inputInfos[arg];
|
|
DynLocation* dl = tas.recordRead(ii, true);
|
|
if (dl->rtt.valueType() != KindOfObject) {
|
|
continue;
|
|
}
|
|
|
|
const StringData* metaName = ni->unit()->lookupLitstrId(info.m_data);
|
|
const StringData* rttName =
|
|
dl->rtt.valueClass() ? dl->rtt.valueClass()->name() : nullptr;
|
|
// The two classes might not be exactly the same, which is ok
|
|
// as long as metaCls is more derived than rttCls.
|
|
Class* metaCls = Unit::lookupUniqueClass(metaName);
|
|
Class* rttCls = rttName ? Unit::lookupUniqueClass(rttName) : nullptr;
|
|
if (metaCls && rttCls && metaCls != rttCls &&
|
|
!metaCls->classof(rttCls)) {
|
|
// Runtime type is more derived
|
|
metaCls = 0;
|
|
}
|
|
if (metaCls && metaCls != rttCls) {
|
|
SKTRACE(1, ni->source, "replacing input %d with a MetaInfo-supplied "
|
|
"class of %s; old type = %s\n",
|
|
arg, metaName->data(), dl->pretty().c_str());
|
|
if (dl->rtt.isRef()) {
|
|
dl->rtt = RuntimeType(KindOfRef, KindOfObject, metaCls);
|
|
} else {
|
|
dl->rtt = RuntimeType(KindOfObject, KindOfInvalid, metaCls);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Unit::MetaInfo::Kind::MVecPropClass: {
|
|
const StringData* metaName = ni->unit()->lookupLitstrId(info.m_data);
|
|
Class* metaCls = Unit::lookupUniqueClass(metaName);
|
|
if (metaCls) {
|
|
ni->immVecClasses[arg] = metaCls;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case Unit::MetaInfo::Kind::NopOut:
|
|
// NopOut should always be the first and only annotation
|
|
// and was handled above.
|
|
not_reached();
|
|
|
|
case Unit::MetaInfo::Kind::GuardedThis:
|
|
case Unit::MetaInfo::Kind::NonRefCounted:
|
|
// fallthrough; these are handled in preInputApplyMetaData.
|
|
case Unit::MetaInfo::Kind::None:
|
|
break;
|
|
}
|
|
} while (metaHand.nextArg(info));
|
|
|
|
return false;
|
|
}
|
|
|
|
static void addMVectorInputs(NormalizedInstruction& ni,
|
|
int& currentStackOffset,
|
|
std::vector<InputInfo>& inputs) {
|
|
assert(ni.immVec.isValid());
|
|
ni.immVecM.reserve(ni.immVec.size());
|
|
|
|
int UNUSED stackCount = 0;
|
|
int UNUSED localCount = 0;
|
|
|
|
currentStackOffset -= ni.immVec.numStackValues();
|
|
int localStackOffset = currentStackOffset;
|
|
|
|
auto push_stack = [&] {
|
|
++stackCount;
|
|
inputs.emplace_back(Location(Location::Stack, localStackOffset++));
|
|
};
|
|
auto push_local = [&] (int imm) {
|
|
++localCount;
|
|
inputs.emplace_back(Location(Location::Local, imm));
|
|
};
|
|
|
|
/*
|
|
* Note that we have to push as we go so that the arguments come in
|
|
* the order expected for the M-vector.
|
|
*
|
|
* Indexes into these argument lists must also be in the same order
|
|
* as the information in Unit::MetaInfo, because the analysis phase
|
|
* may replace some of them with literals.
|
|
*/
|
|
|
|
/*
|
|
* Also note: if we eventually have immediates that are not local
|
|
* ids (i.e. string ids), this analysis step is going to have to be
|
|
* a bit wiser.
|
|
*/
|
|
const uint8_t* vec = ni.immVec.vec();
|
|
const LocationCode lcode = LocationCode(*vec++);
|
|
|
|
const bool trailingClassRef = lcode == LSL || lcode == LSC;
|
|
|
|
switch (numLocationCodeStackVals(lcode)) {
|
|
case 0: {
|
|
if (lcode == LH) {
|
|
inputs.emplace_back(Location(Location::This));
|
|
} else {
|
|
assert(lcode == LL || lcode == LGL || lcode == LNL);
|
|
int numImms = numLocationCodeImms(lcode);
|
|
for (int i = 0; i < numImms; ++i) {
|
|
push_local(decodeVariableSizeImm(&vec));
|
|
}
|
|
}
|
|
} break;
|
|
case 1:
|
|
if (lcode == LSL) {
|
|
// We'll get the trailing stack value after pushing all the
|
|
// member vector elements.
|
|
push_local(decodeVariableSizeImm(&vec));
|
|
} else {
|
|
push_stack();
|
|
}
|
|
break;
|
|
case 2:
|
|
push_stack();
|
|
if (!trailingClassRef) {
|
|
// This one is actually at the back.
|
|
push_stack();
|
|
}
|
|
break;
|
|
default: not_reached();
|
|
}
|
|
|
|
// Now push all the members in the correct order.
|
|
while (vec - ni.immVec.vec() < ni.immVec.size()) {
|
|
const MemberCode mcode = MemberCode(*vec++);
|
|
ni.immVecM.push_back(mcode);
|
|
|
|
if (mcode == MW) {
|
|
// No stack and no locals.
|
|
} else if (memberCodeHasImm(mcode)) {
|
|
int64_t imm = decodeMemberCodeImm(&vec, mcode);
|
|
if (memberCodeImmIsLoc(mcode)) {
|
|
push_local(imm);
|
|
} else if (memberCodeImmIsString(mcode)) {
|
|
inputs.emplace_back(Location(Location::Litstr, imm));
|
|
} else {
|
|
assert(memberCodeImmIsInt(mcode));
|
|
inputs.emplace_back(Location(Location::Litint, imm));
|
|
}
|
|
} else {
|
|
push_stack();
|
|
}
|
|
inputs.back().dontGuardInner = true;
|
|
}
|
|
|
|
if (trailingClassRef) {
|
|
push_stack();
|
|
}
|
|
|
|
ni.immVecClasses.resize(ni.immVecM.size());
|
|
|
|
assert(vec - ni.immVec.vec() == ni.immVec.size());
|
|
assert(stackCount == ni.immVec.numStackValues());
|
|
|
|
SKTRACE(2, ni.source, "M-vector using %d hidden stack "
|
|
"inputs, %d locals\n", stackCount, localCount);
|
|
}
|
|
|
|
/*
|
|
* getInputs --
|
|
* Returns locations for this instruction's inputs.
|
|
*
|
|
* Throws:
|
|
* TranslationFailedExc:
|
|
* Unimplemented functionality, probably an opcode.
|
|
*
|
|
* UnknownInputExc:
|
|
* Consumed a datum whose type or value could not be constrained at
|
|
* translation time, because the tracelet has already modified it.
|
|
* Truncate the tracelet at the preceding instruction, which must
|
|
* exists because *something* modified something in it.
|
|
*/
|
|
void Translator::getInputs(SrcKey startSk,
|
|
NormalizedInstruction* ni,
|
|
int& currentStackOffset,
|
|
InputInfos& inputs,
|
|
std::function<Type(int)> localType) {
|
|
#ifdef USE_TRACE
|
|
const SrcKey& sk = ni->source;
|
|
#endif
|
|
assert(inputs.empty());
|
|
if (debug && !mapContains(instrInfo, ni->op())) {
|
|
fprintf(stderr, "Translator does not understand "
|
|
"instruction %s\n", opcodeToName(ni->op()));
|
|
assert(false);
|
|
}
|
|
const InstrInfo& info = instrInfo[ni->op()];
|
|
Operands input = info.in;
|
|
if (input & FuncdRef) {
|
|
inputs.needsRefCheck = true;
|
|
}
|
|
if (input & Iter) {
|
|
inputs.emplace_back(Location(Location::Iter, ni->imm[0].u_IVA));
|
|
}
|
|
if (input & FStack) {
|
|
currentStackOffset -= ni->imm[0].u_IVA; // arguments consumed
|
|
currentStackOffset -= kNumActRecCells; // ActRec is torn down as well
|
|
}
|
|
if (input & IgnoreInnerType) ni->ignoreInnerType = true;
|
|
if (input & Stack1) {
|
|
SKTRACE(1, sk, "getInputs: stack1 %d\n", currentStackOffset - 1);
|
|
inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
|
|
if (input & DontGuardStack1) inputs.back().dontGuard = true;
|
|
if (input & DontBreakStack1) inputs.back().dontBreak = true;
|
|
if (input & Stack2) {
|
|
SKTRACE(1, sk, "getInputs: stack2 %d\n", currentStackOffset - 1);
|
|
inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
|
|
if (input & Stack3) {
|
|
SKTRACE(1, sk, "getInputs: stack3 %d\n", currentStackOffset - 1);
|
|
inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
|
|
}
|
|
}
|
|
}
|
|
if (input & StackN) {
|
|
int numArgs = ni->imm[0].u_IVA;
|
|
SKTRACE(1, sk, "getInputs: stackN %d %d\n", currentStackOffset - 1,
|
|
numArgs);
|
|
for (int i = 0; i < numArgs; i++) {
|
|
inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
|
|
inputs.back().dontGuard = true;
|
|
inputs.back().dontBreak = true;
|
|
}
|
|
}
|
|
if (input & BStackN) {
|
|
int numArgs = ni->imm[0].u_IVA;
|
|
SKTRACE(1, sk, "getInputs: BStackN %d %d\n", currentStackOffset - 1,
|
|
numArgs);
|
|
for (int i = 0; i < numArgs; i++) {
|
|
inputs.emplace_back(Location(Location::Stack, --currentStackOffset));
|
|
}
|
|
}
|
|
if (input & MVector) {
|
|
addMVectorInputs(*ni, currentStackOffset, inputs);
|
|
}
|
|
if (input & Local) {
|
|
// Many of the continuation instructions read local 0. All other
|
|
// instructions that take a Local have its index at their first
|
|
// immediate.
|
|
int loc;
|
|
auto insertAt = inputs.end();
|
|
switch (ni->op()) {
|
|
case OpUnpackCont:
|
|
case OpContSuspend:
|
|
case OpContRetC:
|
|
case OpContSend:
|
|
case OpContRaise:
|
|
loc = 0;
|
|
break;
|
|
|
|
case OpSetWithRefLM:
|
|
insertAt = inputs.begin();
|
|
// fallthrough
|
|
case OpFPassL:
|
|
loc = ni->imm[1].u_IVA;
|
|
break;
|
|
|
|
default:
|
|
loc = ni->imm[0].u_IVA;
|
|
break;
|
|
}
|
|
SKTRACE(1, sk, "getInputs: local %d\n", loc);
|
|
inputs.emplace(insertAt, Location(Location::Local, loc));
|
|
if (input & DontGuardLocal) inputs.back().dontGuard = true;
|
|
if (input & DontBreakLocal) inputs.back().dontBreak = true;
|
|
}
|
|
|
|
const bool wantInlineReturn = [&] {
|
|
const int localCount = curFunc()->numLocals();
|
|
// Inline return causes us to guard this tracelet more precisely. If
|
|
// we're already chaining to get here, just do a generic return in the
|
|
// hopes of avoiding further specialization. The localCount constraint
|
|
// is an unfortunate consequence of the current generic machinery not
|
|
// working for 0 locals.
|
|
if (tx64->numTranslations(startSk) >= kTooPolyRet && localCount > 0) {
|
|
return false;
|
|
}
|
|
ni->nonRefCountedLocals.resize(localCount);
|
|
int numRefCounted = 0;
|
|
for (int i = 0; i < localCount; ++i) {
|
|
auto curType = localType(i);
|
|
if (ni->nonRefCountedLocals[i]) {
|
|
assert(curType.notCounted() && "Static analysis was wrong");
|
|
}
|
|
numRefCounted += curType.maybeCounted();
|
|
}
|
|
return numRefCounted <= kMaxInlineReturnDecRefs;
|
|
}();
|
|
|
|
if ((input & AllLocals) && wantInlineReturn) {
|
|
ni->inlineReturn = true;
|
|
ni->ignoreInnerType = true;
|
|
int n = curFunc()->numLocals();
|
|
for (int i = 0; i < n; ++i) {
|
|
if (!ni->nonRefCountedLocals[i]) {
|
|
inputs.emplace_back(Location(Location::Local, i));
|
|
}
|
|
}
|
|
}
|
|
|
|
SKTRACE(1, sk, "stack args: virtual sfo now %d\n", currentStackOffset);
|
|
TRACE(1, "%s\n", Trace::prettyNode("Inputs", inputs).c_str());
|
|
|
|
if (inputs.size() &&
|
|
((input & DontGuardAny) || dontGuardAnyInputs(ni->op()))) {
|
|
for (int i = inputs.size(); i--; ) {
|
|
inputs[i].dontGuard = true;
|
|
}
|
|
}
|
|
if (input & This) {
|
|
inputs.emplace_back(Location(Location::This));
|
|
}
|
|
}
|
|
|
|
bool outputDependsOnInput(const Op instr) {
|
|
switch (instrInfo[instr].type) {
|
|
case OutNull:
|
|
case OutNullUninit:
|
|
case OutString:
|
|
case OutStringImm:
|
|
case OutDouble:
|
|
case OutBoolean:
|
|
case OutBooleanImm:
|
|
case OutInt64:
|
|
case OutArray:
|
|
case OutArrayImm:
|
|
case OutObject:
|
|
case OutThisObject:
|
|
case OutUnknown:
|
|
case OutVUnknown:
|
|
case OutClassRef:
|
|
case OutPred:
|
|
case OutCns:
|
|
case OutStrlen:
|
|
case OutNone:
|
|
return false;
|
|
case OutFDesc:
|
|
case OutSameAsInput:
|
|
case OutCInput:
|
|
case OutVInput:
|
|
case OutCInputL:
|
|
case OutVInputL:
|
|
case OutFInputL:
|
|
case OutFInputR:
|
|
case OutArith:
|
|
case OutBitOp:
|
|
case OutSetOp:
|
|
case OutIncDec:
|
|
case OutSetM:
|
|
return true;
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
/*
|
|
* getOutputs --
|
|
* Builds a vector describing this instruction's outputs. Also
|
|
* records any write to a value that *might* alias a local.
|
|
*
|
|
* Throws:
|
|
* TranslationFailedExc:
|
|
* Unimplemented functionality, probably an opcode.
|
|
*/
|
|
void Translator::getOutputs(/*inout*/ Tracelet& t,
|
|
/*inout*/ NormalizedInstruction* ni,
|
|
/*inout*/ int& currentStackOffset,
|
|
/*out*/ bool& varEnvTaint) {
|
|
varEnvTaint = false;
|
|
|
|
const vector<DynLocation*>& inputs = ni->inputs;
|
|
const Op op = ni->op();
|
|
|
|
initInstrInfo();
|
|
assert_not_implemented(instrInfo.find(op) != instrInfo.end());
|
|
const Operands outLocs = instrInfo[op].out;
|
|
const OutTypeConstraints typeInfo = instrInfo[op].type;
|
|
|
|
SKTRACE(1, ni->source, "output flavor %d\n", typeInfo);
|
|
if (typeInfo == OutFInputL || typeInfo == OutFInputR ||
|
|
typeInfo == OutVInputL) {
|
|
// Variable number of outputs. If we box the loc we're reading,
|
|
// we need to write out its boxed-ness.
|
|
assert(inputs.size() >= 1);
|
|
const DynLocation* in = inputs[inputs.size() - 1];
|
|
DynLocation* outDynLoc = t.newDynLocation(in->location, in->rtt);
|
|
outDynLoc->location = Location(Location::Stack, currentStackOffset++);
|
|
bool isRef;
|
|
if (typeInfo == OutVInputL) {
|
|
isRef = true;
|
|
} else {
|
|
assert(typeInfo == OutFInputL || typeInfo == OutFInputR);
|
|
isRef = ni->preppedByRef;
|
|
}
|
|
if (isRef) {
|
|
// Locals can be KindOfUninit, so we need to convert
|
|
// this to KindOfNull
|
|
if (in->rtt.outerType() == KindOfUninit) {
|
|
outDynLoc->rtt = RuntimeType(KindOfRef, KindOfNull);
|
|
} else {
|
|
outDynLoc->rtt = in->rtt.box();
|
|
}
|
|
SKTRACE(1, ni->source, "boxed type: %d -> %d\n",
|
|
outDynLoc->rtt.outerType(), outDynLoc->rtt.innerType());
|
|
} else {
|
|
if (outDynLoc->rtt.outerType() == KindOfUninit) {
|
|
outDynLoc->rtt = RuntimeType(KindOfNull);
|
|
} else {
|
|
outDynLoc->rtt = outDynLoc->rtt.unbox();
|
|
}
|
|
SKTRACE(1, ni->source, "unboxed type: %d\n",
|
|
outDynLoc->rtt.outerType());
|
|
}
|
|
assert(outDynLoc->location.isStack());
|
|
ni->outStack = outDynLoc;
|
|
|
|
if (isRef && in->rtt.outerType() != KindOfRef &&
|
|
typeInfo != OutFInputR &&
|
|
in->location.isLocal()) {
|
|
// VGetH or FPassH boxing a local
|
|
DynLocation* smashedLocal =
|
|
t.newDynLocation(in->location, outDynLoc->rtt);
|
|
assert(smashedLocal->location.isLocal());
|
|
ni->outLocal = smashedLocal;
|
|
}
|
|
// Other things that might be getting boxed here include globals
|
|
// and array values; since we don't attempt to track these things'
|
|
// types in symbolic execution anyway, we can ignore them.
|
|
return;
|
|
}
|
|
|
|
int opnd = None;
|
|
for (int outLocsCopy = (int)outLocs;
|
|
outLocsCopy != (int)None;
|
|
outLocsCopy &= ~opnd) {
|
|
opnd = 1 << (ffs(outLocsCopy) - 1);
|
|
assert(opnd != None && opnd != Stack3); // no instr produces 3 values
|
|
assert(opnd != FuncdRef); // reffiness is immutable
|
|
Location loc;
|
|
switch (opnd) {
|
|
// Pseudo-outputs that affect translator state
|
|
case FStack: {
|
|
currentStackOffset += kNumActRecCells;
|
|
if (op == OpFPushFuncD) {
|
|
const Unit& cu = *ni->unit();
|
|
Id funcId = ni->imm[1].u_SA;
|
|
const NamedEntityPair &nep = cu.lookupNamedEntityPairId(funcId);
|
|
const Func* f = Unit::lookupFunc(nep.second);
|
|
if (f && f->isNameBindingImmutable(&cu)) {
|
|
t.m_arState.pushFuncD(f);
|
|
} else {
|
|
t.m_arState.pushDynFunc();
|
|
}
|
|
} else {
|
|
// Non-deterministic in some way
|
|
t.m_arState.pushDynFunc();
|
|
}
|
|
} continue; // no instr-associated output
|
|
|
|
case Local: {
|
|
if (op == OpSetN || op == OpSetOpN || op == OpIncDecN ||
|
|
op == OpBindN || op == OpUnsetN) {
|
|
varEnvTaint = true;
|
|
continue;
|
|
}
|
|
ASSERT_NOT_IMPLEMENTED(op == OpSetOpL ||
|
|
op == OpSetM || op == OpSetOpM ||
|
|
op == OpBindM ||
|
|
op == OpSetWithRefLM || op == OpSetWithRefRM ||
|
|
op == OpIncDecL ||
|
|
op == OpVGetM ||
|
|
op == OpStaticLocInit || op == OpInitThisLoc ||
|
|
op == OpSetL || op == OpBindL ||
|
|
op == OpUnsetL ||
|
|
op == OpIterInit || op == OpIterInitK ||
|
|
op == OpMIterInit || op == OpMIterInitK ||
|
|
op == OpWIterInit || op == OpWIterInitK ||
|
|
op == OpIterNext || op == OpIterNextK ||
|
|
op == OpMIterNext || op == OpMIterNextK ||
|
|
op == OpWIterNext || op == OpWIterNextK);
|
|
if (op == OpIncDecL) {
|
|
assert(ni->inputs.size() == 1);
|
|
const RuntimeType &inRtt = ni->inputs[0]->rtt;
|
|
RuntimeType rtt = IS_INT_TYPE(inRtt.valueType()) ? inRtt :
|
|
RuntimeType(KindOfInvalid);
|
|
DynLocation* incDecLoc =
|
|
t.newDynLocation(ni->inputs[0]->location, rtt);
|
|
assert(incDecLoc->location.isLocal());
|
|
ni->outLocal = incDecLoc;
|
|
continue; // Doesn't mutate a loc's types for int. Carry on.
|
|
}
|
|
if (op == OpUnsetL) {
|
|
assert(ni->inputs.size() == 1);
|
|
DynLocation* inLoc = ni->inputs[0];
|
|
assert(inLoc->location.isLocal());
|
|
RuntimeType newLhsRtt = RuntimeType(KindOfUninit);
|
|
Location locLocation = inLoc->location;
|
|
SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
|
|
locLocation.spaceName(), locLocation.offset,
|
|
newLhsRtt.valueType());
|
|
DynLocation* unsetLoc = t.newDynLocation(locLocation, newLhsRtt);
|
|
assert(unsetLoc->location.isLocal());
|
|
ni->outLocal = unsetLoc;
|
|
continue;
|
|
}
|
|
if (op == OpStaticLocInit || op == OpInitThisLoc) {
|
|
ni->outLocal = t.newDynLocation(Location(Location::Local,
|
|
ni->imm[0].u_OA),
|
|
KindOfInvalid);
|
|
continue;
|
|
}
|
|
if (op == OpSetM || op == OpSetOpM ||
|
|
op == OpVGetM || op == OpBindM ||
|
|
op == OpSetWithRefLM || op == OpSetWithRefRM) {
|
|
switch (ni->immVec.locationCode()) {
|
|
case LL: {
|
|
const int kVecStart = (op == OpSetM ||
|
|
op == OpSetOpM ||
|
|
op == OpBindM ||
|
|
op == OpSetWithRefLM ||
|
|
op == OpSetWithRefRM) ?
|
|
1 : 0; // 0 is rhs for SetM/SetOpM
|
|
DynLocation* inLoc = ni->inputs[kVecStart];
|
|
assert(inLoc->location.isLocal());
|
|
Location locLoc = inLoc->location;
|
|
if (inLoc->rtt.isString() ||
|
|
inLoc->rtt.valueType() == KindOfBoolean) {
|
|
// Strings and bools produce value-dependent results; "" and
|
|
// false upgrade to an array successfully, while other values
|
|
// fail and leave the lhs unmodified.
|
|
DynLocation* baseLoc = t.newDynLocation(locLoc, KindOfInvalid);
|
|
assert(baseLoc->isLocal());
|
|
ni->outLocal = baseLoc;
|
|
} else if (inLoc->rtt.valueType() == KindOfUninit ||
|
|
inLoc->rtt.valueType() == KindOfNull) {
|
|
RuntimeType newLhsRtt = inLoc->rtt.setValueType(
|
|
mcodeMaybePropName(ni->immVecM[0]) ?
|
|
KindOfObject : KindOfArray);
|
|
SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
|
|
locLoc.spaceName(), locLoc.offset,
|
|
newLhsRtt.valueType());
|
|
DynLocation* baseLoc = t.newDynLocation(locLoc, newLhsRtt);
|
|
assert(baseLoc->location.isLocal());
|
|
ni->outLocal = baseLoc;
|
|
}
|
|
// Note (if we start translating pseudo-mains):
|
|
//
|
|
// A SetM in pseudo-main might alias a local whose type we're
|
|
// remembering:
|
|
//
|
|
// $GLOBALS['a'] = 123; // $a :: Int
|
|
//
|
|
// and more deviously:
|
|
//
|
|
// $loc['b'][17] = $GLOBALS; $x = 'b'; $y = 17;
|
|
// $loc[$x][$y]['a'] = 123; // $a :: Int
|
|
break;
|
|
}
|
|
case LNL:
|
|
case LNC:
|
|
varEnvTaint = true;
|
|
break;
|
|
case LGL:
|
|
case LGC:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (op == OpSetOpL) {
|
|
const int kLocIdx = 1;
|
|
DynLocation* inLoc = ni->inputs[kLocIdx];
|
|
assert(inLoc->location.isLocal());
|
|
DynLocation* dl = t.newDynLocation();
|
|
dl->location = inLoc->location;
|
|
dl->rtt = setOpOutputType(ni, ni->inputs);
|
|
if (inLoc->isRef()) {
|
|
dl->rtt = dl->rtt.box();
|
|
}
|
|
SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
|
|
inLoc->location.spaceName(), inLoc->location.offset,
|
|
dl->rtt.valueType());
|
|
assert(dl->location.isLocal());
|
|
ni->outLocal = dl;
|
|
continue;
|
|
}
|
|
if (op >= OpIterInit && op <= OpWIterNextK) {
|
|
assert(op == OpIterInit || op == OpIterInitK ||
|
|
op == OpMIterInit || op == OpMIterInitK ||
|
|
op == OpWIterInit || op == OpWIterInitK ||
|
|
op == OpIterNext || op == OpIterNextK ||
|
|
op == OpMIterNext || op == OpMIterNextK ||
|
|
op == OpWIterNext || op == OpWIterNextK);
|
|
const int kValImmIdx = 2;
|
|
const int kKeyImmIdx = 3;
|
|
DynLocation* outVal = t.newDynLocation();
|
|
int off = ni->imm[kValImmIdx].u_IVA;
|
|
outVal->location = Location(Location::Local, off);
|
|
if (op == OpMIterInit || op == OpMIterInitK ||
|
|
op == OpMIterNext || op == OpMIterNextK) {
|
|
outVal->rtt = RuntimeType(KindOfRef, KindOfInvalid);
|
|
} else {
|
|
outVal->rtt = RuntimeType(KindOfInvalid);
|
|
}
|
|
ni->outLocal = outVal;
|
|
if (op == OpIterInitK || op == OpIterNextK ||
|
|
op == OpWIterInitK || op == OpWIterNextK ||
|
|
op == OpMIterInitK || op == OpMIterNextK) {
|
|
DynLocation* outKey = t.newDynLocation();
|
|
int keyOff = getImm((Op*)ni->pc(), kKeyImmIdx).u_IVA;
|
|
outKey->location = Location(Location::Local, keyOff);
|
|
outKey->rtt = RuntimeType(KindOfInvalid);
|
|
ni->outLocal2 = outKey;
|
|
}
|
|
continue;
|
|
}
|
|
assert(ni->inputs.size() == 2);
|
|
const int kValIdx = 0;
|
|
const int kLocIdx = 1;
|
|
DynLocation* inLoc = ni->inputs[kLocIdx];
|
|
DynLocation* inVal = ni->inputs[kValIdx];
|
|
Location locLocation = inLoc->location;
|
|
// Variant RHS possible only when binding.
|
|
assert(inVal->rtt.isVagueValue() ||
|
|
(op == OpBindL) ==
|
|
(inVal->rtt.outerType() == KindOfRef));
|
|
assert(!inVal->location.isLocal());
|
|
assert(inLoc->location.isLocal());
|
|
RuntimeType newLhsRtt = inVal->rtt.isVagueValue() || op == OpBindL ?
|
|
inVal->rtt :
|
|
inLoc->rtt.setValueType(inVal->rtt.outerType());
|
|
if (inLoc->rtt.outerType() == KindOfRef) {
|
|
assert(newLhsRtt.outerType() == KindOfRef);
|
|
} else {
|
|
assert(op == OpBindL ||
|
|
newLhsRtt.outerType() != KindOfRef);
|
|
}
|
|
SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
|
|
locLocation.spaceName(), locLocation.offset,
|
|
inVal->rtt.valueType());
|
|
DynLocation* outLhsLoc = t.newDynLocation(locLocation, newLhsRtt);
|
|
assert(outLhsLoc->location.isLocal());
|
|
ni->outLocal = outLhsLoc;
|
|
} continue; // already pushed an output for the local
|
|
|
|
case Stack1:
|
|
case Stack2:
|
|
loc = Location(Location::Stack, currentStackOffset++);
|
|
break;
|
|
case StackIns1: {
|
|
// First stack output is where the inserted element will go.
|
|
// The output code for the instruction will affect what we
|
|
// think about this location.
|
|
loc = Location(Location::Stack, currentStackOffset++);
|
|
|
|
// The existing top is just being moved up a notch. This one
|
|
// always functions as if it were OutSameAsInput.
|
|
assert(ni->inputs.size() >= 1);
|
|
ni->outStack2 = t.newDynLocation(
|
|
Location(Location::Stack, currentStackOffset++),
|
|
ni->inputs[0]->rtt
|
|
);
|
|
} break;
|
|
case StackIns2: {
|
|
// Similar to StackIns1.
|
|
loc = Location(Location::Stack, currentStackOffset++);
|
|
|
|
// Move the top two locations up a slot.
|
|
assert(ni->inputs.size() >= 2);
|
|
ni->outStack2 = t.newDynLocation(
|
|
Location(Location::Stack, currentStackOffset++),
|
|
ni->inputs[1]->rtt
|
|
);
|
|
ni->outStack3 = t.newDynLocation(
|
|
Location(Location::Stack, currentStackOffset++),
|
|
ni->inputs[0]->rtt
|
|
);
|
|
} break;
|
|
default:
|
|
not_reached();
|
|
}
|
|
DynLocation* dl = t.newDynLocation();
|
|
dl->location = loc;
|
|
dl->rtt = getDynLocType(t.m_sk, ni, typeInfo);
|
|
SKTRACE(2, ni->source, "recording output t(%d->%d) #(%s, %d)\n",
|
|
dl->rtt.outerType(), dl->rtt.innerType(),
|
|
dl->location.spaceName(), dl->location.offset);
|
|
assert(dl->location.isStack());
|
|
ni->outStack = dl;
|
|
}
|
|
}
|
|
|
|
void
|
|
Translator::requestResetHighLevelTranslator() {
|
|
if (dbgTranslateCoin) {
|
|
dbgTranslateCoin->reset();
|
|
}
|
|
}
|
|
|
|
bool DynLocation::canBeAliased() const {
|
|
return isValue() &&
|
|
((Translator::liveFrameIsPseudoMain() && isLocal()) || isRef());
|
|
}
|
|
|
|
// Test the type of a location without recording it as a read yet.
|
|
RuntimeType TraceletContext::currentType(const Location& l) const {
|
|
DynLocation* dl;
|
|
if (!mapGet(m_currentMap, l, &dl)) {
|
|
assert(!mapContains(m_deletedSet, l));
|
|
assert(!mapContains(m_changeSet, l));
|
|
return tx64->liveType(l, *curUnit());
|
|
}
|
|
return dl->rtt;
|
|
}
|
|
|
|
DynLocation* TraceletContext::recordRead(const InputInfo& ii,
|
|
bool useHHIR,
|
|
DataType staticType) {
|
|
DynLocation* dl;
|
|
const Location& l = ii.loc;
|
|
if (!mapGet(m_currentMap, l, &dl)) {
|
|
// We should never try to read a location that has been deleted
|
|
assert(!mapContains(m_deletedSet, l));
|
|
// If the given location was not in m_currentMap, then it shouldn't
|
|
// be in m_changeSet either
|
|
assert(!mapContains(m_changeSet, l));
|
|
if (ii.dontGuard && !l.isLiteral()) {
|
|
assert(!useHHIR || staticType != KindOfRef);
|
|
dl = m_t->newDynLocation(l, RuntimeType(staticType));
|
|
if (useHHIR && staticType != KindOfInvalid) {
|
|
m_resolvedDeps[l] = dl;
|
|
}
|
|
} else {
|
|
RuntimeType rtt = tx64->liveType(l, *curUnit());
|
|
assert(rtt.isIter() || !rtt.isVagueValue());
|
|
// Allocate a new DynLocation to represent this and store it in the
|
|
// current map.
|
|
dl = m_t->newDynLocation(l, rtt);
|
|
|
|
if (!l.isLiteral()) {
|
|
if (m_varEnvTaint && dl->isValue() && dl->isLocal()) {
|
|
dl->rtt = RuntimeType(KindOfInvalid);
|
|
} else if ((m_aliasTaint && dl->canBeAliased()) ||
|
|
(rtt.isValue() && rtt.isRef() && ii.dontGuardInner)) {
|
|
dl->rtt = rtt.setValueType(KindOfInvalid);
|
|
}
|
|
// Record that we depend on the live type of the specified location
|
|
// as well (and remember what the live type was)
|
|
m_dependencies[l] = dl;
|
|
}
|
|
}
|
|
m_currentMap[l] = dl;
|
|
}
|
|
TRACE(2, "recordRead: %s : %s\n", l.pretty().c_str(),
|
|
dl->rtt.pretty().c_str());
|
|
return dl;
|
|
}
|
|
|
|
void TraceletContext::recordWrite(DynLocation* dl) {
|
|
TRACE(2, "recordWrite: %s : %s\n", dl->location.pretty().c_str(),
|
|
dl->rtt.pretty().c_str());
|
|
m_currentMap[dl->location] = dl;
|
|
m_changeSet.insert(dl->location);
|
|
m_deletedSet.erase(dl->location);
|
|
}
|
|
|
|
void TraceletContext::recordDelete(const Location& l) {
|
|
// We should not be trying to delete the rtt of location that is
|
|
// not in m_currentMap
|
|
TRACE(2, "recordDelete: %s\n", l.pretty().c_str());
|
|
m_currentMap.erase(l);
|
|
m_changeSet.erase(l);
|
|
m_deletedSet.insert(l);
|
|
}
|
|
|
|
void TraceletContext::aliasTaint() {
|
|
m_aliasTaint = true;
|
|
for (ChangeMap::iterator it = m_currentMap.begin();
|
|
it != m_currentMap.end(); ++it) {
|
|
DynLocation* dl = it->second;
|
|
if (dl->canBeAliased()) {
|
|
TRACE(1, "(%s, %" PRId64 ") <- inner type invalidated\n",
|
|
it->first.spaceName(), it->first.offset);
|
|
RuntimeType newRtt = dl->rtt.setValueType(KindOfInvalid);
|
|
it->second = m_t->newDynLocation(dl->location, newRtt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TraceletContext::varEnvTaint() {
|
|
m_varEnvTaint = true;
|
|
for (ChangeMap::iterator it = m_currentMap.begin();
|
|
it != m_currentMap.end(); ++it) {
|
|
DynLocation* dl = it->second;
|
|
if (dl->isValue() && dl->isLocal()) {
|
|
TRACE(1, "(%s, %" PRId64 ") <- type invalidated\n",
|
|
it->first.spaceName(), it->first.offset);
|
|
it->second = m_t->newDynLocation(dl->location,
|
|
RuntimeType(KindOfInvalid));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TraceletContext::recordJmp() {
|
|
m_numJmps++;
|
|
}
|
|
|
|
/*
|
|
* Helpers for recovering context of this instruction.
|
|
*/
|
|
Op NormalizedInstruction::op() const {
|
|
auto op = toOp(*pc());
|
|
assert(isValidOpcode(op));
|
|
return (Op)op;
|
|
}
|
|
|
|
Op NormalizedInstruction::mInstrOp() const {
|
|
Op opcode = op();
|
|
#define MII(instr, a, b, i, v, d) case Op##instr##M: return opcode;
|
|
switch (opcode) {
|
|
MINSTRS
|
|
case OpFPassM:
|
|
return preppedByRef ? OpVGetM : OpCGetM;
|
|
default:
|
|
not_reached();
|
|
}
|
|
#undef MII
|
|
}
|
|
|
|
PC NormalizedInstruction::pc() const {
|
|
return unit()->at(source.offset());
|
|
}
|
|
|
|
const Unit* NormalizedInstruction::unit() const {
|
|
return m_unit;
|
|
}
|
|
|
|
Offset NormalizedInstruction::offset() const {
|
|
return source.offset();
|
|
}
|
|
|
|
std::string NormalizedInstruction::toString() const {
|
|
return instrToString((Op*)pc(), unit());
|
|
}
|
|
|
|
void Translator::postAnalyze(NormalizedInstruction* ni, SrcKey& sk,
|
|
Tracelet& t, TraceletContext& tas) {
|
|
if (ni->op() == OpBareThis &&
|
|
ni->outStack->rtt.isVagueValue()) {
|
|
SrcKey src = sk;
|
|
const Unit* unit = ni->m_unit;
|
|
src.advance(unit);
|
|
Opcode next = *unit->at(src.offset());
|
|
if (next == OpInstanceOfD || next == OpIsNullC) {
|
|
ni->outStack->rtt = RuntimeType(KindOfObject);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
static bool isPop(const NormalizedInstruction* instr) {
|
|
auto opc = instr->op();
|
|
return (opc == OpPopC ||
|
|
opc == OpPopV ||
|
|
opc == OpPopR);
|
|
}
|
|
|
|
NormalizedInstruction::OutputUse
|
|
NormalizedInstruction::getOutputUsage(const DynLocation* output) const {
|
|
for (NormalizedInstruction* succ = next; succ; succ = succ->next) {
|
|
if (succ->noOp) continue;
|
|
for (size_t i = 0; i < succ->inputs.size(); ++i) {
|
|
if (succ->inputs[i] == output) {
|
|
if (succ->inputWasInferred(i)) {
|
|
return OutputUse::Inferred;
|
|
}
|
|
if (Translator::Get()->dontGuardAnyInputs(succ->op())) {
|
|
/* the consumer doesnt care about its inputs
|
|
but we may still have inferred something about
|
|
its outputs that a later instruction may depend on
|
|
*/
|
|
if (!outputDependsOnInput(succ->op()) ||
|
|
!(succ->outStack && !succ->outStack->rtt.isVagueValue() &&
|
|
succ->getOutputUsage(succ->outStack) != OutputUse::Used) ||
|
|
!(succ->outLocal && !succ->outLocal->rtt.isVagueValue() &&
|
|
succ->getOutputUsage(succ->outLocal) != OutputUse::Used)) {
|
|
return OutputUse::DoesntCare;
|
|
}
|
|
}
|
|
return OutputUse::Used;
|
|
}
|
|
}
|
|
}
|
|
return OutputUse::Unused;
|
|
}
|
|
|
|
bool NormalizedInstruction::isOutputUsed(const DynLocation* output) const {
|
|
return (output && !output->rtt.isVagueValue() &&
|
|
getOutputUsage(output) == OutputUse::Used);
|
|
}
|
|
|
|
bool NormalizedInstruction::isAnyOutputUsed() const
|
|
{
|
|
return (isOutputUsed(outStack) ||
|
|
isOutputUsed(outLocal));
|
|
}
|
|
|
|
GuardType::GuardType(DataType outer, DataType inner)
|
|
: outerType(outer), innerType(inner) {
|
|
}
|
|
|
|
GuardType::GuardType(const RuntimeType& rtt) {
|
|
assert(rtt.isValue());
|
|
outerType = rtt.outerType();
|
|
innerType = rtt.innerType();
|
|
}
|
|
|
|
GuardType::GuardType(const GuardType& other) {
|
|
*this = other;
|
|
}
|
|
|
|
|
|
const DataType GuardType::getOuterType() const {
|
|
return outerType;
|
|
}
|
|
|
|
const DataType GuardType::getInnerType() const {
|
|
return innerType;
|
|
}
|
|
|
|
bool GuardType::isSpecific() const {
|
|
return outerType > KindOfInvalid;
|
|
}
|
|
|
|
bool GuardType::isRelaxed() const {
|
|
switch (outerType) {
|
|
case KindOfAny:
|
|
case KindOfUncounted:
|
|
case KindOfUncountedInit:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool GuardType::isGeneric() const {
|
|
return outerType == KindOfAny;
|
|
}
|
|
|
|
bool GuardType::isCounted() const {
|
|
switch (outerType) {
|
|
case KindOfAny:
|
|
case KindOfStaticString:
|
|
case KindOfString:
|
|
case KindOfArray:
|
|
case KindOfObject:
|
|
case KindOfRef:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool GuardType::isMoreRefinedThan(const GuardType& other) const {
|
|
return getCategory() > other.getCategory();
|
|
}
|
|
|
|
DataTypeCategory GuardType::getCategory() const {
|
|
switch (outerType) {
|
|
case KindOfAny: return DataTypeGeneric;
|
|
case KindOfUncounted: return DataTypeCountness;
|
|
case KindOfUncountedInit: return DataTypeCountnessInit;
|
|
default: return DataTypeSpecific;
|
|
}
|
|
}
|
|
|
|
bool GuardType::mayBeUninit() const {
|
|
switch (outerType) {
|
|
case KindOfAny:
|
|
case KindOfUncounted:
|
|
case KindOfUninit:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
GuardType GuardType::getCountness() const {
|
|
// Note that translations need to be able to handle KindOfString and
|
|
// KindOfStaticString interchangeably. This implies that KindOfStaticString
|
|
// needs to be treated as KindOfString, i.e. as possibly counted.
|
|
assert(isSpecific());
|
|
switch (outerType) {
|
|
case KindOfUninit:
|
|
case KindOfNull:
|
|
case KindOfBoolean:
|
|
case KindOfInt64:
|
|
case KindOfDouble: return GuardType(KindOfUncounted);
|
|
default: return *this;
|
|
}
|
|
}
|
|
|
|
GuardType GuardType::getCountnessInit() const {
|
|
assert(isSpecific());
|
|
switch (outerType) {
|
|
case KindOfNull:
|
|
case KindOfBoolean:
|
|
case KindOfInt64:
|
|
case KindOfDouble: return GuardType(KindOfUncountedInit);
|
|
default: return *this;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns true iff loc is consumed by a Pop* instruction in the sequence
|
|
* starting at instr.
|
|
*/
|
|
bool isPopped(DynLocation* loc, NormalizedInstruction* instr) {
|
|
for (; instr ; instr = instr->next) {
|
|
for (size_t i = 0; i < instr->inputs.size(); i++) {
|
|
if (instr->inputs[i] == loc) {
|
|
return isPop(instr);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DataTypeCategory
|
|
Translator::getOperandConstraintCategory(NormalizedInstruction* instr,
|
|
size_t opndIdx) {
|
|
auto opc = instr->op();
|
|
|
|
switch (opc) {
|
|
case OpSetS:
|
|
case OpSetG:
|
|
case OpSetL: {
|
|
if (opndIdx == 0) { // stack value
|
|
// If the output on the stack is simply popped, then we don't
|
|
// even care whether the type is ref-counted or not because
|
|
// the ref-count is transfered to the target location.
|
|
if (!instr->outStack || isPopped(instr->outStack, instr->next)) {
|
|
return DataTypeGeneric;
|
|
}
|
|
return DataTypeCountness;
|
|
}
|
|
if (opc == OpSetL) {
|
|
// old local value is dec-refed
|
|
assert(opndIdx == 1);
|
|
return DataTypeCountness;
|
|
}
|
|
return DataTypeSpecific;
|
|
}
|
|
|
|
case OpCGetL:
|
|
return DataTypeCountnessInit;
|
|
|
|
case OpRetC:
|
|
case OpRetV:
|
|
return DataTypeCountness;
|
|
|
|
case OpFCall:
|
|
// Note: instead of pessimizing calls that may be inlined with
|
|
// DataTypeSpecific, we could apply the operand constraints of
|
|
// the callee in constrainDep.
|
|
return (instr->calleeTrace && !instr->calleeTrace->m_inliningFailed)
|
|
? DataTypeSpecific
|
|
: DataTypeGeneric;
|
|
|
|
case OpFCallArray:
|
|
return DataTypeGeneric;
|
|
|
|
case OpPopC:
|
|
case OpPopV:
|
|
case OpPopR:
|
|
return DataTypeCountness;
|
|
|
|
case OpContSuspend:
|
|
case OpContRetC:
|
|
// The stack input is teleported to the continuation's m_value field
|
|
return opndIdx == 0 ? DataTypeGeneric : DataTypeSpecific;
|
|
|
|
case OpContHandle:
|
|
// This always calls the interpreter
|
|
return DataTypeGeneric;
|
|
|
|
case OpAddElemC:
|
|
// The stack input is teleported to the array
|
|
return opndIdx == 0 ? DataTypeGeneric : DataTypeSpecific;
|
|
|
|
case OpArrayIdx:
|
|
// The default value (w/ opndIdx 0) is simply passed to a helper,
|
|
// which takes care of dec-refing it if needed
|
|
return opndIdx == 0 ? DataTypeGeneric : DataTypeSpecific;
|
|
|
|
default:
|
|
return DataTypeSpecific;
|
|
}
|
|
}
|
|
|
|
GuardType
|
|
Translator::getOperandConstraintType(NormalizedInstruction* instr,
|
|
size_t opndIdx,
|
|
const GuardType& specType) {
|
|
DataTypeCategory dtCategory = getOperandConstraintCategory(instr, opndIdx);
|
|
switch (dtCategory) {
|
|
case DataTypeGeneric: return GuardType(KindOfAny);
|
|
case DataTypeCountness: return specType.getCountness();
|
|
case DataTypeCountnessInit: return specType.getCountnessInit();
|
|
case DataTypeSpecific:
|
|
default: return specType;
|
|
}
|
|
}
|
|
|
|
void Translator::constrainOperandType(GuardType& relxType,
|
|
NormalizedInstruction* instr,
|
|
size_t opndIdx,
|
|
const GuardType& specType) {
|
|
if (relxType.isSpecific()) return; // Can't constrain any further
|
|
|
|
GuardType consType = getOperandConstraintType(instr, opndIdx, specType);
|
|
if (consType.isMoreRefinedThan(relxType)) {
|
|
relxType = consType;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This method looks at every use of loc in the stream of instructions
|
|
* starting at firstInstr and constrains the relxType towards specType
|
|
* according to each use. Note that this method not only looks at
|
|
* direct uses of loc, but it also recursively looks at any other
|
|
* DynLocs whose type depends on loc's type.
|
|
*/
|
|
void Translator::constrainDep(const DynLocation* loc,
|
|
NormalizedInstruction* firstInstr,
|
|
GuardType specType,
|
|
GuardType& relxType) {
|
|
if (relxType.isSpecific()) return; // can't contrain it any further
|
|
|
|
for (NormalizedInstruction* instr = firstInstr; instr; instr = instr->next) {
|
|
if (instr->noOp) continue;
|
|
auto opc = instr->op();
|
|
size_t nInputs = instr->inputs.size();
|
|
for (size_t i = 0; i < nInputs; i++) {
|
|
DynLocation* usedLoc = instr->inputs[i];
|
|
if (usedLoc == loc) {
|
|
constrainOperandType(relxType, instr, i, specType);
|
|
|
|
// If the instruction's input doesn't propagate to its output,
|
|
// then we're done. Otherwise, we need to constrain relxType
|
|
// based on the uses of the output.
|
|
if (!outputDependsOnInput(opc)) continue;
|
|
|
|
bool outputIsStackInput = false;
|
|
const DynLocation* outStack = instr->outStack;
|
|
const DynLocation* outLocal = instr->outLocal;
|
|
|
|
switch (instrInfo[opc].type) {
|
|
case OutSameAsInput:
|
|
outputIsStackInput = true;
|
|
break;
|
|
|
|
case OutCInput:
|
|
outputIsStackInput = true;
|
|
// fall-through
|
|
case OutCInputL:
|
|
if (specType.getOuterType() == KindOfRef &&
|
|
instr->isAnyOutputUsed()) {
|
|
// Value gets unboxed along the way. Pessimize it for now.
|
|
relxType = specType;
|
|
return;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
relxType = specType;
|
|
return;
|
|
}
|
|
|
|
// The instruction input's type propagates to the outputs.
|
|
// So constrain the dependence further based on uses of outputs.
|
|
if ((i == 0 && outputIsStackInput) || // stack input @ [0]
|
|
(i == nInputs - 1 && !outputIsStackInput)) { // local input is last
|
|
if (outStack && !outStack->rtt.isVagueValue()) {
|
|
// For SetL, getOperandConstraintCategory() generates
|
|
// DataTypeGeneric if the stack output is popped. In this
|
|
// case, don't further constrain the stack output,
|
|
// otherwise the Pop* would make it a DataTypeCountness.
|
|
if (opc != OpSetL || !relxType.isGeneric()) {
|
|
constrainDep(outStack, instr->next, specType, relxType);
|
|
}
|
|
}
|
|
if (outLocal && !outLocal->rtt.isVagueValue()) {
|
|
constrainDep(outLocal, instr->next, specType, relxType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Propagates the relaxed type relxType for loc to all its consumers,
|
|
* starting at firstInstr.
|
|
*/
|
|
void Translator::propagateRelaxedType(Tracelet& tclet,
|
|
NormalizedInstruction* firstInstr,
|
|
DynLocation* loc,
|
|
const GuardType& relxType) {
|
|
assert(relxType.isRelaxed());
|
|
|
|
for (NormalizedInstruction* instr = firstInstr; instr; instr = instr->next) {
|
|
if (instr->noOp) continue;
|
|
auto opc = instr->op();
|
|
size_t nInputs = instr->inputs.size();
|
|
for (size_t i = 0; i < nInputs; i++) {
|
|
DynLocation* usedLoc = instr->inputs[i];
|
|
if (usedLoc == loc) {
|
|
auto outKind = instrInfo[opc].type;
|
|
|
|
// stack input propagates to outputs
|
|
bool outputIsStackInput = (i == 0 && // stack input is inputs[0]
|
|
(outKind == OutSameAsInput || outKind == OutCInput));
|
|
bool outputIsLocalInput = (i == nInputs - 1 && // local input is last
|
|
outKind == OutCInputL);
|
|
bool outputIsInput = outputIsStackInput || outputIsLocalInput;
|
|
|
|
if (outputIsInput) {
|
|
// if instr has outStack, update its type and propagate to consumers
|
|
if (instr->outStack) {
|
|
|
|
TRACE(6,
|
|
"propagateRelaxedType: Loc: %s oldType: %s => newType: %s\n",
|
|
instr->outStack->location.pretty().c_str(),
|
|
instr->outStack->rtt.pretty().c_str(),
|
|
RuntimeType(relxType.getOuterType(),
|
|
relxType.getInnerType()).pretty().c_str());
|
|
|
|
instr->outStack->rtt = RuntimeType(relxType.getOuterType(),
|
|
relxType.getInnerType());
|
|
propagateRelaxedType(tclet, instr->next, instr->outStack, relxType);
|
|
}
|
|
// if instr has outLocal, update its type and propagate to consumers
|
|
if (instr->outLocal) {
|
|
|
|
TRACE(6,
|
|
"propagateRelaxedType: Loc: %s oldType: %s => newType: %s\n",
|
|
instr->outLocal->location.pretty().c_str(),
|
|
instr->outLocal->rtt.pretty().c_str(),
|
|
RuntimeType(relxType.getOuterType(),
|
|
relxType.getInnerType()).pretty().c_str());
|
|
|
|
instr->outLocal->rtt = RuntimeType(relxType.getOuterType(),
|
|
relxType.getInnerType());
|
|
propagateRelaxedType(tclet, instr->next, instr->outLocal, relxType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This method looks at all the uses of the tracelet dependencies in the
|
|
* instruction stream and tries to relax the type associated with each location.
|
|
*/
|
|
void Translator::relaxDeps(Tracelet& tclet, TraceletContext& tctxt) {
|
|
DynLocTypeMap locRelxTypeMap;
|
|
|
|
// Initialize type maps. Relaxed types start off very relaxed, and then
|
|
// they may get more specific depending on how the instructions use them.
|
|
DepMap& deps = tctxt.m_dependencies;
|
|
for (auto depIt = deps.begin(); depIt != deps.end(); depIt++) {
|
|
DynLocation* loc = depIt->second;
|
|
const RuntimeType& rtt = depIt->second->rtt;
|
|
if (rtt.isValue() && !rtt.isVagueValue() && !rtt.isClass() &&
|
|
!loc->location.isThis()) {
|
|
GuardType relxType = GuardType(KindOfAny);
|
|
GuardType specType = GuardType(rtt);
|
|
constrainDep(loc, tclet.m_instrStream.first, specType, relxType);
|
|
locRelxTypeMap[loc] = relxType;
|
|
}
|
|
}
|
|
|
|
// For each dependency, if we found a more relaxed type for it, use
|
|
// such type.
|
|
for (auto& kv : locRelxTypeMap) {
|
|
DynLocation* loc = kv.first;
|
|
const GuardType& relxType = kv.second;
|
|
if (relxType.isRelaxed()) {
|
|
TRACE(1, "relaxDeps: Loc: %s oldType: %s => newType: %s\n",
|
|
loc->location.pretty().c_str(),
|
|
deps[loc->location]->rtt.pretty().c_str(),
|
|
RuntimeType(relxType.getOuterType(),
|
|
relxType.getInnerType()).pretty().c_str());
|
|
assert(deps[loc->location] == loc);
|
|
assert(relxType.getOuterType() != KindOfInvalid);
|
|
deps[loc->location]->rtt = RuntimeType(relxType.getOuterType(),
|
|
relxType.getInnerType());
|
|
propagateRelaxedType(tclet, tclet.m_instrStream.first, loc, relxType);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This method looks at all the uses of the tracelet dependencies in the
|
|
* instruction stream and tries to specialize the type associated
|
|
* with each location.
|
|
*/
|
|
void Translator::specializeDeps(Tracelet& tclet, TraceletContext& tctxt) {
|
|
// Process the instruction stream, look for CGetM/SetM/IssetM and if the
|
|
// instructions are for Vector types and consistent with Vector usage
|
|
// specialize the guard
|
|
for (NormalizedInstruction* instr = tclet.m_instrStream.first; instr;
|
|
instr = instr->next) {
|
|
auto op = instr->op();
|
|
if ((op == OpCGetM || op == OpIssetM || op == OpFPassM) &&
|
|
instr->inputs.size() == 2) {
|
|
specializeCollections(instr, 0, tctxt);
|
|
} else if (op == OpSetM && instr->inputs.size() == 3) {
|
|
specializeCollections(instr, 1, tctxt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Translator::specializeCollections(NormalizedInstruction* instr,
|
|
int index,
|
|
TraceletContext& tctxt) {
|
|
if (instr->inputs[index]->isObject()
|
|
|| instr->inputs[index]->isRefToObject()) {
|
|
Location l = instr->inputs[index]->location;
|
|
auto dep = tctxt.m_dependencies.find(l);
|
|
if (dep != tctxt.m_dependencies.end()) {
|
|
RuntimeType specialized = liveType(l, *curUnit(), true);
|
|
const Class* klass = specialized.knownClass();
|
|
if (klass != nullptr && isOptimizableCollectionClass(klass)) {
|
|
tctxt.m_dependencies[l]->rtt = specialized;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool checkTaintFuncs(StringData* name) {
|
|
static const StringData* s_extract =
|
|
StringData::GetStaticString("extract");
|
|
return name->isame(s_extract);
|
|
}
|
|
|
|
/*
|
|
* Check whether the a given FCall should be analyzed for possible
|
|
* inlining or not.
|
|
*/
|
|
static bool shouldAnalyzeCallee(const NormalizedInstruction* fcall,
|
|
const FPIEnt* fpi,
|
|
const Op pushOp) {
|
|
auto const numArgs = fcall->imm[0].u_IVA;
|
|
auto const target = fcall->funcd;
|
|
|
|
if (!RuntimeOption::RepoAuthoritative) return false;
|
|
|
|
if (pushOp != OpFPushFuncD && pushOp != OpFPushObjMethodD
|
|
&& pushOp != OpFPushCtorD && pushOp != OpFPushCtor
|
|
&& pushOp != OpFPushClsMethodD) {
|
|
FTRACE(1, "analyzeCallee: push op ({}) was not supported\n",
|
|
opcodeToName(pushOp));
|
|
return false;
|
|
}
|
|
|
|
if (!target) {
|
|
FTRACE(1, "analyzeCallee: target func not known\n");
|
|
return false;
|
|
}
|
|
if (target->info()) {
|
|
FTRACE(1, "analyzeCallee: target func is a builtin\n");
|
|
return false;
|
|
}
|
|
|
|
constexpr int kMaxSubtraceAnalysisDepth = 2;
|
|
if (tx64->analysisDepth() + 1 >= kMaxSubtraceAnalysisDepth) {
|
|
FTRACE(1, "analyzeCallee: max inlining depth reached\n");
|
|
return false;
|
|
}
|
|
|
|
if (numArgs != target->numParams()) {
|
|
FTRACE(1, "analyzeCallee: param count mismatch {} != {}\n",
|
|
numArgs, target->numParams());
|
|
return false;
|
|
}
|
|
if (target->numLocals() != target->numParams()) {
|
|
FTRACE(1, "analyzeCallee: not inlining functions with more locals "
|
|
"than params\n");
|
|
return false;
|
|
}
|
|
|
|
if (pushOp == OpFPushClsMethodD && target->mayHaveThis()) {
|
|
FTRACE(1, "analyzeCallee: not inlining static calls which may have a "
|
|
"this pointer\n");
|
|
return false;
|
|
}
|
|
|
|
// Find the fpush and ensure it's in this tracelet---refuse to
|
|
// inline if there are any calls in order to prepare arguments.
|
|
for (auto* ni = fcall->prev; ni; ni = ni->prev) {
|
|
if (ni->source.offset() == fpi->m_fpushOff) {
|
|
return true;
|
|
}
|
|
if (isFCallStar(ni->op()) || ni->op() == OpFCallBuiltin) {
|
|
FTRACE(1, "analyzeCallee: fpi region contained other calls\n");
|
|
return false;
|
|
}
|
|
}
|
|
FTRACE(1, "analyzeCallee: push instruction was in a different "
|
|
"tracelet\n");
|
|
return false;
|
|
}
|
|
|
|
extern bool shouldIRInline(const Func* curFunc,
|
|
const Func* func,
|
|
const Tracelet& callee);
|
|
|
|
void Translator::analyzeCallee(TraceletContext& tas,
|
|
Tracelet& parent,
|
|
NormalizedInstruction* fcall) {
|
|
auto const callerFunc = curFunc();
|
|
auto const fpi = callerFunc->findFPI(fcall->source.offset());
|
|
auto const pushOp = curUnit()->getOpcode(fpi->m_fpushOff);
|
|
|
|
if (!shouldAnalyzeCallee(fcall, fpi, pushOp)) return;
|
|
|
|
auto const numArgs = fcall->imm[0].u_IVA;
|
|
auto const target = fcall->funcd;
|
|
|
|
/*
|
|
* Prepare a map for all the known information about the argument
|
|
* types.
|
|
*
|
|
* Also, fill out KindOfUninit for any remaining locals. The point
|
|
* here is that the subtrace can't call liveType for a local or
|
|
* stack location (since our ActRec is fake), so we need them all in
|
|
* the TraceletContext.
|
|
*
|
|
* If any of the argument types are unknown (including inner-types
|
|
* of KindOfRefs), we don't really try to analyze the callee. It
|
|
* might be possible to do this but we'll need to modify the
|
|
* analyzer to support unknown input types before there are any
|
|
* NormalizedInstructions in the Tracelet.
|
|
*/
|
|
TypeMap initialMap;
|
|
LocationSet callerArgLocs;
|
|
for (int i = 0; i < numArgs; ++i) {
|
|
auto callerLoc = Location(Location::Stack, fcall->stackOffset - i - 1);
|
|
auto calleeLoc = Location(Location::Local, numArgs - i - 1);
|
|
auto type = tas.currentType(callerLoc);
|
|
|
|
callerArgLocs.insert(callerLoc);
|
|
|
|
if (type.isVagueValue()) {
|
|
FTRACE(1, "analyzeCallee: {} has unknown type\n", callerLoc.pretty());
|
|
return;
|
|
}
|
|
if (type.isValue() && type.isRef() &&
|
|
type.innerType() == KindOfInvalid) {
|
|
FTRACE(1, "analyzeCallee: {} has unknown inner-refdata type\n",
|
|
callerLoc.pretty());
|
|
return;
|
|
}
|
|
|
|
FTRACE(2, "mapping arg{} locs {} -> {} :: {}\n",
|
|
numArgs - i - 1,
|
|
callerLoc.pretty(),
|
|
calleeLoc.pretty(),
|
|
type.pretty());
|
|
initialMap[calleeLoc] = type;
|
|
}
|
|
for (int i = numArgs; i < target->numLocals(); ++i) {
|
|
initialMap[Location(Location::Local, i)] = RuntimeType(KindOfUninit);
|
|
}
|
|
|
|
/*
|
|
* When reentering analyze to generate a Tracelet for a callee,
|
|
* currently we handle this by creating a fake ActRec on the stack.
|
|
*
|
|
* This is mostly a compromise to deal with existing code during the
|
|
* analysis phase which pretty liberally inspects live VM state.
|
|
*/
|
|
ActRec fakeAR;
|
|
fakeAR.m_savedRbp = reinterpret_cast<uintptr_t>(curFrame());
|
|
fakeAR.m_savedRip = 0xbaabaa; // should never be inspected
|
|
fakeAR.m_func = fcall->funcd;
|
|
fakeAR.m_soff = 0xb00b00; // should never be inspected
|
|
fakeAR.m_numArgsAndCtorFlag = numArgs;
|
|
fakeAR.m_varEnv = nullptr;
|
|
|
|
/*
|
|
* Even when inlining an object method, we can leave the m_this as
|
|
* null. See outThisObjectType().
|
|
*/
|
|
fakeAR.m_this = nullptr;
|
|
|
|
FTRACE(1, "analyzing sub trace =================================\n");
|
|
auto const oldFP = vmfp();
|
|
auto const oldSP = vmsp();
|
|
auto const oldPC = vmpc();
|
|
auto const oldAnalyzeCalleeDepth = m_analysisDepth++;
|
|
vmpc() = nullptr; // should never be used
|
|
vmsp() = nullptr; // should never be used
|
|
vmfp() = reinterpret_cast<Cell*>(&fakeAR);
|
|
auto restoreFrame = [&]{
|
|
vmfp() = oldFP;
|
|
vmsp() = oldSP;
|
|
vmpc() = oldPC;
|
|
m_analysisDepth = oldAnalyzeCalleeDepth;
|
|
};
|
|
SCOPE_EXIT {
|
|
// It's ok to restoreFrame() twice---we have it in this scope
|
|
// handler to ensure it still happens if we exit via an exception.
|
|
restoreFrame();
|
|
FTRACE(1, "finished sub trace ===================================\n");
|
|
};
|
|
|
|
auto subTrace = analyze(SrcKey(target, target->base()), initialMap);
|
|
|
|
/*
|
|
* Verify the target trace actually ended with a return, or we have
|
|
* no business doing anything based on it right now.
|
|
*/
|
|
if (!subTrace->m_instrStream.last ||
|
|
(subTrace->m_instrStream.last->op() != OpRetC &&
|
|
subTrace->m_instrStream.last->op() != OpRetV)) {
|
|
FTRACE(1, "analyzeCallee: callee did not end in a return\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If the IR can't inline this, give up now. Below we're going to
|
|
* start making changes to the traclet that is making the call
|
|
* (potentially increasing the specificity of guards), and we don't
|
|
* want to do that unnecessarily.
|
|
*/
|
|
if (!shouldIRInline(callerFunc, target, *subTrace)) {
|
|
if (UNLIKELY(Stats::enabledAny() && getenv("HHVM_STATS_FAILEDINL"))) {
|
|
subTrace->m_inliningFailed = true;
|
|
// Save the trace for stats purposes but don't waste time doing any
|
|
// further processing since we know we won't inline it.
|
|
fcall->calleeTrace = std::move(subTrace);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Disabled for now:
|
|
*
|
|
* Propagate the return type to our caller. If the return type is
|
|
* not vague, it will hold if we can inline the trace.
|
|
*
|
|
* This isn't really a sensible thing to do if we aren't also going
|
|
* to inline the callee, however, because the return type may only
|
|
* be what it is due to other output predictions (CGetMs or FCall)
|
|
* inside the callee. This means we would need to check the return
|
|
* value in the caller still as if it were a predicted return type.
|
|
*/
|
|
Location retVal(Location::Stack, 0);
|
|
auto it = subTrace->m_changes.find(retVal);
|
|
assert(it != subTrace->m_changes.end());
|
|
FTRACE(1, "subtrace return: {}\n", it->second->pretty());
|
|
if (false) {
|
|
if (!it->second->rtt.isVagueValue() && !it->second->rtt.isRef()) {
|
|
FTRACE(1, "changing callee's return type from {} to {}\n",
|
|
fcall->outStack->rtt.pretty(),
|
|
it->second->pretty());
|
|
|
|
fcall->outputPredicted = true;
|
|
fcall->outputPredictionStatic = false;
|
|
fcall->outStack = parent.newDynLocation(fcall->outStack->location,
|
|
it->second->rtt);
|
|
tas.recordWrite(fcall->outStack);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In order for relaxDeps not to relax guards on some things we may
|
|
* potentially have depended on here, we need to ensure that the
|
|
* call instruction depends on all the inputs we've used.
|
|
*
|
|
* (We could do better by letting relaxDeps look through the
|
|
* callee.)
|
|
*/
|
|
restoreFrame();
|
|
for (auto& loc : callerArgLocs) {
|
|
fcall->inputs.push_back(tas.recordRead(InputInfo(loc), true));
|
|
}
|
|
|
|
FTRACE(1, "analyzeCallee: inline candidate\n");
|
|
fcall->calleeTrace = std::move(subTrace);
|
|
}
|
|
|
|
/*
|
|
* analyze --
|
|
*
|
|
* Given a sequence of bytecodes, return our tracelet IR.
|
|
*
|
|
* The purposes of this analysis is to determine:
|
|
*
|
|
* 1. Pre-conditions: What locations get read before they get written to:
|
|
* we will need typechecks for these and we will want to load them into
|
|
* registers. (m_dependencies)
|
|
*
|
|
* 2. Post-conditions: the locations that have been written to and are
|
|
* still live at the end of the tracelet. We need to allocate registers
|
|
* of these and we need to spill them at the end of the tracelet.
|
|
* (m_changes)
|
|
*
|
|
* 3. Determine the runtime types for each instruction's input locations
|
|
* and output locations.
|
|
*
|
|
* The main analysis works by doing a single pass over the instructions. It
|
|
* effectively simulates the execution of each instruction, updating its
|
|
* knowledge about types as it goes.
|
|
*
|
|
* The TraceletContext class is used to keep track of the current state of
|
|
* the world. Initially it is empty, and when the inputs for the first
|
|
* instruction are analyzed we call recordRead(). The recordRead() function
|
|
* in turn inspects the live types of the inputs and adds them to the type
|
|
* map. This serves two purposes: (1) it figures out what typechecks this
|
|
* tracelet needs; and (2) it guarantees that the code we generate will
|
|
* satisfy the live types that are about to be passed in.
|
|
*
|
|
* Over time the TraceletContext's type map will change. However, we need to
|
|
* record what the types _were_ right before and right after a given
|
|
* instruction executes. This is where the NormalizedInstruction class comes
|
|
* in. We store the RuntimeTypes from the TraceletContext right before an
|
|
* instruction executes into the NormalizedInstruction's 'inputs' field, and
|
|
* we store the RuntimeTypes from the TraceletContext right after the
|
|
* instruction executes into the various output fields.
|
|
*/
|
|
std::unique_ptr<Tracelet> Translator::analyze(SrcKey sk,
|
|
const TypeMap& initialTypes) {
|
|
std::unique_ptr<Tracelet> retval(new Tracelet());
|
|
auto& t = *retval;
|
|
t.m_sk = sk;
|
|
t.m_func = curFunc();
|
|
|
|
DEBUG_ONLY const char* file = curUnit()->filepath()->data();
|
|
DEBUG_ONLY const int lineNum = curUnit()->getLineNumber(t.m_sk.offset());
|
|
DEBUG_ONLY const char* funcName = curFunc()->fullName()->data();
|
|
|
|
TRACE(1, "Translator::analyze %s:%d %s\n", file, lineNum, funcName);
|
|
TraceletContext tas(&t, initialTypes);
|
|
int stackFrameOffset = 0;
|
|
int oldStackFrameOffset = 0;
|
|
|
|
// numOpcodes counts the original number of opcodes in a tracelet
|
|
// before the translator does any optimization
|
|
t.m_numOpcodes = 0;
|
|
Unit::MetaHandle metaHand;
|
|
|
|
const Unit *unit = curUnit();
|
|
for (;; sk.advance(unit)) {
|
|
head:
|
|
NormalizedInstruction* ni = t.newNormalizedInstruction();
|
|
ni->source = sk;
|
|
ni->stackOffset = stackFrameOffset;
|
|
ni->funcd = (t.m_arState.getCurrentState() == ActRecState::State::KNOWN) ?
|
|
t.m_arState.getCurrentFunc() : nullptr;
|
|
ni->m_unit = unit;
|
|
ni->breaksTracelet = false;
|
|
ni->changesPC = opcodeChangesPC(ni->op());
|
|
ni->fuseBranch = false;
|
|
ni->outputPredicted = false;
|
|
ni->outputPredictionStatic = false;
|
|
|
|
assert(!t.m_analysisFailed);
|
|
oldStackFrameOffset = stackFrameOffset;
|
|
populateImmediates(*ni);
|
|
|
|
SKTRACE(1, sk, "stack args: virtual sfo now %d\n", stackFrameOffset);
|
|
|
|
// Translation could fail entirely (because of an unknown opcode), or
|
|
// encounter an input that cannot be computed.
|
|
try {
|
|
preInputApplyMetaData(metaHand, ni);
|
|
InputInfos inputInfos;
|
|
getInputs(t.m_sk, ni, stackFrameOffset, inputInfos, [&](int i) {
|
|
return Type::fromRuntimeType(
|
|
tas.currentType(Location(Location::Local, i)));
|
|
});
|
|
bool noOp = applyInputMetaData(metaHand, ni, tas, inputInfos);
|
|
if (noOp) {
|
|
t.m_instrStream.append(ni);
|
|
++t.m_numOpcodes;
|
|
stackFrameOffset = oldStackFrameOffset;
|
|
continue;
|
|
}
|
|
if (inputInfos.needsRefCheck) {
|
|
// Drive the arState machine; if it is going to throw an input
|
|
// exception, do so here.
|
|
int argNum = ni->imm[0].u_IVA;
|
|
// instrSpToArDelta() returns the delta relative to the sp at the
|
|
// beginning of the instruction, but getReffiness() wants the delta
|
|
// relative to the sp at the beginning of the tracelet, so we adjust
|
|
// by subtracting ni->stackOff
|
|
int entryArDelta = instrSpToArDelta((Op*)ni->pc()) - ni->stackOffset;
|
|
ni->preppedByRef = t.m_arState.getReffiness(argNum, entryArDelta,
|
|
&t.m_refDeps);
|
|
SKTRACE(1, sk, "passing arg%d by %s\n", argNum,
|
|
ni->preppedByRef ? "reference" : "value");
|
|
}
|
|
|
|
for (unsigned int i = 0; i < inputInfos.size(); i++) {
|
|
SKTRACE(2, sk, "typing input %d\n", i);
|
|
const InputInfo& ii = inputInfos[i];
|
|
DynLocation* dl = tas.recordRead(ii, true);
|
|
const RuntimeType& rtt = dl->rtt;
|
|
// Some instructions are able to handle an input with an unknown type
|
|
if (!ii.dontBreak && !ii.dontGuard) {
|
|
if (rtt.isVagueValue()) {
|
|
// Consumed a "poisoned" output: e.g., result of an array
|
|
// deref.
|
|
throwUnknownInput();
|
|
}
|
|
if (!ni->ignoreInnerType && !ii.dontGuardInner) {
|
|
if (rtt.isValue() && rtt.isRef() &&
|
|
rtt.innerType() == KindOfInvalid) {
|
|
throwUnknownInput();
|
|
}
|
|
}
|
|
}
|
|
ni->inputs.push_back(dl);
|
|
}
|
|
} catch (TranslationFailedExc& tfe) {
|
|
SKTRACE(1, sk, "Translator fail: %s:%d\n", tfe.m_file, tfe.m_line);
|
|
if (!t.m_numOpcodes) {
|
|
t.m_analysisFailed = true;
|
|
t.m_instrStream.append(ni);
|
|
++t.m_numOpcodes;
|
|
}
|
|
goto breakBB;
|
|
} catch (UnknownInputExc& uie) {
|
|
// Subtle: if this instruction consumes an unknown runtime type,
|
|
// break the BB on the *previous* instruction. We know that a
|
|
// previous instruction exists, because the KindOfInvalid must
|
|
// have come from somewhere.
|
|
always_assert(t.m_instrStream.last);
|
|
SKTRACE(2, sk, "Consumed unknown input (%s:%d); breaking BB at "
|
|
"predecessor\n", uie.m_file, uie.m_line);
|
|
goto breakBB;
|
|
}
|
|
|
|
SKTRACE(2, sk, "stack args: virtual sfo now %d\n", stackFrameOffset);
|
|
|
|
bool doVarEnvTaint; // initialized by reference.
|
|
|
|
try {
|
|
getOutputs(t, ni, stackFrameOffset, doVarEnvTaint);
|
|
} catch (TranslationFailedExc& tfe) {
|
|
SKTRACE(1, sk, "Translator getOutputs fail: %s:%d\n",
|
|
tfe.m_file, tfe.m_line);
|
|
if (!t.m_numOpcodes) {
|
|
t.m_analysisFailed = true;
|
|
t.m_instrStream.append(ni);
|
|
++t.m_numOpcodes;
|
|
}
|
|
goto breakBB;
|
|
}
|
|
|
|
if (isFCallStar(ni->op())) {
|
|
if (!doVarEnvTaint) {
|
|
const FPIEnt *fpi = curFunc()->findFPI(ni->source.offset());
|
|
assert(fpi);
|
|
Offset fpushOff = fpi->m_fpushOff;
|
|
PC fpushPc = curUnit()->at(fpushOff);
|
|
if (*fpushPc == OpFPushFunc) {
|
|
doVarEnvTaint = true;
|
|
} else if (*fpushPc == OpFPushFuncD) {
|
|
StringData *funcName =
|
|
curUnit()->lookupLitstrId(getImm((Op*)fpushPc, 1).u_SA);
|
|
doVarEnvTaint = checkTaintFuncs(funcName);
|
|
} else if (*fpushPc == OpFPushFuncU) {
|
|
StringData *fallbackName =
|
|
curUnit()->lookupLitstrId(getImm((Op*)fpushPc, 2).u_SA);
|
|
doVarEnvTaint = checkTaintFuncs(fallbackName);
|
|
}
|
|
}
|
|
t.m_arState.pop();
|
|
}
|
|
if (ni->op() == OpFCallBuiltin && !doVarEnvTaint) {
|
|
StringData* funcName = curUnit()->lookupLitstrId(ni->imm[2].u_SA);
|
|
doVarEnvTaint = checkTaintFuncs(funcName);
|
|
}
|
|
if (doVarEnvTaint) {
|
|
tas.varEnvTaint();
|
|
}
|
|
|
|
DynLocation* outputs[] = { ni->outStack,
|
|
ni->outLocal, ni->outLocal2,
|
|
ni->outStack2, ni->outStack3 };
|
|
for (size_t i = 0; i < sizeof(outputs) / sizeof(*outputs); ++i) {
|
|
if (outputs[i]) {
|
|
DynLocation* o = outputs[i];
|
|
SKTRACE(2, sk, "inserting output t(%d->%d) #(%s, %d)\n",
|
|
o->rtt.outerType(), o->rtt.innerType(),
|
|
o->location.spaceName(), o->location.offset);
|
|
tas.recordWrite(o);
|
|
}
|
|
}
|
|
|
|
SKTRACE(1, sk, "stack args: virtual sfo now %d\n", stackFrameOffset);
|
|
|
|
// This assert failing means that your instruction has an
|
|
// inconsistent row in the InstrInfo table; the stackDelta doesn't
|
|
// agree with the inputs and outputs.
|
|
assert(getStackDelta(*ni) == (stackFrameOffset - oldStackFrameOffset));
|
|
// If this instruction decreased the depth of the stack, mark the
|
|
// appropriate stack locations as "dead". But we need to leave
|
|
// them in the TraceletContext until after analyzeCallee (if this
|
|
// is an FCall).
|
|
if (stackFrameOffset < oldStackFrameOffset) {
|
|
for (int i = stackFrameOffset; i < oldStackFrameOffset; ++i) {
|
|
ni->deadLocs.push_back(Location(Location::Stack, i));
|
|
}
|
|
}
|
|
|
|
if (ni->outputPredicted) {
|
|
assert(ni->outStack);
|
|
ni->outPred = Type::fromDynLocation(ni->outStack);
|
|
}
|
|
|
|
t.m_stackChange += getStackDelta(*ni);
|
|
|
|
t.m_instrStream.append(ni);
|
|
++t.m_numOpcodes;
|
|
|
|
/*
|
|
* The annotation step attempts to track Func*'s associated with
|
|
* given FCalls when the FPush is in a different tracelet.
|
|
*
|
|
* When we're analyzing a callee, we can't do this because we may
|
|
* have class information in some of our RuntimeTypes that is only
|
|
* true because of who the caller was. (Normally it is only there
|
|
* if it came from static analysis.)
|
|
*/
|
|
if (analysisDepth() == 0) {
|
|
annotate(ni);
|
|
}
|
|
|
|
if (ni->op() == OpFCall) {
|
|
analyzeCallee(tas, t, ni);
|
|
}
|
|
|
|
for (auto& l : ni->deadLocs) {
|
|
tas.recordDelete(l);
|
|
}
|
|
|
|
// Check if we need to break the tracelet.
|
|
//
|
|
// If we've gotten this far, it mostly boils down to control-flow
|
|
// instructions. However, we'll trace through a few unconditional jmps.
|
|
if (ni->op() == OpJmp &&
|
|
ni->imm[0].u_IA > 0 &&
|
|
tas.m_numJmps < MaxJmpsTracedThrough) {
|
|
// Continue tracing through jumps. To prevent pathologies, only trace
|
|
// through a finite number of forward jumps.
|
|
SKTRACE(1, sk, "greedily continuing through %dth jmp + %d\n",
|
|
tas.m_numJmps, ni->imm[0].u_IA);
|
|
tas.recordJmp();
|
|
sk = SrcKey(curFunc(), sk.offset() + ni->imm[0].u_IA);
|
|
goto head; // don't advance sk
|
|
} else if (opcodeBreaksBB(ni->op()) ||
|
|
(dontGuardAnyInputs(ni->op()) && opcodeChangesPC(ni->op()))) {
|
|
SKTRACE(1, sk, "BB broken\n");
|
|
sk.advance(unit);
|
|
goto breakBB;
|
|
}
|
|
postAnalyze(ni, sk, t, tas);
|
|
}
|
|
breakBB:
|
|
NormalizedInstruction* ni = t.m_instrStream.last;
|
|
while (ni) {
|
|
switch (ni->op()) {
|
|
// We dont want to end a tracelet with a literal;
|
|
// it will cause the literal to be pushed on the
|
|
// stack, and the next tracelet will have to guard
|
|
// on the type.
|
|
case OpNull:
|
|
case OpNullUninit:
|
|
case OpTrue:
|
|
case OpFalse:
|
|
case OpInt:
|
|
case OpDouble:
|
|
case OpString:
|
|
case OpArray:
|
|
// Similarly, This, Self and Parent will lose
|
|
// type information thats only useful in the
|
|
// following tracelet.
|
|
case OpThis:
|
|
case OpSelf:
|
|
case OpParent:
|
|
ni = ni->prev;
|
|
continue;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if (ni) {
|
|
while (ni != t.m_instrStream.last) {
|
|
t.m_stackChange -= getStackDelta(*t.m_instrStream.last);
|
|
sk = t.m_instrStream.last->source;
|
|
t.m_instrStream.remove(t.m_instrStream.last);
|
|
--t.m_numOpcodes;
|
|
}
|
|
}
|
|
|
|
relaxDeps(t, tas);
|
|
specializeDeps(t, tas);
|
|
|
|
// Mark the last instruction appropriately
|
|
assert(t.m_instrStream.last);
|
|
t.m_instrStream.last->breaksTracelet = true;
|
|
// Populate t.m_changes, t.intermediates, t.m_dependencies
|
|
t.m_dependencies = tas.m_dependencies;
|
|
t.m_resolvedDeps = tas.m_resolvedDeps;
|
|
t.m_changes.clear();
|
|
LocationSet::iterator it = tas.m_changeSet.begin();
|
|
for (; it != tas.m_changeSet.end(); ++it) {
|
|
t.m_changes[*it] = tas.m_currentMap[*it];
|
|
}
|
|
|
|
TRACE(1, "Tracelet done: stack delta %d\n", t.m_stackChange);
|
|
return retval;
|
|
}
|
|
|
|
Translator::Translator()
|
|
: m_curTrace(nullptr)
|
|
, m_curNI(nullptr)
|
|
, m_resumeHelper(nullptr)
|
|
, m_createdTime(Timer::GetCurrentTimeMicros())
|
|
, m_analysisDepth(0)
|
|
{
|
|
initInstrInfo();
|
|
}
|
|
|
|
Translator::~Translator() {
|
|
}
|
|
|
|
Translator*
|
|
Translator::Get() {
|
|
return TranslatorX64::Get();
|
|
}
|
|
|
|
bool
|
|
Translator::isSrcKeyInBL(const Unit* unit, const SrcKey& sk) {
|
|
Lock l(m_dbgBlacklistLock);
|
|
if (m_dbgBLSrcKey.find(sk) != m_dbgBLSrcKey.end()) {
|
|
return true;
|
|
}
|
|
for (PC pc = unit->at(sk.offset()); !opcodeBreaksBB(toOp(*pc));
|
|
pc += instrLen((Op*)pc)) {
|
|
if (m_dbgBLPC.checkPC(pc)) {
|
|
m_dbgBLSrcKey.insert(sk);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Translator::clearDbgBL() {
|
|
Lock l(m_dbgBlacklistLock);
|
|
m_dbgBLSrcKey.clear();
|
|
m_dbgBLPC.clear();
|
|
}
|
|
|
|
bool
|
|
Translator::addDbgBLPC(PC pc) {
|
|
Lock l(m_dbgBlacklistLock);
|
|
if (m_dbgBLPC.checkPC(pc)) {
|
|
// already there
|
|
return false;
|
|
}
|
|
m_dbgBLPC.addPC(pc);
|
|
return true;
|
|
}
|
|
|
|
// Return the SrcKey for the operation that should follow the supplied
|
|
// NormalizedInstruction.
|
|
SrcKey Translator::nextSrcKey(const NormalizedInstruction& i) {
|
|
return i.source.advanced(curUnit());
|
|
}
|
|
|
|
void Translator::populateImmediates(NormalizedInstruction& inst) {
|
|
for (int i = 0; i < numImmediates(inst.op()); i++) {
|
|
inst.imm[i] = getImm((Op*)inst.pc(), i);
|
|
}
|
|
if (hasImmVector(toOp(*inst.pc()))) {
|
|
inst.immVec = getImmVector((Op*)inst.pc());
|
|
}
|
|
if (inst.op() == OpFCallArray) {
|
|
inst.imm[0].u_IVA = 1;
|
|
}
|
|
}
|
|
|
|
const char* Translator::translateResultName(TranslateResult r) {
|
|
static const char* const names[] = {
|
|
"Failure",
|
|
"Retry",
|
|
"Success",
|
|
};
|
|
return names[r];
|
|
}
|
|
|
|
/*
|
|
* Similar to applyInputMetaData, but designed to be used during ir
|
|
* generation. Reads and writes types of values using m_hhbcTrans. This will
|
|
* eventually replace applyInputMetaData.
|
|
*/
|
|
void Translator::readMetaData(Unit::MetaHandle& handle,
|
|
NormalizedInstruction& inst) {
|
|
if (!handle.findMeta(inst.unit(), inst.offset())) return;
|
|
|
|
Unit::MetaInfo info;
|
|
if (!handle.nextArg(info)) return;
|
|
if (info.m_kind == Unit::MetaInfo::Kind::NopOut) {
|
|
inst.noOp = true;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* We need to adjust the indexes in MetaInfo::m_arg if this instruction takes
|
|
* other stack arguments than those related to the MVector. (For example,
|
|
* the rhs of an assignment.)
|
|
*/
|
|
auto const& iInfo = instrInfo[inst.op()];
|
|
if (iInfo.in & AllLocals) {
|
|
/*
|
|
* RetC/RetV dont care about their stack input, but it may have been
|
|
* annotated. Skip it (because RetC/RetV pretend they dont have a stack
|
|
* input).
|
|
*/
|
|
return;
|
|
}
|
|
if (iInfo.in == FuncdRef) {
|
|
/*
|
|
* FPassC* pretend to have no inputs
|
|
*/
|
|
return;
|
|
}
|
|
const int base = !(iInfo.in & MVector) ? 0 :
|
|
!(iInfo.in & Stack1) ? 0 :
|
|
!(iInfo.in & Stack2) ? 1 :
|
|
!(iInfo.in & Stack3) ? 2 : 3;
|
|
|
|
do {
|
|
SKTRACE(3, inst.source, "considering MetaInfo of kind %d\n", info.m_kind);
|
|
|
|
int arg = info.m_arg & Unit::MetaInfo::VectorArg ?
|
|
base + (info.m_arg & ~Unit::MetaInfo::VectorArg) : info.m_arg;
|
|
auto updateType = [&]{
|
|
auto& input = *inst.inputs[arg];
|
|
input.rtt = m_hhbcTrans->rttFromLocation(input.location);
|
|
};
|
|
|
|
switch (info.m_kind) {
|
|
case Unit::MetaInfo::Kind::NoSurprise:
|
|
inst.noSurprise = true;
|
|
break;
|
|
case Unit::MetaInfo::Kind::GuardedCls:
|
|
inst.guardedCls = true;
|
|
break;
|
|
case Unit::MetaInfo::Kind::ArrayCapacity:
|
|
inst.imm[0].u_IVA = info.m_data;
|
|
break;
|
|
case Unit::MetaInfo::Kind::DataTypePredicted: {
|
|
auto const& loc = inst.inputs[arg]->location;
|
|
auto const t = Type::fromDataType(DataType(info.m_data));
|
|
auto const offset = inst.source.offset();
|
|
|
|
// These 'predictions' mean the type is InitNull or the predicted type,
|
|
// so we assert InitNull | t, then guard t. This allows certain
|
|
// optimizations in the IR.
|
|
m_hhbcTrans->assertTypeLocation(loc, Type::InitNull | t);
|
|
m_hhbcTrans->checkTypeLocation(loc, t, offset);
|
|
updateType();
|
|
break;
|
|
}
|
|
case Unit::MetaInfo::Kind::DataTypeInferred: {
|
|
m_hhbcTrans->assertTypeLocation(
|
|
inst.inputs[arg]->location,
|
|
Type::fromDataType(DataType(info.m_data)));
|
|
updateType();
|
|
break;
|
|
}
|
|
case Unit::MetaInfo::Kind::String: {
|
|
m_hhbcTrans->assertString(inst.inputs[arg]->location,
|
|
inst.unit()->lookupLitstrId(info.m_data));
|
|
updateType();
|
|
break;
|
|
}
|
|
case Unit::MetaInfo::Kind::Class: {
|
|
RuntimeType& rtt = inst.inputs[arg]->rtt;
|
|
if (rtt.valueType() != KindOfObject) {
|
|
continue;
|
|
}
|
|
|
|
const StringData* metaName = inst.unit()->lookupLitstrId(info.m_data);
|
|
const StringData* rttName =
|
|
rtt.valueClass() ? rtt.valueClass()->name() : nullptr;
|
|
// The two classes might not be exactly the same, which is ok
|
|
// as long as metaCls is more derived than rttCls.
|
|
Class* metaCls = Unit::lookupUniqueClass(metaName);
|
|
Class* rttCls = rttName ? Unit::lookupUniqueClass(rttName) : nullptr;
|
|
if (metaCls && rttCls && metaCls != rttCls &&
|
|
!metaCls->classof(rttCls)) {
|
|
// Runtime type is more derived
|
|
metaCls = 0;
|
|
}
|
|
if (metaCls && metaCls != rttCls) {
|
|
SKTRACE(1, inst.source, "replacing input %d with a MetaInfo-supplied "
|
|
"class of %s; old type = %s\n",
|
|
arg, metaName->data(), rtt.pretty().c_str());
|
|
if (rtt.isRef()) {
|
|
rtt = RuntimeType(KindOfRef, KindOfObject, metaCls);
|
|
} else {
|
|
rtt = RuntimeType(KindOfObject, KindOfInvalid, metaCls);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Unit::MetaInfo::Kind::MVecPropClass: {
|
|
const StringData* metaName = inst.unit()->lookupLitstrId(info.m_data);
|
|
Class* metaCls = Unit::lookupUniqueClass(metaName);
|
|
if (metaCls) {
|
|
inst.immVecClasses[arg] = metaCls;
|
|
}
|
|
break;
|
|
}
|
|
case Unit::MetaInfo::Kind::NopOut:
|
|
// NopOut should always be the first and only annotation
|
|
// and was handled above.
|
|
not_reached();
|
|
|
|
case Unit::MetaInfo::Kind::GuardedThis:
|
|
case Unit::MetaInfo::Kind::NonRefCounted:
|
|
// fallthrough; these are handled in preInputApplyMetaData.
|
|
case Unit::MetaInfo::Kind::None:
|
|
break;
|
|
}
|
|
} while (handle.nextArg(info));
|
|
}
|
|
|
|
bool Translator::instrMustInterp(const NormalizedInstruction& inst) {
|
|
if (RuntimeOption::EvalJitAlwaysInterpOne) return true;
|
|
|
|
switch (inst.op()) {
|
|
// Generate a case for each instruction we support at least partially.
|
|
# define CASE(name) case Op##name:
|
|
INSTRS
|
|
# undef CASE
|
|
# define NOTHING(...) // PSEUDOINSTR_DISPATCH has the cases in it
|
|
PSEUDOINSTR_DISPATCH(NOTHING)
|
|
# undef NOTHING
|
|
return false;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static Location toLocation(const RegionDesc::Location& loc) {
|
|
typedef RegionDesc::Location::Tag T;
|
|
switch (loc.tag()) {
|
|
case T::Stack:
|
|
return Location(Location::Stack, loc.stackOffset());
|
|
|
|
case T::Local:
|
|
return Location(Location::Local, loc.localId());
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
Translator::TranslateResult
|
|
Translator::translateRegion(const RegionDesc& region,
|
|
RegionBlacklist& toInterp) {
|
|
typedef JIT::RegionDesc::Block Block;
|
|
FTRACE(1, "translateRegion starting with:\n{}\n", show(region));
|
|
assert(!region.blocks.empty());
|
|
const SrcKey startSk = region.blocks.front()->start();
|
|
|
|
for (auto const& block : region.blocks) {
|
|
SrcKey sk = block->start();
|
|
const Func* topFunc = nullptr;
|
|
auto typePreds = makeMapWalker(block->typePreds());
|
|
auto byRefs = makeMapWalker(block->paramByRefs());
|
|
auto refPreds = makeMapWalker(block->reffinessPreds());
|
|
auto knownFuncs = makeMapWalker(block->knownFuncs());
|
|
|
|
for (unsigned i = 0; i < block->length(); ++i, sk.advance(block->unit())) {
|
|
// Emit prediction guards. If this is the first instruction in the
|
|
// region the guards will go to a retranslate request. Otherwise, they'll
|
|
// go to a side exit.
|
|
while (typePreds.hasNext(sk)) {
|
|
auto const& pred = typePreds.next();
|
|
if (block == region.blocks.front() && i == 0) {
|
|
m_hhbcTrans->guardTypeLocation(toLocation(pred.location), pred.type);
|
|
} else {
|
|
m_hhbcTrans->checkTypeLocation(toLocation(pred.location), pred.type,
|
|
sk.offset());
|
|
}
|
|
}
|
|
|
|
// Emit reffiness guards. For now, we only support reffiness guards at
|
|
// the beginning of the region.
|
|
while (refPreds.hasNext(sk)) {
|
|
assert(sk == startSk);
|
|
auto const& pred = refPreds.next();
|
|
m_hhbcTrans->guardRefs(pred.arSpOffset, pred.mask, pred.vals);
|
|
}
|
|
|
|
// Update the current funcd, if we have a new one.
|
|
if (knownFuncs.hasNext(sk)) {
|
|
topFunc = knownFuncs.next();
|
|
}
|
|
|
|
// Create and initialize the instruction.
|
|
NormalizedInstruction inst;
|
|
inst.source = sk;
|
|
inst.m_unit = block->unit();
|
|
inst.breaksTracelet =
|
|
i == block->length() - 1 && block == region.blocks.back();
|
|
inst.changesPC = opcodeChangesPC(inst.op());
|
|
inst.funcd = topFunc;
|
|
populateImmediates(inst);
|
|
|
|
// We can get a more precise output type for interpOne if we know all of
|
|
// its inputs, so we still populate the rest of the instruction even if
|
|
// this is true.
|
|
inst.interp = toInterp.count(sk);
|
|
|
|
// Apply the first round of metadata from the repo and get a list of
|
|
// input locations.
|
|
InputInfos inputInfos;
|
|
Unit::MetaHandle metaHand;
|
|
preInputApplyMetaData(metaHand, &inst);
|
|
|
|
// TranslatorX64 expected top of stack to be index -1, with indexes
|
|
// growing down from there. hhir defines top of stack to be index 1, with
|
|
// indexes growing up from there. To compensate we start with a stack
|
|
// offset of 1 and negate the index of any stack input after the call to
|
|
// getInputs.
|
|
int stackOff = 1;
|
|
getInputs(startSk, &inst, stackOff, inputInfos, [&](int i) {
|
|
return m_hhbcTrans->traceBuilder()->getLocalType(i);
|
|
});
|
|
for (auto& info : inputInfos) {
|
|
if (info.loc.isStack()) info.loc.offset = -info.loc.offset;
|
|
}
|
|
|
|
// Populate the NormalizedInstruction's input vector, using types from
|
|
// HhbcTranslator.
|
|
std::vector<DynLocation> dynLocs;
|
|
dynLocs.reserve(inputInfos.size());
|
|
auto newDynLoc = [&](const InputInfo& ii) {
|
|
dynLocs.emplace_back(ii.loc, m_hhbcTrans->rttFromLocation(ii.loc));
|
|
FTRACE(2, "rttFromLocation: {} -> {}\n",
|
|
ii.loc.pretty(), dynLocs.back().rtt.pretty());
|
|
return &dynLocs.back();
|
|
};
|
|
FTRACE(2, "populating inputs for {}\n", inst.toString());
|
|
for (auto const& ii : inputInfos) {
|
|
inst.inputs.push_back(newDynLoc(ii));
|
|
}
|
|
|
|
// Apply the remaining metadata. This may change the types of some of
|
|
// inst's inputs.
|
|
readMetaData(metaHand, inst);
|
|
if (!inst.noOp && inputInfos.needsRefCheck) {
|
|
assert(byRefs.hasNext(sk));
|
|
auto byRef = byRefs.next();
|
|
inst.preppedByRef = byRef == RegionDesc::ParamByRef::Yes;
|
|
}
|
|
|
|
// Check for a type prediction. Put it in the NormalizedInstruction so
|
|
// the emit* method can use it if needed.
|
|
auto const& iInfo = instrInfo[inst.op()];
|
|
auto doPrediction = iInfo.type == OutPred && !inst.breaksTracelet;
|
|
if (doPrediction) {
|
|
// All OutPred ops have a single stack output for now.
|
|
assert(iInfo.out == Stack1);
|
|
auto dt = predictOutputs(startSk, &inst);
|
|
if (dt != KindOfInvalid) {
|
|
inst.outPred = Type::fromDataType(dt);
|
|
} else {
|
|
doPrediction = false;
|
|
}
|
|
}
|
|
|
|
// Emit IR for the body of the instruction.
|
|
Util::Nuller<NormalizedInstruction> niNuller(&m_curNI);
|
|
m_curNI = &inst;
|
|
try {
|
|
translateInstr(inst);
|
|
} catch (const JIT::FailedIRGen& exn) {
|
|
FTRACE(1, "ir generation for {} failed with {}\n",
|
|
inst.toString(), exn.what());
|
|
always_assert(!toInterp.count(sk));
|
|
toInterp.insert(sk);
|
|
return Retry;
|
|
}
|
|
|
|
// Check the prediction. If the predicted type is less specific than what
|
|
// is currently on the eval stack, checkTypeLocation won't emit any code.
|
|
if (doPrediction) {
|
|
m_hhbcTrans->checkTypeLocation(Location(Location::Stack, 0),
|
|
inst.outPred,
|
|
sk.advanced(block->unit()).offset());
|
|
}
|
|
}
|
|
|
|
assert(!typePreds.hasNext());
|
|
assert(!byRefs.hasNext());
|
|
assert(!refPreds.hasNext());
|
|
assert(!knownFuncs.hasNext());
|
|
}
|
|
|
|
traceEnd();
|
|
try {
|
|
traceCodeGen();
|
|
} catch (const JIT::FailedCodeGen& exn) {
|
|
FTRACE(1, "code generation failed with {}\n", exn.what());
|
|
SrcKey sk{exn.vmFunc, exn.bcOff};
|
|
always_assert(!toInterp.count(sk));
|
|
toInterp.insert(sk);
|
|
return Retry;
|
|
}
|
|
|
|
return Success;
|
|
}
|
|
|
|
uint64_t* Translator::getTransCounterAddr() {
|
|
if (!isTransDBEnabled()) return nullptr;
|
|
|
|
TransID id = m_translations.size();
|
|
|
|
// allocate a new chunk of counters if necessary
|
|
if (id >= m_transCounters.size() * transCountersPerChunk) {
|
|
uint32_t size = sizeof(uint64_t) * transCountersPerChunk;
|
|
auto *chunk = (uint64_t*)malloc(size);
|
|
bzero(chunk, size);
|
|
m_transCounters.push_back(chunk);
|
|
}
|
|
assert(id / transCountersPerChunk < m_transCounters.size());
|
|
return &(m_transCounters[id / transCountersPerChunk]
|
|
[id % transCountersPerChunk]);
|
|
}
|
|
|
|
uint32_t Translator::addTranslation(const TransRec& transRec) {
|
|
if (Trace::moduleEnabledRelease(Trace::trans, 1)) {
|
|
// Log the translation's size, creation time, SrcKey, and size
|
|
Trace::traceRelease("New translation: %" PRId64 " %s %u %u %d\n",
|
|
Timer::GetCurrentTimeMicros() - m_createdTime,
|
|
folly::format("{}:{}:{}",
|
|
curUnit()->filepath()->data(),
|
|
transRec.src.getFuncId(),
|
|
transRec.src.offset()).str().c_str(),
|
|
transRec.aLen,
|
|
transRec.astubsLen,
|
|
transRec.kind);
|
|
}
|
|
|
|
if (!isTransDBEnabled()) return -1u;
|
|
uint32_t id = getCurrentTransID();
|
|
m_translations.push_back(transRec);
|
|
m_translations[id].setID(id);
|
|
|
|
if (transRec.aLen > 0) {
|
|
m_transDB[transRec.aStart] = id;
|
|
}
|
|
if (transRec.astubsLen > 0) {
|
|
m_transDB[transRec.astubsStart] = id;
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
uint64_t Translator::getTransCounter(TransID transId) const {
|
|
if (!isTransDBEnabled()) return -1ul;
|
|
assert(transId < m_translations.size());
|
|
|
|
uint64_t counter;
|
|
|
|
if (transId / transCountersPerChunk >= m_transCounters.size()) {
|
|
counter = 0;
|
|
} else {
|
|
counter = m_transCounters[transId / transCountersPerChunk]
|
|
[transId % transCountersPerChunk];
|
|
}
|
|
return counter;
|
|
}
|
|
|
|
void Translator::setTransCounter(TransID transId, uint64_t value) {
|
|
assert(transId < m_translations.size());
|
|
assert(transId / transCountersPerChunk < m_transCounters.size());
|
|
|
|
m_transCounters[transId / transCountersPerChunk]
|
|
[transId % transCountersPerChunk] = value;
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct DeferredFileInvalidate : public DeferredWorkItem {
|
|
Eval::PhpFile* m_f;
|
|
explicit DeferredFileInvalidate(Eval::PhpFile* f) : m_f(f) {
|
|
TRACE(2, "DeferredFileInvalidate @ %p, m_f %p\n", this, m_f); }
|
|
void operator()() {
|
|
TRACE(2, "DeferredFileInvalidate: Firing @ %p , m_f %p\n", this, m_f);
|
|
tx64->invalidateFileWork(m_f);
|
|
}
|
|
};
|
|
|
|
struct DeferredPathInvalidate : public DeferredWorkItem {
|
|
const std::string m_path;
|
|
explicit DeferredPathInvalidate(const std::string& path) : m_path(path) {
|
|
assert(m_path.size() >= 1 && m_path[0] == '/');
|
|
}
|
|
void operator()() {
|
|
String spath(m_path);
|
|
/*
|
|
* inotify saw this path change. Now poke the file repository;
|
|
* it will notice the underlying PhpFile* has changed, and notify
|
|
* us via ::invalidateFile.
|
|
*
|
|
* We don't actually need to *do* anything with the PhpFile* from
|
|
* this lookup; since the path has changed, the file we'll get out is
|
|
* going to be some new file, not the old file that needs invalidation.
|
|
*/
|
|
UNUSED Eval::PhpFile* f =
|
|
g_vmContext->lookupPhpFile(spath.get(), "");
|
|
// We don't keep around the extra ref.
|
|
if (f) f->decRefAndDelete();
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
void Translator::invalidateFileWork(Eval::PhpFile* f) {
|
|
class FileInvalidationTrigger : public Treadmill::WorkItem {
|
|
Eval::PhpFile* m_f;
|
|
int m_nRefs;
|
|
public:
|
|
FileInvalidationTrigger(Eval::PhpFile* f, int n) : m_f(f), m_nRefs(n) { }
|
|
virtual void operator()() {
|
|
if (m_f->decRef(m_nRefs) == 0) {
|
|
Eval::FileRepository::onDelete(m_f);
|
|
}
|
|
}
|
|
};
|
|
size_t nSmashed = tx64->m_srcDB.invalidateCode(f);
|
|
if (nSmashed) {
|
|
// The srcDB found an entry for this file. The entry's dependency
|
|
// on this file was counted as a reference, and the code is no longer
|
|
// reachable. We need to wait until the last outstanding request
|
|
// drains to know that we can really remove the reference.
|
|
Treadmill::WorkItem::enqueue(new FileInvalidationTrigger(f, nSmashed));
|
|
}
|
|
}
|
|
|
|
bool Translator::invalidateFile(Eval::PhpFile* f) {
|
|
// This is called from high rank, but we'll need the write lease to
|
|
// invalidate code.
|
|
if (!RuntimeOption::EvalJit) return false;
|
|
assert(f != nullptr);
|
|
PendQ::defer(new DeferredFileInvalidate(f));
|
|
return true;
|
|
}
|
|
|
|
void invalidatePath(const std::string& path) {
|
|
TRACE(1, "invalidatePath: abspath %s\n", path.c_str());
|
|
PendQ::defer(new DeferredPathInvalidate(path));
|
|
}
|
|
|
|
static const char *transKindStr[] = {
|
|
"Normal_Tx64",
|
|
"Normal_HHIR",
|
|
"Anchor",
|
|
"Prologue",
|
|
};
|
|
|
|
const char *getTransKindName(TransKind kind) {
|
|
assert(kind >= 0 && kind <= TransProlog);
|
|
return transKindStr[kind];
|
|
}
|
|
|
|
string
|
|
TransRec::print(uint64_t profCount) const {
|
|
const size_t kBufSize = 1000;
|
|
static char formatBuf[kBufSize];
|
|
|
|
snprintf(formatBuf, kBufSize,
|
|
"Translation %d {\n"
|
|
" src.md5 = %s\n"
|
|
" src.funcId = %u\n"
|
|
" src.startOffset = 0x%x\n"
|
|
" src.stopOffset = 0x%x\n"
|
|
" kind = %u (%s)\n"
|
|
" aStart = %p\n"
|
|
" aLen = 0x%x\n"
|
|
" stubStart = %p\n"
|
|
" stubLen = 0x%x\n"
|
|
" profCount = %" PRIu64 "\n"
|
|
" bcMapping = %lu\n",
|
|
id, md5.toString().c_str(), src.getFuncId(), src.offset(),
|
|
bcStopOffset, kind, getTransKindName(kind), aStart, aLen,
|
|
astubsStart, astubsLen, profCount, bcMapping.size());
|
|
|
|
string ret(formatBuf);
|
|
|
|
for (size_t i = 0; i < bcMapping.size(); i++) {
|
|
snprintf(formatBuf, kBufSize, " 0x%x %p %p\n",
|
|
bcMapping[i].bcStart,
|
|
bcMapping[i].aStart,
|
|
bcMapping[i].astubsStart);
|
|
|
|
ret += string(formatBuf);
|
|
}
|
|
|
|
ret += "}\n\n";
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
ActRecState::pushFuncD(const Func* func) {
|
|
TRACE(2, "ActRecState: pushStatic func %p(%s)\n", func, func->name()->data());
|
|
Record r;
|
|
r.m_state = State::KNOWN;
|
|
r.m_topFunc = func;
|
|
r.m_entryArDelta = InvalidEntryArDelta;
|
|
m_arStack.push_back(r);
|
|
}
|
|
|
|
void
|
|
ActRecState::pushDynFunc() {
|
|
TRACE(2, "ActRecState: pushDynFunc\n");
|
|
Record r;
|
|
r.m_state = State::UNKNOWABLE;
|
|
r.m_topFunc = nullptr;
|
|
r.m_entryArDelta = InvalidEntryArDelta;
|
|
m_arStack.push_back(r);
|
|
}
|
|
|
|
void
|
|
ActRecState::pop() {
|
|
if (!m_arStack.empty()) {
|
|
m_arStack.pop_back();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* getReffiness() returns true if the parameter specified by argNum is pass
|
|
* by reference, otherwise it returns false. This function may also throw an
|
|
* UnknownInputException if the reffiness cannot be determined.
|
|
*
|
|
* Note that the 'entryArDelta' parameter specifies the delta between sp at
|
|
* the beginning of the tracelet and ar.
|
|
*/
|
|
bool
|
|
ActRecState::getReffiness(int argNum, int entryArDelta, RefDeps* outRefDeps) {
|
|
assert(outRefDeps);
|
|
TRACE(2, "ActRecState: getting reffiness for arg %d\n", argNum);
|
|
if (m_arStack.empty()) {
|
|
// The ActRec in question was pushed before the beginning of the
|
|
// tracelet, so we can make a guess about parameter reffiness and
|
|
// record our assumptions about parameter reffiness as tracelet
|
|
// guards.
|
|
const ActRec* ar = arFromSpOffset((ActRec*)vmsp(), entryArDelta);
|
|
Record r;
|
|
r.m_state = State::GUESSABLE;
|
|
r.m_entryArDelta = entryArDelta;
|
|
r.m_topFunc = ar->m_func;
|
|
m_arStack.push_back(r);
|
|
}
|
|
Record& r = m_arStack.back();
|
|
if (r.m_state == State::UNKNOWABLE) {
|
|
TRACE(2, "ActRecState: unknowable, throwing in the towel\n");
|
|
throwUnknownInput();
|
|
not_reached();
|
|
}
|
|
assert(r.m_topFunc);
|
|
bool retval = r.m_topFunc->byRef(argNum);
|
|
if (r.m_state == State::GUESSABLE) {
|
|
assert(r.m_entryArDelta != InvalidEntryArDelta);
|
|
TRACE(2, "ActRecState: guessing arg%d -> %d\n", argNum, retval);
|
|
outRefDeps->addDep(r.m_entryArDelta, argNum, retval);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
const Func*
|
|
ActRecState::getCurrentFunc() {
|
|
if (m_arStack.empty()) return nullptr;
|
|
return m_arStack.back().m_topFunc;
|
|
}
|
|
|
|
ActRecState::State
|
|
ActRecState::getCurrentState() {
|
|
if (m_arStack.empty()) return State::GUESSABLE;
|
|
return m_arStack.back().m_state;
|
|
}
|
|
|
|
const Func* lookupImmutableMethod(const Class* cls, const StringData* name,
|
|
bool& magicCall, bool staticLookup) {
|
|
if (!cls || RuntimeOption::EvalJitEnableRenameFunction) return nullptr;
|
|
if (cls->attrs() & AttrInterface) return nullptr;
|
|
bool privateOnly = false;
|
|
if (!RuntimeOption::RepoAuthoritative ||
|
|
!(cls->preClass()->attrs() & AttrUnique)) {
|
|
Class* ctx = curFunc()->cls();
|
|
if (!ctx || !ctx->classof(cls)) {
|
|
return nullptr;
|
|
}
|
|
if (!staticLookup) privateOnly = true;
|
|
}
|
|
|
|
const Func* func;
|
|
MethodLookup::LookupResult res = staticLookup ?
|
|
g_vmContext->lookupClsMethod(func, cls, name, 0,
|
|
g_vmContext->getFP(), false) :
|
|
g_vmContext->lookupObjMethod(func, cls, name, false);
|
|
|
|
if (res == MethodLookup::LookupResult::MethodNotFound) return nullptr;
|
|
|
|
assert(res == MethodLookup::LookupResult::MethodFoundWithThis ||
|
|
res == MethodLookup::LookupResult::MethodFoundNoThis ||
|
|
(staticLookup ?
|
|
res == MethodLookup::LookupResult::MagicCallStaticFound :
|
|
res == MethodLookup::LookupResult::MagicCallFound));
|
|
|
|
magicCall =
|
|
res == MethodLookup::LookupResult::MagicCallStaticFound ||
|
|
res == MethodLookup::LookupResult::MagicCallFound;
|
|
|
|
if ((privateOnly && (!(func->attrs() & AttrPrivate) || magicCall)) ||
|
|
func->isAbstract() ||
|
|
func->attrs() & AttrDynamicInvoke) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (staticLookup) {
|
|
if (magicCall) {
|
|
/*
|
|
* i) We cant tell if a magic call would go to __call or __callStatic
|
|
* - Could deal with this by checking for the existence of __call
|
|
*
|
|
* ii) hphp semantics is that in the case of an object call, we look
|
|
* for __call in the scope of the object (this is incompatible
|
|
* with zend) which means we would have to know that there is no
|
|
* __call higher up in the tree
|
|
* - Could deal with this by checking for AttrNoOverride on the
|
|
* class
|
|
*/
|
|
func = nullptr;
|
|
}
|
|
} else if (!(func->attrs() & AttrPrivate)) {
|
|
if (magicCall || func->attrs() & AttrStatic) {
|
|
if (!(cls->preClass()->attrs() & AttrNoOverride)) {
|
|
func = nullptr;
|
|
}
|
|
} else if (!(func->attrs() & AttrNoOverride && !func->hasStaticLocals()) &&
|
|
!(cls->preClass()->attrs() & AttrNoOverride)) {
|
|
func = nullptr;
|
|
}
|
|
}
|
|
return func;
|
|
}
|
|
|
|
std::string traceletShape(const Tracelet& trace) {
|
|
std::string ret;
|
|
|
|
for (auto ni = trace.m_instrStream.first; ni; ni = ni->next) {
|
|
using folly::toAppend;
|
|
|
|
toAppend(opcodeToName(ni->op()), &ret);
|
|
if (ni->immVec.isValid()) {
|
|
toAppend(
|
|
"<",
|
|
locationCodeString(ni->immVec.locationCode()),
|
|
&ret);
|
|
for (auto& mc : ni->immVecM) {
|
|
toAppend(" ", memberCodeString(mc), &ret);
|
|
}
|
|
toAppend(">", &ret);
|
|
}
|
|
toAppend(" ", &ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
} // HPHP::Transl
|
|
|
|
void invalidatePath(const std::string& path) {
|
|
TRACE(1, "invalidatePath: abspath %s\n", path.c_str());
|
|
PendQ::defer(new DeferredPathInvalidate(path));
|
|
}
|
|
|
|
} // HPHP
|