hphp/runtime/vm/jit/translator.cpp (1,061 lines of code) (raw):

/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/vm/jit/translator.h" #include <cinttypes> #include <assert.h> #include <stdint.h> #include <algorithm> #include <memory> #include <string> #include <utility> #include <vector> #include <folly/Conv.h> #include <folly/MapUtil.h> #include "hphp/util/arch.h" #include "hphp/util/ringbuffer.h" #include "hphp/util/timer.h" #include "hphp/util/trace.h" #include "hphp/runtime/base/repo-auth-type-codec.h" #include "hphp/runtime/base/runtime-option.h" #include "hphp/runtime/base/stats.h" #include "hphp/runtime/base/unit-cache.h" #include "hphp/runtime/ext/generator/ext_generator.h" #include "hphp/runtime/vm/bc-pattern.h" #include "hphp/runtime/vm/bytecode.h" #include "hphp/runtime/vm/hhbc-codec.h" #include "hphp/runtime/vm/hhbc.h" #include "hphp/runtime/vm/method-lookup.h" #include "hphp/runtime/vm/runtime.h" #include "hphp/runtime/vm/treadmill.h" #include "hphp/runtime/vm/type-profile.h" #include "hphp/runtime/vm/jit/inlining-decider.h" #include "hphp/runtime/vm/jit/ir-unit.h" #include "hphp/runtime/vm/jit/irgen-basic.h" #include "hphp/runtime/vm/jit/irgen-bespoke.h" #include "hphp/runtime/vm/jit/irgen-control.h" #include "hphp/runtime/vm/jit/irgen-exit.h" #include "hphp/runtime/vm/jit/irgen-func-prologue.h" #include "hphp/runtime/vm/jit/irgen-internal.h" #include "hphp/runtime/vm/jit/irgen-interpone.h" #include "hphp/runtime/vm/jit/irgen.h" #include "hphp/runtime/vm/jit/normalized-instruction.h" #include "hphp/runtime/vm/jit/print.h" #include "hphp/runtime/vm/jit/prof-data.h" #include "hphp/runtime/vm/jit/punt.h" #include "hphp/runtime/vm/jit/region-selection.h" #include "hphp/runtime/vm/jit/translate-region.h" #include "hphp/runtime/vm/jit/translator-inline.h" #include "hphp/runtime/vm/jit/type.h" TRACE_SET_MOD(trans); namespace HPHP { namespace jit { /////////////////////////////////////////////////////////////////////////////// /* * NB: this opcode structure is sparse; it cannot just be indexed by * opcode. */ using namespace InstrFlags; static const struct { Op op; InstrInfo info; } instrInfoSparse [] = { // Op Inputs Outputs OutputTypes // -- ------ ------- ----------- /*** 1. Basic instructions ***/ { OpPopC, {Stack1| DontGuardStack1, None, OutNone }}, { OpPopU, {Stack1| DontGuardStack1, None, OutNone }}, { OpPopU2, {StackTop2| DontGuardAny, Stack1, OutSameAsInput1 }}, { OpPopL, {Stack1|Local, Local, OutNone }}, { OpDup, {Stack1, StackTop2, OutSameAsInput1 }}, /*** 2. Literal and constant instructions ***/ { OpNull, {None, Stack1, OutNull }}, { OpNullUninit, {None, Stack1, OutNullUninit }}, { OpTrue, {None, Stack1, OutBooleanImm }}, { OpFalse, {None, Stack1, OutBooleanImm }}, { OpInt, {None, Stack1, OutInt64 }}, { OpDouble, {None, Stack1, OutDouble }}, { OpString, {None, Stack1, OutStringImm }}, { OpLazyClass, {None, Stack1, OutLazyClass }}, { OpDict, {None, Stack1, OutDictImm }}, { OpKeyset, {None, Stack1, OutKeysetImm }}, { OpVec, {None, Stack1, OutVecImm }}, { OpNewDictArray, {None, Stack1, OutDict }}, { OpNewStructDict, {StackN, Stack1, OutDict }}, { OpNewVec, {StackN, Stack1, OutVec }}, { OpNewKeysetArray, {StackN, Stack1, OutKeyset }}, { OpAddElemC, {StackTop3, Stack1, OutModifiedInput3 }}, { OpAddNewElemC, {StackTop2, Stack1, OutModifiedInput2 }}, { OpNewCol, {None, Stack1, OutObject }}, { OpNewPair, {StackTop2, Stack1, OutObject }}, { OpColFromArray, {Stack1, Stack1, OutObject }}, { OpCnsE, {None, Stack1, OutCns }}, { OpClsCns, {Stack1, Stack1, OutUnknown }}, { OpClsCnsD, {None, Stack1, OutUnknown }}, { OpClsCnsL, {Stack1|Local, Stack1, OutUnknown }}, { OpFile, {None, Stack1, OutString }}, { OpDir, {None, Stack1, OutString }}, { OpMethod, {None, Stack1, OutString }}, { OpClassName, {Stack1, Stack1, OutString }}, { OpLazyClassFromClass, {Stack1, Stack1, OutLazyClass }}, { OpFuncCred, {None, Stack1, OutObject }}, /*** 3. Operator instructions ***/ /* Binary string */ { OpConcat, {StackTop2, Stack1, OutString }}, { OpConcatN, {StackN, Stack1, OutString }}, /* Arithmetic ops */ { OpAdd, {StackTop2, Stack1, OutArith }}, { OpSub, {StackTop2, Stack1, OutArith }}, { OpMul, {StackTop2, Stack1, OutArith }}, /* Arithmetic ops that overflow ints to floats */ { OpAddO, {StackTop2, Stack1, OutArithO }}, { OpSubO, {StackTop2, Stack1, OutArithO }}, { OpMulO, {StackTop2, Stack1, OutArithO }}, /* Div and mod might return boolean false. Sigh. */ { OpDiv, {StackTop2, Stack1, OutUnknown }}, { OpMod, {StackTop2, Stack1, OutUnknown }}, { OpPow, {StackTop2, Stack1, OutUnknown }}, /* Logical ops */ { OpNot, {Stack1, Stack1, OutBoolean }}, { OpSame, {StackTop2, Stack1, OutBoolean }}, { OpNSame, {StackTop2, Stack1, OutBoolean }}, { OpEq, {StackTop2, Stack1, OutBoolean }}, { OpNeq, {StackTop2, Stack1, OutBoolean }}, { OpLt, {StackTop2, Stack1, OutBoolean }}, { OpLte, {StackTop2, Stack1, OutBoolean }}, { OpGt, {StackTop2, Stack1, OutBoolean }}, { OpGte, {StackTop2, Stack1, OutBoolean }}, { OpCmp, {StackTop2, Stack1, OutInt64 }}, /* Bitwise ops */ { OpBitAnd, {StackTop2, Stack1, OutBitOp }}, { OpBitOr, {StackTop2, Stack1, OutBitOp }}, { OpBitXor, {StackTop2, Stack1, OutBitOp }}, { OpBitNot, {Stack1, Stack1, OutBitOp }}, { OpShl, {StackTop2, Stack1, OutInt64 }}, { OpShr, {StackTop2, Stack1, OutInt64 }}, /* Cast instructions */ { OpCastBool, {Stack1, Stack1, OutBoolean }}, { OpCastInt, {Stack1, Stack1, OutInt64 }}, { OpCastDouble, {Stack1, Stack1, OutDouble }}, { OpCastString, {Stack1, Stack1, OutString }}, { OpCastDict, {Stack1, Stack1, OutDict }}, { OpCastKeyset, {Stack1, Stack1, OutKeyset }}, { OpCastVec, {Stack1, Stack1, OutVec }}, { OpDblAsBits, {Stack1, Stack1, OutInt64 }}, { OpInstanceOf, {StackTop2, Stack1, OutBoolean }}, { OpInstanceOfD, {Stack1, Stack1, OutPredBool }}, { OpIsLateBoundCls,{Stack1, Stack1, OutBoolean }}, { OpIsTypeStructC,{StackTop2, Stack1, OutBoolean }}, { OpThrowAsTypeStructException, {StackTop2, None, OutNone }}, { OpCombineAndResolveTypeStruct, {StackN, Stack1, OutDict }}, { OpSelect, {StackTop3, Stack1, OutUnknown }}, { OpPrint, {Stack1, Stack1, OutInt64 }}, { OpClone, {Stack1, Stack1, OutObject }}, { OpExit, {Stack1, Stack1, OutNull }}, { OpFatal, {Stack1, None, OutNone }}, /*** 4. Control flow instructions ***/ { OpJmp, {None, None, OutNone }}, { OpJmpNS, {None, None, OutNone }}, { OpJmpZ, {Stack1, None, OutNone }}, { OpJmpNZ, {Stack1, None, OutNone }}, { OpSwitch, {Stack1, None, OutNone }}, { OpSSwitch, {Stack1, None, OutNone }}, /* * RetC and RetM are special. Their manipulation of the runtime stack are * outside the boundaries of the tracelet abstraction; since they always end * a basic block, they behave more like "glue" between BBs than the * instructions in the body of a BB. * * RetC and RetM consume values from the stack, and these values' types needs * to be known at compile-time. */ { OpRetC, {None, None, OutNone }}, { OpRetM, {None, None, OutNone }}, { OpRetCSuspended, {None, None, OutNone }}, { OpThrow, {Stack1, None, OutNone }}, /*** 5. Get instructions ***/ { OpCGetL, {Local, Stack1, OutCInputL }}, { OpCGetL2, {Stack1|DontGuardStack1| Local, StackIns1, OutCInputL }}, { OpCGetQuietL, {Local, Stack1, OutCInputL }}, // In OpCUGetL we rely on OutCInputL returning TCell (which covers Uninit // values) instead of TInitCell. { OpCUGetL, {Local, Stack1, OutCInputL }}, { OpPushL, {Local, Stack1|Local, OutCInputL }}, { OpCGetG, {Stack1, Stack1, OutUnknown }}, { OpCGetS, {StackTop2, Stack1, OutUnknown }}, { OpClassGetC, {Stack1, Stack1, OutClass }}, { OpClassGetTS, {Stack1, StackTop2, OutUnknown }}, /*** 6. Isset, Empty, and type querying instructions ***/ { OpAKExists, {StackTop2, Stack1, OutBoolean }}, { OpIssetL, {Local, Stack1, OutBoolean }}, { OpIssetG, {Stack1, Stack1, OutBoolean }}, { OpIssetS, {StackTop2, Stack1, OutBoolean }}, { OpIsUnsetL, {Local, Stack1, OutBoolean }}, { OpIsTypeC, {Stack1| DontGuardStack1, Stack1, OutBoolean }}, { OpIsTypeL, {Local, Stack1, OutIsTypeL }}, /*** 7. Mutator instructions ***/ { OpSetL, {Stack1|Local, Stack1|Local, OutSameAsInput1 }}, { OpSetG, {StackTop2, Stack1, OutSameAsInput1 }}, { OpSetS, {StackTop3, Stack1, OutSameAsInput1 }}, { OpSetOpL, {Stack1|Local, Stack1|Local, OutSetOp }}, { OpSetOpG, {StackTop2, Stack1, OutUnknown }}, { OpSetOpS, {StackTop3, Stack1, OutUnknown }}, { OpIncDecL, {Local, Stack1|Local, OutIncDec }}, { OpIncDecG, {Stack1, Stack1, OutUnknown }}, { OpIncDecS, {StackTop2, Stack1, OutUnknown }}, { OpUnsetL, {Local, Local, OutNone }}, { OpUnsetG, {Stack1, None, OutNone }}, /*** 8. Call instructions ***/ { OpNewObj, {Stack1, Stack1, OutObject }}, { OpNewObjR, {StackTop2, Stack1, OutObject }}, { OpNewObjD, {None, Stack1, OutObject }}, { OpNewObjRD, {Stack1, Stack1, OutObject }}, { OpNewObjS, {None, Stack1, OutObject }}, { OpLockObj, {Stack1, Stack1, OutSameAsInput1 }}, /* * FCall* are special. Like the Ret* instructions, their manipulation of * the runtime stack are outside the boundaries of the tracelet abstraction. */ { OpFCallClsMethod, {StackTop2, StackN, OutUnknown }}, { OpFCallClsMethodD, {None, StackN, OutUnknown }}, { OpFCallClsMethodS, {Stack1, StackN, OutUnknown }}, { OpFCallClsMethodSD, {None, StackN, OutUnknown }}, { OpFCallCtor, {None, StackN, OutUnknown }}, { OpFCallFunc, {Stack1, StackN, OutUnknown }}, { OpFCallFuncD, {None, StackN, OutUnknown }}, { OpFCallObjMethod, {Stack1, StackN, OutUnknown }}, { OpFCallObjMethodD, {None, StackN, OutUnknown }}, /*** 11. Iterator instructions ***/ { OpIterInit, {Stack1, Local, OutUnknown }}, { OpLIterInit, {Local, Local, OutUnknown }}, { OpIterNext, {None, Local, OutUnknown }}, { OpLIterNext, {Local, Local, OutUnknown }}, { OpIterFree, {None, None, OutNone }}, { OpLIterFree, {Local, None, OutNone }}, /*** 12. Include, eval, and define instructions ***/ { OpIncl, {Stack1, Stack1, OutUnknown }}, { OpInclOnce, {Stack1, Stack1, OutUnknown }}, { OpReq, {Stack1, Stack1, OutUnknown }}, { OpReqOnce, {Stack1, Stack1, OutUnknown }}, { OpReqDoc, {Stack1, Stack1, OutUnknown }}, { OpEval, {Stack1, Stack1, OutUnknown }}, /*** 13. Miscellaneous instructions ***/ { OpThis, {None, Stack1, OutThisObject }}, { OpBareThis, {None, Stack1, OutUnknown }}, { OpCheckThis, {This, None, OutNone }}, { OpChainFaults, {StackTop2, Stack1, OutObject }}, { OpVerifyParamType, {Local, Local, OutUnknown }}, { OpVerifyParamTypeTS, {Local|Stack1, Local, OutUnknown }}, { OpVerifyRetTypeC, {Stack1, Stack1, OutUnknown }}, { OpVerifyRetTypeTS, {StackTop2, Stack1, OutUnknown }}, { OpVerifyRetNonNullC, {Stack1, Stack1, OutSameAsInput1 }}, { OpVerifyOutType, {Stack1, Stack1, OutUnknown }}, { OpOODeclExists, {StackTop2, Stack1, OutBoolean }}, { OpSelfCls, {None, Stack1, OutClass }}, { OpParentCls, {None, Stack1, OutClass }}, { OpLateBoundCls,{None, Stack1, OutClass }}, { OpRecordReifiedGeneric, {Stack1, Stack1, OutVec }}, { OpCheckReifiedGenericMismatch, {Stack1, None, OutNone }}, { OpNativeImpl, {None, None, OutNone }}, { OpCreateCl, {BStackN, Stack1, OutObject }}, { OpIdx, {StackTop3, Stack1, OutUnknown }}, { OpArrayIdx, {StackTop3, Stack1, OutUnknown }}, { OpArrayMarkLegacy, {StackTop2, Stack1, OutUnknown }}, { OpArrayUnmarkLegacy, {StackTop2, Stack1, OutUnknown }}, { OpCheckProp, {None, Stack1, OutBoolean }}, { OpInitProp, {Stack1, None, OutNone }}, { OpSilence, {Local|DontGuardAny, Local, OutNone }}, { OpThrowNonExhaustiveSwitch, {None, None, OutNone }}, { OpRaiseClassStringConversionWarning, {None, None, OutNone }}, { OpAssertRATL, {None, None, OutNone }}, { OpAssertRATStk,{None, None, OutNone }}, { OpBreakTraceHint,{None, None, OutNone }}, { OpGetMemoKeyL, {Local, Stack1, OutUnknown }}, { OpResolveRFunc,{Stack1, Stack1, OutFuncLike }}, { OpResolveFunc, {None, Stack1, OutFunc }}, { OpResolveMethCaller, {None, Stack1, OutFunc }}, { OpResolveClsMethod, {Stack1, Stack1, OutClsMeth }}, { OpResolveClsMethodD, {None, Stack1, OutClsMeth }}, { OpResolveClsMethodS, {None, Stack1, OutClsMeth }}, { OpResolveRClsMethod, {StackTop2, Stack1, OutClsMethLike }}, { OpResolveRClsMethodD, {Stack1, Stack1, OutClsMethLike }}, { OpResolveRClsMethodS, {Stack1, Stack1, OutClsMethLike }}, // TODO (T61651936): ResolveClass may return a classptr or a string { OpResolveClass,{None, Stack1, OutUnknown }}, { OpSetImplicitContextByValue, {Stack1, Stack1, OutUnknown }}, /*** 14. Generator instructions ***/ { OpCreateCont, {None, Stack1, OutNull }}, { OpContEnter, {Stack1, Stack1, OutUnknown }}, { OpContRaise, {Stack1, Stack1, OutUnknown }}, { OpYield, {Stack1, Stack1, OutUnknown }}, { OpYieldK, {StackTop2, Stack1, OutUnknown }}, { OpContCheck, {None, None, OutNone }}, { OpContValid, {None, Stack1, OutBoolean }}, { OpContKey, {None, Stack1, OutUnknown }}, { OpContCurrent, {None, Stack1, OutUnknown }}, { OpContGetReturn, {None, Stack1, OutUnknown }}, /*** 15. Async functions instructions ***/ { OpWHResult, {Stack1, Stack1, OutUnknown }}, { OpAwait, {Stack1, Stack1, OutUnknown }}, { OpAwaitAll, {LocalRange, Stack1, OutNull }}, /*** 16. Member instructions ***/ { OpBaseGC, {StackI, MBase, OutNone }}, { OpBaseGL, {Local, MBase, OutNone }}, { OpBaseSC, {StackI|StackI2, MBase, OutNone }}, { OpBaseL, {Local, MBase, OutNone }}, { OpBaseC, {StackI, MBase, OutNone }}, { OpBaseH, {None, MBase, OutNone }}, { OpDim, {MBase|MKey, MBase, OutNone }}, { OpQueryM, {BStackN|MBase|MKey, Stack1, OutUnknown }}, { OpSetM, {Stack1|BStackN|MBase|MKey, Stack1, OutUnknown }}, { OpSetRangeM, {StackTop3|BStackN|MBase, None, OutNone }}, { OpIncDecM, {BStackN|MBase|MKey, Stack1, OutUnknown }}, { OpSetOpM, {Stack1|BStackN|MBase|MKey, Stack1, OutUnknown }}, { OpUnsetM, {BStackN|MBase|MKey, None, OutNone }}, { OpMemoGet, {LocalRange, None, OutUnknown }}, { OpMemoGetEager,{LocalRange, None, OutUnknown }}, { OpMemoSet, {Stack1|LocalRange, Stack1, OutSameAsInput1 }}, { OpMemoSetEager, {Stack1|LocalRange, Stack1, OutSameAsInput1 }}, }; namespace { hphp_hash_map<Op, InstrInfo> instrInfo; bool instrInfoInited; } void initInstrInfo() { if (!instrInfoInited) { instrInfo.reserve(sizeof(instrInfoSparse)/sizeof(*instrInfoSparse)); for (auto& info : instrInfoSparse) { instrInfo[info.op] = info.info; } instrInfoInited = true; } } const InstrInfo& getInstrInfo(Op op) { assertx(instrInfoInited); return instrInfo[op]; } namespace { int64_t countOperands(uint64_t mask) { const uint64_t ignore = Local | Iter | DontGuardStack1 | DontGuardAny | This | MBase | StackI | StackI2 | MKey | LocalRange | DontGuardBase; mask &= ~ignore; static const uint64_t counts[][2] = { {Stack3, 1}, {Stack2, 1}, {Stack1, 1}, {StackIns1, 2}, }; int64_t count = 0; for (auto const& pair : counts) { if (mask & pair[0]) { count += pair[1]; mask &= ~pair[0]; } } assertx(mask == 0); return count; } } int64_t getStackPopped(PC pc) { auto const op = peek_op(pc); switch (op) { case Op::FCallClsMethod: case Op::FCallClsMethodD: case Op::FCallClsMethodS: case Op::FCallClsMethodSD: case Op::FCallCtor: case Op::FCallFunc: case Op::FCallFuncD: case Op::FCallObjMethod: case Op::FCallObjMethodD: { auto const fca = getImm(pc, 0).u_FCA; auto const nin = countOperands(getInstrInfo(op).in); return nin + fca.numInputs() + (kNumActRecCells - 1) + fca.numRets; } case Op::QueryM: case Op::IncDecM: case Op::UnsetM: case Op::NewVec: case Op::NewKeysetArray: case Op::ConcatN: case Op::CombineAndResolveTypeStruct: case Op::CreateCl: return getImm(pc, 0).u_IVA; case Op::SetM: case Op::SetOpM: return getImm(pc, 0).u_IVA + 1; case Op::SetRangeM: return getImm(pc, 0).u_IVA + 3; case Op::NewStructDict: return getImmVector(pc).size(); default: break; } uint64_t mask = getInstrInfo(op).in; // All instructions with these properties are handled above assertx((mask & (StackN | BStackN)) == 0); return countOperands(mask); } int64_t getStackPushed(PC pc) { auto const op = peek_op(pc); switch (op) { case Op::FCallClsMethod: case Op::FCallClsMethodD: case Op::FCallClsMethodS: case Op::FCallClsMethodSD: case Op::FCallCtor: case Op::FCallFunc: case Op::FCallFuncD: case Op::FCallObjMethod: case Op::FCallObjMethodD: return getImm(pc, 0).u_FCA.numRets; default: break; } uint64_t mask = getInstrInfo(op).out; // All instructions with these properties are handled above assertx((mask & (StackN | BStackN)) == 0); return countOperands(mask); } bool isAlwaysNop(const NormalizedInstruction& ni) { switch (ni.op()) { case Op::Nop: case Op::CGetCUNop: case Op::UGetCUNop: case Op::EntryNop: return true; default: return false; } } #define NA #define ONE(a) a(0) #define TWO(a, b) a(0) b(1) #define THREE(a, b, c) a(0) b(1) c(2) #define FOUR(a, b, c, d) a(0) b(1) c(2) d(3) #define FIVE(a, b, c, d, e) a(0) b(1) c(2) d(3) e(4) #define SIX(a, b, c, d, e, f) a(0) b(1) c(2) d(3) e(4) f(5) // Iterator bytecodes are handled specially here #define LA(n) assertx(idx == 0xff); idx = n; #define NLA(n) assertx(idx == 0xff); idx = n; #define ILA(n) assertx(idx == 0xff); idx = n; #define MA(n) #define BLA(n) #define SLA(n) #define IVA(n) #define I64A(n) #define IA(n) #define DA(n) #define SA(n) #define AA(n) #define RATA(n) #define BA(n) #define OA(op) BA #define VSA(n) #define KA(n) #define LAR(n) #define ITA(n) #define FCA(n) #define O(name, imm, ...) case Op::name: imm break; size_t localImmIdx(Op op) { switch (op) { case Op::LIterInit: case Op::LIterNext: case Op::LIterFree: return 1; default: break; } size_t idx = 0xff; switch (op) { OPCODES } assertx(idx != 0xff); return idx; } size_t memberKeyImmIdx(Op op) { size_t idx = 0xff; switch (op) { #undef LA #undef NLA #undef ILA #undef KA #define LA(n) #define NLA(n) #define ILA(n) #define KA(n) assertx(idx == 0xff); idx = n; OPCODES } assertx(idx != 0xff); return idx; } #undef ONE #undef TWO #undef THREE #undef FOUR #undef FIVE #undef SIX #undef LA #undef NLA #undef ILA #undef MA #undef BLA #undef SLA #undef IVA #undef I64A #undef IA #undef DA #undef SA #undef AA #undef RATA #undef BA #undef OA #undef VSA #undef KA #undef LAR #undef ITA #undef FCA #undef O unsigned localRangeImmIdx(Op op) { switch (op) { case Op::AwaitAll: case Op::MemoSet: case Op::MemoSetEager: return 0; case Op::MemoGet: return 1; case Op::MemoGetEager: return 2; default: always_assert_flog("op {} doesn't have LocalRange!\n", opcodeToName(op)); return -1; } } uint32_t getLocalOperand(const NormalizedInstruction& ni) { auto const idx = localImmIdx(ni.op()); auto const argu = ni.imm[idx]; switch (immType(ni.op(), idx)) { case ArgType::LA: return argu.u_LA; case ArgType::NLA: return argu.u_NLA.id; case ArgType::ILA: return argu.u_ILA; default: always_assert(false); } not_reached(); } /* * Get location metadata for the inputs of `ni'. */ InputInfoVec getInputs(const NormalizedInstruction& ni, SBInvOffset bcSPOff) { InputInfoVec inputs; if (isAlwaysNop(ni)) return inputs; always_assert_flog( instrInfo.count(ni.op()), "Invalid opcode in getInputsImpl: {}\n", opcodeToName(ni.op()) ); UNUSED auto const sk = ni.source; auto const& info = instrInfo[ni.op()]; auto const flags = info.in; auto stackOff = bcSPOff; if (flags & Stack1) { SKTRACE(1, sk, "getInputs: Stack1 %d\n", stackOff.offset); inputs.emplace_back(Location::Stack { stackOff-- }); if (flags & DontGuardStack1) inputs.back().dontGuard = true; if (flags & Stack2) { SKTRACE(1, sk, "getInputs: Stack2 %d\n", stackOff.offset); inputs.emplace_back(Location::Stack { stackOff-- }); if (flags & Stack3) { SKTRACE(1, sk, "getInputs: Stack3 %d\n", stackOff.offset); inputs.emplace_back(Location::Stack { stackOff-- }); } } } if (flags & StackI) { inputs.emplace_back(Location::Stack { BCSPRelOffset{safe_cast<int32_t>(ni.imm[0].u_IVA)}. to<SBInvOffset>(bcSPOff) }); } if (flags & StackI2) { inputs.emplace_back(Location::Stack { BCSPRelOffset{safe_cast<int32_t>(ni.imm[1].u_IVA)}. to<SBInvOffset>(bcSPOff) }); } if (flags & StackN) { int numArgs = [&] () -> int { switch (ni.op()) { case Op::NewVec: case Op::NewKeysetArray: case Op::CombineAndResolveTypeStruct: case Op::ConcatN: return ni.imm[0].u_IVA; default: return ni.immVec.numStackValues(); } not_reached(); }(); SKTRACE(1, sk, "getInputs: StackN %d %d\n", stackOff.offset, numArgs); for (int i = 0; i < numArgs; i++) { inputs.emplace_back(Location::Stack { stackOff-- }); inputs.back().dontGuard = true; inputs.back().dontBreak = true; } } if (flags & BStackN) { int numArgs = ni.imm[0].u_IVA; SKTRACE(1, sk, "getInputs: BStackN %d %d\n", stackOff.offset, numArgs); for (int i = 0; i < numArgs; i++) { inputs.emplace_back(Location::Stack { stackOff-- }); } } if (isFCall(ni.op())) { auto const& fca = ni.imm[0].u_FCA; SKTRACE(1, sk, "getInputs: %s %d %d\n", opcodeToName(ni.op()), stackOff.offset, fca.numInputs()); if (fca.hasGenerics()) inputs.emplace_back(Location::Stack { stackOff-- }); if (fca.hasUnpack()) inputs.emplace_back(Location::Stack { stackOff-- }); stackOff -= fca.numArgs + (kNumActRecCells - 1); switch (ni.op()) { case Op::FCallCtor: case Op::FCallObjMethod: case Op::FCallObjMethodD: inputs.emplace_back(Location::Stack { stackOff-- }); break; default: stackOff--; break; } } if (flags & Local) { auto const loc = getLocalOperand(ni); SKTRACE(1, sk, "getInputs: local %d\n", loc); inputs.emplace_back(Location::Local{loc}); } if (flags & LocalRange) { auto const& range = ni.imm[localRangeImmIdx(ni.op())].u_LAR; SKTRACE(1, sk, "getInputs: localRange %d+%d\n", range.first, range.count); for (int i = 0; i < range.count; ++i) { inputs.emplace_back(Location::Local { uint32_t(range.first + i) }); } } if (flags & MKey) { auto mk = ni.imm[memberKeyImmIdx(ni.op())].u_KA; switch (mk.mcode) { case MEL: case MPL: inputs.emplace_back(Location::Local { uint32_t(mk.local.id) }); break; case MEC: case MPC: inputs.emplace_back(Location::Stack { BCSPRelOffset{int32_t(mk.iva)}.to<SBInvOffset>(bcSPOff) }); break; case MW: case MEI: case MET: case MPT: case MQT: // The inputs vector is only used for deciding when to break the // tracelet, which can never happen for these cases. break; } } if (flags & MBase) { inputs.emplace_back(Location::MBase{}); if (flags & DontGuardBase) { inputs.back().dontGuard = true; inputs.back().dontBreak = true; } } SKTRACE(1, sk, "stack args: virtual sfo now %d\n", stackOff.offset); TRACE(1, "%s\n", Trace::prettyNode("Inputs", inputs).c_str()); if ((flags & DontGuardAny) || dontGuardAnyInputs(ni)) { for (auto& info : inputs) info.dontGuard = true; } return inputs; } /* * Get the list of output locals written by the `ni' instruction. */ jit::fast_set<uint32_t> getLocalOutputs(const NormalizedInstruction& ni) { fast_set<uint32_t> locals; auto const op = ni.op(); if (isIteratorOp(op)) { auto const ita = ni.imm[0].u_ITA; locals.insert(ita.valId); if (ita.hasKey()) locals.insert(ita.keyId); } else { auto const info = getInstrInfo(op); if (info.out & Local) { auto const id = getLocalOperand(ni); locals.insert(id); } if (info.out & LocalRange) { auto const& range = ni.imm[localRangeImmIdx(op)].u_LAR; for (unsigned i = 0; i < range.count; ++i) { locals.insert(range.first + i); } } if (info.out & MKey) { auto const mk = ni.imm[memberKeyImmIdx(op)].u_KA; if (mk.mcode == MEL || mk.mcode == MPL) { locals.insert(mk.local.id); } } } return locals; } bool dontGuardAnyInputs(const NormalizedInstruction& ni) { switch (ni.op()) { case Op::IterNext: case Op::LIterNext: case Op::IterInit: case Op::LIterInit: case Op::JmpZ: case Op::JmpNZ: case Op::Jmp: case Op::JmpNS: case Op::ClsCnsD: case Op::NewStructDict: case Op::Switch: case Op::SSwitch: case Op::Lt: case Op::Lte: case Op::Gt: case Op::Gte: case Op::Cmp: case Op::SetOpL: case Op::InitProp: case Op::BreakTraceHint: case Op::IsTypeL: case Op::IsTypeC: case Op::IncDecL: case Op::Eq: case Op::Neq: case Op::AssertRATL: case Op::AssertRATStk: case Op::SetL: case Op::CastBool: case Op::Same: case Op::NSame: case Op::Yield: case Op::YieldK: case Op::ContEnter: case Op::ContRaise: case Op::CreateCont: case Op::Await: case Op::AwaitAll: case Op::BitAnd: case Op::BitOr: case Op::BitXor: case Op::Sub: case Op::Mul: case Op::SubO: case Op::MulO: case Op::Add: case Op::AddO: case Op::ClassGetC: case Op::ClassGetTS: case Op::AKExists: case Op::AddElemC: case Op::AddNewElemC: case Op::Dict: case Op::Keyset: case Op::Vec: case Op::ArrayIdx: case Op::ArrayMarkLegacy: case Op::ArrayUnmarkLegacy: case Op::BareThis: case Op::BitNot: case Op::CGetG: case Op::CGetL: case Op::CGetQuietL: case Op::CGetL2: case Op::CGetS: case Op::CUGetL: case Op::CastDouble: case Op::CastInt: case Op::CastString: case Op::CastDict: case Op::CastKeyset: case Op::CastVec: case Op::DblAsBits: case Op::CheckProp: case Op::CheckThis: case Op::Clone: case Op::CnsE: case Op::ColFromArray: case Op::CombineAndResolveTypeStruct: case Op::RecordReifiedGeneric: case Op::CheckReifiedGenericMismatch: case Op::ConcatN: case Op::Concat: case Op::ContCheck: case Op::ContCurrent: case Op::ContKey: case Op::ContValid: case Op::ContGetReturn: case Op::CreateCl: case Op::Dir: case Op::Div: case Op::Double: case Op::Dup: case Op::FCallClsMethod: case Op::FCallClsMethodD: case Op::FCallClsMethodS: case Op::FCallClsMethodSD: case Op::FCallCtor: case Op::FCallFunc: case Op::FCallFuncD: case Op::FCallObjMethodD: case Op::ResolveRFunc: case Op::ResolveFunc: case Op::ResolveMethCaller: case Op::ResolveClsMethod: case Op::ResolveClsMethodD: case Op::ResolveClsMethodS: case Op::ResolveRClsMethod: case Op::ResolveRClsMethodD: case Op::ResolveRClsMethodS: case Op::ResolveClass: case Op::LazyClass: case Op::False: case Op::File: case Op::FuncCred: case Op::GetMemoKeyL: case Op::Idx: case Op::InstanceOf: case Op::InstanceOfD: case Op::IsLateBoundCls: case Op::IsTypeStructC: case Op::Int: case Op::IssetG: case Op::IssetL: case Op::IssetS: case Op::IsUnsetL: case Op::IterFree: case Op::LIterFree: case Op::LateBoundCls: case Op::Method: case Op::Mod: case Op::Pow: case Op::ClassName: case Op::LazyClassFromClass: case Op::NativeImpl: case Op::NewCol: case Op::NewPair: case Op::NewDictArray: case Op::NewVec: case Op::NewKeysetArray: case Op::NewObj: case Op::NewObjR: case Op::NewObjD: case Op::NewObjRD: case Op::NewObjS: case Op::Not: case Op::Null: case Op::NullUninit: case Op::OODeclExists: case Op::ParentCls: case Op::PopC: case Op::PopU: case Op::PopU2: case Op::PopL: case Op::Print: case Op::PushL: case Op::RetC: case Op::RetCSuspended: case Op::SelfCls: case Op::SetG: case Op::SetS: case Op::Shl: case Op::Shr: case Op::Silence: case Op::String: case Op::This: case Op::Throw: case Op::ThrowAsTypeStructException: case Op::ThrowNonExhaustiveSwitch: case Op::RaiseClassStringConversionWarning: case Op::True: case Op::UnsetL: case Op::VerifyParamType: case Op::VerifyParamTypeTS: case Op::VerifyRetTypeC: case Op::VerifyRetTypeTS: case Op::VerifyRetNonNullC: case Op::VerifyOutType: case Op::WHResult: case Op::BaseGC: case Op::BaseGL: case Op::BaseSC: case Op::BaseL: case Op::BaseC: case Op::BaseH: case Op::Dim: case Op::QueryM: case Op::SetM: case Op::IncDecM: case Op::SetOpM: case Op::UnsetM: case Op::SetRangeM: case Op::MemoGet: case Op::MemoGetEager: case Op::MemoSet: case Op::MemoSetEager: case Op::RetM: case Op::Select: case Op::LockObj: case Op::ClsCnsL: case Op::SetImplicitContextByValue: return false; // These are instructions that are always interp-one'd, or are always no-ops. case Op::Nop: case Op::EntryNop: case Op::CGetCUNop: case Op::UGetCUNop: case Op::ClsCns: case Op::Exit: case Op::Fatal: case Op::SetOpG: case Op::SetOpS: case Op::IncDecG: case Op::IncDecS: case Op::UnsetG: case Op::FCallObjMethod: case Op::Incl: case Op::InclOnce: case Op::Req: case Op::ReqOnce: case Op::ReqDoc: case Op::Eval: case Op::ChainFaults: return true; } always_assert_flog(0, "invalid opcode {}\n", static_cast<uint32_t>(ni.op())); } bool instrBreaksProfileBB(const NormalizedInstruction& inst) { auto const op = inst.op(); if (isFCall(op)) return true; if (instrIsNonCallControlFlow(op) || op == OpAwait || // may branch to scheduler and suspend execution op == OpAwaitAll || // similar to Await op == OpClsCnsD || // side exits if misses in the RDS op == OpThrowNonExhaustiveSwitch || // control flow breaks bb op == OpVerifyParamTypeTS || // avoids combinatorial explosion op == OpVerifyParamType) { // with nullable types return true; } // In profiling mode, don't trace through a control flow merge point, // however, allow inlining of default parameter funclets assertx(profData()); if (profData()->anyBlockEndsAt(inst.func(), inst.offset()) && !inst.func()->isEntry(inst.nextSk().offset())) { return true; } return false; } ////////////////////////////////////////////////////////////////////// namespace { #define IMM_BLA(n) ni.immVec #define IMM_SLA(n) ni.immVec #define IMM_VSA(n) ni.immVec #define IMM_IVA(n) ni.imm[n].u_IVA #define IMM_I64A(n) ni.imm[n].u_I64A #define IMM_LA(n) ni.imm[n].u_LA #define IMM_NLA(n) ni.imm[n].u_NLA #define IMM_ILA(n) ni.imm[n].u_ILA #define IMM_IA(n) ni.imm[n].u_IA #define IMM_DA(n) ni.imm[n].u_DA #define IMM_SA(n) ni.unit()->lookupLitstrId(ni.imm[n].u_SA) #define IMM_RATA(n) ni.imm[n].u_RATA #define IMM_AA(n) ni.unit()->lookupArrayId(ni.imm[n].u_AA) #define IMM_BA(n) ni.imm[n].u_BA #define IMM_OA_IMPL(n) ni.imm[n].u_OA #define IMM_OA(subop) (subop)IMM_OA_IMPL #define IMM_KA(n) ni.imm[n].u_KA #define IMM_LAR(n) ni.imm[n].u_LAR #define IMM_ITA(n) ni.imm[n].u_ITA #define IMM_FCA(n) ni.imm[n].u_FCA #define ONE(x0) , IMM_##x0(0) #define TWO(x0,x1) , IMM_##x0(0), IMM_##x1(1) #define THREE(x0,x1,x2) , IMM_##x0(0), IMM_##x1(1), IMM_##x2(2) #define FOUR(x0,x1,x2,x3) , IMM_##x0(0), IMM_##x1(1), IMM_##x2(2), IMM_##x3(3) #define FIVE(x0,x1,x2,x3,x4) , IMM_##x0(0), IMM_##x1(1), IMM_##x2(2), IMM_##x3(3), IMM_##x4(4) #define SIX(x0,x1,x2,x3,x4,x5) , IMM_##x0(0), IMM_##x1(1), IMM_##x2(2), IMM_##x3(3), IMM_##x4(4), IMM_##x5(5) #define NA /* */ void translateDispatch(irgen::IRGS& irgs, const NormalizedInstruction& ni) { #define O(nm, imms, ...) case Op::nm: irgen::emit##nm(irgs imms); return; switch (ni.op()) { OPCODES } #undef O } #undef SIX #undef FIVE #undef FOUR #undef THREE #undef TWO #undef ONE #undef NA #undef IMM_BLA #undef IMM_SLA #undef IMM_IVA #undef IMM_I64A #undef IMM_LA #undef IMM_NLA #undef IMM_ILA #undef IMM_IA #undef IMM_DA #undef IMM_SA #undef IMM_RATA #undef IMM_AA #undef IMM_BA #undef IMM_OA_IMPL #undef IMM_OA #undef IMM_VSA #undef IMM_KA #undef IMM_LAR #undef IMM_ITA #undef IMM_FCA ////////////////////////////////////////////////////////////////////// Type flavorToType(FlavorDesc f) { switch (f) { case CV: return TInitCell; case CUV: return TCell; case UV: return TUninit; case NOV: break; } always_assert(false); } } void translateInstr(irgen::IRGS& irgs, const NormalizedInstruction& ni) { assertx(curSrcKey(irgs) == ni.source); if (ni.source.funcEntry()) { emitFuncEntry(irgs); return; } auto pc = ni.pc(); for (auto i = 0, num = instrNumPops(pc); i < num; ++i) { if (isFCall(ni.op()) && instrInputFlavor(pc, i) == UV) { // This is a hack to deal with the fact that this instruction is // actually popping an ActRec in the middle of its "pops." We could // assert on the Uninit values on the stack, but the call is going to // override them anyway so it's not worth guarding on them. break; } auto const type = flavorToType(instrInputFlavor(pc, i)); irgen::assertTypeStack(irgs, BCSPRelOffset{i}, type); } FTRACE(1, "\nTranslating {}: {} with state:\n{}\n", ni.offset(), ni, show(irgs)); irgen::ringbufferEntry(irgs, Trace::RBTypeBytecodeStart, ni.source, 2); irgen::implIncStat(irgs, Stats::Instr_TC); if (Stats::enableInstrCount()) { irgen::implIncStat(irgs, Stats::opToTranslStat(ni.op())); } if (isAlwaysNop(ni)) return; if (ni.interp || RuntimeOption::EvalJitAlwaysInterpOne) { irgen::interpOne(irgs); return; } if (ni.forceSurpriseCheck) surpriseCheck(irgs); translateDispatchBespoke(irgs, ni, [&](irgen::IRGS& env) { translateDispatch(env, ni); }); FTRACE(3, "\nTranslated {}: {} with state:\n{}\n", ni.offset(), ni, show(irgs)); } ////////////////////////////////////////////////////////////////////// } void invalidatePath(const std::string& path) { TRACE(1, "invalidatePath: abspath %s\n", path.c_str()); assertx(path.size() >= 1 && path[0] == '/'); Treadmill::enqueue([path] { /* * inotify saw this path change. Now poke the unit loader; it will * notice the underlying php file has changed. * * We don't actually need to *do* anything with the Unit* from * this lookup; since the path has changed, the file we'll get out is * going to be some new file, not the old file that needs invalidation. */ String spath(path); lookupUnit(spath.get(), "", nullptr /* initial_opt */, Native::s_noNativeFuncs, false); }); } /////////////////////////////////////////////////////////////////////////////// }