compiler,runtime: implement maps for arbitrary keys

This implementation simply casts types without special support to an
interface, to make the implementation simpler and possibly reducing the
code size too. It will likely be slower than the canonical Go
implementation though (which builds special compare and hash functions
at compile time).
This commit is contained in:
Ayke van Laethem
2020-01-20 15:54:44 +01:00
committed by Ron Evans
parent 440dc8ed4e
commit 0d34f933eb
4 changed files with 171 additions and 6 deletions
+40 -5
View File
@@ -14,8 +14,20 @@ import (
// initializing an appropriately sized object.
func (c *Compiler) emitMakeMap(frame *Frame, expr *ssa.MakeMap) (llvm.Value, error) {
mapType := expr.Type().Underlying().(*types.Map)
llvmKeyType := c.getLLVMType(mapType.Key().Underlying())
keyType := mapType.Key().Underlying()
llvmValueType := c.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)
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = c.getLLVMType(keyType)
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = c.getLLVMRuntimeType("_interface")
}
keySize := c.targetData.TypeAllocSize(llvmKeyType)
valueSize := c.targetData.TypeAllocSize(llvmValueType)
llvmKeySize := llvm.ConstInt(c.ctx.Int8Type(), keySize, false)
@@ -43,6 +55,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
// 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, key, mapValuePtr}
@@ -58,8 +71,14 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else {
// Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// 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)
}
params := []llvm.Value{m, itfKey, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapInterfaceGet", params, "")
}
// Load the resulting value from the hashmap. The value is set to the zero
@@ -93,7 +112,14 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
c.createRuntimeCall("hashmapBinarySet", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
} else {
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// 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)
}
params := []llvm.Value{m, itfKey, valuePtr}
c.createRuntimeCall("hashmapInterfaceSet", params, "")
}
c.emitLifetimeEnd(valuePtr, valueSize)
}
@@ -113,7 +139,16 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
c.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
// 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)
}
params := []llvm.Value{m, itfKey}
c.createRuntimeCall("hashmapInterfaceDelete", params, "")
return nil
}
}