diff --git a/cpuinfo.go b/cpuinfo.go index 91f50e8..51d1795 100644 --- a/cpuinfo.go +++ b/cpuinfo.go @@ -23,7 +23,8 @@ import ( "github.com/containerd/log" ) -// Present the ARM instruction set architecture, eg: v7, v8 +// Present the instruction set architecture, eg: v7, v8 for ARM CPU, +// v3, v4 for AMD64 CPU. // Don't use this value directly; call cpuVariant() instead. var cpuVariantValue string @@ -33,9 +34,15 @@ func cpuVariant() string { cpuVariantOnce.Do(func() { if isArmArch(runtime.GOARCH) { var err error - cpuVariantValue, err = getCPUVariant() + cpuVariantValue, err = getArmCPUVariant() if err != nil { - log.L.Errorf("Error getCPUVariant for OS %s: %v", runtime.GOOS, err) + log.L.Errorf("Error getArmCPUVariant for OS %s: %v", runtime.GOOS, err) + } + } else if isAmd64Arch(runtime.GOARCH) { + var err error + cpuVariantValue, err = getAmd64MicroArchLevel() + if err != nil { + log.L.Errorf("Error getAmd64MicroArchLevel for OS %s: %v", runtime.GOOS, err) } } }) diff --git a/cpuinfo_linux.go b/cpuinfo_linux.go index 98c7001..549a89b 100644 --- a/cpuinfo_linux.go +++ b/cpuinfo_linux.go @@ -106,12 +106,12 @@ func getCPUVariantFromArch(arch string) (string, error) { return variant, nil } -// getCPUVariant returns cpu variant for ARM +// getArmCPUVariant returns cpu variant for ARM // We first try reading "Cpu architecture" field from /proc/cpuinfo // If we can't find it, then fall back using a system call // This is to cover running ARM in emulated environment on x86 host as this field in /proc/cpuinfo // was not present. -func getCPUVariant() (string, error) { +func getArmCPUVariant() (string, error) { variant, err := getCPUInfo("Cpu architecture") if err != nil { if errors.Is(err, errNotFound) { @@ -158,3 +158,40 @@ func getCPUVariant() (string, error) { return variant, nil } + +func getAmd64MicroArchLevel() (string, error) { + flags, err := getCPUInfo("flags") + if errors.Is(err, errNotFound) { + return "", fmt.Errorf("failure getting CPU flags: %v", err) + } + + containsAll := func(set map[string]interface{}, toMatch []string) bool { + for _, m := range toMatch { + if _, ok := set[m]; !ok { + return false + } + } + return true + } + + flagSet := map[string]interface{}{} + for _, flag := range strings.Split(flags, " ") { + flagSet[flag] = true + } + + // https://unix.stackexchange.com/questions/631217/how-do-i-check-if-my-cpu-supports-x86-64-v2 + level := 1 + if containsAll(flagSet, []string{"lm", "cmov", "cx8", "fpu", "fxsr", "mmx", "syscall", "sse2"}) { + level = 1 + } + if level == 1 && containsAll(flagSet, []string{"cx16", "lahf_lm", "popcnt", "sse4_1", "sse4_2", "ssse3"}) { + level = 2 + } + if level == 2 && containsAll(flagSet, []string{"avx", "avx2", "bmi1", "bmi2", "f16c", "fma", "abm", "movbe", "xsave"}) { + level = 3 + } + if level == 3 && containsAll(flagSet, []string{"avx512f", "avx512bw", "avx512cd", "avx512dq", "avx512vl"}) { + level = 4 + } + return fmt.Sprintf("v%d", level), nil +} diff --git a/cpuinfo_linux_test.go b/cpuinfo_linux_test.go index ecb1150..728484e 100644 --- a/cpuinfo_linux_test.go +++ b/cpuinfo_linux_test.go @@ -29,7 +29,7 @@ func TestCPUVariant(t *testing.T) { variants := []string{"v8", "v7", "v6", "v5", "v4", "v3"} - p, err := getCPUVariant() + p, err := getArmCPUVariant() if err != nil { t.Fatalf("Error getting CPU variant: %v", err) return @@ -137,3 +137,26 @@ func TestGetCPUVariantFromArch(t *testing.T) { } } + +func TestGetAmd64MicroArchLevel(t *testing.T) { + if !isAmd64Arch(runtime.GOARCH) { + t.Skip("only relevant on linux/amd64") + } + + supportedLevels := []string{"v1", "v2", "v3", "v4"} + + actualLevel, err := getAmd64MicroArchLevel() + if err != nil { + t.Fatalf("Error getting AMD64 micro architecture level: %v", err) + return + } + + for _, level := range supportedLevels { + if actualLevel == level { + t.Logf("got valid micro architecture level as expected: %#v = %#v", actualLevel, level) + return + } + } + + t.Fatalf("could not get valid micro architecture levels as expected: %v", supportedLevels) +} diff --git a/cpuinfo_other.go b/cpuinfo_other.go index 97a1fe8..d0104b9 100644 --- a/cpuinfo_other.go +++ b/cpuinfo_other.go @@ -23,7 +23,7 @@ import ( "runtime" ) -func getCPUVariant() (string, error) { +func getArmCPUVariant() (string, error) { var variant string @@ -48,8 +48,13 @@ func getCPUVariant() (string, error) { variant = "unknown" } } else { - return "", fmt.Errorf("getCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented) + return "", fmt.Errorf("getArmCPUVariant for OS %s: %v", runtime.GOOS, errNotImplemented) } return variant, nil } + +func getAmd64MicroArchLevel() (string, error) { + // return v1 on non-Linux platforms + return "v1", nil +} diff --git a/database.go b/database.go index 2e26fd3..a48a92a 100644 --- a/database.go +++ b/database.go @@ -48,6 +48,13 @@ func isArmArch(arch string) bool { return false } +// isAmd64Arch returns true if the architecture is AMD64. +// +// The arch value should be normalized before being passed to this function. +func isAmd64Arch(arch string) bool { + return arch == "amd64" +} + // isKnownArch returns true if we know about the architecture. // // The arch value should be normalized before being passed to this function. diff --git a/defaults_unix.go b/defaults_unix.go index 44acc47..38fb7f0 100644 --- a/defaults_unix.go +++ b/defaults_unix.go @@ -29,7 +29,7 @@ func DefaultSpec() specs.Platform { return specs.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, - // The Variant field will be empty if arch != ARM. + // The Variant field will be empty if arch != ARM and AMD64. Variant: cpuVariant(), } } diff --git a/platforms_test.go b/platforms_test.go index c2af021..b267c24 100644 --- a/platforms_test.go +++ b/platforms_test.go @@ -248,6 +248,15 @@ func TestParseSelector(t *testing.T) { }, formatted: "linux/amd64", }, + { + input: "Linux/x86_64/v2", + expected: specs.Platform{ + OS: "linux", + Architecture: "amd64", + Variant: "v2", + }, + formatted: "linux/amd64/v2", + }, { input: "i386", expected: specs.Platform{