/* * Copyright 2017, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef _FRAMEWORKS_COMPILE_SLANG_REFLECTION_STATE_H_ // NOLINT #define _FRAMEWORKS_COMPILE_SLANG_REFLECTION_STATE_H_ #include <string> #include <utility> #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringSet.h" #include "clang/AST/APValue.h" #include "slang_assert.h" namespace slang { class RSContext; class RSExportForEach; class RSExportFunc; class RSExportRecordType; class RSExportReduce; class RSExportType; class RSExportVar; // --------------------- // class ReflectionState // --------------------- // // This class is used to collect data from 32-bit compilation for use // during the reflected code generation that occurs during 64-bit // compilation. The data is used for two purposes: // // 1) Accommodating rs object handle size differences when laying out // data (in particular, variables and records). // 2) Emitting errors when differences between 32-bit and 64-bit // compilation cannot be tolerated in the reflected code (for // example, size_t has different sizes, and so cannot be part // of any exportable). // // The model for using this class is as follows: // a) Instantiate a class instance. The instance is in the S_Initial // state. // b) Call openJava32() to move the instance to the S_OpenJava32 // ("Collecting") state. // c) Run the reflection pass on all files in 32-bit mode. Do not // actually generate reflected code; but call various methods on // the instance (begin*(), declare*(), end*(), etc.) to collect // information. // d) Call closeJava32() to move the instance to the S_ClosedJava32 // state. // e) Call openJava64() to move the instance to the S_OpenJava64 // ("Using") state. // f) Run the reflection pass on all files in 64-bit mode. Call the // same methods as in step (c), as well as some further methods to // query the information collected in step (c) in order to handle // layout differences. All error reporting for 32-bit versus // 64-bit differences is handled in the methods themselves. // g) Call closeJava64 to move the instance to the S_ClosedJava64 // state. // h) Destroy the instance. // // There are two exceptions to this model: // // 1) If not doing both 32-bit and 64-bit compilation, then skip steps // (b), (d), (e), and (g). (This is what happens if reflecting C++ // instead of Java, or reflecting Java but using the -m32 or -m64 // option.) In this case, the methods called in steps (c) and (f) // are no-ops: They do not collect information, they do not report // errors, and they return "no information available" when step (f) // asks for 32-bit layout related information. // 2) The class instance can be moved to the S_Bad state by class // ReflectionState::Tentative (see that class for more information) // when reflection itself aborts due to some error. The only legal // thing to do with an instance in this state is invoke its // destructor. // // All exported entities except for Records have slot numbers assigned // in reflection order. These slot numbers must match up between // 32-bit and 64-bit compilation. Therefore, we (generally) require // that entities be presented to ReflectionState (via begin*() or // declare*()) in the same order during the Collecting and Using // phases. This presentation order is generally the same as lexical // order in the user code, which makes it simple to emit meaningful // diagnostics when the order is inconsistent (for example, 32-bit and // 64-bit compilation disagree on the name of the kernel in a // particular slot). ReflectionState generally builds up an array of // each sort of entity, in the presentation order. There are two // exceptions: // // a) Records, as mentioned above. Exported Records have no slot // number, and therefore reflection order doesn't matter. In // practice, Records aren't necessarily reflected in consistent // order, because they are determined to be exported as a // consequence of determining that other entities are to be // exported; and variations between 32-bit and 64-bit compilation // can therefore result in inconsistent Record reflection order. // Therefore, ReflectionState builds up a map of Records. // b) ForEach kernels. ForEach kernels are not necessarily reflected // in lexical order (there is some sorting to segregate root // kernel, old-style kernels, and new-style kernels). In order to // give meaningful diagnostics for slot order mismatches, it's // enough to solve the simpler problem of giving meaningful // diagnostics for lexical order mismatches (although this is // stricter than necessary because of the sorting that occurs // before slot assignment). Therefore, ReflectionState builds up // an array of ForEaches in lexical order rather than in // presentation (slot) order, and accesses the array randomly // rather than sequentially. // class ReflectionState { private: // Set this to true to turn everything into a no-op, just as if none // of the open*() or close*() methods were ever called. static const bool kDisabled = false; public: ReflectionState() : mRSC(nullptr), mState(S_Initial), mForEachOpen(-1), mOutputClassOpen(false), mRecordsState(RS_Initial), mStringSet(nullptr) { } ~ReflectionState(); ReflectionState(const ReflectionState &) = delete; void operator=(const ReflectionState &) = delete; // For use in the debugger. void dump(); // A possibly-present value describing a property for a 32-bit target. // When .first is false, the value is absent, and .second is unspecified. typedef std::pair<bool, size_t> Val32; static Val32 NoVal32() { return Val32(false, ~size_t(0)); } void openJava32(size_t NumFiles); void closeJava32(); void openJava64(); void closeJava64(); bool isCollecting() const { return mState==S_OpenJava32; } // ---------------------------------------------------------------------- // Use these methods during the "Collecting" phase to track // information about a class being generated -- a script class or a // type class. We call such a class "Divergent" if it needs to have // at least one runtime check to distinguish between 32-bit and // 64-bit targets. // // Indicate that we are beginning to generate the class. // void beginOutputClass() { slangAssert(!mOutputClassOpen && !isClosed()); mOutputClassOpen = true; mOutputClassDivergent = false; } // // Record the fact that we've learned the class is divergent. // void setOutputClassDivergent() { slangAssert(mOutputClassOpen); mOutputClassDivergent = true; } // // Indicate that we've finished generating the class. Returns // true IFF we've learned the class is divergent. // bool endOutputClass() { slangAssert(mOutputClassOpen); mOutputClassOpen = false; return mOutputClassDivergent; } // ---------------------------------------------------------------------- // -------------------------------- // class ReflectionState::Tentative // -------------------------------- // // This class aids in error handling. The model is as follows: // a) Instantiate the class with a pointer to a ReflectionState // instance. // b) Before destroying the class instance, if there have been no // errors, call the ok() method on the instance. // c) When the instance is destroyed, if ok() has not been called on // it, this class will put the ReflectionState into the S_Bad // state. // // The idea is to "poison" the ReflectionState if we quit reflection // early because of some error -- we don't want to get in a // situation where we only have partial information from the // Collecting phase (because of quitting early) but try to use it // during the Using phase. // friend class Tentative; class Tentative { public: Tentative(ReflectionState *state) : mState(state) { } ~Tentative() { if (mState) mState->mState = ReflectionState::S_Bad; } void ok() { mState = nullptr; } Tentative(const Tentative &) = delete; void operator=(const Tentative &) = delete; private: ReflectionState *mState; }; // ---------------------------------------------------------------------- // Model for ForEach kernels (per File): // // a) beginForEaches(number_of_non_dummy_root_kernels_in_file) // b) mixture of declareForEachDummyRoot() calls and // beginForEach()..endForEach() calls // c) endForEaches() // // For a given ForEach kernel: // // b1) beginForEach() // b2) call any number of addForEachIn() (one per input) // b3) call any number of addForEachParam() (one per param) // b4) call addForEachSignatureMetadata() (if it's reflected) // b5) call endForEach() // // b2, b3, b4 can occur in any order void beginForEaches(size_t Count); void declareForEachDummyRoot(const RSExportForEach *) { /* we don't care */ }; void beginForEach(const RSExportForEach *EF); void addForEachIn(const RSExportForEach *EF, const RSExportType *Type); void addForEachParam(const RSExportForEach *EF, const RSExportType *Type); void addForEachSignatureMetadata(const RSExportForEach *EF, unsigned Metadata); void endForEach(); void endForEaches(); // ---------------------------------------------------------------------- // Model for Invokable functions (per File): // // a) beginInvokables(number_of_invokables_in_file) // b) declareInvokable() for each Invokable (order must be // consistent between 32-bit and 64-bit compile) // c) endInvokables() void beginInvokables(size_t Count) { mInvokablesOrderFatal = false; begin(&File::mInvokables, Count); } void declareInvokable(const RSExportFunc *EF); void endInvokables(); // ---------------------------------------------------------------------- // Model for reduction kernels (per File): // // a) beginReduces(number_of_reduction_kernels_in_file) // b) declareReduce() for each reduction kernel (order must be // consistent between 32-bit and 64-bit compile) // c) endReduces() void beginReduces(size_t Count) { mReducesOrderFatal = false; begin(&File::mReduces, Count); } void declareReduce(const RSExportReduce *ER, bool IsExportable); void endReduces(); // ---------------------------------------------------------------------- // Model for records (per File): // // a) beginRecords() // b) declareRecord() for each Record (order doesn't matter) // c) endRecords() // // And at any time during the Using phase, can call getRecord32() to // get information from the 32-bit compile (Collecting phase). void beginRecords(); // An "Ordinary" record is anything other than an // internally-synthesized helper record. We do not emit diagnostics // for mismatched helper records -- we assume that the constructs // from which those helper records were derived are also mismatched, // and that we'll get diagnostics for those constructs. void declareRecord(const RSExportRecordType *ERT, bool Ordinary = true); void endRecords(); class Record32; // During the Using phase, obtain information about a Record from // the Collecting phase. ERT should be from the Using phase, not // the Collecting phase. The value returned from this function is // valid for the lifetime of the ReflectionState instance. Record32 getRecord32(const RSExportRecordType *ERT); // ---------------------------------------------------------------------- // Model for Variables (per file): // // a) beginVariables(number_of_exported_variables_in_file) // b) declareVariable() for each Variable (order must be consistent // between 32-bit and 64-bit); in the Using phase, returns some // information about the Variable from 32-bit compilation // c) endVariables() void beginVariables(size_t Count) { mVariablesOrderFatal = false; begin(&File::mVariables, Count); } // If isUsing(), returns variable's 32-bit AllocSize; otherwise, returns NoVal32(). Val32 declareVariable(const RSExportVar *EV); void endVariables(); // ---------------------------------------------------------------------- // ReflectionState has a notion of "current file". After an // openJava*() or closeJava*() call, there is no current file. // Calling the nextFile() method when in the Collecting or Using // state "advances" to the next file in the list of files being // compiled, whose properties are specified by the arguments to // nextFile(). All of the various begin*(), declare*(), end*() // etc. calls implicitly refer to entities in the current file. // // RSC must remain valid until the next call to nextFile() or the // next S_* state change. void nextFile(const RSContext *RSC, const std::string &PackageName, const std::string &RSSourceFileName); // ---------------------------------------------------------------------- private: enum State { S_Initial, // No captured information S_OpenJava32, // Capturing information for 32-bit Java S_ClosedJava32, // Captured information for 32-bit Java S_OpenJava64, // Capturing information for 64-bit Java S_ClosedJava64, // Captured information for 64-bit Java S_Bad, // Abnormal termination }; // context associated with compilation of the current file const RSContext *mRSC; State mState; /*== ForEach ==================================================================*/ // The data in this section is transient during ForEach processing // for each File. int mForEachOpen; // if nonnegative, then ordinal of beginForEach() without matching endForEach() bool mForEachFatal; // fatal mismatch in comparing ForEach; do no further comparisons for it // Tracks mismatches discovered during the Use phase. // There are two possibilities: // - if (ordinal + 1) is greater than the number of ForEaches from the Collecting phase, // then this is an "extra" ForEach discovered during the Use phase // - otherwise the Collecting phase and the Use phase disagree on the name of the // ForEach at this ordinal position (the Collecting phase's kernel name is // available in mFiles.Current().mForEaches[ordinal].mName) llvm::SmallVector<const RSExportForEach *, 0> mForEachesBad; // During the Use phase, keep track of how many ForEach ordinals we // have seen that correspond to ordinals seen during the Collect // phase. This helps determine whether we have to issue errors at // endForEaches(). size_t mNumForEachesMatchedByOrdinal; /*== Invokable ================================================================*/ // 32-bit and 64-bit compiles need to see invokables in the same // order, because of slot number assignment. Once we see the first // name mismatch in the sequence of invokables for a given File, it // doesn't make sense to issue further diagnostics regarding // invokables for that File. bool mInvokablesOrderFatal; /*== OutputClass ==============================================================*/ // This data tracks information about a class being generated -- a // script class or a type class. We call such a class "Divergent" // if it needs to have at least one runtime check to distinguish // between 32-bit and 64-bit targets. bool mOutputClassOpen; // beginOutputClass() without matching endOutputClass() bool mOutputClassDivergent; // has class been marked divergent? /*== Record ===================================================================*/ // This field enforces necessary discipline on the use of // beginRecords()/declareRecord()/endRecord(). enum { RS_Initial, // no beginRecords() yet for current File RS_Open, // beginRecords() but no endRecords() for current File RS_Closed // endRecords() for current File } mRecordsState; // During the Use phase, keep track of how many records we have seen // that have same-named counterparts seen during the Collect phase. // This helps determine whether we have to issue errors at // endRecords(). size_t mNumRecordsMatchedByName; /*== Reduce ===================================================================*/ // 32-bit and 64-bit compiles need to see reduction kernels in the // same order, because of slot number assignment. Once we see the // first name mismatch in the sequence of reduction kernels for a // given File, it doesn't make sense to issue further diagnostics // regarding reduction kernels for that File. bool mReducesOrderFatal; /*== Variable =================================================================*/ // 32-bit and 64-bit compiles need to see variables in the same // order, because of slot number assignment. Once we see the first // name mismatch in the sequence of variables for a given File, it // doesn't make sense to issue further diagnostics regarding // variables for that File. bool mVariablesOrderFatal; /*=============================================================================*/ bool isActive() const { return isCollecting() || isUsing(); } bool isClosed() const { return mState==S_ClosedJava32 || mState==S_ClosedJava64; } bool isUsing() const { return mState==S_OpenJava64; } // For anything with a type (such as a Variable or a Record field), // the type is represented via its name. To save space, we don't // create multiple instances of the same name -- we have a canonical // instance in mStringSet, and use a StringRef to refer to it. The // method canon() returns a StringRef to the canonical // instance, creating the instance if necessary. llvm::StringRef canon(const std::string &String); llvm::StringSet<> *mStringSet; // Synthesize a name for the specified type. There should be a // one-to-one correspondence between the name and a C type (after // typedefs and integer expressions have been "flattened", and // considering a struct type to be identified solely by its name). static std::string getUniqueTypeName(const RSExportType *T); // ------------------------------ // template class ArrayWithCursor // ------------------------------ // // This class represents a fixed-length dynamically-allocated array // (length is specified by a method call after instantiation) along // with a cursor that traverses the array. The behavior of the // class is very specific to the needs of ReflectionState. // // The model for using this class is as follows: // a) Instantiate a class instance. The instance is in the // S_Initial state. // b) Call BeginCollecting() with an array capacity. This allocates // the array members and moves the instance to the S_Collecting // state. The array size (contrast with capacity) is zero, and // the cursor has not been placed. // c) Call CollectNext() a number of times equal to the capacity. // Each time CollectNext() is called, it extends the array size // by 1, and advances the cursor to the "new" member. The idea // is to set the value of the "new" member at this time. // d) Call BeginUsing(). This moves the instance to the S_Using // state and "unplaces" the cursor. // e) Call UseNext() a number of times equal to the capacity. Each // time UseNext() is called, it advances the cursor to the next // member (first member, the first time it is called). // The cursor is stepping through the members that were "created" // by CollectNext() during the S_Collecting state; the idea is to // look at their values. // f) Destroy the instance. // template <typename Member> class ArrayWithCursor { public: ArrayWithCursor() : mState(S_Initial), mMembers(nullptr), mCapacity(0), mSize(0), mCursor(~size_t(0)) { } ~ArrayWithCursor() { delete [] mMembers; } ArrayWithCursor(const ArrayWithCursor &) = delete; void operator=(const ArrayWithCursor &) = delete; void BeginCollecting(size_t Size) { slangAssert(mState == S_Initial); mState = S_Collecting; mMembers = new Member[Size]; mCapacity = Size; } // Increments the array size, advances the cursor to the new // member, and returns a reference to that member. Member &CollectNext() { slangAssert((mState == S_Collecting) && (mCursor + 1 == mSize) && (mSize < mCapacity)); ++mSize; return mMembers[++mCursor]; } void BeginUsing() { slangAssert((mState == S_Collecting) && (mCursor + 1 == mSize) && (mSize == mCapacity)); mState = S_Using; mCursor = ~size_t(0); } // Advances the cursor to the next member, and returns a reference // to that member. Member &UseNext() { slangAssert((mState == S_Using) && (mCursor + 1 < mSize)); return mMembers[++mCursor]; } // Is the cursor on the last array member? bool isFinished() const { return mCursor + 1 == mSize; } size_t Size() const { return mSize; } // Return a reference to the member under the cursor. Member &Current() { slangAssert(mCursor < mSize); return mMembers[mCursor]; } const Member &Current() const { slangAssert(mCursor < mSize); return mMembers[mCursor]; } // Return the cursor position (zero-based). Cursor must have been // placed (i.e., if we're Collecting, we must have called // CollectNext() at least once; and if we're Using, we must have // called UseNext() at least once). size_t CurrentIdx() const { slangAssert(mCursor < mSize); return mCursor; } // Return a reference to the specified member. Must be within the // array size (not merely within its capacity). Member &operator[](size_t idx) { slangAssert(idx < mSize); return mMembers[idx]; } const Member &operator[](size_t idx) const { slangAssert(idx < mSize); return mMembers[idx]; } private: enum State { S_Initial, S_Collecting, S_Using }; State mState; Member *mMembers; size_t mCapacity; size_t mSize; size_t mCursor; }; struct File { File() : mForEaches(nullptr) { } ~File() { delete [] mForEaches; } File(const File &) = delete; void operator=(const File &) = delete; std::string mPackageName; std::string mRSSourceFileName; struct ForEach { ForEach() : mState(S_Initial) { } ForEach(const ForEach &) = delete; void operator=(const ForEach &) = delete; enum { S_Initial, // ForEach has been instantiated S_Collected, // beginForEach() has been called while Collecting S_UseMatched // beginForEach() has been called while Using, // and found this ForEach } mState; std::string mName; // Types. mIns[] and mOut can be null in case we have an // old-style kernel with a void* input or output. ArrayWithCursor<llvm::StringRef> mIns; ArrayWithCursor<llvm::StringRef> mParams; llvm::StringRef mOut; bool mHasOut; // to distinguish between no output and void* output. unsigned mSignatureMetadata; bool mIsKernel; // new-style (by-value) rather than old-style }; ForEach *mForEaches; // indexed by ordinal (lexical order) size_t mForEachCount; struct Invokable { Invokable() : mParams(nullptr) { } ~Invokable() { delete [] mParams; } Invokable(const Invokable &) = delete; void operator=(const Invokable &) = delete; std::string mName; llvm::StringRef *mParams; // Types size_t mParamCount; }; ArrayWithCursor<Invokable> mInvokables; // There are two things we need to do with a Record: // - Support structure sizes and layouts that differ between // 32-bit and 64-bit compilation. // - Do consistency checking between 32-bit and 64-bit compilation. // // TODO: Move this out of File to avoid duplication? That is, // instead of tracking Records on a per-File basis, instead // track them globally? // // (Because of ODR, we shouldn't have inconsistencies // between Files.) // struct Record { Record() : mFields(nullptr) { } ~Record() { delete [] mFields; } Record(const Record &) = delete; void operator=(const Record &) = delete; struct Field { std::string mName; llvm::StringRef mType; size_t mPrePadding; // this.OffsetInParent - (prev.OffsetInParent + prev.AllocSize) size_t mPostPadding; // this.AllocSize - this.StoreSize size_t mOffset; // this.OffsetInParent size_t mStoreSize; // this.StoreSize }; Field *mFields; size_t mFieldCount; size_t mPostPadding; // padding after the end of the padded // last field size_t mAllocSize; bool mOrdinary; // anything other than an // internally-synthesized helper // record. We do not emit diagnostics // for inconsistent helper records. bool mMatchedByName; // has declareRecord() been called on // this record during the Using phase? }; llvm::StringMap<Record> mRecords; struct Reduce { Reduce() : mAccumIns(nullptr) { } ~Reduce() { delete [] mAccumIns; } Reduce(const Reduce &) = delete; void operator=(const Reduce &) = delete; std::string mName; // only apply to exportable llvm::StringRef *mAccumIns; // Types size_t mAccumInCount; llvm::StringRef mResult; // Type bool mIsExportable; }; ArrayWithCursor<Reduce> mReduces; struct Variable { Variable() : mInitializers(nullptr) { } ~Variable() { delete [] mInitializers; } Variable(const Variable &) = delete; void operator=(const Variable &) = delete; std::string mName; llvm::StringRef mType; clang::APValue *mInitializers; size_t mInitializerCount; size_t mAllocSize; bool mIsConst; }; ArrayWithCursor<Variable> mVariables; }; ArrayWithCursor<File> mFiles; // Utility template -- common pattern used by many begin*() methods. template <typename Member> void begin(ArrayWithCursor<Member> File::*Array, size_t Count) { slangAssert(!isClosed()); if (!isActive()) return; auto &file = mFiles.Current(); if (isCollecting()) (file.*Array).BeginCollecting(Count); if (isUsing()) (file.*Array).BeginUsing(); } public: // This class represents 32-bit layout information built up during // the Collecting phase, for use during the Using phase. It // provides an interface between class ReflectionState and client // code that actually performs reflection. class Record32 { friend class ReflectionState; public: Record32() : mRecord(nullptr) { } Val32 getRecordPostPadding() const { if (!mRecord) return NoVal32(); return Val32(true, mRecord->mPostPadding); } Val32 getRecordAllocSize() const { if (!mRecord) return NoVal32(); return Val32(true, mRecord->mAllocSize); } std::pair<Val32, Val32> getFieldPreAndPostPadding(unsigned idx) const { if (!mRecord || idx >= mRecord->mFieldCount) return std::make_pair(NoVal32(), NoVal32()); const File::Record::Field &field = mRecord->mFields[idx]; return std::make_pair(Val32(true, field.mPrePadding), Val32(true, field.mPostPadding)); } std::pair<Val32, Val32> getFieldOffsetAndStoreSize(unsigned idx) const { if (!mRecord || idx >= mRecord->mFieldCount) return std::make_pair(NoVal32(), NoVal32()); const File::Record::Field &field = mRecord->mFields[idx]; return std::make_pair(Val32(true, field.mOffset), Val32(true, field.mStoreSize)); } private: Record32(const File::Record *Record) : mRecord(Record) { } const File::Record *mRecord; }; }; } #endif // _FRAMEWORKS_COMPILE_SLANG_REFLECTION_STATE_H_ NOLINT