//==-- CIRGenFunctionInfo.h - Representation of fn argument/return types ---==//
//
// 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 CIRGenFunctionInfo and associated types used in representing the
// CIR source types and ABI-coerced types for function arguments and
// return values.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_CIR_CIRGENFUNCTIONINFO_H
#define LLVM_CLANG_CIR_CIRGENFUNCTIONINFO_H

#include "clang/AST/CanonicalType.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/ADT/FoldingSet.h"
#include "llvm/Support/TrailingObjects.h"

namespace clang::CIRGen {

/// A class for recording the number of arguments that a function signature
/// requires.
class RequiredArgs {
  /// The number of required arguments, or ~0 if the signature does not permit
  /// optional arguments.
  unsigned numRequired;

public:
  enum All_t { All };

  RequiredArgs(All_t _) : numRequired(~0U) {}
  explicit RequiredArgs(unsigned n) : numRequired(n) { assert(n != ~0U); }

  unsigned getOpaqueData() const { return numRequired; }

  bool allowsOptionalArgs() const { return numRequired != ~0U; }

  /// Compute the arguments required by the given formal prototype, given that
  /// there may be some additional, non-formal arguments in play.
  ///
  /// If FD is not null, this will consider pass_object_size params in FD.
  static RequiredArgs
  getFromProtoWithExtraSlots(const clang::FunctionProtoType *prototype,
                             unsigned additional) {
    if (!prototype->isVariadic())
      return All;

    if (prototype->hasExtParameterInfos())
      llvm_unreachable("NYI");

    return RequiredArgs(prototype->getNumParams() + additional);
  }

  static RequiredArgs
  getFromProtoWithExtraSlots(clang::CanQual<clang::FunctionProtoType> prototype,
                             unsigned additional) {
    return getFromProtoWithExtraSlots(prototype.getTypePtr(), additional);
  }

  unsigned getNumRequiredArgs() const {
    assert(allowsOptionalArgs());
    return numRequired;
  }
};

// The TrailingObjects for this class contain the function return type in the
// first CanQualType slot, followed by the argument types.
class CIRGenFunctionInfo final
    : public llvm::FoldingSetNode,
      private llvm::TrailingObjects<CIRGenFunctionInfo, CanQualType> {
  RequiredArgs required;

  unsigned numArgs;

  CanQualType *getArgTypes() { return getTrailingObjects(); }
  const CanQualType *getArgTypes() const { return getTrailingObjects(); }

  CIRGenFunctionInfo() : required(RequiredArgs::All) {}

public:
  static CIRGenFunctionInfo *create(CanQualType resultType,
                                    llvm::ArrayRef<CanQualType> argTypes,
                                    RequiredArgs required);

  void operator delete(void *p) { ::operator delete(p); }

  // Friending class TrailingObjects is apparantly not good enough for MSVC, so
  // these have to be public.
  friend class TrailingObjects;

  using const_arg_iterator = const CanQualType *;
  using arg_iterator = CanQualType *;

  // This function has to be CamelCase because llvm::FoldingSet requires so.
  // NOLINTNEXTLINE(readability-identifier-naming)
  static void Profile(llvm::FoldingSetNodeID &id, RequiredArgs required,
                      CanQualType resultType,
                      llvm::ArrayRef<CanQualType> argTypes) {
    id.AddBoolean(required.getOpaqueData());
    resultType.Profile(id);
    for (const CanQualType &arg : argTypes)
      arg.Profile(id);
  }

  // NOLINTNEXTLINE(readability-identifier-naming)
  void Profile(llvm::FoldingSetNodeID &id) {
    // If the Profile functions get out of sync, we can end up with incorrect
    // function signatures, so we call the static Profile function here rather
    // than duplicating the logic.
    Profile(id, required, getReturnType(), arguments());
  }

  llvm::ArrayRef<CanQualType> arguments() const {
    return llvm::ArrayRef<CanQualType>(argTypesBegin(), numArgs);
  }

  llvm::ArrayRef<CanQualType> requiredArguments() const {
    return llvm::ArrayRef<CanQualType>(argTypesBegin(), getNumRequiredArgs());
  }

  CanQualType getReturnType() const { return getArgTypes()[0]; }

  const_arg_iterator argTypesBegin() const { return getArgTypes() + 1; }
  const_arg_iterator argTypesEnd() const { return getArgTypes() + 1 + numArgs; }
  arg_iterator argTypesBegin() { return getArgTypes() + 1; }
  arg_iterator argTypesEnd() { return getArgTypes() + 1 + numArgs; }

  unsigned argTypeSize() const { return numArgs; }

  llvm::MutableArrayRef<CanQualType> argTypes() {
    return llvm::MutableArrayRef<CanQualType>(argTypesBegin(), numArgs);
  }
  llvm::ArrayRef<CanQualType> argTypes() const {
    return llvm::ArrayRef<CanQualType>(argTypesBegin(), numArgs);
  }

  bool isVariadic() const { return required.allowsOptionalArgs(); }
  RequiredArgs getRequiredArgs() const { return required; }
  unsigned getNumRequiredArgs() const {
    return isVariadic() ? getRequiredArgs().getNumRequiredArgs()
                        : argTypeSize();
  }
};

} // namespace clang::CIRGen

#endif
