|  | // 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 ( | 
|  | "runtime" | 
|  | "testing" | 
|  | "time" | 
|  | "unsafe" | 
|  | ) | 
|  |  | 
|  | type obj struct { | 
|  | x int64 | 
|  | y int64 | 
|  | z int64 | 
|  | } | 
|  |  | 
|  | type objWith[T any] struct { | 
|  | x int64 | 
|  | y int64 | 
|  | z int64 | 
|  | o T | 
|  | } | 
|  |  | 
|  | var ( | 
|  | globalUintptr                uintptr | 
|  | globalPtrToObj               = &obj{} | 
|  | globalPtrToObjWithPtr        = &objWith[*uintptr]{} | 
|  | globalPtrToRuntimeObj        = func() *obj { return &obj{} }() | 
|  | globalPtrToRuntimeObjWithPtr = func() *objWith[*uintptr] { return &objWith[*uintptr]{} }() | 
|  | ) | 
|  |  | 
|  | func assertDidPanic(t *testing.T) { | 
|  | if recover() == nil { | 
|  | t.Fatal("did not panic") | 
|  | } | 
|  | } | 
|  |  | 
|  | func assertCgoCheckPanics(t *testing.T, p any) { | 
|  | defer func() { | 
|  | if recover() == nil { | 
|  | t.Fatal("cgoCheckPointer() did not panic, make sure the tests run with cgocheck=1") | 
|  | } | 
|  | }() | 
|  | runtime.CgoCheckPointer(p, true) | 
|  | } | 
|  |  | 
|  | func TestPinnerSimple(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | p := new(obj) | 
|  | addr := unsafe.Pointer(p) | 
|  | if runtime.IsPinned(addr) { | 
|  | t.Fatal("already marked as pinned") | 
|  | } | 
|  | pinner.Pin(p) | 
|  | if !runtime.IsPinned(addr) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | if runtime.GetPinCounter(addr) != nil { | 
|  | t.Fatal("pin counter should not exist") | 
|  | } | 
|  | pinner.Unpin() | 
|  | if runtime.IsPinned(addr) { | 
|  | t.Fatal("still marked as pinned") | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPinnerPinKeepsAliveAndReleases(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | p := new(obj) | 
|  | done := make(chan struct{}) | 
|  | runtime.SetFinalizer(p, func(any) { | 
|  | done <- struct{}{} | 
|  | }) | 
|  | pinner.Pin(p) | 
|  | p = nil | 
|  | runtime.GC() | 
|  | runtime.GC() | 
|  | select { | 
|  | case <-done: | 
|  | t.Fatal("Pin() didn't keep object alive") | 
|  | case <-time.After(time.Millisecond * 10): | 
|  | break | 
|  | } | 
|  | pinner.Unpin() | 
|  | runtime.GC() | 
|  | runtime.GC() | 
|  | select { | 
|  | case <-done: | 
|  | break | 
|  | case <-time.After(time.Second): | 
|  | t.Fatal("Unpin() didn't release object") | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPinnerMultiplePinsSame(t *testing.T) { | 
|  | const N = 100 | 
|  | var pinner runtime.Pinner | 
|  | p := new(obj) | 
|  | addr := unsafe.Pointer(p) | 
|  | if runtime.IsPinned(addr) { | 
|  | t.Fatal("already marked as pinned") | 
|  | } | 
|  | for i := 0; i < N; i++ { | 
|  | pinner.Pin(p) | 
|  | } | 
|  | if !runtime.IsPinned(addr) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != N-1 { | 
|  | t.Fatalf("pin counter incorrect: %d", *cnt) | 
|  | } | 
|  | pinner.Unpin() | 
|  | if runtime.IsPinned(addr) { | 
|  | t.Fatal("still marked as pinned") | 
|  | } | 
|  | if runtime.GetPinCounter(addr) != nil { | 
|  | t.Fatal("pin counter was not deleted") | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPinnerTwoPinner(t *testing.T) { | 
|  | var pinner1, pinner2 runtime.Pinner | 
|  | p := new(obj) | 
|  | addr := unsafe.Pointer(p) | 
|  | if runtime.IsPinned(addr) { | 
|  | t.Fatal("already marked as pinned") | 
|  | } | 
|  | pinner1.Pin(p) | 
|  | if !runtime.IsPinned(addr) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | if runtime.GetPinCounter(addr) != nil { | 
|  | t.Fatal("pin counter should not exist") | 
|  | } | 
|  | pinner2.Pin(p) | 
|  | if !runtime.IsPinned(addr) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != 1 { | 
|  | t.Fatalf("pin counter incorrect: %d", *cnt) | 
|  | } | 
|  | pinner1.Unpin() | 
|  | if !runtime.IsPinned(addr) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | if runtime.GetPinCounter(addr) != nil { | 
|  | t.Fatal("pin counter should not exist") | 
|  | } | 
|  | pinner2.Unpin() | 
|  | if runtime.IsPinned(addr) { | 
|  | t.Fatal("still marked as pinned") | 
|  | } | 
|  | if runtime.GetPinCounter(addr) != nil { | 
|  | t.Fatal("pin counter was not deleted") | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPinnerPinZerosizeObj(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | p := new(struct{}) | 
|  | pinner.Pin(p) | 
|  | if !runtime.IsPinned(unsafe.Pointer(p)) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPinnerPinGlobalPtr(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | pinner.Pin(globalPtrToObj) | 
|  | pinner.Pin(globalPtrToObjWithPtr) | 
|  | pinner.Pin(globalPtrToRuntimeObj) | 
|  | pinner.Pin(globalPtrToRuntimeObjWithPtr) | 
|  | } | 
|  |  | 
|  | func TestPinnerPinTinyObj(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | const N = 64 | 
|  | var addr [N]unsafe.Pointer | 
|  | for i := 0; i < N; i++ { | 
|  | p := new(bool) | 
|  | addr[i] = unsafe.Pointer(p) | 
|  | pinner.Pin(p) | 
|  | pinner.Pin(p) | 
|  | if !runtime.IsPinned(addr[i]) { | 
|  | t.Fatalf("not marked as pinned: %d", i) | 
|  | } | 
|  | if cnt := runtime.GetPinCounter(addr[i]); cnt == nil || *cnt == 0 { | 
|  | t.Fatalf("pin counter incorrect: %d, %d", *cnt, i) | 
|  | } | 
|  | } | 
|  | pinner.Unpin() | 
|  | for i := 0; i < N; i++ { | 
|  | if runtime.IsPinned(addr[i]) { | 
|  | t.Fatal("still marked as pinned") | 
|  | } | 
|  | if runtime.GetPinCounter(addr[i]) != nil { | 
|  | t.Fatal("pin counter should not exist") | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestPinnerInterface(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | o := new(obj) | 
|  | ifc := any(o) | 
|  | pinner.Pin(&ifc) | 
|  | if !runtime.IsPinned(unsafe.Pointer(&ifc)) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | if runtime.IsPinned(unsafe.Pointer(o)) { | 
|  | t.Fatal("marked as pinned") | 
|  | } | 
|  | pinner.Unpin() | 
|  | pinner.Pin(ifc) | 
|  | if !runtime.IsPinned(unsafe.Pointer(o)) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | if runtime.IsPinned(unsafe.Pointer(&ifc)) { | 
|  | t.Fatal("marked as pinned") | 
|  | } | 
|  | pinner.Unpin() | 
|  | } | 
|  |  | 
|  | func TestPinnerPinNonPtrPanics(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | var i int | 
|  | defer assertDidPanic(t) | 
|  | pinner.Pin(i) | 
|  | } | 
|  |  | 
|  | func TestPinnerReuse(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | p := new(obj) | 
|  | p2 := &p | 
|  | assertCgoCheckPanics(t, p2) | 
|  | pinner.Pin(p) | 
|  | runtime.CgoCheckPointer(p2, true) | 
|  | pinner.Unpin() | 
|  | assertCgoCheckPanics(t, p2) | 
|  | pinner.Pin(p) | 
|  | runtime.CgoCheckPointer(p2, true) | 
|  | pinner.Unpin() | 
|  | } | 
|  |  | 
|  | func TestPinnerEmptyUnpin(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | pinner.Unpin() | 
|  | pinner.Unpin() | 
|  | } | 
|  |  | 
|  | func TestPinnerLeakPanics(t *testing.T) { | 
|  | old := runtime.GetPinnerLeakPanic() | 
|  | func() { | 
|  | defer assertDidPanic(t) | 
|  | old() | 
|  | }() | 
|  | done := make(chan struct{}) | 
|  | runtime.SetPinnerLeakPanic(func() { | 
|  | done <- struct{}{} | 
|  | }) | 
|  | func() { | 
|  | var pinner runtime.Pinner | 
|  | p := new(obj) | 
|  | pinner.Pin(p) | 
|  | }() | 
|  | runtime.GC() | 
|  | runtime.GC() | 
|  | select { | 
|  | case <-done: | 
|  | break | 
|  | case <-time.After(time.Second): | 
|  | t.Fatal("leak didn't make GC to panic") | 
|  | } | 
|  | runtime.SetPinnerLeakPanic(old) | 
|  | } | 
|  |  | 
|  | func TestPinnerCgoCheckPtr2Ptr(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | p := new(obj) | 
|  | p2 := &objWith[*obj]{o: p} | 
|  | assertCgoCheckPanics(t, p2) | 
|  | pinner.Pin(p) | 
|  | runtime.CgoCheckPointer(p2, true) | 
|  | } | 
|  |  | 
|  | func TestPinnerCgoCheckPtr2UnsafePtr(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | p := unsafe.Pointer(new(obj)) | 
|  | p2 := &objWith[unsafe.Pointer]{o: p} | 
|  | assertCgoCheckPanics(t, p2) | 
|  | pinner.Pin(p) | 
|  | runtime.CgoCheckPointer(p2, true) | 
|  | } | 
|  |  | 
|  | func TestPinnerCgoCheckPtr2UnknownPtr(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | p := unsafe.Pointer(new(obj)) | 
|  | p2 := &p | 
|  | func() { | 
|  | defer assertDidPanic(t) | 
|  | runtime.CgoCheckPointer(p2, nil) | 
|  | }() | 
|  | pinner.Pin(p) | 
|  | runtime.CgoCheckPointer(p2, nil) | 
|  | } | 
|  |  | 
|  | func TestPinnerCgoCheckInterface(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | var ifc any | 
|  | var o obj | 
|  | ifc = &o | 
|  | p := &ifc | 
|  | assertCgoCheckPanics(t, p) | 
|  | pinner.Pin(&o) | 
|  | runtime.CgoCheckPointer(p, true) | 
|  | } | 
|  |  | 
|  | func TestPinnerCgoCheckSlice(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | sl := []int{1, 2, 3} | 
|  | assertCgoCheckPanics(t, &sl) | 
|  | pinner.Pin(&sl[0]) | 
|  | runtime.CgoCheckPointer(&sl, true) | 
|  | } | 
|  |  | 
|  | func TestPinnerCgoCheckString(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | b := []byte("foobar") | 
|  | str := unsafe.String(&b[0], 6) | 
|  | assertCgoCheckPanics(t, &str) | 
|  | pinner.Pin(&b[0]) | 
|  | runtime.CgoCheckPointer(&str, true) | 
|  | } | 
|  |  | 
|  | func TestPinnerCgoCheckPinned2UnpinnedPanics(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | p := new(obj) | 
|  | p2 := &objWith[*obj]{o: p} | 
|  | assertCgoCheckPanics(t, p2) | 
|  | pinner.Pin(p2) | 
|  | assertCgoCheckPanics(t, p2) | 
|  | } | 
|  |  | 
|  | func TestPinnerCgoCheckPtr2Pinned2Unpinned(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | defer pinner.Unpin() | 
|  | p := new(obj) | 
|  | p2 := &objWith[*obj]{o: p} | 
|  | p3 := &objWith[*objWith[*obj]]{o: p2} | 
|  | assertCgoCheckPanics(t, p2) | 
|  | assertCgoCheckPanics(t, p3) | 
|  | pinner.Pin(p2) | 
|  | assertCgoCheckPanics(t, p2) | 
|  | assertCgoCheckPanics(t, p3) | 
|  | pinner.Pin(p) | 
|  | runtime.CgoCheckPointer(p2, true) | 
|  | runtime.CgoCheckPointer(p3, true) | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerPinUnpinBatch(b *testing.B) { | 
|  | const Batch = 1000 | 
|  | var data [Batch]*obj | 
|  | for i := 0; i < Batch; i++ { | 
|  | data[i] = new(obj) | 
|  | } | 
|  | b.ResetTimer() | 
|  | for n := 0; n < b.N; n++ { | 
|  | var pinner runtime.Pinner | 
|  | for i := 0; i < Batch; i++ { | 
|  | pinner.Pin(data[i]) | 
|  | } | 
|  | pinner.Unpin() | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerPinUnpinBatchDouble(b *testing.B) { | 
|  | const Batch = 1000 | 
|  | var data [Batch]*obj | 
|  | for i := 0; i < Batch; i++ { | 
|  | data[i] = new(obj) | 
|  | } | 
|  | b.ResetTimer() | 
|  | for n := 0; n < b.N; n++ { | 
|  | var pinner runtime.Pinner | 
|  | for i := 0; i < Batch; i++ { | 
|  | pinner.Pin(data[i]) | 
|  | pinner.Pin(data[i]) | 
|  | } | 
|  | pinner.Unpin() | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerPinUnpinBatchTiny(b *testing.B) { | 
|  | const Batch = 1000 | 
|  | var data [Batch]*bool | 
|  | for i := 0; i < Batch; i++ { | 
|  | data[i] = new(bool) | 
|  | } | 
|  | b.ResetTimer() | 
|  | for n := 0; n < b.N; n++ { | 
|  | var pinner runtime.Pinner | 
|  | for i := 0; i < Batch; i++ { | 
|  | pinner.Pin(data[i]) | 
|  | } | 
|  | pinner.Unpin() | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerPinUnpin(b *testing.B) { | 
|  | p := new(obj) | 
|  | for n := 0; n < b.N; n++ { | 
|  | var pinner runtime.Pinner | 
|  | pinner.Pin(p) | 
|  | pinner.Unpin() | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerPinUnpinTiny(b *testing.B) { | 
|  | p := new(bool) | 
|  | for n := 0; n < b.N; n++ { | 
|  | var pinner runtime.Pinner | 
|  | pinner.Pin(p) | 
|  | pinner.Unpin() | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerPinUnpinDouble(b *testing.B) { | 
|  | p := new(obj) | 
|  | for n := 0; n < b.N; n++ { | 
|  | var pinner runtime.Pinner | 
|  | pinner.Pin(p) | 
|  | pinner.Pin(p) | 
|  | pinner.Unpin() | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerPinUnpinParallel(b *testing.B) { | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | p := new(obj) | 
|  | for pb.Next() { | 
|  | var pinner runtime.Pinner | 
|  | pinner.Pin(p) | 
|  | pinner.Unpin() | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerPinUnpinParallelTiny(b *testing.B) { | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | p := new(bool) | 
|  | for pb.Next() { | 
|  | var pinner runtime.Pinner | 
|  | pinner.Pin(p) | 
|  | pinner.Unpin() | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerPinUnpinParallelDouble(b *testing.B) { | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | p := new(obj) | 
|  | for pb.Next() { | 
|  | var pinner runtime.Pinner | 
|  | pinner.Pin(p) | 
|  | pinner.Pin(p) | 
|  | pinner.Unpin() | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerIsPinnedOnPinned(b *testing.B) { | 
|  | var pinner runtime.Pinner | 
|  | ptr := new(obj) | 
|  | pinner.Pin(ptr) | 
|  | b.ResetTimer() | 
|  | for n := 0; n < b.N; n++ { | 
|  | runtime.IsPinned(unsafe.Pointer(ptr)) | 
|  | } | 
|  | pinner.Unpin() | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerIsPinnedOnUnpinned(b *testing.B) { | 
|  | ptr := new(obj) | 
|  | b.ResetTimer() | 
|  | for n := 0; n < b.N; n++ { | 
|  | runtime.IsPinned(unsafe.Pointer(ptr)) | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerIsPinnedOnPinnedParallel(b *testing.B) { | 
|  | var pinner runtime.Pinner | 
|  | ptr := new(obj) | 
|  | pinner.Pin(ptr) | 
|  | b.ResetTimer() | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | runtime.IsPinned(unsafe.Pointer(ptr)) | 
|  | } | 
|  | }) | 
|  | pinner.Unpin() | 
|  | } | 
|  |  | 
|  | func BenchmarkPinnerIsPinnedOnUnpinnedParallel(b *testing.B) { | 
|  | ptr := new(obj) | 
|  | b.ResetTimer() | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | for pb.Next() { | 
|  | runtime.IsPinned(unsafe.Pointer(ptr)) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | // const string data is not in span. | 
|  | func TestPinnerConstStringData(t *testing.T) { | 
|  | var pinner runtime.Pinner | 
|  | str := "test-const-string" | 
|  | p := unsafe.StringData(str) | 
|  | addr := unsafe.Pointer(p) | 
|  | if !runtime.IsPinned(addr) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | pinner.Pin(p) | 
|  | pinner.Unpin() | 
|  | if !runtime.IsPinned(addr) { | 
|  | t.Fatal("not marked as pinned") | 
|  | } | 
|  | } |