d078c2b895
This diff adds an initial framework for profile-guided optimizations
in the JIT. Two new translation modes are added, effectively creating
a multi-gear JIT:
1. Profile: this collects profiling data, which includes execution
counters and type information.
2. Optimize: this uses data collected by Profiling translations to
produce optimized translations.
Right now, the Optimize gear is solely used to produce larger
compilation regions (traces), which increase the scope of
optimizations exposed to the JIT (compared to tracelets). This is
still work in progress, and it's disabled by default.
This diff also fixes a number of bugs exposed by trace regions, gets
tools/reduce working again, and makes a number of improvements to
tc-print related to the new translation modes.
1720 linhas
47 KiB
C++
1720 linhas
47 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/ir-translator.h"
|
|
|
|
#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/target-cache.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/check.h"
|
|
#include "hphp/runtime/vm/jit/code-gen.h"
|
|
#include "hphp/runtime/vm/jit/hhbc-translator.h"
|
|
#include "hphp/runtime/vm/jit/ir.h"
|
|
#include "hphp/runtime/vm/jit/ir-translator.h"
|
|
#include "hphp/runtime/vm/jit/linear-scan.h"
|
|
#include "hphp/runtime/vm/jit/opt.h"
|
|
#include "hphp/runtime/vm/jit/print.h"
|
|
|
|
// Include last to localize effects to this file
|
|
#include "hphp/util/assert_throw.h"
|
|
|
|
namespace HPHP {
|
|
namespace JIT {
|
|
|
|
using namespace reg;
|
|
using namespace Util;
|
|
using namespace Trace;
|
|
using std::max;
|
|
|
|
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_EMIT(op, ...) \
|
|
do { \
|
|
m_hhbcTrans.emit ## op(__VA_ARGS__); \
|
|
return; \
|
|
} while (0)
|
|
|
|
#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)
|
|
|
|
namespace {
|
|
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;
|
|
}
|
|
}
|
|
|
|
IRTranslator::IRTranslator(Offset bcOff, Offset spOff, const Func* curFunc)
|
|
: m_hhbcTrans(bcOff, spOff, curFunc)
|
|
{
|
|
}
|
|
|
|
void IRTranslator::checkType(const Transl::Location& l,
|
|
const Transl::RuntimeType& rtt) {
|
|
// We can get invalid inputs as a side effect of reading invalid
|
|
// items out of BBs we truncate; they don't need guards.
|
|
if (rtt.isVagueValue() || l.isThis()) return;
|
|
|
|
using Transl::Location;
|
|
switch (l.space) {
|
|
case Location::Stack: {
|
|
uint32_t stackOffset = locPhysicalOffset(l);
|
|
JIT::Type type = JIT::Type::fromRuntimeType(rtt);
|
|
if (type.subtypeOf(Type::Cls)) {
|
|
m_hhbcTrans.assertTypeStack(stackOffset, type);
|
|
} else {
|
|
m_hhbcTrans.guardTypeStack(stackOffset, type);
|
|
}
|
|
break;
|
|
}
|
|
case Location::Local:
|
|
m_hhbcTrans.guardTypeLocal(l.offset, Type::fromRuntimeType(rtt));
|
|
break;
|
|
|
|
case Location::Iter:
|
|
case Location::Invalid:
|
|
case Location::Litstr:
|
|
case Location::Litint:
|
|
case Location::This:
|
|
not_reached();
|
|
}
|
|
}
|
|
|
|
void IRTranslator::assertType(const Transl::Location& l,
|
|
const Transl::RuntimeType& rtt) {
|
|
if (rtt.isVagueValue()) return;
|
|
|
|
using Transl::Location;
|
|
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, Type::fromRuntimeType(rtt));
|
|
break;
|
|
}
|
|
case Location::Local: // Stack frame's registers; offset == local register
|
|
m_hhbcTrans.assertTypeLocal(l.offset, 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
|
|
IRTranslator::translateMod(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Mod);
|
|
}
|
|
|
|
|
|
void
|
|
IRTranslator::translateDiv(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Div);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translateSameOp(const NormalizedInstruction& i) {
|
|
auto const op = i.op();
|
|
assert(op == Op::Same || op == Op::NSame);
|
|
if (op == Op::Same) {
|
|
HHIR_EMIT(Same);
|
|
} else {
|
|
HHIR_EMIT(NSame);
|
|
}
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateEqOp(const NormalizedInstruction& i) {
|
|
auto const op = i.op();
|
|
assert(op == Op::Eq || op == Op::Neq);
|
|
if (op == Op::Eq) {
|
|
HHIR_EMIT(Eq);
|
|
} else {
|
|
HHIR_EMIT(Neq);
|
|
}
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateLtGtOp(const NormalizedInstruction& i) {
|
|
auto const op = i.op();
|
|
assert(op == Op::Lt || op == Op::Lte || op == Op::Gt || op == Op::Gte);
|
|
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 Op::Lt : HHIR_EMIT(Lt);
|
|
case Op::Lte : HHIR_EMIT(Lte);
|
|
case Op::Gt : HHIR_EMIT(Gt);
|
|
case Op::Gte : HHIR_EMIT(Gte);
|
|
default : HHIR_UNIMPLEMENTED(LtGtOp);
|
|
}
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translateBranchOp(const NormalizedInstruction& i) {
|
|
auto const op = i.op();
|
|
assert(op == OpJmpZ || op == OpJmpNZ);
|
|
|
|
Offset takenOffset = i.offset() + i.imm[0].u_BA;
|
|
Offset fallthruOffset = i.offset() + instrLen((Op*)(i.pc()));
|
|
assert(i.breaksTracelet ||
|
|
i.nextOffset == takenOffset ||
|
|
i.nextOffset == fallthruOffset);
|
|
|
|
if (i.breaksTracelet || i.nextOffset == fallthruOffset) {
|
|
if (op == OpJmpZ) {
|
|
HHIR_EMIT(JmpZ, takenOffset);
|
|
} else {
|
|
HHIR_EMIT(JmpNZ, takenOffset);
|
|
}
|
|
return;
|
|
}
|
|
assert(i.nextOffset == takenOffset);
|
|
// invert the branch
|
|
if (op == OpJmpZ) {
|
|
HHIR_EMIT(JmpNZ, fallthruOffset);
|
|
} else {
|
|
HHIR_EMIT(JmpZ, fallthruOffset);
|
|
}
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translateCGetL2(const NormalizedInstruction& ni) {
|
|
const int locIdx = 1;
|
|
|
|
HHIR_EMIT(CGetL2, ni.inputs[locIdx]->location.offset);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateVGetL(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(VGetL, i.inputs[0]->location.offset);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translatePopC(const NormalizedInstruction& i) {
|
|
assert(i.inputs.size() == 1);
|
|
|
|
if (i.inputs[0]->rtt.isVagueValue()) {
|
|
HHIR_EMIT(PopR);
|
|
} else {
|
|
HHIR_EMIT(PopC);
|
|
}
|
|
}
|
|
|
|
void
|
|
IRTranslator::translatePopV(const NormalizedInstruction& i) {
|
|
assert(i.inputs[0]->rtt.isVagueValue() || i.inputs[0]->isRef());
|
|
HHIR_EMIT(PopV);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translatePopR(const NormalizedInstruction& i) {
|
|
translatePopC(i);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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.assertTypeStack(0, JIT::Type::Cell);
|
|
} else {
|
|
HHIR_EMIT(UnboxR);
|
|
}
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateNull(const NormalizedInstruction& i) {
|
|
assert(i.inputs.size() == 0);
|
|
|
|
HHIR_EMIT(Null);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateNullUninit(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(NullUninit);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateTrue(const NormalizedInstruction& i) {
|
|
assert(i.inputs.size() == 0);
|
|
|
|
HHIR_EMIT(True);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateFalse(const NormalizedInstruction& i) {
|
|
assert(i.inputs.size() == 0);
|
|
|
|
HHIR_EMIT(False);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateInt(const NormalizedInstruction& i) {
|
|
assert(i.inputs.size() == 0);
|
|
|
|
HHIR_EMIT(Int, i.imm[0].u_I64A);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateDouble(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Double, i.imm[0].u_DA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateString(const NormalizedInstruction& i) {
|
|
assert(i.inputs.size() == 0);
|
|
|
|
HHIR_EMIT(String, (i.imm[0].u_SA));
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateArray(const NormalizedInstruction& i) {
|
|
assert(i.inputs.size() == 0);
|
|
|
|
HHIR_EMIT(Array, i.imm[0].u_AA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateNewArray(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(NewArray, i.imm[0].u_IVA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateNop(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Nop);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateAddElemC(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(AddElemC);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translateCns(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Cns, i.imm[0].u_SA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateDefCns(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(DefCns, (i.imm[0].u_SA));
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateClsCnsD(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ClsCnsD, (i.imm[0].u_SA), (i.imm[1].u_SA), i.outPred);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateConcat(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Concat);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translateXor(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Xor);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateNot(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Not);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateBitNot(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(BitNot);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateShl(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Shl);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateShr(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Shr);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateCastInt(const NormalizedInstruction& i) {
|
|
assert(i.inputs.size() == 1);
|
|
|
|
HHIR_EMIT(CastInt);
|
|
/* nop */
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateCastArray(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(CastArray);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateCastObject(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(CastObject);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateCastDouble(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(CastDouble);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateCastString(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(CastString);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translatePrint(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Print);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateJmp(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Jmp, i.offset() + i.imm[0].u_BA, i.breaksTracelet, i.noSurprise);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateSwitch(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Switch, i.immVec, i.imm[1].u_I64A, i.imm[2].u_IVA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translateRetC(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(RetC, i.inlineReturn);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateRetV(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(RetV, i.inlineReturn);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateNativeImpl(const NormalizedInstruction& ni) {
|
|
HHIR_EMIT(NativeImpl);
|
|
}
|
|
|
|
// emitClsLocalIndex --
|
|
// emitStringToClass --
|
|
// emitStringToKnownClass --
|
|
// emitObjToClass --
|
|
// emitClsAndPals --
|
|
// Helpers for AGetC/AGetL.
|
|
|
|
const int kEmitClsLocalIdx = 0;
|
|
|
|
void IRTranslator::translateAGetC(const NormalizedInstruction& ni) {
|
|
const StringData* clsName =
|
|
ni.inputs[kEmitClsLocalIdx]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(AGetC, clsName);
|
|
}
|
|
|
|
void IRTranslator::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 IRTranslator::translateSelf(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Self);
|
|
}
|
|
|
|
void IRTranslator::translateParent(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Parent);
|
|
}
|
|
|
|
void IRTranslator::translateDup(const NormalizedInstruction& ni) {
|
|
HHIR_EMIT(Dup);
|
|
}
|
|
|
|
void IRTranslator::translateCreateCont(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(CreateCont, i.imm[0].u_SA);
|
|
}
|
|
|
|
void IRTranslator::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 IRTranslator::translateUnpackCont(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(UnpackCont);
|
|
}
|
|
|
|
void IRTranslator::translateContSuspend(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContSuspend, i.imm[0].u_IVA);
|
|
}
|
|
|
|
void IRTranslator::translateContSuspendK(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContSuspendK, i.imm[0].u_IVA);
|
|
}
|
|
|
|
void IRTranslator::translateContRetC(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContRetC);
|
|
}
|
|
|
|
void IRTranslator::translateContCheck(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContCheck, i.imm[0].u_IVA);
|
|
}
|
|
|
|
void IRTranslator::translateContRaise(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContRaise);
|
|
}
|
|
|
|
void IRTranslator::translateContValid(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContValid);
|
|
}
|
|
|
|
void IRTranslator::translateContKey(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContKey);
|
|
}
|
|
|
|
void IRTranslator::translateContCurrent(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContCurrent);
|
|
}
|
|
|
|
void IRTranslator::translateContStopped(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContStopped);
|
|
}
|
|
|
|
void IRTranslator::translateContHandle(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ContHandle);
|
|
}
|
|
|
|
void IRTranslator::translateStrlen(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(Strlen);
|
|
}
|
|
|
|
void IRTranslator::translateIncStat(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(IncStat, i.imm[0].u_IVA, i.imm[1].u_IVA);
|
|
}
|
|
|
|
void IRTranslator::translateArrayIdx(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ArrayIdx);
|
|
}
|
|
|
|
void IRTranslator::translateClassExists(const NormalizedInstruction& i) {
|
|
const StringData* clsName = i.inputs[1]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(ClassExists, clsName);
|
|
}
|
|
|
|
void IRTranslator::translateInterfaceExists(const NormalizedInstruction& i) {
|
|
const StringData* ifaceName = i.inputs[1]->rtt.valueStringOrNull();
|
|
|
|
HHIR_EMIT(InterfaceExists, ifaceName);
|
|
}
|
|
|
|
void IRTranslator::translateTraitExists(const NormalizedInstruction& i) {
|
|
const StringData* traitName = i.inputs[1]->rtt.valueStringOrNull();
|
|
|
|
HHIR_EMIT(TraitExists, traitName);
|
|
}
|
|
|
|
void IRTranslator::translateVGetS(const NormalizedInstruction& i) {
|
|
const int kPropNameIdx = 1;
|
|
const StringData* propName = i.inputs[kPropNameIdx]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(VGetS, propName);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateVGetG(const NormalizedInstruction& i) {
|
|
const StringData* name = i.inputs[0]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(VGetG, name);
|
|
}
|
|
|
|
void IRTranslator::translateBindS(const NormalizedInstruction& i) {
|
|
const int kPropIdx = 2;
|
|
const StringData* propName = i.inputs[kPropIdx]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(BindS, propName);
|
|
}
|
|
|
|
void IRTranslator::translateEmptyS(const NormalizedInstruction& i) {
|
|
const int kPropNameIdx = 1;
|
|
const StringData* propName = i.inputs[kPropNameIdx]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(EmptyS, propName);
|
|
}
|
|
|
|
void IRTranslator::translateEmptyG(const NormalizedInstruction& i) {
|
|
const StringData* gblName = i.inputs[0]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(EmptyG, gblName);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateIssetS(const NormalizedInstruction& i) {
|
|
const int kPropNameIdx = 1;
|
|
const StringData* propName = i.inputs[kPropNameIdx]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(IssetS, propName);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateIssetG(const NormalizedInstruction& i) {
|
|
const StringData* gblName = i.inputs[0]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(IssetG, gblName);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateUnsetG(const NormalizedInstruction& i) {
|
|
const StringData* gblName = i.inputs[0]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(UnsetG, gblName);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateUnsetN(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(UnsetN);
|
|
}
|
|
|
|
void IRTranslator::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 IRTranslator::translateSetS(const NormalizedInstruction& i) {
|
|
const int kPropIdx = 2;
|
|
const StringData* propName = i.inputs[kPropIdx]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(SetS, propName);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateCGetG(const NormalizedInstruction& i) {
|
|
const StringData* name = i.inputs[0]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(CGetG, name, getInferredOrPredictedType(i), isInferredType(i));
|
|
}
|
|
|
|
void IRTranslator::translateSetG(const NormalizedInstruction& i) {
|
|
const StringData* name = i.inputs[1]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(SetG, name);
|
|
}
|
|
|
|
void IRTranslator::translateBindG(const NormalizedInstruction& i) {
|
|
const StringData* name = i.inputs[1]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(BindG, name);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateLateBoundCls(const NormalizedInstruction&i) {
|
|
HHIR_EMIT(LateBoundCls);
|
|
}
|
|
|
|
void IRTranslator::translateFPassL(const NormalizedInstruction& ni) {
|
|
if (ni.preppedByRef) {
|
|
translateVGetL(ni);
|
|
} else {
|
|
translateCGetL(ni);
|
|
}
|
|
}
|
|
|
|
void IRTranslator::translateFPassS(const NormalizedInstruction& ni) {
|
|
if (ni.preppedByRef) {
|
|
translateVGetS(ni);
|
|
} else {
|
|
translateCGetS(ni);
|
|
}
|
|
}
|
|
|
|
void IRTranslator::translateFPassG(const NormalizedInstruction& ni) {
|
|
if (ni.preppedByRef) {
|
|
translateVGetG(ni);
|
|
} else {
|
|
translateCGetG(ni);
|
|
}
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translateAKExists(const NormalizedInstruction& ni) {
|
|
HHIR_EMIT(AKExists);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateSetOpL(const NormalizedInstruction& i) {
|
|
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
|
|
IRTranslator::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_EMIT(IncDecL, pre, inc, inputs[0]->location.offset);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateUnsetL(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(UnsetL, i.inputs[0]->location.offset);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateReqDoc(const NormalizedInstruction& i) {
|
|
const StringData* name = i.inputs[0]->rtt.valueStringOrNull();
|
|
HHIR_EMIT(ReqDoc, name);
|
|
}
|
|
|
|
void IRTranslator::translateDefCls(const NormalizedInstruction& i) {
|
|
int cid = i.imm[0].u_IVA;
|
|
HHIR_EMIT(DefCls, cid, i.source.offset());
|
|
}
|
|
|
|
void IRTranslator::translateDefFunc(const NormalizedInstruction& i) {
|
|
int fid = i.imm[0].u_IVA;
|
|
HHIR_EMIT(DefFunc, fid);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateFPushFunc(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(FPushFunc, (i.imm[0].u_IVA));
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateFPushClsMethodD(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(FPushClsMethodD,
|
|
(i.imm[0].u_IVA),
|
|
(i.imm[1].u_SA),
|
|
(i.imm[2].u_SA));
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::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 IRTranslator::translateFPushCtor(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(FPushCtor, (i.imm[0].u_IVA));
|
|
}
|
|
|
|
void IRTranslator::translateFPushCtorD(const NormalizedInstruction& i) {
|
|
|
|
HHIR_EMIT(FPushCtorD, (i.imm[0].u_IVA), (i.imm[1].u_SA));
|
|
}
|
|
|
|
void IRTranslator::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
|
|
IRTranslator::translateThis(const NormalizedInstruction &i) {
|
|
HHIR_EMIT(This);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateBareThis(const NormalizedInstruction &i) {
|
|
HHIR_EMIT(BareThis, (i.imm[0].u_OA));
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateCheckThis(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(CheckThis);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateInitThisLoc(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(InitThisLoc, i.imm[0].u_HA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateFPushFuncD(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(FPushFuncD, (i.imm[0].u_IVA), (i.imm[1].u_SA));
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateFPushFuncU(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(FPushFuncU, i.imm[0].u_IVA, i.imm[1].u_SA, i.imm[2].u_SA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translateFPassV(const NormalizedInstruction& i) {
|
|
if (i.preppedByRef || i.noOp) {
|
|
TRACE(1, "HHIR: translateFPassV: noOp\n");
|
|
return;
|
|
}
|
|
HHIR_EMIT(FPassV);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
IRTranslator::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 Op::Add: case Op::Sub: case Op::Mul: case Op::Div: case Op::Mod:
|
|
case Op::Xor: case Op::Not: case Op::Same: case Op::NSame: case Op::Eq:
|
|
case Op::Neq: case Op::Lt: case Op::Lte: case Op::Gt: case Op::Gte:
|
|
case Op::BitAnd: case Op::BitOr: case Op::BitXor: case Op::BitNot:
|
|
case Op::Shl: case Op::Shr:
|
|
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(Op::Not)) 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
|
|
IRTranslator::translateFCall(const NormalizedInstruction& i) {
|
|
auto const numArgs = i.imm[0].u_IVA;
|
|
|
|
always_assert(!m_hhbcTrans.isInlining() && "curUnit and curFunc calls");
|
|
const PC 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) {
|
|
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
|
|
IRTranslator::translateFCallArray(const NormalizedInstruction& i) {
|
|
const Offset pcOffset = i.offset();
|
|
SrcKey next = nextSrcKey(i);
|
|
const Offset after = next.offset();
|
|
|
|
HHIR_EMIT(FCallArray, pcOffset, after);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateNewTuple(const NormalizedInstruction& i) {
|
|
int numArgs = i.imm[0].u_IVA;
|
|
HHIR_EMIT(NewTuple, numArgs);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateNewCol(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(NewCol, i.imm[0].u_IVA, i.imm[1].u_IVA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateColAddNewElemC(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ColAddNewElemC);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateColAddElemC(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(ColAddElemC);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::translateVerifyParamType(const NormalizedInstruction& i) {
|
|
int param = i.imm[0].u_IVA;
|
|
HHIR_EMIT(VerifyParamType, param);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateInstanceOfD(const NormalizedInstruction& i) {
|
|
HHIR_EMIT(InstanceOfD, (i.imm[0].u_SA));
|
|
}
|
|
|
|
/*
|
|
* This function returns the offset of instruction i's branch target.
|
|
* This is normally the offset corresponding to the branch being
|
|
* taken. However, if i does not break a trace and it's followed in
|
|
* the trace by the instruction in the taken branch, then this
|
|
* function returns the offset of the i's fall-through instruction.
|
|
* In that case, the invertCond output argument is set to true;
|
|
* otherwise it's set to false.
|
|
*/
|
|
static Offset getBranchTarget(const NormalizedInstruction& i,
|
|
bool& invertCond) {
|
|
assert(instrJumpOffset((Op*)(i.pc())) != nullptr);
|
|
Offset targetOffset = i.offset() + i.imm[1].u_BA;
|
|
invertCond = false;
|
|
|
|
if (!i.breaksTracelet && i.nextOffset == targetOffset) {
|
|
invertCond = true;
|
|
Offset fallthruOffset = i.offset() + instrLen((Op*)i.pc());
|
|
targetOffset = fallthruOffset;
|
|
}
|
|
|
|
return targetOffset;
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateIterInit(const NormalizedInstruction& i) {
|
|
bool invertCond = false;
|
|
Offset targetOffset = getBranchTarget(i, invertCond);
|
|
|
|
HHIR_EMIT(IterInit,
|
|
i.imm[0].u_IVA,
|
|
targetOffset,
|
|
i.imm[2].u_IVA,
|
|
invertCond);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateIterInitK(const NormalizedInstruction& i) {
|
|
bool invertCond = false;
|
|
Offset targetOffset = getBranchTarget(i, invertCond);
|
|
|
|
HHIR_EMIT(IterInitK,
|
|
i.imm[0].u_IVA,
|
|
targetOffset,
|
|
i.imm[2].u_IVA,
|
|
i.imm[3].u_IVA,
|
|
invertCond);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateIterNext(const NormalizedInstruction& i) {
|
|
bool invertCond = false;
|
|
Offset targetOffset = getBranchTarget(i, invertCond);
|
|
|
|
HHIR_EMIT(IterNext,
|
|
i.imm[0].u_IVA,
|
|
targetOffset,
|
|
i.imm[2].u_IVA,
|
|
invertCond);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateIterNextK(const NormalizedInstruction& i) {
|
|
bool invertCond = false;
|
|
Offset targetOffset = getBranchTarget(i, invertCond);
|
|
|
|
HHIR_EMIT(IterNextK,
|
|
i.imm[0].u_IVA,
|
|
targetOffset,
|
|
i.imm[2].u_IVA,
|
|
i.imm[3].u_IVA,
|
|
invertCond);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
IRTranslator::translateWIterInit(const NormalizedInstruction& i) {
|
|
bool invertCond = false;
|
|
Offset targetOffset = getBranchTarget(i, invertCond);
|
|
|
|
HHIR_EMIT(WIterInit,
|
|
i.imm[0].u_IVA,
|
|
targetOffset,
|
|
i.imm[2].u_IVA,
|
|
invertCond);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateWIterInitK(const NormalizedInstruction& i) {
|
|
bool invertCond = false;
|
|
Offset targetOffset = getBranchTarget(i, invertCond);
|
|
|
|
HHIR_EMIT(WIterInitK,
|
|
i.imm[0].u_IVA,
|
|
targetOffset,
|
|
i.imm[2].u_IVA,
|
|
i.imm[3].u_IVA,
|
|
invertCond);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateWIterNext(const NormalizedInstruction& i) {
|
|
bool invertCond = false;
|
|
Offset targetOffset = getBranchTarget(i, invertCond);
|
|
|
|
HHIR_EMIT(WIterNext,
|
|
i.imm[0].u_IVA,
|
|
targetOffset,
|
|
i.imm[2].u_IVA,
|
|
invertCond);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateWIterNextK(const NormalizedInstruction& i) {
|
|
bool invertCond = false;
|
|
Offset targetOffset = getBranchTarget(i, invertCond);
|
|
|
|
HHIR_EMIT(WIterNextK,
|
|
i.imm[0].u_IVA,
|
|
targetOffset,
|
|
i.imm[2].u_IVA,
|
|
i.imm[3].u_IVA,
|
|
invertCond);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateIterFree(const NormalizedInstruction& i) {
|
|
|
|
HHIR_EMIT(IterFree, i.imm[0].u_IVA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateMIterFree(const NormalizedInstruction& i) {
|
|
|
|
HHIR_EMIT(MIterFree, i.imm[0].u_IVA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateIterBreak(const NormalizedInstruction& i) {
|
|
|
|
assert(i.breaksTracelet);
|
|
HHIR_EMIT(IterBreak, i.immVec, i.offset() + i.imm[1].u_BA, i.breaksTracelet,
|
|
i.noSurprise);
|
|
}
|
|
|
|
void
|
|
IRTranslator::translateDecodeCufIter(const NormalizedInstruction& i) {
|
|
|
|
HHIR_EMIT(DecodeCufIter, i.imm[0].u_IVA, i.offset() + i.imm[1].u_BA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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 IRTranslator::translate##instr##M(const NormalizedInstruction& ni) { \
|
|
m_hhbcTrans.emitMInstr(ni); \
|
|
}
|
|
MINSTRS
|
|
MII(FPass)
|
|
#undef MII
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::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 IRTranslator::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 IRTranslator::translateInstr(const NormalizedInstruction& i) {
|
|
FTRACE(1, "\n{:-^60}\n", folly::format("translating {} with stack:\n{}",
|
|
i.toString(),
|
|
m_hhbcTrans.showStack()));
|
|
// When profiling, we disable type predictions to avoid side exits
|
|
assert(Transl::tx64->mode() != TransProfile || !i.outputPredicted);
|
|
|
|
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);
|
|
}
|
|
|
|
}}
|