summaryrefslogtreecommitdiffstats
path: root/src/runtime/unsafepoint_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime/unsafepoint_test.go')
-rw-r--r--src/runtime/unsafepoint_test.go122
1 files changed, 122 insertions, 0 deletions
diff --git a/src/runtime/unsafepoint_test.go b/src/runtime/unsafepoint_test.go
new file mode 100644
index 0000000..2c97ade
--- /dev/null
+++ b/src/runtime/unsafepoint_test.go
@@ -0,0 +1,122 @@
+// Copyright 2023 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 runtime_test
+
+import (
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "reflect"
+ "runtime"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// This is the function we'll be testing.
+// It has a simple write barrier in it.
+func setGlobalPointer() {
+ globalPointer = nil
+}
+
+var globalPointer *int
+
+func TestUnsafePoint(t *testing.T) {
+ testenv.MustHaveExec(t)
+ switch runtime.GOARCH {
+ case "amd64", "arm64":
+ default:
+ t.Skipf("test not enabled for %s", runtime.GOARCH)
+ }
+
+ // Get a reference we can use to ask the runtime about
+ // which of its instructions are unsafe preemption points.
+ f := runtime.FuncForPC(reflect.ValueOf(setGlobalPointer).Pointer())
+
+ // Disassemble the test function.
+ // Note that normally "go test runtime" would strip symbols
+ // and prevent this step from working. So there's a hack in
+ // cmd/go/internal/test that exempts runtime tests from
+ // symbol stripping.
+ cmd := exec.Command(testenv.GoToolPath(t), "tool", "objdump", "-s", "setGlobalPointer", os.Args[0])
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("can't objdump %v", err)
+ }
+ lines := strings.Split(string(out), "\n")[1:]
+
+ // Walk through assembly instructions, checking preemptible flags.
+ var entry uint64
+ var startedWB bool
+ var doneWB bool
+ instructionCount := 0
+ unsafeCount := 0
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+ t.Logf("%s", line)
+ parts := strings.Fields(line)
+ if len(parts) < 4 {
+ continue
+ }
+ if !strings.HasPrefix(parts[0], "unsafepoint_test.go:") {
+ continue
+ }
+ pc, err := strconv.ParseUint(parts[1][2:], 16, 64)
+ if err != nil {
+ t.Fatalf("can't parse pc %s: %v", parts[1], err)
+ }
+ if entry == 0 {
+ entry = pc
+ }
+ // Note that some platforms do ASLR, so the PCs in the disassembly
+ // don't match PCs in the address space. Only offsets from function
+ // entry make sense.
+ unsafe := runtime.UnsafePoint(f.Entry() + uintptr(pc-entry))
+ t.Logf("unsafe: %v\n", unsafe)
+ instructionCount++
+ if unsafe {
+ unsafeCount++
+ }
+
+ // All the instructions inside the write barrier must be unpreemptible.
+ if startedWB && !doneWB && !unsafe {
+ t.Errorf("instruction %s must be marked unsafe, but isn't", parts[1])
+ }
+
+ // Detect whether we're in the write barrier.
+ switch runtime.GOARCH {
+ case "arm64":
+ if parts[3] == "MOVWU" {
+ // The unpreemptible region starts after the
+ // load of runtime.writeBarrier.
+ startedWB = true
+ }
+ if parts[3] == "MOVD" && parts[4] == "ZR," {
+ // The unpreemptible region ends after the
+ // write of nil.
+ doneWB = true
+ }
+ case "amd64":
+ if parts[3] == "CMPL" {
+ startedWB = true
+ }
+ if parts[3] == "MOVQ" && parts[4] == "$0x0," {
+ doneWB = true
+ }
+ }
+ }
+
+ if instructionCount == 0 {
+ t.Errorf("no instructions")
+ }
+ if unsafeCount == instructionCount {
+ t.Errorf("no interruptible instructions")
+ }
+ // Note that there are other instructions marked unpreemptible besides
+ // just the ones required by the write barrier. Those include possibly
+ // the preamble and postamble, as well as bleeding out from the
+ // write barrier proper into adjacent instructions (in both directions).
+ // Hopefully we can clean up the latter at some point.
+}