blob: eb7b93b0a263924d86be0db1285eff33d8933278 [file] [log] [blame] [edit]
// Copyright 2025 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.
//go:build 386 || amd64
package cpu
// DataCacheSizes returns the size of each data cache from lowest
// level in the hierarchy to highest.
//
// Unlike other parts of this package's public API, it is not safe
// to reference early in runtime initialization because it allocates.
// It's intended for testing only.
func DataCacheSizes() []uintptr {
maxFunctionInformation, ebx0, ecx0, edx0 := cpuid(0, 0)
if maxFunctionInformation < 1 {
return nil
}
switch {
// Check for "GenuineIntel"
case ebx0 == 0x756E6547 && ecx0 == 0x6C65746E && edx0 == 0x49656E69:
return getDataCacheSizesIntel(maxFunctionInformation)
// Check for "AuthenticAMD"
case ebx0 == 0x68747541 && ecx0 == 0x444D4163 && edx0 == 0x69746E65:
return getDataCacheSizesAMD()
}
return nil
}
func extractBits(arg uint32, l int, r int) uint32 {
if l > r {
panic("bad bit range")
}
return (arg >> l) & ((1 << (r - l + 1)) - 1)
}
func getDataCacheSizesIntel(maxID uint32) []uintptr {
// Constants for cache types
const (
noCache = 0
dataCache = 1
instructionCache = 2
unifiedCache = 3
)
if maxID < 4 {
return nil
}
// Iterate through CPUID leaf 4 (deterministic cache parameters)
var caches []uintptr
for i := uint32(0); i < 0xFFFF; i++ {
eax, ebx, ecx, _ := cpuid(4, i)
cacheType := eax & 0xF // EAX bits 4-0: Cache Type
if cacheType == 0 {
break
}
// Report only data caches.
if !(cacheType == dataCache || cacheType == unifiedCache) {
continue
}
// Guaranteed to always start counting from 1.
level := (eax >> 5) & 0x7
lineSize := extractBits(ebx, 0, 11) + 1 // Bits 11-0: Line size in bytes - 1
partitions := extractBits(ebx, 12, 21) + 1 // Bits 21-12: Physical line partitions - 1
ways := extractBits(ebx, 22, 31) + 1 // Bits 31-22: Ways of associativity - 1
sets := uint64(ecx) + 1 // Number of sets - 1
size := uint64(ways*partitions*lineSize) * sets // Calculate cache size in bytes
caches = append(caches, uintptr(size))
// If we see more than one cache described per level, or they appear
// out of order, crash.
//
// Going by the SDM, it's not clear whether this is actually possible,
// so this code is purely defensive.
if level != uint32(len(caches)) {
panic("expected levels to be in order and for there to be one data/unified cache per level")
}
}
return caches
}
func getDataCacheSizesAMD() []uintptr {
maxExtendedFunctionInformation, _, _, _ := cpuid(0x80000000, 0)
if maxExtendedFunctionInformation < 0x80000006 {
return nil
}
var caches []uintptr
_, _, ecx5, _ := cpuid(0x80000005, 0)
_, _, ecx6, edx6 := cpuid(0x80000006, 0)
// The size is return in kb, turning into bytes.
l1dSize := uintptr(extractBits(ecx5, 24, 31) << 10)
caches = append(caches, l1dSize)
// Check that L2 cache is present.
if l2Assoc := extractBits(ecx6, 12, 15); l2Assoc == 0 {
return caches
}
l2Size := uintptr(extractBits(ecx6, 16, 31) << 10)
caches = append(caches, l2Size)
// Check that L3 cache is present.
if l3Assoc := extractBits(edx6, 12, 15); l3Assoc == 0 {
return caches
}
// Specifies the L3 cache size is within the following range:
// (L3Size[31:18] * 512KB) <= L3 cache size < ((L3Size[31:18]+1) * 512KB).
l3Size := uintptr(extractBits(edx6, 18, 31) * (512 << 10))
caches = append(caches, l3Size)
return caches
}