//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// A helper class for emitting expressions and values as cir::ConstantOp
// and as initializers for global variables.
//
// Note: this is based on clang's LLVM IR codegen in ConstantEmitter.h, reusing
// this class interface makes it easier move forward with bringing CIR codegen
// to completion.
//
//===----------------------------------------------------------------------===//

#ifndef CLANG_LIB_CIR_CODEGEN_CIRGENCONSTANTEMITTER_H
#define CLANG_LIB_CIR_CODEGEN_CIRGENCONSTANTEMITTER_H

#include "CIRGenFunction.h"
#include "CIRGenModule.h"

namespace clang::CIRGen {

class ConstantEmitter {
public:
  CIRGenModule &cgm;
  const CIRGenFunction *cgf;

private:
  bool abstract = false;

#ifndef NDEBUG
  // Variables used for asserting state consistency.

  /// Whether non-abstract components of the emitter have been initialized.
  bool initializedNonAbstract = false;

  /// Whether the emitter has been finalized.
  bool finalized = false;

  /// Whether the constant-emission failed.
  bool failed = false;
#endif // NDEBUG

  /// Whether we're in a constant context.
  bool inConstantContext = false;

public:
  /// Initialize this emission in the context of the given function.
  /// Use this if the expression might contain contextual references like
  /// block addresses or PredefinedExprs.
  ConstantEmitter(CIRGenFunction &cgf) : cgm(cgf.cgm), cgf(&cgf) {}

  ConstantEmitter(CIRGenModule &cgm, CIRGenFunction *cgf = nullptr)
      : cgm(cgm), cgf(cgf) {}

  ConstantEmitter(const ConstantEmitter &other) = delete;
  ConstantEmitter &operator=(const ConstantEmitter &other) = delete;

  ~ConstantEmitter();

  /// Try to emit the initializer of the given declaration as an abstract
  /// constant.  If this succeeds, the emission must be finalized.
  mlir::Attribute tryEmitForInitializer(const VarDecl &d);

  void finalize(cir::GlobalOp gv);

  // All of the "abstract" emission methods below permit the emission to
  // be immediately discarded without finalizing anything.  Therefore, they
  // must also promise not to do anything that will, in the future, require
  // finalization:
  //
  //   - using the CGF (if present) for anything other than establishing
  //     semantic context; for example, an expression with ignored
  //     side-effects must not be emitted as an abstract expression
  //
  //   - doing anything that would not be safe to duplicate within an
  //     initializer or to propagate to another context; for example,
  //     side effects, or emitting an initialization that requires a
  //     reference to its current location.
  mlir::Attribute emitForMemory(mlir::Attribute c, QualType t);

  /// Try to emit the initializer of the given declaration as an abstract
  /// constant.
  mlir::Attribute tryEmitAbstractForInitializer(const VarDecl &d);

  /// Emit the result of the given expression as an abstract constant,
  /// asserting that it succeeded.  This is only safe to do when the
  /// expression is known to be a constant expression with either a fairly
  /// simple type or a known simple form.
  mlir::Attribute emitAbstract(SourceLocation loc, const APValue &value,
                               QualType t);

  mlir::Attribute tryEmitConstantExpr(const ConstantExpr *ce);

  // These are private helper routines of the constant emitter that
  // can't actually be private because things are split out into helper
  // functions and classes.

  mlir::Attribute tryEmitPrivateForVarInit(const VarDecl &d);

  mlir::Attribute tryEmitPrivate(const APValue &value, QualType destType);
  mlir::Attribute tryEmitPrivateForMemory(const APValue &value, QualType t);

private:
#ifndef NDEBUG
  void initializeNonAbstract() {
    assert(!initializedNonAbstract);
    initializedNonAbstract = true;
    assert(!cir::MissingFeatures::addressSpace());
  }
  mlir::Attribute markIfFailed(mlir::Attribute init) {
    if (!init)
      failed = true;
    return init;
  }
#else
  void initializeNonAbstract() {}
  mlir::Attribute markIfFailed(mlir::Attribute init) { return init; }
#endif // NDEBUG

  class AbstractStateRAII {
    ConstantEmitter &emitter;
    bool oldValue;

  public:
    AbstractStateRAII(ConstantEmitter &emitter, bool value)
        : emitter(emitter), oldValue(emitter.abstract) {
      emitter.abstract = value;
    }
    ~AbstractStateRAII() { emitter.abstract = oldValue; }
  };
};

} // namespace clang::CIRGen

#endif // CLANG_LIB_CIR_CODEGEN_CIRGENCONSTANTEMITTER_H
