blob: de2aa9f10e1f75c588f8c0ac0077ebb6591a7214 [file] [edit]
// run
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import "runtime"
type T struct {
a, b, c, d int
}
func (t *T) sum() int {
return t.a + t.b + t.c + t.d
}
//go:noinline
func newT(x int) *T {
t := new(T)
t.a = x
t.b = x
t.c = x
t.d = x
return t
}
//go:noinline
func intptr() *int {
return &g
}
var g int
//go:noinline
func f(x int) []*T {
const N = 40
var q [N]*T
for i := range N {
q[i] = newT(x)
}
var a [2]*int
for i := range a {
a[i] = intptr()
}
// reserve 2 registers we will later drop.
b := a[0]
c := a[1]
// Load pointers into registers (or spill slots)
p0 := q[0]
p1 := q[1]
p2 := q[2]
p3 := q[3]
p4 := q[4]
p5 := q[5]
p6 := q[6]
p7 := q[7]
p8 := q[8]
p9 := q[9]
p10 := q[10]
p11 := q[11]
p12 := q[12]
p13 := q[13]
p14 := q[14]
p15 := q[15]
p16 := q[16]
p17 := q[17]
p18 := q[18]
p19 := q[19]
p20 := q[20]
p21 := q[21]
p22 := q[22]
p23 := q[23]
p24 := q[24]
p25 := q[25]
p26 := q[26]
p27 := q[27]
p28 := q[28]
p29 := q[29]
p30 := q[30]
p31 := q[31]
p32 := q[32]
p33 := q[33]
p34 := q[34]
p35 := q[35]
p36 := q[36]
p37 := q[37]
p38 := q[38]
p39 := q[39]
// q is dead at this point. The only live reference to
// the objects allocated by newT are in the pXX variables.
// But if async preempted, we will scan the frame conservatively,
// which will find dead entries in q. Remove them.
q[0] = nil
q[1] = nil
q[2] = nil
q[3] = nil
q[4] = nil
q[5] = nil
q[6] = nil
q[7] = nil
q[8] = nil
q[9] = nil
q[10] = nil
q[11] = nil
q[12] = nil
q[13] = nil
q[14] = nil
q[15] = nil
q[16] = nil
q[17] = nil
q[18] = nil
q[19] = nil
q[20] = nil
q[21] = nil
q[22] = nil
q[23] = nil
q[24] = nil
q[25] = nil
q[26] = nil
q[27] = nil
q[28] = nil
q[29] = nil
q[30] = nil
q[31] = nil
q[32] = nil
q[33] = nil
q[34] = nil
q[35] = nil
q[36] = nil
q[37] = nil
q[38] = nil
q[39] = nil
// Some pXX is held in R30.
// If async preemption happens here, the pointer in R30
// will not get scanned and might cause the pointed-to
// object to be collected prematurely. It does not live
// anywhere else in this frame, and we will not scan
// this frame again later.
//
// Note that the write barrier has to be off for some of
// the newT calls for objects not to be allocated black.
// But it must be on here to get an async preempt.
// It must be off again by the final copy to not get put
// in a write barrier buffer somewhere.
// delay here to encourage async preemption
*b = 0
*c = 0
// The 2 registers holding b,c are now free. They
// can be used to implement this loop without spilling
// R30 to get a free register.
for range 10000 {
}
// Store registers back to stack memory (with no write barriers)
var z [N]*T
z[0] = p0
z[1] = p1
z[2] = p2
z[3] = p3
z[4] = p4
z[5] = p5
z[6] = p6
z[7] = p7
z[8] = p8
z[9] = p9
z[10] = p10
z[11] = p11
z[12] = p12
z[13] = p13
z[14] = p14
z[15] = p15
z[16] = p16
z[17] = p17
z[18] = p18
z[19] = p19
z[20] = p20
z[21] = p21
z[22] = p22
z[23] = p23
z[24] = p24
z[25] = p25
z[26] = p26
z[27] = p27
z[28] = p28
z[29] = p29
z[30] = p30
z[31] = p31
z[32] = p32
z[33] = p33
z[34] = p34
z[35] = p35
z[36] = p36
z[37] = p37
z[38] = p38
z[39] = p39
// Delay again, hoping GC finishes before we publish
// the unscanned pointer via the write barrier.
for range 10000 {
}
// Copy results to heap to return.
r := new([N]*T)
*r = z
return r[:]
}
func main() {
runtime.GOMAXPROCS(2)
c := make(chan bool)
for range 4 {
go goroutine(c)
}
for range 4 {
<-c
}
}
func goroutine(c chan bool) {
var all [][]*T
for x := range 10000 {
all = append(all, f(x))
}
for x, a := range all {
for _, t := range a {
if t.a != x || t.b != x || t.c != x || t.d != x {
panic("bad")
}
}
}
c <- true
}