compiler,runtime: support operations on nil map
The index expression and delete keyword are valid on nil maps, so the runtime must be modified to support this.
Этот коммит содержится в:
родитель
53688c86c8
коммит
4dfc289ae5
4 изменённых файлов: 37 добавлений и 12 удалений
|
@ -51,14 +51,23 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||||
// Allocate the memory for the resulting type. Do not zero this memory: it
|
// 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
|
// will be zeroed by the hashmap get implementation if the key is not
|
||||||
// present in the map.
|
// present in the map.
|
||||||
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
mapValueAlloca, mapValuePtr, mapValueAllocaSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
||||||
|
|
||||||
|
// 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 := mapValueAllocaSize
|
||||||
|
if mapValueSize.Type().IntTypeWidth() > c.uintptrType.IntTypeWidth() {
|
||||||
|
mapValueSize = llvm.ConstTrunc(mapValueSize, c.uintptrType)
|
||||||
|
}
|
||||||
|
|
||||||
// Do the lookup. How it is done depends on the key type.
|
// Do the lookup. How it is done depends on the key type.
|
||||||
var commaOkValue llvm.Value
|
var commaOkValue llvm.Value
|
||||||
keyType = keyType.Underlying()
|
keyType = keyType.Underlying()
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||||
// key is a string
|
// key is a string
|
||||||
params := []llvm.Value{m, key, mapValuePtr}
|
params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
|
||||||
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
|
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
// key can be compared with runtime.memequal
|
// key can be compared with runtime.memequal
|
||||||
|
@ -67,7 +76,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||||
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||||
c.builder.CreateStore(key, mapKeyAlloca)
|
c.builder.CreateStore(key, mapKeyAlloca)
|
||||||
// Fetch the value from the hashmap.
|
// Fetch the value from the hashmap.
|
||||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
|
||||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||||
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
||||||
} else {
|
} else {
|
||||||
|
@ -77,14 +86,14 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||||
// Not already an interface, so convert it to an interface now.
|
// Not already an interface, so convert it to an interface now.
|
||||||
itfKey = c.parseMakeInterface(key, keyType, pos)
|
itfKey = c.parseMakeInterface(key, keyType, pos)
|
||||||
}
|
}
|
||||||
params := []llvm.Value{m, itfKey, mapValuePtr}
|
params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize}
|
||||||
commaOkValue = c.createRuntimeCall("hashmapInterfaceGet", params, "")
|
commaOkValue = c.createRuntimeCall("hashmapInterfaceGet", params, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the resulting value from the hashmap. The value is set to the zero
|
// Load the resulting value from the hashmap. The value is set to the zero
|
||||||
// value if the key doesn't exist in the hashmap.
|
// value if the key doesn't exist in the hashmap.
|
||||||
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||||
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
|
c.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
|
||||||
|
|
||||||
if commaOk {
|
if commaOk {
|
||||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||||
|
|
|
@ -166,7 +166,14 @@ func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash u
|
||||||
|
|
||||||
// Get the value of a specified key, or zero the value if not found.
|
// Get the value of a specified key, or zero the value if not found.
|
||||||
//go:nobounds
|
//go:nobounds
|
||||||
func hashmapGet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) bool {
|
func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) bool {
|
||||||
|
if m == nil {
|
||||||
|
// Getting a value out of a nil map is valid. From the spec:
|
||||||
|
// > if the map is nil or does not contain such an entry, a[x] is the
|
||||||
|
// > zero value for the element type of M
|
||||||
|
memzero(value, uintptr(valueSize))
|
||||||
|
return false
|
||||||
|
}
|
||||||
numBuckets := uintptr(1) << m.bucketBits
|
numBuckets := uintptr(1) << m.bucketBits
|
||||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||||
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||||
|
@ -207,6 +214,12 @@ func hashmapGet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
||||||
// map.
|
// map.
|
||||||
//go:nobounds
|
//go:nobounds
|
||||||
func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
||||||
|
if m == nil {
|
||||||
|
// The delete builtin is defined even when the map is nil. From the spec:
|
||||||
|
// > If the map m is nil or the element m[k] does not exist, delete is a
|
||||||
|
// > no-op.
|
||||||
|
return
|
||||||
|
}
|
||||||
numBuckets := uintptr(1) << m.bucketBits
|
numBuckets := uintptr(1) << m.bucketBits
|
||||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||||
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
|
||||||
|
@ -284,9 +297,9 @@ func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
|
||||||
hashmapSet(m, key, value, hash, memequal)
|
hashmapSet(m, key, value, hash, memequal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer) bool {
|
func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool {
|
||||||
hash := hashmapHash(key, uintptr(m.keySize))
|
hash := hashmapHash(key, uintptr(m.keySize))
|
||||||
return hashmapGet(m, key, value, hash, memequal)
|
return hashmapGet(m, key, value, valueSize, hash, memequal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
|
func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
|
||||||
|
@ -310,9 +323,9 @@ func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) {
|
||||||
hashmapSet(m, unsafe.Pointer(&key), value, hash, hashmapStringEqual)
|
hashmapSet(m, unsafe.Pointer(&key), value, hash, hashmapStringEqual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer) bool {
|
func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool {
|
||||||
hash := hashmapStringHash(key)
|
hash := hashmapStringHash(key)
|
||||||
return hashmapGet(m, unsafe.Pointer(&key), value, hash, hashmapStringEqual)
|
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash, hashmapStringEqual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashmapStringDelete(m *hashmap, key string) {
|
func hashmapStringDelete(m *hashmap, key string) {
|
||||||
|
@ -381,9 +394,9 @@ func hashmapInterfaceSet(m *hashmap, key interface{}, value unsafe.Pointer) {
|
||||||
hashmapSet(m, unsafe.Pointer(&key), value, hash, hashmapInterfaceEqual)
|
hashmapSet(m, unsafe.Pointer(&key), value, hash, hashmapInterfaceEqual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer) bool {
|
func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valueSize uintptr) bool {
|
||||||
hash := hashmapInterfaceHash(key)
|
hash := hashmapInterfaceHash(key)
|
||||||
return hashmapGet(m, unsafe.Pointer(&key), value, hash, hashmapInterfaceEqual)
|
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash, hashmapInterfaceEqual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hashmapInterfaceDelete(m *hashmap, key interface{}) {
|
func hashmapInterfaceDelete(m *hashmap, key interface{}) {
|
||||||
|
|
2
testdata/map.go
предоставленный
2
testdata/map.go
предоставленный
|
@ -44,6 +44,8 @@ func main() {
|
||||||
var nilmap map[string]int
|
var nilmap map[string]int
|
||||||
println(m == nil, m != nil, len(m))
|
println(m == nil, m != nil, len(m))
|
||||||
println(nilmap == nil, nilmap != nil, len(nilmap))
|
println(nilmap == nil, nilmap != nil, len(nilmap))
|
||||||
|
delete(nilmap, "foo")
|
||||||
|
println("nilmap:", nilmap["bar"])
|
||||||
println(testmapIntInt[2])
|
println(testmapIntInt[2])
|
||||||
testmapIntInt[2] = 42
|
testmapIntInt[2] = 42
|
||||||
println(testmapIntInt[2])
|
println(testmapIntInt[2])
|
||||||
|
|
1
testdata/map.txt
предоставленный
1
testdata/map.txt
предоставленный
|
@ -50,6 +50,7 @@ lookup with comma-ok: eight 8 true
|
||||||
lookup with comma-ok: nokey 0 false
|
lookup with comma-ok: nokey 0 false
|
||||||
false true 2
|
false true 2
|
||||||
true false 0
|
true false 0
|
||||||
|
nilmap: 0
|
||||||
4
|
4
|
||||||
42
|
42
|
||||||
4321
|
4321
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче