Skip to content

Commit 7250e5b

Browse files
committed
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
1 parent 7d67f8d commit 7250e5b

File tree

6 files changed

+164
-35
lines changed

6 files changed

+164
-35
lines changed

src/cmd/compile/internal/gc/obj.go

+20
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ import (
1717
"cmd/internal/bio"
1818
"cmd/internal/obj"
1919
"cmd/internal/objabi"
20+
"cmd/internal/sys"
2021
"encoding/json"
2122
"fmt"
23+
"internal/buildcfg"
2224
)
2325

2426
// These modes say which kind of object file to generate.
@@ -162,6 +164,8 @@ func dumpdata() {
162164
if newNumPTabs != numPTabs {
163165
base.Fatalf("ptabs changed after compile functions loop")
164166
}
167+
168+
addMicroarch()
165169
}
166170

167171
func dumpLinkerObj(bout *bio.Writer) {
@@ -264,6 +268,22 @@ func addGCLocals() {
264268
}
265269
}
266270

271+
// addMicroarch creates a special symbol pkgpath + "_microarch"
272+
// which is used by the linker to recreate information about
273+
// microarchitecture level that the object was compiled for.
274+
// see ld/data.go - microarch
275+
func addMicroarch() {
276+
// For now only AMD64 supports microaritecture level configuration.
277+
// It's done with GOAMD64 enviromnent variable
278+
if base.Ctxt.Arch.Family != sys.AMD64 {
279+
return
280+
}
281+
282+
microarchSym := base.Ctxt.Lookup(base.Ctxt.Pkgpath + "._microarch")
283+
base.Ctxt.Data = append(base.Ctxt.Data, microarchSym)
284+
microarchSym.WriteInt(base.Ctxt, 0, 1, int64(buildcfg.GOAMD64))
285+
}
286+
267287
func ggloblnod(nam *ir.Name) {
268288
s := nam.Linksym()
269289
s.Gotype = reflectdata.TypeLinksym(nam.Type())

src/cmd/link/internal/ld/data.go

+28
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,34 @@ func (ctxt *Link) buildinfo() {
21992199
r.SetSym(ldr.LookupOrCreateSym("runtime.modinfo", 0))
22002200
}
22012201

2202+
// microarch creates a special symbol runtime.microarch that's used by runtime to
2203+
// check a micraarchitecture level that the program was compiled for.
2204+
// see runtime/proc.go - checkmicroarch
2205+
func (ctxt *Link) microarch() {
2206+
ldr := ctxt.loader
2207+
s := ldr.CreateSymForUpdate("runtime.microarch", 0)
2208+
2209+
var microarchLvl uint8 = 1
2210+
for pkgname, _ := range ctxt.LibraryByPkg {
2211+
// find the microarctitecture level that the package was compiled for
2212+
microarchSym := ldr.Lookup(pkgname+"._microarch", 0)
2213+
if microarchSym == 0 {
2214+
continue
2215+
}
2216+
d := ldr.Data(microarchSym)
2217+
pkgMicroarchLvl := d[0]
2218+
if pkgMicroarchLvl > microarchLvl {
2219+
microarchLvl = pkgMicroarchLvl
2220+
}
2221+
}
2222+
2223+
data := make([]byte, 1)
2224+
data[0] = microarchLvl
2225+
s.SetData(data)
2226+
s.SetSize(int64(len(data)))
2227+
s.SetType(sym.SDATA)
2228+
}
2229+
22022230
// assign addresses to text
22032231
func (ctxt *Link) textaddress() {
22042232
addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05)

src/cmd/link/internal/ld/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@ func Main(arch *sys.Arch, theArch Arch) {
309309
ctxt.typelink()
310310
bench.Start("buildinfo")
311311
ctxt.buildinfo()
312+
bench.Start("microarch")
313+
ctxt.microarch()
312314
bench.Start("pclntab")
313315
containers := ctxt.findContainerSyms()
314316
pclnState := ctxt.pclntab(containers)

src/internal/cpu/cpu.go

+28-18
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,34 @@ var CacheLineSize uintptr = CacheLinePadSize
2424
// in addition to the cpuid feature bit being set.
2525
// The struct is padded to avoid false sharing.
2626
var X86 struct {
27-
_ CacheLinePad
28-
HasAES bool
29-
HasADX bool
30-
HasAVX bool
31-
HasAVX2 bool
32-
HasBMI1 bool
33-
HasBMI2 bool
34-
HasERMS bool
35-
HasFMA bool
36-
HasOSXSAVE bool
37-
HasPCLMULQDQ bool
38-
HasPOPCNT bool
39-
HasRDTSCP bool
40-
HasSSE3 bool
41-
HasSSSE3 bool
42-
HasSSE41 bool
43-
HasSSE42 bool
44-
_ CacheLinePad
27+
_ CacheLinePad
28+
HasABM bool
29+
HasAES bool
30+
HasADX bool
31+
HasAVX bool
32+
HasAVX2 bool
33+
HasAVX512F bool
34+
HasAVX512BW bool
35+
HasAVX512CD bool
36+
HasAVX512DQ bool
37+
HasAVX512VL bool
38+
HasBMI1 bool
39+
HasBMI2 bool
40+
HasCMPXCHG16B bool
41+
HasERMS bool
42+
HasFMA bool
43+
HasF16C bool
44+
HasLAHF bool
45+
HasMOVBE bool
46+
HasOSXSAVE bool
47+
HasPCLMULQDQ bool
48+
HasPOPCNT bool
49+
HasRDTSCP bool
50+
HasSSE3 bool
51+
HasSSSE3 bool
52+
HasSSE41 bool
53+
HasSSE42 bool
54+
_ CacheLinePad
4555
}
4656

4757
// The booleans in ARM contain the correspondingly named cpu feature bit.

src/internal/cpu/cpu_x86.go

+39-16
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,38 @@ const (
2020
cpuid_SSE2 = 1 << 26
2121

2222
// ecx bits
23-
cpuid_SSE3 = 1 << 0
24-
cpuid_PCLMULQDQ = 1 << 1
25-
cpuid_SSSE3 = 1 << 9
26-
cpuid_FMA = 1 << 12
27-
cpuid_SSE41 = 1 << 19
28-
cpuid_SSE42 = 1 << 20
29-
cpuid_POPCNT = 1 << 23
30-
cpuid_AES = 1 << 25
31-
cpuid_OSXSAVE = 1 << 27
32-
cpuid_AVX = 1 << 28
23+
cpuid_SSE3 = 1 << 0
24+
cpuid_PCLMULQDQ = 1 << 1
25+
cpuid_SSSE3 = 1 << 9
26+
cpuid_FMA = 1 << 12
27+
cpuid_CMPXCHG16B = 1 << 13
28+
cpuid_SSE41 = 1 << 19
29+
cpuid_SSE42 = 1 << 20
30+
cpuid_MOVBE = 1 << 22
31+
cpuid_POPCNT = 1 << 23
32+
cpuid_AES = 1 << 25
33+
cpuid_OSXSAVE = 1 << 27
34+
cpuid_AVX = 1 << 28
35+
cpuid_F16C = 1 << 29
3336

3437
// ebx bits
35-
cpuid_BMI1 = 1 << 3
36-
cpuid_AVX2 = 1 << 5
37-
cpuid_BMI2 = 1 << 8
38-
cpuid_ERMS = 1 << 9
39-
cpuid_ADX = 1 << 19
38+
cpuid_BMI1 = 1 << 3
39+
cpuid_AVX2 = 1 << 5
40+
cpuid_BMI2 = 1 << 8
41+
cpuid_ERMS = 1 << 9
42+
cpuid_ADX = 1 << 19
43+
cpuid_AVX512F = 1 << 16
44+
cpuid_AVX512DQ = 1 << 17
45+
cpuid_AVX512CD = 1 << 28
46+
cpuid_AVX512BW = 1 << 30
47+
cpuid_AVX512VL = 1 << 31
4048

4149
// edx bits for CPUID 0x80000001
4250
cpuid_RDTSCP = 1 << 27
51+
52+
// ecx bits for CPUID 0x80000001
53+
cpuid_LAHF = 1 << 0
54+
cpuid_ABM = 1 << 5
4355
)
4456

4557
var maxExtendedFunctionInformation uint32
@@ -80,6 +92,9 @@ func doinit() {
8092
X86.HasSSE42 = isSet(ecx1, cpuid_SSE42)
8193
X86.HasPOPCNT = isSet(ecx1, cpuid_POPCNT)
8294
X86.HasAES = isSet(ecx1, cpuid_AES)
95+
X86.HasMOVBE = isSet(ecx1, cpuid_MOVBE)
96+
X86.HasCMPXCHG16B = isSet(ecx1, cpuid_CMPXCHG16B)
97+
X86.HasF16C = isSet(ecx1, cpuid_F16C)
8398

8499
// OSXSAVE can be false when using older Operating Systems
85100
// or when explicitly disabled on newer Operating Systems by
@@ -112,6 +127,11 @@ func doinit() {
112127
X86.HasBMI2 = isSet(ebx7, cpuid_BMI2)
113128
X86.HasERMS = isSet(ebx7, cpuid_ERMS)
114129
X86.HasADX = isSet(ebx7, cpuid_ADX)
130+
X86.HasAVX512F = isSet(ebx7, cpuid_AVX512F)
131+
X86.HasAVX512BW = isSet(ebx7, cpuid_AVX512BW)
132+
X86.HasAVX512CD = isSet(ebx7, cpuid_AVX512CD)
133+
X86.HasAVX512DQ = isSet(ebx7, cpuid_AVX512DQ)
134+
X86.HasAVX512VL = isSet(ebx7, cpuid_AVX512VL)
115135

116136
var maxExtendedInformation uint32
117137
maxExtendedInformation, _, _, _ = cpuid(0x80000000, 0)
@@ -120,8 +140,11 @@ func doinit() {
120140
return
121141
}
122142

123-
_, _, _, edxExt1 := cpuid(0x80000001, 0)
143+
_, _, ecxExt1, edxExt1 := cpuid(0x80000001, 0)
124144
X86.HasRDTSCP = isSet(edxExt1, cpuid_RDTSCP)
145+
146+
X86.HasLAHF = isSet(ecxExt1, cpuid_LAHF)
147+
X86.HasABM = isSet(ecxExt1, cpuid_ABM)
125148
}
126149

127150
func isSet(hwc uint32, value uint32) bool {

src/runtime/proc.go

+47-1
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,51 @@ func cpuinit() {
636636
}
637637
}
638638

639+
//go:linkname microarch runtime.microarch
640+
var microarch uint8
641+
642+
// checkmicroarch checks if a platform supports a micraarchitecture level that the program was compiled for.
643+
// https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels - more about AMD64 micraarchitecture levels
644+
func checkmicroarch() {
645+
missmatchHandler := func(platform int) {
646+
println("Could not run this program because it was compiled for microarhitecture level", microarch)
647+
println("But platform microarhitecture level is less than", platform)
648+
throw("Microarhitecture level missmatch")
649+
}
650+
651+
if goarch.IsAmd64 > 0 {
652+
x86 := cpu.X86
653+
if microarch >= 2 {
654+
if !(x86.HasPOPCNT &&
655+
x86.HasCMPXCHG16B &&
656+
x86.HasLAHF &&
657+
x86.HasPOPCNT &&
658+
x86.HasSSE3 &&
659+
x86.HasSSE41 &&
660+
x86.HasSSE42 &&
661+
x86.HasSSSE3) {
662+
missmatchHandler(2)
663+
}
664+
}
665+
if microarch >= 3 {
666+
if !(x86.HasAVX && x86.HasAVX2 &&
667+
x86.HasBMI1 && x86.HasBMI2 &&
668+
x86.HasF16C &&
669+
x86.HasFMA &&
670+
x86.HasABM && // for LZCNT
671+
x86.HasMOVBE &&
672+
x86.HasOSXSAVE) {
673+
missmatchHandler(3)
674+
}
675+
}
676+
if microarch >= 4 {
677+
if !(x86.HasAVX512F && x86.HasAVX512BW && x86.HasAVX512CD && x86.HasAVX512DQ && x86.HasAVX512VL) {
678+
missmatchHandler(4)
679+
}
680+
}
681+
}
682+
}
683+
639684
// The bootstrap sequence is:
640685
//
641686
// call osinit
@@ -682,7 +727,8 @@ func schedinit() {
682727
mallocinit()
683728
fastrandinit() // must run before mcommoninit
684729
mcommoninit(_g_.m, -1)
685-
cpuinit() // must run before alginit
730+
cpuinit() // must run before alginit
731+
checkmicroarch()
686732
alginit() // maps must not be used before this call
687733
modulesinit() // provides activeModules
688734
typelinksinit() // uses maps, activeModules

0 commit comments

Comments
 (0)