Browse Source

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.
pull/926/head
Ayke van Laethem 5 years ago
committed by Ayke
parent
commit
4dfc289ae5
  1. 19
      compiler/map.go
  2. 27
      src/runtime/hashmap.go
  3. 2
      testdata/map.go
  4. 1
      testdata/map.txt

19
compiler/map.go

@ -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
// will be zeroed by the hashmap get implementation if the key is not
// 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.
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}
params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// 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")
c.builder.CreateStore(key, mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} 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.
itfKey = c.parseMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, mapValuePtr}
params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize}
commaOkValue = c.createRuntimeCall("hashmapInterfaceGet", params, "")
}
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
c.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
if commaOk {
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))

27
src/runtime/hashmap.go

@ -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.
//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
bucketNumber := (uintptr(hash) & (numBuckets - 1))
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.
//go:nobounds
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
bucketNumber := (uintptr(hash) & (numBuckets - 1))
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)
}
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))
return hashmapGet(m, key, value, hash, memequal)
return hashmapGet(m, key, value, valueSize, hash, memequal)
}
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)
}
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)
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) {
@ -381,9 +394,9 @@ func hashmapInterfaceSet(m *hashmap, key interface{}, value unsafe.Pointer) {
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)
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{}) {

2
testdata/map.go

@ -44,6 +44,8 @@ func main() {
var nilmap map[string]int
println(m == nil, m != nil, len(m))
println(nilmap == nil, nilmap != nil, len(nilmap))
delete(nilmap, "foo")
println("nilmap:", nilmap["bar"])
println(testmapIntInt[2])
testmapIntInt[2] = 42
println(testmapIntInt[2])

1
testdata/map.txt

@ -50,6 +50,7 @@ lookup with comma-ok: eight 8 true
lookup with comma-ok: nokey 0 false
false true 2
true false 0
nilmap: 0
4
42
4321

Loading…
Cancel
Save