0a111314bd
shift left and right in codegen
1640 linhas
45 KiB
C++
1640 linhas
45 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:
|
|
m_hhbcTrans.guardTypeStack(locPhysicalOffset(l),
|
|
Type::fromRuntimeType(rtt));
|
|
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::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);
|
|
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
|
|
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));
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
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) {
|
|
HHIR_EMIT(WIterInit,
|
|
i.imm[0].u_IVA,
|
|
i.offset() + i.imm[1].u_BA,
|
|
i.imm[2].u_IVA);
|
|
}
|
|
|
|
void
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
IRTranslator::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
|
|
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()));
|
|
|
|
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);
|
|
}
|
|
|
|
}}
|