hphp/runtime/vm/jit/extra-data.h (2,322 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. | +----------------------------------------------------------------------+ */ #pragma once #include "hphp/runtime/base/collections.h" #include "hphp/runtime/base/typed-value.h" #include "hphp/runtime/vm/bytecode.h" #include "hphp/runtime/vm/hhbc.h" #include "hphp/runtime/vm/iter.h" #include "hphp/runtime/vm/srckey.h" #include "hphp/runtime/vm/jit/alias-class.h" #include "hphp/runtime/vm/jit/types.h" #include "hphp/runtime/vm/jit/ir-opcode.h" #include "hphp/runtime/vm/jit/stack-offsets.h" #include "hphp/runtime/ext/generator/ext_generator.h" #include "hphp/util/arena.h" #include "hphp/util/optional.h" #include "hphp/util/ringbuffer.h" #include "hphp/util/safe-cast.h" #include <folly/Conv.h> #include <folly/Hash.h> #include <folly/gen/Base.h> #include <folly/gen/String.h> #include <algorithm> #include <string> namespace HPHP::jit { ////////////////////////////////////////////////////////////////////// /* * Some IRInstructions with compile-time-only constants may carry along extra * data in the form of one of these structures. * * Note that this isn't really appropriate for compile-time constants that are * actually representing user values (we want them to be visible to * optimization passes, allocatable to registers, etc), just compile-time * metadata. * * These types must: * * - Derive from IRExtraData (for overloading purposes). * - Be arena-allocatable (no non-trivial destructors). * - Either CopyConstructible, or implement a clone member function that * takes an arena to clone to. * - Implement an equals() member that indicates equality. * - Implement a stableHash() method that is invariant across process * restarts. * * In addition, extra data belonging to IRInstructions that may be hashed in * IRInstrTables must: * * - Implement a hash() method. * * Finally, optionally they may implement a show() method for use in debug * printouts. */ /* * Traits that returns the type of the extra C++ data structure for a * given instruction, if it has one, along with some other information * about the type. */ template<Opcode op> struct OpHasExtraData { enum { value = 0 }; }; template<Opcode op> struct IRExtraDataType; template<typename T> struct SharedProfileEntry; struct DecRefProfile; using DecRefProfileEntry = SharedProfileEntry<DecRefProfile>; ////////////////////////////////////////////////////////////////////// struct IRExtraData {}; ////////////////////////////////////////////////////////////////////// /* * Shared IRExtraData classes. * * These subtypes represent common parameters (e.g., statically known Func, * local variable ID, stack offset, etc.) that are useful for a variety of HHIR * instructions. * * These are kept separate from the one-off IRExtraDatas to make it easier to * find existing common parameters. */ /* * Class pointer. * * Required to be non-null. */ struct ClassData : IRExtraData { explicit ClassData(const Class* cls) : cls(cls) { assertx(cls != nullptr); } std::string show() const { return folly::to<std::string>(cls->name()->data()); } bool equals(const ClassData& o) const { return cls == o.cls; } size_t hash() const { return pointer_hash<Class>()(cls); } size_t stableHash() const { return cls->stableHash(); } const Class* cls; }; struct OptClassData : IRExtraData { explicit OptClassData(const Class* cls) : cls(cls) {} std::string show() const { return folly::to<std::string>(cls ? cls->name()->data() : "{null}"); } bool equals(const OptClassData& o) const { return cls == o.cls; } size_t hash() const { return pointer_hash<Class>()(cls); } size_t stableHash() const { return (cls ? cls->stableHash() : 0); } const Class* cls; }; /* * Class pointer, suppress flag, is or as operation flag and range into the * stack (for type structures) needed for resolve type struct instruction * * Class pointer could be null. */ struct ResolveTypeStructData : IRExtraData { explicit ResolveTypeStructData( const Class* cls, bool suppress, IRSPRelOffset offset, uint32_t size, bool isOrAsOp ) : cls(cls) , suppress(suppress) , offset(offset) , size(size) , isOrAsOp(isOrAsOp) {} std::string show() const { return folly::sformat("{},{},{},{},{}", cls ? cls->name()->data() : "nullptr", suppress ? "suppress" : "no-suppress", offset.offset, size, isOrAsOp); } bool equals(const ResolveTypeStructData& o) const { return cls == o.cls && suppress == o.suppress && offset == o.offset && size == o.size && isOrAsOp == o.isOrAsOp; } size_t hash() const { return (pointer_hash<Class>()(cls) + std::hash<int32_t>()(offset.offset) + std::hash<uint32_t>()(size)) ^ ((int64_t)(suppress ? -1 : 0) << 32 | (isOrAsOp ? -1 : 0)); } size_t stableHash() const { return folly::hash::hash_combine( cls ? cls->stableHash() : 0, std::hash<int32_t>()(offset.offset), std::hash<uint32_t>()(size), std::hash<uint8_t>()(suppress << 1 | isOrAsOp) ); } const Class* cls; bool suppress; IRSPRelOffset offset; uint32_t size; bool isOrAsOp; }; /* * ExtendsClass. */ struct ExtendsClassData : IRExtraData { explicit ExtendsClassData(const Class* cls, bool strictLikely = false) : cls(cls), strictLikely(strictLikely) { assertx(cls != nullptr); } std::string show() const { return folly::sformat("{}{}", cls->name(), strictLikely ? ":strictLikely" : ""); } bool equals(const ExtendsClassData& o) const { return cls == o.cls && strictLikely == o.strictLikely; } size_t hash() const { return pointer_hash<Class>()(cls) ^ (strictLikely ? -1 : 0); } size_t stableHash() const { return cls->stableHash() ^ (strictLikely ? -1 : 0); } const Class* cls; bool strictLikely; }; /* * InstanceOfIfaceVtable. */ struct InstanceOfIfaceVtableData : IRExtraData { InstanceOfIfaceVtableData(const Class* cls, bool canOptimize) : cls(cls), canOptimize(canOptimize) { assertx(cls != nullptr); } std::string show() const { return folly::sformat("{}{}", cls->name(), canOptimize ? ":canOptimize" : ""); } bool equals(const InstanceOfIfaceVtableData& o) const { return cls == o.cls && canOptimize == o.canOptimize; } size_t hash() const { return pointer_hash<Class>()(cls) ^ (canOptimize ? -1 : 0); } size_t stableHash() const { return cls->stableHash() ^ (canOptimize ? -1 : 0); } const Class* cls; bool canOptimize; }; /* * Class with method name. */ struct ClsMethodData : IRExtraData { ClsMethodData(const StringData* cls, const StringData* method, const NamedEntity* ne, const Class* context) : clsName(cls) , methodName(method) , namedEntity(ne) , context(context) {} std::string show() const { return folly::sformat( "{}::{} ({})", clsName, methodName, context ? context->name()->data() : "{no context}"); } bool equals(const ClsMethodData& o) const { // The strings are static so we can use pointer equality. return clsName == o.clsName && methodName == o.methodName && context == o.context; } size_t hash() const { return folly::hash::hash_combine( std::hash<const StringData*>()(clsName), std::hash<const StringData*>()(methodName), std::hash<const Class*>()(context) ); } size_t stableHash() const { return folly::hash::hash_combine( clsName->hashStatic(), methodName->hashStatic(), context ? context->stableHash() : 0 ); } const StringData* clsName; const StringData* methodName; const NamedEntity* namedEntity; const Class* context; }; struct IfaceMethodData : IRExtraData { IfaceMethodData(Slot vtableIdx, Slot methodIdx) : vtableIdx(vtableIdx) , methodIdx(methodIdx) {} std::string show() const { return folly::sformat("{}, {}", vtableIdx, methodIdx); } bool equals(const IfaceMethodData& o) const { return vtableIdx == o.vtableIdx && methodIdx == o.methodIdx; } size_t hash() const { return hash_int64((int64_t)vtableIdx << 32 | methodIdx); } size_t stableHash() const { return hash_int64((int64_t)vtableIdx << 32 | methodIdx); } Slot vtableIdx; Slot methodIdx; }; /* * Func */ struct FuncData : IRExtraData { explicit FuncData(const Func* f) : func(f) {} std::string show() const { return folly::format("{}", func->fullName()).str(); } bool equals(const FuncData& f) const { return func == f.func; } size_t stableHash() const { return func->stableHash(); } const Func* func; }; /* * Func with argument index. */ struct FuncArgData : IRExtraData { explicit FuncArgData(const Func* f, int64_t arg) : func(f) , argNum(arg) {} std::string show() const { return folly::format("{},{}", func->name(), argNum).str(); } bool equals(const FuncArgData& o) const { return func == o.func && argNum == o.argNum; } size_t stableHash() const { return folly::hash::hash_combine( func->stableHash(), std::hash<int64_t>()(argNum) ); } const Func* func; int64_t argNum; }; /* * Func with argument index and expected type. */ struct FuncArgTypeData : IRExtraData { explicit FuncArgTypeData(const Func* f, int64_t arg, const StringData* t) : func(f) , argNum(arg) , type(t) {} std::string show() const { return folly::format("{},{},{}", func->name(), argNum, type).str(); } bool equals(const FuncArgTypeData& o) const { return func == o.func && argNum == o.argNum && type == o.type; } size_t stableHash() const { return folly::hash::hash_combine( func->stableHash(), std::hash<int64_t>()(argNum), type->hashStatic() ); } const Func* func; int64_t argNum; const StringData* type; }; /* * Local variable ID. */ struct LocalId : IRExtraData { explicit LocalId(uint32_t id) : locId(id) {} std::string show() const { return folly::to<std::string>(locId); } bool equals(LocalId o) const { return locId == o.locId; } size_t hash() const { return std::hash<uint32_t>()(locId); } size_t stableHash() const { return std::hash<uint32_t>()(locId); } uint32_t locId; }; /* * Index into, e.g., an array. */ struct IndexData : IRExtraData { explicit IndexData(uint32_t index) : index(index) {} std::string show() const { return folly::format("{}", index).str(); } size_t hash() const { return std::hash<uint32_t>()(index); } size_t stableHash() const { return std::hash<uint32_t>()(index); } bool equals(const IndexData& o) const { return index == o.index; } uint32_t index; }; /* * An index and a key used to initialized a dict-ish array element. */ struct KeyedIndexData : IRExtraData { explicit KeyedIndexData(uint32_t index, const StringData* key) : index(index) , key(key) {} std::string show() const; size_t stableHash() const { return folly::hash::hash_combine( std::hash<uint32_t>()(index), key->hashStatic() ); } bool equals(const KeyedIndexData& o) const { return index == o.index && key == o.key; } uint32_t index; const StringData* key; }; /* * Used to optimize array accesses. Does not change semantics, but changes how * we do the lookup - e.g. we scan small static arrays for static string keys. * * NOTE: Currently, we only use this hint in DictIdx. We may want * to use it for ArrayExists / etc. */ struct SizeHintData : IRExtraData { enum SizeHint { Default, SmallStatic }; SizeHintData() : hint(SizeHint::Default) {} explicit SizeHintData(SizeHint hint) : hint(hint) {} std::string show() const { switch (hint) { case SizeHint::Default: return "Default"; case SizeHint::SmallStatic: return "SmallStatic"; } not_reached(); } size_t hash() const { return stableHash(); } size_t stableHash() const { return std::hash<SizeHint>()(hint); } bool equals(const SizeHintData& o) const { return hint == o.hint; } SizeHint hint; }; /* * Iterator ID. */ struct IterId : IRExtraData { explicit IterId(uint32_t id) : iterId(id) {} std::string show() const { return folly::to<std::string>(iterId); } bool equals(IterId o) const { return iterId == o.iterId; } size_t hash() const { return std::hash<uint32_t>()(iterId); } size_t stableHash() const { return std::hash<uint32_t>()(iterId); } uint32_t iterId; }; /* * Iter instruction data, used for both key-value and value-only iterators. * Check args.hasKey() to distinguish between the two. */ struct IterData : IRExtraData { explicit IterData(IterArgs args) : args(args) {} std::string show() const { return HPHP::show(args, [&](int32_t id) { return folly::to<std::string>(id); }); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(args.iterId), std::hash<int32_t>()(args.keyId), std::hash<int32_t>()(args.valId), std::hash<IterArgs::Flags>()(args.flags) ); } bool equals(const IterData& o) const { return args == o.args; } IterArgs args; }; struct IterTypeData : IRExtraData { IterTypeData(uint32_t iterId, IterSpecialization type, ArrayLayout layout) : iterId{iterId} , type{type} , layout{layout} { always_assert(type.specialized); } std::string show() const { auto const type_str = HPHP::show(type); auto const layout_str = layout.describe(); return folly::format("{}::{}::{}", iterId, type_str, layout_str).str(); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<uint32_t>()(iterId), std::hash<uint8_t>()(type.as_byte), std::hash<uint16_t>()(layout.toUint16()) ); } bool equals(const IterTypeData& o) const { return iterId == o.iterId && type.as_byte == o.type.as_byte && layout == o.layout; } uint32_t iterId; IterSpecialization type; ArrayLayout layout; }; struct IterOffsetData : IRExtraData { IterOffsetData(int16_t offset) : offset(offset) {} std::string show() const { return folly::to<std::string>(offset); } size_t stableHash() const { return std::hash<int16_t>()(offset); } bool equals(const IterOffsetData& o) const { return offset == o.offset; } int16_t offset; }; /* * RDS handle. */ struct RDSHandleData : IRExtraData { explicit RDSHandleData(rds::Handle handle) : handle(handle) {} std::string show() const { return folly::to<std::string>(handle); } bool equals(RDSHandleData o) const { return handle == o.handle; } size_t hash() const { return std::hash<uint32_t>()(handle); } size_t stableHash() const { auto const sym = rds::reverseLink(handle); if (!sym) return 0; return rds::symbol_stable_hash(*sym); } rds::Handle handle; }; struct RDSHandleAndType : RDSHandleData { RDSHandleAndType(rds::Handle handle, Type type) : RDSHandleData{handle}, type{type} { assertx(type <= TCell); } std::string show() const { return folly::sformat("{},{}", handle, type); } bool equals(const RDSHandleAndType& o) const { return handle == o.handle && type == o.type; } size_t hash() const { return folly::hash::hash_combine( RDSHandleData::hash(), type.hash() ); } size_t stableHash() const { return folly::hash::hash_combine( RDSHandleData::stableHash(), type.stableHash() ); } Type type; }; struct ArrayAccessProfileData : RDSHandleData { ArrayAccessProfileData(rds::Handle handle, DecRefProfileEntry* extra) : RDSHandleData(handle), extra{extra} {} std::string show() const { if (!extra) return RDSHandleData::show(); return folly::sformat("{},{}", handle, reinterpret_cast<void*>(extra)); } bool equals(const ArrayAccessProfileData& o) const { return handle == o.handle && extra == o.extra; } DecRefProfileEntry* extra; }; struct TVInRDSHandleData : RDSHandleData { TVInRDSHandleData (rds::Handle handle, bool includeAux) : RDSHandleData(handle), includeAux(includeAux) {} std::string show() const { return folly::to<std::string>(handle, ",", includeAux); } bool equals(TVInRDSHandleData o) const { return handle == o.handle && includeAux == o.includeAux; } size_t hash() const { return folly::hash::hash_combine(std::hash<uint32_t>()(handle), std::hash<bool>()(includeAux)); } size_t stableHash() const { return folly::hash::hash_combine(RDSHandleData::stableHash(), std::hash<bool>()(includeAux)); } bool includeAux; }; /* * Translation ID. * * Used with profiling-related instructions. */ struct TransIDData : IRExtraData { explicit TransIDData(TransID transId) : transId(transId) {} std::string show() const { return folly::to<std::string>(transId); } size_t stableHash() const { return std::hash<TransID>()(transId); } bool equals(const TransIDData& o) const { return transId == o.transId; } TransID transId; }; struct DefFPData : IRExtraData { explicit DefFPData(Optional<IRSPRelOffset> offset) : offset(offset) {} std::string show() const { if (!offset) return "IRSPOff unknown"; return folly::to<std::string>("IRSPOff ", offset->offset); } bool equals(DefFPData o) const { return offset == o.offset; } size_t stableHash() const { return offset ? std::hash<int32_t>()(offset->offset) : 0; } // Frame position on the stack, if it lives there and the position is known. Optional<IRSPRelOffset> offset; }; /* * Stack pointer offset. */ struct DefStackData : IRExtraData { explicit DefStackData(SBInvOffset irSPOff, SBInvOffset bcSPOff) : irSPOff(irSPOff) , bcSPOff(bcSPOff) {} std::string show() const { return folly::sformat("irSPOff={}, bcSPOff={}", irSPOff.offset, bcSPOff.offset); } bool equals(DefStackData o) const { return irSPOff == o.irSPOff && bcSPOff == o.bcSPOff; } size_t hash() const { return folly::hash::hash_combine( std::hash<int32_t>()(irSPOff.offset), std::hash<int32_t>()(bcSPOff.offset) ); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(irSPOff.offset), std::hash<int32_t>()(bcSPOff.offset) ); } SBInvOffset irSPOff; // offset from stack base to vmsp() SBInvOffset bcSPOff; // offset from stack base to top of the stack }; /* * Stack offset. */ struct IRSPRelOffsetData : IRExtraData { explicit IRSPRelOffsetData(IRSPRelOffset offset) : offset(offset) {} std::string show() const { return folly::to<std::string>("IRSPOff ", offset.offset); } bool equals(IRSPRelOffsetData o) const { return offset == o.offset; } size_t hash() const { return std::hash<int32_t>()(offset.offset); } size_t stableHash() const { return std::hash<int32_t>()(offset.offset); } IRSPRelOffset offset; }; /////////////////////////////////////////////////////////////////////////////// /* * One-off IRExtraData classes. * * These are used for only one or two instructions and are in no particular * order. Add new IRExtraData types here. */ struct IsAsyncData : IRExtraData { explicit IsAsyncData(bool isAsync) : isAsync(isAsync) {} std::string show() const { return folly::to<std::string>(isAsync); } bool equals(IsAsyncData d) const { return isAsync == d.isAsync; } size_t hash() const { return std::hash<int32_t>()(isAsync); } size_t stableHash() const { return std::hash<int32_t>()(isAsync); } bool isAsync; }; struct LdBindAddrData : IRExtraData { explicit LdBindAddrData(SrcKey sk, SBInvOffset bcSPOff) : sk(sk) , bcSPOff(bcSPOff) {} std::string show() const { return showShort(sk); } size_t stableHash() const { return folly::hash::hash_combine( SrcKey::StableHasher()(sk), std::hash<int32_t>()(bcSPOff.offset) ); } bool equals(const LdBindAddrData& o) const { return sk == o.sk && bcSPOff == o.bcSPOff; } SrcKey sk; SBInvOffset bcSPOff; }; struct LdSSwitchData : IRExtraData { struct Elm { const StringData* str; SrcKey dest; }; explicit LdSSwitchData() = default; LdSSwitchData(const LdSSwitchData&) = delete; LdSSwitchData& operator=(const LdSSwitchData&) = delete; LdSSwitchData* clone(Arena& arena) const { LdSSwitchData* target = new (arena) LdSSwitchData; target->numCases = numCases; target->defaultSk = defaultSk; target->cases = new (arena) Elm[numCases]; target->bcSPOff = bcSPOff; std::copy(cases, cases + numCases, const_cast<Elm*>(target->cases)); return target; } std::string show() const { return folly::to<std::string>(bcSPOff.offset); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int64_t>()(numCases), SrcKey::StableHasher()(defaultSk), std::hash<int32_t>()(bcSPOff.offset) ); } bool equals(const LdSSwitchData& o) const { if (numCases != o.numCases) return false; if (defaultSk != o.defaultSk) return false; if (bcSPOff != o.bcSPOff) return false; for (int64_t i = 0; i < numCases; i++) { if (cases[i].dest != o.cases[i].dest) return false; if (cases[i].str != o.cases[i].str) return false; } return true; } int64_t numCases; const Elm* cases; SrcKey defaultSk; SBInvOffset bcSPOff; }; struct ProfileSwitchData : IRExtraData { ProfileSwitchData(rds::Handle handle, int32_t cases, int64_t base) : handle(handle) , cases(cases) , base(base) {} std::string show() const { return folly::sformat("handle {}, {} cases, base {}", handle, cases, base); } size_t stableHash() const { auto const sym = rds::reverseLink(handle); return folly::hash::hash_combine( sym ? rds::symbol_stable_hash(*sym) : 0, std::hash<int32_t>()(cases), std::hash<int64_t>()(base) ); } bool equals(const ProfileSwitchData& o) const { return handle == o.handle && cases == o.cases && base == o.base; } rds::Handle handle; int32_t cases; int64_t base; }; struct JmpSwitchData : IRExtraData { JmpSwitchData* clone(Arena& arena) const { JmpSwitchData* sd = new (arena) JmpSwitchData; sd->cases = cases; sd->targets = new (arena) SrcKey[cases]; sd->spOffBCFromStackBase = spOffBCFromStackBase; sd->spOffBCFromIRSP = spOffBCFromIRSP; std::copy(targets, targets + cases, const_cast<SrcKey*>(sd->targets)); return sd; } std::string show() const { return folly::sformat("{} cases", cases); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(cases), std::hash<int32_t>()(spOffBCFromStackBase.offset), std::hash<int32_t>()(spOffBCFromIRSP.offset) ); } bool equals(const JmpSwitchData& o) const { if (cases != o.cases) return false; if (spOffBCFromStackBase != o.spOffBCFromStackBase) return false; if (spOffBCFromIRSP != o.spOffBCFromIRSP) return false; for (int64_t i = 0; i < cases; i++) { if (targets[i] != o.targets[i]) return false; } return true; } int32_t cases; // number of cases SrcKey* targets; // srckeys for all targets SBInvOffset spOffBCFromStackBase; IRSPRelOffset spOffBCFromIRSP; }; struct LdTVAuxData : IRExtraData { explicit LdTVAuxData(int32_t v = -1) : valid(v) {} std::string show() const { return folly::sformat("{:x}", valid); } size_t stableHash() const { return std::hash<int32_t>()(valid); } bool equals(const LdTVAuxData& o) const { return valid == o.valid; } int32_t valid; }; struct ReqBindJmpData : IRExtraData { explicit ReqBindJmpData(const SrcKey& target, SBInvOffset invSPOff, IRSPRelOffset irSPOff) : target(target) , invSPOff(invSPOff) , irSPOff(irSPOff) {} std::string show() const { return folly::sformat( "{}, SBInv {}, IRSP {}", target.printableOffset(), invSPOff.offset, irSPOff.offset ); } size_t stableHash() const { return folly::hash::hash_combine( SrcKey::StableHasher()(target), std::hash<int32_t>()(invSPOff.offset), std::hash<int32_t>()(irSPOff.offset) ); } bool equals(const ReqBindJmpData& o) const { return target == o.target && invSPOff == o.invSPOff && irSPOff == o.irSPOff; } SrcKey target; SBInvOffset invSPOff; IRSPRelOffset irSPOff; }; struct StFrameMetaData : IRExtraData { std::string show() const { return folly::to<std::string>( callBCOff, ',', asyncEagerReturn ); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<Offset>()(callBCOff), std::hash<bool>()(asyncEagerReturn) ); } bool equals(const StFrameMetaData& o) const { return callBCOff == o.callBCOff && asyncEagerReturn == o.asyncEagerReturn; } Offset callBCOff; bool isInlined; bool asyncEagerReturn; }; struct CallBuiltinData : IRExtraData { explicit CallBuiltinData(IRSPRelOffset spOffset, const Func* callee) : spOffset(spOffset) , callee{callee} {} std::string show() const { return folly::to<std::string>( spOffset.offset, ',', callee->fullName()->data() ); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(spOffset.offset), callee->stableHash() ); } bool equals(const CallBuiltinData& o) const { return spOffset == o.spOffset && callee == o.callee; } IRSPRelOffset spOffset; // offset from StkPtr to last passed arg const Func* callee; }; struct CallData : IRExtraData { explicit CallData(IRSPRelOffset spOffset, uint32_t numArgs, uint32_t numOut, Offset callOffset, uint16_t genericsBitmap, bool hasGenerics, bool hasUnpack, bool skipRepack, bool dynamicCall, bool asyncEagerReturn, bool formingRegion) : spOffset(spOffset) , numArgs(numArgs) , numOut(numOut) , callOffset(callOffset) , genericsBitmap(genericsBitmap) , hasGenerics(hasGenerics) , hasUnpack(hasUnpack) , skipRepack(skipRepack) , dynamicCall(dynamicCall) , asyncEagerReturn(asyncEagerReturn) , formingRegion(formingRegion) {} std::string show() const { return folly::to<std::string>( spOffset.offset, ',', numArgs, ',', numOut, ',', callOffset, hasGenerics ? folly::sformat(",hasGenerics({})", genericsBitmap) : std::string{}, hasUnpack ? ",unpack" : "", skipRepack ? ",skipRepack" : "", dynamicCall ? ",dynamicCall" : "", asyncEagerReturn ? ",asyncEagerReturn" : "", formingRegion ? ",formingRegion" : "" ); } uint32_t numInputs() const { return numArgs + (hasUnpack ? 1 : 0) + (hasGenerics ? 1 : 0); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(spOffset.offset), std::hash<uint32_t>()(numArgs), std::hash<uint32_t>()(numOut), std::hash<Offset>()(callOffset), std::hash<uint16_t>()(genericsBitmap), std::hash<uint8_t>()( hasGenerics << 5 | hasUnpack << 4 | skipRepack << 3 | dynamicCall << 2 | asyncEagerReturn << 1 | formingRegion ) ); } bool equals(const CallData& o) const { return spOffset == o.spOffset && numArgs == o.numArgs && numOut == o.numOut && callOffset == o.callOffset && genericsBitmap == o.genericsBitmap && hasGenerics == o.hasGenerics && hasUnpack == o.hasUnpack && skipRepack == o.skipRepack && dynamicCall == o.dynamicCall && asyncEagerReturn == o.asyncEagerReturn && formingRegion == o.formingRegion; } IRSPRelOffset spOffset; // offset from StkPtr to bottom of call's ActRec+args uint32_t numArgs; uint32_t numOut; // number of values returned via stack from the callee Offset callOffset; // offset from func->base() uint16_t genericsBitmap; bool hasGenerics; bool hasUnpack; bool skipRepack; bool dynamicCall; bool asyncEagerReturn; bool formingRegion; }; struct RetCtrlData : IRExtraData { explicit RetCtrlData(IRSPRelOffset offset, bool suspendingResumed, AuxUnion aux) : offset(offset) , suspendingResumed(suspendingResumed) , aux(aux) {} std::string show() const { return folly::to<std::string>( offset.offset, suspendingResumed ? ",suspendingResumed" : "" ); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(offset.offset), std::hash<bool>()(suspendingResumed), std::hash<uint32_t>()(aux.u_raw) ); } bool equals(const RetCtrlData& o) const { return offset == o.offset && suspendingResumed == o.suspendingResumed && aux.u_raw == o.aux.u_raw; } // Adjustment we need to make to the stack pointer (for cross-tracelet ABI // purposes) before returning. IRSPRelOffset offset; // Indicates that the current resumable frame is being suspended without // decrefing locals. Used by refcount optimizer. bool suspendingResumed; // TV aux value to attach to the function's return value. AuxUnion aux; }; /* * Name of a class constant in a known class */ struct ClsCnsName : IRExtraData { explicit ClsCnsName(const StringData* cls, const StringData* cns) : clsName(cls) , cnsName(cns) {} std::string show() const { return folly::to<std::string>(clsName->data(), "::", cnsName->data()); } size_t stableHash() const { return folly::hash::hash_combine( clsName->hashStatic(), cnsName->hashStatic() ); } bool equals(const ClsCnsName& o) const { return clsName == o.clsName && cnsName == o.cnsName; } const StringData* clsName; const StringData* cnsName; }; /* * Name and slot of a class constant. */ struct ClsCnsSlotData : IRExtraData { explicit ClsCnsSlotData(const StringData* cns, Slot s) : cnsName(cns) , slot(s) {} std::string show() const { return folly::sformat("{},{}", cnsName, slot); } size_t stableHash() const { return folly::hash::hash_combine( cnsName->hashStatic(), std::hash<Slot>()(slot) ); } bool equals(const ClsCnsSlotData& o) const { return cnsName == o.cnsName && slot == o.slot; } const StringData* cnsName; Slot slot; }; /* * Name and handle of profiled class constant */ struct ProfileSubClsCnsData : IRExtraData { explicit ProfileSubClsCnsData(const StringData* cns, rds::Handle h) : cnsName(cns) , handle(h) {} std::string show() const { return folly::to<std::string>("<cls>::", cnsName->data()); } size_t stableHash() const { auto const sym = rds::reverseLink(handle); return folly::hash::hash_combine( cnsName->hashStatic(), sym ? rds::symbol_stable_hash(*sym) : 0 ); } bool equals(const ProfileSubClsCnsData& o) const { return cnsName == o.cnsName && handle == o.handle; } const StringData* cnsName; rds::Handle handle; }; struct FuncNameData : IRExtraData { FuncNameData(const StringData* name, const Class* context) : name(name) , context(context) {} std::string show() const { return folly::to<std::string>( name->data(), ",", context ? context->name()->data() : "{no context}"); } size_t hash() const { return name->hash(); } size_t stableHash() const { return folly::hash::hash_combine( name->hash(), context ? context->stableHash() : 0 ); } bool equals(const FuncNameData& o) const { return name == o.name && context == o.context; } const StringData* name; const Class* context; }; /* * Offset and stack deltas for InterpOne. */ struct InterpOneData : IRExtraData { struct LocalType { explicit LocalType(uint32_t id = 0, Type type = TBottom) : id(id) , type(type) {} uint32_t id; Type type; }; explicit InterpOneData(IRSPRelOffset spOffset) : spOffset(spOffset) , nChangedLocals(0) , changedLocals(nullptr) , smashesAllLocals(false) {} size_t stableHash() const { auto hash = folly::hash::hash_combine( std::hash<int32_t>()(spOffset.offset), std::hash<Offset>()(bcOff), std::hash<int64_t>()(cellsPopped), std::hash<int64_t>()(cellsPushed), std::hash<Op>()(opcode), std::hash<uint32_t>()(nChangedLocals), std::hash<bool>()(smashesAllLocals) ); for (uint32_t i = 0; i < nChangedLocals; i++) { hash = folly::hash::hash_combine( hash, changedLocals[i].id, changedLocals[i].type.stableHash() ); } return hash; } bool equals(const InterpOneData& o) const { if (spOffset == o.spOffset && bcOff == o.bcOff && cellsPopped == o.cellsPopped && cellsPushed == o.cellsPushed && opcode == o.opcode && nChangedLocals == o.nChangedLocals && smashesAllLocals == o.smashesAllLocals) { for (uint32_t i = 0; i < nChangedLocals; i++) { if (changedLocals[i].id != o.changedLocals[i].id) return false; if (changedLocals[i].type != o.changedLocals[i].type) return false; } return true; } return false; } // Offset of the BC stack top relative to the current IR stack pointer. IRSPRelOffset spOffset; // Offset of the instruction to interpret, in the Unit indicated by the // current Marker. Offset bcOff; // The number of eval stack cells consumed and produced by the instruction, // respectively. Includes ActRecs. int64_t cellsPopped; int64_t cellsPushed; // Opcode, in case we need to fix the stack differently. Some bytecode // instructions modify things below the top of the stack. Op opcode; uint32_t nChangedLocals; LocalType* changedLocals; bool smashesAllLocals; InterpOneData* clone(Arena& arena) const { auto* id = new (arena) InterpOneData(spOffset); id->bcOff = bcOff; id->cellsPopped = cellsPopped; id->cellsPushed = cellsPushed; id->opcode = opcode; id->nChangedLocals = nChangedLocals; id->changedLocals = new (arena) LocalType[nChangedLocals]; id->smashesAllLocals = smashesAllLocals; std::copy(changedLocals, changedLocals + nChangedLocals, id->changedLocals); return id; } std::string show() const { auto ret = folly::sformat( "{}: spOff:{}, bcOff:{}, popped:{}, pushed:{}", opcodeToName(opcode), spOffset.offset, bcOff, cellsPopped, cellsPushed ); assertx(!smashesAllLocals || !nChangedLocals); if (smashesAllLocals) ret += ", smashes all locals"; if (nChangedLocals) { for (auto i = 0; i < nChangedLocals; ++i) { ret += folly::sformat(", Local {} -> {}", changedLocals[i].id, changedLocals[i].type); } } return ret; } }; struct RBEntryData : IRExtraData { RBEntryData(Trace::RingBufferType t, SrcKey sk) : type(t) , sk(sk) {} std::string show() const { return folly::sformat("{}: {}", ringbufferName(type), showShort(sk)); } size_t stableHash() const { return std::hash<Trace::RingBufferType>()(type) ^ SrcKey::StableHasher()(sk); } bool equals(const RBEntryData& o) const { return type == o.type && sk == o.sk; } Trace::RingBufferType type; SrcKey sk; }; struct RBMsgData : IRExtraData { RBMsgData(Trace::RingBufferType t, const StringData* msg) : type(t) , msg(msg) { assertx(msg->isStatic()); } std::string show() const { return folly::sformat("{}: {}", ringbufferName(type), msg->data()); } size_t stableHash() const { return std::hash<Trace::RingBufferType>()(type) ^ msg->hash(); } bool equals(const RBMsgData& o) const { return type == o.type && msg->equal(o.msg); } Trace::RingBufferType type; const StringData* msg; }; struct ClassKindData : IRExtraData { explicit ClassKindData(ClassKind kind): kind(uint32_t(kind)) {} std::string show() const { switch (static_cast<ClassKind>(kind)) { case ClassKind::Class: return "cls"; case ClassKind::Interface: return "interface"; case ClassKind::Trait: return "trait"; case ClassKind::Enum: return "enum"; } not_reached(); } size_t stableHash() const { return std::hash<uint32_t>()(kind); } bool equals(const ClassKindData& o) const { return kind == o.kind; } uint32_t kind; // ... allows for direct usage in native_call }; struct NewStructData : IRExtraData { std::string show() const; size_t stableHash() const { auto hash = folly::hash::hash_combine( std::hash<int32_t>()(offset.offset), std::hash<uint32_t>()(numKeys) ); for (uint32_t i = 0; i < numKeys; i++) { hash = folly::hash::hash_combine( hash, keys[i]->hashStatic() ); } return hash; } bool equals(const NewStructData& o) const { if (offset != o.offset) return false; if (numKeys != o.numKeys) return false; for (uint32_t i = 0; i < numKeys; i++) { if (keys[i] != o.keys[i]) return false; } return true; } IRSPRelOffset offset; uint32_t numKeys; StringData** keys; }; struct AllocInitROMData : IRExtraData { std::string show() const { return ""; // TODO } size_t stableHash() const { return size; // TODO(michaelofarrell): fixme } bool equals(const AllocInitROMData& o) const { return rom == o.rom && size == o.size; } const uint8_t* rom; size_t size; }; struct ArrayLayoutData : IRExtraData { explicit ArrayLayoutData(ArrayLayout layout) : layout(layout) {} std::string show() const { return layout.describe(); } size_t stableHash() const { return layout.toUint16(); } bool equals(const ArrayLayoutData& o) const { return layout == o.layout; } ArrayLayout layout; }; struct NewBespokeStructData : IRExtraData { NewBespokeStructData(ArrayLayout layout, IRSPRelOffset offset, uint32_t numSlots, Slot* slots) : layout(layout), offset(offset), numSlots(numSlots), slots(slots) {} std::string show() const; size_t stableHash() const { auto hash = folly::hash::hash_combine( std::hash<uint16_t>()(layout.toUint16()), std::hash<int32_t>()(offset.offset), std::hash<uint32_t>()(numSlots) ); for (auto i = 0; i < numSlots; i++) { hash = folly::hash::hash_combine(hash, slots[i]); } return hash; } bool equals(const NewBespokeStructData& o) const { if (layout != o.layout) return false; if (offset != o.offset) return false; if (numSlots != o.numSlots) return false; for (auto i = 0; i < numSlots; i++) { if (slots[i] != o.slots[i]) return false; } return true; } ArrayLayout layout; IRSPRelOffset offset; uint32_t numSlots; Slot* slots; }; struct InitStructPositionsData : IRExtraData { InitStructPositionsData(ArrayLayout layout, uint32_t numSlots, Slot* slots) : layout(layout), numSlots(numSlots), slots(slots) {} std::string show() const; size_t stableHash() const { auto hash = folly::hash::hash_combine( std::hash<uint16_t>()(layout.toUint16()), std::hash<uint32_t>()(numSlots) ); for (auto i = 0; i < numSlots; i++) { hash = folly::hash::hash_combine(hash, slots[i]); } return hash; } bool equals(const InitStructPositionsData& o) const { if (layout != o.layout) return false; if (numSlots != o.numSlots) return false; for (auto i = 0; i < numSlots; i++) { if (slots[i] != o.slots[i]) return false; } return true; } ArrayLayout layout; uint32_t numSlots; Slot* slots; }; struct VanillaVecData : IRExtraData { explicit VanillaVecData(uint32_t size) : size(size) {} std::string show() const { return folly::format("{}", size).str(); } size_t stableHash() const { return std::hash<uint32_t>()(size); } bool equals(const VanillaVecData& o) const { return size == o.size; } uint32_t size; }; struct InitVanillaVecLoopData : IRExtraData { explicit InitVanillaVecLoopData(IRSPRelOffset offset, uint32_t size) : offset(offset) , size(size) {} std::string show() const { return folly::format("{},{}", offset.offset, size).str(); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(offset.offset), std::hash<uint32_t>()(size) ); } bool equals(const InitVanillaVecLoopData& o) const { return offset == o.offset && size == o.size; } IRSPRelOffset offset; uint32_t size; }; struct CreateAAWHData : IRExtraData { explicit CreateAAWHData(uint32_t first, uint32_t count) : first(first) , count(count) {} std::string show() const { return folly::format("{},{}", first, count).str(); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<uint32_t>()(first), std::hash<uint32_t>()(count) ); } bool equals(const CreateAAWHData& o) const { return first == o.first && count == o.count; } uint32_t first; uint32_t count; }; struct CountWHNotDoneData : IRExtraData { explicit CountWHNotDoneData(uint32_t first, uint32_t count) : first(first) , count(count) {} std::string show() const { return folly::format("{},{}", first, count).str(); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<uint32_t>()(first), std::hash<uint32_t>()(count) ); } bool equals(const CountWHNotDoneData& o) const { return first == o.first && count == o.count; } uint32_t first; uint32_t count; }; struct NewKeysetArrayData : IRExtraData { explicit NewKeysetArrayData(IRSPRelOffset offset, uint32_t size) : offset(offset) , size(size) {} std::string show() const { return folly::format("{},{}", offset.offset, size).str(); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(offset.offset), std::hash<uint32_t>()(size) ); } bool equals(const NewKeysetArrayData& o) const { return offset == o.offset && size == o.size; } IRSPRelOffset offset; uint32_t size; }; struct MemoValueStaticData : IRExtraData { explicit MemoValueStaticData(const Func* func, Optional<bool> asyncEager, bool loadAux) : func{func} , asyncEager{asyncEager} , loadAux{loadAux} {} std::string show() const { return folly::sformat( "{},{},{}", func->fullName()->toCppString(), asyncEager ? folly::to<std::string>(*asyncEager) : "-", loadAux ); } size_t stableHash() const { return folly::hash::hash_combine( func->stableHash(), std::hash<Optional<bool>>()(asyncEager), std::hash<bool>()(loadAux) ); } bool equals(const MemoValueStaticData& o) const { return func == o.func && asyncEager == o.asyncEager && loadAux == o.loadAux; } const Func* func; Optional<bool> asyncEager; bool loadAux; }; struct MemoValueInstanceData : IRExtraData { explicit MemoValueInstanceData(Slot slot, const Func* func, Optional<bool> asyncEager, bool loadAux) : slot{slot} , func{func} , asyncEager{asyncEager} , loadAux{loadAux} {} std::string show() const { return folly::sformat( "{},{},{},{}", slot, func->fullName(), asyncEager ? folly::to<std::string>(*asyncEager) : "-", loadAux ); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<Slot>()(slot), func->stableHash(), std::hash<Optional<bool>>()(asyncEager), std::hash<bool>()(loadAux) ); } bool equals(const MemoValueInstanceData& o) const { return slot == o.slot && func == o.func && asyncEager == o.asyncEager && loadAux == o.loadAux; } Slot slot; const Func* func; Optional<bool> asyncEager; bool loadAux; }; struct MemoCacheStaticData : IRExtraData { MemoCacheStaticData(const Func* func, LocalRange keys, const bool* types, Optional<bool> asyncEager, bool loadAux) : func{func} , keys{keys} , types{types} , asyncEager{asyncEager} , loadAux{loadAux} {} MemoCacheStaticData* clone(Arena& arena) const { auto p = new (arena) MemoCacheStaticData(func, keys, types, asyncEager, loadAux); auto tmp = new (arena) bool[keys.count]; std::copy(types, types + keys.count, tmp); p->types = tmp; return p; } std::string show() const { std::string ret; ret += folly::sformat("{},{}", func->fullName(), HPHP::show(keys)); if (keys.count > 0) { ret += ",<"; for (auto i = 0; i < keys.count; ++i) { if (i > 0) ret += ","; ret += folly::sformat("{}", types[i] ? "string" : "int"); } ret += ">"; } return ret; } size_t stableHash() const { auto hash = folly::hash::hash_combine( func->stableHash(), std::hash<Optional<bool>>()(asyncEager), std::hash<uint32_t>()(keys.first), std::hash<uint32_t>()(keys.count), std::hash<bool>()(loadAux) ); for (auto i = 0; i < keys.count; i++) { hash = folly::hash::hash_combine( hash, std::hash<bool>()(types[i]) ); } return hash; } bool equals(const MemoCacheStaticData& o) const { if (func == o.func && asyncEager == o.asyncEager && loadAux == o.loadAux && keys.first == o.keys.first && keys.count == o.keys.count) { for (auto i = 0; i < keys.count; i++) { if (types[i] != o.types[i]) return false; } return true; } return false; } const Func* func; LocalRange keys; const bool* types; Optional<bool> asyncEager; bool loadAux; }; struct MemoCacheInstanceData : IRExtraData { MemoCacheInstanceData(Slot slot, LocalRange keys, const bool* types, const Func* func, bool shared, Optional<bool> asyncEager, bool loadAux) : slot{slot} , keys{keys} , types{types} , func{func} , shared{shared} , asyncEager{asyncEager} , loadAux{loadAux} {} MemoCacheInstanceData* clone(Arena& arena) const { auto p = new (arena) MemoCacheInstanceData( slot, keys, types, func, shared, asyncEager, loadAux ); auto tmp = new (arena) bool[keys.count]; std::copy(types, types + keys.count, tmp); p->types = tmp; return p; } std::string show() const { return folly::sformat( "{},{},{}<{}>,{}", slot, func->fullName(), HPHP::show(keys), [&]{ using namespace folly::gen; return range<uint32_t>(0, keys.count) | map([this] (uint32_t i) { return types[i] ? "string" : "int"; }) | unsplit<std::string>(","); }(), shared ? "shared" : "non-shared" ); } size_t stableHash() const { auto hash = folly::hash::hash_combine( std::hash<Slot>()(slot), func->stableHash(), std::hash<Optional<bool>>()(asyncEager), std::hash<uint32_t>()(keys.first), std::hash<uint32_t>()(keys.count), std::hash<bool>()(loadAux), std::hash<bool>()(shared) ); for (auto i = 0; i < keys.count; i++) { hash = folly::hash::hash_combine( hash, std::hash<bool>()(types[i]) ); } return hash; } bool equals(const MemoCacheInstanceData& o) const { if (slot == o.slot && func == o.func && asyncEager == o.asyncEager && loadAux == o.loadAux && keys.first == o.keys.first && keys.count == o.keys.count && shared == o.shared) { for (auto i = 0; i < keys.count; i++) { if (types[i] != o.types[i]) return false; } return true; } return false; } Slot slot; LocalRange keys; const bool* types; const Func* func; bool shared; Optional<bool> asyncEager; bool loadAux; }; struct MOpModeData : IRExtraData { explicit MOpModeData(MOpMode mode) : mode{mode} {} std::string show() const { return subopToName(mode); } size_t stableHash() const { return std::hash<MOpMode>()(mode); } bool equals(const MOpModeData& o) const { return mode == o.mode; } MOpMode mode; }; struct PropData : IRExtraData { explicit PropData(MOpMode mode, ReadonlyOp op) : mode{mode}, op(op) {} std::string show() const { return fmt::format("{} {}", subopToName(mode), subopToName(op)); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<MOpMode>()(mode), std::hash<ReadonlyOp>()(op) ); } bool equals(const PropData& o) const { return mode == o.mode && op == o.op; } MOpMode mode; ReadonlyOp op; }; struct ReadonlyData : IRExtraData { explicit ReadonlyData(ReadonlyOp op) : op(op) {} std::string show() const { return subopToName(op); } size_t hash() const { return std::hash<ReadonlyOp>()(op); } size_t stableHash() const { return std::hash<ReadonlyOp>()(op); } bool equals(const ReadonlyData& o) const { return op == o.op; } ReadonlyOp op; }; struct SetOpData : IRExtraData { explicit SetOpData(SetOpOp op) : op(op) {} std::string show() const { return subopToName(op); } size_t stableHash() const { return std::hash<SetOpOp>()(op); } bool equals(const SetOpData& o) const { return op == o.op; } SetOpOp op; }; struct DecRefData : IRExtraData { explicit DecRefData(int locId = -1) : locId(locId) {} std::string show() const { return locId != -1 ? folly::to<std::string>("Loc", locId) : "-"; } size_t stableHash() const { return std::hash<int>()(locId); } bool equals(const DecRefData& o) const { return locId == o.locId; } int locId; // If a known local, this has its id; -1 otherwise. }; struct IncDecData : IRExtraData { explicit IncDecData(IncDecOp op) : op(op) {} std::string show() const { return subopToName(op); } size_t stableHash() const { return std::hash<IncDecOp>()(op); } bool equals(const IncDecData& o) const { return op == o.op; } IncDecOp op; }; struct SuspendOffset : IRExtraData { explicit SuspendOffset(Offset off) : off(off) {} std::string show() const { return folly::to<std::string>(off); } size_t stableHash() const { return std::hash<Offset>()(off); } bool equals(const SuspendOffset& o) const { return off == o.off; } Offset off; }; struct GeneratorState : IRExtraData { explicit GeneratorState(BaseGenerator::State state) : state(state) {} std::string show() const { using U = std::underlying_type<BaseGenerator::State>::type; return folly::to<std::string>(static_cast<U>(state)); } size_t stableHash() const { return std::hash<BaseGenerator::State>()(state); } bool equals(const GeneratorState& o) const { return state == o.state; } BaseGenerator::State state; }; struct ContEnterData : IRExtraData { explicit ContEnterData(IRSPRelOffset spOffset, Offset callBCOffset, bool isAsync) : spOffset(spOffset) , callBCOffset(callBCOffset) , isAsync(isAsync) {} std::string show() const { return folly::to<std::string>(spOffset.offset, ',', callBCOffset, isAsync ? ",async" : ""); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(spOffset.offset), std::hash<Offset>()(callBCOffset), std::hash<bool>()(isAsync) ); } bool equals(const ContEnterData& o) const { return spOffset == o.spOffset && callBCOffset == o.callBCOffset && isAsync && o.isAsync; } IRSPRelOffset spOffset; Offset callBCOffset; bool isAsync; }; struct NewColData : IRExtraData { explicit NewColData(CollectionType itype) : type(itype) {} std::string show() const { return collections::typeToString(type)->toCppString(); } size_t stableHash() const { return std::hash<CollectionType>()(type); } bool equals(const NewColData& o) const { return type == o.type; } CollectionType type; }; struct LocalIdRange : IRExtraData { LocalIdRange(uint32_t start, uint32_t end) : start(start) , end(end) {} std::string show() const { return folly::format("[{}, {})", start, end).str(); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<uint32_t>()(start), std::hash<uint32_t>()(end) ); } bool equals(const LocalIdRange& o) const { return start == o.start && end == o.end; } uint32_t start, end; }; struct StackRange : IRExtraData { StackRange(IRSPRelOffset start, uint32_t count) : start(start) , count(count) {} std::string show() const { return folly::sformat("[{}, {})", start.offset, start.offset + count); } size_t stableHash() const { return folly::hash::hash_combine(std::hash<int32_t>()(start.offset), std::hash<int32_t>()(count)); } bool equals(const StackRange& o) const { return start == o.start && count == o.count; } IRSPRelOffset start; uint32_t count; }; struct FuncEntryData : IRExtraData { FuncEntryData(const Func* func, uint32_t argc) : func(func) , argc(argc) {} std::string show() const { return folly::format( "{}({} args)", func->fullName(), argc ).str(); } size_t stableHash() const { return folly::hash::hash_combine( func->stableHash(), std::hash<uint32_t>()(argc) ); } bool equals(const FuncEntryData& o) const { return func == o.func && argc == o.argc; } const Func* func; uint32_t argc; }; struct BoolVecArgsData : IRExtraData { BoolVecArgsData(uint32_t numArgs, const uint8_t* args) : numArgs(numArgs) , args(reinterpret_cast<uintptr_t>(args)) {} std::string show() const { return HPHP::show(numArgs, reinterpret_cast<const uint8_t*>(args)); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<uint32_t>()(numArgs), std::hash<uintptr_t>()(args) ); } bool equals(const BoolVecArgsData& o) const { return numArgs == o.numArgs && args == o.args; } uint32_t numArgs; uintptr_t args; }; struct ProfileCallTargetData : IRExtraData { explicit ProfileCallTargetData(rds::Handle handle) : handle(handle) {} std::string show() const { return folly::to<std::string>(handle); } size_t stableHash() const { auto const sym = rds::reverseLink(handle); if (!sym) return 0; return rds::symbol_stable_hash(*sym); } bool equals(const ProfileCallTargetData& o) const { return handle == o.handle; } rds::Handle handle; }; struct BeginInliningData : IRExtraData { BeginInliningData(IRSPRelOffset offset, const Func* func, unsigned depth, SrcKey returnSk, IRSPRelOffset sbOffset, SBInvOffset returnSPOff, int cost, int numArgs) : spOffset(offset) , func(func) , depth(depth) , returnSk(returnSk) , sbOffset(sbOffset) , returnSPOff(returnSPOff) , cost(cost) , numArgs(numArgs) {} std::string show() const { return folly::to<std::string>("IRSPOff ", spOffset.offset, " FUNC ", func->fullName()->data()); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(spOffset.offset), func->stableHash(), std::hash<unsigned>()(depth), SrcKey::StableHasher()(returnSk), std::hash<int32_t>()(sbOffset.offset), std::hash<int32_t>()(returnSPOff.offset), std::hash<int>()(cost), std::hash<int>()(numArgs) ); } bool equals(const BeginInliningData& o) const { return spOffset == o.spOffset && func == o.func && depth == o.depth && returnSk == o.returnSk && sbOffset == o.sbOffset && returnSPOff == o.returnSPOff && cost == o.cost && numArgs == o.numArgs; } IRSPRelOffset spOffset; // offset from SP to the bottom of callee's ActRec const Func* func; // inlined function unsigned depth; // inlining depth SrcKey returnSk; // return SrcKey IRSPRelOffset sbOffset; // offset from SP to the callee's stack base SBInvOffset returnSPOff; // offset from caller's stack base to return slot int cost; int numArgs; }; struct ParamData : IRExtraData { explicit ParamData(int32_t paramId) : paramId(paramId) {} std::string show() const { return folly::to<std::string>(paramId); } size_t stableHash() const { return std::hash<int32_t>()(paramId); } bool equals(const ParamData& o) const { return paramId == o.paramId; } int32_t paramId; }; struct FuncParamData : IRExtraData { explicit FuncParamData(const Func* func, int32_t paramId) : func(func) , paramId(paramId) {} std::string show() const { return folly::to<std::string>(func->fullName()->data(), ":", paramId); } size_t stableHash() const { return folly::hash::hash_combine( func->stableHash(), std::hash<int32_t>()(paramId) ); } bool equals(const FuncParamData& o) const { return func == o.func && paramId == o.paramId; } const Func* func; int32_t paramId; }; struct FuncParamWithTCData : IRExtraData { explicit FuncParamWithTCData(const Func* func, int32_t paramId, const TypeConstraint* tc) : func(func) , paramId(paramId) , tc(tc) {} std::string show() const { return folly::to<std::string>( func->fullName()->data(), ":", paramId, ":", tc->displayName()); } size_t stableHash() const { return folly::hash::hash_combine( func->stableHash(), std::hash<int32_t>()(paramId), std::hash<std::string>()(tc->fullName()) // Not great but hey its easy. ); } bool equals(const FuncParamWithTCData& o) const { return func == o.func && paramId == o.paramId && *tc == *o.tc; } const Func* func; int32_t paramId; union { const TypeConstraint* tc; uintptr_t tcAsInt; }; }; struct TypeConstraintData : IRExtraData { explicit TypeConstraintData(const TypeConstraint* tc) : tc(tc) {} std::string show() const { return tc->displayName(); } size_t stableHash() const { // Not great but easy. return std::hash<std::string>()(tc->fullName()); } bool equals(const TypeConstraintData& o) const { return *tc == *o.tc; } const TypeConstraint* tc; }; struct ArrayGetExceptionData : IRExtraData { explicit ArrayGetExceptionData(bool isInOut) : isInOut(isInOut) {} std::string show() const { return isInOut ? "inout" : "none"; } size_t stableHash() const { return std::hash<bool>()(isInOut); } bool equals(const ArrayGetExceptionData& o) const { return isInOut == o.isInOut; } bool isInOut; }; struct AssertReason : IRExtraData { explicit AssertReason(Reason r) : reason{r.file, r.line} {} std::string show() const { return jit::show(reason); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<const char*>()(reason.file), std::hash<unsigned>()(reason.line) ); } bool equals(const AssertReason& o) const { return reason == o.reason; } Reason reason; }; #define ASSERT_REASON AssertReason{Reason{__FILE__, __LINE__}} struct EndCatchData : IRExtraData { enum class CatchMode { UnwindOnly, SideExit, LocalsDecRefd }; enum class FrameMode { Phplogue, Stublogue }; enum class Teardown { NA, None, Full, OnlyThis }; explicit EndCatchData(IRSPRelOffset offset, CatchMode mode, FrameMode stublogue, Teardown teardown) : offset{offset} , mode{mode} , stublogue{stublogue} , teardown{teardown} {} std::string show() const { return folly::to<std::string>( "IRSPOff ", offset.offset, ",", mode == CatchMode::UnwindOnly ? "UnwindOnly" : mode == CatchMode::SideExit ? "SideExit" : "LocalsDecRefd", ",", stublogue == FrameMode::Stublogue ? "Stublogue" : "Phplogue", ",", teardown == Teardown::NA ? "NA" : teardown == Teardown::None ? "None" : teardown == Teardown::Full ? "Full" : "OnlyThis"); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(offset.offset), std::hash<CatchMode>()(mode), std::hash<FrameMode>()(stublogue), std::hash<Teardown>()(teardown) ); } bool equals(const EndCatchData& o) const { return offset == o.offset && mode == o.mode && stublogue == o.stublogue && teardown == o.teardown; } IRSPRelOffset offset; CatchMode mode; FrameMode stublogue; Teardown teardown; }; struct EnterTCUnwindData : IRExtraData { explicit EnterTCUnwindData(IRSPRelOffset offset, bool teardown) : offset{offset}, teardown{teardown} {} std::string show() const { return folly::to<std::string>( "IRSPOff ", offset.offset, ",", teardown ? "" : "no-", "teardown" ); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<int32_t>()(offset.offset), std::hash<bool>()(teardown) ); } bool equals(const EnterTCUnwindData& o) const { return offset == o.offset && teardown == o.teardown; } IRSPRelOffset offset; bool teardown; }; /* * Func/Class/Prop attributes */ struct AttrData : IRExtraData { explicit AttrData(Attr attr) : attr(static_cast<int32_t>(attr)) {} std::string show() const { return folly::format("{}", attr).str(); } size_t stableHash() const { return std::hash<int32_t>()(attr); } size_t hash() const { return std::hash<int32_t>()(attr); } bool equals(const AttrData& o) const { return attr == o.attr; } int32_t attr; }; struct MethCallerData : IRExtraData { explicit MethCallerData(bool isCls) : isCls(isCls) {} std::string show() const { return folly::format("{}", isCls).str(); } size_t stableHash() const { return std::hash<bool>()(isCls); } bool equals(const MethCallerData& o) const { return isCls == o.isCls; } bool isCls; }; struct LoggingProfileData : IRExtraData { LoggingProfileData(bespoke::LoggingProfile* profile, bool isStatic) : profile(profile) , isStatic(isStatic) {} std::string show() const { // profile->source is already printed in the instruction's marker. return folly::sformat("{}", reinterpret_cast<void*>(profile)); } size_t stableHash() const; bool equals(const LoggingProfileData& o) const { return profile == o.profile; } bespoke::LoggingProfile* profile; bool isStatic; // Whether the output is guaranteed to be static }; struct SinkProfileData : IRExtraData { explicit SinkProfileData(bespoke::SinkProfile* profile) : profile(profile) {} std::string show() const { // profile->sink is already printed in the instruction's marker. return folly::sformat("{}", reinterpret_cast<void*>(profile)); } size_t stableHash() const; bool equals(const SinkProfileData& o) const { return profile == o.profile; } bespoke::SinkProfile* profile; }; struct BespokeGetData : IRExtraData { enum class KeyState { Present, Unknown }; explicit BespokeGetData(KeyState state) : state(state) {} std::string show() const { switch (state) { case KeyState::Present: return "Present"; case KeyState::Unknown: return "Unknown"; } always_assert(false); } size_t hash() const { return stableHash(); } size_t stableHash() const { return std::hash<KeyState>()(state); } bool equals(const BespokeGetData& o) const { return state == o.state; } KeyState state; }; struct ConvNoticeData : IRExtraData { explicit ConvNoticeData(ConvNoticeLevel l = ConvNoticeLevel::None, const StringData* r = nullptr) : level(l), reason(r) {} std::string show() const { assertx(level == ConvNoticeLevel::None || (reason != nullptr && reason->isStatic())); const auto reason_str = reason ? folly::format(" for {}", reason).str() : ""; return folly::format("{}{}", convOpToName(level), reason_str).str(); } size_t stableHash() const { return folly::hash::hash_combine( std::hash<ConvNoticeLevel>()(level), std::hash<const StringData*>()(reason) ); } bool equals(const ConvNoticeData& o) const { // can use pointer equality bc reason is always a StaticString return level == o.level && reason == o.reason; } ConvNoticeLevel level; union { const StringData* reason; int64_t reasonIntVal; }; }; struct AliasClassData : IRExtraData { explicit AliasClassData(AliasClass acls) : acls{acls} {} std::string show() const { return jit::show(acls); } bool equals(const AliasClassData& o) const { return acls == o.acls; } AliasClass acls; }; ////////////////////////////////////////////////////////////////////// #define X(op, data) \ template<> struct IRExtraDataType<op> { typedef data type; }; \ template<> struct OpHasExtraData<op> { enum { value = 1 }; }; \ static_assert(boost::has_trivial_destructor<data>::value, \ "IR extra data type must be trivially destructible") X(DictIdx, SizeHintData); X(LdBindAddr, LdBindAddrData); X(ProfileSwitchDest, ProfileSwitchData); X(JmpSwitchDest, JmpSwitchData); X(LdSSwitchDest, LdSSwitchData); X(CheckLoc, LocalId); X(AssertLoc, LocalId); X(LdLocAddr, LocalId); X(LdLoc, LocalId); X(LdClsInitElem, IndexData); X(StClsInitElem, IndexData); X(KillLoc, LocalId); X(StLoc, LocalId); X(StLocMeta, LocalId); X(StLocRange, LocalIdRange); X(AdvanceDictPtrIter, IterOffsetData); X(AdvanceVecPtrIter, IterOffsetData); X(StFrameFunc, FuncData); X(CheckIter, IterTypeData); X(StIterBase, IterId); X(StIterType, IterTypeData); X(StIterPos, IterId); X(StIterEnd, IterId); X(LdIterBase, IterId); X(LdIterPos, IterId); X(LdIterEnd, IterId); X(KillIter, IterId); X(IterFree, IterId); X(IterInit, IterData); X(IterInitK, IterData); X(IterNext, IterData); X(IterNextK, IterData); X(LIterInit, IterData); X(LIterInitK, IterData); X(LIterNext, IterData); X(LIterNextK, IterData); X(ConstructInstance, ClassData); X(ConstructClosure, ClassData); X(InitProps, ClassData); X(InitSProps, ClassData); X(NewInstanceRaw, ClassData); X(InitObjProps, ClassData); X(InitObjMemoSlots, ClassData); X(InstanceOfIfaceVtable, InstanceOfIfaceVtableData); X(ResolveTypeStruct, ResolveTypeStructData); X(ExtendsClass, ExtendsClassData); X(CheckStk, IRSPRelOffsetData); X(StStk, IRSPRelOffsetData); X(StStkMeta, IRSPRelOffsetData); X(StStkRange, StackRange); X(StOutValue, IndexData); X(LdOutAddr, IndexData); X(AssertStk, IRSPRelOffsetData); X(DefFP, DefFPData); X(DefFrameRelSP, DefStackData); X(DefRegSP, DefStackData); X(LdStk, IRSPRelOffsetData); X(LdStkAddr, IRSPRelOffsetData); X(StFrameMeta, StFrameMetaData); X(BeginInlining, BeginInliningData); X(ReqBindJmp, ReqBindJmpData); X(ReqInterpBBNoTranslate, ReqBindJmpData); X(ReqRetranslate, IRSPRelOffsetData); X(ReqRetranslateOpt, IRSPRelOffsetData); X(CheckCold, TransIDData); X(IncProfCounter, TransIDData); X(IncCallCounter, FuncData); X(LogArrayReach, SinkProfileData); X(NewLoggingArray, LoggingProfileData); X(BespokeGet, BespokeGetData); X(DefFuncEntryFP, FuncData); X(InitFrame, FuncData); X(Call, CallData); X(CallBuiltin, CallBuiltinData); X(RetCtrl, RetCtrlData); X(AsyncFuncRet, IRSPRelOffsetData); X(AsyncFuncRetSlow, IRSPRelOffsetData); X(AsyncGenRetR, IRSPRelOffsetData); X(AsyncGenYieldR, IRSPRelOffsetData); X(AsyncSwitchFast, IRSPRelOffsetData); X(LookupClsMethodCache, ClsMethodData); X(LdClsMethodCacheFunc, ClsMethodData); X(LdClsMethodCacheCls, ClsMethodData); X(LdClsMethodFCacheFunc, ClsMethodData); X(LookupClsMethodFCache, ClsMethodData); X(LdIfaceMethod, IfaceMethodData); X(LdClsCns, ClsCnsName); X(InitClsCns, ClsCnsName); X(InitSubClsCns, ClsCnsSlotData); X(LdSubClsCns, ClsCnsSlotData); X(CheckSubClsCns, ClsCnsSlotData); X(LdResolvedTypeCns, ClsCnsSlotData); X(LdResolvedTypeCnsClsName, ClsCnsSlotData); X(LdResolvedTypeCnsNoCheck, ClsCnsSlotData); X(ProfileSubClsCns, ProfileSubClsCnsData); X(LdFuncCached, FuncNameData); X(LookupFuncCached, FuncNameData); X(LdObjMethodS, FuncNameData); X(LdObjMethodD, OptClassData); X(ThrowMissingArg, FuncArgData); X(RaiseTooManyArg, FuncData); X(RaiseCoeffectsCallViolation, FuncData); X(RaiseCoeffectsFunParamTypeViolation, ParamData); X(CheckInOutMismatch, BoolVecArgsData); X(ThrowInOutMismatch, ParamData); X(CheckReadonlyMismatch, BoolVecArgsData); X(ThrowReadonlyMismatch, ParamData); X(ThrowParameterWrongType, FuncArgTypeData); X(CheckClsReifiedGenericMismatch, ClassData); X(IsFunReifiedGenericsMatched, FuncData); X(IsTypeStruct, RDSHandleData); X(InterpOne, InterpOneData); X(InterpOneCF, InterpOneData); X(StClosureArg, IndexData); X(RBTraceEntry, RBEntryData); X(RBTraceMsg, RBMsgData); X(OODeclExists, ClassKindData); X(NewStructDict, NewStructData); X(AllocStructDict, NewStructData); X(AllocInitROM, AllocInitROMData); X(AllocBespokeStructDict, ArrayLayoutData); X(InitStructPositions, InitStructPositionsData); X(NewBespokeStructDict, NewBespokeStructData); X(AllocVec, VanillaVecData); X(NewKeysetArray, NewKeysetArrayData); X(InitVecElemLoop, InitVanillaVecLoopData); X(InitVecElem, IndexData); X(InitDictElem, KeyedIndexData); X(InitStructElem, KeyedIndexData); X(CreateAAWH, CreateAAWHData); X(CountWHNotDone, CountWHNotDoneData); X(CheckDictOffset, IndexData); X(CheckKeysetOffset, IndexData); X(ProfileArrayCOW, RDSHandleData); X(ProfileDictAccess, ArrayAccessProfileData); X(ProfileKeysetAccess, ArrayAccessProfileData); X(ProfileType, RDSHandleData); X(ProfileCall, ProfileCallTargetData); X(ProfileMethod, ProfileCallTargetData); X(ProfileIsTypeStruct, RDSHandleData); X(CheckRDSInitialized, RDSHandleData); X(MarkRDSInitialized, RDSHandleData); X(MarkRDSAccess, RDSHandleData); X(LdRDSAddr, RDSHandleAndType); X(LdInitRDSAddr, RDSHandleAndType); X(LdTVFromRDS, TVInRDSHandleData); X(StTVInRDS, TVInRDSHandleData); X(BaseG, MOpModeData); X(PropX, PropData); X(PropQ, PropData); X(PropDX, PropData); X(ElemX, MOpModeData); X(ElemDX, MOpModeData); X(ElemUX, MOpModeData); X(CGetProp, PropData); X(CGetPropQ, PropData); X(CGetElem, MOpModeData); X(MemoGetStaticValue, MemoValueStaticData); X(MemoSetStaticValue, MemoValueStaticData); X(MemoGetStaticCache, MemoCacheStaticData); X(MemoSetStaticCache, MemoCacheStaticData); X(MemoGetLSBValue, MemoValueStaticData); X(MemoSetLSBValue, MemoValueStaticData); X(MemoGetLSBCache, MemoCacheStaticData); X(MemoSetLSBCache, MemoCacheStaticData); X(MemoGetInstanceValue, MemoValueInstanceData); X(MemoSetInstanceValue, MemoValueInstanceData); X(MemoGetInstanceCache, MemoCacheInstanceData); X(MemoSetInstanceCache, MemoCacheInstanceData); X(SetOpProp, SetOpData); X(SetOpTV, SetOpData); X(SetProp, ReadonlyData); X(OutlineSetOp, SetOpData); X(IncDecProp, IncDecData); X(SetOpElem, SetOpData); X(IncDecElem, IncDecData); X(StArResumeAddr, SuspendOffset); X(StContArState, GeneratorState); X(ContEnter, ContEnterData); X(LoadBCSP, IRSPRelOffsetData); X(JmpSSwitchDest, IRSPRelOffsetData); X(DbgTrashStk, IRSPRelOffsetData); X(DbgTrashFrame, IRSPRelOffsetData); X(DbgTraceCall, IRSPRelOffsetData); X(LdPropAddr, IndexData); X(LdInitPropAddr, IndexData); X(NewCol, NewColData); X(NewColFromArray, NewColData); X(CheckSurpriseFlagsEnter, FuncEntryData); X(CheckSurpriseAndStack, FuncEntryData); X(ContCheckNext, IsAsyncData); X(ContValid, IsAsyncData); X(LdContResumeAddr, IsAsyncData); X(LdContActRec, IsAsyncData); X(DecRef, DecRefData); X(DecRefNZ, DecRefData); X(ProfileDecRef, DecRefData); X(LdTVAux, LdTVAuxData); X(DbgAssertRefCount, AssertReason); X(Unreachable, AssertReason); X(EndBlock, AssertReason); X(VerifyParamCallable, FuncParamData); X(VerifyParamCls, FuncParamWithTCData); X(VerifyParamFail, FuncParamWithTCData); X(VerifyParamFailHard, FuncParamWithTCData); X(VerifyRetCallable, FuncParamData); X(VerifyRetCls, FuncParamWithTCData); X(VerifyRetFail, FuncParamWithTCData); X(VerifyRetFailHard, FuncParamWithTCData); X(VerifyReifiedLocalType, FuncParamData); X(VerifyReifiedReturnType, FuncData); X(VerifyPropCls, TypeConstraintData); X(VerifyPropFail, TypeConstraintData); X(VerifyPropFailHard, TypeConstraintData); X(VerifyProp, TypeConstraintData); X(VerifyPropCoerce, TypeConstraintData); X(EndCatch, EndCatchData); X(EnterTCUnwind, EnterTCUnwindData); X(FuncHasAttr, AttrData); X(ClassHasAttr, AttrData); X(LdMethCallerName, MethCallerData); X(LdUnitPerRequestFilepath, RDSHandleData); X(ConvTVToStr, ConvNoticeData); X(CheckFuncNeedsCoverage, FuncData); X(RecordFuncCall, FuncData); X(LdClsPropAddrOrNull, ReadonlyData); X(LdClsPropAddrOrRaise, ReadonlyData); X(LdMBase, AliasClassData); X(ThrowMustBeEnclosedInReadonly,ClassData); X(ThrowMustBeMutableException, ClassData); X(ThrowMustBeReadonlyException, ClassData); X(ThrowMustBeValueTypeException,ClassData); #undef X ////////////////////////////////////////////////////////////////////// template<bool hasExtra, Opcode opc, class T> struct AssertExtraTypes { static void doassertx() { assertx(!"called extra on an opcode without extra data"); } static void doassert_same() { assertx(!"called extra on an opcode without extra data"); } }; template<Opcode opc, class T> struct AssertExtraTypes<true,opc,T> { typedef typename IRExtraDataType<opc>::type ExtraType; static void doassertx() { if (!std::is_base_of<T,ExtraType>::value) { assertx(!"extra<T> was called with an extra data " "type that doesn't match the opcode type"); } } static void doassert_same() { if (!std::is_same<T,ExtraType>::value) { fprintf(stderr, "opcode = %s\n", opcodeName(opc)); \ assertx(!"extra<T> was called with an extra data type that " "doesn't exactly match the opcode type"); } } }; // Asserts that Opcode opc has extradata and it is of type T, or a // type derived from T. template<class T> void assert_opcode_extra(Opcode opc) { #define O(opcode, dstinfo, srcinfo, flags) \ case opcode: \ AssertExtraTypes< \ OpHasExtraData<opcode>::value,opcode,T \ >::doassertx(); \ break; switch (opc) { IR_OPCODES default: not_reached(); } #undef O } template<class T> void assert_opcode_extra_same(Opcode opc) { #define O(opcode, dstinfo, srcinfo, flags) \ case opcode: \ AssertExtraTypes< \ OpHasExtraData<opcode>::value,opcode,T \ >::doassert_same(); \ break; switch (opc) { IR_OPCODES default: not_reached(); } #undef O } size_t hashExtra(Opcode opc, const IRExtraData* data); size_t stableHashExtra(Opcode opc, const IRExtraData* data); bool equalsExtra(Opcode opc, const IRExtraData* a, const IRExtraData* b); IRExtraData* cloneExtra(Opcode opc, IRExtraData* data, Arena& a); std::string showExtra(Opcode opc, const IRExtraData* data); ////////////////////////////////////////////////////////////////////// }