From 7250e5b951539667dc13acf22195c360f51c347b Mon Sep 17 00:00:00 2001 From: vpachkov Date: Mon, 20 Sep 2021 19:34:37 +0300 Subject: [PATCH] runtime: check a microarchitecture level at startup Make Go runtime throw if it's been compiled to assume instruction set extensions that aren't available on the CPU. This feature is originally suggested by mdempsky in #45453 and #25489. Updates #45453 --- src/cmd/compile/internal/gc/obj.go | 20 +++++++++++ src/cmd/link/internal/ld/data.go | 28 +++++++++++++++ src/cmd/link/internal/ld/main.go | 2 ++ src/internal/cpu/cpu.go | 46 +++++++++++++++---------- src/internal/cpu/cpu_x86.go | 55 +++++++++++++++++++++--------- src/runtime/proc.go | 48 +++++++++++++++++++++++++- 6 files changed, 164 insertions(+), 35 deletions(-) diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index aae7d03ebe247c..099a976616e3ec 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -17,8 +17,10 @@ import ( "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/objabi" + "cmd/internal/sys" "encoding/json" "fmt" + "internal/buildcfg" ) // These modes say which kind of object file to generate. @@ -162,6 +164,8 @@ func dumpdata() { if newNumPTabs != numPTabs { base.Fatalf("ptabs changed after compile functions loop") } + + addMicroarch() } func dumpLinkerObj(bout *bio.Writer) { @@ -264,6 +268,22 @@ func addGCLocals() { } } +// addMicroarch creates a special symbol pkgpath + "_microarch" +// which is used by the linker to recreate information about +// microarchitecture level that the object was compiled for. +// see ld/data.go - microarch +func addMicroarch() { + // For now only AMD64 supports microaritecture level configuration. + // It's done with GOAMD64 enviromnent variable + if base.Ctxt.Arch.Family != sys.AMD64 { + return + } + + microarchSym := base.Ctxt.Lookup(base.Ctxt.Pkgpath + "._microarch") + base.Ctxt.Data = append(base.Ctxt.Data, microarchSym) + microarchSym.WriteInt(base.Ctxt, 0, 1, int64(buildcfg.GOAMD64)) +} + func ggloblnod(nam *ir.Name) { s := nam.Linksym() s.Gotype = reflectdata.TypeLinksym(nam.Type()) diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index 1898ee020cbd24..9b3dea55766b34 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -2199,6 +2199,34 @@ func (ctxt *Link) buildinfo() { r.SetSym(ldr.LookupOrCreateSym("runtime.modinfo", 0)) } +// microarch creates a special symbol runtime.microarch that's used by runtime to +// check a micraarchitecture level that the program was compiled for. +// see runtime/proc.go - checkmicroarch +func (ctxt *Link) microarch() { + ldr := ctxt.loader + s := ldr.CreateSymForUpdate("runtime.microarch", 0) + + var microarchLvl uint8 = 1 + for pkgname, _ := range ctxt.LibraryByPkg { + // find the microarctitecture level that the package was compiled for + microarchSym := ldr.Lookup(pkgname+"._microarch", 0) + if microarchSym == 0 { + continue + } + d := ldr.Data(microarchSym) + pkgMicroarchLvl := d[0] + if pkgMicroarchLvl > microarchLvl { + microarchLvl = pkgMicroarchLvl + } + } + + data := make([]byte, 1) + data[0] = microarchLvl + s.SetData(data) + s.SetSize(int64(len(data))) + s.SetType(sym.SDATA) +} + // assign addresses to text func (ctxt *Link) textaddress() { addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05) diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go index 33b03b50248972..0b608721cc064a 100644 --- a/src/cmd/link/internal/ld/main.go +++ b/src/cmd/link/internal/ld/main.go @@ -309,6 +309,8 @@ func Main(arch *sys.Arch, theArch Arch) { ctxt.typelink() bench.Start("buildinfo") ctxt.buildinfo() + bench.Start("microarch") + ctxt.microarch() bench.Start("pclntab") containers := ctxt.findContainerSyms() pclnState := ctxt.pclntab(containers) diff --git a/src/internal/cpu/cpu.go b/src/internal/cpu/cpu.go index 4f0c5d2896bfa9..fc9b2a31c3e9bd 100644 --- a/src/internal/cpu/cpu.go +++ b/src/internal/cpu/cpu.go @@ -24,24 +24,34 @@ var CacheLineSize uintptr = CacheLinePadSize // in addition to the cpuid feature bit being set. // The struct is padded to avoid false sharing. var X86 struct { - _ CacheLinePad - HasAES bool - HasADX bool - HasAVX bool - HasAVX2 bool - HasBMI1 bool - HasBMI2 bool - HasERMS bool - HasFMA bool - HasOSXSAVE bool - HasPCLMULQDQ bool - HasPOPCNT bool - HasRDTSCP bool - HasSSE3 bool - HasSSSE3 bool - HasSSE41 bool - HasSSE42 bool - _ CacheLinePad + _ CacheLinePad + HasABM bool + HasAES bool + HasADX bool + HasAVX bool + HasAVX2 bool + HasAVX512F bool + HasAVX512BW bool + HasAVX512CD bool + HasAVX512DQ bool + HasAVX512VL bool + HasBMI1 bool + HasBMI2 bool + HasCMPXCHG16B bool + HasERMS bool + HasFMA bool + HasF16C bool + HasLAHF bool + HasMOVBE bool + HasOSXSAVE bool + HasPCLMULQDQ bool + HasPOPCNT bool + HasRDTSCP bool + HasSSE3 bool + HasSSSE3 bool + HasSSE41 bool + HasSSE42 bool + _ CacheLinePad } // The booleans in ARM contain the correspondingly named cpu feature bit. diff --git a/src/internal/cpu/cpu_x86.go b/src/internal/cpu/cpu_x86.go index 1582e832a45a54..eb932bec2e0ef2 100644 --- a/src/internal/cpu/cpu_x86.go +++ b/src/internal/cpu/cpu_x86.go @@ -20,26 +20,38 @@ const ( cpuid_SSE2 = 1 << 26 // ecx bits - cpuid_SSE3 = 1 << 0 - cpuid_PCLMULQDQ = 1 << 1 - cpuid_SSSE3 = 1 << 9 - cpuid_FMA = 1 << 12 - cpuid_SSE41 = 1 << 19 - cpuid_SSE42 = 1 << 20 - cpuid_POPCNT = 1 << 23 - cpuid_AES = 1 << 25 - cpuid_OSXSAVE = 1 << 27 - cpuid_AVX = 1 << 28 + cpuid_SSE3 = 1 << 0 + cpuid_PCLMULQDQ = 1 << 1 + cpuid_SSSE3 = 1 << 9 + cpuid_FMA = 1 << 12 + cpuid_CMPXCHG16B = 1 << 13 + cpuid_SSE41 = 1 << 19 + cpuid_SSE42 = 1 << 20 + cpuid_MOVBE = 1 << 22 + cpuid_POPCNT = 1 << 23 + cpuid_AES = 1 << 25 + cpuid_OSXSAVE = 1 << 27 + cpuid_AVX = 1 << 28 + cpuid_F16C = 1 << 29 // ebx bits - cpuid_BMI1 = 1 << 3 - cpuid_AVX2 = 1 << 5 - cpuid_BMI2 = 1 << 8 - cpuid_ERMS = 1 << 9 - cpuid_ADX = 1 << 19 + cpuid_BMI1 = 1 << 3 + cpuid_AVX2 = 1 << 5 + cpuid_BMI2 = 1 << 8 + cpuid_ERMS = 1 << 9 + cpuid_ADX = 1 << 19 + cpuid_AVX512F = 1 << 16 + cpuid_AVX512DQ = 1 << 17 + cpuid_AVX512CD = 1 << 28 + cpuid_AVX512BW = 1 << 30 + cpuid_AVX512VL = 1 << 31 // edx bits for CPUID 0x80000001 cpuid_RDTSCP = 1 << 27 + + // ecx bits for CPUID 0x80000001 + cpuid_LAHF = 1 << 0 + cpuid_ABM = 1 << 5 ) var maxExtendedFunctionInformation uint32 @@ -80,6 +92,9 @@ func doinit() { X86.HasSSE42 = isSet(ecx1, cpuid_SSE42) X86.HasPOPCNT = isSet(ecx1, cpuid_POPCNT) X86.HasAES = isSet(ecx1, cpuid_AES) + X86.HasMOVBE = isSet(ecx1, cpuid_MOVBE) + X86.HasCMPXCHG16B = isSet(ecx1, cpuid_CMPXCHG16B) + X86.HasF16C = isSet(ecx1, cpuid_F16C) // OSXSAVE can be false when using older Operating Systems // or when explicitly disabled on newer Operating Systems by @@ -112,6 +127,11 @@ func doinit() { X86.HasBMI2 = isSet(ebx7, cpuid_BMI2) X86.HasERMS = isSet(ebx7, cpuid_ERMS) X86.HasADX = isSet(ebx7, cpuid_ADX) + X86.HasAVX512F = isSet(ebx7, cpuid_AVX512F) + X86.HasAVX512BW = isSet(ebx7, cpuid_AVX512BW) + X86.HasAVX512CD = isSet(ebx7, cpuid_AVX512CD) + X86.HasAVX512DQ = isSet(ebx7, cpuid_AVX512DQ) + X86.HasAVX512VL = isSet(ebx7, cpuid_AVX512VL) var maxExtendedInformation uint32 maxExtendedInformation, _, _, _ = cpuid(0x80000000, 0) @@ -120,8 +140,11 @@ func doinit() { return } - _, _, _, edxExt1 := cpuid(0x80000001, 0) + _, _, ecxExt1, edxExt1 := cpuid(0x80000001, 0) X86.HasRDTSCP = isSet(edxExt1, cpuid_RDTSCP) + + X86.HasLAHF = isSet(ecxExt1, cpuid_LAHF) + X86.HasABM = isSet(ecxExt1, cpuid_ABM) } func isSet(hwc uint32, value uint32) bool { diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 605e1330000864..d887a7e874c9ed 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -636,6 +636,51 @@ func cpuinit() { } } +//go:linkname microarch runtime.microarch +var microarch uint8 + +// checkmicroarch checks if a platform supports a micraarchitecture level that the program was compiled for. +// https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels - more about AMD64 micraarchitecture levels +func checkmicroarch() { + missmatchHandler := func(platform int) { + println("Could not run this program because it was compiled for microarhitecture level", microarch) + println("But platform microarhitecture level is less than", platform) + throw("Microarhitecture level missmatch") + } + + if goarch.IsAmd64 > 0 { + x86 := cpu.X86 + if microarch >= 2 { + if !(x86.HasPOPCNT && + x86.HasCMPXCHG16B && + x86.HasLAHF && + x86.HasPOPCNT && + x86.HasSSE3 && + x86.HasSSE41 && + x86.HasSSE42 && + x86.HasSSSE3) { + missmatchHandler(2) + } + } + if microarch >= 3 { + if !(x86.HasAVX && x86.HasAVX2 && + x86.HasBMI1 && x86.HasBMI2 && + x86.HasF16C && + x86.HasFMA && + x86.HasABM && // for LZCNT + x86.HasMOVBE && + x86.HasOSXSAVE) { + missmatchHandler(3) + } + } + if microarch >= 4 { + if !(x86.HasAVX512F && x86.HasAVX512BW && x86.HasAVX512CD && x86.HasAVX512DQ && x86.HasAVX512VL) { + missmatchHandler(4) + } + } + } +} + // The bootstrap sequence is: // // call osinit @@ -682,7 +727,8 @@ func schedinit() { mallocinit() fastrandinit() // must run before mcommoninit mcommoninit(_g_.m, -1) - cpuinit() // must run before alginit + cpuinit() // must run before alginit + checkmicroarch() alginit() // maps must not be used before this call modulesinit() // provides activeModules typelinksinit() // uses maps, activeModules