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  }