github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/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  	"golang.org/x/tools/go/ssa"
    10  	"tinygo.org/x/go-llvm"
    11  )
    12  
    13  // constants for hashmap algorithms; must match src/runtime/hashmap.go
    14  const (
    15  	hashmapAlgorithmBinary = iota
    16  	hashmapAlgorithmString
    17  	hashmapAlgorithmInterface
    18  )
    19  
    20  // createMakeMap creates a new map object (runtime.hashmap) by allocating and
    21  // initializing an appropriately sized object.
    22  func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
    23  	mapType := expr.Type().Underlying().(*types.Map)
    24  	keyType := mapType.Key().Underlying()
    25  	llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
    26  	var llvmKeyType llvm.Type
    27  	var alg uint64 // must match values in src/runtime/hashmap.go
    28  	if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
    29  		// String keys.
    30  		llvmKeyType = b.getLLVMType(keyType)
    31  		alg = hashmapAlgorithmString
    32  	} else if hashmapIsBinaryKey(keyType) {
    33  		// Trivially comparable keys.
    34  		llvmKeyType = b.getLLVMType(keyType)
    35  		alg = hashmapAlgorithmBinary
    36  	} else {
    37  		// All other keys. Implemented as map[interface{}]valueType for ease of
    38  		// implementation.
    39  		llvmKeyType = b.getLLVMRuntimeType("_interface")
    40  		alg = hashmapAlgorithmInterface
    41  	}
    42  	keySize := b.targetData.TypeAllocSize(llvmKeyType)
    43  	valueSize := b.targetData.TypeAllocSize(llvmValueType)
    44  	llvmKeySize := llvm.ConstInt(b.uintptrType, keySize, false)
    45  	llvmValueSize := llvm.ConstInt(b.uintptrType, valueSize, false)
    46  	sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
    47  	algEnum := llvm.ConstInt(b.ctx.Int8Type(), alg, false)
    48  	if expr.Reserve != nil {
    49  		sizeHint = b.getValue(expr.Reserve, getPos(expr))
    50  		var err error
    51  		sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
    52  		if err != nil {
    53  			return llvm.Value{}, err
    54  		}
    55  	}
    56  	hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint, algEnum}, "")
    57  	return hashmap, nil
    58  }
    59  
    60  // createMapLookup returns the value in a map. It calls a runtime function
    61  // depending on the map key type to load the map value and its comma-ok value.
    62  func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
    63  	llvmValueType := b.getLLVMType(valueType)
    64  
    65  	// Allocate the memory for the resulting type. Do not zero this memory: it
    66  	// will be zeroed by the hashmap get implementation if the key is not
    67  	// present in the map.
    68  	mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
    69  
    70  	// We need the map size (with type uintptr) to pass to the hashmap*Get
    71  	// functions. This is necessary because those *Get functions are valid on
    72  	// nil maps, and they'll need to zero the value pointer by that number of
    73  	// bytes.
    74  	mapValueSize := mapValueAllocaSize
    75  	if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
    76  		mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
    77  	}
    78  
    79  	// Do the lookup. How it is done depends on the key type.
    80  	var commaOkValue llvm.Value
    81  	origKeyType := keyType
    82  	keyType = keyType.Underlying()
    83  	if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
    84  		// key is a string
    85  		params := []llvm.Value{m, key, mapValueAlloca, mapValueSize}
    86  		commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
    87  	} else if hashmapIsBinaryKey(keyType) {
    88  		// key can be compared with runtime.memequal
    89  		// Store the key in an alloca, in the entry block to avoid dynamic stack
    90  		// growth.
    91  		mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
    92  		b.CreateStore(key, mapKeyAlloca)
    93  		b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca)
    94  		// Fetch the value from the hashmap.
    95  		params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize}
    96  		commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
    97  		b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
    98  	} else {
    99  		// Not trivially comparable using memcmp. Make it an interface instead.
   100  		itfKey := key
   101  		if _, ok := keyType.(*types.Interface); !ok {
   102  			// Not already an interface, so convert it to an interface now.
   103  			itfKey = b.createMakeInterface(key, origKeyType, pos)
   104  		}
   105  		params := []llvm.Value{m, itfKey, mapValueAlloca, mapValueSize}
   106  		commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
   107  	}
   108  
   109  	// Load the resulting value from the hashmap. The value is set to the zero
   110  	// value if the key doesn't exist in the hashmap.
   111  	mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
   112  	b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize)
   113  
   114  	if commaOk {
   115  		tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
   116  		tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
   117  		tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
   118  		return tuple, nil
   119  	} else {
   120  		return mapValue, nil
   121  	}
   122  }
   123  
   124  // createMapUpdate updates a map key to a given value, by creating an
   125  // appropriate runtime call.
   126  func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
   127  	valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
   128  	b.CreateStore(value, valueAlloca)
   129  	origKeyType := keyType
   130  	keyType = keyType.Underlying()
   131  	if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
   132  		// key is a string
   133  		params := []llvm.Value{m, key, valueAlloca}
   134  		b.createRuntimeCall("hashmapStringSet", params, "")
   135  	} else if hashmapIsBinaryKey(keyType) {
   136  		// key can be compared with runtime.memequal
   137  		keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
   138  		b.CreateStore(key, keyAlloca)
   139  		b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
   140  		params := []llvm.Value{m, keyAlloca, valueAlloca}
   141  		b.createRuntimeCall("hashmapBinarySet", params, "")
   142  		b.emitLifetimeEnd(keyAlloca, keySize)
   143  	} else {
   144  		// Key is not trivially comparable, so compare it as an interface instead.
   145  		itfKey := key
   146  		if _, ok := keyType.(*types.Interface); !ok {
   147  			// Not already an interface, so convert it to an interface first.
   148  			itfKey = b.createMakeInterface(key, origKeyType, pos)
   149  		}
   150  		params := []llvm.Value{m, itfKey, valueAlloca}
   151  		b.createRuntimeCall("hashmapInterfaceSet", params, "")
   152  	}
   153  	b.emitLifetimeEnd(valueAlloca, valueSize)
   154  }
   155  
   156  // createMapDelete deletes a key from a map by calling the appropriate runtime
   157  // function. It is the implementation of the Go delete() builtin.
   158  func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
   159  	origKeyType := keyType
   160  	keyType = keyType.Underlying()
   161  	if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
   162  		// key is a string
   163  		params := []llvm.Value{m, key}
   164  		b.createRuntimeCall("hashmapStringDelete", params, "")
   165  		return nil
   166  	} else if hashmapIsBinaryKey(keyType) {
   167  		keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
   168  		b.CreateStore(key, keyAlloca)
   169  		b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
   170  		params := []llvm.Value{m, keyAlloca}
   171  		b.createRuntimeCall("hashmapBinaryDelete", params, "")
   172  		b.emitLifetimeEnd(keyAlloca, keySize)
   173  		return nil
   174  	} else {
   175  		// Key is not trivially comparable, so compare it as an interface
   176  		// instead.
   177  		itfKey := key
   178  		if _, ok := keyType.(*types.Interface); !ok {
   179  			// Not already an interface, so convert it to an interface first.
   180  			itfKey = b.createMakeInterface(key, origKeyType, pos)
   181  		}
   182  		params := []llvm.Value{m, itfKey}
   183  		b.createRuntimeCall("hashmapInterfaceDelete", params, "")
   184  		return nil
   185  	}
   186  }
   187  
   188  // Clear the given map.
   189  func (b *builder) createMapClear(m llvm.Value) {
   190  	b.createRuntimeCall("hashmapClear", []llvm.Value{m}, "")
   191  }
   192  
   193  // createMapIteratorNext lowers the *ssa.Next instruction for iterating over a
   194  // map. It returns a tuple of {bool, key, value} with the result of the
   195  // iteration.
   196  func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llvm.Value) llvm.Value {
   197  	// Determine the type of the values to return from the *ssa.Next
   198  	// instruction. It is returned as {bool, keyType, valueType}.
   199  	keyType := rangeVal.Type().Underlying().(*types.Map).Key()
   200  	valueType := rangeVal.Type().Underlying().(*types.Map).Elem()
   201  	llvmKeyType := b.getLLVMType(keyType)
   202  	llvmValueType := b.getLLVMType(valueType)
   203  
   204  	// There is a special case in which keys are stored as an interface value
   205  	// instead of the value they normally are. This happens for non-trivially
   206  	// comparable types such as float32 or some structs.
   207  	isKeyStoredAsInterface := false
   208  	if t, ok := keyType.Underlying().(*types.Basic); ok && t.Info()&types.IsString != 0 {
   209  		// key is a string
   210  	} else if hashmapIsBinaryKey(keyType) {
   211  		// key can be compared with runtime.memequal
   212  	} else {
   213  		// The key is stored as an interface value, and may or may not be an
   214  		// interface type (for example, float32 keys are stored as an interface
   215  		// value).
   216  		if _, ok := keyType.Underlying().(*types.Interface); !ok {
   217  			isKeyStoredAsInterface = true
   218  		}
   219  	}
   220  
   221  	// Determine the type of the key as stored in the map.
   222  	llvmStoredKeyType := llvmKeyType
   223  	if isKeyStoredAsInterface {
   224  		llvmStoredKeyType = b.getLLVMRuntimeType("_interface")
   225  	}
   226  
   227  	// Extract the key and value from the map.
   228  	mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key")
   229  	mapValueAlloca, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value")
   230  	ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyAlloca, mapValueAlloca}, "range.next")
   231  	mapKey := b.CreateLoad(llvmStoredKeyType, mapKeyAlloca, "")
   232  	mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
   233  
   234  	if isKeyStoredAsInterface {
   235  		// The key is stored as an interface but it isn't of interface type.
   236  		// Extract the underlying value.
   237  		mapKey = b.extractValueFromInterface(mapKey, llvmKeyType)
   238  	}
   239  
   240  	// End the lifetimes of the allocas, because we're done with them.
   241  	b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
   242  	b.emitLifetimeEnd(mapValueAlloca, mapValueSize)
   243  
   244  	// Construct the *ssa.Next return value: {ok, mapKey, mapValue}
   245  	tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false))
   246  	tuple = b.CreateInsertValue(tuple, ok, 0, "")
   247  	tuple = b.CreateInsertValue(tuple, mapKey, 1, "")
   248  	tuple = b.CreateInsertValue(tuple, mapValue, 2, "")
   249  
   250  	return tuple
   251  }
   252  
   253  // Returns true if this key type does not contain strings, interfaces etc., so
   254  // can be compared with runtime.memequal.  Note that padding bytes are undef
   255  // and can alter two "equal" structs being equal when compared with memequal.
   256  func hashmapIsBinaryKey(keyType types.Type) bool {
   257  	switch keyType := keyType.(type) {
   258  	case *types.Basic:
   259  		return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
   260  	case *types.Pointer:
   261  		return true
   262  	case *types.Struct:
   263  		for i := 0; i < keyType.NumFields(); i++ {
   264  			fieldType := keyType.Field(i).Type().Underlying()
   265  			if !hashmapIsBinaryKey(fieldType) {
   266  				return false
   267  			}
   268  		}
   269  		return true
   270  	case *types.Array:
   271  		return hashmapIsBinaryKey(keyType.Elem())
   272  	case *types.Named:
   273  		return hashmapIsBinaryKey(keyType.Underlying())
   274  	default:
   275  		return false
   276  	}
   277  }
   278  
   279  func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error {
   280  	// We know that hashmapIsBinaryKey is true, so we only have to handle those types that can show up there.
   281  	// To zero all undefined bytes, we iterate over all the fields in the type.  For each element, compute the
   282  	// offset of that element.  If it's Basic type, there are no internal padding bytes.  For compound types, we recurse to ensure
   283  	// we handle nested types.  Next, we determine if there are any padding bytes before the next
   284  	// element and zero those as well.
   285  
   286  	zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
   287  
   288  	switch llvmType.TypeKind() {
   289  	case llvm.IntegerTypeKind:
   290  		// no padding bytes
   291  		return nil
   292  	case llvm.PointerTypeKind:
   293  		// mo padding bytes
   294  		return nil
   295  	case llvm.ArrayTypeKind:
   296  		llvmArrayType := llvmType
   297  		llvmElemType := llvmType.ElementType()
   298  
   299  		for i := 0; i < llvmArrayType.ArrayLength(); i++ {
   300  			idx := llvm.ConstInt(b.uintptrType, uint64(i), false)
   301  			elemPtr := b.CreateInBoundsGEP(llvmArrayType, ptr, []llvm.Value{zero, idx}, "")
   302  
   303  			// zero any padding bytes in this element
   304  			b.zeroUndefBytes(llvmElemType, elemPtr)
   305  		}
   306  
   307  	case llvm.StructTypeKind:
   308  		llvmStructType := llvmType
   309  		numFields := llvmStructType.StructElementTypesCount()
   310  		llvmElementTypes := llvmStructType.StructElementTypes()
   311  
   312  		for i := 0; i < numFields; i++ {
   313  			idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)
   314  			elemPtr := b.CreateInBoundsGEP(llvmStructType, ptr, []llvm.Value{zero, idx}, "")
   315  
   316  			// zero any padding bytes in this field
   317  			llvmElemType := llvmElementTypes[i]
   318  			b.zeroUndefBytes(llvmElemType, elemPtr)
   319  
   320  			// zero any padding bytes before the next field, if any
   321  			offset := b.targetData.ElementOffset(llvmStructType, i)
   322  			storeSize := b.targetData.TypeStoreSize(llvmElemType)
   323  			fieldEndOffset := offset + storeSize
   324  
   325  			var nextOffset uint64
   326  			if i < numFields-1 {
   327  				nextOffset = b.targetData.ElementOffset(llvmStructType, i+1)
   328  			} else {
   329  				// Last field?  Next offset is the total size of the allcoate struct.
   330  				nextOffset = b.targetData.TypeAllocSize(llvmStructType)
   331  			}
   332  
   333  			if fieldEndOffset != nextOffset {
   334  				n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false)
   335  				llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false)
   336  				paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), elemPtr, []llvm.Value{llvmStoreSize}, "")
   337  				b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "")
   338  			}
   339  		}
   340  	}
   341  
   342  	return nil
   343  }