aboutsummaryrefslogtreecommitdiff
path: root/src/internal
diff options
context:
space:
mode:
Diffstat (limited to 'src/internal')
-rw-r--r--src/internal/abi/escape.go11
-rw-r--r--src/internal/weak/pointer.go83
-rw-r--r--src/internal/weak/pointer_test.go130
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)
+ }
+}