diff --git a/compiler/compiler.go b/compiler/compiler.go index 6af6debe..6dd43935 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1631,6 +1631,10 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c }, "") call.AddCallSiteAttribute(1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elementAlign))) + return llvm.Value{}, nil + case *types.Map: + m := argValues[0] + b.createMapClear(m) return llvm.Value{}, nil default: return llvm.Value{}, b.makeError(pos, "unsupported type in clear builtin: "+typ.String()) diff --git a/compiler/map.go b/compiler/map.go index c3d42890..21f0ee4a 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -185,6 +185,11 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok } } +// 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. diff --git a/compiler/testdata/go1.21.go b/compiler/testdata/go1.21.go index 3d92b69b..589486d0 100644 --- a/compiler/testdata/go1.21.go +++ b/compiler/testdata/go1.21.go @@ -59,3 +59,7 @@ func clearSlice(s []int) { func clearZeroSizedSlice(s []struct{}) { clear(s) } + +func clearMap(m map[string]int) { + clear(m) +} diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll index 73a36838..d65c75f4 100644 --- a/compiler/testdata/go1.21.ll +++ b/compiler/testdata/go1.21.ll @@ -147,6 +147,15 @@ entry: ret void } +; Function Attrs: nounwind +define hidden void @main.clearMap(ptr dereferenceable_or_null(40) %m, ptr %context) unnamed_addr #2 { +entry: + call void @runtime.hashmapClear(ptr %m, ptr undef) #5 + ret void +} + +declare void @runtime.hashmapClear(ptr dereferenceable_or_null(40), ptr) #1 + ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn declare i32 @llvm.smin.i32(i32, i32) #4 diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 8a902a55..dfbec300 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -91,6 +91,35 @@ func hashmapMakeUnsafePointer(keySize, valueSize uintptr, sizeHint uintptr, alg return (unsafe.Pointer)(hashmapMake(keySize, valueSize, sizeHint, alg)) } +// Remove all entries from the map, without actually deallocating the space for +// it. This is used for the clear builtin, and can be used to reuse a map (to +// avoid extra heap allocations). +func hashmapClear(m *hashmap) { + if m == nil { + // Nothing to do. According to the spec: + // > If the map or slice is nil, clear is a no-op. + return + } + + m.count = 0 + numBuckets := uintptr(1) << m.bucketBits + bucketSize := hashmapBucketSize(m) + for i := uintptr(0); i < numBuckets; i++ { + bucket := hashmapBucketAddr(m, m.buckets, i) + for bucket != nil { + // Clear the tophash, to mark these keys/values as removed. + bucket.tophash = [8]uint8{} + + // Clear the keys and values in the bucket so that the GC won't pin + // these allocations. + memzero(unsafe.Add(unsafe.Pointer(bucket), unsafe.Sizeof(hashmapBucket{})), bucketSize-unsafe.Sizeof(hashmapBucket{})) + + // Move on to the next bucket in the chain. + bucket = bucket.next + } + } +} + func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool { switch alg { case hashmapAlgorithmBinary: diff --git a/testdata/go1.21.go b/testdata/go1.21.go index 184bb2d8..603bd06e 100644 --- a/testdata/go1.21.go +++ b/testdata/go1.21.go @@ -15,4 +15,15 @@ func main() { s := []int{1, 2, 3, 4, 5} clear(s[:3]) println("cleared s[:3]:", s[0], s[1], s[2], s[3], s[4]) + + // The clear builtin, for maps. + m := map[int]string{ + 1: "one", + 2: "two", + 3: "three", + } + clear(m) + println("cleared map:", m[1], m[2], m[3], len(m)) + m[4] = "four" + println("added to cleared map:", m[1], m[2], m[3], m[4], len(m)) } diff --git a/testdata/go1.21.txt b/testdata/go1.21.txt index 459631a3..3edfdb45 100644 --- a/testdata/go1.21.txt +++ b/testdata/go1.21.txt @@ -1,3 +1,5 @@ min/max: -3 5 min/max: -3.000000e+000 +5.000000e+000 cleared s[:3]: 0 0 0 4 5 +cleared map: 0 +added to cleared map: four 1