diff options
author | Ayke van Laethem <[email protected]> | 2023-07-07 15:47:49 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2023-08-04 11:59:11 +0200 |
commit | f1e25a18d2584cda1d7b2d478e17a4358ee7daf0 (patch) | |
tree | 1cee732b73801015de741855ad047c71fcf3fa4a | |
parent | a2f886a67a645add0c2e42289e20f89fe402294d (diff) | |
download | tinygo-f1e25a18d2584cda1d7b2d478e17a4358ee7daf0.tar.gz tinygo-f1e25a18d2584cda1d7b2d478e17a4358ee7daf0.zip |
compiler: implement clear builtin for maps
-rw-r--r-- | compiler/compiler.go | 4 | ||||
-rw-r--r-- | compiler/map.go | 5 | ||||
-rw-r--r-- | compiler/testdata/go1.21.go | 4 | ||||
-rw-r--r-- | compiler/testdata/go1.21.ll | 9 | ||||
-rw-r--r-- | src/runtime/hashmap.go | 29 | ||||
-rw-r--r-- | testdata/go1.21.go | 11 | ||||
-rw-r--r-- | testdata/go1.21.txt | 2 |
7 files changed, 64 insertions, 0 deletions
diff --git a/compiler/compiler.go b/compiler/compiler.go index 6af6debe8..6dd43935a 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1632,6 +1632,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 c3d428902..21f0ee4a6 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 3d92b69b1..589486d02 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 73a368383..d65c75f4f 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 8a902a55d..dfbec300e 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 184bb2d8a..603bd06e2 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 459631a30..3edfdb456 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 |