Skip to content

Commit

Permalink
Add AMD64 micro architecture level support
Browse files Browse the repository at this point in the history
This commit adds the support for AMD64 micro architecture levels
on Linux.
  • Loading branch information
laozc committed Mar 18, 2024
1 parent db76a43 commit 0870598
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 9 deletions.
13 changes: 10 additions & 3 deletions cpuinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
}
}
})
Expand Down
41 changes: 39 additions & 2 deletions cpuinfo_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
25 changes: 24 additions & 1 deletion cpuinfo_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
9 changes: 7 additions & 2 deletions cpuinfo_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
"runtime"
)

func getCPUVariant() (string, error) {
func getArmCPUVariant() (string, error) {

var variant string

Expand All @@ -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
}
7 changes: 7 additions & 0 deletions database.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion defaults_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}
}
Expand Down
9 changes: 9 additions & 0 deletions platforms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down

0 comments on commit 0870598

Please sign in to comment.