Arquivos
hhvm/hphp/runtime/vm/jit/irtranslator.cpp
T
Paul Bissonnette 0d5d5bca72 Added IterBreakV, MIter{Init,InitK,Next,NextK,Free} and fixed memory tracking bug.
Added IR opcodes to perform MIter* instructions in JIT.  Added IterBreakV bytecode
operation to break out of multiple loops containing iterators.  Emitter and assembler
were modified to support such use.
2013-07-06 11:12:28 -07:00

1815 linhas
51 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 <stdint.h>
#include "hphp/runtime/base/strings.h"
#include "folly/Format.h"
#include "folly/Conv.h"
#include "hphp/util/trace.h"
#include "hphp/util/stack_trace.h"
#include "hphp/util/util.h"
#include "hphp/runtime/vm/bytecode.h"
#include "hphp/runtime/vm/runtime.h"
#include "hphp/runtime/base/complex_types.h"
#include "hphp/runtime/base/runtime_option.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/base/stats.h"
#include "hphp/runtime/vm/jit/ir.h"
#include "hphp/runtime/vm/jit/opt.h"
#include "hphp/runtime/vm/jit/linearscan.h"
#include "hphp/runtime/vm/jit/codegen.h"
#include "hphp/runtime/vm/jit/hhbctranslator.h"
#include "hphp/runtime/vm/jit/print.h"
#include "hphp/runtime/vm/jit/check.h"
// Include last to localize effects to this file
#include "hphp/util/assert_throw.h"
namespace HPHP {
namespace Transl {
using namespace reg;
using namespace Util;
using namespace Trace;
using std::max;
using JIT::HhbcTranslator;
TRACE_SET_MOD(hhir);
#ifdef DEBUG
static const bool debug = true;
#else
static const bool debug = false;
#endif
#define TVOFF(nm) offsetof(TypedValue, nm)
#define AROFF(nm) offsetof(ActRec, nm)
#define HHIR_UNIMPLEMENTED_OP(op) \
do { \
throw JIT::FailedIRGen(__FILE__, __LINE__, op); \
} while (0)
#define HHIR_UNIMPLEMENTED(op) \
do { \
throw JIT::FailedIRGen(__FILE__, __LINE__, #op); \
} while (0)
#define HHIR_UNIMPLEMENTED_WHEN(expr, op) \
do { \
if (expr) { \
throw JIT::FailedIRGen(__FILE__, __LINE__, #op); \
} \
} while (0)
#define HHIR_EMIT(op, ...) \
do { \
m_hhbcTrans->emit ## op(__VA_ARGS__); \
return; \
} while (0)
bool isInferredType(const NormalizedInstruction& i) {
return (i.getOutputUsage(i.outStack) ==
NormalizedInstruction::OutputUse::Inferred);
}
JIT::Type getInferredOrPredictedType(const NormalizedInstruction& i) {
NormalizedInstruction::OutputUse u = i.getOutputUsage(i.outStack);
if (u == NormalizedInstruction::OutputUse::Inferred ||
(u == NormalizedInstruction::OutputUse::Used && i.outputPredicted)) {
return JIT::Type::fromRuntimeType(i.outStack->rtt);
}
return JIT::Type::None;
}
void
TranslatorX64::irCheckType(X64Assembler& a,
const Location& l,
const RuntimeType& rtt,
SrcRec& fail) {
// We can get invalid inputs as a side effect of reading invalid
// items out of BBs we truncate; they don't need guards.
assert(!rtt.isVagueValue());
switch (l.space) {
case Location::Stack:
{
uint32_t stackOffset = locPhysicalOffset(l);
m_hhbcTrans->guardTypeStack(stackOffset,
JIT::Type::fromRuntimeType(rtt));
}
break;
case Location::Local:
m_hhbcTrans->guardTypeLocal(l.offset, JIT::Type::fromRuntimeType(rtt));
break;
case Location::Iter:
case Location::Invalid:
case Location::Litstr:
case Location::Litint:
case Location::This:
not_reached();
}
}
void
Translator::translateMod(const NormalizedInstruction& i) {
HHIR_EMIT(Mod);
}
void
Translator::translateBinaryArithOp(const NormalizedInstruction& i) {
auto const op = i.op();
switch (op) {
#define CASE(OpBc) \
case Op ## OpBc: HHIR_EMIT(OpBc);
CASE(Add)
CASE(Sub)
CASE(BitAnd)
CASE(BitOr)
CASE(BitXor)
CASE(Mul)
#undef CASE
default: {
not_reached();
};
}
NOT_REACHED();
}
void
Translator::translateSameOp(const NormalizedInstruction& i) {
auto const op = i.op();
assert(op == OpSame || op == OpNSame);
if (op == OpSame) {
HHIR_EMIT(Same);
} else {
HHIR_EMIT(NSame);
}
}
void
Translator::translateEqOp(const NormalizedInstruction& i) {
auto const op = i.op();
assert(op == OpEq || op == OpNeq);
if (op == OpEq) {
HHIR_EMIT(Eq);
} else {
HHIR_EMIT(Neq);
}
}
void
Translator::translateLtGtOp(const NormalizedInstruction& i) {
auto const op = i.op();
assert(op == OpLt || op == OpLte || op == OpGt || op == OpGte);
assert(i.inputs.size() == 2);
assert(i.inputs[0]->outerType() != KindOfRef);
assert(i.inputs[1]->outerType() != KindOfRef);
DataType leftType = i.inputs[0]->outerType();
DataType rightType = i.inputs[1]->outerType();
bool ok = TypeConstraint::equivDataTypes(leftType, rightType) &&
(i.inputs[0]->isNull() ||
leftType == KindOfBoolean ||
i.inputs[0]->isInt());
HHIR_UNIMPLEMENTED_WHEN(!ok, LtGtOp);
switch (op) {
case OpLt : HHIR_EMIT(Lt);
case OpLte : HHIR_EMIT(Lte);
case OpGt : HHIR_EMIT(Gt);
case OpGte : HHIR_EMIT(Gte);
default : HHIR_UNIMPLEMENTED(LtGtOp);
}
}
void
Translator::translateUnaryBooleanOp(const NormalizedInstruction& i) {
auto const op = i.op();
assert(op == OpCastBool || op == OpEmptyL);
if (op == OpCastBool) {
HHIR_EMIT(CastBool);
} else {
HHIR_EMIT(EmptyL, i.inputs[0]->location.offset);
}
}
void
Translator::translateBranchOp(const NormalizedInstruction& i) {
auto const op = i.op();
assert(op == OpJmpZ || op == OpJmpNZ);
assert(!i.next);
if (op == OpJmpZ) {
HHIR_EMIT(JmpZ, i.offset() + i.imm[0].u_BA);
} else {
HHIR_EMIT(JmpNZ, i.offset() + i.imm[0].u_BA);
}
}
void
Translator::translateCGetL(const NormalizedInstruction& i) {
DEBUG_ONLY auto const op = i.op();
assert(op == OpFPassL || op == OpCGetL);
const vector<DynLocation*>& inputs = i.inputs;
assert(inputs.size() == 1);
assert(inputs[0]->isLocal());
HHIR_EMIT(CGetL, inputs[0]->location.offset);
}
void
Translator::translateCGetL2(const NormalizedInstruction& ni) {
const int locIdx = 1;
HHIR_EMIT(CGetL2, ni.inputs[locIdx]->location.offset);
}
void
Translator::translateVGetL(const NormalizedInstruction& i) {
HHIR_EMIT(VGetL, i.inputs[0]->location.offset);
}
void
Translator::translateAssignToLocalOp(const NormalizedInstruction& ni) {
DEBUG_ONLY const int rhsIdx = 0;
const int locIdx = 1;
auto const op = ni.op();
assert(op == OpSetL || op == OpBindL);
assert(ni.inputs.size() == 2);
assert((op == OpBindL) ==
(ni.inputs[rhsIdx]->outerType() == KindOfRef));
assert(ni.inputs[rhsIdx]->isStack());
if (op == OpSetL) {
assert(ni.inputs[locIdx]->isLocal());
HHIR_EMIT(SetL, ni.inputs[locIdx]->location.offset);
} else {
assert(op == OpBindL);
HHIR_EMIT(BindL, ni.inputs[locIdx]->location.offset);
}
}
void
Translator::translatePopC(const NormalizedInstruction& i) {
assert(i.inputs.size() == 1);
if (i.inputs[0]->rtt.isVagueValue()) {
HHIR_EMIT(PopR);
} else {
HHIR_EMIT(PopC);
}
}
void
Translator::translatePopV(const NormalizedInstruction& i) {
assert(i.inputs[0]->rtt.isVagueValue() || i.inputs[0]->isRef());
HHIR_EMIT(PopV);
}
void
Translator::translatePopR(const NormalizedInstruction& i) {
translatePopC(i);
}
void
Translator::translateUnboxR(const NormalizedInstruction& i) {
if (i.noOp) {
// statically proved to be unboxed -- just pass that info to the IR
TRACE(1, "HHIR: translateUnboxR: output inferred to be Cell\n");
m_hhbcTrans->assertTypeLocation(Location(Location::Stack, 0),
JIT::Type::Cell);
} else {
HHIR_EMIT(UnboxR);
}
}
void
Translator::translateNull(const NormalizedInstruction& i) {
assert(i.inputs.size() == 0);
HHIR_EMIT(Null);
}
void
Translator::translateNullUninit(const NormalizedInstruction& i) {
HHIR_EMIT(NullUninit);
}
void
Translator::translateTrue(const NormalizedInstruction& i) {
assert(i.inputs.size() == 0);
HHIR_EMIT(True);
}
void
Translator::translateFalse(const NormalizedInstruction& i) {
assert(i.inputs.size() == 0);
HHIR_EMIT(False);
}
void
Translator::translateInt(const NormalizedInstruction& i) {
assert(i.inputs.size() == 0);
HHIR_EMIT(Int, i.imm[0].u_I64A);
}
void
Translator::translateDouble(const NormalizedInstruction& i) {
HHIR_EMIT(Double, i.imm[0].u_DA);
}
void
Translator::translateString(const NormalizedInstruction& i) {
assert(i.inputs.size() == 0);
HHIR_EMIT(String, (i.imm[0].u_SA));
}
void
Translator::translateArray(const NormalizedInstruction& i) {
assert(i.inputs.size() == 0);
HHIR_EMIT(Array, i.imm[0].u_AA);
}
void
Translator::translateNewArray(const NormalizedInstruction& i) {
HHIR_EMIT(NewArray, i.imm[0].u_IVA);
}
void
Translator::translateNop(const NormalizedInstruction& i) {
HHIR_EMIT(Nop);
}
void
Translator::translateAddElemC(const NormalizedInstruction& i) {
HHIR_EMIT(AddElemC);
}
void
Translator::translateAddNewElemC(const NormalizedInstruction& i) {
assert(i.inputs.size() == 2);
assert(i.inputs[0]->outerType() != KindOfRef);
assert(i.inputs[1]->outerType() != KindOfRef);
assert(i.inputs[0]->isStack());
assert(i.inputs[1]->isStack());
HHIR_EMIT(AddNewElemC);
}
void
Translator::translateCns(const NormalizedInstruction& i) {
HHIR_EMIT(Cns, i.imm[0].u_SA);
}
void
Translator::translateDefCns(const NormalizedInstruction& i) {
HHIR_EMIT(DefCns, (i.imm[0].u_SA));
}
void
Translator::translateClsCnsD(const NormalizedInstruction& i) {
HHIR_EMIT(ClsCnsD, (i.imm[0].u_SA), (i.imm[1].u_SA), i.outPred);
}
void
Translator::translateConcat(const NormalizedInstruction& i) {
HHIR_EMIT(Concat);
}
void
Translator::translateAdd(const NormalizedInstruction& i) {
assert(i.inputs.size() == 2);
if (i.inputs[0]->valueType() == KindOfArray &&
i.inputs[1]->valueType() == KindOfArray) {
HHIR_EMIT(ArrayAdd);
return;
}
HHIR_EMIT(Add);
}
void
Translator::translateXor(const NormalizedInstruction& i) {
HHIR_EMIT(Xor);
}
void
Translator::translateNot(const NormalizedInstruction& i) {
HHIR_EMIT(Not);
}
void
Translator::translateBitNot(const NormalizedInstruction& i) {
HHIR_EMIT(BitNot);
}
void
Translator::translateCastInt(const NormalizedInstruction& i) {
assert(i.inputs.size() == 1);
HHIR_EMIT(CastInt);
/* nop */
}
void
Translator::translateCastArray(const NormalizedInstruction& i) {
HHIR_EMIT(CastArray);
}
void
Translator::translateCastObject(const NormalizedInstruction& i) {
HHIR_EMIT(CastObject);
}
void
Translator::translateCastDouble(const NormalizedInstruction& i) {
HHIR_EMIT(CastDouble);
}
void
Translator::translateCastString(const NormalizedInstruction& i) {
HHIR_EMIT(CastString);
}
void
Translator::translatePrint(const NormalizedInstruction& i) {
HHIR_EMIT(Print);
}
void
Translator::translateJmp(const NormalizedInstruction& i) {
HHIR_EMIT(Jmp, i.offset() + i.imm[0].u_BA, i.breaksTracelet, i.noSurprise);
}
void
Translator::translateSwitch(const NormalizedInstruction& i) {
HHIR_EMIT(Switch, i.immVec, i.imm[1].u_I64A, i.imm[2].u_IVA);
}
void
Translator::translateSSwitch(const NormalizedInstruction& i) {
HHIR_EMIT(SSwitch, i.immVec);
}
/*
* translateRetC --
*
* Return to caller with the current activation record replaced with the
* top-of-stack return value.
*/
void
Translator::translateRetC(const NormalizedInstruction& i) {
HHIR_EMIT(RetC, i.inlineReturn);
}
void
Translator::translateRetV(const NormalizedInstruction& i) {
HHIR_EMIT(RetV, i.inlineReturn);
}
void
Translator::translateNativeImpl(const NormalizedInstruction& ni) {
HHIR_EMIT(NativeImpl);
}
// emitClsLocalIndex --
// emitStringToClass --
// emitStringToKnownClass --
// emitObjToClass --
// emitClsAndPals --
// Helpers for AGetC/AGetL.
const int kEmitClsLocalIdx = 0;
void Translator::translateAGetC(const NormalizedInstruction& ni) {
const StringData* clsName =
ni.inputs[kEmitClsLocalIdx]->rtt.valueStringOrNull();
HHIR_EMIT(AGetC, clsName);
}
void Translator::translateAGetL(const NormalizedInstruction& i) {
assert(i.inputs[kEmitClsLocalIdx]->isLocal());
const DynLocation* dynLoc = i.inputs[kEmitClsLocalIdx];
const StringData* clsName = dynLoc->rtt.valueStringOrNull();
HHIR_EMIT(AGetL, dynLoc->location.offset, clsName);
}
void Translator::translateSelf(const NormalizedInstruction& i) {
HHIR_EMIT(Self);
}
void Translator::translateParent(const NormalizedInstruction& i) {
HHIR_EMIT(Parent);
}
void Translator::translateDup(const NormalizedInstruction& ni) {
HHIR_EMIT(Dup);
}
void Translator::translateCreateCont(const NormalizedInstruction& i) {
HHIR_EMIT(CreateCont, i.imm[0].u_SA);
}
void Translator::translateContEnter(const NormalizedInstruction& i) {
auto after = nextSrcKey(i).offset();
// ContEnter can't exist in an inlined function right now. (If it
// ever can, this curFunc() needs to change.)
assert(!m_hhbcTrans->isInlining());
const Func* srcFunc = curFunc();
int32_t callOffsetInUnit = after - srcFunc->base();
HHIR_EMIT(ContEnter, callOffsetInUnit);
}
void Translator::translateUnpackCont(const NormalizedInstruction& i) {
HHIR_EMIT(UnpackCont);
}
void Translator::translateContSuspend(const NormalizedInstruction& i) {
HHIR_EMIT(ContSuspend, i.imm[0].u_IVA);
}
void Translator::translateContSuspendK(const NormalizedInstruction& i) {
HHIR_EMIT(ContSuspendK, i.imm[0].u_IVA);
}
void Translator::translateContRetC(const NormalizedInstruction& i) {
HHIR_EMIT(ContRetC);
}
void Translator::translateContCheck(const NormalizedInstruction& i) {
HHIR_EMIT(ContCheck, i.imm[0].u_IVA);
}
void Translator::translateContRaise(const NormalizedInstruction& i) {
HHIR_EMIT(ContRaise);
}
void Translator::translateContValid(const NormalizedInstruction& i) {
HHIR_EMIT(ContValid);
}
void Translator::translateContKey(const NormalizedInstruction& i) {
HHIR_EMIT(ContKey);
}
void Translator::translateContCurrent(const NormalizedInstruction& i) {
HHIR_EMIT(ContCurrent);
}
void Translator::translateContStopped(const NormalizedInstruction& i) {
HHIR_EMIT(ContStopped);
}
void Translator::translateContHandle(const NormalizedInstruction& i) {
HHIR_EMIT(ContHandle);
}
void Translator::translateStrlen(const NormalizedInstruction& i) {
HHIR_EMIT(Strlen);
}
void Translator::translateIncStat(const NormalizedInstruction& i) {
HHIR_EMIT(IncStat, i.imm[0].u_IVA, i.imm[1].u_IVA);
}
void Translator::translateArrayIdx(const NormalizedInstruction& i) {
HHIR_EMIT(ArrayIdx);
}
void Translator::translateClassExists(const NormalizedInstruction& i) {
const StringData* clsName = i.inputs[1]->rtt.valueStringOrNull();
HHIR_EMIT(ClassExists, clsName);
}
void Translator::translateInterfaceExists(const NormalizedInstruction& i) {
const StringData* ifaceName = i.inputs[1]->rtt.valueStringOrNull();
HHIR_EMIT(InterfaceExists, ifaceName);
}
void Translator::translateTraitExists(const NormalizedInstruction& i) {
const StringData* traitName = i.inputs[1]->rtt.valueStringOrNull();
HHIR_EMIT(TraitExists, traitName);
}
void Translator::translateVGetS(const NormalizedInstruction& i) {
const int kPropNameIdx = 1;
const StringData* propName = i.inputs[kPropNameIdx]->rtt.valueStringOrNull();
HHIR_EMIT(VGetS, propName);
}
void
Translator::translateVGetG(const NormalizedInstruction& i) {
const StringData* name = i.inputs[0]->rtt.valueStringOrNull();
HHIR_EMIT(VGetG, name);
}
void Translator::translateBindS(const NormalizedInstruction& i) {
const int kPropIdx = 2;
const StringData* propName = i.inputs[kPropIdx]->rtt.valueStringOrNull();
HHIR_EMIT(BindS, propName);
}
void Translator::translateEmptyS(const NormalizedInstruction& i) {
const int kPropNameIdx = 1;
const StringData* propName = i.inputs[kPropNameIdx]->rtt.valueStringOrNull();
HHIR_EMIT(EmptyS, propName);
}
void Translator::translateEmptyG(const NormalizedInstruction& i) {
const StringData* gblName = i.inputs[0]->rtt.valueStringOrNull();
HHIR_EMIT(EmptyG, gblName);
}
void
Translator::translateIssetS(const NormalizedInstruction& i) {
const int kPropNameIdx = 1;
const StringData* propName = i.inputs[kPropNameIdx]->rtt.valueStringOrNull();
HHIR_EMIT(IssetS, propName);
}
void
Translator::translateIssetG(const NormalizedInstruction& i) {
const StringData* gblName = i.inputs[0]->rtt.valueStringOrNull();
HHIR_EMIT(IssetG, gblName);
}
void
Translator::translateUnsetG(const NormalizedInstruction& i) {
const StringData* gblName = i.inputs[0]->rtt.valueStringOrNull();
HHIR_EMIT(UnsetG, gblName);
}
void
Translator::translateUnsetN(const NormalizedInstruction& i) {
HHIR_EMIT(UnsetN);
}
void Translator::translateCGetS(const NormalizedInstruction& i) {
const int kPropNameIdx = 1;
const StringData* propName = i.inputs[kPropNameIdx]->rtt.valueStringOrNull();
HHIR_EMIT(CGetS, propName,
getInferredOrPredictedType(i), isInferredType(i));
}
void Translator::translateSetS(const NormalizedInstruction& i) {
const int kPropIdx = 2;
const StringData* propName = i.inputs[kPropIdx]->rtt.valueStringOrNull();
HHIR_EMIT(SetS, propName);
}
void
Translator::translateCGetG(const NormalizedInstruction& i) {
const StringData* name = i.inputs[0]->rtt.valueStringOrNull();
HHIR_EMIT(CGetG, name, getInferredOrPredictedType(i), isInferredType(i));
}
void Translator::translateSetG(const NormalizedInstruction& i) {
const StringData* name = i.inputs[1]->rtt.valueStringOrNull();
HHIR_EMIT(SetG, name);
}
void Translator::translateBindG(const NormalizedInstruction& i) {
const StringData* name = i.inputs[1]->rtt.valueStringOrNull();
HHIR_EMIT(BindG, name);
}
void
Translator::translateLateBoundCls(const NormalizedInstruction&i) {
HHIR_EMIT(LateBoundCls);
}
void Translator::translateFPassL(const NormalizedInstruction& ni) {
if (ni.preppedByRef) {
translateVGetL(ni);
} else {
translateCGetL(ni);
}
}
void Translator::translateFPassS(const NormalizedInstruction& ni) {
if (ni.preppedByRef) {
translateVGetS(ni);
} else {
translateCGetS(ni);
}
}
void Translator::translateFPassG(const NormalizedInstruction& ni) {
if (ni.preppedByRef) {
translateVGetG(ni);
} else {
translateCGetG(ni);
}
}
void
Translator::translateCheckTypeOp(const NormalizedInstruction& ni) {
assert(ni.inputs.size() == 1);
auto const op = ni.op();
const int off = ni.inputs[0]->location.offset;
switch (op) {
case OpIssetL: HHIR_EMIT(IssetL, off);
case OpIsNullL: HHIR_EMIT(IsNullL, off);
case OpIsNullC: HHIR_EMIT(IsNullC);
case OpIsStringL: HHIR_EMIT(IsStringL, off);
case OpIsStringC: HHIR_EMIT(IsStringC);
case OpIsArrayL: HHIR_EMIT(IsArrayL, off);
case OpIsArrayC: HHIR_EMIT(IsArrayC);
case OpIsIntL: HHIR_EMIT(IsIntL, off);
case OpIsIntC: HHIR_EMIT(IsIntC);
case OpIsBoolL: HHIR_EMIT(IsBoolL, off);
case OpIsBoolC: HHIR_EMIT(IsBoolC);
case OpIsDoubleL: HHIR_EMIT(IsDoubleL, off);
case OpIsDoubleC: HHIR_EMIT(IsDoubleC);
case OpIsObjectL: HHIR_EMIT(IsObjectL, off);
case OpIsObjectC: HHIR_EMIT(IsObjectC);
// Note: for IsObject*, we need to emit some kind of
// call to ObjectData::isResource or something.
default: not_reached();
}
}
void
Translator::translateAKExists(const NormalizedInstruction& ni) {
HHIR_EMIT(AKExists);
}
void
Translator::translateSetOpL(const NormalizedInstruction& i) {
JIT::Opcode opc;
switch (i.imm[1].u_OA) {
case SetOpPlusEqual: opc = JIT::OpAdd; break;
case SetOpMinusEqual: opc = JIT::OpSub; break;
case SetOpMulEqual: opc = JIT::OpMul; break;
case SetOpDivEqual: HHIR_UNIMPLEMENTED(SetOpL_Div);
case SetOpConcatEqual: opc = JIT::Concat; break;
case SetOpModEqual: HHIR_UNIMPLEMENTED(SetOpL_Mod);
case SetOpAndEqual: opc = JIT::OpBitAnd; break;
case SetOpOrEqual: opc = JIT::OpBitOr; break;
case SetOpXorEqual: opc = JIT::OpBitXor; break;
case SetOpSlEqual: HHIR_UNIMPLEMENTED(SetOpL_Shl);
case SetOpSrEqual: HHIR_UNIMPLEMENTED(SetOpL_Shr);
default: not_reached();
}
const int localIdx = 1;
HHIR_EMIT(SetOpL, opc, i.inputs[localIdx]->location.offset);
}
void
Translator::translateIncDecL(const NormalizedInstruction& i) {
const vector<DynLocation*>& inputs = i.inputs;
assert(inputs.size() == 1);
assert(inputs[0]->isLocal());
const IncDecOp oplet = IncDecOp(i.imm[1].u_OA);
assert(oplet == PreInc || oplet == PostInc || oplet == PreDec ||
oplet == PostDec);
bool post = (oplet == PostInc || oplet == PostDec);
bool pre = !post;
bool inc = (oplet == PostInc || oplet == PreInc);
HHIR_UNIMPLEMENTED_WHEN((i.inputs[0]->valueType() != KindOfBoolean) &&
(i.inputs[0]->valueType() != KindOfInt64) &&
(i.inputs[0]->valueType() != KindOfDouble),
IncDecL_unsupported);
HHIR_EMIT(IncDecL, pre, inc, inputs[0]->location.offset);
}
void
Translator::translateUnsetL(const NormalizedInstruction& i) {
HHIR_EMIT(UnsetL, i.inputs[0]->location.offset);
}
void
Translator::translateReqDoc(const NormalizedInstruction& i) {
const StringData* name = i.inputs[0]->rtt.valueStringOrNull();
HHIR_EMIT(ReqDoc, name);
}
void Translator::translateDefCls(const NormalizedInstruction& i) {
int cid = i.imm[0].u_IVA;
HHIR_EMIT(DefCls, cid, i.source.offset());
}
void Translator::translateDefFunc(const NormalizedInstruction& i) {
int fid = i.imm[0].u_IVA;
HHIR_EMIT(DefFunc, fid);
}
void
Translator::translateFPushFunc(const NormalizedInstruction& i) {
HHIR_EMIT(FPushFunc, (i.imm[0].u_IVA));
}
void
Translator::translateFPushClsMethodD(const NormalizedInstruction& i) {
HHIR_EMIT(FPushClsMethodD,
(i.imm[0].u_IVA),
(i.imm[1].u_SA),
(i.imm[2].u_SA));
}
void
Translator::translateFPushClsMethodF(const NormalizedInstruction& i) {
// For now, only support cases where both the class and the method are known.
const int classIdx = 0;
const int methodIdx = 1;
DynLocation* classLoc = i.inputs[classIdx];
DynLocation* methodLoc = i.inputs[methodIdx];
HHIR_UNIMPLEMENTED_WHEN(!(methodLoc->isString() &&
methodLoc->rtt.valueString() != nullptr &&
classLoc->valueType() == KindOfClass &&
classLoc->rtt.valueClass() != nullptr),
FPushClsMethodF_unknown);
auto cls = classLoc->rtt.valueClass();
HHIR_EMIT(FPushClsMethodF,
i.imm[0].u_IVA, // # of arguments
cls,
methodLoc->rtt.valueString());
}
void
Translator::translateFPushObjMethodD(const NormalizedInstruction& i) {
assert(i.inputs.size() == 1);
HHIR_UNIMPLEMENTED_WHEN((i.inputs[0]->valueType() != KindOfObject),
FPushObjMethod_nonObj);
assert(i.inputs[0]->valueType() == KindOfObject);
const Class* baseClass = i.inputs[0]->rtt.valueClass();
HHIR_EMIT(FPushObjMethodD,
i.imm[0].u_IVA,
i.imm[1].u_IVA,
baseClass);
}
void Translator::translateFPushCtor(const NormalizedInstruction& i) {
HHIR_EMIT(FPushCtor, (i.imm[0].u_IVA));
}
void Translator::translateFPushCtorD(const NormalizedInstruction& i) {
HHIR_EMIT(FPushCtorD, (i.imm[0].u_IVA), (i.imm[1].u_SA));
}
void Translator::translateCreateCl(const NormalizedInstruction& i) {
HHIR_EMIT(CreateCl, (i.imm[0].u_IVA), (i.imm[1].u_SA));
}
// static void fatalNullThis() { raise_error(Strings::FATAL_NULL_THIS); }
void
Translator::translateThis(const NormalizedInstruction &i) {
HHIR_EMIT(This);
}
void
Translator::translateBareThis(const NormalizedInstruction &i) {
HHIR_EMIT(BareThis, (i.imm[0].u_OA));
}
void
Translator::translateCheckThis(const NormalizedInstruction& i) {
HHIR_EMIT(CheckThis);
}
void
Translator::translateInitThisLoc(const NormalizedInstruction& i) {
HHIR_EMIT(InitThisLoc, i.imm[0].u_HA);
}
void
Translator::translateFPushFuncD(const NormalizedInstruction& i) {
HHIR_EMIT(FPushFuncD, (i.imm[0].u_IVA), (i.imm[1].u_SA));
}
void
Translator::translateFPassCOp(const NormalizedInstruction& i) {
auto const op = i.op();
if (i.preppedByRef && (op == OpFPassCW || op == OpFPassCE)) {
// These cases might have to raise a warning or an error
HHIR_UNIMPLEMENTED(FPassCW_FPassCE_byref);
} else {
HHIR_EMIT(FPassCOp);
}
}
void
Translator::translateFPassV(const NormalizedInstruction& i) {
if (i.preppedByRef || i.noOp) {
TRACE(1, "HHIR: translateFPassV: noOp\n");
return;
}
HHIR_EMIT(FPassV);
}
void
Translator::translateFPushCufIter(const NormalizedInstruction& i) {
HHIR_EMIT(FPushCufIter, i.imm[0].u_IVA, i.imm[1].u_IA);
}
static const Func*
findCuf(const NormalizedInstruction& ni,
Class*& cls, StringData*& invName, bool& forward) {
forward = (ni.op() == OpFPushCufF);
cls = nullptr;
invName = nullptr;
DynLocation* callable = ni.inputs[ni.op() == OpFPushCufSafe ? 1 : 0];
const StringData* str =
callable->isString() ? callable->rtt.valueString() : nullptr;
const ArrayData* arr =
callable->isArray() ? callable->rtt.valueArray() : nullptr;
StringData* sclass = nullptr;
StringData* sname = nullptr;
if (str) {
Func* f = HPHP::Unit::lookupFunc(str);
if (f) return f;
String name(const_cast<StringData*>(str));
int pos = name.find("::");
if (pos <= 0 || pos + 2 >= name.size() ||
name.find("::", pos + 2) != String::npos) {
return nullptr;
}
sclass = StringData::GetStaticString(name.substr(0, pos).get());
sname = StringData::GetStaticString(name.substr(pos + 2).get());
} else if (arr) {
if (arr->size() != 2) return nullptr;
CVarRef e0 = arr->get(int64_t(0), false);
CVarRef e1 = arr->get(int64_t(1), false);
if (!e0.isString() || !e1.isString()) return nullptr;
sclass = e0.getStringData();
sname = e1.getStringData();
String name(sname);
if (name.find("::") != String::npos) return nullptr;
} else {
return nullptr;
}
Class* ctx = curFunc()->cls();
if (sclass->isame(s_self.get())) {
if (!ctx) return nullptr;
cls = ctx;
forward = true;
} else if (sclass->isame(s_parent.get())) {
if (!ctx || !ctx->parent()) return nullptr;
cls = ctx->parent();
forward = true;
} else if (sclass->isame(s_static.get())) {
return nullptr;
} else {
cls = Unit::lookupUniqueClass(sclass);
if (!cls) return nullptr;
}
bool magicCall = false;
const Func* f = lookupImmutableMethod(cls, sname, magicCall, true);
if (!f || (forward && !ctx->classof(f->cls()))) {
/*
* To preserve the invariant that the lsb class
* is an instance of the context class, we require
* that f's class is an instance of the context class.
* This is conservative, but without it, we would need
* a runtime check to decide whether or not to forward
* the lsb class
*/
return nullptr;
}
if (magicCall) invName = sname;
return f;
}
void
Translator::translateFPushCufOp(const NormalizedInstruction& i) {
Class* cls = nullptr;
StringData* invName = nullptr;
bool forward = false;
const Func* func = findCuf(i, cls, invName, forward);
HHIR_EMIT(FPushCufOp, i.op(), cls, invName, func, i.imm[0].u_IVA);
}
void
Translator::translateFPassR(const NormalizedInstruction& i) {
/*
* Like FPassC, FPassR is able to cheat on boxing if the current
* parameter is pass by reference but we have a cell: the box would refer
* to exactly one datum (the value currently on the stack).
*
* However, if the callee wants a cell and we have a variant we must
* unbox; otherwise we might accidentally make callee changes to its
* parameter globally visible.
*/
if (!i.preppedByRef) {
HHIR_EMIT(FPassR);
} else {
HHIR_UNIMPLEMENTED(FPassR);
}
}
void
Translator::translateFCallBuiltin(const NormalizedInstruction& i) {
int numArgs = i.imm[0].u_IVA;
int numNonDefault = i.imm[1].u_IVA;
Id funcId = i.imm[2].u_SA;
HHIR_EMIT(FCallBuiltin, numArgs, numNonDefault, funcId);
}
bool shouldIRInline(const Func* curFunc,
const Func* func,
const Tracelet& callee) {
if (!RuntimeOption::EvalHHIREnableGenTimeInlining) {
return false;
}
auto refuse = [&](const char* why) -> bool {
FTRACE(1, "shouldIRInline: refusing {} <reason: {}>\n",
func->fullName()->data(), why);
return false;
};
auto accept = [&](const char* kind) -> bool {
FTRACE(1, "shouldIRInline: inlining {} <kind: {}>\n",
func->fullName()->data(), kind);
return true;
};
if (func->numLocals() != func->numParams()) {
return refuse("more locals than params (unsupported)");
}
if (func->numIterators() != 0) {
return refuse("iterators");
}
if (func->maxStackCells() >= kStackCheckLeafPadding) {
FTRACE(1, "{} >= {}\n", func->maxStackCells(), kStackCheckLeafPadding);
return refuse("too many stack cells");
}
/////////////
// Little pattern recognition helpers:
const NormalizedInstruction* cursor;
Op current;
auto resetCursor = [&] {
cursor = callee.m_instrStream.first;
current = cursor->op();
};
auto next = [&]() -> Op {
auto op = cursor->op();
cursor = cursor->next;
current = cursor->op();
return op;
};
auto nextIf = [&](Op op) -> bool {
if (current != op) return false;
next();
return true;
};
auto atRet = [&] { return current == OpRetC || current == OpRetV; };
// Simple operations that just put a Cell on the stack. There must
// either be no inputs, or a single local as an input. For now
// avoid CreateCont because it depends on the frame.
auto simpleCell = [&]() -> bool {
if (current == OpCreateCont) return false;
if (cursor->outStack && cursor->inputs.empty()) {
next();
return true;
}
if (current == OpCGetL || current == OpVGetL) {
next();
return true;
}
return false;
};
// Simple two-cell comparison operators.
auto simpleCmp = [&]() -> bool {
switch (current) {
case OpAdd: case OpSub: case OpMul: case OpDiv: case OpMod:
case OpXor: case OpNot: case OpSame: case OpNSame: case OpEq:
case OpNeq: case OpLt: case OpLte: case OpGt: case OpGte:
case OpBitAnd: case OpBitOr: case OpBitXor: case OpBitNot:
case OpShl: case OpShr:
next();
return true;
default:
return false;
}
};
// In the various patterns below, when we're down to a cell on the
// stack, this is used to allow simple constant-foldable
// manipulations of it before return.
auto cellManipRet = [&]() -> bool {
if (nextIf(OpNot)) return atRet();
if (simpleCell() && simpleCmp()) return atRet();
return atRet();
};
// Constants that can be printed without an InterpOne.
auto simplePrintConstant = [&]() -> bool {
switch (current) {
case OpFalse: case OpInt: case OpString: case OpTrue: case OpNull:
next();
return true;
default:
return false;
}
};
resetCursor();
////////////
// Simple property accessors.
resetCursor();
if (current == OpCheckThis) next();
if (cursor->op() == OpCGetM &&
cursor->immVec.locationCode() == LH &&
cursor->immVecM.size() == 1 &&
cursor->immVecM.front() == MPT &&
!mInstrHasUnknownOffsets(*cursor, func->cls())) {
next();
// Can't currently support cellManipRet because it's usually going
// to be CGetM-prediction, which will use the frame.
if (atRet()) {
return accept("simple property accessor");
}
}
/*
* Functions that set an object property to a simple cell value.
* E.g. something that does $this->foo = null;
*/
resetCursor();
if (current == OpCheckThis) next();
if (simpleCell()) {
if (cursor->op() == OpSetM &&
cursor->immVec.locationCode() == LH &&
cursor->immVecM.size() == 1 &&
cursor->immVecM.front() == MPT &&
!mInstrHasUnknownOffsets(*cursor, func->cls())) {
next();
if (nextIf(OpPopC) && simpleCell() && atRet()) {
return accept("simpleCell prop setter");
}
}
}
/*
* Continuation allocation functions.
*/
resetCursor();
if (current == OpCreateCont) {
if (func->numParams()) {
FTRACE(1, "CreateCont with {} args\n", func->numParams());
}
next();
if (atRet()) {
return accept("continuation creator");
}
}
/*
* Anything that just puts a value on the stack with no inputs, and
* then returns it, after possibly doing some comparison with
* another such thing.
*
* E.g. String; String; Same; RetC, or Null; RetC.
*/
resetCursor();
if (simpleCell() && cellManipRet()) {
return accept("simple returner");
}
// BareThis; InstanceOfD; RetC
resetCursor();
if (nextIf(OpBareThis) && nextIf(OpInstanceOfD) && atRet()) {
return accept("$this instanceof D");
}
// E.g. String; Print; PopC; Null; RetC
// Useful primarily for debugging.
resetCursor();
if (simplePrintConstant() && nextIf(OpPrint) && nextIf(OpPopC) &&
simpleCell() && cellManipRet()) {
return accept("constant printer");
}
return refuse("unknown kind of function");
}
void
Translator::translateFCall(const NormalizedInstruction& i) {
auto const numArgs = i.imm[0].u_IVA;
always_assert(!m_hhbcTrans->isInlining() && "curUnit and curFunc calls");
const Opcode* after = curUnit()->at(nextSrcKey(i).offset());
const Func* srcFunc = curFunc();
Offset returnBcOffset =
srcFunc->unit()->offsetOf(after - srcFunc->base());
/*
* If we have a calleeTrace, we're going to see if we should inline
* the call.
*/
if (i.calleeTrace) {
if (!i.calleeTrace->m_inliningFailed && !m_hhbcTrans->isInlining()) {
assert(shouldIRInline(curFunc(), i.funcd, *i.calleeTrace));
m_hhbcTrans->beginInlining(numArgs, i.funcd, returnBcOffset);
static const bool shapeStats = Stats::enabledAny() &&
getenv("HHVM_STATS_INLINESHAPE");
if (shapeStats) {
m_hhbcTrans->profileInlineFunctionShape(traceletShape(*i.calleeTrace));
}
for (auto* ni = i.calleeTrace->m_instrStream.first;
ni; ni = ni->next) {
m_curNI = ni;
SCOPE_EXIT { m_curNI = &i; };
translateInstr(*ni);
}
return;
}
static const auto enabled = Stats::enabledAny() &&
getenv("HHVM_STATS_FAILEDINL");
if (enabled) {
m_hhbcTrans->profileFunctionEntry("FailedCandidate");
m_hhbcTrans->profileFailedInlShape(traceletShape(*i.calleeTrace));
}
}
HHIR_EMIT(FCall, numArgs, returnBcOffset, i.funcd);
}
void
Translator::translateFCallArray(const NormalizedInstruction& i) {
const Offset pcOffset = i.offset();
SrcKey next = nextSrcKey(i);
const Offset after = next.offset();
HHIR_EMIT(FCallArray, pcOffset, after);
}
void
Translator::translateNewTuple(const NormalizedInstruction& i) {
int numArgs = i.imm[0].u_IVA;
HHIR_EMIT(NewTuple, numArgs);
}
void
Translator::translateNewCol(const NormalizedInstruction& i) {
HHIR_EMIT(NewCol, i.imm[0].u_IVA, i.imm[1].u_IVA);
}
void
Translator::translateColAddNewElemC(const NormalizedInstruction& i) {
HHIR_EMIT(ColAddNewElemC);
}
void
Translator::translateColAddElemC(const NormalizedInstruction& i) {
HHIR_EMIT(ColAddElemC);
}
void
Translator::translateStaticLocInit(const NormalizedInstruction& i) {
HHIR_EMIT(StaticLocInit, i.imm[0].u_IVA, i.imm[1].u_SA);
}
// check class hierarchy and fail if no match
void
Translator::translateVerifyParamType(const NormalizedInstruction& i) {
int param = i.imm[0].u_IVA;
HHIR_EMIT(VerifyParamType, param);
}
void
Translator::translateInstanceOfD(const NormalizedInstruction& i) {
HHIR_EMIT(InstanceOfD, (i.imm[0].u_SA));
}
void
Translator::translateIterInit(const NormalizedInstruction& i) {
HHIR_EMIT(IterInit,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA);
}
void
Translator::translateIterInitK(const NormalizedInstruction& i) {
HHIR_EMIT(IterInitK,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA,
i.imm[3].u_IVA);
}
void
Translator::translateIterNext(const NormalizedInstruction& i) {
HHIR_EMIT(IterNext,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA);
}
void
Translator::translateIterNextK(const NormalizedInstruction& i) {
HHIR_EMIT(IterNextK,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA,
i.imm[3].u_IVA);
}
void
Translator::translateMIterInit(const NormalizedInstruction& i) {
HHIR_EMIT(MIterInit,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA);
}
void
Translator::translateMIterInitK(const NormalizedInstruction& i) {
HHIR_EMIT(MIterInitK,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA,
i.imm[3].u_IVA);
}
void
Translator::translateMIterNext(const NormalizedInstruction& i) {
HHIR_EMIT(MIterNext,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA);
}
void
Translator::translateMIterNextK(const NormalizedInstruction& i) {
HHIR_EMIT(MIterNextK,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA,
i.imm[3].u_IVA);
}
void
Translator::translateWIterInit(const NormalizedInstruction& i) {
HHIR_EMIT(WIterInit,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA);
}
void
Translator::translateWIterInitK(const NormalizedInstruction& i) {
HHIR_EMIT(WIterInitK,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA,
i.imm[3].u_IVA);
}
void
Translator::translateWIterNext(const NormalizedInstruction& i) {
HHIR_EMIT(WIterNext,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA);
}
void
Translator::translateWIterNextK(const NormalizedInstruction& i) {
HHIR_EMIT(WIterNextK,
i.imm[0].u_IVA,
i.offset() + i.imm[1].u_BA,
i.imm[2].u_IVA,
i.imm[3].u_IVA);
}
void
Translator::translateIterFree(const NormalizedInstruction& i) {
HHIR_EMIT(IterFree, i.imm[0].u_IVA);
}
void
Translator::translateMIterFree(const NormalizedInstruction& i) {
HHIR_EMIT(MIterFree, i.imm[0].u_IVA);
}
void
Translator::translateIterBreak(const NormalizedInstruction& i) {
assert(i.breaksTracelet);
HHIR_EMIT(IterBreak, i.immVec, i.offset() + i.imm[1].u_BA, i.breaksTracelet,
i.noSurprise);
}
void
Translator::translateDecodeCufIter(const NormalizedInstruction& i) {
HHIR_EMIT(DecodeCufIter, i.imm[0].u_IVA, i.offset() + i.imm[1].u_BA);
}
void
Translator::translateCIterFree(const NormalizedInstruction& i) {
HHIR_EMIT(CIterFree, i.imm[0].u_IVA);
}
// All vector instructions are handled by one HhbcTranslator method.
#define MII(instr, ...) \
void Translator::translate##instr##M(const NormalizedInstruction& ni) { \
m_hhbcTrans->emitMInstr(ni); \
}
MINSTRS
MII(FPass)
#undef MII
void
Translator::translateInstrWork(const NormalizedInstruction& i) {
auto const op = i.op();
switch (op) {
#define CASE(iNm) \
case Op ## iNm: \
translate ## iNm(i); \
break;
#define TRANSLATE(name, inst) translate ## name(inst); break;
INSTRS
PSEUDOINSTR_DISPATCH(TRANSLATE)
#undef TRANSLATE
#undef CASE
default:
not_reached();
}
}
static bool isPop(Op opc) {
return opc == OpPopC || opc == OpPopR;
}
void
Translator::passPredictedAndInferredTypes(const NormalizedInstruction& i) {
if (!i.outStack || i.breaksTracelet) return;
NormalizedInstruction::OutputUse u = i.getOutputUsage(i.outStack);
JIT::Type jitType = JIT::Type::fromRuntimeType(i.outStack->rtt);
if (u == NormalizedInstruction::OutputUse::Inferred) {
TRACE(1, "irPassPredictedAndInferredTypes: output inferred as %s\n",
jitType.toString().c_str());
m_hhbcTrans->assertTypeStack(0, jitType);
} else if (u == NormalizedInstruction::OutputUse::Used && i.outputPredicted) {
// If the value was predicted statically by the front-end, it
// means that it's either the predicted type or null. In this
// case, if the predicted value is not ref-counted and it's simply
// going to be popped, then pass the information as an assertion
// that the type is not ref-counted. This avoid both generating a
// type check and dec-refing the value.
if (i.outputPredictionStatic && isPop(i.next->op()) &&
!jitType.isCounted()) {
TRACE(1, "irPassPredictedAndInferredTypes: output inferred as %s\n",
jitType.toString().c_str());
m_hhbcTrans->assertTypeStack(0, JIT::Type::Uncounted);
} else {
TRACE(1, "irPassPredictedAndInferredTypes: output predicted as %s\n",
jitType.toString().c_str());
m_hhbcTrans->checkTypeTopOfStack(jitType, i.next->offset());
}
}
}
/**
* Returns the number of cells that instruction i pops from the stack.
*/
static int getNumPopped(const NormalizedInstruction& i) {
return -getStackDelta(i)
// getStackDelta includes the output left on the stack, so discount it
+ (i.outStack ? 1 : 0)
// getStackDelta includes ActRec cells pushed on the stack, so discount them
+ (pushesActRec(i.op()) ? kNumActRecCells : 0);
}
void Translator::interpretInstr(const NormalizedInstruction& i) {
int poppedCells = getNumPopped(i);
FTRACE(5, "HHIR: BC Instr {} Popped = {}\n",
i.toString(), poppedCells);
if (i.changesPC) {
m_hhbcTrans->emitInterpOneCF(poppedCells);
} else {
m_hhbcTrans->emitInterpOne(i);
}
}
void Translator::translateInstr(const NormalizedInstruction& i) {
FTRACE(1, "\n{:-^60}\n", folly::format("translating {} with stack:\n{}",
i.toString(),
m_hhbcTrans->showStack()));
m_hhbcTrans->setBcOff(i.source.offset(),
i.breaksTracelet && !m_hhbcTrans->isInlining());
if (i.guardedThis) {
// Task #2067635: This should really generate an AssertThis
m_hhbcTrans->setThisAvailable();
}
if (moduleEnabled(HPHP::Trace::stats, 2)) {
m_hhbcTrans->emitIncStat(Stats::opcodeToIRPreStatCounter(i.op()), 1);
}
if (RuntimeOption::EnableInstructionCounts ||
moduleEnabled(HPHP::Trace::stats, 3)) {
// If the instruction takes a slow exit, the exit trace will
// decrement the post counter for that opcode.
m_hhbcTrans->emitIncStat(Stats::opcodeToIRPostStatCounter(i.op()),
1, true);
}
if (instrMustInterp(i) || i.interp) {
interpretInstr(i);
} else {
translateInstrWork(i);
}
passPredictedAndInferredTypes(i);
}
void TranslatorX64::irAssertType(const Location& l,
const RuntimeType& rtt) {
if (rtt.isVagueValue()) return;
switch (l.space) {
case Location::Stack: {
// tx64LocPhysicalOffset returns positive offsets for stack values,
// relative to rVmSp
uint32_t stackOffset = locPhysicalOffset(l);
m_hhbcTrans->assertTypeStack(stackOffset,
JIT::Type::fromRuntimeType(rtt));
break;
}
case Location::Local: // Stack frame's registers; offset == local register
m_hhbcTrans->assertTypeLocal(l.offset, JIT::Type::fromRuntimeType(rtt));
break;
case Location::Invalid: // Unknown location
HHIR_UNIMPLEMENTED(Invalid);
break;
case Location::Iter: // Stack frame's iterators
HHIR_UNIMPLEMENTED(AssertType_Iter);
break;
case Location::Litstr: // Literal string pseudo-location
HHIR_UNIMPLEMENTED(AssertType_Litstr);
break;
case Location::Litint: // Literal int pseudo-location
HHIR_UNIMPLEMENTED(AssertType_Litint);
break;
case Location::This:
HHIR_UNIMPLEMENTED(AssertType_This);
break;
}
}
void TranslatorX64::irEmitResolvedDeps(const ChangeMap& resolvedDeps) {
for (const auto dep : resolvedDeps) {
irAssertType(dep.first, dep.second->rtt);
}
}
TranslatorX64::TranslateResult
TranslatorX64::irTranslateTracelet(Tracelet& t) {
FTRACE(2, "attempting to translate tracelet:\n{}\n", t.toString());
const SrcKey &sk = t.m_sk;
SrcRec& srcRec = *getSrcRec(sk);
assert(srcRec.inProgressTailJumps().size() == 0);
try {
irEmitResolvedDeps(t.m_resolvedDeps);
emitGuardChecks(a, sk, t.m_dependencies, t.m_refDeps, srcRec);
dumpTranslationInfo(t, a.code.frontier);
// after guards, add a counter for the translation if requested
if (RuntimeOption::EvalJitTransCounters) {
m_hhbcTrans->emitIncTransCounter();
}
emitRB(a, RBTypeTraceletBody, t.m_sk);
Stats::emitInc(a, Stats::Instr_TC, t.m_numOpcodes);
// Profiling on function entry.
if (m_curTrace->m_sk.offset() == curFunc()->base()) {
m_hhbcTrans->profileFunctionEntry("Normal");
}
/*
* Profiling on the shapes of tracelets that are whole functions.
* (These are the things we might consider trying to support
* inlining.)
*/
[&]{
static const bool enabled = Stats::enabledAny() &&
getenv("HHVM_STATS_FUNCSHAPE");
if (!enabled) return;
if (m_curTrace->m_sk.offset() != curFunc()->base()) return;
if (auto last = m_curTrace->m_instrStream.last) {
if (last->op() != OpRetC && last->op() != OpRetV) {
return;
}
}
m_hhbcTrans->profileSmallFunctionShape(traceletShape(*m_curTrace));
}();
// Translate each instruction in the tracelet
for (auto* ni = t.m_instrStream.first; ni && !m_hhbcTrans->hasExit();
ni = ni->next) {
try {
SKTRACE(1, ni->source, "HHIR: translateInstr\n");
Nuller<NormalizedInstruction> niNuller(&m_curNI);
m_curNI = ni;
translateInstr(*ni);
} catch (JIT::FailedIRGen& fcg) {
always_assert(!ni->interp);
ni->interp = true;
return Retry;
}
assert(ni->source.offset() >= curFunc()->base());
// We sometimes leave the tail of a truncated tracelet in place to aid
// analysis, but breaksTracelet is authoritative.
if (ni->breaksTracelet) break;
}
traceEnd();
try {
traceCodeGen();
TRACE(1, "HHIR: SUCCEEDED to generate code for Translation %d\n\n\n",
getCurrentTransID());
return Success;
} catch (JIT::FailedCodeGen& fcg) {
// Code-gen failed. Search for the bytecode instruction that caused the
// problem, flag it to be interpreted, and retranslate the tracelet.
for (auto ni = t.m_instrStream.first; ni; ni = ni->next) {
if (ni->source.offset() == fcg.bcOff) {
always_assert(!ni->interp);
ni->interp = true;
TRACE(1, "HHIR: RETRY Translation %d: will interpOne BC instr %s "
"after failing to code-gen \n\n",
getCurrentTransID(), ni->toString().c_str());
return Retry;
}
}
throw fcg;
}
} catch (JIT::FailedCodeGen& fcg) {
TRACE(1, "HHIR: FAILED to generate code for Translation %d "
"@ %s:%d (%s)\n", getCurrentTransID(),
fcg.file, fcg.line, fcg.func);
// HHIR:TODO Remove extra TRACE and adjust tools
TRACE(1, "HHIR: FAILED to translate @ %s:%d (%s)\n",
fcg.file, fcg.line, fcg.func);
} catch (JIT::FailedIRGen& x) {
TRACE(1, "HHIR: FAILED to translate @ %s:%d (%s)\n",
x.file, x.line, x.func);
} catch (const FailedAssertion& fa) {
fa.print();
StackTraceNoHeap::AddExtraLogging(
"Assertion failure",
folly::format("{}\n\nActive Trace:\n{}\n",
fa.summary, m_hhbcTrans->trace()->toString()).str());
abort();
} catch (const std::exception& e) {
FTRACE(1, "HHIR: FAILED with exception: {}\n", e.what());
assert(0);
}
return Failure;
}
void Translator::traceStart(Offset bcStartOffset) {
assert(!m_irFactory);
Cell* fp = vmfp();
if (curFunc()->isGenerator()) {
fp = (Cell*)Stack::generatorStackBase((ActRec*)fp);
}
FTRACE(1, "{}{:-^40}{}\n",
color(ANSI_COLOR_BLACK, ANSI_BGCOLOR_GREEN),
" HHIR during translation ",
color(ANSI_COLOR_END));
m_irFactory.reset(new JIT::IRFactory());
m_hhbcTrans.reset(new JIT::HhbcTranslator(
*m_irFactory, bcStartOffset, fp - vmsp(), curFunc()));
}
void Translator::traceEnd() {
m_hhbcTrans->end();
FTRACE(1, "{}{:-^40}{}\n",
color(ANSI_COLOR_BLACK, ANSI_BGCOLOR_GREEN),
"",
color(ANSI_COLOR_END));
}
void TranslatorX64::traceCodeGen() {
using namespace JIT;
HPHP::JIT::IRTrace* trace = m_hhbcTrans->trace();
auto finishPass = [&](const char* msg, int level,
const RegAllocInfo* regs,
const LifetimeInfo* lifetime) {
dumpTrace(level, trace, msg, regs, lifetime);
assert(checkCfg(trace, *m_irFactory));
};
finishPass(" after initial translation ", kIRLevel, nullptr, nullptr);
optimizeTrace(trace, m_hhbcTrans->traceBuilder());
finishPass(" after optimizing ", kOptLevel, nullptr, nullptr);
auto* factory = m_irFactory.get();
recordBCInstr(OpTraceletGuard, a, a.code.frontier);
if (dumpIREnabled() || RuntimeOption::EvalJitCompareHHIR) {
LifetimeInfo lifetime(factory);
RegAllocInfo regs = allocRegsForTrace(trace, factory, &lifetime);
finishPass(" after reg alloc ", kRegAllocLevel, &regs, &lifetime);
assert(checkRegisters(trace, *factory, regs));
AsmInfo ai(factory);
genCodeForTrace(trace, a, astubs, factory, &m_bcMap, this, regs,
&lifetime, &ai);
if (RuntimeOption::EvalJitCompareHHIR) {
std::ostringstream out;
dumpTraceImpl(trace, out, &regs, &lifetime, &ai);
} else {
dumpTrace(kCodeGenLevel, trace, " after code gen ", &regs,
&lifetime, &ai);
}
} else {
RegAllocInfo regs = allocRegsForTrace(trace, factory);
finishPass(" after reg alloc ", kRegAllocLevel, nullptr, nullptr);
assert(checkRegisters(trace, *factory, regs));
genCodeForTrace(trace, a, astubs, factory, &m_bcMap, this, regs);
}
m_numHHIRTrans++;
}
void Translator::traceFree() {
FTRACE(1, "HHIR free: arena size: {}\n",
m_irFactory->arena().size());
m_hhbcTrans.reset();
m_irFactory.reset();
}
}}