blob: cfe4a56001438a515190c38e2378271f4a8d6b1c [file]
// 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[:])
}