cpu: add GODEBUG options to disable use of instruction set extensions

The GODEBUG environment variable can be used to disable usage of
specific processor features of Go programs that use the sys/cpu package.
This is useful for testing and benchmarking different code paths that
are guarded by sys/cpu variable checks.

Use of processor features can not be enabled through GODEBUG.

To disable usage of AVX and SSE41 cpu features on GOARCH amd64 use:
GODEBUG=cpu.avx=off,cpu.sse41=off

The special "all" option can be used to disable all options:
GODEBUG=all=off

This aligns the support of GODEBUG for sys/cpu with existing support
for GODEBUG in the Go standard library package internal/cpu.

Fixes golang/go#33963

Change-Id: I618b71af397bf06c57a49b2a300d032a16d05664
Reviewed-on: https://go-review.googlesource.com/c/sys/+/245237
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Martin Möhrmann <moehrmann@google.com>
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
diff --git a/cpu/cpu.go b/cpu/cpu.go
index e44deb7..5cce25e 100644
--- a/cpu/cpu.go
+++ b/cpu/cpu.go
@@ -6,6 +6,11 @@
 // various CPU architectures.
 package cpu
 
+import (
+	"os"
+	"strings"
+)
+
 // Initialized reports whether the CPU features were initialized.
 //
 // For some GOOS/GOARCH combinations initialization of the CPU features depends
@@ -169,3 +174,94 @@
 	HasVXE    bool // vector-enhancements facility 1
 	_         CacheLinePad
 }
+
+func init() {
+	archInit()
+	initOptions()
+	processOptions()
+}
+
+// options contains the cpu debug options that can be used in GODEBUG.
+// Options are arch dependent and are added by the arch specific initOptions functions.
+// Features that are mandatory for the specific GOARCH should have the Required field set
+// (e.g. SSE2 on amd64).
+var options []option
+
+// Option names should be lower case. e.g. avx instead of AVX.
+type option struct {
+	Name      string
+	Feature   *bool
+	Specified bool // whether feature value was specified in GODEBUG
+	Enable    bool // whether feature should be enabled
+	Required  bool // whether feature is mandatory and can not be disabled
+}
+
+func processOptions() {
+	env := os.Getenv("GODEBUG")
+field:
+	for env != "" {
+		field := ""
+		i := strings.IndexByte(env, ',')
+		if i < 0 {
+			field, env = env, ""
+		} else {
+			field, env = env[:i], env[i+1:]
+		}
+		if len(field) < 4 || field[:4] != "cpu." {
+			continue
+		}
+		i = strings.IndexByte(field, '=')
+		if i < 0 {
+			print("GODEBUG sys/cpu: no value specified for \"", field, "\"\n")
+			continue
+		}
+		key, value := field[4:i], field[i+1:] // e.g. "SSE2", "on"
+
+		var enable bool
+		switch value {
+		case "on":
+			enable = true
+		case "off":
+			enable = false
+		default:
+			print("GODEBUG sys/cpu: value \"", value, "\" not supported for cpu option \"", key, "\"\n")
+			continue field
+		}
+
+		if key == "all" {
+			for i := range options {
+				options[i].Specified = true
+				options[i].Enable = enable || options[i].Required
+			}
+			continue field
+		}
+
+		for i := range options {
+			if options[i].Name == key {
+				options[i].Specified = true
+				options[i].Enable = enable
+				continue field
+			}
+		}
+
+		print("GODEBUG sys/cpu: unknown cpu feature \"", key, "\"\n")
+	}
+
+	for _, o := range options {
+		if !o.Specified {
+			continue
+		}
+
+		if o.Enable && !*o.Feature {
+			print("GODEBUG sys/cpu: can not enable \"", o.Name, "\", missing CPU support\n")
+			continue
+		}
+
+		if !o.Enable && o.Required {
+			print("GODEBUG sys/cpu: can not disable \"", o.Name, "\", required CPU feature\n")
+			continue
+		}
+
+		*o.Feature = o.Enable
+	}
+}
diff --git a/cpu/cpu_aix.go b/cpu/cpu_aix.go
index da29896..464a209 100644
--- a/cpu/cpu_aix.go
+++ b/cpu/cpu_aix.go
@@ -6,8 +6,6 @@
 
 package cpu
 
-const cacheLineSize = 128
-
 const (
 	// getsystemcfg constants
 	_SC_IMPL     = 2
@@ -15,7 +13,7 @@
 	_IMPL_POWER9 = 0x20000
 )
 
-func init() {
+func archInit() {
 	impl := getsystemcfg(_SC_IMPL)
 	if impl&_IMPL_POWER8 != 0 {
 		PPC64.IsPOWER8 = true
diff --git a/cpu/cpu_arm.go b/cpu/cpu_arm.go
index 981af68..301b752 100644
--- a/cpu/cpu_arm.go
+++ b/cpu/cpu_arm.go
@@ -38,3 +38,36 @@
 	hwcap2_SHA2  = 1 << 3
 	hwcap2_CRC32 = 1 << 4
 )
+
+func initOptions() {
+	options = []option{
+		{Name: "pmull", Feature: &ARM.HasPMULL},
+		{Name: "sha1", Feature: &ARM.HasSHA1},
+		{Name: "sha2", Feature: &ARM.HasSHA2},
+		{Name: "swp", Feature: &ARM.HasSWP},
+		{Name: "thumb", Feature: &ARM.HasTHUMB},
+		{Name: "thumbee", Feature: &ARM.HasTHUMBEE},
+		{Name: "tls", Feature: &ARM.HasTLS},
+		{Name: "vfp", Feature: &ARM.HasVFP},
+		{Name: "vfpd32", Feature: &ARM.HasVFPD32},
+		{Name: "vfpv3", Feature: &ARM.HasVFPv3},
+		{Name: "vfpv3d16", Feature: &ARM.HasVFPv3D16},
+		{Name: "vfpv4", Feature: &ARM.HasVFPv4},
+		{Name: "half", Feature: &ARM.HasHALF},
+		{Name: "26bit", Feature: &ARM.Has26BIT},
+		{Name: "fastmul", Feature: &ARM.HasFASTMUL},
+		{Name: "fpa", Feature: &ARM.HasFPA},
+		{Name: "edsp", Feature: &ARM.HasEDSP},
+		{Name: "java", Feature: &ARM.HasJAVA},
+		{Name: "iwmmxt", Feature: &ARM.HasIWMMXT},
+		{Name: "crunch", Feature: &ARM.HasCRUNCH},
+		{Name: "neon", Feature: &ARM.HasNEON},
+		{Name: "idivt", Feature: &ARM.HasIDIVT},
+		{Name: "idiva", Feature: &ARM.HasIDIVA},
+		{Name: "lpae", Feature: &ARM.HasLPAE},
+		{Name: "evtstrm", Feature: &ARM.HasEVTSTRM},
+		{Name: "aes", Feature: &ARM.HasAES},
+		{Name: "crc32", Feature: &ARM.HasCRC32},
+	}
+
+}
diff --git a/cpu/cpu_arm64.go b/cpu/cpu_arm64.go
index 7bcb36c..2d90024 100644
--- a/cpu/cpu_arm64.go
+++ b/cpu/cpu_arm64.go
@@ -8,7 +8,36 @@
 
 const cacheLineSize = 64
 
-func init() {
+func initOptions() {
+	options = []option{
+		{Name: "fp", Feature: &ARM64.HasFP},
+		{Name: "asimd", Feature: &ARM64.HasASIMD},
+		{Name: "evstrm", Feature: &ARM64.HasEVTSTRM},
+		{Name: "aes", Feature: &ARM64.HasAES},
+		{Name: "fphp", Feature: &ARM64.HasFPHP},
+		{Name: "jscvt", Feature: &ARM64.HasJSCVT},
+		{Name: "lrcpc", Feature: &ARM64.HasLRCPC},
+		{Name: "pmull", Feature: &ARM64.HasPMULL},
+		{Name: "sha1", Feature: &ARM64.HasSHA1},
+		{Name: "sha2", Feature: &ARM64.HasSHA2},
+		{Name: "sha3", Feature: &ARM64.HasSHA3},
+		{Name: "sha512", Feature: &ARM64.HasSHA512},
+		{Name: "sm3", Feature: &ARM64.HasSM3},
+		{Name: "sm4", Feature: &ARM64.HasSM4},
+		{Name: "sve", Feature: &ARM64.HasSVE},
+		{Name: "crc32", Feature: &ARM64.HasCRC32},
+		{Name: "atomics", Feature: &ARM64.HasATOMICS},
+		{Name: "asimdhp", Feature: &ARM64.HasASIMDHP},
+		{Name: "cpuid", Feature: &ARM64.HasCPUID},
+		{Name: "asimrdm", Feature: &ARM64.HasASIMDRDM},
+		{Name: "fcma", Feature: &ARM64.HasFCMA},
+		{Name: "dcpop", Feature: &ARM64.HasDCPOP},
+		{Name: "asimddp", Feature: &ARM64.HasASIMDDP},
+		{Name: "asimdfhm", Feature: &ARM64.HasASIMDFHM},
+	}
+}
+
+func archInit() {
 	switch runtime.GOOS {
 	case "android", "darwin", "netbsd":
 		// Android and iOS don't seem to allow reading these registers.
diff --git a/cpu/cpu_linux.go b/cpu/cpu_linux.go
index fe13918..6fc874f 100644
--- a/cpu/cpu_linux.go
+++ b/cpu/cpu_linux.go
@@ -6,7 +6,7 @@
 
 package cpu
 
-func init() {
+func archInit() {
 	if err := readHWCAP(); err != nil {
 		return
 	}
diff --git a/cpu/cpu_linux_ppc64x.go b/cpu/cpu_linux_ppc64x.go
index 6c8d975..99f8a63 100644
--- a/cpu/cpu_linux_ppc64x.go
+++ b/cpu/cpu_linux_ppc64x.go
@@ -7,8 +7,6 @@
 
 package cpu
 
-const cacheLineSize = 128
-
 // HWCAP/HWCAP2 bits. These are exposed by the kernel.
 const (
 	// ISA Level
diff --git a/cpu/cpu_linux_s390x.go b/cpu/cpu_linux_s390x.go
index d579eae..b88d6b8 100644
--- a/cpu/cpu_linux_s390x.go
+++ b/cpu/cpu_linux_s390x.go
@@ -4,8 +4,6 @@
 
 package cpu
 
-const cacheLineSize = 256
-
 const (
 	// bit mask values from /usr/include/bits/hwcap.h
 	hwcap_ZARCH  = 2
diff --git a/cpu/cpu_mips64x.go b/cpu/cpu_mips64x.go
index 6165f12..57b5b67 100644
--- a/cpu/cpu_mips64x.go
+++ b/cpu/cpu_mips64x.go
@@ -7,3 +7,9 @@
 package cpu
 
 const cacheLineSize = 32
+
+func initOptions() {
+	options = []option{
+		{Name: "msa", Feature: &MIPS64X.HasMSA},
+	}
+}
diff --git a/cpu/cpu_mipsx.go b/cpu/cpu_mipsx.go
index 1269eee..cfc1946 100644
--- a/cpu/cpu_mipsx.go
+++ b/cpu/cpu_mipsx.go
@@ -7,3 +7,5 @@
 package cpu
 
 const cacheLineSize = 32
+
+func initOptions() {}
diff --git a/cpu/cpu_other_arm.go b/cpu/cpu_other_arm.go
new file mode 100644
index 0000000..b412efc
--- /dev/null
+++ b/cpu/cpu_other_arm.go
@@ -0,0 +1,9 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !linux,arm
+
+package cpu
+
+func archInit() {}
diff --git a/cpu/cpu_ppc64x.go b/cpu/cpu_ppc64x.go
new file mode 100644
index 0000000..d28d675
--- /dev/null
+++ b/cpu/cpu_ppc64x.go
@@ -0,0 +1,16 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ppc64 ppc64le
+
+package cpu
+
+const cacheLineSize = 128
+
+func initOptions() {
+	options = []option{
+		{Name: "darn", Feature: &PPC64.HasDARN},
+		{Name: "scv", Feature: &PPC64.HasSCV},
+	}
+}
diff --git a/cpu/cpu_riscv64.go b/cpu/cpu_riscv64.go
index efe2b7a..8b08de3 100644
--- a/cpu/cpu_riscv64.go
+++ b/cpu/cpu_riscv64.go
@@ -7,3 +7,5 @@
 package cpu
 
 const cacheLineSize = 32
+
+func initOptions() {}
diff --git a/cpu/cpu_s390x.go b/cpu/cpu_s390x.go
new file mode 100644
index 0000000..544cd62
--- /dev/null
+++ b/cpu/cpu_s390x.go
@@ -0,0 +1,30 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cpu
+
+const cacheLineSize = 256
+
+func initOptions() {
+	options = []option{
+		{Name: "zarch", Feature: &S390X.HasZARCH},
+		{Name: "stfle", Feature: &S390X.HasSTFLE},
+		{Name: "ldisp", Feature: &S390X.HasLDISP},
+		{Name: "eimm", Feature: &S390X.HasEIMM},
+		{Name: "dfp", Feature: &S390X.HasDFP},
+		{Name: "etf3eh", Feature: &S390X.HasETF3EH},
+		{Name: "msa", Feature: &S390X.HasMSA},
+		{Name: "aes", Feature: &S390X.HasAES},
+		{Name: "aescbc", Feature: &S390X.HasAESCBC},
+		{Name: "aesctr", Feature: &S390X.HasAESCTR},
+		{Name: "aesgcm", Feature: &S390X.HasAESGCM},
+		{Name: "ghash", Feature: &S390X.HasGHASH},
+		{Name: "sha1", Feature: &S390X.HasSHA1},
+		{Name: "sha256", Feature: &S390X.HasSHA256},
+		{Name: "sha3", Feature: &S390X.HasSHA3},
+		{Name: "sha512", Feature: &S390X.HasSHA512},
+		{Name: "vx", Feature: &S390X.HasVX},
+		{Name: "vxe", Feature: &S390X.HasVXE},
+	}
+}
diff --git a/cpu/cpu_wasm.go b/cpu/cpu_wasm.go
index 8681e87..5382f2a 100644
--- a/cpu/cpu_wasm.go
+++ b/cpu/cpu_wasm.go
@@ -11,3 +11,7 @@
 // rules are good enough.
 
 const cacheLineSize = 0
+
+func initOptions() {}
+
+func archInit() {}
diff --git a/cpu/cpu_x86.go b/cpu/cpu_x86.go
index d70d317..2ad039d 100644
--- a/cpu/cpu_x86.go
+++ b/cpu/cpu_x86.go
@@ -6,9 +6,37 @@
 
 package cpu
 
+import "runtime"
+
 const cacheLineSize = 64
 
-func init() {
+func initOptions() {
+	options = []option{
+		{Name: "adx", Feature: &X86.HasADX},
+		{Name: "aes", Feature: &X86.HasAES},
+		{Name: "avx", Feature: &X86.HasAVX},
+		{Name: "avx2", Feature: &X86.HasAVX2},
+		{Name: "bmi1", Feature: &X86.HasBMI1},
+		{Name: "bmi2", Feature: &X86.HasBMI2},
+		{Name: "erms", Feature: &X86.HasERMS},
+		{Name: "fma", Feature: &X86.HasFMA},
+		{Name: "osxsave", Feature: &X86.HasOSXSAVE},
+		{Name: "pclmulqdq", Feature: &X86.HasPCLMULQDQ},
+		{Name: "popcnt", Feature: &X86.HasPOPCNT},
+		{Name: "rdrand", Feature: &X86.HasRDRAND},
+		{Name: "rdseed", Feature: &X86.HasRDSEED},
+		{Name: "sse3", Feature: &X86.HasSSE3},
+		{Name: "sse41", Feature: &X86.HasSSE41},
+		{Name: "sse42", Feature: &X86.HasSSE42},
+		{Name: "ssse3", Feature: &X86.HasSSSE3},
+
+		// These capabilities should always be enabled on amd64:
+		{Name: "sse2", Feature: &X86.HasSSE2, Required: runtime.GOARCH == "amd64"},
+	}
+}
+
+func archInit() {
+
 	Initialized = true
 
 	maxID, _, _, _ := cpuid(0, 0)
@@ -52,6 +80,7 @@
 	X86.HasERMS = isSet(9, ebx7)
 	X86.HasRDSEED = isSet(18, ebx7)
 	X86.HasADX = isSet(19, ebx7)
+
 }
 
 func isSet(bitpos uint, value uint32) bool {