aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2023-07-07 15:47:49 +0200
committerRon Evans <[email protected]>2023-08-04 11:59:11 +0200
commitf1e25a18d2584cda1d7b2d478e17a4358ee7daf0 (patch)
tree1cee732b73801015de741855ad047c71fcf3fa4a
parenta2f886a67a645add0c2e42289e20f89fe402294d (diff)
downloadtinygo-f1e25a18d2584cda1d7b2d478e17a4358ee7daf0.tar.gz
tinygo-f1e25a18d2584cda1d7b2d478e17a4358ee7daf0.zip
compiler: implement clear builtin for maps
-rw-r--r--compiler/compiler.go4
-rw-r--r--compiler/map.go5
-rw-r--r--compiler/testdata/go1.21.go4
-rw-r--r--compiler/testdata/go1.21.ll9
-rw-r--r--src/runtime/hashmap.go29
-rw-r--r--testdata/go1.21.go11
-rw-r--r--testdata/go1.21.txt2
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