package compiler // This file emits the correct map intrinsics for map operations. import ( "fmt" "go/token" "go/types" "golang.org/x/tools/go/ssa" "strings" "tinygo.org/x/go-llvm" ) const hashArrayUnrollLimit = 4 // createMakeMap creates a new map object (runtime.hashmap) by allocating and // initializing an appropriately sized object. func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) { mapType := expr.Type().Underlying().(*types.Map) keyType := mapType.Key().Underlying() llvmValueType := b.getLLVMType(mapType.Elem().Underlying()) llvmKeyType := b.getLLVMType(keyType) keySize := b.targetData.TypeAllocSize(llvmKeyType) valueSize := b.targetData.TypeAllocSize(llvmValueType) llvmKeySize := llvm.ConstInt(b.uintptrType, keySize, false) llvmValueSize := llvm.ConstInt(b.uintptrType, valueSize, false) sizeHint := llvm.ConstInt(b.uintptrType, 8, false) if expr.Reserve != nil { sizeHint = b.getValue(expr.Reserve, getPos(expr)) var err error sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) if err != nil { return llvm.Value{}, err } } // Resolve hash and equal functions for this key type. For string and // binary key types, reference the corresponding runtime functions // directly. For composite types, generate type-specific functions. var hashFn, equalFn llvm.Value if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { hashFn = b.getRuntimeFunctionValue("hashmapStringPtrHash", hashmapKeyHashSignature()) equalFn = b.getRuntimeFunctionValue("hashmapStringEqual", hashmapKeyEqualSignature()) } else if hashmapIsBinaryKey(keyType) { hashFn = b.getRuntimeFunctionValue("hash32", hashmapKeyHashSignature()) equalFn = b.getRuntimeFunctionValue("memequal", hashmapKeyEqualSignature()) } else { fn := b.getOrGenerateKeyHashFunc(keyType) hashFn = b.createFuncValue(fn, llvm.ConstNull(b.dataPtrType), hashmapKeyHashSignature()) fn = b.getOrGenerateKeyEqualFunc(keyType) equalFn = b.createFuncValue(fn, llvm.ConstNull(b.dataPtrType), hashmapKeyEqualSignature()) } hashmap := b.createRuntimeCall("hashmapMakeGeneric", []llvm.Value{ llvmKeySize, llvmValueSize, sizeHint, hashFn, equalFn, }, "") return hashmap, nil } // getRuntimeFunctionValue returns a TinyGo function value (with nil context) // for the named runtime function. func (b *builder) getRuntimeFunctionValue(name string, sig *types.Signature) llvm.Value { member := b.program.ImportedPackage("runtime").Members[name] if member == nil { panic("unknown runtime function: " + name) } _, llvmFn := b.getFunction(member.(*ssa.Function)) return b.createFuncValue(llvmFn, llvm.ConstNull(b.dataPtrType), sig) } // 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 llvm.Value, key ssa.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. result := b.createRuntimeValueResult(llvmValueType, commaOk, false, "hashmap") mapValueAlloca := result.valuePtr // 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 := result.valueSize 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. var commaOkValue llvm.Value keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, b.getValue(key, getPos(key)), mapValueAlloca, mapValueSize} commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") } else { // Key stored at actual type: either binary-comparable or with // compiler-generated hash/equal. mapKey := b.getValueStorage(key, "hashmap.key") params := []llvm.Value{m, mapKey.ptr, mapValueAlloca, mapValueSize} fnName := "hashmapBinaryGet" if !hashmapIsBinaryKey(keyType) { fnName = "hashmapGenericGet" } commaOkValue = b.createRuntimeCall(fnName, params, "") b.endValueStorage(mapKey) } // The value is set to the zero value if the key doesn't exist. return result.finish(b, commaOkValue, ""), nil } // createMapUpdate updates a map key to a given value, by creating an // appropriate runtime call. func (b *builder) createMapUpdate(keyType types.Type, m llvm.Value, key, value ssa.Value, pos token.Pos) { storedValue := b.getValueStorage(value, "hashmap.value") keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string params := []llvm.Value{m, b.getValue(key, getPos(key)), storedValue.ptr} b.createRuntimeInvoke("hashmapStringSet", params, "") } else { // Key stored at actual type. keyStorage := b.getValueStorage(key, "hashmap.key") fnName := "hashmapBinarySet" if !hashmapIsBinaryKey(keyType) { fnName = "hashmapGenericSet" } params := []llvm.Value{m, keyStorage.ptr, storedValue.ptr} b.createRuntimeInvoke(fnName, params, "") b.endValueStorage(keyStorage) } b.endValueStorage(storedValue) } // 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} b.createRuntimeCall("hashmapStringDelete", params, "") return nil } else { // Key stored at actual type. keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") b.CreateStore(key, keyAlloca) fnName := "hashmapBinaryDelete" if !hashmapIsBinaryKey(keyType) { fnName = "hashmapGenericDelete" } params := []llvm.Value{m, keyAlloca} b.createRuntimeCall(fnName, params, "") b.emitLifetimeEnd(keyAlloca, keySize) return nil } } // Clear the given map. func (b *builder) createMapClear(m llvm.Value) { b.createRuntimeCall("hashmapClear", []llvm.Value{m}, "") } // createMapIteratorNext lowers the *ssa.Next instruction for iterating over a // map. It returns a tuple of {bool, key, value} with the result of the // iteration. func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llvm.Value) llvm.Value { // Determine the type of the values to return from the *ssa.Next // instruction. It is returned as {bool, keyType, valueType}. keyType := rangeVal.Type().Underlying().(*types.Map).Key() valueType := rangeVal.Type().Underlying().(*types.Map).Elem() llvmKeyType := b.getLLVMType(keyType) llvmValueType := b.getLLVMType(valueType) // All key types are now stored at their declared type (no interface wrapping). // Extract the key and value from the map. mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmKeyType, "range.key") mapValueAlloca, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value") ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyAlloca, mapValueAlloca}, "range.next") mapKey := b.CreateLoad(llvmKeyType, mapKeyAlloca, "") mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") // End the lifetimes of the allocas, because we're done with them. b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) b.emitLifetimeEnd(mapValueAlloca, mapValueSize) // Construct the *ssa.Next return value: {ok, mapKey, mapValue} tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) tuple = b.CreateInsertValue(tuple, ok, 0, "") tuple = b.CreateInsertValue(tuple, mapKey, 1, "") tuple = b.CreateInsertValue(tuple, mapValue, 2, "") return tuple } // Returns true if this key type does not contain strings, interfaces etc., so // can be compared with runtime.memequal. Note that padding bytes are undef // and can alter two "equal" structs being equal when compared with memequal. func hashmapIsBinaryKey(keyType types.Type) bool { switch keyType := keyType.Underlying().(type) { case *types.Basic: return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0 || keyType.Kind() == types.UnsafePointer case *types.Pointer: return true case *types.Array: return hashmapIsBinaryKey(keyType.Elem()) default: return false } } // hashmapKeyHashSignature returns the Go type signature for hashmap key hash // functions: func(key unsafe.Pointer, size, seed uintptr) uint32 func hashmapKeyHashSignature() *types.Signature { return types.NewSignatureType(nil, nil, nil, types.NewTuple( types.NewVar(token.NoPos, nil, "key", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "size", types.Typ[types.Uintptr]), types.NewVar(token.NoPos, nil, "seed", types.Typ[types.Uintptr]), ), types.NewTuple( types.NewVar(token.NoPos, nil, "", types.Typ[types.Uint32]), ), false, ) } // hashmapKeyEqualSignature returns the Go type signature for hashmap key equal // functions: func(x, y unsafe.Pointer, n uintptr) bool func hashmapKeyEqualSignature() *types.Signature { return types.NewSignatureType(nil, nil, nil, types.NewTuple( types.NewVar(token.NoPos, nil, "x", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "y", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "n", types.Typ[types.Uintptr]), ), types.NewTuple( types.NewVar(token.NoPos, nil, "", types.Typ[types.Bool]), ), false, ) } // hashmapKeyFuncName returns a canonical name for a generated hash or equal // function based on the key type's underlying structure. Named types are // replaced with their underlying types so that structurally identical key // types (e.g., struct{i1; str1} and struct{i2; str2} where both i1, i2 are // int and str1, str2 are string) share the same generated function. func hashmapKeyFuncName(prefix string, keyType types.Type) string { return prefix + "." + hashmapCanonicalTypeName(keyType) } // hashmapCanonicalTypeName returns a string representation of the hash/equal // operations needed for a type, stripping named types where the operation does // not depend on the name. Pointer and channel names do not include the element // type because their hash/equal operations only use the pointer word. func hashmapCanonicalTypeName(t types.Type) string { switch t := t.Underlying().(type) { case *types.Basic: return t.Name() case *types.Pointer: return "*" case *types.Chan: switch t.Dir() { case types.SendRecv: return "chan" case types.SendOnly: return "chan<-" case types.RecvOnly: return "<-chan" } case *types.Interface: if t.NumMethods() == 0 { return "interface{}" } return t.String() case *types.Struct: var s strings.Builder s.WriteString("struct{") for i := 0; i < t.NumFields(); i++ { if i > 0 { s.WriteString("; ") } s.WriteString(hashmapCanonicalTypeName(t.Field(i).Type())) } return s.String() + "}" case *types.Array: return fmt.Sprintf("[%d]%s", t.Len(), hashmapCanonicalTypeName(t.Elem())) } return t.String() } // getOrGenerateKeyHashFunc returns an LLVM function that computes the hash // of a key of the given type. The function is generated on first call and // cached in the module. func (b *builder) getOrGenerateKeyHashFunc(keyType types.Type) llvm.Value { name := hashmapKeyFuncName("hashmapKeyHash", keyType) if fn := b.mod.NamedFunction(name); !fn.IsNil() { return fn } // Create the LLVM function type: // (key ptr, size uintptr, seed uintptr, context ptr) -> i32 fnType := llvm.FunctionType(b.ctx.Int32Type(), []llvm.Type{ b.dataPtrType, b.uintptrType, b.uintptrType, b.dataPtrType, }, false) fn := llvm.AddFunction(b.mod, name, fnType) fn.SetLinkage(llvm.LinkOnceODRLinkage) fn.SetUnnamedAddr(true) b.addStandardAttributes(fn) // Generate the function body. savedBlock := b.GetInsertBlock() defer b.SetInsertPointAtEnd(savedBlock) entry := b.ctx.AddBasicBlock(fn, "entry") b.SetInsertPointAtEnd(entry) keyPtr := fn.Param(0) seed := fn.Param(2) llvmKeyType := b.getLLVMType(keyType) hash := b.generateKeyHash(keyType, llvmKeyType, keyPtr, seed) b.CreateRet(hash) return fn } // getOrGenerateKeyEqualFunc returns an LLVM function that compares two keys // of the given type for equality. The function is generated on first call // and cached in the module. func (b *builder) getOrGenerateKeyEqualFunc(keyType types.Type) llvm.Value { name := hashmapKeyFuncName("hashmapKeyEqual", keyType) if fn := b.mod.NamedFunction(name); !fn.IsNil() { return fn } // Create the LLVM function type: // (x ptr, y ptr, n uintptr, context ptr) -> i1 fnType := llvm.FunctionType(b.ctx.Int1Type(), []llvm.Type{ b.dataPtrType, b.dataPtrType, b.uintptrType, b.dataPtrType, }, false) fn := llvm.AddFunction(b.mod, name, fnType) fn.SetLinkage(llvm.LinkOnceODRLinkage) fn.SetUnnamedAddr(true) b.addStandardAttributes(fn) // Generate the function body. savedBlock := b.GetInsertBlock() defer b.SetInsertPointAtEnd(savedBlock) entry := b.ctx.AddBasicBlock(fn, "entry") b.SetInsertPointAtEnd(entry) xPtr := fn.Param(0) yPtr := fn.Param(1) llvmKeyType := b.getLLVMType(keyType) result := b.generateKeyEqual(keyType, llvmKeyType, xPtr, yPtr, fn) b.CreateRet(result) return fn } // generateKeyHash generates IR that hashes a key value. Returns the i32 hash. func (b *builder) generateKeyHash(keyType types.Type, llvmKeyType llvm.Type, keyPtr llvm.Value, seed llvm.Value) llvm.Value { switch keyType := keyType.Underlying().(type) { case *types.Basic: if keyType.Info()&types.IsString != 0 { // Hash the string contents. The size parameter is unused by // hashmapStringPtrHash (it dereferences the string header to // get the actual length), but we pass it for signature // consistency with other hash functions. size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false) return b.createRuntimeCall("hashmapStringPtrHash", []llvm.Value{keyPtr, size, seed}, "hash") } if keyType.Info()&types.IsFloat != 0 { // Float hash: normalizes -0 to +0 before hashing. if keyType.Kind() == types.Float32 { return b.createRuntimeCall("hashmapFloat32Hash", []llvm.Value{keyPtr, seed}, "hash") } return b.createRuntimeCall("hashmapFloat64Hash", []llvm.Value{keyPtr, seed}, "hash") } if keyType.Info()&types.IsComplex != 0 { // Complex hash: hash real and imaginary parts as floats. if keyType.Kind() == types.Complex64 { realPtr := keyPtr imagPtr := b.CreateInBoundsGEP(b.ctx.Int8Type(), keyPtr, []llvm.Value{ llvm.ConstInt(b.uintptrType, 4, false), }, "") realHash := b.createRuntimeCall("hashmapFloat32Hash", []llvm.Value{realPtr, seed}, "hash.real") imagHash := b.createRuntimeCall("hashmapFloat32Hash", []llvm.Value{imagPtr, seed}, "hash.imag") return b.CreateXor(realHash, imagHash, "") } realPtr := keyPtr imagPtr := b.CreateInBoundsGEP(b.ctx.Int8Type(), keyPtr, []llvm.Value{ llvm.ConstInt(b.uintptrType, 8, false), }, "") realHash := b.createRuntimeCall("hashmapFloat64Hash", []llvm.Value{realPtr, seed}, "hash.real") imagHash := b.createRuntimeCall("hashmapFloat64Hash", []llvm.Value{imagPtr, seed}, "hash.imag") return b.CreateXor(realHash, imagHash, "") } // Integer/boolean: hash the raw bytes. size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false) return b.createRuntimeCall("hash32", []llvm.Value{keyPtr, size, seed}, "hash") case *types.Pointer, *types.Chan: // Pointers and channels: hash as raw pointer-sized bytes. size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false) return b.createRuntimeCall("hash32", []llvm.Value{keyPtr, size, seed}, "hash") case *types.Interface: // Interface: use runtime reflection-based hash. size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false) return b.createRuntimeCall("hashmapInterfacePtrHash", []llvm.Value{keyPtr, size, seed}, "hash") case *types.Struct: hash := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 0; i < keyType.NumFields(); i++ { if keyType.Field(i).Name() == "_" { continue // blank fields are ignored in Go equality } fieldType := keyType.Field(i).Type() llvmFieldType := b.getLLVMType(fieldType) if b.targetData.TypeAllocSize(llvmFieldType) == 0 { continue // skip zero-sized fields } idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false) fieldPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "") fieldHash := b.generateKeyHash(fieldType, llvmFieldType, fieldPtr, seed) hash = b.CreateXor(hash, fieldHash, "") } return hash case *types.Array: elemType := keyType.Elem() llvmElemType := b.getLLVMType(elemType) arrayLen := keyType.Len() if hashmapIsBinaryKey(elemType) { // All elements are binary-comparable; hash the entire array as raw bytes. size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false) return b.createRuntimeCall("hash32", []llvm.Value{keyPtr, size, seed}, "hash") } if arrayLen == 0 { return llvm.ConstInt(b.ctx.Int32Type(), 0, false) } if arrayLen <= hashArrayUnrollLimit { hash := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 0; i < int(arrayLen); i++ { idx := llvm.ConstInt(b.uintptrType, uint64(i), false) elemPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, idx}, "") elemHash := b.generateKeyHash(elemType, llvmElemType, elemPtr, seed) hash = b.CreateXor(hash, elemHash, "") } return hash } initHash := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) loopEntry := b.GetInsertBlock() loopBody := b.ctx.AddBasicBlock(loopEntry.Parent(), "hash.array.body") loopDone := b.ctx.AddBasicBlock(loopEntry.Parent(), "hash.array.done") b.CreateBr(loopBody) b.SetInsertPointAtEnd(loopBody) phiI := b.CreatePHI(b.uintptrType, "i") phiHash := b.CreatePHI(b.ctx.Int32Type(), "hash.acc") elemPtr := b.CreateInBoundsGEP(llvmKeyType, keyPtr, []llvm.Value{zero, phiI}, "") elemHash := b.generateKeyHash(elemType, llvmElemType, elemPtr, seed) newHash := b.CreateXor(phiHash, elemHash, "") nextI := b.CreateAdd(phiI, llvm.ConstInt(b.uintptrType, 1, false), "") cond := b.CreateICmp(llvm.IntULT, nextI, llvm.ConstInt(b.uintptrType, uint64(arrayLen), false), "") b.CreateCondBr(cond, loopBody, loopDone) bodyEnd := b.GetInsertBlock() phiI.AddIncoming([]llvm.Value{llvm.ConstInt(b.uintptrType, 0, false), nextI}, []llvm.BasicBlock{loopEntry, bodyEnd}) phiHash.AddIncoming([]llvm.Value{initHash, newHash}, []llvm.BasicBlock{loopEntry, bodyEnd}) b.SetInsertPointAtEnd(loopDone) return newHash default: panic(fmt.Sprintf("unhandled key type for hash generation: %T", keyType)) } } // generateKeyEqual generates IR that compares two key values for equality. // Returns an i1 result. func (b *builder) generateKeyEqual(keyType types.Type, llvmKeyType llvm.Type, xPtr, yPtr llvm.Value, fn llvm.Value) llvm.Value { switch keyType := keyType.Underlying().(type) { case *types.Basic: if keyType.Info()&types.IsString != 0 { // Compare strings: load both string headers and compare. xStr := b.CreateLoad(llvmKeyType, xPtr, "x.str") yStr := b.CreateLoad(llvmKeyType, yPtr, "y.str") return b.createRuntimeCall("stringEqual", []llvm.Value{xStr, yStr}, "eq") } if keyType.Info()&types.IsFloat != 0 { // Float equality: fcmp oeq handles -0==+0 (true) and NaN==NaN (false). xVal := b.CreateLoad(llvmKeyType, xPtr, "x.float") yVal := b.CreateLoad(llvmKeyType, yPtr, "y.float") return b.CreateFCmp(llvm.FloatOEQ, xVal, yVal, "eq") } if keyType.Info()&types.IsComplex != 0 { // Complex equality: both real and imaginary parts must be equal. var floatType llvm.Type if keyType.Kind() == types.Complex64 { floatType = b.ctx.FloatType() } else { floatType = b.ctx.DoubleType() } floatSize := b.targetData.TypeAllocSize(floatType) imagOffset := llvm.ConstInt(b.uintptrType, floatSize, false) // Real parts xReal := b.CreateLoad(floatType, xPtr, "x.real") yReal := b.CreateLoad(floatType, yPtr, "y.real") realEq := b.CreateFCmp(llvm.FloatOEQ, xReal, yReal, "eq.real") // Imaginary parts xImagPtr := b.CreateInBoundsGEP(b.ctx.Int8Type(), xPtr, []llvm.Value{imagOffset}, "") yImagPtr := b.CreateInBoundsGEP(b.ctx.Int8Type(), yPtr, []llvm.Value{imagOffset}, "") xImag := b.CreateLoad(floatType, xImagPtr, "x.imag") yImag := b.CreateLoad(floatType, yImagPtr, "y.imag") imagEq := b.CreateFCmp(llvm.FloatOEQ, xImag, yImag, "eq.imag") return b.CreateAnd(realEq, imagEq, "") } // Integer/boolean: compare raw bytes. size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false) return b.createRuntimeCall("memequal", []llvm.Value{xPtr, yPtr, size}, "eq") case *types.Pointer, *types.Chan: // Pointers and channels: compare as raw pointer-sized bytes. size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false) return b.createRuntimeCall("memequal", []llvm.Value{xPtr, yPtr, size}, "eq") case *types.Interface: // Interface: use runtime interface equality. size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false) return b.createRuntimeCall("hashmapInterfaceEqual", []llvm.Value{xPtr, yPtr, size}, "eq") case *types.Struct: result := llvm.ConstInt(b.ctx.Int1Type(), 1, false) // start with true zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 0; i < keyType.NumFields(); i++ { if keyType.Field(i).Name() == "_" { continue // blank fields are ignored in Go equality } fieldType := keyType.Field(i).Type() llvmFieldType := b.getLLVMType(fieldType) if b.targetData.TypeAllocSize(llvmFieldType) == 0 { continue // skip zero-sized fields } idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false) xFieldPtr := b.CreateInBoundsGEP(llvmKeyType, xPtr, []llvm.Value{zero, idx}, "") yFieldPtr := b.CreateInBoundsGEP(llvmKeyType, yPtr, []llvm.Value{zero, idx}, "") fieldEq := b.generateKeyEqual(fieldType, llvmFieldType, xFieldPtr, yFieldPtr, fn) result = b.CreateAnd(result, fieldEq, "") } return result case *types.Array: elemType := keyType.Elem() llvmElemType := b.getLLVMType(elemType) arrayLen := keyType.Len() if hashmapIsBinaryKey(elemType) { // All elements are binary-comparable; compare the entire array. size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(llvmKeyType), false) return b.createRuntimeCall("memequal", []llvm.Value{xPtr, yPtr, size}, "eq") } if arrayLen == 0 { return llvm.ConstInt(b.ctx.Int1Type(), 1, false) } if arrayLen <= hashArrayUnrollLimit { result := llvm.ConstInt(b.ctx.Int1Type(), 1, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 0; i < int(arrayLen); i++ { idx := llvm.ConstInt(b.uintptrType, uint64(i), false) xElemPtr := b.CreateInBoundsGEP(llvmKeyType, xPtr, []llvm.Value{zero, idx}, "") yElemPtr := b.CreateInBoundsGEP(llvmKeyType, yPtr, []llvm.Value{zero, idx}, "") elemEq := b.generateKeyEqual(elemType, llvmElemType, xElemPtr, yElemPtr, fn) result = b.CreateAnd(result, elemEq, "") } return result } zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) loopEntry := b.GetInsertBlock() loopBody := b.ctx.AddBasicBlock(loopEntry.Parent(), "eq.array.body") loopDone := b.ctx.AddBasicBlock(loopEntry.Parent(), "eq.array.done") b.CreateBr(loopBody) b.SetInsertPointAtEnd(loopBody) phiI := b.CreatePHI(b.uintptrType, "i") xElemPtr := b.CreateInBoundsGEP(llvmKeyType, xPtr, []llvm.Value{zero, phiI}, "") yElemPtr := b.CreateInBoundsGEP(llvmKeyType, yPtr, []llvm.Value{zero, phiI}, "") elemEq := b.generateKeyEqual(elemType, llvmElemType, xElemPtr, yElemPtr, fn) nextI := b.CreateAdd(phiI, llvm.ConstInt(b.uintptrType, 1, false), "") atEnd := b.CreateICmp(llvm.IntUGE, nextI, llvm.ConstInt(b.uintptrType, uint64(arrayLen), false), "") exitLoop := b.CreateOr(atEnd, b.CreateNot(elemEq, ""), "") b.CreateCondBr(exitLoop, loopDone, loopBody) bodyEnd := b.GetInsertBlock() phiI.AddIncoming([]llvm.Value{llvm.ConstInt(b.uintptrType, 0, false), nextI}, []llvm.BasicBlock{loopEntry, bodyEnd}) b.SetInsertPointAtEnd(loopDone) return elemEq default: panic(fmt.Sprintf("unhandled key type for equal generation: %T", keyType)) } }