From c0c1ccb381eb56192deef1e3ce86282932ef459e Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 20 Oct 2018 16:18:55 +0200 Subject: [PATCH] compiler, runtime: implement delete builtin --- compiler/compiler.go | 59 +++++------------------ compiler/map.go | 107 +++++++++++++++++++++++++++++++++++++++++ compiler/util.go | 26 ---------- src/runtime/hashmap.go | 45 +++++++++++++++++ testdata/map.go | 2 + testdata/map.txt | 13 +++++ 6 files changed, 179 insertions(+), 73 deletions(-) create mode 100644 compiler/map.go delete mode 100644 compiler/util.go diff --git a/compiler/compiler.go b/compiler/compiler.go index e42bcdb8..9b5773b2 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1559,28 +1559,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) error { return err } 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.Info()&types.IsString != 0 { - params := []llvm.Value{m, key, valuePtr} - c.createRuntimeCall("hashmapStringSet", 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} - c.createRuntimeCall("hashmapBinarySet", params, "") - return nil - } else { - return errors.New("todo: map update key type: " + keyType.String()) - } - default: - return errors.New("todo: map update key type: " + keyType.String()) - } + return c.emitMapUpdate(mapType.Key(), m, key, value) case *ssa.Panic: value, err := c.parseExpr(frame, instr.X) if err != nil { @@ -1718,6 +1697,16 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string) srcBuf = c.builder.CreateBitCast(srcBuf, c.i8ptrType, "copy.srcPtr") elemSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(elemType), false) return c.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil + case "delete": + m, err := c.parseExpr(frame, args[0]) + if err != nil { + return llvm.Value{}, err + } + key, err := c.parseExpr(frame, args[1]) + if err != nil { + return llvm.Value{}, err + } + return llvm.Value{}, c.emitMapDelete(args[1].Type(), m, key) case "len": value, err := c.parseExpr(frame, args[0]) if err != nil { @@ -2284,31 +2273,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { bufPtr := c.builder.CreateGEP(buf, []llvm.Value{index}, "") return c.builder.CreateLoad(bufPtr, ""), nil 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.Info()&types.IsString != 0 { - params := []llvm.Value{value, index, mapValuePtr} - c.createRuntimeCall("hashmapStringGet", 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} - c.createRuntimeCall("hashmapBinaryGet", params, "") - return c.builder.CreateLoad(mapValueAlloca, ""), nil - } else { - return llvm.Value{}, errors.New("todo: map lookup key type: " + keyType.String()) - } - default: - return llvm.Value{}, errors.New("todo: map lookup key type: " + keyType.String()) - } + return c.emitMapLookup(xType.Key(), expr.Type(), value, index) default: panic("unknown lookup type: " + expr.String()) } diff --git a/compiler/map.go b/compiler/map.go new file mode 100644 index 00000000..3ed0b455 --- /dev/null +++ b/compiler/map.go @@ -0,0 +1,107 @@ +package compiler + +// This file emits the correct map intrinsics for map operations. + +import ( + "errors" + "go/types" + + "github.com/aykevl/go-llvm" +) + +func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value) (llvm.Value, error) { + switch keyType := keyType.Underlying().(type) { + case *types.Basic: + llvmValueType, err := c.getLLVMType(valueType) + 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.Info()&types.IsString != 0 { + params := []llvm.Value{m, key, mapValuePtr} + c.createRuntimeCall("hashmapStringGet", params, "") + return c.builder.CreateLoad(mapValueAlloca, ""), 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, mapValuePtr} + c.createRuntimeCall("hashmapBinaryGet", params, "") + return c.builder.CreateLoad(mapValueAlloca, ""), nil + } else { + return llvm.Value{}, errors.New("todo: map lookup key type: " + keyType.String()) + } + default: + return llvm.Value{}, errors.New("todo: map lookup key type: " + keyType.String()) + } +} + +func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value) error { + switch keyType := keyType.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.Info()&types.IsString != 0 { + params := []llvm.Value{m, key, valuePtr} + c.createRuntimeCall("hashmapStringSet", 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} + c.createRuntimeCall("hashmapBinarySet", params, "") + return nil + } else { + return errors.New("todo: map update key type: " + keyType.String()) + } + default: + return errors.New("todo: map update key type: " + keyType.String()) + } +} + +func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value) error { + switch keyType := keyType.Underlying().(type) { + case *types.Basic: + if keyType.Info()&types.IsString != 0 { + params := []llvm.Value{m, key} + c.createRuntimeCall("hashmapStringDelete", 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} + c.createRuntimeCall("hashmapBinaryDelete", params, "") + return nil + } else { + return errors.New("todo: map lookup key type: " + keyType.String()) + } + default: + return errors.New("todo: map delete key type: " + keyType.String()) + } +} + +// 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 hashmapHash(data []byte) uint32 { + var result uint32 = 2166136261 // FNV offset basis + for _, c := range data { + result ^= uint32(c) + result *= 16777619 // FNV prime + } + return result +} + +// Get the topmost 8 bits of the hash, without using a special value (like 0). +func hashmapTopHash(hash uint32) uint8 { + tophash := uint8(hash >> 24) + if tophash < 1 { + // 0 means empty slot, so make it bigger. + tophash += 1 + } + return tophash +} diff --git a/compiler/util.go b/compiler/util.go deleted file mode 100644 index 52a9b15c..00000000 --- a/compiler/util.go +++ /dev/null @@ -1,26 +0,0 @@ -package compiler - -// This file contains functions that are also used by the runtime. These must be -// kept in sync. - -// 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 hashmapHash(data []byte) uint32 { - var result uint32 = 2166136261 // FNV offset basis - for _, c := range data { - result ^= uint32(c) - result *= 16777619 // FNV prime - } - return result -} - -// Get the topmost 8 bits of the hash, without using a special value (like 0). -func hashmapTopHash(hash uint32) uint8 { - tophash := uint8(hash >> 24) - if tophash < 1 { - // 0 means empty slot, so make it bigger. - tophash += 1 - } - return tophash -} diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index fdd0cc04..9e93928a 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -158,6 +158,41 @@ func hashmapGet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3 memzero(value, uintptr(m.valueSize)) } +// 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) { + 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 := 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)) { + // Found the key, delete it. + bucket.tophash[i] = 0 + m.count-- + return + } + } + } + bucket = bucket.next + } +} + // Iterate over a hashmap. //go:nobounds func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) bool { @@ -209,6 +244,11 @@ func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer) { hashmapGet(m, key, value, hash, memequal) } +func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) { + hash := hashmapHash(key, uintptr(m.keySize)) + hashmapDelete(m, key, hash, memequal) +} + // Hashmap with string keys (a common case). func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool { @@ -229,3 +269,8 @@ func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer) { hash := hashmapStringHash(key) hashmapGet(m, unsafe.Pointer(&key), value, hash, hashmapStringEqual) } + +func hashmapStringDelete(m *hashmap, key string) { + hash := hashmapStringHash(key) + hashmapDelete(m, unsafe.Pointer(&key), hash, hashmapStringEqual) +} diff --git a/testdata/map.go b/testdata/map.go index bbaf33f7..fe070625 100644 --- a/testdata/map.go +++ b/testdata/map.go @@ -22,6 +22,8 @@ func main() { readMap(testmap1, "data") readMap(testmap2, "three") readMap(testmap2, "ten") + delete(testmap2, "six") + readMap(testmap2, "seven") } func readMap(m map[string]int, key string) { diff --git a/testdata/map.txt b/testdata/map.txt index ccb70dfc..4f96c74f 100644 --- a/testdata/map.txt +++ b/testdata/map.txt @@ -33,3 +33,16 @@ map read: ten = 10 ten = 10 eleven = 11 twelve = 12 +map length: 11 +map read: seven = 7 + one = 1 + two = 2 + three = 3 + four = 4 + five = 5 + seven = 7 + eight = 8 + nine = 9 + ten = 10 + eleven = 11 + twelve = 12