Skip to content

Commit 5f11275

Browse files
prattmicgopherbot
authored andcommitted
runtime: reusable intrusive doubly-linked list
Unfortunately we have two nearly identical types. One for standard types and one for sys.NotInHeap types or cases that must avoid write barriers. The latter must use uintptr fields, as assignment to unsafe.Pointer fields generates a write barrier. Change-Id: I6a6a636c62d83fa93b991033c7108d3b934412ac Reviewed-on: https://go-review.googlesource.com/c/go/+/714020 Reviewed-by: Michael Knyszek <[email protected]> Commit-Queue: Michael Pratt <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Michael Pratt <[email protected]>
1 parent 951cf05 commit 5f11275

File tree

5 files changed

+949
-0
lines changed

5 files changed

+949
-0
lines changed

src/runtime/export_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,3 +1938,51 @@ func TraceStack(gp *G, tab *TraceStackTable) {
19381938
var DebugDecorateMappings = &debug.decoratemappings
19391939

19401940
func SetVMANameSupported() bool { return setVMANameSupported() }
1941+
1942+
type ListHead struct {
1943+
l listHead
1944+
}
1945+
1946+
func (head *ListHead) Init(off uintptr) {
1947+
head.l.init(off)
1948+
}
1949+
1950+
type ListNode struct {
1951+
l listNode
1952+
}
1953+
1954+
func (head *ListHead) Push(p unsafe.Pointer) {
1955+
head.l.push(p)
1956+
}
1957+
1958+
func (head *ListHead) Pop() unsafe.Pointer {
1959+
return head.l.pop()
1960+
}
1961+
1962+
func (head *ListHead) Remove(p unsafe.Pointer) {
1963+
head.l.remove(p)
1964+
}
1965+
1966+
type ListHeadManual struct {
1967+
l listHeadManual
1968+
}
1969+
1970+
func (head *ListHeadManual) Init(off uintptr) {
1971+
head.l.init(off)
1972+
}
1973+
1974+
type ListNodeManual struct {
1975+
l listNodeManual
1976+
}
1977+
1978+
func (head *ListHeadManual) Push(p unsafe.Pointer) {
1979+
head.l.push(p)
1980+
}
1981+
1982+
func (head *ListHeadManual) Pop() unsafe.Pointer {
1983+
return head.l.pop()
1984+
}
1985+
1986+
func (head *ListHeadManual) Remove(p unsafe.Pointer) {
1987+
head.l.remove(p)
1988+
}

src/runtime/list.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright 2025 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+
package runtime
6+
7+
import (
8+
"unsafe"
9+
)
10+
11+
// listHead points to the head of an intrusive doubly-linked list.
12+
//
13+
// Prior to use, you must call init to store the offset of listNode fields.
14+
//
15+
// Every object in the list should be the same type.
16+
type listHead struct {
17+
obj unsafe.Pointer
18+
19+
initialized bool
20+
nodeOffset uintptr
21+
}
22+
23+
// init initializes the list head. off is the offset (via unsafe.Offsetof) of
24+
// the listNode field in the objects in the list.
25+
func (head *listHead) init(off uintptr) {
26+
head.initialized = true
27+
head.nodeOffset = off
28+
}
29+
30+
// listNode is the linked list node for objects in a listHead list.
31+
//
32+
// listNode must be stored as a field in objects placed in the linked list. The
33+
// offset of the field is registered via listHead.init.
34+
//
35+
// For example:
36+
//
37+
// type foo struct {
38+
// val int
39+
//
40+
// node listNode
41+
// }
42+
//
43+
// var fooHead listHead
44+
// fooHead.init(unsafe.Offsetof(foo{}.node))
45+
type listNode struct {
46+
prev unsafe.Pointer
47+
next unsafe.Pointer
48+
}
49+
50+
func (head *listHead) getNode(p unsafe.Pointer) *listNode {
51+
if !head.initialized {
52+
throw("runtime: uninitialized listHead")
53+
}
54+
55+
if p == nil {
56+
return nil
57+
}
58+
return (*listNode)(unsafe.Add(p, head.nodeOffset))
59+
}
60+
61+
// Returns true if the list is empty.
62+
func (head *listHead) empty() bool {
63+
return head.obj == nil
64+
}
65+
66+
// Returns the head of the list without removing it.
67+
func (head *listHead) head() unsafe.Pointer {
68+
return head.obj
69+
}
70+
71+
// Push p onto the front of the list.
72+
func (head *listHead) push(p unsafe.Pointer) {
73+
// p becomes the head of the list.
74+
75+
// ... so p's next is the current head.
76+
pNode := head.getNode(p)
77+
pNode.next = head.obj
78+
79+
// ... and the current head's prev is p.
80+
if head.obj != nil {
81+
headNode := head.getNode(head.obj)
82+
headNode.prev = p
83+
}
84+
85+
head.obj = p
86+
}
87+
88+
// Pop removes the head of the list.
89+
func (head *listHead) pop() unsafe.Pointer {
90+
if head.obj == nil {
91+
return nil
92+
}
93+
94+
// Return the head of the list.
95+
p := head.obj
96+
97+
// ... so the new head is p's next.
98+
pNode := head.getNode(p)
99+
head.obj = pNode.next
100+
// p is no longer on the list. Clear next to remove unused references.
101+
// N.B. as the head, prev must already be nil.
102+
pNode.next = nil
103+
104+
// ... and the new head no longer has a prev.
105+
if head.obj != nil {
106+
headNode := head.getNode(head.obj)
107+
headNode.prev = nil
108+
}
109+
110+
return p
111+
}
112+
113+
// Remove p from the middle of the list.
114+
func (head *listHead) remove(p unsafe.Pointer) {
115+
if head.obj == p {
116+
// Use pop to ensure head is updated when removing the head.
117+
head.pop()
118+
return
119+
}
120+
121+
pNode := head.getNode(p)
122+
prevNode := head.getNode(pNode.prev)
123+
nextNode := head.getNode(pNode.next)
124+
125+
// Link prev to next.
126+
if prevNode != nil {
127+
prevNode.next = pNode.next
128+
}
129+
// Link next to prev.
130+
if nextNode != nil {
131+
nextNode.prev = pNode.prev
132+
}
133+
134+
pNode.prev = nil
135+
pNode.next = nil
136+
}

src/runtime/list_manual.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2025 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+
package runtime
6+
7+
import (
8+
"unsafe"
9+
)
10+
11+
// The types in this file are exact copies of the types in list.go, but with
12+
// unsafe.Pointer replaced with uintptr for use where write barriers must be
13+
// avoided, such as uses of muintptr, puintptr, guintptr.
14+
//
15+
// Objects in these lists must be kept alive via another real reference.
16+
17+
// listHeadManual points to the head of an intrusive doubly-linked list of
18+
// objects.
19+
//
20+
// Prior to use, you must call init to store the offset of listNodeManual fields.
21+
//
22+
// Every object in the list should be the same type.
23+
type listHeadManual struct {
24+
obj uintptr
25+
26+
initialized bool
27+
nodeOffset uintptr
28+
}
29+
30+
// init initializes the list head. off is the offset (via unsafe.Offsetof) of
31+
// the listNodeManual field in the objects in the list.
32+
func (head *listHeadManual) init(off uintptr) {
33+
head.initialized = true
34+
head.nodeOffset = off
35+
}
36+
37+
// listNodeManual is the linked list node for objects in a listHeadManual list.
38+
//
39+
// listNodeManual must be stored as a field in objects placed in the linked list.
40+
// The offset of the field is registered via listHeadManual.init.
41+
//
42+
// For example:
43+
//
44+
// type foo struct {
45+
// val int
46+
//
47+
// node listNodeManual
48+
// }
49+
//
50+
// var fooHead listHeadManual
51+
// fooHead.init(unsafe.Offsetof(foo{}.node))
52+
type listNodeManual struct {
53+
prev uintptr
54+
next uintptr
55+
}
56+
57+
func (head *listHeadManual) getNode(p unsafe.Pointer) *listNodeManual {
58+
if !head.initialized {
59+
throw("runtime: uninitialized listHead")
60+
}
61+
62+
if p == nil {
63+
return nil
64+
}
65+
return (*listNodeManual)(unsafe.Add(p, head.nodeOffset))
66+
}
67+
68+
// Returns true if the list is empty.
69+
func (head *listHeadManual) empty() bool {
70+
return head.obj == 0
71+
}
72+
73+
// Returns the head of the list without removing it.
74+
func (head *listHeadManual) head() unsafe.Pointer {
75+
return unsafe.Pointer(head.obj)
76+
}
77+
78+
// Push p onto the front of the list.
79+
func (head *listHeadManual) push(p unsafe.Pointer) {
80+
// p becomes the head of the list.
81+
82+
// ... so p's next is the current head.
83+
pNode := head.getNode(p)
84+
pNode.next = head.obj
85+
86+
// ... and the current head's prev is p.
87+
if head.obj != 0 {
88+
headNode := head.getNode(unsafe.Pointer(head.obj))
89+
headNode.prev = uintptr(p)
90+
}
91+
92+
head.obj = uintptr(p)
93+
}
94+
95+
// Pop removes the head of the list.
96+
func (head *listHeadManual) pop() unsafe.Pointer {
97+
if head.obj == 0 {
98+
return nil
99+
}
100+
101+
// Return the head of the list.
102+
p := unsafe.Pointer(head.obj)
103+
104+
// ... so the new head is p's next.
105+
pNode := head.getNode(p)
106+
head.obj = pNode.next
107+
// p is no longer on the list. Clear next to remove unused references.
108+
// N.B. as the head, prev must already be nil.
109+
pNode.next = 0
110+
111+
// ... and the new head no longer has a prev.
112+
if head.obj != 0 {
113+
headNode := head.getNode(unsafe.Pointer(head.obj))
114+
headNode.prev = 0
115+
}
116+
117+
return p
118+
}
119+
120+
// Remove p from the middle of the list.
121+
func (head *listHeadManual) remove(p unsafe.Pointer) {
122+
if unsafe.Pointer(head.obj) == p {
123+
// Use pop to ensure head is updated when removing the head.
124+
head.pop()
125+
return
126+
}
127+
128+
pNode := head.getNode(p)
129+
prevNode := head.getNode(unsafe.Pointer(pNode.prev))
130+
nextNode := head.getNode(unsafe.Pointer(pNode.next))
131+
132+
// Link prev to next.
133+
if prevNode != nil {
134+
prevNode.next = pNode.next
135+
}
136+
// Link next to prev.
137+
if nextNode != nil {
138+
nextNode.prev = pNode.prev
139+
}
140+
141+
pNode.prev = 0
142+
pNode.next = 0
143+
}

0 commit comments

Comments
 (0)