/*
 * 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