| // 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 |
| } |