mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-09 13:33:39 +00:00
Preliminary implementation of a hashmap, unfinished
Missing features: * keys other than strings * more than 8 values in the hashmap * growing a map when needed * initial size hint * delete(m, key) * iterators (for range) * initializing global maps * ...more?
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
package runtime
|
||||
|
||||
// This is a hashmap implementation for the map[T]T type.
|
||||
// It is very rougly based on the implementation of the Go hashmap:
|
||||
//
|
||||
// https://golang.org/src/runtime/hashmap.go
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// 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 uint
|
||||
keySize uint8 // maybe this can store the key type as well? E.g. keysize == 5 means string?
|
||||
valueSize uint8
|
||||
bucketBits uint8
|
||||
}
|
||||
|
||||
// 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
|
||||
// them is smaller than the system word size.
|
||||
type hashmapBucket struct {
|
||||
tophash [8]uint8
|
||||
next *hashmapBucket // next bucket (if there are more than 8 in a chain)
|
||||
// Followed by the actual keys, and then the actual values. These are
|
||||
// allocated but as they're of variable size they can't be shown here.
|
||||
}
|
||||
|
||||
// Get FNV-1a hash of this string.
|
||||
//
|
||||
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
|
||||
func stringhash(s *string) uint32 {
|
||||
var result uint32 = 2166136261 // FNV offset basis
|
||||
for i := 0; i < len(*s); i++ {
|
||||
result ^= uint32((*s)[i])
|
||||
result *= 16777619 // FNV prime
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Set a specified key to a given value. Grow the map if necessary.
|
||||
func hashmapSet(m *hashmap, key string, value unsafe.Pointer) {
|
||||
hash := stringhash(&key)
|
||||
numBuckets := uintptr(1) << m.bucketBits
|
||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
|
||||
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
|
||||
|
||||
tophash := uint8(hash >> 24)
|
||||
if tophash < 1 {
|
||||
// 0 means empty slot, so make it bigger.
|
||||
tophash += 1
|
||||
}
|
||||
|
||||
// See whether the key already exists somewhere.
|
||||
var emptySlotKey *string
|
||||
var emptySlotValue unsafe.Pointer
|
||||
var emptySlotTophash *byte
|
||||
for bucket != nil {
|
||||
for i := uintptr(0); i < 8; i++ {
|
||||
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
|
||||
slotKey := (*string)(unsafe.Pointer(bucketAddr + slotKeyOffset))
|
||||
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
|
||||
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
|
||||
if bucket.tophash[i] == 0 && emptySlotKey == nil {
|
||||
// Found an empty slot, store it for if we couldn't find an
|
||||
// existing slot.
|
||||
emptySlotKey = slotKey
|
||||
emptySlotValue = slotValue
|
||||
emptySlotTophash = &bucket.tophash[i]
|
||||
}
|
||||
if bucket.tophash[i] == tophash {
|
||||
// Could be an existing value that's the same.
|
||||
if key == *slotKey {
|
||||
// found same key, replace it
|
||||
memcpy(slotValue, value, uintptr(m.valueSize))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
bucket = bucket.next
|
||||
}
|
||||
if emptySlotKey != nil {
|
||||
*emptySlotKey = key
|
||||
memcpy(emptySlotValue, value, uintptr(m.valueSize))
|
||||
*emptySlotTophash = tophash
|
||||
return
|
||||
}
|
||||
panic("todo: hashmap: grow bucket")
|
||||
}
|
||||
|
||||
// Get the value of a specified key, or zero the value if not found.
|
||||
func hashmapGet(m *hashmap, key string, value unsafe.Pointer) {
|
||||
hash := stringhash(&key)
|
||||
numBuckets := uintptr(1) << m.bucketBits
|
||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||
bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber
|
||||
bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr))
|
||||
|
||||
tophash := uint8(hash >> 24)
|
||||
if tophash < 1 {
|
||||
// 0 means empty slot, so make it bigger.
|
||||
tophash += 1
|
||||
}
|
||||
|
||||
// Try to find the key.
|
||||
for bucket != nil {
|
||||
for i := uintptr(0); i < 8; i++ {
|
||||
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i)
|
||||
slotKey := (*string)(unsafe.Pointer(bucketAddr + slotKeyOffset))
|
||||
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i)
|
||||
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
|
||||
if bucket.tophash[i] == tophash {
|
||||
// This could be the key we're looking for.
|
||||
if key == *slotKey {
|
||||
// Found the key, copy it.
|
||||
memcpy(value, slotValue, uintptr(m.valueSize))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
bucket = bucket.next
|
||||
}
|
||||
|
||||
// Did not find the key.
|
||||
memzero(value, uintptr(m.valueSize))
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const Compiler = "tgo"
|
||||
|
||||
// The bitness of the CPU (e.g. 8, 32, 64). Set by the compiler as a constant.
|
||||
@@ -29,6 +33,20 @@ func stringequal(x, y string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Copy size bytes from src to dst. The memory areas must not overlap.
|
||||
func memcpy(dst, src unsafe.Pointer, size uintptr) {
|
||||
for i := uintptr(0); i < size; i++ {
|
||||
*(*uint8)(unsafe.Pointer(uintptr(dst) + i)) = *(*uint8)(unsafe.Pointer(uintptr(src) + i))
|
||||
}
|
||||
}
|
||||
|
||||
// Set the given number of bytes to zero.
|
||||
func memzero(ptr unsafe.Pointer, size uintptr) {
|
||||
for i := uintptr(0); i < size; i++ {
|
||||
*(*byte)(unsafe.Pointer(uintptr(ptr) + size)) = 0
|
||||
}
|
||||
}
|
||||
|
||||
func _panic(message interface{}) {
|
||||
printstring("panic: ")
|
||||
printitf(message)
|
||||
|
||||
Reference in New Issue
Block a user