Permalink
Switch branches/tags
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1458 lines (1335 sloc) 50.3 KB
/*
+----------------------------------------------------------------------+
| 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 <stdarg.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/map-walker.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/annotation.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-control.h"
#include "hphp/runtime/vm/jit/irgen-exit.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/timer.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 ***/
{ OpDiscardClsRef, {None, None, OutNone }},
{ OpPopC, {Stack1|
DontGuardStack1, None, OutNone }},
{ OpPopV, {Stack1|
DontGuardStack1|
IgnoreInnerType, None, OutNone }},
{ OpPopR, {Stack1|
DontGuardStack1|
IgnoreInnerType, None, OutNone }},
{ OpPopU, {Stack1|
DontGuardStack1, None, OutNone }},
{ OpPopL, {Stack1|Local, Local, OutNone }},
{ OpDup, {Stack1, StackTop2, OutSameAsInput1 }},
{ OpBox, {Stack1, Stack1, OutVInput }},
{ OpUnbox, {Stack1, Stack1, OutCInput }},
{ OpBoxR, {Stack1, Stack1, OutVInput }},
{ OpUnboxR, {Stack1, Stack1, OutCInput }},
/*** 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 }},
{ OpArray, {None, Stack1, OutArrayImm }},
{ OpDict, {None, Stack1, OutDictImm }},
{ OpKeyset, {None, Stack1, OutKeysetImm }},
{ OpVec, {None, Stack1, OutVecImm }},
{ OpNewArray, {None, Stack1, OutArray }},
{ OpNewMixedArray, {None, Stack1, OutArray }},
{ OpNewDictArray, {None, Stack1, OutDict }},
{ OpNewLikeArrayL, {Local, Stack1, OutArray }},
{ OpNewPackedArray, {StackN, Stack1, OutArray }},
{ OpNewStructArray, {StackN, Stack1, OutArray }},
{ OpNewStructDArray,{StackN, Stack1, OutArray }},
{ OpNewStructDict, {StackN, Stack1, OutDict }},
{ OpNewVecArray, {StackN, Stack1, OutVec }},
{ OpNewKeysetArray, {StackN, Stack1, OutKeyset }},
{ OpNewVArray, {StackN, Stack1, OutArray }},
{ OpNewDArray, {None, Stack1, OutArray }},
{ OpAddElemC, {StackTop3, Stack1, OutModifiedInput3 }},
{ OpAddElemV, {StackTop3, Stack1, OutModifiedInput3 }},
{ OpAddNewElemC, {StackTop2, Stack1, OutModifiedInput2 }},
{ OpAddNewElemV, {StackTop2, Stack1, OutArray }},
{ OpNewCol, {None, Stack1, OutObject }},
{ OpNewPair, {StackTop2, Stack1, OutObject }},
{ OpColFromArray, {Stack1, Stack1, OutObject }},
{ OpCns, {None, Stack1, OutCns }},
{ OpCnsE, {None, Stack1, OutCns }},
{ OpCnsU, {None, Stack1, OutCns }},
{ OpClsCns, {None, Stack1, OutUnknown }},
{ OpClsCnsD, {None, Stack1, OutUnknown }},
{ OpFile, {None, Stack1, OutString }},
{ OpDir, {None, Stack1, OutString }},
{ OpMethod, {None, Stack1, OutString }},
{ OpClsRefName, {None, Stack1, OutString }},
/*** 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 */
{ OpXor, {StackTop2, Stack1, OutBoolean }},
{ 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 }},
{ OpCastArray, {Stack1, Stack1, OutArray }},
{ OpCastObject, {Stack1, Stack1, OutObject }},
{ OpCastDict, {Stack1, Stack1, OutDict }},
{ OpCastKeyset, {Stack1, Stack1, OutKeyset }},
{ OpCastVec, {Stack1, Stack1, OutVec }},
{ OpCastVArray, {Stack1, Stack1, OutArray }},
{ OpCastDArray, {Stack1, Stack1, OutArray }},
{ OpDblAsBits, {Stack1, Stack1, OutInt64 }},
{ OpInstanceOf, {StackTop2, Stack1, OutBoolean }},
{ OpInstanceOfD, {Stack1, Stack1, OutPredBool }},
{ OpIsTypeStruct,{Stack1, Stack1, OutBoolean }},
{ OpAsTypeStruct,{Stack1, Stack1, OutSameAsInput1 }},
{ 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 RetV are special. Their manipulation of the runtime stack are
* outside the boundaries of the tracelet abstraction; since they always end
* a basic block, they behave more like "glue" between BBs than the
* instructions in the body of a BB.
*
* RetC and RetV consume a value from the stack, and this value's type needs
* to be known at compile-time.
*/
{ OpRetC, {AllLocals, None, OutNone }},
{ OpRetV, {AllLocals, None, OutNone }},
{ OpRetM, {AllLocals, None, OutNone }},
{ OpThrow, {Stack1, None, OutNone }},
{ OpUnwind, {None, 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 }},
{ OpCGetN, {Stack1, Stack1, OutUnknown }},
{ OpCGetQuietN, {Stack1, Stack1, OutUnknown }},
{ OpCGetG, {Stack1, Stack1, OutUnknown }},
{ OpCGetQuietG, {Stack1, Stack1, OutUnknown }},
{ OpCGetS, {Stack1, Stack1, OutUnknown }},
{ OpVGetL, {Local, Stack1|Local, OutVInputL }},
{ OpVGetN, {Stack1, Stack1|Local, OutVUnknown }},
// TODO: In pseudo-main, the VGetG instruction invalidates what we know
// about the types of the locals because it could cause any one of the
// local variables to become "boxed". We need to add logic to tracelet
// analysis to deal with this properly.
{ OpVGetG, {Stack1, Stack1, OutVUnknown }},
{ OpVGetS, {Stack1, Stack1, OutVUnknown }},
{ OpClsRefGetC, {Stack1, None, OutNone }},
{ OpClsRefGetL, {Local, None, OutNone }},
/*** 6. Isset, Empty, and type querying instructions ***/
{ OpAKExists, {StackTop2, Stack1, OutBoolean }},
{ OpIssetL, {Local, Stack1, OutBoolean }},
{ OpIssetN, {Stack1, Stack1, OutBoolean }},
{ OpIssetG, {Stack1, Stack1, OutBoolean }},
{ OpIssetS, {Stack1, Stack1, OutBoolean }},
{ OpEmptyL, {Local, Stack1, OutBoolean }},
{ OpEmptyN, {Stack1, Stack1, OutBoolean }},
{ OpEmptyG, {Stack1, Stack1, OutBoolean }},
{ OpEmptyS, {Stack1, Stack1, OutBoolean }},
{ OpIsTypeC, {Stack1|
DontGuardStack1, Stack1, OutBoolean }},
{ OpIsTypeL, {Local, Stack1, OutIsTypeL }},
/*** 7. Mutator instructions ***/
{ OpSetL, {Stack1|Local, Stack1|Local, OutSameAsInput1 }},
{ OpSetN, {StackTop2, Stack1|Local, OutSameAsInput1 }},
{ OpSetG, {StackTop2, Stack1, OutSameAsInput1 }},
{ OpSetS, {StackTop2, Stack1, OutSameAsInput1 }},
{ OpSetOpL, {Stack1|Local, Stack1|Local, OutSetOp }},
{ OpSetOpN, {StackTop2, Stack1|Local, OutUnknown }},
{ OpSetOpG, {StackTop2, Stack1, OutUnknown }},
{ OpSetOpS, {StackTop2, Stack1, OutUnknown }},
{ OpIncDecL, {Local, Stack1|Local, OutIncDec }},
{ OpIncDecN, {Stack1, Stack1|Local, OutUnknown }},
{ OpIncDecG, {Stack1, Stack1, OutUnknown }},
{ OpIncDecS, {Stack1, Stack1, OutUnknown }},
{ OpBindL, {Stack1|Local|
IgnoreInnerType, Stack1|Local, OutSameAsInput1 }},
{ OpBindN, {StackTop2, Stack1|Local, OutSameAsInput1 }},
{ OpBindG, {StackTop2, Stack1, OutSameAsInput1 }},
{ OpBindS, {StackTop2, Stack1, OutSameAsInput1 }},
{ OpUnsetL, {Local, Local, OutNone }},
{ OpUnsetN, {Stack1, Local, OutNone }},
{ OpUnsetG, {Stack1, None, OutNone }},
/*** 8. Call instructions ***/
{ OpFPushFunc, {Stack1, FStack, OutFDesc }},
{ OpFPushFuncD, {None, FStack, OutFDesc }},
{ OpFPushFuncU, {None, FStack, OutFDesc }},
{ OpFPushObjMethod,
{StackTop2, FStack, OutFDesc }},
{ OpFPushObjMethodD,
{Stack1, FStack, OutFDesc }},
{ OpFPushClsMethod,
{Stack1, FStack, OutFDesc }},
{ OpFPushClsMethodS,
{Stack1, FStack, OutFDesc }},
{ OpFPushClsMethodSD,
{None, FStack, OutFDesc }},
{ OpFPushClsMethodD,
{None, FStack, OutFDesc }},
{ OpFPushCtor, {None, Stack1|FStack,OutObject }},
{ OpFPushCtorD, {None, Stack1|FStack,OutObject }},
{ OpFPushCtorI, {None, Stack1|FStack,OutObject }},
{ OpFPushCtorS, {None, Stack1|FStack,OutObject }},
{ OpFPushCufIter,{None, FStack, OutFDesc }},
{ OpFIsParamByRef,
{None, Stack1, OutBoolean }},
{ OpFIsParamByRefCufIter,
{None, Stack1, OutBoolean }},
{ OpFThrowOnRefMismatch,
{None, None, OutNone }},
{ OpFHandleRefMismatch,
{None, None, OutNone }},
/*
* FCall is special. Like the Ret* instructions, its manipulation of the
* runtime stack are outside the boundaries of the tracelet abstraction.
*/
{ OpFCall, {FStack, StackN, OutUnknown }},
{ OpFCallAwait, {FStack, Stack1, OutUnknown }},
{ OpFCallBuiltin,{BStackN|DontGuardAny,
Stack1, OutUnknown }},
{ OpDecodeCufIter,{Stack1, None, OutNone }},
/*** 11. Iterator instructions ***/
{ OpIterInit, {Stack1, Local, OutUnknown }},
{ OpMIterInit, {Stack1, Local, OutUnknown }},
{ OpWIterInit, {Stack1, Local, OutUnknown }},
{ OpLIterInit, {Local, Local, OutUnknown }},
{ OpIterInitK, {Stack1, Local, OutUnknown }},
{ OpMIterInitK, {Stack1, Local, OutUnknown }},
{ OpWIterInitK, {Stack1, Local, OutUnknown }},
{ OpLIterInitK, {Local, Local, OutUnknown }},
{ OpIterNext, {None, Local, OutUnknown }},
{ OpMIterNext, {None, Local, OutUnknown }},
{ OpWIterNext, {None, Local, OutUnknown }},
{ OpLIterNext, {Local, Local, OutUnknown }},
{ OpIterNextK, {None, Local, OutUnknown }},
{ OpMIterNextK, {None, Local, OutUnknown }},
{ OpWIterNextK, {None, Local, OutUnknown }},
{ OpLIterNextK, {Local, Local, OutUnknown }},
{ OpIterFree, {None, None, OutNone }},
{ OpMIterFree, {None, None, OutNone }},
{ OpCIterFree, {None, None, OutNone }},
{ OpLIterFree, {Local, None, OutNone }},
{ OpIterBreak, {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 }},
{ OpDefFunc, {None, None, OutNone }},
{ OpDefTypeAlias,{None, None, OutNone }},
{ OpDefCls, {None, None, OutNone }},
{ OpDefCns, {Stack1, Stack1, OutBoolean }},
{ OpAliasCls, {Stack1, Stack1, OutBoolean }},
/*** 13. Miscellaneous instructions ***/
{ OpThis, {None, Stack1, OutThisObject }},
{ OpBareThis, {None, Stack1, OutUnknown }},
{ OpCheckThis, {This, None, OutNone }},
{ OpInitThisLoc,
{None, Local, OutUnknown }},
{ OpStaticLocCheck,
{None, Stack1|Local, OutBoolean }},
{ OpStaticLocDef,
{Stack1, Local, OutVUnknown }},
{ OpStaticLocInit,
{Stack1, Local, OutVUnknown }},
{ OpCatch, {None, Stack1, OutObject }},
{ OpChainFaults, {StackTop2, Stack1, OutObject }},
{ OpVerifyParamType,
{Local, Local, OutUnknown }},
{ OpVerifyRetTypeV,
{Stack1, Stack1, OutSameAsInput1 }},
{ OpVerifyRetTypeC,
{Stack1, Stack1, OutSameAsInput1 }},
{ OpVerifyRetNonNullC,
{Stack1, Stack1, OutSameAsInput1 }},
{ OpVerifyOutType,
{Stack1, Stack1, OutSameAsInput1 }},
{ OpOODeclExists,
{StackTop2, Stack1, OutBoolean }},
{ OpSelf, {None, None, OutNone }},
{ OpParent, {None, None, OutNone }},
{ OpLateBoundCls,{None, None, OutNone }},
{ OpNativeImpl, {None, None, OutNone }},
{ OpCreateCl, {BStackN, Stack1, OutObject }},
{ OpIdx, {StackTop3, Stack1, OutUnknown }},
{ OpArrayIdx, {StackTop3, Stack1, OutUnknown }},
{ OpCheckProp, {None, Stack1, OutBoolean }},
{ OpInitProp, {Stack1, None, OutNone }},
{ OpSilence, {Local|DontGuardAny,
Local, OutNone }},
{ OpAssertRATL, {None, None, OutNone }},
{ OpAssertRATStk,{None, None, OutNone }},
{ OpBreakTraceHint,{None, None, OutNone }},
{ OpGetMemoKeyL, {Local, Stack1, OutUnknown }},
{ OpResolveFunc, {None, Stack1, OutFunc }},
{ OpResolveObjMethod,
{StackTop2, Stack1, OutVArray }},
/*** 14. Generator instructions ***/
{ OpCreateCont, {None, Stack1, OutNull }},
{ OpContEnter, {Stack1, Stack1, OutUnknown }},
{ OpContRaise, {Stack1, Stack1, OutUnknown }},
{ OpYield, {Stack1, Stack1, OutUnknown }},
{ OpYieldK, {StackTop2, Stack1, OutUnknown }},
{ OpContAssignDelegate,
{Stack1, None, OutNone }},
{ OpContEnterDelegate,
{Stack1, None, OutNone }},
{ OpYieldFromDelegate,
{None, Stack1, OutUnknown }},
{ OpContUnsetDelegate,
{None, None, OutNone }},
{ OpContCheck, {None, None, OutNone }},
{ OpContValid, {None, Stack1, OutBoolean }},
{ OpContStarted, {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 ***/
{ OpBaseNC, {StackI, MBase, OutNone }},
{ OpBaseNL, {Local, MBase, OutNone }},
{ OpBaseGC, {StackI, MBase, OutNone }},
{ OpBaseGL, {Local, MBase, OutNone }},
{ OpBaseSC, {StackI, MBase, OutNone }},
{ OpBaseSL, {Local, MBase, OutNone }},
{ OpBaseL, {Local, MBase, OutNone }},
{ OpBaseC, {StackI, MBase, OutNone }},
{ OpBaseR, {StackI, MBase, OutNone }},
{ OpBaseH, {None, MBase, OutNone }},
{ OpDim, {MBase|MKey, MBase, OutNone }},
{ OpQueryM, {BStackN|MBase|MKey,
Stack1, OutUnknown }},
{ OpVGetM, {BStackN|MBase|MKey,
Stack1, OutVUnknown }},
{ OpSetM, {Stack1|BStackN|MBase|MKey,
Stack1, OutUnknown }},
{ OpIncDecM, {BStackN|MBase|MKey,
Stack1, OutUnknown }},
{ OpSetOpM, {Stack1|BStackN|MBase|MKey,
Stack1, OutUnknown }},
{ OpBindM, {Stack1|BStackN|MBase|MKey,
Stack1, OutSameAsInput1 }},
{ OpUnsetM, {BStackN|MBase|MKey,
None, OutNone }},
{ OpSetWithRefLML,
{MBase, None, OutNone }},
{ OpSetWithRefRML,
{Stack1|MBase, None, OutNone }},
{ OpMemoGet, {LocalRange, None, OutUnknown }},
{ OpMemoSet, {Stack1|LocalRange,
Stack1, OutSameAsInput1 }},
};
namespace {
hphp_hash_map<Op, InstrInfo> instrInfo;
bool instrInfoInited;
}
void initInstrInfo() {
if (!instrInfoInited) {
for (auto& info : instrInfoSparse) {
instrInfo[info.op] = info.info;
}
if (!RuntimeOption::EvalCheckReturnTypeHints) {
for (size_t j = 0; j < 2; ++j) {
auto& ii = instrInfo[j == 0 ? OpVerifyRetTypeC : OpVerifyRetTypeV];
ii.in = ii.out = None;
ii.type = OutNone;
}
}
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 | AllLocals | DontGuardStack1 |
IgnoreInnerType | DontGuardAny | This | MBase | StackI | MKey | LocalRange |
DontGuardBase;
mask &= ~ignore;
static const uint64_t counts[][2] = {
{Stack3, 1},
{Stack2, 1},
{Stack1, 1},
{StackIns1, 2},
{FStack, kNumActRecCells},
};
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::FCallAwait:
return getImm(pc, 0).u_IVA + kNumActRecCells;
case Op::FCall: {
auto const fca = getImm(pc, 0).u_FCA;
return fca.numArgs + (fca.hasUnpack ? 1 : 0) + fca.numRets +
kNumActRecCells - 1;
}
case Op::QueryM:
case Op::VGetM:
case Op::IncDecM:
case Op::UnsetM:
case Op::NewPackedArray:
case Op::NewVecArray:
case Op::NewKeysetArray:
case Op::NewVArray:
case Op::ConcatN:
case Op::FCallBuiltin:
case Op::CreateCl:
return getImm(pc, 0).u_IVA;
case Op::SetM:
case Op::SetOpM:
case Op::BindM:
return getImm(pc, 0).u_IVA + 1;
case Op::NewStructArray:
case Op::NewStructDArray:
case Op::NewStructDict:
return getImmVector(pc).size();
default: break;
}
uint64_t mask = getInstrInfo(op).in;
int64_t count = 0;
// All instructions with these properties are handled above
assertx((mask & (StackN | BStackN)) == 0);
return count + countOperands(mask);
}
int64_t getStackPushed(PC pc) {
auto const op = peek_op(pc);
switch (op) {
case Op::FCall: 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::BoxRNop:
case Op::DefClsNop:
case Op::Nop:
case Op::UnboxRNop:
case Op::RGetCNop:
case Op::CGetCUNop:
case Op::UGetCUNop:
case Op::EntryNop:
return true;
case Op::VerifyRetTypeC:
case Op::VerifyRetTypeV:
return !RuntimeOption::EvalCheckReturnTypeHints;
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)
// Iterator bytecodes are handled specially here
#define LA(n) assertx(idx == 0xff); idx = n;
#define MA(n)
#define BLA(n)
#define SLA(n)
#define ILA(n)
#define I32LA(n)
#define BLLA(n)
#define IVA(n)
#define I64A(n)
#define IA(n)
#define CAR(n)
#define CAW(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 FCA(n)
#define O(name, imm, ...) case Op::name: imm break;
size_t localImmIdx(Op op) {
switch (op) {
case Op::LIterInit:
case Op::LIterInitK:
case Op::LIterNext:
case Op::LIterNextK:
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 KA
#define LA(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 LA
#undef MA
#undef BLA
#undef SLA
#undef ILA
#undef I32LA
#undef BLLA
#undef IVA
#undef I64A
#undef IA
#undef CAR
#undef CAW
#undef DA
#undef SA
#undef AA
#undef RATA
#undef BA
#undef OA
#undef VSA
#undef KA
#undef LAR
#undef FCA
#undef O
/*
* Get location metadata for the inputs of `ni'.
*/
InputInfoVec getInputs(NormalizedInstruction& ni, FPInvOffset 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 & FStack) {
assertx(isFCallStar(ni.op()));
// arguments consumed
stackOff -= ni.op() == Op::FCallAwait
? ni.imm[0].u_IVA
: ni.imm[0].u_FCA.numArgs + (ni.imm[0].u_FCA.hasUnpack ? 1 : 0);
stackOff -= kNumActRecCells; // ActRec is torn down as well
}
if (flags & IgnoreInnerType) ni.ignoreInnerType = true;
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<FPInvOffset>(bcSPOff)
});
}
if (flags & StackN) {
int numArgs = (ni.op() == Op::NewPackedArray ||
ni.op() == Op::NewVecArray ||
ni.op() == Op::NewKeysetArray ||
ni.op() == Op::NewVArray ||
ni.op() == Op::ConcatN)
? ni.imm[0].u_IVA
: ni.immVec.numStackValues();
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 (flags & Local) {
// (Almost) all instructions that take a Local have its index at their
// first immediate.
if (ni.op() == Op::IterBreak) {
for (auto const& it : ni.immIters) {
if (it.kind != KindOfLIter) continue;
SKTRACE(1, sk, "getInputs: local %d\n", it.local);
inputs.emplace_back(Location::Local { uint32_t(it.local) });
}
} else {
auto const loc = ni.imm[localImmIdx(ni.op())].u_IVA;
SKTRACE(1, sk, "getInputs: local %d\n", loc);
inputs.emplace_back(Location::Local { uint32_t(loc) });
}
}
if (flags & LocalRange) {
auto const& range = ni.imm[1].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 & AllLocals) ni.ignoreInnerType = true;
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.iva) });
break;
case MEC:
case MPC:
inputs.emplace_back(Location::Stack {
BCSPRelOffset{int32_t(mk.iva)}.to<FPInvOffset>(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;
}
bool dontGuardAnyInputs(const NormalizedInstruction& ni) {
switch (ni.op()) {
case Op::IterBreak:
case Op::DecodeCufIter:
case Op::IterNext:
case Op::IterNextK:
case Op::LIterNext:
case Op::LIterNextK:
case Op::WIterInit:
case Op::WIterInitK:
case Op::WIterNext:
case Op::WIterNextK:
case Op::MIterInit:
case Op::MIterInitK:
case Op::MIterNext:
case Op::MIterNextK:
case Op::IterInitK:
case Op::IterInit:
case Op::LIterInitK:
case Op::LIterInit:
case Op::JmpZ:
case Op::JmpNZ:
case Op::Jmp:
case Op::JmpNS:
case Op::FCall:
case Op::FCallAwait:
case Op::ClsCnsD:
case Op::FIsParamByRef:
case Op::FIsParamByRefCufIter:
case Op::FThrowOnRefMismatch:
case Op::FCallBuiltin:
case Op::NewStructArray:
case Op::NewStructDArray:
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::DefCls:
case Op::AliasCls:
case Op::Eq:
case Op::Neq:
case Op::AssertRATL:
case Op::AssertRATStk:
case Op::SetL:
case Op::BindL:
case Op::EmptyL:
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::ClsRefGetC:
case Op::ClsRefGetL:
case Op::AKExists:
case Op::AddElemC:
case Op::AddNewElemC:
case Op::Array:
case Op::Dict:
case Op::Keyset:
case Op::Vec:
case Op::ArrayIdx:
case Op::BareThis:
case Op::BindG:
case Op::BindS:
case Op::BitNot:
case Op::CGetG:
case Op::CGetQuietG:
case Op::CGetL:
case Op::CGetQuietL:
case Op::CGetL2:
case Op::CGetS:
case Op::CUGetL:
case Op::CIterFree:
case Op::CastArray:
case Op::CastDouble:
case Op::CastInt:
case Op::CastObject:
case Op::CastString:
case Op::CastDict:
case Op::CastKeyset:
case Op::CastVec:
case Op::CastVArray:
case Op::CastDArray:
case Op::DblAsBits:
case Op::CheckProp:
case Op::CheckThis:
case Op::Clone:
case Op::Cns:
case Op::CnsE:
case Op::CnsU:
case Op::ColFromArray:
case Op::ConcatN:
case Op::Concat:
case Op::ContCheck:
case Op::ContCurrent:
case Op::ContKey:
case Op::ContValid:
case Op::ContStarted:
case Op::ContGetReturn:
case Op::CreateCl:
case Op::DefCns:
case Op::DefFunc:
case Op::Dir:
case Op::Div:
case Op::Double:
case Op::Dup:
case Op::EmptyG:
case Op::EmptyS:
case Op::FPushClsMethodD:
case Op::FPushClsMethod:
case Op::FPushClsMethodS:
case Op::FPushClsMethodSD:
case Op::FPushCtor:
case Op::FPushCtorD:
case Op::FPushCtorI:
case Op::FPushCtorS:
case Op::FPushCufIter:
case Op::FPushFunc:
case Op::FPushFuncD:
case Op::FPushFuncU:
case Op::FPushObjMethodD:
case Op::ResolveFunc:
case Op::ResolveObjMethod:
case Op::False:
case Op::File:
case Op::GetMemoKeyL:
case Op::Idx:
case Op::InitThisLoc:
case Op::InstanceOf:
case Op::InstanceOfD:
case Op::IsTypeStruct:
case Op::AsTypeStruct:
case Op::Int:
case Op::IssetG:
case Op::IssetL:
case Op::IssetS:
case Op::IterFree:
case Op::LIterFree:
case Op::LateBoundCls:
case Op::Method:
case Op::MIterFree:
case Op::Mod:
case Op::Pow:
case Op::ClsRefName:
case Op::NativeImpl:
case Op::NewArray:
case Op::NewCol:
case Op::NewPair:
case Op::NewLikeArrayL:
case Op::NewMixedArray:
case Op::NewDictArray:
case Op::NewPackedArray:
case Op::NewVecArray:
case Op::NewKeysetArray:
case Op::NewVArray:
case Op::NewDArray:
case Op::Not:
case Op::Null:
case Op::NullUninit:
case Op::OODeclExists:
case Op::Parent:
case Op::DiscardClsRef:
case Op::PopC:
case Op::PopR:
case Op::PopV:
case Op::PopU:
case Op::PopL:
case Op::Print:
case Op::PushL:
case Op::RetC:
case Op::RetV:
case Op::Self:
case Op::SetG:
case Op::SetS:
case Op::Shl:
case Op::Shr:
case Op::Silence:
case Op::StaticLocDef:
case Op::StaticLocCheck:
case Op::StaticLocInit:
case Op::String:
case Op::This:
case Op::True:
case Op::Unbox:
case Op::UnboxR:
case Op::UnsetL:
case Op::VGetG:
case Op::VGetL:
case Op::VGetS:
case Op::VerifyParamType:
case Op::VerifyRetTypeC:
case Op::VerifyRetNonNullC:
case Op::VerifyRetTypeV:
case Op::VerifyOutType:
case Op::WHResult:
case Op::Xor:
case Op::BaseNC:
case Op::BaseNL:
case Op::BaseGC:
case Op::BaseGL:
case Op::BaseSC:
case Op::BaseSL:
case Op::BaseL:
case Op::BaseC:
case Op::BaseR:
case Op::BaseH:
case Op::Dim:
case Op::QueryM:
case Op::VGetM:
case Op::SetM:
case Op::IncDecM:
case Op::SetOpM:
case Op::BindM:
case Op::UnsetM:
case Op::SetWithRefLML:
case Op::SetWithRefRML:
case Op::MemoGet:
case Op::MemoSet:
case Op::RetM:
case Op::Select:
return false;
// These are instructions that are always interp-one'd, or are always no-ops.
case Op::Nop:
case Op::EntryNop:
case Op::Box:
case Op::BoxR:
case Op::BoxRNop:
case Op::UnboxRNop:
case Op::RGetCNop:
case Op::CGetCUNop:
case Op::UGetCUNop:
case Op::AddElemV:
case Op::AddNewElemV:
case Op::ClsCns:
case Op::Exit:
case Op::Fatal:
case Op::Unwind:
case Op::Throw:
case Op::CGetN:
case Op::CGetQuietN:
case Op::VGetN:
case Op::IssetN:
case Op::EmptyN:
case Op::SetN:
case Op::SetOpN:
case Op::SetOpG:
case Op::SetOpS:
case Op::IncDecN:
case Op::IncDecG:
case Op::IncDecS:
case Op::BindN:
case Op::UnsetN:
case Op::UnsetG:
case Op::FPushObjMethod:
case Op::Incl:
case Op::InclOnce:
case Op::Req:
case Op::ReqOnce:
case Op::ReqDoc:
case Op::Eval:
case Op::DefClsNop:
case Op::DefTypeAlias:
case Op::Catch:
case Op::ChainFaults:
case Op::ContAssignDelegate:
case Op::ContEnterDelegate:
case Op::YieldFromDelegate:
case Op::ContUnsetDelegate:
return true;
case Op::FHandleRefMismatch:
return static_cast<FPassHint>(ni.imm[1].u_OA) == FPassHint::Any || (
!RuntimeOption::EvalThrowOnCallByRefAnnotationMismatch &&
!RuntimeOption::EvalWarnOnCallByRefAnnotationMismatch
);
}
always_assert_flog(0, "invalid opcode {}\n", static_cast<uint32_t>(ni.op()));
}
bool instrBreaksProfileBB(const NormalizedInstruction* inst) {
if (isFCallStar(inst->op())) return true;
if (instrIsNonCallControlFlow(inst->op()) ||
inst->op() == OpAwait || // may branch to scheduler and suspend execution
inst->op() == OpAwaitAll || // similar to Await
inst->op() == OpFCallAwait || // similar to Await
inst->op() == OpClsCnsD || // side exits if misses in the RDS
inst->op() == OpVerifyParamType) { // avoids combinatorial explosion 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;
}
//////////////////////////////////////////////////////////////////////
#define IMM_BLA(n) ni.immVec
#define IMM_SLA(n) ni.immVec
#define IMM_ILA(n) ni.immIters
#define IMM_I32LA(n) ni.immVec
#define IMM_BLLA(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_IA(n) ni.imm[n].u_IA
#define IMM_CAR(n) ni.imm[n].u_CAR
#define IMM_CAW(n) ni.imm[n].u_CAW
#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_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 NA /* */
static 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 FIVE
#undef FOUR
#undef THREE
#undef TWO
#undef ONE
#undef NA
#undef IMM_BLA
#undef IMM_SLA
#undef IMM_ILA
#undef IMM_I32LA
#undef IMM_BLLA
#undef IMM_IVA
#undef IMM_I64A
#undef IMM_LA
#undef IMM_IA
#undef IMM_CAR
#undef IMM_CAW
#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_FCA
//////////////////////////////////////////////////////////////////////
namespace {
Type flavorToType(FlavorDesc f) {
switch (f) {
case NOV: not_reached();
case CV: return TCell; // TODO(#3029148) this could be InitCell
case CUV: return TCell;
case UV: return TUninit;
case VV: return TBoxedInitCell;
case RV: case CRV: case CVV: case CVUV: return TGen;
}
not_reached();
}
}
void translateInstr(irgen::IRGS& irgs, const NormalizedInstruction& ni,
bool /*checkOuterTypeOnly*/, bool /*firstInst*/
) {
irgen::prepareForNextHHBC(
irgs,
&ni,
ni.source,
ni.endsRegion && !irgen::isInlining(irgs)
);
const Func* builtinFunc = nullptr;
if (ni.op() == OpFCallBuiltin) {
auto str = ni.m_unit->lookupLitstrId(ni.imm[2].u_SA);
builtinFunc = Unit::lookupBuiltin(str);
}
auto pc = ni.pc();
for (auto i = 0, num = instrNumPops(pc); i < num; ++i) {
if (ni.op() == OpFCall && 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 =
!builtinFunc ? flavorToType(instrInputFlavor(pc, i)) :
builtinFunc->byRef(num - i - 1) ? TGen : TCell;
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, ni);
return;
}
if (ni.forceSurpriseCheck) {
surpriseCheck(irgs);
}
translateDispatch(irgs, ni);
FTRACE(3, "\nTranslated {}: {} with state:\n{}\n",
ni.offset(), ni, show(irgs));
}
//////////////////////////////////////////////////////////////////////
const Func* lookupImmutableMethod(const Class* cls, const StringData* name,
bool& magicCall, bool staticLookup,
const Func* ctxFunc,
bool exactClass) {
if (!cls) return nullptr;
if (cls->attrs() & AttrInterface) return nullptr;
auto ctx = ctxFunc->cls();
const Func* func;
LookupResult res = staticLookup ?
lookupClsMethod(func, cls, name, nullptr, ctx, false) :
lookupObjMethod(func, cls, name, ctx, false);
if (res == LookupResult::MethodNotFound) return nullptr;
if (func->isAbstract() && exactClass) return nullptr;
assertx(res == LookupResult::MethodFoundWithThis ||
res == LookupResult::MethodFoundNoThis ||
(staticLookup ?
res == LookupResult::MagicCallStaticFound :
res == LookupResult::MagicCallFound));
magicCall =
res == LookupResult::MagicCallStaticFound ||
res == LookupResult::MagicCallFound;
if (staticLookup) {
if (magicCall) {
if (ctx && !ctxFunc->isStatic() &&
(ctx->classof(cls) || cls->classof(ctx))) {
// we might need to call __call instead
return nullptr;
}
if (!exactClass && !(cls->attrs() & AttrNoOverride)) {
// there might be a derived class which defines the method
return nullptr;
}
}
} else if (!exactClass && !(func->attrs() & AttrPrivate)) {
if (magicCall) {
if (!(cls->attrs() & AttrNoOverride)) {
return nullptr;
}
}
}
return func;
}
const Func* lookupImmutableCtor(const Class* cls, const Class* ctx) {
if (!cls) return nullptr;
auto const func = cls->getCtor();
if (func && !(func->attrs() & AttrPublic)) {
auto fcls = func->cls();
if (fcls != ctx) {
if (!ctx) return nullptr;
if ((func->attrs() & AttrPrivate) ||
!(ctx->classof(fcls) || fcls->classof(ctx))) {
return nullptr;
}
}
}
return func;
}
ImmutableFuncLookup lookupImmutableFunc(const Unit* unit,
const StringData* name) {
// Trust nothing if we can rename functions
if (RuntimeOption::EvalJitEnableRenameFunction) return {nullptr, true};
auto const ne = NamedEntity::get(name);
if (auto const f = ne->uniqueFunc()) {
// We have an unique function. However, it may be conditionally defined
// (!f->top()) or interceptable, which means we can't use it directly.
if (!f->top() || f->isInterceptable()) return {nullptr, true};
// We can use this function. If its persistent (which means its unit's
// pseudo-main is trivial), its safe to use unconditionally. If its defined
// in the same unit as the caller, its also safe to use unconditionally. By
// virtue of the fact that we're already in the unit, we know its already
// defined.
if (f->isPersistent() || f->unit() == unit) return {f, false};
// Use the function, but ensure its unit is loaded.
return {f, true};
}
// There's no unique function currently known for this name. However, if the
// current unit defines a single top-level function with this name, we can use
// it. Why? When this unit is loaded, either it successfully defined the
// function, in which case its the correct function, or it fataled, which
// means this code won't run anyways.
Func* found = nullptr;
for (auto& f : unit->funcs()) {
if (f->isPseudoMain()) continue;
if (!f->name()->isame(name)) continue;
if (found) {
// Function with duplicate name
found = nullptr;
break;
}
found = f;
}
if (found && found->top() && !found->isInterceptable()) {
return {found, false};
}
return {nullptr, true};
}
///////////////////////////////////////////////////////////////////////////////
}
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);
});
}
///////////////////////////////////////////////////////////////////////////////
}