Arquivos
hhvm/hphp/runtime/vm/func.h
T
Andrei Alexandrescu a2e76eb7b2 Eliminate static noise from .h files
The codebase had several namespace-level static data definitions and function definitions. Using namespace-level "static" in a .h file is near-always a bad idea, as follows:

  - for simple types, static is implied. Example: "const int x = 42;" is the same as "static const int x = 42;"
  - for aggregate types, static linkage implies that a copy of the aggregate will appear in every compilation unit that includes the header.
  - for functions, static means the function will have a separate body generated in each compilation unit including the header.
  - in several places functions were defined 'static inline', which is just as bad. 'inline' is just a hint which means the function may end up having a body (and actually more due to 'static').

True, gnu's linker has means to remove duplicate definition, but that's not always guaranteed or possible (think e.g. static functions that define static data inside, ouch). So static is useless at best and pernicious at worst. We should never, ever use static at namespace level in headers. I will create a bootcamp task for a lint rule.

I expected the performance to be neutral after the change, but in fact there's a significant drop in instruction count and therefore a measurable reduction in CPU time: https://our.intern.facebook.com/intern/perflab/details.php?eq_id=431903
2013-06-06 11:39:13 -07:00

739 linhas
24 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_VM_FUNC_H_
#define incl_HPHP_VM_FUNC_H_
#include "hphp/runtime/vm/bytecode.h"
#include "hphp/runtime/vm/type_constraint.h"
#include "hphp/runtime/vm/repo_helpers.h"
#include "hphp/runtime/vm/indexed_string_map.h"
#include "hphp/runtime/base/intercept.h"
namespace HPHP {
const int kNumFixedPrologues = 6;
typedef TypedValue*(*BuiltinFunction)(ActRec* ar);
/*
* Unique identifier for a Func*.
*/
typedef uint32_t FuncId;
constexpr FuncId InvalidFuncId = FuncId(-1LL);
/*
* Metadata about a php function or object method.
*/
struct Func {
friend class FuncEmitter;
typedef hphp_hash_map<const StringData*, TypedValue, string_data_hash,
string_data_isame> UserAttributeMap;
struct ParamInfo { // Parameter default value info.
// construct a dummy ParamInfo
ParamInfo()
: m_builtinType(KindOfInvalid), m_funcletOff(InvalidAbsoluteOffset),
m_phpCode(nullptr), m_userType(nullptr) {
tvWriteUninit(&m_defVal);
}
template<class SerDe>
void serde(SerDe& sd) {
const StringData* tcName = m_typeConstraint.typeName();
TypeConstraint::Flags tcFlags = m_typeConstraint.flags();
sd(m_builtinType)
(m_funcletOff)
(m_defVal)
(m_phpCode)
(tcName)
(tcFlags)
(m_userAttributes)
(m_userType)
;
if (SerDe::deserializing) {
setTypeConstraint(TypeConstraint(tcName, tcFlags));
}
}
void setBuiltinType(DataType type) { m_builtinType = type; }
DataType builtinType() const { return m_builtinType; }
void setFuncletOff(Offset funcletOff) { m_funcletOff = funcletOff; }
Offset funcletOff() const { return m_funcletOff; }
bool hasDefaultValue() const {
return m_funcletOff != InvalidAbsoluteOffset;
}
bool hasNonNullDefaultValue() const {
return hasDefaultValue() && m_defVal.m_type != KindOfNull;
}
bool hasScalarDefaultValue() const {
return hasDefaultValue() && m_defVal.m_type != KindOfUninit;
}
void setDefaultValue(const TypedValue& defVal) { m_defVal = defVal; }
const TypedValue& defaultValue() const { return m_defVal; }
void setPhpCode(const StringData* phpCode) { m_phpCode = phpCode; }
const StringData* phpCode() const { return m_phpCode; }
void setTypeConstraint(const TypeConstraint& tc) { m_typeConstraint = tc; }
const TypeConstraint& typeConstraint() const { return m_typeConstraint; }
void addUserAttribute(const StringData* name, TypedValue tv) {
m_userAttributes[name] = tv;
}
void setUserAttributes(const Func::UserAttributeMap& uaMap) {
m_userAttributes = uaMap;
}
const Func::UserAttributeMap& userAttributes() const {
return m_userAttributes;
}
void setUserType(const StringData* userType) {
m_userType = userType;
}
const StringData* userType() const {
return m_userType;
}
private:
DataType m_builtinType; // typehint for builtins
Offset m_funcletOff; // If no default: InvalidAbsoluteOffset.
TypedValue m_defVal; // Set to uninit null if there is no default value
// or if there is a non-scalar default value.
const StringData* m_phpCode; // eval'able PHP code.
TypeConstraint m_typeConstraint;
Func::UserAttributeMap m_userAttributes;
// the type the user typed in source code, contains type parameters and all
const StringData* m_userType;
};
struct SVInfo { // Static variable info.
const StringData* name;
const StringData* phpCode; // eval'able PHP or NULL if no default.
template<class SerDe> void serde(SerDe& sd) { sd(name)(phpCode); }
};
typedef FixedVector<ParamInfo> ParamInfoVec;
typedef FixedVector<SVInfo> SVInfoVec;
typedef FixedVector<EHEnt> EHEntVec;
typedef FixedVector<FPIEnt> FPIEntVec;
Func(Unit& unit, Id id, int line1, int line2, Offset base,
Offset past, const StringData* name, Attr attrs, bool top,
const StringData* docComment, int numParams, bool isGenerator);
Func(Unit& unit, PreClass* preClass, int line1,
int line2, Offset base, Offset past,
const StringData* name, Attr attrs, bool top,
const StringData* docComment, int numParams, bool isGenerator);
~Func();
static void destroy(Func* func);
Func* clone() const;
const Func* cloneAndSetClass(Class* cls) const;
void validate() const {
#ifdef DEBUG
assert(this && m_magic == kMagic);
#endif
}
FuncId getFuncId() const {
assert(m_funcId != InvalidFuncId);
return m_funcId;
}
void setFuncId(FuncId id);
void setNewFuncId();
void rename(const StringData* name);
int numSlotsInFrame() const {
return shared()->m_numLocals + shared()->m_numIterators * kNumIterCells;
}
Id lookupVarId(const StringData* name) const;
/*
* Return true if Offset o is inside the protected region of a fault
* funclet for iterId, otherwise false. itRef will be set to true if
* the iterator was initialized with MIterInit*, false if the iterator
* was initialized with IterInit*.
*/
bool checkIterScope(Offset o, Id iterId, bool& itRef) const;
/*
* Find the first EHEnt that covers a given offset, or return null.
*/
const EHEnt* findEH(Offset o) const;
/*
* Locate FPI regions by offset.
*/
const FPIEnt* findFPI(Offset o) const;
const FPIEnt* findPrecedingFPI(Offset o) const;
void parametersCompat(const PreClass* preClass, const Func* imeth) const;
// This can be thought of as "if I look up this Func's name while in fromUnit,
// will I always get this Func back?" This is important for the translator: if
// this condition holds, it allows for some translation-time optimizations by
// making assumptions about where function calls will go.
bool isNameBindingImmutable(const Unit* fromUnit) const;
void setMaxStackCells(int cells) { m_maxStackCells = cells; }
int maxStackCells() const {
// All functions have to return something, which pushes at least 1 cell
assert(m_maxStackCells > 0);
return m_maxStackCells;
}
bool byRef(int32_t arg) const;
bool mustBeRef(int32_t arg) const;
void prettyPrint(std::ostream& out) const;
bool isPseudoMain() const { return m_name->empty(); }
bool isBuiltin() const { return m_attrs & AttrBuiltin; }
bool skipFrame() const { return m_attrs & AttrSkipFrame; }
bool isMethod() const {
return !isPseudoMain() && (bool)cls();
}
bool isTraitMethod() const {
PreClass* pcls = preClass();
return pcls && (pcls->attrs() & AttrTrait);
}
bool isNonClosureMethod() const {
return isMethod() && !isClosureBody();
}
bool isPublic() const { return bool(m_attrs & AttrPublic); }
bool isStatic() const { return bool(m_attrs & AttrStatic); }
bool isAbstract() const { return bool(m_attrs & AttrAbstract); }
bool isUnique() const { return bool(m_attrs & AttrUnique); }
bool isDestructor() const {
return !strcmp(m_name->data(), "__destruct");
}
bool isPersistent() const { return m_attrs & AttrPersistent; }
static bool isMagicCallMethodName(const StringData* name) {
return name->isame(s___call) || name->isame(s___callStatic);
}
bool isMagicCallMethod() const {
return m_name->isame(s___call);
}
bool isMagicCallStaticMethod() const {
return m_name->isame(s___callStatic);
}
bool isMagic() const {
return isMagicCallMethod() || isMagicCallStaticMethod();
}
static bool isSpecial(const StringData* methName) {
return strncmp("86", methName->data(), 2) == 0;
}
bool isNoInjection() const { return bool(m_attrs & AttrNoInjection); }
HphpArray* getStaticLocals() const;
void getFuncInfo(ClassInfo::MethodInfo* mi) const;
Unit* unit() const { return m_unit; }
PreClass* preClass() const { return shared()->m_preClass; }
Class* cls() const { return m_cls; }
void setCls(Class* cls) {
m_cls = cls;
setFullName();
}
void setClsAndName(Class* cls, const StringData* name) {
m_cls = cls;
m_name = name;
setFullName();
}
Class* baseCls() const { return m_baseCls; }
void setBaseCls(Class* baseCls) { m_baseCls = baseCls; }
bool hasPrivateAncestor() const { return m_hasPrivateAncestor; }
void setHasPrivateAncestor(bool b) { m_hasPrivateAncestor = b; }
Id id() const {
assert(preClass() == nullptr);
return shared()->m_id;
}
Offset base() const { return shared()->m_base; }
const inline Opcode* getEntry() const {
return m_unit->entry() + shared()->m_base;
}
Offset past() const { return shared()->m_past; }
int line1() const { return shared()->m_line1; }
int line2() const { return shared()->m_line2; }
DataType returnType() const { return shared()->m_returnType; }
const SVInfoVec& staticVars() const { return shared()->m_staticVars; }
const StringData* name() const {
assert(m_name != nullptr);
return m_name;
}
CStrRef nameRef() const {
assert(m_name != nullptr);
return *(String*)(&m_name);
}
const StringData* fullName() const {
if (m_fullName == nullptr) return m_name;
return m_fullName;
}
CStrRef fullNameRef() const {
assert(m_fullName != nullptr);
return *(String*)(&m_fullName);
}
// Assembly linkage.
static size_t fullNameOffset() {
return offsetof(Func, m_fullName);
}
static size_t sharedOffset() {
return offsetof(Func, m_shared);
}
static size_t sharedBaseOffset() {
return offsetof(SharedData, m_base);
}
char &maybeIntercepted() const { return m_maybeIntercepted; }
int numParams() const { return m_numParams; }
const ParamInfoVec& params() const { return shared()->m_params; }
int numLocals() const { return shared()->m_numLocals; }
const StringData* const* localNames() const {
return shared()->m_localNames.accessList();
}
Id numNamedLocals() const { return shared()->m_localNames.size(); }
// Returns the name of a local variable, or null if this varid is an
// unnamed local.
const StringData* localVarName(Id id) const {
assert(id >= 0);
return id < numNamedLocals() ? shared()->m_localNames[id] : 0;
}
const StringData* returnTypeConstraint() const {
return shared()->m_retTypeConstraint;
}
const StringData* originalFilename() const {
return shared()->m_originalFilename;
}
int numIterators() const { return shared()->m_numIterators; }
const EHEntVec& ehtab() const { return shared()->m_ehtab; }
const FPIEntVec& fpitab() const { return shared()->m_fpitab; }
Attr attrs() const { return m_attrs; }
void setAttrs(Attr attrs) { m_attrs = attrs; }
bool top() const { return shared()->m_top; }
const StringData* docComment() const { return shared()->m_docComment; }
bool isClosureBody() const { return shared()->m_isClosureBody; }
bool isClonedClosure() const;
bool isGenerator() const { return shared()->m_isGenerator; }
bool isGeneratorFromClosure() const {
return shared()->m_isGeneratorFromClosure;
}
/**
* If this function is a generator then it is implemented as a simple
* function that just returns another function. hasGeneratorAsBody() will be
* true for the outer functions and isGenerator() is true for the
* inner function.
*
* This isn't a pointer to the function itself because it was too hard to
* hook the parts up. If you know more and need it, there probably isn't a
* technical reason not to.
*/
bool hasGeneratorAsBody() const { return shared()->m_hasGeneratorAsBody; }
const Func* getGeneratorBody(const StringData* name) const;
bool hasStaticLocals() const { return !shared()->m_staticVars.empty(); }
int numStaticLocals() const { return shared()->m_staticVars.size(); }
const ClassInfo::MethodInfo* info() const { return shared()->m_info; }
bool isAllowOverride() const { return m_attrs & AttrAllowOverride; }
const BuiltinFunction& nativeFuncPtr() const {
return shared()->m_nativeFuncPtr;
}
const BuiltinFunction& builtinFuncPtr() const {
return shared()->m_builtinFuncPtr;
}
const UserAttributeMap& userAttributes() const {
return shared()->m_userAttributes;
}
/**
* Closure's __invoke()s have an extra pointer used to keep cloned versions
* of themselves with different contexts.
*
* const here is the equivalent of "mutable" since this is just a cache
*/
Func*& nextClonedClosure() const {
assert(isClosureBody() || isGeneratorFromClosure());
return ((Func**)this)[-1];
}
static void* allocFuncMem(
const StringData* name, int numParams, bool needsNextClonedClosure);
void setPrologue(int index, unsigned char* tca) {
m_prologueTable[index] = tca;
}
void setFuncBody(unsigned char* fb) {
m_funcBody = fb;
}
unsigned char* getFuncBody() const {
return m_funcBody;
}
unsigned char* getPrologue(int index) const {
return m_prologueTable[index];
}
int numPrologues() const {
return getMaxNumPrologues(m_numParams);
}
static int getMaxNumPrologues(int numParams) {
// maximum number of prologues is numParams+2. The extra 2 are for
// the case where the number of actual params equals numParams and
// the case where the number of actual params is greater than
// numParams.
return numParams + 2;
}
void resetPrologues() {
// Useful when killing code; forget what we've learned about the contents
// of the translation cache.
initPrologues(m_numParams, isGenerator());
}
const NamedEntity* getNamedEntity() const {
assert(!m_shared->m_preClass);
return m_namedEntity;
}
Slot methodSlot() const {
assert(m_cls);
return m_methodSlot;
}
void setMethodSlot(Slot s) {
assert(m_cls);
m_methodSlot = s;
}
Func** getCachedAddr();
Func* getCached() { return *getCachedAddr(); }
void setCached();
unsigned getCachedOffset() const { return m_cachedOffset; }
public: // Offset accessors for the translator.
#define X(f) static size_t f##Off() { return offsetof(Func, m_##f); }
X(attrs);
X(unit);
X(cls);
X(numParams);
X(refBitVec);
X(fullName);
X(prologueTable);
X(maybeIntercepted);
X(maxStackCells);
X(funcBody);
#undef X
private:
typedef IndexedStringMap<const StringData*,true,Id> NamedLocalsMap;
struct SharedData : public AtomicCountable {
PreClass* m_preClass;
Id m_id;
Offset m_base;
Id m_numLocals;
Id m_numIterators;
Offset m_past;
int m_line1;
int m_line2;
DataType m_returnType;
const ClassInfo::MethodInfo* m_info; // For builtins.
uint64_t* m_refBitVec;
BuiltinFunction m_builtinFuncPtr;
BuiltinFunction m_nativeFuncPtr;
ParamInfoVec m_params; // m_params[i] corresponds to parameter i.
NamedLocalsMap m_localNames; // includes parameter names
SVInfoVec m_staticVars;
EHEntVec m_ehtab;
FPIEntVec m_fpitab;
const StringData* m_docComment;
bool m_top : 1; // Defined at top level.
bool m_isClosureBody : 1;
bool m_isGenerator : 1;
bool m_isGeneratorFromClosure : 1;
bool m_hasGeneratorAsBody : 1;
UserAttributeMap m_userAttributes;
const StringData* m_retTypeConstraint;
// per-func filepath for traits flattened during repo construction
const StringData* m_originalFilename;
SharedData(PreClass* preClass, Id id, Offset base,
Offset past, int line1, int line2, bool top,
const StringData* docComment);
~SharedData();
void atomicRelease();
};
typedef AtomicSmartPtr<SharedData> SharedDataPtr;
static const int kBitsPerQword = 64;
static const StringData* s___call;
static const StringData* s___callStatic;
static const int kMagic = 0xba5eba11;
private:
void setFullName();
void init(int numParams, bool isGenerator);
void initPrologues(int numParams, bool isGenerator);
void appendParam(bool ref, const ParamInfo& info,
std::vector<ParamInfo>& pBuilder);
void allocVarId(const StringData* name);
const SharedData* shared() const { return m_shared.get(); }
SharedData* shared() { return m_shared.get(); }
const Func* findCachedClone(Class* cls) const;
private:
Unit* m_unit;
Class* m_cls; // The Class that provided this method implementation
Class* m_baseCls; // 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
const StringData* m_name;
const StringData* m_fullName;
SharedDataPtr m_shared;
union {
const NamedEntity* m_namedEntity;
Slot m_methodSlot;
};
uint64_t* m_refBitVec;
public: // used by Unit
unsigned m_cachedOffset;
private:
#ifdef DEBUG
int m_magic; // For asserts only.
#endif
int m_maxStackCells;
int m_numParams;
Attr m_attrs;
FuncId m_funcId;
bool m_hasPrivateAncestor : 1; // This flag indicates if any of this
// Class's ancestors provide a
// "private" implementation for this
// method
// TODO(#1114385) intercept should work via invalidation.
mutable char m_maybeIntercepted; // -1, 0, or 1. Accessed atomically.
unsigned char* volatile m_funcBody; // Accessed from assembly.
// This must be the last field declared in this structure
// and the Func class should not be inherited from.
unsigned char* volatile m_prologueTable[kNumFixedPrologues];
};
class FuncEmitter {
public:
typedef std::vector<Func::SVInfo> SVInfoVec;
typedef std::vector<EHEnt> EHEntVec;
typedef std::vector<FPIEnt> FPIEntVec;
struct ParamInfo: public Func::ParamInfo {
ParamInfo() : m_ref(false) {}
void setRef(bool ref) { m_ref = ref; }
bool ref() const { return m_ref; }
template<class SerDe> void serde(SerDe& sd) {
Func::ParamInfo* parent = this;
parent->serde(sd);
sd(m_ref);
}
private:
bool m_ref; // True if parameter is passed by reference.
};
typedef std::vector<ParamInfo> ParamInfoVec;
FuncEmitter(UnitEmitter& ue, int sn, Id id, const StringData* n);
FuncEmitter(UnitEmitter& ue, int sn, const StringData* n,
PreClassEmitter* pce);
~FuncEmitter();
void init(int line1, int line2, Offset base, Attr attrs, bool top,
const StringData* docComment);
void finish(Offset past, bool load);
template<class SerDe> void serdeMetaData(SerDe&);
EHEnt& addEHEnt();
FPIEnt& addFPIEnt();
Id newLocal();
void appendParam(const StringData* name, const ParamInfo& info);
void setParamFuncletOff(Id id, Offset off) {
m_params[id].setFuncletOff(off);
}
void allocVarId(const StringData* name);
Id lookupVarId(const StringData* name) const;
bool hasVar(const StringData* name) const;
Id numParams() const { return m_params.size(); }
void setReturnTypeConstraint(const StringData* retTypeConstraint) {
m_retTypeConstraint = retTypeConstraint;
}
Id allocIterator();
void freeIterator(Id id);
void setNumIterators(Id numIterators);
Id numIterators() const { return m_numIterators; }
Id allocUnnamedLocal();
void freeUnnamedLocal(Id id);
Id numLocals() const { return m_numLocals; }
void setNumLocals(Id numLocals);
void setMaxStackCells(int cells) { m_maxStackCells = cells; }
void addStaticVar(Func::SVInfo svInfo);
UnitEmitter& ue() const { return m_ue; }
PreClassEmitter* pce() const { return m_pce; }
void setIds(int sn, Id id) {
m_sn = sn;
m_id = id;
}
int sn() const { return m_sn; }
Id id() const {
assert(m_pce == nullptr);
return m_id;
}
Offset base() const { return m_base; }
Offset past() const { return m_past; }
const StringData* name() const { return m_name; }
const ParamInfoVec& params() const { return m_params; }
const EHEntVec& ehtab() const { return m_ehtab; }
EHEntVec& ehtab() { return m_ehtab; }
const FPIEntVec& fpitab() const { return m_fpitab; }
void setAttrs(Attr attrs) { m_attrs = attrs; }
Attr attrs() const { return m_attrs; }
void setTop(bool top) { m_top = top; }
bool top() { return m_top; }
bool isPseudoMain() const { return m_name->empty(); }
void setIsClosureBody(bool isClosureBody) { m_isClosureBody = isClosureBody; }
bool isClosureBody() const { return m_isClosureBody; }
void setIsGenerator(bool isGenerator) { m_isGenerator = isGenerator; }
bool isGenerator() const { return m_isGenerator; }
void setIsGeneratorFromClosure(bool b) { m_isGeneratorFromClosure = b; }
bool isGeneratorFromClosure() const { return m_isGeneratorFromClosure; }
void setHasGeneratorAsBody(bool b) { m_hasGeneratorAsBody = b; }
bool hasGeneratorAsBody() const { return m_hasGeneratorAsBody; }
void setContainsCalls() { m_containsCalls = true; }
void addUserAttribute(const StringData* name, TypedValue tv);
void commit(RepoTxn& txn) const;
Func* create(Unit& unit, PreClass* preClass = nullptr) const;
void setBuiltinFunc(const ClassInfo::MethodInfo* info,
BuiltinFunction bif, BuiltinFunction nif, Offset base);
void setOriginalFilename(const StringData* name) {
m_originalFilename = name;
}
private:
void sortEHTab();
void sortFPITab(bool load);
UnitEmitter& m_ue;
PreClassEmitter* m_pce;
int m_sn;
Id m_id;
Offset m_base;
Offset m_past;
int m_line1;
int m_line2;
const StringData* m_name;
ParamInfoVec m_params;
Func::NamedLocalsMap::Builder m_localNames;
Id m_numLocals;
int m_numUnnamedLocals;
int m_activeUnnamedLocals;
Id m_numIterators;
Id m_nextFreeIterator;
int m_maxStackCells;
SVInfoVec m_staticVars;
const StringData* m_retTypeConstraint;
EHEntVec m_ehtab;
FPIEntVec m_fpitab;
Attr m_attrs;
DataType m_returnType;
bool m_top;
const StringData* m_docComment;
bool m_isClosureBody;
bool m_isGenerator;
bool m_isGeneratorFromClosure;
bool m_hasGeneratorAsBody;
bool m_containsCalls;
Func::UserAttributeMap m_userAttributes;
const ClassInfo::MethodInfo* m_info;
BuiltinFunction m_builtinFuncPtr;
BuiltinFunction m_nativeFuncPtr;
const StringData* m_originalFilename;
};
class FuncRepoProxy : public RepoProxy {
friend class Func;
friend class FuncEmitter;
public:
explicit FuncRepoProxy(Repo& repo);
~FuncRepoProxy();
void createSchema(int repoId, RepoTxn& txn);
#define FRP_IOP(o) FRP_OP(Insert##o, insert##o)
#define FRP_GOP(o) FRP_OP(Get##o, get##o)
#define FRP_OPS \
FRP_IOP(Func) \
FRP_GOP(Funcs)
class InsertFuncStmt : public RepoProxy::Stmt {
public:
InsertFuncStmt(Repo& repo, int repoId) : Stmt(repo, repoId) {}
void insert(const FuncEmitter& fe,
RepoTxn& txn, int64_t unitSn, int funcSn, Id preClassId,
const StringData* name, bool top);
};
class GetFuncsStmt : public RepoProxy::Stmt {
public:
GetFuncsStmt(Repo& repo, int repoId) : Stmt(repo, repoId) {}
void get(UnitEmitter& ue);
};
#define FRP_OP(c, o) \
public: \
c##Stmt& o(int repoId) { return *m_##o[repoId]; } \
private: \
c##Stmt m_##o##Local; \
c##Stmt m_##o##Central; \
c##Stmt* m_##o[RepoIdCount];
FRP_OPS
#undef FRP_OP
};
}
#endif