github.com/aykevl/tinygo@v0.5.0/compiler/map.go (about) 1 package compiler 2 3 // This file emits the correct map intrinsics for map operations. 4 5 import ( 6 "go/token" 7 "go/types" 8 9 "tinygo.org/x/go-llvm" 10 ) 11 12 func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) { 13 llvmValueType, err := c.getLLVMType(valueType) 14 if err != nil { 15 return llvm.Value{}, err 16 } 17 mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value") 18 mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr") 19 var commaOkValue llvm.Value 20 if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { 21 // key is a string 22 params := []llvm.Value{m, key, mapValuePtr} 23 commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "") 24 } else if hashmapIsBinaryKey(keyType) { 25 // key can be compared with runtime.memequal 26 keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key") 27 c.builder.CreateStore(key, keyAlloca) 28 keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr") 29 params := []llvm.Value{m, keyPtr, mapValuePtr} 30 commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "") 31 } else { 32 return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) 33 } 34 mapValue := c.builder.CreateLoad(mapValueAlloca, "") 35 if commaOk { 36 tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false)) 37 tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "") 38 tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "") 39 return tuple, nil 40 } else { 41 return mapValue, nil 42 } 43 } 44 45 func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) error { 46 valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value") 47 c.builder.CreateStore(value, valueAlloca) 48 valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr") 49 keyType = keyType.Underlying() 50 if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { 51 // key is a string 52 params := []llvm.Value{m, key, valuePtr} 53 c.createRuntimeCall("hashmapStringSet", params, "") 54 return nil 55 } else if hashmapIsBinaryKey(keyType) { 56 // key can be compared with runtime.memequal 57 keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key") 58 c.builder.CreateStore(key, keyAlloca) 59 keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr") 60 params := []llvm.Value{m, keyPtr, valuePtr} 61 c.createRuntimeCall("hashmapBinarySet", params, "") 62 return nil 63 } else { 64 return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) 65 } 66 } 67 68 func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { 69 keyType = keyType.Underlying() 70 if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { 71 // key is a string 72 params := []llvm.Value{m, key} 73 c.createRuntimeCall("hashmapStringDelete", params, "") 74 return nil 75 } else if hashmapIsBinaryKey(keyType) { 76 keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key") 77 c.builder.CreateStore(key, keyAlloca) 78 keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr") 79 params := []llvm.Value{m, keyPtr} 80 c.createRuntimeCall("hashmapBinaryDelete", params, "") 81 return nil 82 } else { 83 return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String()) 84 } 85 } 86 87 // Get FNV-1a hash of this string. 88 // 89 // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash 90 func hashmapHash(data []byte) uint32 { 91 var result uint32 = 2166136261 // FNV offset basis 92 for _, c := range data { 93 result ^= uint32(c) 94 result *= 16777619 // FNV prime 95 } 96 return result 97 } 98 99 // Get the topmost 8 bits of the hash, without using a special value (like 0). 100 func hashmapTopHash(hash uint32) uint8 { 101 tophash := uint8(hash >> 24) 102 if tophash < 1 { 103 // 0 means empty slot, so make it bigger. 104 tophash += 1 105 } 106 return tophash 107 } 108 109 // Returns true if this key type does not contain strings, interfaces etc., so 110 // can be compared with runtime.memequal. 111 func hashmapIsBinaryKey(keyType types.Type) bool { 112 switch keyType := keyType.(type) { 113 case *types.Basic: 114 return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0 115 case *types.Struct: 116 for i := 0; i < keyType.NumFields(); i++ { 117 fieldType := keyType.Field(i).Type().Underlying() 118 if !hashmapIsBinaryKey(fieldType) { 119 return false 120 } 121 } 122 return true 123 case *types.Array: 124 return hashmapIsBinaryKey(keyType.Elem()) 125 case *types.Named: 126 return hashmapIsBinaryKey(keyType.Underlying()) 127 default: 128 return false 129 } 130 }