Files
tinygo/testdata/gc.go
T
2026-09-10 08:49:07 +02:00

226 lines
5.0 KiB
Go

package main
import (
"reflect"
"runtime"
)
var xorshift32State uint32 = 1
func xorshift32(x uint32) uint32 {
// Algorithm "xor" from p. 4 of Marsaglia, "Xorshift RNGs"
x ^= x << 13
x ^= x >> 17
x ^= x << 5
return x
}
func randuint32() uint32 {
xorshift32State = xorshift32(xorshift32State)
return xorshift32State
}
func main() {
testNonPointerHeap()
testGlobalMapRoots()
testGlobalChannelRoots()
testReflectRoots()
testKeepAlive()
}
var scalarSlices [4][]byte
var randSeeds [4]uint32
func testNonPointerHeap() {
maxSliceSize := uint32(1024)
if ^uintptr(0) <= 0xffff {
// 16-bit and lower devices, such as AVR.
// Heap size is a real issue there, while it is still useful to run
// these tests. Therefore, lower the max slice size.
maxSliceSize = 64
}
// Allocate roughly 0.5MB of memory.
for i := 0; i < 1000; i++ {
// Pick a random index that the optimizer can't predict.
index := randuint32() % 4
// Check whether the contents of the previous allocation was correct.
rand := randSeeds[index]
for _, b := range scalarSlices[index] {
rand = xorshift32(rand)
if b != byte(rand) {
panic("memory was overwritten!")
}
}
// Allocate a randomly-sized slice, randomly sliced to be smaller.
sliceLen := randuint32() % maxSliceSize
slice := make([]byte, sliceLen)
cutLen := randuint32() % maxSliceSize
if cutLen < sliceLen {
slice = slice[cutLen:]
}
scalarSlices[index] = slice
// Fill the slice with a pattern that looks random but is easily
// calculated and verified.
rand = randuint32() + 1
randSeeds[index] = rand
for i := 0; i < len(slice); i++ {
rand = xorshift32(rand)
slice[i] = byte(rand)
}
}
println("ok")
}
func testKeepAlive() {
// There isn't much we can test, but at least we can test that
// runtime.KeepAlive compiles correctly.
var x int
runtime.KeepAlive(&x)
}
type globalMapObject struct {
marker int
data [64]byte
}
var globalMap = make(map[int]*globalMapObject)
var globalChannel chan *globalMapObject
var globalPointerSlice []*globalMapObject
var globalGCClobber any
type globalMapLargeKey struct {
object *globalMapObject
data [129]byte
}
type globalMapLargeValue struct {
object *globalMapObject
data [129]byte
}
var globalLargeKeyMap = make(map[globalMapLargeKey]int)
var globalLargeValueMap = make(map[int]globalMapLargeValue)
//go:noinline
func populateGlobalMaps() {
for i := 0; i < 32; i++ {
globalMap[i] = &globalMapObject{marker: 100 + i}
globalPointerSlice = append(globalPointerSlice, &globalMapObject{marker: 800 + i})
}
globalLargeKeyMap[globalMapLargeKey{
object: &globalMapObject{marker: 200},
}] = 1
globalLargeValueMap[0] = globalMapLargeValue{
object: &globalMapObject{marker: 300},
}
}
func testGlobalMapRoots() {
populateGlobalMaps()
runtime.GC()
for i := 0; i < 100; i++ {
globalGCClobber = new(globalMapObject)
}
runtime.GC()
for i := 0; i < 32; i++ {
if globalMap[i].marker != 100+i {
panic("global map value was collected")
}
}
for key := range globalLargeKeyMap {
if key.object.marker != 200 {
panic("indirect global map key was collected")
}
}
if globalLargeValueMap[0].object.marker != 300 {
panic("indirect global map value was collected")
}
for i, object := range globalPointerSlice {
if object.marker != 800+i {
panic("global slice value was collected")
}
}
}
//go:noinline
func populateGlobalChannel() {
globalChannel = make(chan *globalMapObject, 4)
globalChannel <- &globalMapObject{marker: 400}
}
func testGlobalChannelRoots() {
populateGlobalChannel()
runtime.GC()
for i := 0; i < 100; i++ {
globalGCClobber = new(globalMapObject)
}
runtime.GC()
if (<-globalChannel).marker != 400 {
panic("global channel value was collected")
}
}
type reflectRootObject struct {
marker int
child *reflectRootObject
data [128]byte
}
type reflectMapKey struct {
object *reflectRootObject
data [129]byte
}
type reflectMapValue struct {
object *reflectRootObject
data [129]byte
}
type reflectRootMap map[reflectMapKey]reflectMapValue
var globalReflectObject *reflectRootObject
var globalReflectMap reflectRootMap
//go:noinline
func populateReflectRoots() {
value := reflect.New(reflect.TypeOf(reflectRootObject{}))
globalReflectObject = value.Interface().(*reflectRootObject)
globalReflectObject.child = &reflectRootObject{marker: 500}
mapValue := reflect.MakeMapWithSize(reflect.TypeOf(globalReflectMap), 1)
mapValue.SetMapIndex(
reflect.ValueOf(reflectMapKey{object: &reflectRootObject{marker: 600}}),
reflect.ValueOf(reflectMapValue{object: &reflectRootObject{marker: 700}}),
)
globalReflectMap = mapValue.Interface().(reflectRootMap)
}
func testReflectRoots() {
populateReflectRoots()
runtime.GC()
for i := 0; i < 100; i++ {
globalGCClobber = new(reflectRootObject)
}
runtime.GC()
if globalReflectObject.child.marker != 500 {
panic("reflected object field was collected")
}
for key, value := range globalReflectMap {
if key.object.marker != 600 {
panic("reflected map key was collected")
}
if value.object.marker != 700 {
panic("reflected map value was collected")
}
}
}