/*
 * Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
 *
 * SPDX-License-Identifier: MIT
 */

#ifndef _UTILS_OPTIONS_HPP_
#define _UTILS_OPTIONS_HPP_

#include <string>
#include <vector>
#include <cstdio>
#include "top.hpp"
#include <cassert>
#include <sstream>
#ifdef __linux__
#include <unistd.h>
#endif
#ifdef _WIN32
#include <process.h>
#include <cstdio>
#endif

namespace amd {

class Device;

namespace option {

// Option Type : Info[0:5]
enum OptionType { OT_BOOL, OT_INT32, OT_UINT32, OT_CSTRING, OT_UCHAR, OT_MASK = 0x3f };

/*
   Option's Attributes : Info[6:31]

   OxA_<sth> : Only one in each group is required.
   OA_<sth>  : Each one is independent, all OA_<sth> can be OR'ed together.
*/

// Option Value Attributes
enum OptionValue {
  OVA_OPTIONAL = 0x00,    // value is optional
  OVA_REQUIRED = 0x40,    // value is required to appear;
  OVA_DISALLOWED = 0x80,  // value may not be specified.
  OVA_MASK = 0xC0
};

// Option Form Attributes
enum OptionForm {
  OFA_NORMAL = 0x000,    // normal form, no prefix
  OFA_PREFIX_F = 0x100,  // -f<flag>, machine-independent
  OFA_PREFIX_M = 0x200,  // -m<flag>, machine-dependent
  OFA_PREFIX_W = 0x300,  // -W<flag>, warning (TODO)
  OFA_MASK = 0x300
};

// Option Group, at least one must be used.
enum OptionGroup { OA_RUNTIME = 0x400, OA_CLC = 0x800, OA_LINK_EXE = 0x1000, OA_LINK_LIB = 0x2000 };

// Option Value Separator, at least one must be used.
enum OptionValueSeparator {
  OA_SEPARATOR_NONE = 0x4000,
  OA_SEPARATOR_EQUAL = 0x8000,
  OA_SEPARATOR_SPACE = 0x10000
};

// Option visibility : at least one must be used.
enum OptionVisibility {
  OVIS_PUBLIC = 0x00000,    // ducumented
  OVIS_SUPPORT = 0x20000,   // supported, but undocumented
  OVIS_INTERNAL = 0x40000,  // internal-only, not available in product release
  OVIS_MASK = 0x60000
};

// Option other attributes : optional
enum OptionMisc {
  OA_MISC_ALIAS = 0x80000  // An alias option is one that refers to another option or options
                           // and its meaning is hard-coded in setAliasOptionVariable().
};

typedef bool OT_BOOL_t;
typedef int32_t OT_INT32_t;
typedef uint32_t OT_UINT32_t;
typedef const char* OT_CSTRING_t;
typedef unsigned char OT_UCHAR_t;

// This must be a POD
struct OptionDescriptor {
  const char* NameShort;      // short option starts with -
  const char* NameLong;       // long option starts with --
  uint32_t Info;              // defined by above enums
  uint32_t OptionOffset;      // member offset to OptionVariable
  int64_t DefaultVal;         // default value for option of non-string type
  int64_t ValMin;             // (ValMin, ValMax) is the option's Value range
  int64_t ValMax;             //     for option of non-string type
  const char* DefaultString;  // default value for string.
  const char* Description;    // Short Description for this option. -h will show this.
};

#define OPTION_sname(o) ((o)->NameShort)
#define OPTION_lname(o) ((o)->NameLong)
#define OPTION_type(o) ((o)->Info & OT_MASK)
#define OPTION_value(o) ((o)->Info & OVA_MASK)
#define OPTION_form(o) ((o)->Info & OFA_MASK)
#define OPTION_vis(o) ((o)->Info & OVIS_MASK)
#define OPTION_info(o) ((o)->Info)
#define OPTION_offset(o) ((o)->OptionOffset)
#define OPTION_default(o) ((o)->DefaultVal)
#define OPTION_min(o) ((o)->ValMin)
#define OPTION_max(o) ((o)->ValMax)
#define OPTION_defaultstr(o) ((o)->DefaultString)
#define OPTION_desc(o) ((o)->Description)

// OptionVariables must be a POD
struct OptionVariables {
#define OPTION(type, a, sn, ln, var, ideft, imin, imax, sdeft, desc) type##_t var;
#define NOPTION(type, a, sn, ln, var, ideft, imin, imax, sdeft, desc)
#define FLAG(type, a, sn, var, deft, desc) type##_t var;
#include "OPTIONS.def"
#undef OPTION
#undef NOPTION
#undef FLAG
};

#define OPTION_valueSeparator(c) (((c) == ' ') || ((c) == '='))
#define OFFSET(var) ((uint32_t)&(((OptionVariable*)0)->var))


enum OptionIdentifier {
#define OPTION(type, a, sn, ln, var, ideft, imin, imax, sdeft, desc) OID_##var,
#define NOPTION(type, a, sn, ln, var, ideft, imin, imax, sdeft, desc) OID_##var,
#define FLAG(type, a, sn, var, deft, desc) OID_##var,
#include "OPTIONS.def"
  OID_LAST
#undef OPTION
#undef NOPTION
#undef FLAG
};

// Only option that is a RUNTIME option and is not an alias option has an entry in OptionVariables
#define OPTIONHasOVariable(od) ((OPTION_info(od) & (OA_RUNTIME | OA_MISC_ALIAS)) == OA_RUNTIME)

// DumpFlags defines the values for oVariables->DumpFlags
enum DumpFlags {
  DUMP_NONE = 0x00000000,          // default
  DUMP_CL = 0x00000001,            // CL source
  DUMP_I = 0x00000002,             // pre-processed CL source
  DUMP_S = 0x00000004,             // x86 assembly text
  DUMP_O = 0x00000008,             // x86 object / GPU code object
  DUMP_DLL = 0x00000010,           // x86 DLL (.so)
  DUMP_IL = 0x00000020,            // per-kernel IL (GPU)
  DUMP_ISA = 0x00000040,           // per-kernel assembly text (GPU)
  DUMP_BIF = 0x00000080,           // binary for all kernels (GPU)
  DUMP_BC_ORIGINAL = 0x00000100,   // input bitcode (generated by clc)
  DUMP_BC_LINKED = 0x00000200,     // bitcode after linking and before optimization
  DUMP_BC_OPTIMIZED = 0x00000400,  // bitcode after optimization (llvm opt's output)
  DUMP_CGIL = 0x00000800,          // output il generated by Codegen (llvm llc's output)
  DUMP_DEBUGIL = 0x00001000,       // debug il (from MDParser, dwarf refers to)
  DUMP_SPIRV = 0x00002000,         // SPIR-V binary
  DUMP_BC_SPIRV = 0x00004000,      // bitcode translated from SPIR-V

  // For a binary is encrypted, can only dump the following
  DUMP_ENCRYPT = (DUMP_DLL | DUMP_BIF),

  // For a release product, can only dump the following
  DUMP_PRODUCT_FLAGS = (DUMP_CL | DUMP_I | DUMP_S | DUMP_O | DUMP_DLL |
                        // handling DUMP_BC_ORIGINAL would require a fair amount of work,
                        // since the output of comgr is already optimized
                        DUMP_BC_OPTIMIZED | DUMP_BC_LINKED | DUMP_IL | DUMP_CGIL | DUMP_DEBUGIL |
                        DUMP_ISA | DUMP_BIF),

  DUMP_ALL = 0x00007FFF  // Everything
};

enum OptLevelFlags : unsigned char {
  OPT_O0 = '0',  // No optimization setting.
  OPT_O1 = '1',
  OPT_O2 = '2',
  OPT_O3 = '3',
  OPT_O4 = '4',
  OPT_O5 = '5',
  OPT_OG = 'g',   // g ASCII
  OPT_OS = 's',   // s ASCII
  OPT_Error = 0,  // Invalid optimization set
  /** Canary Value that guards against enum changes
   * @warning This value cannot be changed without updating the appropriate
   * tests and should NEVER be decreased.
   */
  optLast = 117
};

class Options {
 public:
  std::string origOptionStr;

  OptionVariables* oVariables;                // pointer to a struct of all option variables
  std::string clcOptions;                     // options passed into EDG frontend (clc)
  std::vector<std::string> clangOptions;      // options passed into Clang frontend
  std::string llvmOptions;                    // options passed into backend (llvm)
  std::vector<std::string> finalizerOptions;  // options passed into finalizer

  // Given as build option
  int WorkGroupSize[3];  // -1: use default
  int NumAvailGPRs;
  unsigned kernelArgAlign;

  Options();
  ~Options();

  bool isDumpFlagSet(DumpFlags f) { return ((oVariables->DumpFlags) & f) && dumpEncrypt(f); }

  // Definition of each entry in flags
  // Note: this is 1-bit entry now, it may need to be expanded to
  //       more than 1 bit later.
  enum { FLAG_UNSEEN = 0, FLAG_SEEN = 1 };

  void setFlag(int option_ndx, uint32_t v) {
    int r = option_ndx / 32;
    int c = option_ndx % 32;
    uint32_t* p = &flags[r];
    uint32_t b = (1 << c);
    v = (v << c);
    *p = ((*p) & (~b)) | v;
  }

  uint32_t getFlag(int option_ndx) const {
    int r = option_ndx / 32;
    int c = option_ndx % 32;
    const uint32_t* p = &flags[r];
    return 1 & ((*p) >> c);
  }

  bool isOptionSeen(int option_ndx) const { return (getFlag(option_ndx) == FLAG_SEEN); }

  int getLLVMArgc() const { return llvmargc; }
  char** getLLVMArgv() { return llvmargv; }
  void setLLVMArgs(int argc, char** argv) {
    llvmargc = argc;
    llvmargv = argv;
  }

  void recordMemoryHandle(char* handle) { MemoryHandles.push_back(handle); }

  // Do post-parse processing After parsing all options,
  void postParseInit();

  void setBuildNo(unsigned int bnum) { buildNo = bnum; }
  unsigned int getBuildNo() const { return buildNo; }
  void setCurrKernelName(const char* name) { currKernelName = name; }
  const char* getCurrKernelName() const { return currKernelName; }
  std::string getDumpFileName(const std::string& ext);

  void setPerBuildInfo(const char* val, int encrypt, bool device);
  bool isCStrOptionsEqual(const char* cs1, const char* cs2) const;


  bool useDefaultWGS() const { return UseDefaultWGS; }
  void setDefaultWGS(bool V) { UseDefaultWGS = V; }

  std::string& optionsLog() { return OptionsLog; }

  // Returns whether this set of options equals to another set of options
  bool equals(const Options& other, bool ignoreClcOptions = false) const;

  // Set the option variables same as defined in "other"
  bool setOptionVariablesAs(const Options& other);

  std::string getFinalizerOptions() { return getStringFromStringVec(finalizerOptions); }

 private:
  std::string fullPath, baseName;
  long basename_max;
  std::string OptionsLog;

  // One bit for each flag.
  uint32_t flags[(OID_LAST + 31) / 32];

  int llvmargc;
  char** llvmargv;

  // buildNo is a unique number for each device build, save it here so that
  // dumping can use it in its file names.
  //
  // Note that buildNo, dumpFileRoot, and encryptCode are valid only during the lifetime
  // of a device build.
  unsigned int buildNo;
  std::string dumpFileRoot;
  const char* currKernelName;
  int encryptCode;

  std::vector<char*> MemoryHandles;

  bool UseDefaultWGS;

  bool dumpEncrypt(DumpFlags f) const {
    return ((encryptCode == 0) ||  // return true if not encrypted
            (f & DUMP_ENCRYPT));
  }

  std::string getStringFromStringVec(const std::vector<std::string>& stringVec);
  void setDumpFileName(const char* val);

 public:
  std::string sourceFileName_;
};

OptionDescriptor* getOptDescTable();
bool init();
bool teardown();
bool parseAllOptions(std::string& options, Options& Opts, bool linkOptsOnly);
inline bool parseLinkOptions(std::string& options, Options& Opts) {
  return parseAllOptions(options, Opts, true /*linkOptsOnly*/);
}


}  // namespace option

}  // namespace amd

#endif  // _UTILS_OPTIONS_HPP_
