diff --git a/compiler.go b/compiler.go index 6216770c..564940bf 100644 --- a/compiler.go +++ b/compiler.go @@ -1100,12 +1100,20 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error { mapType := instr.Map.Type().Underlying().(*types.Map) switch keyType := mapType.Key().Underlying().(type) { case *types.Basic: + valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value") + c.builder.CreateStore(value, valueAlloca) + valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr") if keyType.Kind() == types.String { - valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value") - c.builder.CreateStore(value, valueAlloca) - valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr") params := []llvm.Value{m, key, valuePtr} - fn := c.mod.NamedFunction("runtime.hashmapSet") + fn := c.mod.NamedFunction("runtime.hashmapStringSet") + c.builder.CreateCall(fn, params, "") + return nil + } else if keyType.Info()&(types.IsBoolean|types.IsInteger) != 0 { + keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key") + c.builder.CreateStore(key, keyAlloca) + keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr") + params := []llvm.Value{m, keyPtr, valuePtr} + fn := c.mod.NamedFunction("runtime.hashmapBinarySet") c.builder.CreateCall(fn, params, "") return nil } else { @@ -1588,15 +1596,23 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { case *types.Map: switch keyType := xType.Key().Underlying().(type) { case *types.Basic: + llvmValueType, err := c.getLLVMType(expr.Type()) + if err != nil { + return llvm.Value{}, err + } + mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value") + mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr") if keyType.Kind() == types.String { - llvmValueType, err := c.getLLVMType(expr.Type()) - if err != nil { - return llvm.Value{}, err - } - mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value") - mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr") params := []llvm.Value{value, index, mapValuePtr} - fn := c.mod.NamedFunction("runtime.hashmapGet") + fn := c.mod.NamedFunction("runtime.hashmapStringGet") + c.builder.CreateCall(fn, params, "") + return c.builder.CreateLoad(mapValueAlloca, ""), nil + } else if keyType.Info()&(types.IsBoolean|types.IsInteger) != 0 { + keyAlloca := c.builder.CreateAlloca(index.Type(), "hashmap.key") + c.builder.CreateStore(index, keyAlloca) + keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr") + params := []llvm.Value{value, keyPtr, mapValuePtr} + fn := c.mod.NamedFunction("runtime.hashmapBinaryGet") c.builder.CreateCall(fn, params, "") return c.builder.CreateLoad(mapValueAlloca, ""), nil } else { @@ -1658,22 +1674,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { if err != nil { return llvm.Value{}, err } - switch keyType := mapType.Key().Underlying().(type) { - case *types.Basic: - if keyType.Kind() == types.String { - keySize := c.targetData.TypeAllocSize(llvmKeyType) - valueSize := c.targetData.TypeAllocSize(llvmValueType) - hashmapMake := c.mod.NamedFunction("runtime.hashmapMake") - llvmKeySize := llvm.ConstInt(llvm.Int8Type(), keySize, false) - llvmValueSize := llvm.ConstInt(llvm.Int8Type(), valueSize, false) - hashmap := c.builder.CreateCall(hashmapMake, []llvm.Value{llvmKeySize, llvmValueSize}, "") - return hashmap, nil - } else { - return llvm.Value{}, errors.New("todo: map key type: " + keyType.String()) - } - default: - return llvm.Value{}, errors.New("todo: map key type: " + keyType.String()) - } + keySize := c.targetData.TypeAllocSize(llvmKeyType) + valueSize := c.targetData.TypeAllocSize(llvmValueType) + hashmapMake := c.mod.NamedFunction("runtime.hashmapMake") + llvmKeySize := llvm.ConstInt(llvm.Int8Type(), keySize, false) + llvmValueSize := llvm.ConstInt(llvm.Int8Type(), valueSize, false) + hashmap := c.builder.CreateCall(hashmapMake, []llvm.Value{llvmKeySize, llvmValueSize}, "") + return hashmap, nil case *ssa.Phi: t, err := c.getLLVMType(expr.Type()) if err != nil { diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index cbdb5ede..3d6869ad 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -30,14 +30,15 @@ type hashmapBucket struct { // allocated but as they're of variable size they can't be shown here. } -// Get FNV-1a hash of this string. +// Get FNV-1a hash of this key. // // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash -func stringhash(s *string) uint32 { +func hashmapHash(ptr unsafe.Pointer, n uintptr) uint32 { var result uint32 = 2166136261 // FNV offset basis - for i := 0; i < len(*s); i++ { - result ^= uint32((*s)[i]) - result *= 16777619 // FNV prime + for i := uintptr(0); i < n; i++ { + c := *(*uint8)(unsafe.Pointer(uintptr(ptr) + i)) + result ^= uint32(c) // XOR with byte + result *= 16777619 // FNV prime } return result } @@ -65,8 +66,7 @@ func hashmapMake(keySize, valueSize uint8) *hashmap { } // 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) +func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) { numBuckets := uintptr(1) << m.bucketBits bucketNumber := (uintptr(hash) & (numBuckets - 1)) bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8 @@ -76,13 +76,13 @@ func hashmapSet(m *hashmap, key string, value unsafe.Pointer) { tophash := hashmapTopHash(hash) // See whether the key already exists somewhere. - var emptySlotKey *string + var emptySlotKey unsafe.Pointer 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)) + slotKey := 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 { @@ -94,7 +94,7 @@ func hashmapSet(m *hashmap, key string, value unsafe.Pointer) { } if bucket.tophash[i] == tophash { // Could be an existing value that's the same. - if key == *slotKey { + if keyEqual(key, slotKey, uintptr(m.keySize)) { // found same key, replace it memcpy(slotValue, value, uintptr(m.valueSize)) return @@ -105,7 +105,7 @@ func hashmapSet(m *hashmap, key string, value unsafe.Pointer) { } if emptySlotKey != nil { m.count++ - *emptySlotKey = key + memcpy(emptySlotKey, key, uintptr(m.keySize)) memcpy(emptySlotValue, value, uintptr(m.valueSize)) *emptySlotTophash = tophash return @@ -114,8 +114,7 @@ func hashmapSet(m *hashmap, key string, value unsafe.Pointer) { } // 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) +func hashmapGet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) { numBuckets := uintptr(1) << m.bucketBits bucketNumber := (uintptr(hash) & (numBuckets - 1)) bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8 @@ -132,12 +131,12 @@ func hashmapGet(m *hashmap, key string, value unsafe.Pointer) { 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)) + slotKey := 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 { + if keyEqual(key, slotKey, uintptr(m.keySize)) { // Found the key, copy it. memcpy(value, slotValue, uintptr(m.valueSize)) return @@ -150,3 +149,36 @@ func hashmapGet(m *hashmap, key string, value unsafe.Pointer) { // Did not find the key. memzero(value, uintptr(m.valueSize)) } + +// Hashmap with plain binary data keys (not containing strings etc.). + +func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) { + hash := hashmapHash(key, uintptr(m.keySize)) + hashmapSet(m, key, value, hash, memequal) +} + +func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer) { + hash := hashmapHash(key, uintptr(m.keySize)) + hashmapGet(m, key, value, hash, memequal) +} + +// Hashmap with string keys (a common case). + +func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool { + return *(*string)(x) == *(*string)(y) +} + +func hashmapStringHash(s string) uint32 { + _s := (*_string)(unsafe.Pointer(&s)) + return hashmapHash(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) +} + +func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer) { + hash := hashmapStringHash(key) + hashmapGet(m, unsafe.Pointer(&key), value, hash, hashmapStringEqual) +} diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index cc841b44..cbc36ceb 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -35,6 +35,18 @@ func memzero(ptr unsafe.Pointer, size uintptr) { } } +// Compare two same-size buffers for equality. +func memequal(x, y unsafe.Pointer, n uintptr) bool { + for i := uintptr(0); i < n; i++ { + cx := *(*uint8)(unsafe.Pointer(uintptr(x) + i)) + cy := *(*uint8)(unsafe.Pointer(uintptr(y) + i)) + if cx != cy { + return false + } + } + return true +} + func _panic(message interface{}) { printstring("panic: ") printitf(message)