Skip to content

Commit

Permalink
runtime: check a microarchitecture level at startup
Browse files Browse the repository at this point in the history
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 golang#45453 and golang#25489.
Updates golang#45453
  • Loading branch information
vpachkov committed Sep 21, 2021
1 parent 7d67f8d commit 7250e5b
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 35 deletions.
20 changes: 20 additions & 0 deletions src/cmd/compile/internal/gc/obj.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -162,6 +164,8 @@ func dumpdata() {
if newNumPTabs != numPTabs {
base.Fatalf("ptabs changed after compile functions loop")
}

addMicroarch()
}

func dumpLinkerObj(bout *bio.Writer) {
Expand Down Expand Up @@ -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())
Expand Down
28 changes: 28 additions & 0 deletions src/cmd/link/internal/ld/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions src/cmd/link/internal/ld/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
46 changes: 28 additions & 18 deletions src/internal/cpu/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
55 changes: 39 additions & 16 deletions src/internal/cpu/cpu_x86.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
48 changes: 47 additions & 1 deletion src/runtime/proc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 7250e5b

Please sign in to comment.