| // Copyright 2026 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. |
| |
| package maps |
| |
| import ( |
| "internal/byteorder" |
| "internal/cpu" |
| "internal/goarch" |
| "unsafe" |
| ) |
| |
| // MinAeshashSize is the smallest key size hashed with the AES-based |
| // implementation. Selecting hash is a size comparison against this value. |
| // Setting this to MaxUintptr disables AES altogether. |
| // |
| // When support is detected, the threshold is lowered to select between the |
| // scalar-based fallback hash and the vector-based AES hash. This value is |
| // selected on a per-platform basis based on what value produces the best |
| // benchmark results. |
| // |
| // Scalar hashes are faster on small values because it avoids taking a trip |
| // into the vector unit, which hurts latency (and for very small values, |
| // throughput). |
| var MinAeshashSize uintptr = ^uintptr(0) |
| |
| // AeshashEnabled reports whether this machine hashes any sizes with AES. |
| // |
| // Test-only; compare against MinAeshashSize in non-test code to fuse this |
| // comparison with the MinAeshashSize check. |
| func AeshashEnabled() bool { |
| return MinAeshashSize != ^uintptr(0) |
| } |
| |
| const hashRandomBytes = goarch.PtrSize / 4 * 64 |
| |
| // used to seed the hash function |
| var aeskeysched [hashRandomBytes]byte |
| |
| // used in hash{32,64}.go to seed the hash function |
| var hashkey [4]uintptr |
| |
| // Pre-computed comparisons against MinAeshashSize, which reduces a |
| // load-and-compare-and-branch to a load-and-branch. |
| var ( |
| useAeshash32 bool // = MinAeshashSize <= 4 |
| useAeshash64 bool // = MinAeshashSize <= 8 |
| ) |
| |
| func AlgInit() { |
| // Always intialize hashkey. |
| // |
| // See #78073 |
| for i := range hashkey { |
| hashkey[i] = uintptr(bootstrapRand()) |
| } |
| |
| // Install AES hash algorithms if the instructions needed are present. |
| if (goarch.GOARCH == "386" || goarch.GOARCH == "amd64") && |
| cpu.X86.HasAES && // AESENC |
| cpu.X86.HasSSSE3 && // PSHUFB |
| cpu.X86.HasSSE41 { // PINSR{D,Q} |
| |
| // In memHashAES we have global variables that should be properly aligned. |
| // |
| // See #12415 |
| if !checkMasksAndShiftsAlignment() { |
| fatal("maps: global variables for AES hashing are not properly aligned!") |
| } |
| initAlgAES() |
| |
| if memHashUsesVAES && !cpu.X86.HasAVX { |
| // We are using intrinsics hash implementation. |
| // Override the UseAeshash in this case, since it uses VAES (AVX) instructions. |
| // While assembly implementation used AES-NI instructions, |
| // simd intrinsics only provide access to AVX ones. |
| MinAeshashSize = ^uintptr(0) |
| } |
| } else if goarch.GOARCH == "arm64" && cpu.ARM64.HasAES { |
| initAlgAES() |
| } |
| |
| useAeshash32 = 4 >= MinAeshashSize |
| useAeshash64 = 8 >= MinAeshashSize |
| } |
| |
| func initAlgAES() { |
| // TODO(mcy): investigate cutoffs on a per-uarch basis. |
| // See memhash_bench_test.go. |
| switch goarch.ArchFamily { |
| case goarch.AMD64: |
| // Measured on AMD Ryzen Threadripper PRO 7995WX (Zen4). |
| MinAeshashSize = 9 |
| default: |
| MinAeshashSize = 0 |
| } |
| |
| // Initialize with random data so hash collisions will be hard to engineer. |
| key := (*[hashRandomBytes / 8]uint64)(unsafe.Pointer(&aeskeysched)) |
| for i := range key { |
| key[i] = bootstrapRand() |
| } |
| } |
| |
| //go:nosplit |
| func add(p unsafe.Pointer, x uintptr) unsafe.Pointer { |
| return unsafe.Pointer(uintptr(p) + x) |
| } |
| |
| // Note: These routines perform the read with a native endianness. |
| func readUnaligned32(p unsafe.Pointer) uint32 { |
| q := (*[4]byte)(p) |
| if goarch.BigEndian { |
| return byteorder.BEUint32(q[:]) |
| } |
| return byteorder.LEUint32(q[:]) |
| } |
| |
| func readUnaligned64(p unsafe.Pointer) uint64 { |
| q := (*[8]byte)(p) |
| if goarch.BigEndian { |
| return byteorder.BEUint64(q[:]) |
| } |
| return byteorder.LEUint64(q[:]) |
| } |