hphp/runtime/vm/func.h (671 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/atomic-countable.h" #include "hphp/runtime/base/attr.h" #include "hphp/runtime/base/datatype.h" #include "hphp/runtime/base/rds.h" #include "hphp/runtime/base/tracing.h" #include "hphp/runtime/base/type-string.h" #include "hphp/runtime/base/typed-value.h" #include "hphp/runtime/base/user-attributes.h" #include "hphp/runtime/vm/coeffects.h" #include "hphp/runtime/vm/indexed-string-map.h" #include "hphp/runtime/vm/iter.h" #include "hphp/runtime/vm/reified-generics-info.h" #include "hphp/runtime/vm/repo-file.h" #include "hphp/runtime/vm/type-constraint.h" #include "hphp/runtime/vm/unit.h" #include "hphp/util/fixed-vector.h" #include "hphp/util/low-ptr.h" #include <atomic> #include <utility> #include <vector> namespace HPHP { /////////////////////////////////////////////////////////////////////////////// struct ActRec; struct Class; struct NamedEntity; struct PreClass; struct StringData; struct StructuredLogEntry; template <typename T> struct AtomicLowPtrVector; /* * Signature for native functions called by the hhvm using the hhvm * calling convention that provides raw access to the ActRec. */ using ArFunction = TypedValue* (*)(ActRec* ar); /* * Signature for native functions expecting the platform ABI calling * convention. This must always be casted to a proper signature before * calling, so make something up to prevent accidental mixing with other * function pointer types. */ struct NativeArgs; // never defined using NativeFunction = void(*)(NativeArgs*); using StaticCoeffectNamesMap = CompactVector<LowStringPtr>; /////////////////////////////////////////////////////////////////////////////// // EH table. /* * Exception handler table entry. */ struct EHEnt { Offset m_base; Offset m_past; int m_iterId; int m_parentIndex; Offset m_handler; Offset m_end; EHEnt() : m_base() , m_past() , m_iterId() , m_parentIndex() , m_handler() , m_end() {} template<class SerDe> void serde(SerDe& sd); }; /////////////////////////////////////////////////////////////////////////////// template <typename T, size_t Expected, size_t Actual = sizeof(T)> constexpr bool CheckSize() { static_assert(Expected == Actual); return true; }; /////////////////////////////////////////////////////////////////////////////// /* * Metadata about a PHP function or method. * * The Func class cannot be safely extended, because variable amounts of memory * associated with the Func are allocated before and after the actual object. * * All Funcs are also followed by a variable number of function prologue * pointers. Six are statically allocated as part of the Func object, but more * may follow, depending on the value of getMaxNumPrologues(). * * +--------------------------------+ Func* address * | Func object | * | | * | prologues at end of Func | * +--------------------------------+ Func* address * | [additional prologues] | * +--------------------------------+ high address * */ struct Func final { friend struct FuncEmitter; friend struct UnitEmitter; #ifndef USE_LOWPTR // DO NOT access it directly, instead use Func::getFuncVec() // Exposed in the header file for gdb python macros static AtomicLowPtrVector<const Func> s_funcVec; #endif ///////////////////////////////////////////////////////////////////////////// // Types. /* * Parameter default value info. */ struct ParamInfo { enum class Flags { InOut, // Is this an `inout' parameter? Readonly, // Is this a `readonly` parameter? Variadic, // Is this a `...' parameter? NativeArg, // Does this use a NativeArg? AsVariant, // Native function takes as const Variant& AsTypedValue // Native function takes as TypedValue }; ParamInfo(); bool hasDefaultValue() const; bool hasScalarDefaultValue() const; bool isInOut() const; bool isReadonly() const; bool isVariadic() const; bool isNativeArg() const; bool isTakenAsVariant() const; bool isTakenAsTypedValue() const; void setFlag(Flags flag); template<class SerDe> void serde(SerDe& sd); // Typehint for builtins. MaybeDataType builtinType{std::nullopt}; // Flags as defined by the Flags enum. uint8_t flags{0}; // DV initializer funclet offset. Offset funcletOff{kInvalidOffset}; // Set to Uninit if there is no DV, or if there's a nonscalar DV. TypedValue defaultValue; // Eval-able PHP code. LowStringPtr phpCode{nullptr}; // User-annotated type. LowStringPtr userType{nullptr}; // offset of dvi funclet from cti section base. Offset ctiFunclet{kInvalidOffset}; TypeConstraint typeConstraint; UserAttributeMap userAttributes; }; using ParamInfoVec = VMFixedVector<ParamInfo>; using EHEntVec = VMFixedVector<EHEnt>; using UpperBoundVec = VMCompactVector<TypeConstraint>; using ParamUBMap = vm_flat_map<uint32_t, UpperBoundVec>; using CoeffectRules = VMFixedVector<CoeffectRule>; ///////////////////////////////////////////////////////////////////////////// // Creation and destruction. Func(Unit& unit, const StringData* name, Attr attrs); Func(Unit& unit, const StringData* name, Attr attrs, const StringData *methCallerCls, const StringData *methCallerMeth); ~Func(); /* * Allocate memory for a function, including the variable number of prologues * that follow. */ static void* allocFuncMem(int numParams); /* * Destruct and free a Func*. */ static void destroy(Func* func); /* * Address of the end of the Func's variable-length memory allocation. */ const void* mallocEnd() const; /* * Duplicate this function. * * Funcs are cloned for a number of reasons---most notably, methods on * Classes are cloned from the methods defined on their respective * PreClasses. * * We also clone methods from traits when we transclude the trait in its user * Classes in repo mode. */ Func* clone(Class* cls, const StringData* name = nullptr) const; /* * Reset this function's cls and attrs. * * Used to change the Class scope of a closure method. */ void rescope(Class* ctx); /* * Free up a PreFunc for re-use as a cloned Func. * * @requires: isPreFunc() */ void freeClone(); /* * Verify that a Func's data is coherent. * * FIXME: Currently this method does almost nothing. */ bool validate() const; ///////////////////////////////////////////////////////////////////////////// // FuncId manipulation. /* * Get this function's ID. * * We allocate a unique 32-bit ID to almost all Funcs. The Func* can be * retrieved by using this ID as an index into a global vector. This lets * the JIT store references to Funcs more compactly. * * Funcs which do not represent actual runtime functions (namely, Funcs on * PreClasses) are not assigned an ID. */ FuncId getFuncId() const; private: /* * Reserve the next available FuncId for `this', and add `this' to the * function table. */ void setNewFuncId(); public: /* * The max FuncId num. */ static FuncId::Int maxFuncIdNum(); /* * Lookup a Func* by its ID. */ static const Func* fromFuncId(FuncId id); /* * Whether `id' actually keys a Func*. */ static bool isFuncIdValid(FuncId id); ///////////////////////////////////////////////////////////////////////////// // Basic info. [const] /* * The Unit the function is defined in. */ Unit* unit() const; /* * The various Class contexts of a method. * * cls(): The Class context of the method. This is usually the Class * which implements the method, but for closure methods (i.e., * the __invoke() method on a closure object), it is instead the * Class that the Closure object is scoped to. * * preClass(): The PreClass of the method's cls(). For closures, this still * corresponds to the Closure subclass, rather than to the * scoped Class. * * When isFromTrait() is true, preClass() refers to different * entities in repo vs. non-repo mode. In repo mode, traits are * flattened ahead of time, and preClass() refers to the class * which imported the trait. In non-repo mode, trait methods * are cloned into trait users, but preClass() will still refer * to the trait which defined the method. * * baseCls(): The first Class in the inheritance hierarchy which declares * this method. * * implCls(): The Class which implements the method. Just like cls(), but * ignores closure scope (so it returns baseCls() for closures). * * It is possible for cls() to be nullptr on a method---this occurs when a * closure method is scoped to a null class context (e.g., if the closure is * created in a non-method function scope). In this case, only the `cls' is * changed; the `preClass' and `baseCls' will continue to refer to the * PreClass and Class of the closure object. * * The converse also occurs---a function can have a `cls' (and `baseCls') * without being a method. This happens when a pseudomain is included from a * class context. * * Consequently, none of these methods should be used to test whether the * function is a method; for that purpose, see isMethod(). */ Class* cls() const; PreClass* preClass() const; bool hasBaseCls() const; Class* baseCls() const; Class* implCls() const; int sn() const; /* * The function's short name (e.g., foo). */ const StringData* name() const; String nameWithClosureName() const; StrNR nameStr() const; /* * A hash for this func that will remain constant across process restarts. */ size_t stableHash() const; /* * The function's fully class-qualified, name (e.g., C::foo). */ const StringData* fullName() const; String fullNameWithClosureName() const; StrNR fullNameStr() const; /* * The function's named entity. Only valid for non-methods. * * @requires: shared()->m_preClass == nullptr */ NamedEntity* getNamedEntity(); const NamedEntity* getNamedEntity() const; /** * meth_caller */ const StringData* methCallerClsName() const; const StringData* methCallerMethName() const; ///////////////////////////////////////////////////////////////////////////// // File info. [const] /* * The filename where the function was originally defined. * * In repo mode, we flatten traits into the classes they're used in, so we * need this to track the original file for backtraces and errors. */ const StringData* originalFilename() const; /* * The original filename if it is defined, the unit's filename otherwise. */ const StringData* filename() const; /* * Start and end line of the function. * * It'd be nice if these were called lineStart and lineEnd or something, but * we're not allowed to have nice things. */ int line1() const; int line2() const; /* * The system- or user-defined doc comment accompanying the function. */ const StringData* docComment() const; ///////////////////////////////////////////////////////////////////////////// // Bytecode. [const] /* * Get the function's main entrypoint. */ PC entry() const; Offset bclen() const; /* * Whether a given PC or Offset (from the beginning of the unit) is within * the function's bytecode stream. */ bool contains(PC pc) const; bool contains(Offset offset) const; /* * Convert between PC and Offset from entry(). */ PC at(Offset off) const; Offset offsetOf(PC pc) const; /* * Get the Op at `instrOffset'. */ Op getOp(Offset instrOffset) const; /* * Is there a main or default value entrypoint at the given offset? */ bool isEntry(Offset offset) const; bool isDVEntry(Offset offset) const; /* * Number of params required when entering at the given offset. * * Return -1 if an invalid offset is provided. */ int getEntryNumParams(Offset offset) const; int getDVEntryNumParams(Offset offset) const; /* * Get the correct entrypoint (whether the main entry or a DV funclet) when * `numArgsPassed' arguments are passed to the function. * * This is the DV funclet offset of the numArgsPassed-th parameter, or the * next parameter that has a DV funclet. */ Offset getEntryForNumArgs(int numArgsPassed) const; // CTI entry points Offset ctiEntry() const; void setCtiFunclet(int i, Offset); void setCtiEntry(Offset entry, uint32_t size); ///////////////////////////////////////////////////////////////////////////// // Return type. [const] /* * CPP builtin's return type. Returns std::nullopt if function is not a CPP * builtin. * * There are a number of caveats regarding this value: * * - If the return type is std::nullopt, the return is a Variant. * * - If the return type is a string, array-like, object, ref, or resource * type, null may also be returned. * * - Likewise, if the function is marked AttrParamCoerceModeNull, null * might also be returned. * * - This list of caveats may be incorrect and/or incomplete. */ MaybeDataType hniReturnType() const; /* * Return type inferred by HHBBC's static analysis. TGen if no data is * available. */ RepoAuthType repoReturnType() const; /* * For async functions, the statically inferred inner type of the returned * WH based on HHBBC's analysis. */ RepoAuthType repoAwaitedReturnType() const; /* * For builtins, whether the return value is returned in registers (as * opposed to indirect return, via tvBuiltinReturn). * * Not well-defined if this function is not a builtin. */ bool isReturnByValue() const; /* * The TypeConstraint of the return. */ const TypeConstraint& returnTypeConstraint() const; /* * The user-annotated Hack return type. */ const StringData* returnUserType() const; bool hasReturnWithMultiUBs() const; const UpperBoundVec& returnUBs() const; ///////////////////////////////////////////////////////////////////////////// // Parameters. [const] /* * Const reference to the parameter info table. * * ParamInfo objects pulled from the table will also be const. */ const ParamInfoVec& params() const; /* * Number of parameters (including `...') accepted by the function. */ uint32_t numParams() const; /* * Number of parameters, not including `...', accepted by the function. */ uint32_t numNonVariadicParams() const; /* * Number of required parameters, i.e. all arguments starting from * the returned position have default value. */ uint32_t numRequiredParams() const; /* * Whether the function is declared with a `...' parameter. */ bool hasVariadicCaptureParam() const; /* * Whether the arg-th parameter was declared inout. */ bool isInOut(int32_t arg) const; /* * Return the raw m_inoutBits field. */ uint64_t inOutBits() const; /* * Whether the arg-th parameter was declared readonly. */ bool isReadonly(int32_t arg) const; /* * Whether any of the parameters to this function are inout parameters. */ bool takesInOutParams() const; /* * Returns the number of inout parameters taken by func. */ uint32_t numInOutParams() const; /* * Returns the number of inout parameters for the given number of * arguments. */ uint32_t numInOutParamsForArgs(int32_t numArgs) const; bool hasParamsWithMultiUBs() const; const ParamUBMap& paramUBs() const; ///////////////////////////////////////////////////////////////////////////// // Locals, iterators, and stack. [const] /* * Number of locals, iterators, closure use locals or named locals. */ int numLocals() const; int numIterators() const; uint32_t numClosureUseLocals() const; Id numNamedLocals() const; /* * Find the integral ID assigned to a named local. */ Id lookupVarId(const StringData* name) const; /* * Number of initialized locals at FuncEntry. */ uint32_t numFuncEntryInputs() const; /* * Returns the ID of coeffects and reified generics locals. * Requires hasCoeffectRules() and hasReifiedGenerics() respectively */ uint32_t coeffectsLocalId() const; uint32_t reifiedGenericsLocalId() const; /* * Returns the ID of the first closure use local. */ uint32_t firstClosureUseLocalId() const; /* * Returns the ID of the first regular local, i.e. a first local that is not * a parameter, reified generics, coeffects or closure use local. */ uint32_t firstRegularLocalId() const; /* * Find the name of the local with the given ID. */ const StringData* localVarName(Id id) const; /* * Array of named locals. Includes parameter names. * May contain nullptrs for unammed locals that mixed in with named ones. * * Should not be indexed past numNamedLocals() - 1. */ LowStringPtr const* localNames() const; /* * Number of stack slots used by locals and iterator cells. */ int numSlotsInFrame() const; /* * Access to the maximum stack cells this function can use. This is * used for stack overflow checks. * * The maximum cells for a function includes all its locals, all cells * for its iterators, and all temporary eval stack slots. It does not * include its own ActRec, because whoever called it must have(+) included * the stack slot space reserved for this ActRec. The reason it must still * count its parameter locals is that the caller may or may not pass any of * the parameters, regardless of how many are declared. * * + Except in a re-entry situation. That must be handled * specially in bytecode.cpp. */ int maxStackCells() const; /* * Checks if $this belong to a class that is not a subclass of cls(). */ bool hasForeignThis() const; void setHasForeignThis(bool); void registerInDataMap(); void deregisterInDataMap(); ///////////////////////////////////////////////////////////////////////////// // Definition context. [const] /* * Is this function a method defined on a class? * * Note that trait methods may not satisfy isMethod(). */ bool isMethod() const; /* * Was this function imported from a trait? * * Note that this returns false for a trait method in the trait it was * originally declared. */ bool isFromTrait() const; /* * Is this function declared with `public', `static', or `abstract'? */ bool isPublic() const; bool isStatic() const; bool isAbstract() const; /* * Whether a function is called non-statically. Generally this means * isStatic(), but eg static closures are still called with a valid * this pointer. */ bool isStaticInPrologue() const; /* * Whether a method is guaranteed to have a valid this in the body. * A method which is !isStatic() || isClosureBody() is guaranteed to * be called with a valid this, but closures swap out the closure * object for the closure context in the prologue, so may not have * a this in the body. */ bool hasThisInBody() const; /* * Is this Func owned by a PreClass? * * A PreFunc may be "adopted" by a Class when clone() is called, but only the * owning PreClass is allowed to free it. */ bool isPreFunc() const; /* * Is this func a memoization wrapper? */ bool isMemoizeWrapper() const; /* * Is this func a memoization wrapper with LSB parameter set? */ bool isMemoizeWrapperLSB() const; bool isPolicyShardedMemoize() const; /* * Is this string the name of a memoize implementation. */ static bool isMemoizeImplName(const StringData*); /* * Is this function a memoization implementation. */ bool isMemoizeImpl() const; /* * Assuming this func is a memoization wrapper, the name of the function it is * wrapping. * * Pre: isMemoizeWrapper() */ const StringData* memoizeImplName() const; /* * Given the name of a memoization wrapper function, return the generated name * of the function it wraps. This is static so it can be used in contexts * where the actual Func* is not available. */ static const StringData* genMemoizeImplName(const StringData*); /* * Returns the number of local slots used for the memoization key calculation. */ size_t numKeysForMemoize() const; /* * Given a meth_caller, return the class name or method name */ static std::pair<const StringData*, const StringData*> getMethCallerNames( const StringData* name); ///////////////////////////////////////////////////////////////////////////// // Builtins. [const] /* * Is the function a builtin, whether PHP or C++? */ bool isBuiltin() const; /* * Is this function a C++ builtin (ie HNI function)?. * * @implies: isBuiltin() */ bool isCPPBuiltin() const; /* * The function returned by arFuncPtr() takes an ActRec*, unpacks it, * and usually dispatches to a nativeFuncPtr() with a specific signature. * * All C++ builtins have an ArFunction, with no exceptions. * * Most HNI functions share a single ArFunction, which performs * unpacking and dispatch. The exception is HNI functions declared * with NeedsActRec, which do not have NativeFunctions, but have unique * ArFunctions which do all their work. */ ArFunction arFuncPtr() const; /* * The nativeFuncPtr is a type-punned function pointer to the unerlying * function which takes the actual argument types, and does the actual work. * * These are the functions with names prefixed by f_ or t_. * * All C++ builtins have NativeFunctions, with the ironic exception of HNI * functions declared with NeedsActRec. */ NativeFunction nativeFuncPtr() const; ///////////////////////////////////////////////////////////////////////////// // Closures. [const] /* * Is this function the body (i.e., __invoke() method) of a Closure object? * * (All PHP anonymous functions are Closure objects.) */ bool isClosureBody() const; ///////////////////////////////////////////////////////////////////////////// // Resumables. [const] /* * Is this function asynchronous? (May also be a generator.) */ bool isAsync() const; /* * Is this function a generator? (May also be async.) */ bool isGenerator() const; /* * Is this function a generator which yields both key and value? * * @implies: isGenerator() */ bool isPairGenerator() const; /* * @returns: !isGenerator() && isAsync() */ bool isAsyncFunction() const; /* * @returns: isGenerator() && !isAsync() */ bool isNonAsyncGenerator() const; /* * @returns: isGenerator() && isAsync() */ bool isAsyncGenerator() const; /* * Is this a resumable function? * * @returns: isGenerator() || isAsync() */ bool isResumable() const; ///////////////////////////////////////////////////////////////////////////// // Coeffects. [const] /* * Returns the runtime representation of coeffects */ RuntimeCoeffects requiredCoeffects() const; RuntimeCoeffects coeffectEscapes() const; /* * Sets required coeffects */ void setRequiredCoeffects(RuntimeCoeffects); /* * Names of the static coeffects on the function * Used for reflection */ StaticCoeffectNamesMap staticCoeffectNames() const; /* * Does this function use coeffects local to store its ambient coeffects? */ bool hasCoeffectsLocal() const; /* * Does this function have coeffect rules? */ bool hasCoeffectRules() const; /* * List of rules for enforcing coeffects */ const CoeffectRules& getCoeffectRules() const; ///////////////////////////////////////////////////////////////////////////// // Methods. [const] /* * Index of this function in the method table of its Class. */ Slot methodSlot() const; /* * Whether this function has a private implementation on a parent class. */ bool hasPrivateAncestor() const; ///////////////////////////////////////////////////////////////////////////// // Magic methods. [const] /* * Is this a compiler-generated function? * * This includes special methods like 86pinit and 86sinit as well * as all closures. */ bool isGenerated() const; /* * Is `name' the name of a special initializer function? */ static bool isSpecial(const StringData* name); ///////////////////////////////////////////////////////////////////////////// // Persistence. [const] /* * Whether this function is uniquely named across the codebase. * * It's legal in PHP to define multiple functions in different pseudomains * with the same name, so long as both are not required in the same request. * * Note that if EvalJitEnableRenameFunction is set, no Func is unique. */ bool isUnique() const; /* * Whether we can load this function once and persist it across requests. * * Persistence is possible when a Func is defined in a pseudomain that has no * side-effects (except other persistent definitions). * * @implies: isUnique() */ bool isPersistent() const; bool isInterceptable() const; /* * Given that func would be called when func->name() is invoked on cls, * determine if it would also be called when invoked on any descendant * of cls. */ bool isImmutableFrom(const Class* cls) const; ///////////////////////////////////////////////////////////////////////////// // Other attributes. [const] /* * Get the system and coeffect attributes of the function. */ Attr attrs() const; /* * Get the user-declared attributes of the function. */ const UserAttributeMap& userAttributes() const; /* * Whether to ignore this function's frame in backtraces. */ bool isNoInjection() const; /* * Whether this function's frame should be skipped when searching for context * (e.g., array_map evaluates its callback in the context of its caller). */ bool isSkipFrame() const; /* * Whether this function's frame should be skipped with searching for a * context for array provenance */ bool isProvenanceSkipFrame() const; /* * Whether the function can be constant-folded at callsites where it is * passed constant arguments. */ bool isFoldable() const; /* * Supports async eager return optimization? */ bool supportsAsyncEagerReturn() const; /* * Is this func allowed to be called dynamically? */ bool isDynamicallyCallable() const; /* * If this function is called dynamically should we raise sampled warnings? * * N.B. When errors are enabled for dynamic calls this overrides that behavior * for functions which specify it. */ Optional<int64_t> dynCallSampleRate() const; /* * Is this a meth_caller func? */ bool isMethCaller() const; /* * Indicates that a function does not make any explicit calls to other PHP * functions. It may still call other user-level functions via re-entry * (e.g., for autoload), and it may make calls to builtins using FCallBuiltin. */ bool isPhpLeafFn() const; /* * Does this function has reified generics? */ bool hasReifiedGenerics() const; /* * Returns a ReifiedGenericsInfo containing how many generics this func has, * indices of its reified generics, and which ones are soft reified */ const ReifiedGenericsInfo& getReifiedGenericsInfo() const; ///////////////////////////////////////////////////////////////////////////// // Unit table entries. [const] const EHEntVec& ehtab() const; /* * Find the first EHEnt that covers a given offset, or return null. */ const EHEnt* findEH(Offset o) const; /* * Same as non-static findEH(), but takes as an operand any ehtab-like * container. */ template<class Container> static const typename Container::value_type* findEH(const Container& ehtab, Offset o); bool shouldSampleJit() const { return m_shouldSampleJit; } ///////////////////////////////////////////////////////////////////////////// // JIT data. /* * Get the RDS handle for the function with this function's name. * * We can burn these into the TC even when functions are not persistent, * since only a single name-to-function mapping will exist per request. */ rds::Handle funcHandle() const; /* * Get, set and reset the function body code pointer. */ jit::TCA getFuncEntry() const; void setFuncEntry(jit::TCA funcEntry); void resetFuncEntry(); /* * Get and set the `index'-th function prologue. */ uint8_t* getPrologue(int index) const; void setPrologue(int index, unsigned char* tca); /* * Number of prologues allocated for the function. */ int numPrologues() const; /* * Reset a specific prologue, or all prologues. */ void resetPrologue(int numParams); ///////////////////////////////////////////////////////////////////////////// // Pretty printer. [const] struct PrintOpts { PrintOpts() : name(true) , metadata(true) , startOffset(0) , stopOffset(kInvalidOffset) , showLines(true) , indentSize(1) {} PrintOpts& noName() { name = false; return *this; } PrintOpts& noMetadata() { metadata = false; return *this; } PrintOpts& noBytecode() { startOffset = kInvalidOffset; stopOffset = kInvalidOffset; return *this; } PrintOpts& range(Offset start, Offset stop) { startOffset = start; stopOffset = stop; return *this; } PrintOpts& noLineNumbers() { showLines = false; return *this; } PrintOpts& indent(int i) { indentSize = i; return *this; } bool name; bool metadata; Offset startOffset; Offset stopOffset; bool showLines; int indentSize; }; void prettyPrint(std::ostream& out, const PrintOpts& = PrintOpts()) const; /* * Print function attributes to out. */ static void print_attrs(std::ostream& out, Attr attrs); ///////////////////////////////////////////////////////////////////////////// // Other methods. // // You should avoid adding methods to this section. If the logic you're // implementing is specific to a particular subsystem, define it as a helper // there instead. // // If you absolutely must add more methods to Func here, just follow these // simple guidelines: // // (1) Don't add more methods to Func here. /* * Intercept hook flag. */ bool maybeIntercepted() const; void setMaybeIntercepted(); /* * When function call based coverage is enabled for the current request, * records a call to `this`. The no check version asserts that function * coverage has already been enabled and the function is both eligible to be * covered and has not yet been seen. */ void recordCall() const; void recordCallNoCheck() const; /* * EnableCoverage enables recording of called functions for the current * request. */ static void EnableCoverage(); /* * GetCoverage returns a keyset of called functions and disables further * coverage for the current request until reenabled by EnableCoverage. */ static Array GetCoverage(); /* * RDS based counter (uint32_t) that when zero indicates coverage is disabled * and when non-zero indicates an index which can be used to short circuit * tests that functions have been covered. */ static rds::Handle GetCoverageIndex(); /* * Get an RDS counter (uint32_t) that can be compared against GetCoverageIndex * to determine if the function has been covered in the current request. */ rds::Handle getCoverageHandle() const; ///////////////////////////////////////////////////////////////////////////// // Public setters. // // TODO(#4504609): These setters are only used by Class at Class creation // time. We should refactor the creation path into a separate friend module // to avoid this garbage. // // Having public setters here should be avoided, so try not to add any. void setAttrs(Attr attrs); void setBaseCls(Class* baseCls); void setHasPrivateAncestor(bool b); void setMethodSlot(Slot s); void setGenerated(bool b); ///////////////////////////////////////////////////////////////////////////// // Offset accessors. [static] #define OFF(f) \ static constexpr ptrdiff_t f##Off() { \ return offsetof(Func, m_##f); \ } OFF(attrs) OFF(requiredCoeffects) OFF(name) OFF(maxStackCells) OFF(paramCounts) OFF(prologueTable) OFF(inoutBits) OFF(shared) OFF(unit) OFF(methCallerMethName) #undef OFF static constexpr ptrdiff_t clsOff() { return offsetof(Func, m_u); } static constexpr ptrdiff_t methCallerClsNameOff() { return offsetof(Func, m_u); } static constexpr ptrdiff_t sharedAllFlags() { return offsetof(SharedData, m_allFlags); } static uint32_t reifiedGenericsMask() { ExtendedSharedData::Flags mask; mask.m_allFlags = 0; mask.m_hasReifiedGenerics = true; return mask.m_allFlags; } ///////////////////////////////////////////////////////////////////////////// // Lookup [static] /* * Define `func' for this request by initializing its RDS handle. */ static void def(Func* func); /* * Look up the defined Func in this request with name `name', or with the name * mapped to the NamedEntity `ne'. * * Return nullptr if the function is not yet defined in this request. */ static Func* lookup(const NamedEntity* ne); static Func* lookup(const StringData* name); /* * Look up, or autoload and define, the Func in this request with name `name', * or with the name mapped to the NamedEntity `ne'. * * @requires: NamedEntity::get(name) == ne */ static Func* load(const NamedEntity* ne, const StringData* name); static Func* load(const StringData* name); /* * Lookup the builtin in this request with name `name', or nullptr if none * exists. This does not access RDS so it is safe to use from within the * compiler. Note that does not mean imply that the name binding for the * builtin is immutable. The builtin could be renamed or intercepted. */ static Func* lookupBuiltin(const StringData* name); ///////////////////////////////////////////////////////////////////////////// // SharedData. private: using NamedLocalsMap = IndexedStringMap<LowStringPtr, Id>; using BCPtr = TokenOrPtr<unsigned char>; using LineTablePtr = TokenOrPtr<LineTable>; // Some 16-bit values in SharedData are stored as small deltas if they fit // under this limit. If not, they're set to the limit value and an // ExtendedSharedData will be allocated for the full-width field. static constexpr auto kSmallDeltaLimit = uint16_t(-1); /* * Properties shared by all clones of a Func. */ struct SharedData : AtomicCountable { SharedData(BCPtr bc, Offset bclen, PreClass* preClass, int sn, int line1, int line2, bool isPhpLeafFn); ~SharedData(); /* * Interface for AtomicCountable. */ void atomicRelease(); Offset bclen() const; /* * Data fields are packed to minimize size. Try not to add anything new * here or reorder anything. */ // (There's a 32-bit integer in the AtomicCountable base class here.) LockFreePtrWrapper<BCPtr> m_bc; PreClass* m_preClass; int m_line1; ParamInfoVec m_params; NamedLocalsMap m_localNames; EHEntVec m_ehtab; StaticCoeffectNamesMap m_staticCoeffectNames; /* * Up to 16 bits. */ union Flags { struct { bool m_isClosureBody : true; bool m_isAsync : true; bool m_isGenerator : true; bool m_isPairGenerator : true; bool m_isGenerated : true; bool m_hasExtendedSharedData : true; bool m_returnByValue : true; // only for builtins bool m_isMemoizeWrapper : true; bool m_isMemoizeWrapperLSB : true; bool m_isPolicyShardedMemoize : true; bool m_isPhpLeafFn : true; bool m_hasReifiedGenerics : true; bool m_hasParamsWithMultiUBs : true; bool m_hasReturnWithMultiUBs : true; }; uint16_t m_allFlags; }; static_assert(sizeof(Flags) == sizeof(uint16_t)); Flags m_allFlags; uint16_t m_sn; LowStringPtr m_retUserType; UserAttributeMap m_userAttributes; TypeConstraint m_retTypeConstraint; // NB: sizeof(TypeConstraint) == 12 LowStringPtr m_originalFilename; RepoAuthType m_repoReturnType; RepoAuthType m_repoAwaitedReturnType; /* * The `line2' are likely to be small, particularly relative to m_line1, * so we encode each as a 16-bit difference. * * If the delta doesn't fit, we need to have an ExtendedSharedData to hold * the real values---in that case, the field here that overflowed is set to * kSmallDeltaLimit and the corresponding field in ExtendedSharedData will * be valid. */ uint16_t m_line2Delta; /** * bclen is likely to be small. So we encode each as a 16-bit value * * If the value doesn't fit, we need to have an ExtendedSharedData to hold * the real values---in that case, the field here that overflowed is set to * kSmallDeltaLimit and the corresponding field in ExtendedSharedData will * be valid. */ uint16_t m_bclenSmall; std::atomic<Offset> m_cti_base; // relative to CodeCache cti section uint32_t m_cti_size; // size of cti code uint16_t m_numLocals; uint16_t m_numIterators; mutable LockFreePtrWrapper<VMCompactVector<LineInfo>> m_lineMap; mutable LockFreePtrWrapper<LineTablePtr> m_lineTable; }; static_assert(CheckSize<SharedData, use_lowptr ? 152 : 176>(), ""); /* * If this Func represents a native function or is exceptionally large * (line count or bytecode size), it requires extra information that most * Funcs don't need, so it's SharedData is actually one of these extended * SharedDatas. */ struct ExtendedSharedData : SharedData { template<class... Args> explicit ExtendedSharedData(Args&&... args) : SharedData(std::forward<Args>(args)...) { m_allFlags.m_hasExtendedSharedData = true; } ExtendedSharedData(const ExtendedSharedData&) = delete; ExtendedSharedData(ExtendedSharedData&&) = delete; ~ExtendedSharedData(); ArFunction m_arFuncPtr; NativeFunction m_nativeFuncPtr; ReifiedGenericsInfo m_reifiedGenericsInfo; ParamUBMap m_paramUBs; UpperBoundVec m_returnUBs; CoeffectRules m_coeffectRules; Offset m_bclen; // Only read if SharedData::m_bclen is kSmallDeltaLimit int m_line2; // Only read if SharedData::m_line2 is kSmallDeltaLimit int m_sn; // Only read if SharedData::m_sn is kSmallDeltaLimit MaybeDataType m_hniReturnType; RuntimeCoeffects m_coeffectEscapes{RuntimeCoeffects::none()}; int64_t m_dynCallSampleRate; LowStringPtr m_docComment; }; static_assert(CheckSize<ExtendedSharedData, use_lowptr ? 280 : 304>(), ""); /* * SharedData accessors for internal use. */ const SharedData* shared() const { return m_shared.get(); } SharedData* shared() { return m_shared.get(); } /* * Returns ExtendedSharedData if we have one, or else a nullptr. */ const ExtendedSharedData* extShared() const; ExtendedSharedData* extShared(); /* * We store 'detailed' line number information on a table on the side, because * in production modes for HHVM it's generally not useful (which keeps Func * smaller in that case)---this stuff is only used for the debugger, where we * can afford the lookup here. The normal Func m_lineMap is capable of * producing enough line number information for things needed in production * modes (backtraces, warnings, etc). */ struct ExtendedLineInfo { SourceLocTable sourceLocTable; /* * Map from source lines to a collection of all the bytecode ranges the line * encompasses. * * The value type of the map is a list of offset ranges, so a single line * with several sub-statements may correspond to the bytecodes of all of the * sub-statements. * * May not be initialized. Lookups need to check if it's empty() and if so * compute it from sourceLocTable. */ LineToOffsetRangeVecMap lineToOffsetRange; }; using ExtendedLineInfoCache = tbb::concurrent_hash_map< const SharedData*, ExtendedLineInfo, pointer_hash<SharedData> >; static ExtendedLineInfoCache s_extendedLineInfo; ///////////////////////////////////////////////////////////////////////////// // Internal methods. // // These are all used at emit-time, and should be outsourced to FuncEmitter. private: Func(const Func&) = default; // used for clone() Func& operator=(const Func&) = delete; void init(int numParams); void initPrologues(int numParams); void setFullName(int numParams); void finishedEmittingParams(std::vector<ParamInfo>& pBuilder); void setNamedEntity(const NamedEntity*); PC loadBytecode(); ///////////////////////////////////////////////////////////////////////////// // Internal types. struct ClonedFlag { ClonedFlag() {} ClonedFlag(const ClonedFlag&) {} ClonedFlag& operator=(const ClonedFlag&) = delete; std::atomic_flag flag = ATOMIC_FLAG_INIT; }; /* * Wrapper around std::atomic<Attr> that pretends like it's not atomic. * * Func::m_attrs is only accessed by multiple threads in the closure scoping * process for Closure classes, which is synchronized in Class::rescope(). * This wrapper is just to make m_attrs copy-constructible, and there should * never be a race when copying. */ struct AtomicAttr { AtomicAttr() {} explicit AtomicAttr(Attr attrs) : m_attrs{attrs} {} AtomicAttr(const AtomicAttr& o) : m_attrs{o.m_attrs.load(std::memory_order_relaxed)} {} AtomicAttr& operator=(Attr attrs) { m_attrs.store(attrs, std::memory_order_relaxed); return *this; } /* implicit */ operator Attr() const { return m_attrs.load(std::memory_order_relaxed); } private: std::atomic<Attr> m_attrs; }; public: #ifdef USE_LOWPTR using low_storage_t = uint32_t; #else using low_storage_t = uintptr_t; #endif private: /* * Lowptr wrapper around std::atomic<Union> for Class* or StringData* */ struct UnionWrapper { union U { low_storage_t m_cls; low_storage_t m_methCallerClsName; }; std::atomic<U> m_u; // constructors explicit UnionWrapper(Class *cls) : m_u([](Class *cls){ U u; u.m_cls = to_low(cls); return u; }(cls)) {} explicit UnionWrapper(const StringData *name) : m_u([](const StringData *n){ U u; u.m_methCallerClsName = to_low(n, kMethCallerBit); return u; }(name)) {} /* implicit */ UnionWrapper(std::nullptr_t /*px*/) : m_u([](){ U u; u.m_cls = 0; return u; }()) {} UnionWrapper(const UnionWrapper& r) : m_u(r.m_u.load()) { } // Assignments UnionWrapper& operator=(UnionWrapper r) { m_u.store(r.m_u, std::memory_order_relaxed); return *this; } // setter & getter void setCls(Class *cls) { U u; u.m_cls = to_low(cls); m_u.store(u, std::memory_order_relaxed); } Class* cls() const { auto cls = m_u.load(std::memory_order_relaxed).m_cls; assertx(!(cls & kMethCallerBit)); return reinterpret_cast<Class*>(cls); } StringData* name() const { auto n = m_u.load(std::memory_order_relaxed).m_methCallerClsName; assertx(n & kMethCallerBit); return reinterpret_cast<StringData*>(n - kMethCallerBit); } }; template <class T> static Func::low_storage_t to_low(T* px, Func::low_storage_t bit = 0) { Func::low_storage_t ones = ~0; auto ptr = reinterpret_cast<uintptr_t>(px) | bit; always_assert((ptr & ones) == ptr); return (Func::low_storage_t)(ptr); } ///////////////////////////////////////////////////////////////////////////// // Atomic Flags. public: enum Flags : uint8_t { None = 0, Optimized = 1 << 0, Locked = 1 << 1, MaybeIntercepted = 1 << 2, }; /* * Wrapper around std::atomic<uint8_t> that enables it to be * copy constructable, */ struct AtomicFlags { AtomicFlags() {} AtomicFlags(const AtomicFlags&) {} AtomicFlags& operator=(const AtomicFlags&) = delete; bool set(Flags flags) { auto const prev = m_flags.fetch_or(flags, std::memory_order_release); return prev & flags; } bool unset(Flags flags) { auto const prev = m_flags.fetch_and(~uint8_t(flags), std::memory_order_release); return prev & flags; } bool check(Flags flags) const { return m_flags.load(std::memory_order_acquire) & flags; } std::atomic<uint8_t> m_flags{Flags::None}; }; inline AtomicFlags& atomicFlags() const { return m_atomicFlags; } inline AtomicFlags& atomicFlags() { return m_atomicFlags; } ///////////////////////////////////////////////////////////////////////////// // Code locations. [const] /* * Get the line number corresponding to `offset'. * * Return -1 if not found. */ int getLineNumber(Offset offset) const; /* * Get the SourceLoc corresponding to `offset'. * * Return false if not found, else true. */ bool getSourceLoc(Offset offset, SourceLoc& sLoc) const; /* * Get the Offset range(s) corresponding to `offset'. * * Return false if not found, else true. */ bool getOffsetRange(Offset offset, OffsetRange& range) const; void setLineTable(LineTable); void setLineTable(LineTablePtr::Token); void stashExtendedLineTable(SourceLocTable table) const; const SourceLocTable& getLocTable() const; LineToOffsetRangeVecMap getLineToOffsetRangeVecMap() const; const LineTable* getLineTable() const; LineTable getOrLoadLineTableCopy() const; private: const LineTable& getOrLoadLineTable() const; ///////////////////////////////////////////////////////////////////////////// // Constants. private: static constexpr int kMagic = 0xba5eba11; static constexpr intptr_t kNeedsFullName = 0x1; public: // Use by m_inoutBits static constexpr uint32_t kInoutFastCheckBits = 31; static std::atomic<bool> s_treadmill; static std::atomic<uint32_t> s_totalClonedClosures; // To conserve space, we use unions for pairs of mutually exclusive fields static auto constexpr kMethCallerBit = 0x1; // set for m_methCaller ///////////////////////////////////////////////////////////////////////////// // Data members. // // The fields of Func are organized in reverse order of frequency of use. // Do not re-order without checking perf! private: #ifndef NDEBUG // For asserts only. int m_magic; #endif AtomicLowPtr<uint8_t> m_funcEntry{nullptr}; #ifndef USE_LOWPTR FuncId m_funcId{FuncId::Invalid}; #endif mutable AtomicLowPtr<const StringData> m_fullName{nullptr}; LowStringPtr m_name{nullptr}; union { // The first Class in the inheritance hierarchy that declared this method. // Note that this may be an abstract class that did not provide an // implementation. low_storage_t m_baseCls{0}; // m_methCallerMethName can be accessed by meth_caller() only low_storage_t m_methCallerMethName; }; // m_u is used to represent // the Class that provided this method implementation, or // the class name provided by meth_caller() UnionWrapper m_u{nullptr}; union { Slot m_methodSlot{0}; LowPtr<const NamedEntity>::storage_type m_namedEntity; }; mutable ClonedFlag m_cloned; mutable AtomicFlags m_atomicFlags; bool m_isPreFunc : 1; bool m_hasPrivateAncestor : 1; bool m_shouldSampleJit : 1; bool m_hasForeignThis : 1; bool m_registeredInDataMap : 1; // 3 free bits + 1 free byte RuntimeCoeffects m_requiredCoeffects{RuntimeCoeffects::none()}; int16_t m_maxStackCells{0}; Unit* const m_unit; AtomicSharedPtr<SharedData> m_shared; // The lower 31 bits represent inout-ness of the corresponding parameter. The // highest bit is set if there is an inout parameter beyond the 0..31 range. // Initialized by Func::finishedEmittingParams. uint32_t m_inoutBits{0}; // Initialized by Func::finishedEmittingParams. The least significant bit is // 1 if the last param is not variadic; the 31 most significant bits are the // total number of params (including the variadic param). uint32_t m_paramCounts{0}; AtomicAttr m_attrs; // This must be the last field declared in this structure, and the Func class // should not be inherited from. AtomicLowPtr<uint8_t> m_prologueTable[1]; }; static constexpr size_t kFuncSize = debug ? (use_lowptr ? 72 : 112) : (use_lowptr ? 64 : 104); static_assert(CheckSize<Func, kFuncSize>(), ""); /////////////////////////////////////////////////////////////////////////////// /* * A prologue is identified by the called function and the number of arguments * that the prologue handles. */ struct PrologueID { PrologueID(FuncId funcId, uint32_t nargs) : m_funcId(funcId) , m_nargs(nargs) { } PrologueID(const Func* func, uint32_t nargs) : m_funcId(func->getFuncId()) , m_nargs(nargs) { } PrologueID() { } FuncId funcId() const { return m_funcId; } uint32_t nargs() const { return m_nargs; } const Func* func() const { return Func::fromFuncId(m_funcId); } bool operator==(const PrologueID& other) const { return m_funcId == other.m_funcId && m_nargs == other.m_nargs; } struct Eq { bool operator()(const PrologueID& pid1, const PrologueID& pid2) const { return pid1 == pid2; } }; struct Hasher { size_t operator()(PrologueID pid) const { return pid.funcId().toInt() + (size_t(pid.nargs()) << 32); } }; private: FuncId m_funcId{FuncId::Invalid}; uint32_t m_nargs{0xffffffff}; }; std::string show(PrologueID pid); /////////////////////////////////////////////////////////////////////////////// /* * Log meta-information about func. Records attributes, number of locals, * parameters, static locals, class ref slots, frame cells, high watermark, * and iterators. Does not record function name or class. */ void logFunc(const Func* func, StructuredLogEntry& ent); inline tracing::Props traceProps(const Func* f) { return tracing::Props{}.add("func_name", f->fullName()); } /* * Throw an exception that func cannot be converted to type. */ [[noreturn]] void invalidFuncConversion(const char* type); /////////////////////////////////////////////////////////////////////////////// // Bytecode /* * Report capacity of RepoAuthoritative mode bytecode arena. * * Returns 0 if !RuntimeOption::RepoAuthoritative. */ size_t hhbc_arena_capacity(); unsigned char* allocateBCRegion(const unsigned char* bc, size_t bclen); void freeBCRegion(const unsigned char* bc, size_t bclen); /////////////////////////////////////////////////////////////////////////////// } #define incl_HPHP_VM_FUNC_INL_H_ #include "hphp/runtime/vm/func-inl.h" #undef incl_HPHP_VM_FUNC_INL_H_