diff options
Diffstat (limited to 'src/internal')
| -rw-r--r-- | src/internal/abi/escape.go | 11 | ||||
| -rw-r--r-- | src/internal/weak/pointer.go | 83 | ||||
| -rw-r--r-- | src/internal/weak/pointer_test.go | 130 |
3 files changed, 224 insertions, 0 deletions
diff --git a/src/internal/abi/escape.go b/src/internal/abi/escape.go index 8f3756333c..8cdae1438e 100644 --- a/src/internal/abi/escape.go +++ b/src/internal/abi/escape.go @@ -20,3 +20,14 @@ func NoEscape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) } + +var alwaysFalse bool +var escapeSink any + +// Escape forces any pointers in x to escape to the heap. +func Escape[T any](x T) T { + if alwaysFalse { + escapeSink = x + } + return x +} diff --git a/src/internal/weak/pointer.go b/src/internal/weak/pointer.go new file mode 100644 index 0000000000..44d26738bc --- /dev/null +++ b/src/internal/weak/pointer.go @@ -0,0 +1,83 @@ +// Copyright 2024 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. + +/* +The weak package is a package for managing weak pointers. + +Weak pointers are pointers that explicitly do not keep a value live and +must be queried for a regular Go pointer. +The result of such a query may be observed as nil at any point after a +weakly-pointed-to object becomes eligible for reclamation by the garbage +collector. +More specifically, weak pointers become nil as soon as the garbage collector +identifies that the object is unreachable, before it is made reachable +again by a finalizer. +In terms of the C# language, these semantics are roughly equivalent to the +the semantics of "short" weak references. +In terms of the Java language, these semantics are roughly equivalent to the +semantics of the WeakReference type. + +Using go:linkname to access this package and the functions it references +is explicitly forbidden by the toolchain because the semantics of this +package have not gone through the proposal process. By exposing this +functionality, we risk locking in the existing semantics due to Hyrum's Law. + +If you believe you have a good use-case for weak references not already +covered by the standard library, file a proposal issue at +https://github.com/golang/go/issues instead of relying on this package. +*/ +package weak + +import ( + "internal/abi" + "runtime" + "unsafe" +) + +// Pointer is a weak pointer to a value of type T. +// +// This value is comparable is guaranteed to compare equal if the pointers +// that they were created from compare equal. This property is retained even +// after the object referenced by the pointer used to create a weak reference +// is reclaimed. +// +// If multiple weak pointers are made to different offsets within same object +// (for example, pointers to different fields of the same struct), those pointers +// will not compare equal. +// If a weak pointer is created from an object that becomes reachable again due +// to a finalizer, that weak pointer will not compare equal with weak pointers +// created before it became unreachable. +type Pointer[T any] struct { + u unsafe.Pointer +} + +// Make creates a weak pointer from a strong pointer to some value of type T. +func Make[T any](ptr *T) Pointer[T] { + // Explicitly force ptr to escape to the heap. + ptr = abi.Escape(ptr) + + var u unsafe.Pointer + if ptr != nil { + u = runtime_registerWeakPointer(unsafe.Pointer(ptr)) + } + runtime.KeepAlive(ptr) + return Pointer[T]{u} +} + +// Strong creates a strong pointer from the weak pointer. +// Returns nil if the original value for the weak pointer was reclaimed by +// the garbage collector. +// If a weak pointer points to an object with a finalizer, thhen Strong will +// return nil as soon as the object's finalizer is queued for execution. +func (p Pointer[T]) Strong() *T { + return (*T)(runtime_makeStrongFromWeak(unsafe.Pointer(p.u))) +} + +// Implemented in runtime. + +//go:linkname runtime_registerWeakPointer +func runtime_registerWeakPointer(unsafe.Pointer) unsafe.Pointer + +//go:linkname runtime_makeStrongFromWeak +func runtime_makeStrongFromWeak(unsafe.Pointer) unsafe.Pointer diff --git a/src/internal/weak/pointer_test.go b/src/internal/weak/pointer_test.go new file mode 100644 index 0000000000..e143749230 --- /dev/null +++ b/src/internal/weak/pointer_test.go @@ -0,0 +1,130 @@ +// Copyright 2024 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 weak_test + +import ( + "internal/weak" + "runtime" + "testing" +) + +type T struct { + // N.B. This must contain a pointer, otherwise the weak handle might get placed + // in a tiny block making the tests in this package flaky. + t *T + a int +} + +func TestPointer(t *testing.T) { + bt := new(T) + wt := weak.Make(bt) + if st := wt.Strong(); st != bt { + t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt) + } + // bt is still referenced. + runtime.GC() + + if st := wt.Strong(); st != bt { + t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt) + } + // bt is no longer referenced. + runtime.GC() + + if st := wt.Strong(); st != nil { + t.Fatalf("expected weak pointer to be nil, got %p", st) + } +} + +func TestPointerEquality(t *testing.T) { + bt := make([]*T, 10) + wt := make([]weak.Pointer[T], 10) + for i := range bt { + bt[i] = new(T) + wt[i] = weak.Make(bt[i]) + } + for i := range bt { + st := wt[i].Strong() + if st != bt[i] { + t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i]) + } + if wp := weak.Make(st); wp != wt[i] { + t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i]) + } + if i == 0 { + continue + } + if wt[i] == wt[i-1] { + t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i]) + } + } + // bt is still referenced. + runtime.GC() + for i := range bt { + st := wt[i].Strong() + if st != bt[i] { + t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i]) + } + if wp := weak.Make(st); wp != wt[i] { + t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i]) + } + if i == 0 { + continue + } + if wt[i] == wt[i-1] { + t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i]) + } + } + bt = nil + // bt is no longer referenced. + runtime.GC() + for i := range bt { + st := wt[i].Strong() + if st != nil { + t.Fatalf("expected weak pointer to be nil, got %p", st) + } + if i == 0 { + continue + } + if wt[i] == wt[i-1] { + t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i]) + } + } +} + +func TestPointerFinalizer(t *testing.T) { + bt := new(T) + wt := weak.Make(bt) + done := make(chan struct{}, 1) + runtime.SetFinalizer(bt, func(bt *T) { + if wt.Strong() != nil { + t.Errorf("weak pointer did not go nil before finalizer ran") + } + done <- struct{}{} + }) + + // Make sure the weak pointer stays around while bt is live. + runtime.GC() + if wt.Strong() == nil { + t.Errorf("weak pointer went nil too soon") + } + runtime.KeepAlive(bt) + + // bt is no longer referenced. + // + // Run one cycle to queue the finalizer. + runtime.GC() + if wt.Strong() != nil { + t.Errorf("weak pointer did not go nil when finalizer was enqueued") + } + + // Wait for the finalizer to run. + <-done + + // The weak pointer should still be nil after the finalizer runs. + runtime.GC() + if wt.Strong() != nil { + t.Errorf("weak pointer is non-nil even after finalization: %v", wt) + } +} |
