//go:build gc

package platform

import (
	"runtime"
	"sync"
)

// CpuFeatures exposes the capabilities for this CPU, queried via the Has, HasExtra methods.
var CpuFeatures = sync.OnceValue(loadCpuFeatureFlags)

// cpuFeatureFlags implements CpuFeatureFlags interface.
type cpuFeatureFlags struct {
	isar0 uint64
	isar1 uint64
}

// implemented in cpuid_arm64.s
func getisar0() uint64

// implemented in cpuid_arm64.s
func getisar1() uint64

func loadCpuFeatureFlags() CpuFeatureFlags {
	switch runtime.GOOS {
	case "darwin", "windows":
		// These OSes do not allow userland to read the instruction set attribute registers,
		// but basically require atomic instructions:
		// - "darwin" is the desktop version (mobile version is "ios"),
		//   and the M1 is a ARMv8.4.
		// - "windows" requires them from Windows 11, see page 12
		//   https://download.microsoft.com/download/7/8/8/788bf5ab-0751-4928-a22c-dffdc23c27f2/Minimum%20Hardware%20Requirements%20for%20Windows%2011.pdf
		return &cpuFeatureFlags{
			isar0: uint64(CpuFeatureArm64Atomic),
			isar1: 0,
		}
	case "linux", "freebsd":
		// These OSes allow userland to read the instruction set attribute registers,
		// which is otherwise restricted to EL0:
		// https://kernel.org/doc/Documentation/arm64/cpu-feature-registers.txt
		// See these for contents of the registers:
		// https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/ID-AA64ISAR0-EL1--AArch64-Instruction-Set-Attribute-Register-0
		// https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/ID-AA64ISAR1-EL1--AArch64-Instruction-Set-Attribute-Register-1
		return &cpuFeatureFlags{
			isar0: getisar0(),
			isar1: getisar1(),
		}
	default:
		return &cpuFeatureFlags{}
	}
}

// Has implements the same method on the CpuFeatureFlags interface.
func (f *cpuFeatureFlags) Has(cpuFeature CpuFeature) bool {
	return (f.isar0 & uint64(cpuFeature)) != 0
}

// HasExtra implements the same method on the CpuFeatureFlags interface.
func (f *cpuFeatureFlags) HasExtra(cpuFeature CpuFeature) bool {
	return (f.isar1 & uint64(cpuFeature)) != 0
}

// Raw implements the same method on the CpuFeatureFlags interface.
func (f *cpuFeatureFlags) Raw() uint64 {
	// Below, we only set bits for the features we care about,
	// instead of setting all the unnecessary bits obtained from the
	// instruction set attribute registers.
	var ret uint64
	if f.Has(CpuFeatureArm64Atomic) {
		ret = 1 << 0
	}
	return ret
}
