1// Copyright 2021 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//go:build goexperiment.regabiargs
6
7// This file contains tests specific to making sure the register ABI
8// works in a bunch of contexts in the runtime.
9
10package runtime_test
11
12import (
13	"internal/abi"
14	"internal/runtime/atomic"
15	"internal/testenv"
16	"os"
17	"os/exec"
18	"runtime"
19	"strings"
20	"testing"
21	"time"
22)
23
24var regConfirmRun atomic.Int32
25
26//go:registerparams
27func regFinalizerPointer(v *TintPointer) (int, float32, [10]byte) {
28	regConfirmRun.Store(int32(*(*int)(v.p)))
29	return 5151, 4.0, [10]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
30}
31
32//go:registerparams
33func regFinalizerIface(v Tinter) (int, float32, [10]byte) {
34	regConfirmRun.Store(int32(*(*int)(v.(*TintPointer).p)))
35	return 5151, 4.0, [10]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
36}
37
38// TintPointer has a pointer member to make sure that it isn't allocated by the
39// tiny allocator, so we know when its finalizer will run
40type TintPointer struct {
41	p *Tint
42}
43
44func (*TintPointer) m() {}
45
46func TestFinalizerRegisterABI(t *testing.T) {
47	testenv.MustHaveExec(t)
48
49	// Actually run the test in a subprocess because we don't want
50	// finalizers from other tests interfering.
51	if os.Getenv("TEST_FINALIZER_REGABI") != "1" {
52		cmd := testenv.CleanCmdEnv(exec.Command(os.Args[0], "-test.run=^TestFinalizerRegisterABI$", "-test.v"))
53		cmd.Env = append(cmd.Env, "TEST_FINALIZER_REGABI=1")
54		out, err := cmd.CombinedOutput()
55		if !strings.Contains(string(out), "PASS\n") || err != nil {
56			t.Fatalf("%s\n(exit status %v)", string(out), err)
57		}
58		return
59	}
60
61	// Optimistically clear any latent finalizers from e.g. the testing
62	// package before continuing.
63	//
64	// It's possible that a finalizer only becomes available to run
65	// after this point, which would interfere with the test and could
66	// cause a crash, but because we're running in a separate process
67	// it's extremely unlikely.
68	runtime.GC()
69	runtime.GC()
70
71	// fing will only pick the new IntRegArgs up if it's currently
72	// sleeping and wakes up, so wait for it to go to sleep.
73	success := false
74	for i := 0; i < 100; i++ {
75		if runtime.FinalizerGAsleep() {
76			success = true
77			break
78		}
79		time.Sleep(20 * time.Millisecond)
80	}
81	if !success {
82		t.Fatal("finalizer not asleep?")
83	}
84
85	argRegsBefore := runtime.SetIntArgRegs(abi.IntArgRegs)
86	defer runtime.SetIntArgRegs(argRegsBefore)
87
88	tests := []struct {
89		name         string
90		fin          any
91		confirmValue int
92	}{
93		{"Pointer", regFinalizerPointer, -1},
94		{"Interface", regFinalizerIface, -2},
95	}
96	for i := range tests {
97		test := &tests[i]
98		t.Run(test.name, func(t *testing.T) {
99			x := &TintPointer{p: new(Tint)}
100			*x.p = (Tint)(test.confirmValue)
101			runtime.SetFinalizer(x, test.fin)
102
103			runtime.KeepAlive(x)
104
105			// Queue the finalizer.
106			runtime.GC()
107			runtime.GC()
108
109			if !runtime.BlockUntilEmptyFinalizerQueue(int64(time.Second)) {
110				t.Fatal("finalizer failed to execute")
111			}
112			if got := int(regConfirmRun.Load()); got != test.confirmValue {
113				t.Fatalf("wrong finalizer executed? got %d, want %d", got, test.confirmValue)
114			}
115		})
116	}
117}
118