//===- subzero/src/IceClFlags.cpp - Command line flags and parsing --------===//
//
//                        The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief Defines commandline flags parsing of class Ice::ClFlags.
///
/// This currently relies on llvm::cl to parse. In the future, the minimal build
/// can have a simpler parser.
///
//===----------------------------------------------------------------------===//

#include "IceClFlags.h"

#include "IceClFlags.def"

#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif // __clang__

#include "llvm/Support/CommandLine.h"

#ifdef __clang__
#pragma clang diagnostic pop
#endif // __clang__

#include <utility>

namespace {
// cl is used to alias the llvm::cl types and functions that we need.
namespace cl {

using alias = llvm::cl::alias;

using aliasopt = llvm::cl::aliasopt;

using llvm::cl::CommaSeparated;

using desc = llvm::cl::desc;

template <typename T> using initializer = llvm::cl::initializer<T>;

template <typename T> initializer<T> init(const T &Val) {
  return initializer<T>(Val);
}

template <typename T> using list = llvm::cl::list<T>;

using llvm::cl::NotHidden;

template <typename T> using opt = llvm::cl::opt<T>;

using llvm::cl::ParseCommandLineOptions;

using llvm::cl::Positional;

// LLVM commit 3ffe113e11168abcd809ec5ac539538ade5db0cb changed the internals of
// llvm::cl that need to be mirrored here.  That commit removed the clEnumValEnd
// macro, so we can use that to determine which version of LLVM we're compiling
// against.
#if defined(clEnumValEnd)

#define CLENUMVALEND , clEnumValEnd

template <typename T> using ValuesClass = llvm::cl::ValuesClass<T>;

template <typename T, typename... A>
ValuesClass<T> values(const char *Arg, T Val, const char *Desc, A &&... Args) {
  return llvm::cl::values(Arg, Val, Desc, std::forward<A>(Args)..., nullptr);
}

#else // !defined(clEnumValEnd)

#define CLENUMVALEND

using llvm::cl::OptionEnumValue;

template <typename... A> llvm::cl::ValuesClass values(A &&... Args) {
  return llvm::cl::values(std::forward<A>(Args)...);
}

#endif // !defined(clEnumValEnd)

using llvm::cl::value_desc;
} // end of namespace cl

// cl_type_traits is used to convert between a tuple of <T, cl_detail::*flag> to
// the appropriate (llvm::)cl object.
template <typename B, typename CL> struct cl_type_traits {};

template <typename T>
struct cl_type_traits<T, ::Ice::cl_detail::dev_list_flag> {
  using cl_type = cl::list<T>;
};

template <typename T> struct cl_type_traits<T, ::Ice::cl_detail::dev_opt_flag> {
  using cl_type = cl::opt<T>;
};

template <typename T>
struct cl_type_traits<T, ::Ice::cl_detail::release_opt_flag> {
  using cl_type = cl::opt<T>;
};

#define X(Name, Type, ClType, ...)                                             \
  cl_type_traits<Type, Ice::cl_detail::ClType>::cl_type Name##Obj(__VA_ARGS__);
COMMAND_LINE_FLAGS
#undef X

// Add declarations that do not need to add members to ClFlags below.
cl::alias AllowExternDefinedSymbolsA(
    "allow-extern", cl::desc("Alias for --allow-externally-defined-symbols"),
    cl::NotHidden, cl::aliasopt(AllowExternDefinedSymbolsObj));

std::string AppNameObj;

} // end of anonymous namespace

namespace Ice {

ClFlags ClFlags::Flags;

void ClFlags::parseFlags(int argc, const char *const *argv) {
  cl::ParseCommandLineOptions(argc, argv);
  AppNameObj = argv[0];
}

namespace {
// flagInitOrStorageTypeDefault is some template voodoo for peeling off the
// llvm::cl modifiers from a flag's declaration, until its initial value is
// found. If none is found, then the default value for the storage type is
// returned.
template <typename Ty> Ty flagInitOrStorageTypeDefault() { return Ty(); }

template <typename Ty, typename T, typename... A>
Ty flagInitOrStorageTypeDefault(cl::initializer<T> &&Value, A &&...) {
  return Value.Init;
}

// is_cl_initializer is used to prevent an ambiguous call between the previous
// version of flagInitOrStorageTypeDefault, and the next, which is flagged by
// g++.
template <typename T> struct is_cl_initializer {
  static constexpr bool value = false;
};

template <typename T> struct is_cl_initializer<cl::initializer<T>> {
  static constexpr bool value = true;
};

template <typename Ty, typename T, typename... A>
typename std::enable_if<!is_cl_initializer<T>::value, Ty>::type
flagInitOrStorageTypeDefault(T &&, A &&... Other) {
  return flagInitOrStorageTypeDefault<Ty>(std::forward<A>(Other)...);
}

} // end of anonymous namespace

void ClFlags::resetClFlags() {
#define X(Name, Type, ClType, ...)                                             \
  Name = flagInitOrStorageTypeDefault<                                         \
      detail::cl_type_traits<Type, cl_detail::ClType>::storage_type>(          \
      __VA_ARGS__);
  COMMAND_LINE_FLAGS
#undef X
}

namespace {

// toSetterParam is template magic that is needed to convert between (llvm::)cl
// objects and the arguments to ClFlags' setters. ToSetterParam is a traits
// object that we need in order for the multiple specializations to
// toSetterParam to agree on their return type.
template <typename T> struct ToSetterParam { using ReturnType = const T &; };

template <> struct ToSetterParam<cl::list<Ice::VerboseItem>> {
  using ReturnType = Ice::VerboseMask;
};

template <> struct ToSetterParam<cl::list<std::string>> {
  using ReturnType = std::vector<std::string>;
};

template <typename T>
typename ToSetterParam<T>::ReturnType toSetterParam(const T &Param) {
  return Param;
}

template <>
ToSetterParam<cl::list<Ice::VerboseItem>>::ReturnType
toSetterParam(const cl::list<Ice::VerboseItem> &Param) {
  Ice::VerboseMask VMask = Ice::IceV_None;
  // Don't generate verbose messages if routines to dump messages are not
  // available.
  if (BuildDefs::dump()) {
    for (unsigned i = 0; i != Param.size(); ++i)
      VMask |= Param[i];
  }
  return VMask;
}

template <>
ToSetterParam<cl::list<std::string>>::ReturnType
toSetterParam(const cl::list<std::string> &Param) {
  return *&Param;
}

} // end of anonymous namespace

void ClFlags::getParsedClFlags(ClFlags &OutFlags) {
#define X(Name, Type, ClType, ...) OutFlags.set##Name(toSetterParam(Name##Obj));
  COMMAND_LINE_FLAGS
#undef X

  // If any value needs a non-trivial parsed value, set it below.
  OutFlags.setAllowExternDefinedSymbols(AllowExternDefinedSymbolsObj ||
                                        DisableInternalObj);
  OutFlags.setDisableHybridAssembly(DisableHybridAssemblyObj ||
                                    (OutFileTypeObj != Ice::FT_Iasm));
  OutFlags.ForceO2.init(OutFlags.getForceO2String());
  OutFlags.SplitInsts.init(OutFlags.getSplitInstString());
  OutFlags.TestStatus.init(OutFlags.getTestStatusString());
  OutFlags.TimingFocus.init(OutFlags.getTimingFocusOnString());
  OutFlags.TranslateOnly.init(OutFlags.getTranslateOnlyString());
  OutFlags.VerboseFocus.init(OutFlags.getVerboseFocusOnString());
}

} // end of namespace Ice