From 2d9f3605b918bf0c2797a23fa1230a0840f0ed30 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 23 Nov 2019 13:34:21 +0100 Subject: [PATCH] compiler: refactor map operations to use the builder object --- compiler/compiler.go | 8 ++-- compiler/map.go | 106 +++++++++++++++++++++++-------------------- 2 files changed, 60 insertions(+), 54 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 26115d6f..e128d5d6 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1107,7 +1107,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) { key := frame.getValue(instr.Key) value := frame.getValue(instr.Value) mapType := instr.Map.Type().Underlying().(*types.Map) - c.emitMapUpdate(mapType.Key(), m, key, value, instr.Pos()) + frame.createMapUpdate(mapType.Key(), m, key, value, instr.Pos()) case *ssa.Panic: value := frame.getValue(instr.X) c.createRuntimeCall("_panic", []llvm.Value{value}, "") @@ -1219,7 +1219,7 @@ func (c *Compiler) parseBuiltin(frame *Frame, args []ssa.Value, callName string, case "delete": m := frame.getValue(args[0]) key := frame.getValue(args[1]) - return llvm.Value{}, c.emitMapDelete(args[1].Type(), m, key, pos) + return llvm.Value{}, frame.createMapDelete(args[1].Type(), m, key, pos) case "imag": cplx := frame.getValue(args[0]) return c.builder.CreateExtractValue(cplx, 1, "imag"), nil @@ -1619,7 +1619,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { if expr.CommaOk { valueType = valueType.(*types.Tuple).At(0).Type() } - return c.emitMapLookup(xType.Key(), valueType, value, index, expr.CommaOk, expr.Pos()) + return frame.createMapLookup(xType.Key(), valueType, value, index, expr.CommaOk, expr.Pos()) default: panic("unknown lookup type: " + expr.String()) } @@ -1631,7 +1631,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { val := frame.getValue(expr.X) return frame.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil case *ssa.MakeMap: - return c.emitMakeMap(frame, expr) + return frame.createMakeMap(expr) case *ssa.MakeSlice: sliceLen := frame.getValue(expr.Len) sliceCap := frame.getValue(expr.Cap) diff --git a/compiler/map.go b/compiler/map.go index b4e163fe..d469db5b 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -10,56 +10,58 @@ import ( "tinygo.org/x/go-llvm" ) -// emitMakeMap creates a new map object (runtime.hashmap) by allocating and +// createMakeMap creates a new map object (runtime.hashmap) by allocating and // initializing an appropriately sized object. -func (c *Compiler) emitMakeMap(frame *Frame, expr *ssa.MakeMap) (llvm.Value, error) { +func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) { mapType := expr.Type().Underlying().(*types.Map) keyType := mapType.Key().Underlying() - llvmValueType := c.getLLVMType(mapType.Elem().Underlying()) + llvmValueType := b.getLLVMType(mapType.Elem().Underlying()) var llvmKeyType llvm.Type if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // String keys. - llvmKeyType = c.getLLVMType(keyType) + llvmKeyType = b.getLLVMType(keyType) } else if hashmapIsBinaryKey(keyType) { // Trivially comparable keys. - llvmKeyType = c.getLLVMType(keyType) + llvmKeyType = b.getLLVMType(keyType) } else { // All other keys. Implemented as map[interface{}]valueType for ease of // implementation. - llvmKeyType = c.getLLVMRuntimeType("_interface") + llvmKeyType = b.getLLVMRuntimeType("_interface") } - keySize := c.targetData.TypeAllocSize(llvmKeyType) - valueSize := c.targetData.TypeAllocSize(llvmValueType) - llvmKeySize := llvm.ConstInt(c.ctx.Int8Type(), keySize, false) - llvmValueSize := llvm.ConstInt(c.ctx.Int8Type(), valueSize, false) - sizeHint := llvm.ConstInt(c.uintptrType, 8, false) + 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) if expr.Reserve != nil { - sizeHint = frame.getValue(expr.Reserve) + sizeHint = b.getValue(expr.Reserve) var err error - sizeHint, err = frame.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) + sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) if err != nil { return llvm.Value{}, err } } - hashmap := c.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "") + hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "") return hashmap, nil } -func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { - llvmValueType := c.getLLVMType(valueType) +// createMapLookup returns the value in a map. It calls a runtime function +// depending on the map key type to load the map value and its comma-ok value. +func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { + llvmValueType := b.getLLVMType(valueType) // Allocate the memory for the resulting type. Do not zero this memory: it // will be zeroed by the hashmap get implementation if the key is not // present in the map. - mapValueAlloca, mapValuePtr, mapValueAllocaSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value") + mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value") // We need the map size (with type uintptr) to pass to the hashmap*Get // functions. This is necessary because those *Get functions are valid on // nil maps, and they'll need to zero the value pointer by that number of // bytes. mapValueSize := mapValueAllocaSize - if mapValueSize.Type().IntTypeWidth() > c.uintptrType.IntTypeWidth() { - mapValueSize = llvm.ConstTrunc(mapValueSize, c.uintptrType) + if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() { + mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType) } // Do the lookup. How it is done depends on the key type. @@ -68,84 +70,88 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, key, mapValuePtr, mapValueSize} - commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "") + commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal // Store the key in an alloca, in the entry block to avoid dynamic stack // growth. - mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") - c.builder.CreateStore(key, mapKeyAlloca) + mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") + b.CreateStore(key, mapKeyAlloca) // Fetch the value from the hashmap. params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize} - commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "") - c.emitLifetimeEnd(mapKeyPtr, mapKeySize) + commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "") + b.emitLifetimeEnd(mapKeyPtr, mapKeySize) } else { // Not trivially comparable using memcmp. Make it an interface instead. itfKey := key if _, ok := keyType.(*types.Interface); !ok { // Not already an interface, so convert it to an interface now. - itfKey = c.parseMakeInterface(key, keyType, pos) + itfKey = b.createMakeInterface(key, keyType, pos) } params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize} - commaOkValue = c.createRuntimeCall("hashmapInterfaceGet", params, "") + commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "") } // Load the resulting value from the hashmap. The value is set to the zero // value if the key doesn't exist in the hashmap. - mapValue := c.builder.CreateLoad(mapValueAlloca, "") - c.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize) + mapValue := b.CreateLoad(mapValueAlloca, "") + b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize) if commaOk { - tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false)) - tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "") - tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "") + tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false)) + tuple = b.CreateInsertValue(tuple, mapValue, 0, "") + tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "") return tuple, nil } else { return mapValue, nil } } -func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { - valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value") - c.builder.CreateStore(value, valueAlloca) +// createMapUpdate updates a map key to a given value, by creating an +// appropriate runtime call. +func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { + valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") + b.CreateStore(value, valueAlloca) keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, key, valuePtr} - c.createRuntimeCall("hashmapStringSet", params, "") + b.createRuntimeCall("hashmapStringSet", params, "") } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal - keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") - c.builder.CreateStore(key, keyAlloca) + keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") + b.CreateStore(key, keyAlloca) params := []llvm.Value{m, keyPtr, valuePtr} - c.createRuntimeCall("hashmapBinarySet", params, "") - c.emitLifetimeEnd(keyPtr, keySize) + b.createRuntimeCall("hashmapBinarySet", params, "") + b.emitLifetimeEnd(keyPtr, keySize) } else { // Key is not trivially comparable, so compare it as an interface instead. itfKey := key if _, ok := keyType.(*types.Interface); !ok { // Not already an interface, so convert it to an interface first. - itfKey = c.parseMakeInterface(key, keyType, pos) + itfKey = b.createMakeInterface(key, keyType, pos) } params := []llvm.Value{m, itfKey, valuePtr} - c.createRuntimeCall("hashmapInterfaceSet", params, "") + b.createRuntimeCall("hashmapInterfaceSet", params, "") } - c.emitLifetimeEnd(valuePtr, valueSize) + b.emitLifetimeEnd(valuePtr, valueSize) } -func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { +// createMapDelete deletes a key from a map by calling the appropriate runtime +// function. It is the implementation of the Go delete() builtin. +func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, key} - c.createRuntimeCall("hashmapStringDelete", params, "") + b.createRuntimeCall("hashmapStringDelete", params, "") return nil } else if hashmapIsBinaryKey(keyType) { - keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") - c.builder.CreateStore(key, keyAlloca) + keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") + b.CreateStore(key, keyAlloca) params := []llvm.Value{m, keyPtr} - c.createRuntimeCall("hashmapBinaryDelete", params, "") - c.emitLifetimeEnd(keyPtr, keySize) + b.createRuntimeCall("hashmapBinaryDelete", params, "") + b.emitLifetimeEnd(keyPtr, keySize) return nil } else { // Key is not trivially comparable, so compare it as an interface @@ -153,10 +159,10 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke itfKey := key if _, ok := keyType.(*types.Interface); !ok { // Not already an interface, so convert it to an interface first. - itfKey = c.parseMakeInterface(key, keyType, pos) + itfKey = b.createMakeInterface(key, keyType, pos) } params := []llvm.Value{m, itfKey} - c.createRuntimeCall("hashmapInterfaceDelete", params, "") + b.createRuntimeCall("hashmapInterfaceDelete", params, "") return nil } }