]> Cypherpunks.ru repositories - gostls13.git/blob - src/runtime/gc_test.go
runtime: implement experiment to replace heap bitmap with alloc headers
[gostls13.git] / src / runtime / gc_test.go
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package runtime_test
6
7 import (
8         "fmt"
9         "internal/goexperiment"
10         "math/rand"
11         "os"
12         "reflect"
13         "runtime"
14         "runtime/debug"
15         "sort"
16         "strings"
17         "sync"
18         "sync/atomic"
19         "testing"
20         "time"
21         "unsafe"
22 )
23
24 func TestGcSys(t *testing.T) {
25         t.Skip("skipping known-flaky test; golang.org/issue/37331")
26         if os.Getenv("GOGC") == "off" {
27                 t.Skip("skipping test; GOGC=off in environment")
28         }
29         got := runTestProg(t, "testprog", "GCSys")
30         want := "OK\n"
31         if got != want {
32                 t.Fatalf("expected %q, but got %q", want, got)
33         }
34 }
35
36 func TestGcDeepNesting(t *testing.T) {
37         type T [2][2][2][2][2][2][2][2][2][2]*int
38         a := new(T)
39
40         // Prevent the compiler from applying escape analysis.
41         // This makes sure new(T) is allocated on heap, not on the stack.
42         t.Logf("%p", a)
43
44         a[0][0][0][0][0][0][0][0][0][0] = new(int)
45         *a[0][0][0][0][0][0][0][0][0][0] = 13
46         runtime.GC()
47         if *a[0][0][0][0][0][0][0][0][0][0] != 13 {
48                 t.Fail()
49         }
50 }
51
52 func TestGcMapIndirection(t *testing.T) {
53         defer debug.SetGCPercent(debug.SetGCPercent(1))
54         runtime.GC()
55         type T struct {
56                 a [256]int
57         }
58         m := make(map[T]T)
59         for i := 0; i < 2000; i++ {
60                 var a T
61                 a.a[0] = i
62                 m[a] = T{}
63         }
64 }
65
66 func TestGcArraySlice(t *testing.T) {
67         type X struct {
68                 buf     [1]byte
69                 nextbuf []byte
70                 next    *X
71         }
72         var head *X
73         for i := 0; i < 10; i++ {
74                 p := &X{}
75                 p.buf[0] = 42
76                 p.next = head
77                 if head != nil {
78                         p.nextbuf = head.buf[:]
79                 }
80                 head = p
81                 runtime.GC()
82         }
83         for p := head; p != nil; p = p.next {
84                 if p.buf[0] != 42 {
85                         t.Fatal("corrupted heap")
86                 }
87         }
88 }
89
90 func TestGcRescan(t *testing.T) {
91         type X struct {
92                 c     chan error
93                 nextx *X
94         }
95         type Y struct {
96                 X
97                 nexty *Y
98                 p     *int
99         }
100         var head *Y
101         for i := 0; i < 10; i++ {
102                 p := &Y{}
103                 p.c = make(chan error)
104                 if head != nil {
105                         p.nextx = &head.X
106                 }
107                 p.nexty = head
108                 p.p = new(int)
109                 *p.p = 42
110                 head = p
111                 runtime.GC()
112         }
113         for p := head; p != nil; p = p.nexty {
114                 if *p.p != 42 {
115                         t.Fatal("corrupted heap")
116                 }
117         }
118 }
119
120 func TestGcLastTime(t *testing.T) {
121         ms := new(runtime.MemStats)
122         t0 := time.Now().UnixNano()
123         runtime.GC()
124         t1 := time.Now().UnixNano()
125         runtime.ReadMemStats(ms)
126         last := int64(ms.LastGC)
127         if t0 > last || last > t1 {
128                 t.Fatalf("bad last GC time: got %v, want [%v, %v]", last, t0, t1)
129         }
130         pause := ms.PauseNs[(ms.NumGC+255)%256]
131         // Due to timer granularity, pause can actually be 0 on windows
132         // or on virtualized environments.
133         if pause == 0 {
134                 t.Logf("last GC pause was 0")
135         } else if pause > 10e9 {
136                 t.Logf("bad last GC pause: got %v, want [0, 10e9]", pause)
137         }
138 }
139
140 var hugeSink any
141
142 func TestHugeGCInfo(t *testing.T) {
143         // The test ensures that compiler can chew these huge types even on weakest machines.
144         // The types are not allocated at runtime.
145         if hugeSink != nil {
146                 // 400MB on 32 bots, 4TB on 64-bits.
147                 const n = (400 << 20) + (unsafe.Sizeof(uintptr(0))-4)<<40
148                 hugeSink = new([n]*byte)
149                 hugeSink = new([n]uintptr)
150                 hugeSink = new(struct {
151                         x float64
152                         y [n]*byte
153                         z []string
154                 })
155                 hugeSink = new(struct {
156                         x float64
157                         y [n]uintptr
158                         z []string
159                 })
160         }
161 }
162
163 func TestPeriodicGC(t *testing.T) {
164         if runtime.GOARCH == "wasm" {
165                 t.Skip("no sysmon on wasm yet")
166         }
167
168         // Make sure we're not in the middle of a GC.
169         runtime.GC()
170
171         var ms1, ms2 runtime.MemStats
172         runtime.ReadMemStats(&ms1)
173
174         // Make periodic GC run continuously.
175         orig := *runtime.ForceGCPeriod
176         *runtime.ForceGCPeriod = 0
177
178         // Let some periodic GCs happen. In a heavily loaded system,
179         // it's possible these will be delayed, so this is designed to
180         // succeed quickly if things are working, but to give it some
181         // slack if things are slow.
182         var numGCs uint32
183         const want = 2
184         for i := 0; i < 200 && numGCs < want; i++ {
185                 time.Sleep(5 * time.Millisecond)
186
187                 // Test that periodic GC actually happened.
188                 runtime.ReadMemStats(&ms2)
189                 numGCs = ms2.NumGC - ms1.NumGC
190         }
191         *runtime.ForceGCPeriod = orig
192
193         if numGCs < want {
194                 t.Fatalf("no periodic GC: got %v GCs, want >= 2", numGCs)
195         }
196 }
197
198 func TestGcZombieReporting(t *testing.T) {
199         // This test is somewhat sensitive to how the allocator works.
200         // Pointers in zombies slice may cross-span, thus we
201         // add invalidptr=0 for avoiding the badPointer check.
202         // See issue https://golang.org/issues/49613/
203         got := runTestProg(t, "testprog", "GCZombie", "GODEBUG=invalidptr=0")
204         want := "found pointer to free object"
205         if !strings.Contains(got, want) {
206                 t.Fatalf("expected %q in output, but got %q", want, got)
207         }
208 }
209
210 func TestGCTestMoveStackOnNextCall(t *testing.T) {
211         t.Parallel()
212         var onStack int
213         // GCTestMoveStackOnNextCall can fail in rare cases if there's
214         // a preemption. This won't happen many times in quick
215         // succession, so just retry a few times.
216         for retry := 0; retry < 5; retry++ {
217                 runtime.GCTestMoveStackOnNextCall()
218                 if moveStackCheck(t, &onStack, uintptr(unsafe.Pointer(&onStack))) {
219                         // Passed.
220                         return
221                 }
222         }
223         t.Fatal("stack did not move")
224 }
225
226 // This must not be inlined because the point is to force a stack
227 // growth check and move the stack.
228 //
229 //go:noinline
230 func moveStackCheck(t *testing.T, new *int, old uintptr) bool {
231         // new should have been updated by the stack move;
232         // old should not have.
233
234         // Capture new's value before doing anything that could
235         // further move the stack.
236         new2 := uintptr(unsafe.Pointer(new))
237
238         t.Logf("old stack pointer %x, new stack pointer %x", old, new2)
239         if new2 == old {
240                 // Check that we didn't screw up the test's escape analysis.
241                 if cls := runtime.GCTestPointerClass(unsafe.Pointer(new)); cls != "stack" {
242                         t.Fatalf("test bug: new (%#x) should be a stack pointer, not %s", new2, cls)
243                 }
244                 // This was a real failure.
245                 return false
246         }
247         return true
248 }
249
250 func TestGCTestMoveStackRepeatedly(t *testing.T) {
251         // Move the stack repeatedly to make sure we're not doubling
252         // it each time.
253         for i := 0; i < 100; i++ {
254                 runtime.GCTestMoveStackOnNextCall()
255                 moveStack1(false)
256         }
257 }
258
259 //go:noinline
260 func moveStack1(x bool) {
261         // Make sure this function doesn't get auto-nosplit.
262         if x {
263                 println("x")
264         }
265 }
266
267 func TestGCTestIsReachable(t *testing.T) {
268         var all, half []unsafe.Pointer
269         var want uint64
270         for i := 0; i < 16; i++ {
271                 // The tiny allocator muddies things, so we use a
272                 // scannable type.
273                 p := unsafe.Pointer(new(*int))
274                 all = append(all, p)
275                 if i%2 == 0 {
276                         half = append(half, p)
277                         want |= 1 << i
278                 }
279         }
280
281         got := runtime.GCTestIsReachable(all...)
282         if want != got {
283                 t.Fatalf("did not get expected reachable set; want %b, got %b", want, got)
284         }
285         runtime.KeepAlive(half)
286 }
287
288 var pointerClassBSS *int
289 var pointerClassData = 42
290
291 func TestGCTestPointerClass(t *testing.T) {
292         t.Parallel()
293         check := func(p unsafe.Pointer, want string) {
294                 t.Helper()
295                 got := runtime.GCTestPointerClass(p)
296                 if got != want {
297                         // Convert the pointer to a uintptr to avoid
298                         // escaping it.
299                         t.Errorf("for %#x, want class %s, got %s", uintptr(p), want, got)
300                 }
301         }
302         var onStack int
303         var notOnStack int
304         check(unsafe.Pointer(&onStack), "stack")
305         check(unsafe.Pointer(runtime.Escape(&notOnStack)), "heap")
306         check(unsafe.Pointer(&pointerClassBSS), "bss")
307         check(unsafe.Pointer(&pointerClassData), "data")
308         check(nil, "other")
309 }
310
311 func BenchmarkSetTypePtr(b *testing.B) {
312         benchSetType[*byte](b)
313 }
314
315 func BenchmarkSetTypePtr8(b *testing.B) {
316         benchSetType[[8]*byte](b)
317 }
318
319 func BenchmarkSetTypePtr16(b *testing.B) {
320         benchSetType[[16]*byte](b)
321 }
322
323 func BenchmarkSetTypePtr32(b *testing.B) {
324         benchSetType[[32]*byte](b)
325 }
326
327 func BenchmarkSetTypePtr64(b *testing.B) {
328         benchSetType[[64]*byte](b)
329 }
330
331 func BenchmarkSetTypePtr126(b *testing.B) {
332         benchSetType[[126]*byte](b)
333 }
334
335 func BenchmarkSetTypePtr128(b *testing.B) {
336         benchSetType[[128]*byte](b)
337 }
338
339 func BenchmarkSetTypePtrSlice(b *testing.B) {
340         benchSetTypeSlice[*byte](b, 1<<10)
341 }
342
343 type Node1 struct {
344         Value       [1]uintptr
345         Left, Right *byte
346 }
347
348 func BenchmarkSetTypeNode1(b *testing.B) {
349         benchSetType[Node1](b)
350 }
351
352 func BenchmarkSetTypeNode1Slice(b *testing.B) {
353         benchSetTypeSlice[Node1](b, 32)
354 }
355
356 type Node8 struct {
357         Value       [8]uintptr
358         Left, Right *byte
359 }
360
361 func BenchmarkSetTypeNode8(b *testing.B) {
362         benchSetType[Node8](b)
363 }
364
365 func BenchmarkSetTypeNode8Slice(b *testing.B) {
366         benchSetTypeSlice[Node8](b, 32)
367 }
368
369 type Node64 struct {
370         Value       [64]uintptr
371         Left, Right *byte
372 }
373
374 func BenchmarkSetTypeNode64(b *testing.B) {
375         benchSetType[Node64](b)
376 }
377
378 func BenchmarkSetTypeNode64Slice(b *testing.B) {
379         benchSetTypeSlice[Node64](b, 32)
380 }
381
382 type Node64Dead struct {
383         Left, Right *byte
384         Value       [64]uintptr
385 }
386
387 func BenchmarkSetTypeNode64Dead(b *testing.B) {
388         benchSetType[Node64Dead](b)
389 }
390
391 func BenchmarkSetTypeNode64DeadSlice(b *testing.B) {
392         benchSetTypeSlice[Node64Dead](b, 32)
393 }
394
395 type Node124 struct {
396         Value       [124]uintptr
397         Left, Right *byte
398 }
399
400 func BenchmarkSetTypeNode124(b *testing.B) {
401         benchSetType[Node124](b)
402 }
403
404 func BenchmarkSetTypeNode124Slice(b *testing.B) {
405         benchSetTypeSlice[Node124](b, 32)
406 }
407
408 type Node126 struct {
409         Value       [126]uintptr
410         Left, Right *byte
411 }
412
413 func BenchmarkSetTypeNode126(b *testing.B) {
414         benchSetType[Node126](b)
415 }
416
417 func BenchmarkSetTypeNode126Slice(b *testing.B) {
418         benchSetTypeSlice[Node126](b, 32)
419 }
420
421 type Node128 struct {
422         Value       [128]uintptr
423         Left, Right *byte
424 }
425
426 func BenchmarkSetTypeNode128(b *testing.B) {
427         benchSetType[Node128](b)
428 }
429
430 func BenchmarkSetTypeNode128Slice(b *testing.B) {
431         benchSetTypeSlice[Node128](b, 32)
432 }
433
434 type Node130 struct {
435         Value       [130]uintptr
436         Left, Right *byte
437 }
438
439 func BenchmarkSetTypeNode130(b *testing.B) {
440         benchSetType[Node130](b)
441 }
442
443 func BenchmarkSetTypeNode130Slice(b *testing.B) {
444         benchSetTypeSlice[Node130](b, 32)
445 }
446
447 type Node1024 struct {
448         Value       [1024]uintptr
449         Left, Right *byte
450 }
451
452 func BenchmarkSetTypeNode1024(b *testing.B) {
453         benchSetType[Node1024](b)
454 }
455
456 func BenchmarkSetTypeNode1024Slice(b *testing.B) {
457         benchSetTypeSlice[Node1024](b, 32)
458 }
459
460 func benchSetType[T any](b *testing.B) {
461         if goexperiment.AllocHeaders {
462                 b.Skip("not supported with allocation headers experiment")
463         }
464         b.SetBytes(int64(unsafe.Sizeof(*new(T))))
465         runtime.BenchSetType[T](b.N, b.ResetTimer)
466 }
467
468 func benchSetTypeSlice[T any](b *testing.B, len int) {
469         if goexperiment.AllocHeaders {
470                 b.Skip("not supported with allocation headers experiment")
471         }
472         b.SetBytes(int64(unsafe.Sizeof(*new(T)) * uintptr(len)))
473         runtime.BenchSetTypeSlice[T](b.N, b.ResetTimer, len)
474 }
475
476 func BenchmarkAllocation(b *testing.B) {
477         type T struct {
478                 x, y *byte
479         }
480         ngo := runtime.GOMAXPROCS(0)
481         work := make(chan bool, b.N+ngo)
482         result := make(chan *T)
483         for i := 0; i < b.N; i++ {
484                 work <- true
485         }
486         for i := 0; i < ngo; i++ {
487                 work <- false
488         }
489         for i := 0; i < ngo; i++ {
490                 go func() {
491                         var x *T
492                         for <-work {
493                                 for i := 0; i < 1000; i++ {
494                                         x = &T{}
495                                 }
496                         }
497                         result <- x
498                 }()
499         }
500         for i := 0; i < ngo; i++ {
501                 <-result
502         }
503 }
504
505 func TestPrintGC(t *testing.T) {
506         if testing.Short() {
507                 t.Skip("Skipping in short mode")
508         }
509         defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2))
510         done := make(chan bool)
511         go func() {
512                 for {
513                         select {
514                         case <-done:
515                                 return
516                         default:
517                                 runtime.GC()
518                         }
519                 }
520         }()
521         for i := 0; i < 1e4; i++ {
522                 func() {
523                         defer print("")
524                 }()
525         }
526         close(done)
527 }
528
529 func testTypeSwitch(x any) error {
530         switch y := x.(type) {
531         case nil:
532                 // ok
533         case error:
534                 return y
535         }
536         return nil
537 }
538
539 func testAssert(x any) error {
540         if y, ok := x.(error); ok {
541                 return y
542         }
543         return nil
544 }
545
546 func testAssertVar(x any) error {
547         var y, ok = x.(error)
548         if ok {
549                 return y
550         }
551         return nil
552 }
553
554 var a bool
555
556 //go:noinline
557 func testIfaceEqual(x any) {
558         if x == "abc" {
559                 a = true
560         }
561 }
562
563 func TestPageAccounting(t *testing.T) {
564         // Grow the heap in small increments. This used to drop the
565         // pages-in-use count below zero because of a rounding
566         // mismatch (golang.org/issue/15022).
567         const blockSize = 64 << 10
568         blocks := make([]*[blockSize]byte, (64<<20)/blockSize)
569         for i := range blocks {
570                 blocks[i] = new([blockSize]byte)
571         }
572
573         // Check that the running page count matches reality.
574         pagesInUse, counted := runtime.CountPagesInUse()
575         if pagesInUse != counted {
576                 t.Fatalf("mheap_.pagesInUse is %d, but direct count is %d", pagesInUse, counted)
577         }
578 }
579
580 func TestReadMemStats(t *testing.T) {
581         base, slow := runtime.ReadMemStatsSlow()
582         if base != slow {
583                 logDiff(t, "MemStats", reflect.ValueOf(base), reflect.ValueOf(slow))
584                 t.Fatal("memstats mismatch")
585         }
586 }
587
588 func logDiff(t *testing.T, prefix string, got, want reflect.Value) {
589         typ := got.Type()
590         switch typ.Kind() {
591         case reflect.Array, reflect.Slice:
592                 if got.Len() != want.Len() {
593                         t.Logf("len(%s): got %v, want %v", prefix, got, want)
594                         return
595                 }
596                 for i := 0; i < got.Len(); i++ {
597                         logDiff(t, fmt.Sprintf("%s[%d]", prefix, i), got.Index(i), want.Index(i))
598                 }
599         case reflect.Struct:
600                 for i := 0; i < typ.NumField(); i++ {
601                         gf, wf := got.Field(i), want.Field(i)
602                         logDiff(t, prefix+"."+typ.Field(i).Name, gf, wf)
603                 }
604         case reflect.Map:
605                 t.Fatal("not implemented: logDiff for map")
606         default:
607                 if got.Interface() != want.Interface() {
608                         t.Logf("%s: got %v, want %v", prefix, got, want)
609                 }
610         }
611 }
612
613 func BenchmarkReadMemStats(b *testing.B) {
614         var ms runtime.MemStats
615         const heapSize = 100 << 20
616         x := make([]*[1024]byte, heapSize/1024)
617         for i := range x {
618                 x[i] = new([1024]byte)
619         }
620
621         b.ResetTimer()
622         for i := 0; i < b.N; i++ {
623                 runtime.ReadMemStats(&ms)
624         }
625
626         runtime.KeepAlive(x)
627 }
628
629 func applyGCLoad(b *testing.B) func() {
630         // We’ll apply load to the runtime with maxProcs-1 goroutines
631         // and use one more to actually benchmark. It doesn't make sense
632         // to try to run this test with only 1 P (that's what
633         // BenchmarkReadMemStats is for).
634         maxProcs := runtime.GOMAXPROCS(-1)
635         if maxProcs == 1 {
636                 b.Skip("This benchmark can only be run with GOMAXPROCS > 1")
637         }
638
639         // Code to build a big tree with lots of pointers.
640         type node struct {
641                 children [16]*node
642         }
643         var buildTree func(depth int) *node
644         buildTree = func(depth int) *node {
645                 tree := new(node)
646                 if depth != 0 {
647                         for i := range tree.children {
648                                 tree.children[i] = buildTree(depth - 1)
649                         }
650                 }
651                 return tree
652         }
653
654         // Keep the GC busy by continuously generating large trees.
655         done := make(chan struct{})
656         var wg sync.WaitGroup
657         for i := 0; i < maxProcs-1; i++ {
658                 wg.Add(1)
659                 go func() {
660                         defer wg.Done()
661                         var hold *node
662                 loop:
663                         for {
664                                 hold = buildTree(5)
665                                 select {
666                                 case <-done:
667                                         break loop
668                                 default:
669                                 }
670                         }
671                         runtime.KeepAlive(hold)
672                 }()
673         }
674         return func() {
675                 close(done)
676                 wg.Wait()
677         }
678 }
679
680 func BenchmarkReadMemStatsLatency(b *testing.B) {
681         stop := applyGCLoad(b)
682
683         // Spend this much time measuring latencies.
684         latencies := make([]time.Duration, 0, 1024)
685
686         // Run for timeToBench hitting ReadMemStats continuously
687         // and measuring the latency.
688         b.ResetTimer()
689         var ms runtime.MemStats
690         for i := 0; i < b.N; i++ {
691                 // Sleep for a bit, otherwise we're just going to keep
692                 // stopping the world and no one will get to do anything.
693                 time.Sleep(100 * time.Millisecond)
694                 start := time.Now()
695                 runtime.ReadMemStats(&ms)
696                 latencies = append(latencies, time.Since(start))
697         }
698         // Make sure to stop the timer before we wait! The load created above
699         // is very heavy-weight and not easy to stop, so we could end up
700         // confusing the benchmarking framework for small b.N.
701         b.StopTimer()
702         stop()
703
704         // Disable the default */op metrics.
705         // ns/op doesn't mean anything because it's an average, but we
706         // have a sleep in our b.N loop above which skews this significantly.
707         b.ReportMetric(0, "ns/op")
708         b.ReportMetric(0, "B/op")
709         b.ReportMetric(0, "allocs/op")
710
711         // Sort latencies then report percentiles.
712         sort.Slice(latencies, func(i, j int) bool {
713                 return latencies[i] < latencies[j]
714         })
715         b.ReportMetric(float64(latencies[len(latencies)*50/100]), "p50-ns")
716         b.ReportMetric(float64(latencies[len(latencies)*90/100]), "p90-ns")
717         b.ReportMetric(float64(latencies[len(latencies)*99/100]), "p99-ns")
718 }
719
720 func TestUserForcedGC(t *testing.T) {
721         // Test that runtime.GC() triggers a GC even if GOGC=off.
722         defer debug.SetGCPercent(debug.SetGCPercent(-1))
723
724         var ms1, ms2 runtime.MemStats
725         runtime.ReadMemStats(&ms1)
726         runtime.GC()
727         runtime.ReadMemStats(&ms2)
728         if ms1.NumGC == ms2.NumGC {
729                 t.Fatalf("runtime.GC() did not trigger GC")
730         }
731         if ms1.NumForcedGC == ms2.NumForcedGC {
732                 t.Fatalf("runtime.GC() was not accounted in NumForcedGC")
733         }
734 }
735
736 func writeBarrierBenchmark(b *testing.B, f func()) {
737         runtime.GC()
738         var ms runtime.MemStats
739         runtime.ReadMemStats(&ms)
740         //b.Logf("heap size: %d MB", ms.HeapAlloc>>20)
741
742         // Keep GC running continuously during the benchmark, which in
743         // turn keeps the write barrier on continuously.
744         var stop uint32
745         done := make(chan bool)
746         go func() {
747                 for atomic.LoadUint32(&stop) == 0 {
748                         runtime.GC()
749                 }
750                 close(done)
751         }()
752         defer func() {
753                 atomic.StoreUint32(&stop, 1)
754                 <-done
755         }()
756
757         b.ResetTimer()
758         f()
759         b.StopTimer()
760 }
761
762 func BenchmarkWriteBarrier(b *testing.B) {
763         if runtime.GOMAXPROCS(-1) < 2 {
764                 // We don't want GC to take our time.
765                 b.Skip("need GOMAXPROCS >= 2")
766         }
767
768         // Construct a large tree both so the GC runs for a while and
769         // so we have a data structure to manipulate the pointers of.
770         type node struct {
771                 l, r *node
772         }
773         var wbRoots []*node
774         var mkTree func(level int) *node
775         mkTree = func(level int) *node {
776                 if level == 0 {
777                         return nil
778                 }
779                 n := &node{mkTree(level - 1), mkTree(level - 1)}
780                 if level == 10 {
781                         // Seed GC with enough early pointers so it
782                         // doesn't start termination barriers when it
783                         // only has the top of the tree.
784                         wbRoots = append(wbRoots, n)
785                 }
786                 return n
787         }
788         const depth = 22 // 64 MB
789         root := mkTree(22)
790
791         writeBarrierBenchmark(b, func() {
792                 var stack [depth]*node
793                 tos := -1
794
795                 // There are two write barriers per iteration, so i+=2.
796                 for i := 0; i < b.N; i += 2 {
797                         if tos == -1 {
798                                 stack[0] = root
799                                 tos = 0
800                         }
801
802                         // Perform one step of reversing the tree.
803                         n := stack[tos]
804                         if n.l == nil {
805                                 tos--
806                         } else {
807                                 n.l, n.r = n.r, n.l
808                                 stack[tos] = n.l
809                                 stack[tos+1] = n.r
810                                 tos++
811                         }
812
813                         if i%(1<<12) == 0 {
814                                 // Avoid non-preemptible loops (see issue #10958).
815                                 runtime.Gosched()
816                         }
817                 }
818         })
819
820         runtime.KeepAlive(wbRoots)
821 }
822
823 func BenchmarkBulkWriteBarrier(b *testing.B) {
824         if runtime.GOMAXPROCS(-1) < 2 {
825                 // We don't want GC to take our time.
826                 b.Skip("need GOMAXPROCS >= 2")
827         }
828
829         // Construct a large set of objects we can copy around.
830         const heapSize = 64 << 20
831         type obj [16]*byte
832         ptrs := make([]*obj, heapSize/unsafe.Sizeof(obj{}))
833         for i := range ptrs {
834                 ptrs[i] = new(obj)
835         }
836
837         writeBarrierBenchmark(b, func() {
838                 const blockSize = 1024
839                 var pos int
840                 for i := 0; i < b.N; i += blockSize {
841                         // Rotate block.
842                         block := ptrs[pos : pos+blockSize]
843                         first := block[0]
844                         copy(block, block[1:])
845                         block[blockSize-1] = first
846
847                         pos += blockSize
848                         if pos+blockSize > len(ptrs) {
849                                 pos = 0
850                         }
851
852                         runtime.Gosched()
853                 }
854         })
855
856         runtime.KeepAlive(ptrs)
857 }
858
859 func BenchmarkScanStackNoLocals(b *testing.B) {
860         var ready sync.WaitGroup
861         teardown := make(chan bool)
862         for j := 0; j < 10; j++ {
863                 ready.Add(1)
864                 go func() {
865                         x := 100000
866                         countpwg(&x, &ready, teardown)
867                 }()
868         }
869         ready.Wait()
870         b.ResetTimer()
871         for i := 0; i < b.N; i++ {
872                 b.StartTimer()
873                 runtime.GC()
874                 runtime.GC()
875                 b.StopTimer()
876         }
877         close(teardown)
878 }
879
880 func BenchmarkMSpanCountAlloc(b *testing.B) {
881         // Allocate one dummy mspan for the whole benchmark.
882         s := runtime.AllocMSpan()
883         defer runtime.FreeMSpan(s)
884
885         // n is the number of bytes to benchmark against.
886         // n must always be a multiple of 8, since gcBits is
887         // always rounded up 8 bytes.
888         for _, n := range []int{8, 16, 32, 64, 128} {
889                 b.Run(fmt.Sprintf("bits=%d", n*8), func(b *testing.B) {
890                         // Initialize a new byte slice with pseduo-random data.
891                         bits := make([]byte, n)
892                         rand.Read(bits)
893
894                         b.ResetTimer()
895                         for i := 0; i < b.N; i++ {
896                                 runtime.MSpanCountAlloc(s, bits)
897                         }
898                 })
899         }
900 }
901
902 func countpwg(n *int, ready *sync.WaitGroup, teardown chan bool) {
903         if *n == 0 {
904                 ready.Done()
905                 <-teardown
906                 return
907         }
908         *n--
909         countpwg(n, ready, teardown)
910 }
911
912 func TestMemoryLimit(t *testing.T) {
913         if testing.Short() {
914                 t.Skip("stress test that takes time to run")
915         }
916         if runtime.NumCPU() < 4 {
917                 t.Skip("want at least 4 CPUs for this test")
918         }
919         got := runTestProg(t, "testprog", "GCMemoryLimit")
920         want := "OK\n"
921         if got != want {
922                 t.Fatalf("expected %q, but got %q", want, got)
923         }
924 }
925
926 func TestMemoryLimitNoGCPercent(t *testing.T) {
927         if testing.Short() {
928                 t.Skip("stress test that takes time to run")
929         }
930         if runtime.NumCPU() < 4 {
931                 t.Skip("want at least 4 CPUs for this test")
932         }
933         got := runTestProg(t, "testprog", "GCMemoryLimitNoGCPercent")
934         want := "OK\n"
935         if got != want {
936                 t.Fatalf("expected %q, but got %q", want, got)
937         }
938 }
939
940 func TestMyGenericFunc(t *testing.T) {
941         runtime.MyGenericFunc[int]()
942 }