//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// These classes implement wrappers around mlir::Value in order to fully
// represent the range of values for C L- and R- values.
//
//===----------------------------------------------------------------------===//

#ifndef CLANG_LIB_CIR_CIRGENVALUE_H
#define CLANG_LIB_CIR_CIRGENVALUE_H

#include "Address.h"

#include "clang/AST/CharUnits.h"
#include "clang/AST/Type.h"

#include "CIRGenRecordLayout.h"
#include "mlir/IR/Value.h"

#include "clang/CIR/MissingFeatures.h"

namespace clang::CIRGen {

/// This trivial value class is used to represent the result of an
/// expression that is evaluated. It can be one of three things: either a
/// simple MLIR SSA value, a pair of SSA values for complex numbers, or the
/// address of an aggregate value in memory.
class RValue {
  enum Flavor { Scalar, Complex, Aggregate };

  union {
    mlir::Value value;

    // Stores aggregate address.
    Address aggregateAddr;
  };

  unsigned isVolatile : 1;
  unsigned flavor : 2;

public:
  RValue() : value(nullptr), flavor(Scalar) {}

  bool isScalar() const { return flavor == Scalar; }
  bool isComplex() const { return flavor == Complex; }
  bool isAggregate() const { return flavor == Aggregate; }

  bool isVolatileQualified() const { return isVolatile; }

  /// Return the value of this scalar value.
  mlir::Value getValue() const {
    assert(isScalar() && "Not a scalar!");
    return value;
  }

  /// Return the value of the address of the aggregate.
  Address getAggregateAddress() const {
    assert(isAggregate() && "Not an aggregate!");
    return aggregateAddr;
  }

  mlir::Value getAggregatePointer(QualType pointeeType) const {
    return getAggregateAddress().getPointer();
  }

  static RValue getIgnored() {
    // FIXME: should we make this a more explicit state?
    return get(nullptr);
  }

  static RValue get(mlir::Value v) {
    RValue er;
    er.value = v;
    er.flavor = Scalar;
    er.isVolatile = false;
    return er;
  }

  static RValue getComplex(mlir::Value v) {
    RValue er;
    er.value = v;
    er.flavor = Complex;
    er.isVolatile = false;
    return er;
  }

  // volatile or not.  Remove default to find all places that probably get this
  // wrong.

  /// Convert an Address to an RValue. If the Address is not
  /// signed, create an RValue using the unsigned address. Otherwise, resign the
  /// address using the provided type.
  static RValue getAggregate(Address addr, bool isVolatile = false) {
    RValue er;
    er.aggregateAddr = addr;
    er.flavor = Aggregate;
    er.isVolatile = isVolatile;
    return er;
  }
};

/// The source of the alignment of an l-value; an expression of
/// confidence in the alignment actually matching the estimate.
enum class AlignmentSource {
  /// The l-value was an access to a declared entity or something
  /// equivalently strong, like the address of an array allocated by a
  /// language runtime.
  Decl,

  /// The l-value was considered opaque, so the alignment was
  /// determined from a type, but that type was an explicitly-aligned
  /// typedef.
  AttributedType,

  /// The l-value was considered opaque, so the alignment was
  /// determined from a type.
  Type
};

/// Given that the base address has the given alignment source, what's
/// our confidence in the alignment of the field?
static inline AlignmentSource getFieldAlignmentSource(AlignmentSource source) {
  // For now, we don't distinguish fields of opaque pointers from
  // top-level declarations, but maybe we should.
  return AlignmentSource::Decl;
}

class LValueBaseInfo {
  AlignmentSource alignSource;

public:
  explicit LValueBaseInfo(AlignmentSource source = AlignmentSource::Type)
      : alignSource(source) {}
  AlignmentSource getAlignmentSource() const { return alignSource; }
  void setAlignmentSource(AlignmentSource source) { alignSource = source; }

  void mergeForCast(const LValueBaseInfo &info) {
    setAlignmentSource(info.getAlignmentSource());
  }
};

class LValue {
  enum {
    Simple,       // This is a normal l-value, use getAddress().
    VectorElt,    // This is a vector element l-value (V[i]), use getVector*
    BitField,     // This is a bitfield l-value, use getBitfield*.
    ExtVectorElt, // This is an extended vector subset, use getExtVectorComp
    GlobalReg,    // This is a register l-value, use getGlobalReg()
    MatrixElt     // This is a matrix element, use getVector*
  } lvType;
  clang::QualType type;
  clang::Qualifiers quals;

  // The alignment to use when accessing this lvalue. (For vector elements,
  // this is the alignment of the whole vector)
  unsigned alignment;
  mlir::Value v;
  mlir::Value vectorIdx; // Index for vector subscript
  mlir::Type elementType;
  LValueBaseInfo baseInfo;
  const CIRGenBitFieldInfo *bitFieldInfo{nullptr};

  void initialize(clang::QualType type, clang::Qualifiers quals,
                  clang::CharUnits alignment, LValueBaseInfo baseInfo) {
    assert((!alignment.isZero() || type->isIncompleteType()) &&
           "initializing l-value with zero alignment!");
    this->type = type;
    this->quals = quals;
    const unsigned maxAlign = 1U << 31;
    this->alignment = alignment.getQuantity() <= maxAlign
                          ? alignment.getQuantity()
                          : maxAlign;
    assert(this->alignment == alignment.getQuantity() &&
           "Alignment exceeds allowed max!");
    this->baseInfo = baseInfo;
  }

public:
  bool isSimple() const { return lvType == Simple; }
  bool isVectorElt() const { return lvType == VectorElt; }
  bool isBitField() const { return lvType == BitField; }
  bool isVolatile() const { return quals.hasVolatile(); }

  bool isVolatileQualified() const { return quals.hasVolatile(); }

  unsigned getVRQualifiers() const {
    return quals.getCVRQualifiers() & ~clang::Qualifiers::Const;
  }

  clang::QualType getType() const { return type; }

  mlir::Value getPointer() const { return v; }

  clang::CharUnits getAlignment() const {
    return clang::CharUnits::fromQuantity(alignment);
  }
  void setAlignment(clang::CharUnits a) { alignment = a.getQuantity(); }

  Address getAddress() const {
    return Address(getPointer(), elementType, getAlignment());
  }

  const clang::Qualifiers &getQuals() const { return quals; }
  clang::Qualifiers &getQuals() { return quals; }

  LValueBaseInfo getBaseInfo() const { return baseInfo; }
  void setBaseInfo(LValueBaseInfo info) { baseInfo = info; }

  static LValue makeAddr(Address address, clang::QualType t,
                         LValueBaseInfo baseInfo) {
    // Classic codegen sets the objc gc qualifier here. That requires an
    // ASTContext, which is passed in from CIRGenFunction::makeAddrLValue.
    assert(!cir::MissingFeatures::objCGC());

    LValue r;
    r.lvType = Simple;
    r.v = address.getPointer();
    r.elementType = address.getElementType();
    r.initialize(t, t.getQualifiers(), address.getAlignment(), baseInfo);
    return r;
  }

  Address getVectorAddress() const {
    return Address(getVectorPointer(), elementType, getAlignment());
  }

  mlir::Value getVectorPointer() const {
    assert(isVectorElt());
    return v;
  }

  mlir::Value getVectorIdx() const {
    assert(isVectorElt());
    return vectorIdx;
  }

  static LValue makeVectorElt(Address vecAddress, mlir::Value index,
                              clang::QualType t, LValueBaseInfo baseInfo) {
    LValue r;
    r.lvType = VectorElt;
    r.v = vecAddress.getPointer();
    r.elementType = vecAddress.getElementType();
    r.vectorIdx = index;
    r.initialize(t, t.getQualifiers(), vecAddress.getAlignment(), baseInfo);
    return r;
  }

  // bitfield lvalue
  Address getBitFieldAddress() const {
    return Address(getBitFieldPointer(), elementType, getAlignment());
  }

  mlir::Value getBitFieldPointer() const {
    assert(isBitField());
    return v;
  }

  const CIRGenBitFieldInfo &getBitFieldInfo() const {
    assert(isBitField());
    return *bitFieldInfo;
  }

  /// Create a new object to represent a bit-field access.
  ///
  /// \param Addr - The base address of the bit-field sequence this
  /// bit-field refers to.
  /// \param Info - The information describing how to perform the bit-field
  /// access.
  static LValue makeBitfield(Address addr, const CIRGenBitFieldInfo &info,
                             clang::QualType type, LValueBaseInfo baseInfo) {
    LValue r;
    r.lvType = BitField;
    r.v = addr.getPointer();
    r.elementType = addr.getElementType();
    r.bitFieldInfo = &info;
    r.initialize(type, type.getQualifiers(), addr.getAlignment(), baseInfo);
    return r;
  }
};

/// An aggregate value slot.
class AggValueSlot {

  Address addr;
  clang::Qualifiers quals;

  /// This is set to true if some external code is responsible for setting up a
  /// destructor for the slot.  Otherwise the code which constructs it should
  /// push the appropriate cleanup.
  LLVM_PREFERRED_TYPE(bool)
  LLVM_ATTRIBUTE_UNUSED unsigned destructedFlag : 1;

  /// This is set to true if the memory in the slot is known to be zero before
  /// the assignment into it.  This means that zero fields don't need to be set.
  LLVM_PREFERRED_TYPE(bool)
  unsigned zeroedFlag : 1;

  /// This is set to true if the slot might be aliased and it's not undefined
  /// behavior to access it through such an alias.  Note that it's always
  /// undefined behavior to access a C++ object that's under construction
  /// through an alias derived from outside the construction process.
  ///
  /// This flag controls whether calls that produce the aggregate
  /// value may be evaluated directly into the slot, or whether they
  /// must be evaluated into an unaliased temporary and then memcpy'ed
  /// over.  Since it's invalid in general to memcpy a non-POD C++
  /// object, it's important that this flag never be set when
  /// evaluating an expression which constructs such an object.
  LLVM_PREFERRED_TYPE(bool)
  LLVM_ATTRIBUTE_UNUSED unsigned aliasedFlag : 1;

  /// This is set to true if the tail padding of this slot might overlap
  /// another object that may have already been initialized (and whose
  /// value must be preserved by this initialization). If so, we may only
  /// store up to the dsize of the type. Otherwise we can widen stores to
  /// the size of the type.
  LLVM_PREFERRED_TYPE(bool)
  LLVM_ATTRIBUTE_UNUSED unsigned overlapFlag : 1;

public:
  enum IsDestructed_t { IsNotDestructed, IsDestructed };
  enum IsZeroed_t { IsNotZeroed, IsZeroed };
  enum IsAliased_t { IsNotAliased, IsAliased };
  enum Overlap_t { MayOverlap, DoesNotOverlap };

  /// Returns an aggregate value slot indicating that the aggregate
  /// value is being ignored.
  static AggValueSlot ignored() {
    return forAddr(Address::invalid(), clang::Qualifiers(), IsNotDestructed,
                   IsNotAliased, DoesNotOverlap);
  }

  AggValueSlot(Address addr, clang::Qualifiers quals, bool destructedFlag,
               bool zeroedFlag, bool aliasedFlag, bool overlapFlag)
      : addr(addr), quals(quals), destructedFlag(destructedFlag),
        zeroedFlag(zeroedFlag), aliasedFlag(aliasedFlag),
        overlapFlag(overlapFlag) {}

  static AggValueSlot forAddr(Address addr, clang::Qualifiers quals,
                              IsDestructed_t isDestructed,
                              IsAliased_t isAliased, Overlap_t mayOverlap,
                              IsZeroed_t isZeroed = IsNotZeroed) {
    return AggValueSlot(addr, quals, isDestructed, isZeroed, isAliased,
                        mayOverlap);
  }

  static AggValueSlot forLValue(const LValue &LV, IsDestructed_t isDestructed,
                                IsAliased_t isAliased, Overlap_t mayOverlap,
                                IsZeroed_t isZeroed = IsNotZeroed) {
    return forAddr(LV.getAddress(), LV.getQuals(), isDestructed, isAliased,
                   mayOverlap, isZeroed);
  }

  clang::Qualifiers getQualifiers() const { return quals; }

  Address getAddress() const { return addr; }

  bool isIgnored() const { return !addr.isValid(); }

  mlir::Value getPointer() const { return addr.getPointer(); }

  IsZeroed_t isZeroed() const { return IsZeroed_t(zeroedFlag); }

  RValue asRValue() const {
    if (isIgnored())
      return RValue::getIgnored();
    assert(!cir::MissingFeatures::aggValueSlot());
    return RValue::getAggregate(getAddress());
  }
};

} // namespace clang::CIRGen

#endif // CLANG_LIB_CIR_CIRGENVALUE_H
