//===--- InterpFrame.h - Call Frame implementation for the VM ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Defines the class storing information about stack frames in the interpreter.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_AST_INTERP_INTERPFRAME_H
#define LLVM_CLANG_AST_INTERP_INTERPFRAME_H

#include "Frame.h"
#include "InterpBlock.h"
#include "Pointer.h"

namespace clang {
namespace interp {
class Function;
class InterpState;
class Pointer;

/// Frame storing local variables.
class InterpFrame final : public Frame {
public:
  /// The frame of the previous function.
  InterpFrame *Caller;

  /// Bottom Frame.
  InterpFrame(InterpState &S);

  /// Creates a new frame for a method call.
  InterpFrame(InterpState &S, const Function *Func, InterpFrame *Caller,
              CodePtr RetPC, unsigned ArgSize);

  /// Creates a new frame with the values that make sense.
  /// I.e., the caller is the current frame of S,
  /// the This() pointer is the current Pointer on the top of S's stack,
  /// and the RVO pointer is before that.
  InterpFrame(InterpState &S, const Function *Func, CodePtr RetPC,
              unsigned VarArgSize = 0);

  /// Destroys the frame, killing all live pointers to stack slots.
  ~InterpFrame();

  /// Returns the number of bytes needed to allocate an InterpFrame for the
  /// given function.
  static size_t allocSize(const Function *F) {
    return sizeof(InterpFrame) + F->getFrameSize() +
           (F->getArgSize() + (sizeof(Block) * F->getNumWrittenParams()));
  }

  std::string getName() const {
    if (!Func)
      return "Bottom frame";
    return Func->getName();
  }

  static void free(InterpFrame *F) {
    if (!F->isBottomFrame()) {
      F->~InterpFrame();
      delete[] reinterpret_cast<char *>(F);
    } else {
      F->~InterpFrame();
    }
  }

  /// Invokes the destructors for a scope.
  void destroy(unsigned Idx);
  void initScope(unsigned Idx);
  void destroyScopes();
  void enableLocal(unsigned Idx);
  bool isLocalEnabled(unsigned Idx) const {
    return localInlineDesc(Idx)->IsActive;
  }

  /// Describes the frame with arguments for diagnostic purposes.
  void describe(llvm::raw_ostream &OS) const override;

  /// Returns the parent frame object.
  Frame *getCaller() const override { return Caller; }

  /// Returns the location of the call to the frame.
  SourceRange getCallRange() const override;

  /// Returns the caller.
  const FunctionDecl *getCallee() const override;

  /// Returns the current function.
  const Function *getFunction() const { return Func; }

#ifndef NDEBUG
  /// Returns the offset on the stack at which the frame starts.
  size_t getFrameOffset() const { return FrameOffset; }
#endif

  /// Returns the value of a local variable.
  template <typename T> const T &getLocal(unsigned Offset) const {
    return localRef<T>(Offset);
  }

  /// Mutates a local variable.
  template <typename T> void setLocal(unsigned Offset, const T &Value) {
    localRef<T>(Offset) = Value;
    localInlineDesc(Offset)->IsInitialized = true;
    localInlineDesc(Offset)->LifeState = Lifetime::Started;
  }

  /// Returns a pointer to a local variables.
  Pointer getLocalPointer(unsigned Offset) const;
  Block *getLocalBlock(unsigned Offset) const;

  /// Returns the value of an argument.
  template <typename T> const T &getParam(unsigned Index) const {
    Block *ArgBlock = argBlock(Index);
    if (!ArgBlock->isInitialized())
      return stackRef<T>(Func->getParamDescriptor(Index).Offset);
    return ArgBlock->deref<T>();
  }

  /// Mutates a local copy of a parameter.
  template <typename T> void setParam(unsigned Index, const T &Value) {
    argBlock(Index)->deref<T>() = Value;
  }

  /// Returns a pointer to an argument - lazily creates a block.
  Pointer getParamPointer(unsigned Offset);

  bool hasThisPointer() const { return FuncFlags & HasThisFlag; }

  /// Returns the 'this' pointer.
  const Pointer &getThis() const {
    assert(hasThisPointer());
    assert(!isBottomFrame());
    return stackRef<Pointer>((FuncFlags & HasRVOFlag) ? sizeof(Pointer) : 0);
  }

  /// Returns the RVO pointer, if the Function has one.
  const Pointer &getRVOPtr() const {
    assert(Func);
    assert(Func->hasRVO());
    assert(!isBottomFrame());
    return stackRef<Pointer>(0);
  }

  /// Checks if the frame is a root frame - return should quit the interpreter.
  bool isRoot() const { return !Func; }

  /// Returns the return address of the frame.
  CodePtr getRetPC() const { return RetPC; }
  /// Returns the return address of the opcode in the caller frame.
  CodePtr getRetOpPC() const {
    // All the Call ops we have take a Function* and an unsigned.
    if (RetPC)
      return RetPC - align(sizeof(void *)) - align(sizeof(unsigned));
    return RetPC;
  }

  /// Map a location to a source.
  SourceInfo getSource(CodePtr PC) const;
  const Expr *getExpr(CodePtr PC) const;
  SourceLocation getLocation(CodePtr PC) const;
  SourceRange getRange(CodePtr PC) const;

  unsigned getDepth() const { return Depth; }
  unsigned getArgSize() const { return ArgSize; }

  bool isStdFunction() const;

  bool isBottomFrame() const { return !Caller; }

  void dump() const { dump(llvm::errs(), 0); }
  void dump(llvm::raw_ostream &OS, unsigned Indent = 0) const;

private:
  static constexpr uint8_t HasRVOFlag = 1u << 0u;
  static constexpr uint8_t HasThisFlag = 1u << 1u;

  /// Returns an original argument from the stack.
  template <typename T> const T &stackRef(unsigned Offset) const {
    assert(Args);
    return *reinterpret_cast<const T *>(Args - ArgSize + Offset);
  }

  /// Returns an offset to a local.
  template <typename T> T &localRef(unsigned Offset) const {
    return localBlock(Offset)->deref<T>();
  }

  /// Pointer to local memory.
  char *locals() const {
    return (reinterpret_cast<char *>(const_cast<InterpFrame *>(this))) +
           align(sizeof(InterpFrame));
  }

  /// Pointer to argument memory.
  char *args() const {
    return (reinterpret_cast<char *>(const_cast<InterpFrame *>(this))) +
           sizeof(InterpFrame) + Func->getFrameSize();
  }

  /// Returns a pointer to a local's block.
  Block *localBlock(unsigned Offset) const {
    return reinterpret_cast<Block *>(locals() + Offset - sizeof(Block));
  }

  /// Returns a pointer to an argument block.
  Block *argBlock(unsigned Index) const {
    unsigned ByteOffset = Func->getParamDescriptor(Index).BlockOffset;
    return reinterpret_cast<Block *>(args() + ByteOffset);
  }

  /// Returns the inline descriptor of the local.
  InlineDescriptor *localInlineDesc(unsigned Offset) const {
    return reinterpret_cast<InlineDescriptor *>(locals() + Offset);
  }

private:
  /// Reference to the interpreter state.
  InterpState &S;
  /// Depth of this frame.
  unsigned Depth;
  /// Reference to the function being executed.
  const Function *Func;
  /// Return address.
  CodePtr RetPC;
  /// The size of all the arguments.
  const unsigned ArgSize;
  /// Pointer to the arguments in the callee's frame.
  char *Args = nullptr;
#ifndef NDEBUG
  /// Offset on the stack at entry.
  size_t FrameOffset = 0;
#endif

public:
  unsigned MSVCConstexprAllowed = 0;

private:
  /// Relevant flags about the function.
  uint8_t FuncFlags = 0;
};

} // namespace interp
} // namespace clang

#endif
