package x86_64

import (
    `fmt`
)

// ISA represents an extension to x86-64 instruction set.
type ISA uint64

const (
    ISA_CPUID ISA = 1 << iota
    ISA_RDTSC
    ISA_RDTSCP
    ISA_CMOV
    ISA_MOVBE
    ISA_POPCNT
    ISA_LZCNT
    ISA_TBM
    ISA_BMI
    ISA_BMI2
    ISA_ADX
    ISA_MMX
    ISA_MMX_PLUS
    ISA_FEMMS
    ISA_3DNOW
    ISA_3DNOW_PLUS
    ISA_SSE
    ISA_SSE2
    ISA_SSE3
    ISA_SSSE3
    ISA_SSE4A
    ISA_SSE4_1
    ISA_SSE4_2
    ISA_FMA3
    ISA_FMA4
    ISA_XOP
    ISA_F16C
    ISA_AVX
    ISA_AVX2
    ISA_AVX512F
    ISA_AVX512BW
    ISA_AVX512DQ
    ISA_AVX512VL
    ISA_AVX512PF
    ISA_AVX512ER
    ISA_AVX512CD
    ISA_AVX512VBMI
    ISA_AVX512IFMA
    ISA_AVX512VPOPCNTDQ
    ISA_AVX512_4VNNIW
    ISA_AVX512_4FMAPS
    ISA_PREFETCH
    ISA_PREFETCHW
    ISA_PREFETCHWT1
    ISA_CLFLUSH
    ISA_CLFLUSHOPT
    ISA_CLWB
    ISA_CLZERO
    ISA_RDRAND
    ISA_RDSEED
    ISA_PCLMULQDQ
    ISA_AES
    ISA_SHA
    ISA_MONITOR
    ISA_MONITORX
    ISA_ALL = ^ISA(0)
)

var _ISA_NAMES = map[ISA]string {
    ISA_CPUID           : "CPUID",
    ISA_RDTSC           : "RDTSC",
    ISA_RDTSCP          : "RDTSCP",
    ISA_CMOV            : "CMOV",
    ISA_MOVBE           : "MOVBE",
    ISA_POPCNT          : "POPCNT",
    ISA_LZCNT           : "LZCNT",
    ISA_TBM             : "TBM",
    ISA_BMI             : "BMI",
    ISA_BMI2            : "BMI2",
    ISA_ADX             : "ADX",
    ISA_MMX             : "MMX",
    ISA_MMX_PLUS        : "MMX+",
    ISA_FEMMS           : "FEMMS",
    ISA_3DNOW           : "3dnow!",
    ISA_3DNOW_PLUS      : "3dnow!+",
    ISA_SSE             : "SSE",
    ISA_SSE2            : "SSE2",
    ISA_SSE3            : "SSE3",
    ISA_SSSE3           : "SSSE3",
    ISA_SSE4A           : "SSE4A",
    ISA_SSE4_1          : "SSE4.1",
    ISA_SSE4_2          : "SSE4.2",
    ISA_FMA3            : "FMA3",
    ISA_FMA4            : "FMA4",
    ISA_XOP             : "XOP",
    ISA_F16C            : "F16C",
    ISA_AVX             : "AVX",
    ISA_AVX2            : "AVX2",
    ISA_AVX512F         : "AVX512F",
    ISA_AVX512BW        : "AVX512BW",
    ISA_AVX512DQ        : "AVX512DQ",
    ISA_AVX512VL        : "AVX512VL",
    ISA_AVX512PF        : "AVX512PF",
    ISA_AVX512ER        : "AVX512ER",
    ISA_AVX512CD        : "AVX512CD",
    ISA_AVX512VBMI      : "AVX512VBMI",
    ISA_AVX512IFMA      : "AVX512IFMA",
    ISA_AVX512VPOPCNTDQ : "AVX512VPOPCNTDQ",
    ISA_AVX512_4VNNIW   : "AVX512_4VNNIW",
    ISA_AVX512_4FMAPS   : "AVX512_4FMAPS",
    ISA_PREFETCH        : "PREFETCH",
    ISA_PREFETCHW       : "PREFETCHW",
    ISA_PREFETCHWT1     : "PREFETCHWT1",
    ISA_CLFLUSH         : "CLFLUSH",
    ISA_CLFLUSHOPT      : "CLFLUSHOPT",
    ISA_CLWB            : "CLWB",
    ISA_CLZERO          : "CLZERO",
    ISA_RDRAND          : "RDRAND",
    ISA_RDSEED          : "RDSEED",
    ISA_PCLMULQDQ       : "PCLMULQDQ",
    ISA_AES             : "AES",
    ISA_SHA             : "SHA",
    ISA_MONITOR         : "MONITOR",
    ISA_MONITORX        : "MONITORX",
}

var _ISA_MAPPING = map[string]ISA {
    "CPUID"           : ISA_CPUID,
    "RDTSC"           : ISA_RDTSC,
    "RDTSCP"          : ISA_RDTSCP,
    "CMOV"            : ISA_CMOV,
    "MOVBE"           : ISA_MOVBE,
    "POPCNT"          : ISA_POPCNT,
    "LZCNT"           : ISA_LZCNT,
    "TBM"             : ISA_TBM,
    "BMI"             : ISA_BMI,
    "BMI2"            : ISA_BMI2,
    "ADX"             : ISA_ADX,
    "MMX"             : ISA_MMX,
    "MMX+"            : ISA_MMX_PLUS,
    "FEMMS"           : ISA_FEMMS,
    "3dnow!"          : ISA_3DNOW,
    "3dnow!+"         : ISA_3DNOW_PLUS,
    "SSE"             : ISA_SSE,
    "SSE2"            : ISA_SSE2,
    "SSE3"            : ISA_SSE3,
    "SSSE3"           : ISA_SSSE3,
    "SSE4A"           : ISA_SSE4A,
    "SSE4.1"          : ISA_SSE4_1,
    "SSE4.2"          : ISA_SSE4_2,
    "FMA3"            : ISA_FMA3,
    "FMA4"            : ISA_FMA4,
    "XOP"             : ISA_XOP,
    "F16C"            : ISA_F16C,
    "AVX"             : ISA_AVX,
    "AVX2"            : ISA_AVX2,
    "AVX512F"         : ISA_AVX512F,
    "AVX512BW"        : ISA_AVX512BW,
    "AVX512DQ"        : ISA_AVX512DQ,
    "AVX512VL"        : ISA_AVX512VL,
    "AVX512PF"        : ISA_AVX512PF,
    "AVX512ER"        : ISA_AVX512ER,
    "AVX512CD"        : ISA_AVX512CD,
    "AVX512VBMI"      : ISA_AVX512VBMI,
    "AVX512IFMA"      : ISA_AVX512IFMA,
    "AVX512VPOPCNTDQ" : ISA_AVX512VPOPCNTDQ,
    "AVX512_4VNNIW"   : ISA_AVX512_4VNNIW,
    "AVX512_4FMAPS"   : ISA_AVX512_4FMAPS,
    "PREFETCH"        : ISA_PREFETCH,
    "PREFETCHW"       : ISA_PREFETCHW,
    "PREFETCHWT1"     : ISA_PREFETCHWT1,
    "CLFLUSH"         : ISA_CLFLUSH,
    "CLFLUSHOPT"      : ISA_CLFLUSHOPT,
    "CLWB"            : ISA_CLWB,
    "CLZERO"          : ISA_CLZERO,
    "RDRAND"          : ISA_RDRAND,
    "RDSEED"          : ISA_RDSEED,
    "PCLMULQDQ"       : ISA_PCLMULQDQ,
    "AES"             : ISA_AES,
    "SHA"             : ISA_SHA,
    "MONITOR"         : ISA_MONITOR,
    "MONITORX"        : ISA_MONITORX,
}

func (self ISA) String() string {
    if v, ok := _ISA_NAMES[self]; ok {
        return v
    } else {
        return fmt.Sprintf("(invalid: %#x)", uint64(self))
    }
}

// ParseISA parses name into ISA, it will panic if the name is invalid.
func ParseISA(name string) ISA {
    if v, ok := _ISA_MAPPING[name]; ok {
        return v
    } else {
        panic("invalid ISA name: " + name)
    }
}

// Arch represents the x86_64 architecture.
type Arch struct {
    isa ISA
}

// DefaultArch is the default architecture with all ISA enabled.
var DefaultArch = CreateArch()

// CreateArch creates a new Arch with all ISA enabled.
func CreateArch() *Arch {
    return new(Arch).EnableISA(ISA_ALL)
}

// HasISA checks if a particular ISA was enabled.
func (self *Arch) HasISA(isa ISA) bool {
    return (self.isa & isa) != 0
}

// EnableISA enables a particular ISA.
func (self *Arch) EnableISA(isa ISA) *Arch {
    self.isa |= isa
    return self
}

// DisableISA disables a particular ISA.
func (self *Arch) DisableISA(isa ISA) *Arch {
    self.isa &^= isa
    return self
}

// CreateProgram creates a new empty program.
func (self *Arch) CreateProgram() *Program {
    return newProgram(self)
}
