родитель
8568d4f625
коммит
6812a4dc04
2 изменённых файлов: 156 добавлений и 28 удалений
|
@ -10,6 +10,13 @@ import (
|
|||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// constants for hashmap algorithms; must match src/runtime/hashmap.go
|
||||
const (
|
||||
hashmapAlgorithmBinary = iota
|
||||
hashmapAlgorithmString
|
||||
hashmapAlgorithmInterface
|
||||
)
|
||||
|
||||
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
|
||||
// initializing an appropriately sized object.
|
||||
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
|
||||
|
@ -17,22 +24,27 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
|
|||
keyType := mapType.Key().Underlying()
|
||||
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
|
||||
var llvmKeyType llvm.Type
|
||||
var alg uint64 // must match values in src/runtime/hashmap.go
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// String keys.
|
||||
llvmKeyType = b.getLLVMType(keyType)
|
||||
alg = hashmapAlgorithmString
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// Trivially comparable keys.
|
||||
llvmKeyType = b.getLLVMType(keyType)
|
||||
alg = hashmapAlgorithmBinary
|
||||
} else {
|
||||
// All other keys. Implemented as map[interface{}]valueType for ease of
|
||||
// implementation.
|
||||
llvmKeyType = b.getLLVMRuntimeType("_interface")
|
||||
alg = hashmapAlgorithmInterface
|
||||
}
|
||||
keySize := b.targetData.TypeAllocSize(llvmKeyType)
|
||||
valueSize := b.targetData.TypeAllocSize(llvmValueType)
|
||||
llvmKeySize := llvm.ConstInt(b.ctx.Int8Type(), keySize, false)
|
||||
llvmValueSize := llvm.ConstInt(b.ctx.Int8Type(), valueSize, false)
|
||||
sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
|
||||
algEnum := llvm.ConstInt(b.ctx.Int8Type(), alg, false)
|
||||
if expr.Reserve != nil {
|
||||
sizeHint = b.getValue(expr.Reserve)
|
||||
var err error
|
||||
|
@ -41,7 +53,7 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
|
|||
return llvm.Value{}, err
|
||||
}
|
||||
}
|
||||
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
|
||||
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint, algEnum}, "")
|
||||
return hashmap, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -12,14 +12,23 @@ import (
|
|||
|
||||
// The underlying hashmap structure for Go.
|
||||
type hashmap struct {
|
||||
next *hashmap // hashmap after evacuate (for iterators)
|
||||
buckets unsafe.Pointer // pointer to array of buckets
|
||||
count uintptr
|
||||
keySize uint8 // maybe this can store the key type as well? E.g. keysize == 5 means string?
|
||||
valueSize uint8
|
||||
bucketBits uint8
|
||||
keyEqual func(x, y unsafe.Pointer, n uintptr) bool
|
||||
keyHash func(key unsafe.Pointer, size uintptr) uint32
|
||||
}
|
||||
|
||||
type hashmapAlgorithm uint8
|
||||
|
||||
const (
|
||||
hashmapAlgorithmBinary hashmapAlgorithm = iota
|
||||
hashmapAlgorithmString
|
||||
hashmapAlgorithmInterface
|
||||
)
|
||||
|
||||
// A hashmap bucket. A bucket is a container of 8 key/value pairs: first the
|
||||
// following two entries, then the 8 keys, then the 8 values. This somewhat odd
|
||||
// ordering is to make sure the keys and values are well aligned when one of
|
||||
|
@ -32,6 +41,8 @@ type hashmapBucket struct {
|
|||
}
|
||||
|
||||
type hashmapIterator struct {
|
||||
buckets unsafe.Pointer
|
||||
numBuckets uintptr
|
||||
bucketNumber uintptr
|
||||
bucket *hashmapBucket
|
||||
bucketIndex uint8
|
||||
|
@ -48,7 +59,7 @@ func hashmapTopHash(hash uint32) uint8 {
|
|||
}
|
||||
|
||||
// Create a new hashmap with the given keySize and valueSize.
|
||||
func hashmapMake(keySize, valueSize uint8, sizeHint uintptr) *hashmap {
|
||||
func hashmapMake(keySize, valueSize uint8, sizeHint uintptr, alg uint8) *hashmap {
|
||||
numBuckets := sizeHint / 8
|
||||
bucketBits := uint8(0)
|
||||
for numBuckets != 0 {
|
||||
|
@ -57,14 +68,58 @@ func hashmapMake(keySize, valueSize uint8, sizeHint uintptr) *hashmap {
|
|||
}
|
||||
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(keySize)*8 + uintptr(valueSize)*8
|
||||
buckets := alloc(bucketBufSize*(1<<bucketBits), nil)
|
||||
|
||||
keyHash := hashmapKeyHashAlg(hashmapAlgorithm(alg))
|
||||
keyEqual := hashmapKeyEqualAlg(hashmapAlgorithm(alg))
|
||||
|
||||
return &hashmap{
|
||||
buckets: buckets,
|
||||
keySize: keySize,
|
||||
valueSize: valueSize,
|
||||
bucketBits: bucketBits,
|
||||
keyEqual: keyEqual,
|
||||
keyHash: keyHash,
|
||||
}
|
||||
}
|
||||
|
||||
func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool {
|
||||
switch alg {
|
||||
case hashmapAlgorithmBinary:
|
||||
return memequal
|
||||
case hashmapAlgorithmString:
|
||||
return hashmapStringEqual
|
||||
case hashmapAlgorithmInterface:
|
||||
return hashmapInterfaceEqual
|
||||
default:
|
||||
panic("unknown hashmap equal algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
func hashmapKeyHashAlg(alg hashmapAlgorithm) func(key unsafe.Pointer, n uintptr) uint32 {
|
||||
switch alg {
|
||||
case hashmapAlgorithmBinary:
|
||||
return hash32
|
||||
case hashmapAlgorithmString:
|
||||
return hashmapStringPtrHash
|
||||
case hashmapAlgorithmInterface:
|
||||
return hashmapInterfacePtrHash
|
||||
default:
|
||||
panic("unknown hashmap hash algorithm")
|
||||
}
|
||||
}
|
||||
|
||||
func hashmapShouldGrow(m *hashmap) bool {
|
||||
if m.bucketBits == 32 {
|
||||
// can't grow any more
|
||||
return false
|
||||
}
|
||||
|
||||
// "maximum" number of elements is 0.75 * buckets * elements per bucket
|
||||
max := uintptr((3 * ((1 << m.bucketBits) * 8)) / 4)
|
||||
|
||||
return m.count > max
|
||||
}
|
||||
|
||||
// Return the number of entries in this hashmap, called from the len builtin.
|
||||
// A nil hashmap is defined as having length 0.
|
||||
//go:inline
|
||||
|
@ -83,7 +138,7 @@ func hashmapLenUnsafePointer(p unsafe.Pointer) int {
|
|||
|
||||
// Set a specified key to a given value. Grow the map if necessary.
|
||||
//go:nobounds
|
||||
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
||||
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32) {
|
||||
tophash := hashmapTopHash(hash)
|
||||
|
||||
if m.buckets == nil {
|
||||
|
@ -92,6 +147,10 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
|||
return
|
||||
}
|
||||
|
||||
if hashmapShouldGrow(m) {
|
||||
hashmapGrow(m)
|
||||
}
|
||||
|
||||
numBuckets := uintptr(1) << m.bucketBits
|
||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||
|
@ -118,7 +177,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
|||
}
|
||||
if bucket.tophash[i] == tophash {
|
||||
// Could be an existing key that's the same.
|
||||
if keyEqual(key, slotKey, uintptr(m.keySize)) {
|
||||
if m.keyEqual(key, slotKey, uintptr(m.keySize)) {
|
||||
// found same key, replace it
|
||||
memcpy(slotValue, value, uintptr(m.valueSize))
|
||||
return
|
||||
|
@ -158,9 +217,35 @@ func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash u
|
|||
return bucket
|
||||
}
|
||||
|
||||
func hashmapGrow(m *hashmap) {
|
||||
|
||||
// clone map as empty
|
||||
n := *m
|
||||
n.count = 0
|
||||
|
||||
// allocate our new buckets twice as big
|
||||
n.bucketBits = m.bucketBits + 1
|
||||
numBuckets := uintptr(1) << n.bucketBits
|
||||
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||
n.buckets = alloc(bucketBufSize*numBuckets, nil)
|
||||
|
||||
// use a hashmap iterator to go through the old map
|
||||
var it hashmapIterator
|
||||
|
||||
var key = alloc(uintptr(m.keySize), nil)
|
||||
var value = alloc(uintptr(m.valueSize), nil)
|
||||
|
||||
for hashmapNext(m, &it, key, value) {
|
||||
h := m.keyHash(key, uintptr(m.keySize))
|
||||
hashmapSet(&n, key, value, h)
|
||||
}
|
||||
|
||||
*m = n
|
||||
}
|
||||
|
||||
// Get the value of a specified key, or zero the value if not found.
|
||||
//go:nobounds
|
||||
func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) bool {
|
||||
func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash uint32) bool {
|
||||
if m == nil {
|
||||
// Getting a value out of a nil map is valid. From the spec:
|
||||
// > if the map is nil or does not contain such an entry, a[x] is the
|
||||
|
@ -189,7 +274,7 @@ func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash u
|
|||
slotValue := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotValueOffset)
|
||||
if bucket.tophash[i] == tophash {
|
||||
// This could be the key we're looking for.
|
||||
if keyEqual(key, slotKey, uintptr(m.keySize)) {
|
||||
if m.keyEqual(key, slotKey, uintptr(m.keySize)) {
|
||||
// Found the key, copy it.
|
||||
memcpy(value, slotValue, uintptr(m.valueSize))
|
||||
return true
|
||||
|
@ -207,7 +292,7 @@ func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash u
|
|||
// Delete a given key from the map. No-op when the key does not exist in the
|
||||
// map.
|
||||
//go:nobounds
|
||||
func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
||||
func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32) {
|
||||
if m == nil {
|
||||
// The delete builtin is defined even when the map is nil. From the spec:
|
||||
// > If the map m is nil or the element m[k] does not exist, delete is a
|
||||
|
@ -233,7 +318,7 @@ func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32, keyEqual func(x,
|
|||
slotKey := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotKeyOffset)
|
||||
if bucket.tophash[i] == tophash {
|
||||
// This could be the key we're looking for.
|
||||
if keyEqual(key, slotKey, uintptr(m.keySize)) {
|
||||
if m.keyEqual(key, slotKey, uintptr(m.keySize)) {
|
||||
// Found the key, delete it.
|
||||
bucket.tophash[i] = 0
|
||||
m.count--
|
||||
|
@ -249,13 +334,16 @@ func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32, keyEqual func(x,
|
|||
//go:nobounds
|
||||
func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) bool {
|
||||
if m == nil {
|
||||
// Iterating over a nil slice appears to be allowed by the Go spec:
|
||||
// https://groups.google.com/g/golang-nuts/c/gVgVLQU1FFE?pli=1
|
||||
// https://play.golang.org/p/S8jxAMytKDB
|
||||
// From the spec: If the map is nil, the number of iterations is 0.
|
||||
return false
|
||||
}
|
||||
|
||||
numBuckets := uintptr(1) << m.bucketBits
|
||||
if it.buckets == nil {
|
||||
// initialize iterator
|
||||
it.buckets = m.buckets
|
||||
it.numBuckets = uintptr(1) << m.bucketBits
|
||||
}
|
||||
|
||||
for {
|
||||
if it.bucketIndex >= 8 {
|
||||
// end of bucket, move to the next in the chain
|
||||
|
@ -263,12 +351,12 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
|
|||
it.bucket = it.bucket.next
|
||||
}
|
||||
if it.bucket == nil {
|
||||
if it.bucketNumber >= numBuckets {
|
||||
if it.bucketNumber >= it.numBuckets {
|
||||
// went through all buckets
|
||||
return false
|
||||
}
|
||||
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||
bucketAddr := uintptr(m.buckets) + bucketSize*it.bucketNumber
|
||||
bucketAddr := uintptr(it.buckets) + bucketSize*it.bucketNumber
|
||||
it.bucket = (*hashmapBucket)(unsafe.Pointer(bucketAddr))
|
||||
it.bucketNumber++ // next bucket
|
||||
}
|
||||
|
@ -281,11 +369,29 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
|
|||
bucketAddr := uintptr(unsafe.Pointer(it.bucket))
|
||||
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(it.bucketIndex)
|
||||
slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset)
|
||||
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(it.bucketIndex)
|
||||
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
|
||||
memcpy(key, slotKey, uintptr(m.keySize))
|
||||
memcpy(value, slotValue, uintptr(m.valueSize))
|
||||
it.bucketIndex++
|
||||
|
||||
if it.buckets == m.buckets {
|
||||
// Our view of the buckets is the same as the parent map.
|
||||
// Just copy the value we have
|
||||
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(it.bucketIndex)
|
||||
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
|
||||
memcpy(value, slotValue, uintptr(m.valueSize))
|
||||
it.bucketIndex++
|
||||
} else {
|
||||
it.bucketIndex++
|
||||
|
||||
// Our view of the buckets doesn't match the parent map.
|
||||
// Look up the key in the new buckets and return that value if it exists
|
||||
hash := m.keyHash(key, uintptr(m.keySize))
|
||||
ok := hashmapGet(m, key, value, uintptr(m.valueSize), hash)
|
||||
if !ok {
|
||||
// doesn't exist in parent map; try next key
|
||||
continue
|
||||
}
|
||||
|
||||
// All good.
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -296,7 +402,7 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
|
|||
func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
|
||||
// TODO: detect nil map here and throw a better panic message?
|
||||
hash := hash32(key, uintptr(m.keySize))
|
||||
hashmapSet(m, key, value, hash, memequal)
|
||||
hashmapSet(m, key, value, hash)
|
||||
}
|
||||
|
||||
func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool {
|
||||
|
@ -305,7 +411,7 @@ func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr)
|
|||
return false
|
||||
}
|
||||
hash := hash32(key, uintptr(m.keySize))
|
||||
return hashmapGet(m, key, value, valueSize, hash, memequal)
|
||||
return hashmapGet(m, key, value, valueSize, hash)
|
||||
}
|
||||
|
||||
func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
|
||||
|
@ -313,7 +419,7 @@ func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
|
|||
return
|
||||
}
|
||||
hash := hash32(key, uintptr(m.keySize))
|
||||
hashmapDelete(m, key, hash, memequal)
|
||||
hashmapDelete(m, key, hash)
|
||||
}
|
||||
|
||||
// Hashmap with string keys (a common case).
|
||||
|
@ -327,19 +433,24 @@ func hashmapStringHash(s string) uint32 {
|
|||
return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length))
|
||||
}
|
||||
|
||||
func hashmapStringPtrHash(sptr unsafe.Pointer, size uintptr) uint32 {
|
||||
_s := *(*_string)(sptr)
|
||||
return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length))
|
||||
}
|
||||
|
||||
func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) {
|
||||
hash := hashmapStringHash(key)
|
||||
hashmapSet(m, unsafe.Pointer(&key), value, hash, hashmapStringEqual)
|
||||
hashmapSet(m, unsafe.Pointer(&key), value, hash)
|
||||
}
|
||||
|
||||
func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool {
|
||||
hash := hashmapStringHash(key)
|
||||
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash, hashmapStringEqual)
|
||||
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
|
||||
}
|
||||
|
||||
func hashmapStringDelete(m *hashmap, key string) {
|
||||
hash := hashmapStringHash(key)
|
||||
hashmapDelete(m, unsafe.Pointer(&key), hash, hashmapStringEqual)
|
||||
hashmapDelete(m, unsafe.Pointer(&key), hash)
|
||||
}
|
||||
|
||||
// Hashmap with interface keys (for everything else).
|
||||
|
@ -427,21 +538,26 @@ func hashmapInterfaceHash(itf interface{}) uint32 {
|
|||
}
|
||||
}
|
||||
|
||||
func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr) uint32 {
|
||||
_i := *(*_interface)(iptr)
|
||||
return hashmapInterfaceHash(_i)
|
||||
}
|
||||
|
||||
func hashmapInterfaceEqual(x, y unsafe.Pointer, n uintptr) bool {
|
||||
return *(*interface{})(x) == *(*interface{})(y)
|
||||
}
|
||||
|
||||
func hashmapInterfaceSet(m *hashmap, key interface{}, value unsafe.Pointer) {
|
||||
hash := hashmapInterfaceHash(key)
|
||||
hashmapSet(m, unsafe.Pointer(&key), value, hash, hashmapInterfaceEqual)
|
||||
hashmapSet(m, unsafe.Pointer(&key), value, hash)
|
||||
}
|
||||
|
||||
func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valueSize uintptr) bool {
|
||||
hash := hashmapInterfaceHash(key)
|
||||
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash, hashmapInterfaceEqual)
|
||||
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
|
||||
}
|
||||
|
||||
func hashmapInterfaceDelete(m *hashmap, key interface{}) {
|
||||
hash := hashmapInterfaceHash(key)
|
||||
hashmapDelete(m, unsafe.Pointer(&key), hash, hashmapInterfaceEqual)
|
||||
hashmapDelete(m, unsafe.Pointer(&key), hash)
|
||||
}
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче