// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build amd64
// +build amd64

package cpuid

// block is a collection of 32 Feature bits.
type block int

// blockSize is the number of bits in a single block.
const blockSize = 32

// featureID returns the feature identified by the given block and bit.
//
// Feature bits are numbered according to "blocks". Each block is 32 bits, and
// feature bits from the same source (cpuid leaf/level) are in the same block.
func featureID(b block, bit int) Feature {
	return Feature(blockSize*int(b) + bit)
}

// block returns the block associated with the feature.
func (f Feature) block() block {
	return block(f / blockSize)
}

// Bit returns the bit associated with the feature.
func (f Feature) bit() uint32 {
	return uint32(1 << (f % blockSize))
}

// ChangeableSet is a feature set that can allows changes.
type ChangeableSet interface {
	Query(in In) Out
	Set(in In, out Out)
}

// Set sets the given feature.
func (f Feature) Set(s ChangeableSet) {
	f.set(s, true)
}

// Unset unsets the given feature.
func (f Feature) Unset(s ChangeableSet) {
	f.set(s, false)
}

// set sets the given feature.
func (f Feature) set(s ChangeableSet, on bool) {
	switch f.block() {
	case 0:
		out := s.Query(In{Eax: uint32(featureInfo)})
		if on {
			out.Ecx |= f.bit()
		} else {
			out.Ecx &^= f.bit()
		}
		s.Set(In{Eax: uint32(featureInfo)}, out)
	case 1:
		out := s.Query(In{Eax: uint32(featureInfo)})
		if on {
			out.Edx |= f.bit()
		} else {
			out.Edx &^= f.bit()
		}
		s.Set(In{Eax: uint32(featureInfo)}, out)
	case 2:
		out := s.Query(In{Eax: uint32(extendedFeatureInfo)})
		if on {
			out.Ebx |= f.bit()
		} else {
			out.Ebx &^= f.bit()
		}
		s.Set(In{Eax: uint32(extendedFeatureInfo)}, out)
	case 3:
		out := s.Query(In{Eax: uint32(extendedFeatureInfo)})
		if on {
			out.Ecx |= f.bit()
		} else {
			out.Ecx &^= f.bit()
		}
		s.Set(In{Eax: uint32(extendedFeatureInfo)}, out)
	case 4:
		// Need to turn on the bit in block 0.
		out := s.Query(In{Eax: uint32(featureInfo)})
		out.Ecx |= (1 << 26)
		s.Set(In{Eax: uint32(featureInfo)}, out)

		out = s.Query(In{Eax: xSaveInfoSub.eax(), Ecx: xSaveInfoSub.ecx()})
		if on {
			out.Eax |= f.bit()
		} else {
			out.Eax &^= f.bit()
		}
		s.Set(In{Eax: xSaveInfoSub.eax(), Ecx: xSaveInfoSub.ecx()}, out)
	case 5, 6:
		// Need to enable extended features.
		out := s.Query(In{Eax: uint32(extendedFunctionInfo)})
		if out.Eax < uint32(extendedFeatures) {
			out.Eax = uint32(extendedFeatures)
		}
		s.Set(In{Eax: uint32(extendedFunctionInfo)}, out)
		out = s.Query(In{Eax: uint32(extendedFeatures)})
		if f.block() == 5 {
			if on {
				out.Ecx |= f.bit()
			} else {
				out.Ecx &^= f.bit()
			}
		} else {
			if on {
				out.Edx |= f.bit()
			} else {
				out.Edx &^= f.bit()
			}
		}
		s.Set(In{Eax: uint32(extendedFeatures)}, out)
	}
}

// check checks for the given feature.
//
//go:nosplit
func (f Feature) check(fs FeatureSet) bool {
	switch f.block() {
	case 0:
		_, _, cx, _ := fs.query(featureInfo)
		return (cx & f.bit()) != 0
	case 1:
		_, _, _, dx := fs.query(featureInfo)
		return (dx & f.bit()) != 0
	case 2:
		_, bx, _, _ := fs.query(extendedFeatureInfo)
		return (bx & f.bit()) != 0
	case 3:
		_, _, cx, _ := fs.query(extendedFeatureInfo)
		return (cx & f.bit()) != 0
	case 4:
		// Need to check appropriate bit in block 0.
		_, _, cx, _ := fs.query(featureInfo)
		if (cx & (1 << 26)) == 0 {
			return false
		}
		ax, _, _, _ := fs.query(xSaveInfoSub)
		return (ax & f.bit()) != 0
	case 5, 6:
		// eax=0x80000000 gets supported extended levels. We use this
		// to determine if there are any non-zero block 4 or block 6
		// bits to find.
		ax, _, _, _ := fs.query(extendedFunctionInfo)
		if ax >= uint32(extendedFeatures) {
			_, _, cx, dx := fs.query(extendedFeatures)
			if f.block() == 5 {
				return (cx & f.bit()) != 0
			}
			// Ignore features duplicated from block 1 on AMD.
			// These bits are reserved on Intel.
			return ((dx &^ block6DuplicateMask) & f.bit()) != 0
		}
		return false
	default:
		return false
	}
}

// Block 0 constants are all of the "basic" feature bits returned by a cpuid in
// ecx with eax=1.
const (
	X86FeatureSSE3 Feature = iota
	X86FeaturePCLMULDQ
	X86FeatureDTES64
	X86FeatureMONITOR
	X86FeatureDSCPL
	X86FeatureVMX
	X86FeatureSMX
	X86FeatureEST
	X86FeatureTM2
	X86FeatureSSSE3 // Not a typo, "supplemental" SSE3.
	X86FeatureCNXTID
	X86FeatureSDBG
	X86FeatureFMA
	X86FeatureCX16
	X86FeatureXTPR
	X86FeaturePDCM
	_ // ecx bit 16 is reserved.
	X86FeaturePCID
	X86FeatureDCA
	X86FeatureSSE4_1
	X86FeatureSSE4_2
	X86FeatureX2APIC
	X86FeatureMOVBE
	X86FeaturePOPCNT
	X86FeatureTSCD
	X86FeatureAES
	X86FeatureXSAVE
	X86FeatureOSXSAVE
	X86FeatureAVX
	X86FeatureF16C
	X86FeatureRDRAND
	X86FeatureHypervisor
)

// Block 1 constants are all of the "basic" feature bits returned by a cpuid in
// edx with eax=1.
const (
	X86FeatureFPU Feature = 32 + iota
	X86FeatureVME
	X86FeatureDE
	X86FeaturePSE
	X86FeatureTSC
	X86FeatureMSR
	X86FeaturePAE
	X86FeatureMCE
	X86FeatureCX8
	X86FeatureAPIC
	_ // edx bit 10 is reserved.
	X86FeatureSEP
	X86FeatureMTRR
	X86FeaturePGE
	X86FeatureMCA
	X86FeatureCMOV
	X86FeaturePAT
	X86FeaturePSE36
	X86FeaturePSN
	X86FeatureCLFSH
	_ // edx bit 20 is reserved.
	X86FeatureDS
	X86FeatureACPI
	X86FeatureMMX
	X86FeatureFXSR
	X86FeatureSSE
	X86FeatureSSE2
	X86FeatureSS
	X86FeatureHTT
	X86FeatureTM
	X86FeatureIA64
	X86FeaturePBE
)

// Block 2 bits are the "structured extended" features returned in ebx for
// eax=7, ecx=0.
const (
	X86FeatureFSGSBase Feature = 2*32 + iota
	X86FeatureTSC_ADJUST
	_ // ebx bit 2 is reserved.
	X86FeatureBMI1
	X86FeatureHLE
	X86FeatureAVX2
	X86FeatureFDP_EXCPTN_ONLY
	X86FeatureSMEP
	X86FeatureBMI2
	X86FeatureERMS
	X86FeatureINVPCID
	X86FeatureRTM
	X86FeatureCQM
	X86FeatureFPCSDS
	X86FeatureMPX
	X86FeatureRDT
	X86FeatureAVX512F
	X86FeatureAVX512DQ
	X86FeatureRDSEED
	X86FeatureADX
	X86FeatureSMAP
	X86FeatureAVX512IFMA
	X86FeaturePCOMMIT
	X86FeatureCLFLUSHOPT
	X86FeatureCLWB
	X86FeatureIPT // Intel processor trace.
	X86FeatureAVX512PF
	X86FeatureAVX512ER
	X86FeatureAVX512CD
	X86FeatureSHA
	X86FeatureAVX512BW
	X86FeatureAVX512VL
)

// Block 3 bits are the "extended" features returned in ecx for eax=7, ecx=0.
const (
	X86FeaturePREFETCHWT1 Feature = 3*32 + iota
	X86FeatureAVX512VBMI
	X86FeatureUMIP
	X86FeaturePKU
	X86FeatureOSPKE
	X86FeatureWAITPKG
	X86FeatureAVX512_VBMI2
	X86FeatureCET_SS
	X86FeatureGFNI
	X86FeatureVAES
	X86FeatureVPCLMULQDQ
	X86FeatureAVX512_VNNI
	X86FeatureAVX512_BITALG
	X86FeatureTME
	X86FeatureAVX512_VPOPCNTDQ
	_ // ecx bit 15 is reserved
	X86FeatureLA57
	// ecx bits 17-21 are reserved
	_
	_
	_
	_
	_
	X86FeatureRDPID
	// ecx bits 23-24 are reserved
	_
	_
	X86FeatureCLDEMOTE
	_ // ecx bit 26 is reserved
	X86FeatureMOVDIRI
	X86FeatureMOVDIR64B
)

// Block 4 constants are for xsave capabilities in CPUID.(EAX=0DH,ECX=01H):EAX.
// The CPUID leaf is available only if 'X86FeatureXSAVE' is present.
const (
	X86FeatureXSAVEOPT Feature = 4*32 + iota
	X86FeatureXSAVEC
	X86FeatureXGETBV1
	X86FeatureXSAVES
	// EAX[31:4] are reserved.
)

// Block 5 constants are the extended feature bits in
// CPUID.(EAX=0x80000001):ECX.
const (
	X86FeatureLAHF64 Feature = 5*32 + iota
	X86FeatureCMP_LEGACY
	X86FeatureSVM
	X86FeatureEXTAPIC
	X86FeatureCR8_LEGACY
	X86FeatureLZCNT
	X86FeatureSSE4A
	X86FeatureMISALIGNSSE
	X86FeaturePREFETCHW
	X86FeatureOSVW
	X86FeatureIBS
	X86FeatureXOP
	X86FeatureSKINIT
	X86FeatureWDT
	_ // ecx bit 14 is reserved.
	X86FeatureLWP
	X86FeatureFMA4
	X86FeatureTCE
	_ // ecx bit 18 is reserved.
	_ // ecx bit 19 is reserved.
	_ // ecx bit 20 is reserved.
	X86FeatureTBM
	X86FeatureTOPOLOGY
	X86FeaturePERFCTR_CORE
	X86FeaturePERFCTR_NB
	_ // ecx bit 25 is reserved.
	X86FeatureBPEXT
	X86FeaturePERFCTR_TSC
	X86FeaturePERFCTR_LLC
	X86FeatureMWAITX
	X86FeatureADMSKEXTN
	_ // ecx bit 31 is reserved.
)

// Block 6 constants are the extended feature bits in
// CPUID.(EAX=0x80000001):EDX.
//
// These are sparse, and so the bit positions are assigned manually.
const (
	// On AMD, EDX[24:23] | EDX[17:12] | EDX[9:0] are duplicate features
	// also defined in block 1 (in identical bit positions). Those features
	// are not listed here.
	block6DuplicateMask = 0x183f3ff

	X86FeatureSYSCALL  Feature = 6*32 + 11
	X86FeatureNX       Feature = 6*32 + 20
	X86FeatureMMXEXT   Feature = 6*32 + 22
	X86FeatureFXSR_OPT Feature = 6*32 + 25
	X86FeatureGBPAGES  Feature = 6*32 + 26
	X86FeatureRDTSCP   Feature = 6*32 + 27
	X86FeatureLM       Feature = 6*32 + 29
	X86Feature3DNOWEXT Feature = 6*32 + 30
	X86Feature3DNOW    Feature = 6*32 + 31
)

// These are the extended floating point state features. They are used to
// enumerate floating point features in XCR0, XSTATE_BV, etc.
const (
	XSAVEFeatureX87         = 1 << 0
	XSAVEFeatureSSE         = 1 << 1
	XSAVEFeatureAVX         = 1 << 2
	XSAVEFeatureBNDREGS     = 1 << 3
	XSAVEFeatureBNDCSR      = 1 << 4
	XSAVEFeatureAVX512op    = 1 << 5
	XSAVEFeatureAVX512zmm0  = 1 << 6
	XSAVEFeatureAVX512zmm16 = 1 << 7
	XSAVEFeaturePKRU        = 1 << 9
)

// allFeatures is the set of allFeatures.
//
// These match names used in arch/x86/kernel/cpu/capflags.c.
var allFeatures = map[Feature]allFeatureInfo{
	// Block 0.
	X86FeatureSSE3:       {"pni", true},
	X86FeaturePCLMULDQ:   {"pclmulqdq", true},
	X86FeatureDTES64:     {"dtes64", true},
	X86FeatureMONITOR:    {"monitor", true},
	X86FeatureDSCPL:      {"ds_cpl", true},
	X86FeatureVMX:        {"vmx", true},
	X86FeatureSMX:        {"smx", true},
	X86FeatureEST:        {"est", true},
	X86FeatureTM2:        {"tm2", true},
	X86FeatureSSSE3:      {"ssse3", true},
	X86FeatureCNXTID:     {"cid", true},
	X86FeatureSDBG:       {"sdbg", true},
	X86FeatureFMA:        {"fma", true},
	X86FeatureCX16:       {"cx16", true},
	X86FeatureXTPR:       {"xtpr", true},
	X86FeaturePDCM:       {"pdcm", true},
	X86FeaturePCID:       {"pcid", true},
	X86FeatureDCA:        {"dca", true},
	X86FeatureSSE4_1:     {"sse4_1", true},
	X86FeatureSSE4_2:     {"sse4_2", true},
	X86FeatureX2APIC:     {"x2apic", true},
	X86FeatureMOVBE:      {"movbe", true},
	X86FeaturePOPCNT:     {"popcnt", true},
	X86FeatureTSCD:       {"tsc_deadline_timer", true},
	X86FeatureAES:        {"aes", true},
	X86FeatureXSAVE:      {"xsave", true},
	X86FeatureAVX:        {"avx", true},
	X86FeatureF16C:       {"f16c", true},
	X86FeatureRDRAND:     {"rdrand", true},
	X86FeatureHypervisor: {"hypervisor", true},
	X86FeatureOSXSAVE:    {"osxsave", false},

	// Block 1.
	X86FeatureFPU:   {"fpu", true},
	X86FeatureVME:   {"vme", true},
	X86FeatureDE:    {"de", true},
	X86FeaturePSE:   {"pse", true},
	X86FeatureTSC:   {"tsc", true},
	X86FeatureMSR:   {"msr", true},
	X86FeaturePAE:   {"pae", true},
	X86FeatureMCE:   {"mce", true},
	X86FeatureCX8:   {"cx8", true},
	X86FeatureAPIC:  {"apic", true},
	X86FeatureSEP:   {"sep", true},
	X86FeatureMTRR:  {"mtrr", true},
	X86FeaturePGE:   {"pge", true},
	X86FeatureMCA:   {"mca", true},
	X86FeatureCMOV:  {"cmov", true},
	X86FeaturePAT:   {"pat", true},
	X86FeaturePSE36: {"pse36", true},
	X86FeaturePSN:   {"pn", true},
	X86FeatureCLFSH: {"clflush", true},
	X86FeatureDS:    {"dts", true},
	X86FeatureACPI:  {"acpi", true},
	X86FeatureMMX:   {"mmx", true},
	X86FeatureFXSR:  {"fxsr", true},
	X86FeatureSSE:   {"sse", true},
	X86FeatureSSE2:  {"sse2", true},
	X86FeatureSS:    {"ss", true},
	X86FeatureHTT:   {"ht", true},
	X86FeatureTM:    {"tm", true},
	X86FeatureIA64:  {"ia64", true},
	X86FeaturePBE:   {"pbe", true},

	// Block 2.
	X86FeatureFSGSBase:        {"fsgsbase", true},
	X86FeatureTSC_ADJUST:      {"tsc_adjust", true},
	X86FeatureBMI1:            {"bmi1", true},
	X86FeatureHLE:             {"hle", true},
	X86FeatureAVX2:            {"avx2", true},
	X86FeatureSMEP:            {"smep", true},
	X86FeatureBMI2:            {"bmi2", true},
	X86FeatureERMS:            {"erms", true},
	X86FeatureINVPCID:         {"invpcid", true},
	X86FeatureRTM:             {"rtm", true},
	X86FeatureCQM:             {"cqm", true},
	X86FeatureMPX:             {"mpx", true},
	X86FeatureRDT:             {"rdt_a", true},
	X86FeatureAVX512F:         {"avx512f", true},
	X86FeatureAVX512DQ:        {"avx512dq", true},
	X86FeatureRDSEED:          {"rdseed", true},
	X86FeatureADX:             {"adx", true},
	X86FeatureSMAP:            {"smap", true},
	X86FeatureCLWB:            {"clwb", true},
	X86FeatureAVX512PF:        {"avx512pf", true},
	X86FeatureAVX512ER:        {"avx512er", true},
	X86FeatureAVX512CD:        {"avx512cd", true},
	X86FeatureSHA:             {"sha_ni", true},
	X86FeatureAVX512BW:        {"avx512bw", true},
	X86FeatureAVX512VL:        {"avx512vl", true},
	X86FeatureFDP_EXCPTN_ONLY: {"fdp_excptn_only", false},
	X86FeatureFPCSDS:          {"fpcsds", false},
	X86FeatureIPT:             {"ipt", false},
	X86FeatureCLFLUSHOPT:      {"clfushopt", false},

	// Block 3.
	X86FeatureAVX512VBMI:       {"avx512vbmi", true},
	X86FeatureUMIP:             {"umip", true},
	X86FeaturePKU:              {"pku", true},
	X86FeatureOSPKE:            {"ospke", true},
	X86FeatureWAITPKG:          {"waitpkg", true},
	X86FeatureAVX512_VBMI2:     {"avx512_vbmi2", true},
	X86FeatureGFNI:             {"gfni", true},
	X86FeatureCET_SS:           {"cet_ss", false},
	X86FeatureVAES:             {"vaes", true},
	X86FeatureVPCLMULQDQ:       {"vpclmulqdq", true},
	X86FeatureAVX512_VNNI:      {"avx512_vnni", true},
	X86FeatureAVX512_BITALG:    {"avx512_bitalg", true},
	X86FeatureTME:              {"tme", true},
	X86FeatureAVX512_VPOPCNTDQ: {"avx512_vpopcntdq", true},
	X86FeatureLA57:             {"la57", true},
	X86FeatureRDPID:            {"rdpid", true},
	X86FeatureCLDEMOTE:         {"cldemote", true},
	X86FeatureMOVDIRI:          {"movdiri", true},
	X86FeatureMOVDIR64B:        {"movdir64b", true},
	X86FeaturePREFETCHWT1:      {"prefetchwt1", false},

	// Block 4.
	X86FeatureXSAVEOPT: {"xsaveopt", true},
	X86FeatureXSAVEC:   {"xsavec", true},
	X86FeatureXGETBV1:  {"xgetbv1", true},
	X86FeatureXSAVES:   {"xsaves", true},

	// Block 5.
	X86FeatureLAHF64:       {"lahf_lm", true}, // LAHF/SAHF in long mode.
	X86FeatureCMP_LEGACY:   {"cmp_legacy", true},
	X86FeatureSVM:          {"svm", true},
	X86FeatureEXTAPIC:      {"extapic", true},
	X86FeatureCR8_LEGACY:   {"cr8_legacy", true},
	X86FeatureLZCNT:        {"abm", true}, // Advanced bit manipulation.
	X86FeatureSSE4A:        {"sse4a", true},
	X86FeatureMISALIGNSSE:  {"misalignsse", true},
	X86FeaturePREFETCHW:    {"3dnowprefetch", true},
	X86FeatureOSVW:         {"osvw", true},
	X86FeatureIBS:          {"ibs", true},
	X86FeatureXOP:          {"xop", true},
	X86FeatureSKINIT:       {"skinit", true},
	X86FeatureWDT:          {"wdt", true},
	X86FeatureLWP:          {"lwp", true},
	X86FeatureFMA4:         {"fma4", true},
	X86FeatureTCE:          {"tce", true},
	X86FeatureTBM:          {"tbm", true},
	X86FeatureTOPOLOGY:     {"topoext", true},
	X86FeaturePERFCTR_CORE: {"perfctr_core", true},
	X86FeaturePERFCTR_NB:   {"perfctr_nb", true},
	X86FeatureBPEXT:        {"bpext", true},
	X86FeaturePERFCTR_TSC:  {"ptsc", true},
	X86FeaturePERFCTR_LLC:  {"perfctr_llc", true},
	X86FeatureMWAITX:       {"mwaitx", true},
	X86FeatureADMSKEXTN:    {"ad_mask_extn", false},

	// Block 6.
	X86FeatureSYSCALL:  {"syscall", true},
	X86FeatureNX:       {"nx", true},
	X86FeatureMMXEXT:   {"mmxext", true},
	X86FeatureFXSR_OPT: {"fxsr_opt", true},
	X86FeatureGBPAGES:  {"pdpe1gb", true},
	X86FeatureRDTSCP:   {"rdtscp", true},
	X86FeatureLM:       {"lm", true},
	X86Feature3DNOWEXT: {"3dnowext", true},
	X86Feature3DNOW:    {"3dnow", true},
}

// linuxBlockOrder defines the order in which linux organizes the feature
// blocks. Linux also tracks feature bits in 32-bit blocks, but in an order
// which doesn't match well here, so for the /proc/cpuinfo generation we simply
// re-map the blocks to Linux's ordering and then go through the bits in each
// block.
var linuxBlockOrder = []block{1, 6, 0, 5, 2, 4, 3}

func archFlagOrder(fn func(Feature)) {
	for _, b := range linuxBlockOrder {
		for i := 0; i < blockSize; i++ {
			f := featureID(b, i)
			if _, ok := allFeatures[f]; ok {
				fn(f)
			}
		}
	}
}
