//
// Copyright (c) 2009 Advanced Micro Devices, Inc. All rights reserved.
//

#ifndef _UTILS_OPTIONS_HPP_
#define _UTILS_OPTIONS_HPP_

#include <string>
#include <vector>
#include <cstdio>
#include "top.hpp"
#include "library.hpp"
#include <cassert>
#include <sstream>
#ifdef __linux__
#include <unistd.h>
#endif
#ifdef _WIN32
#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 |
                          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];
        uint32_t b = (1 << c);
        return 1 & ((*p) >> c);
    }

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

    int    getLLVMArgc() { 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() { 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.
    const int flagsSize;
    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) {
        return ((encryptCode == 0) ||   // return true if not encrypted
                (f & DUMP_ENCRYPT));
    }

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

public:
    LibrarySelector libraryType_;
    std::string sourceFileName_;
};

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


} // option

} // amd

#endif // _UTILS_OPTIONS_HPP_
