Skip to content

Commit e92adfe

Browse files
committed
net/art: allow non-pointers as values
Values are still turned into pointers internally to maintain the invariants of strideTable, but from the user's perspective it's now possible to tbl.Insert(pfx, true) rather than tbl.Insert(pfx, ptr.To(true)). Updates tailscale#7781 Signed-off-by: David Anderson <[email protected]>
1 parent bc0eb6b commit e92adfe

File tree

4 files changed

+246
-220
lines changed

4 files changed

+246
-220
lines changed

net/art/stride_table.go

+91-61
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,6 @@ const (
1818
debugStrideDelete = false
1919
)
2020

21-
// strideEntry is a strideTable entry.
22-
type strideEntry[T any] struct {
23-
// prefixIndex is the prefixIndex(...) value that caused this stride entry's
24-
// value to be populated, or 0 if value is nil.
25-
//
26-
// We need to keep track of this because allot() uses it to determine
27-
// whether an entry was propagated from a parent entry, or if it's a
28-
// different independent route.
29-
prefixIndex int
30-
// value is the value associated with the strideEntry, if any.
31-
value *T
32-
}
33-
3421
// strideTable is a binary tree that implements an 8-bit routing table.
3522
//
3623
// The leaves of the binary tree are host routes (/8s). Each parent is a
@@ -54,7 +41,9 @@ type strideTable[T any] struct {
5441
// paper, it's hijacked through sneaky C memory trickery to store
5542
// the refcount, but this is Go, where we don't store random bits
5643
// in pointers lest we confuse the GC)
57-
entries [lastHostIndex + 1]strideEntry[T]
44+
//
45+
// A nil value means no route matches the queried route.
46+
entries [lastHostIndex + 1]*T
5847
// children are the child tables of this table. Each child
5948
// represents the address space within one of this table's host
6049
// routes (/8).
@@ -112,13 +101,6 @@ func (t *strideTable[T]) getOrCreateChild(addr uint8) (child *strideTable[T], cr
112101
return ret, false
113102
}
114103

115-
// getValAndChild returns both the prefix and child strideTable for
116-
// addr. Both returned values can be nil if no entry of that type
117-
// exists for addr.
118-
func (t *strideTable[T]) getValAndChild(addr uint8) (*T, *strideTable[T]) {
119-
return t.entries[hostIndex(addr)].value, t.children[addr]
120-
}
121-
122104
// findFirstChild returns the first child strideTable in t, or nil if
123105
// t has no children.
124106
func (t *strideTable[T]) findFirstChild() *strideTable[T] {
@@ -130,73 +112,115 @@ func (t *strideTable[T]) findFirstChild() *strideTable[T] {
130112
return nil
131113
}
132114

115+
// hasPrefixRootedAt reports whether t.entries[idx] is the root node of
116+
// a prefix.
117+
func (t *strideTable[T]) hasPrefixRootedAt(idx int) bool {
118+
val := t.entries[idx]
119+
if val == nil {
120+
return false
121+
}
122+
123+
parentIdx := parentIndex(idx)
124+
if parentIdx == 0 {
125+
// idx is non-nil, and is at the 0/0 route position.
126+
return true
127+
}
128+
if parent := t.entries[parentIdx]; val != parent {
129+
// parent node in the tree isn't the same prefix, so idx must
130+
// be a root.
131+
return true
132+
}
133+
return false
134+
}
135+
133136
// allot updates entries whose stored prefixIndex matches oldPrefixIndex, in the
134137
// subtree rooted at idx. Matching entries have their stored prefixIndex set to
135138
// newPrefixIndex, and their value set to val.
136139
//
137140
// allot is the core of the ART algorithm, enabling efficient insertion/deletion
138141
// while preserving very fast lookups.
139-
func (t *strideTable[T]) allot(idx int, oldPrefixIndex, newPrefixIndex int, val *T) {
140-
if t.entries[idx].prefixIndex != oldPrefixIndex {
141-
// current prefixIndex isn't what we expect. This is a recursive call
142-
// that found a child subtree that already has a more specific route
143-
// installed. Don't touch it.
142+
func (t *strideTable[T]) allot(idx int, old, new *T) {
143+
if t.entries[idx] != old {
144+
// current idx isn't what we expect. This is a recursive call
145+
// that found a child subtree that already has a more specific
146+
// route installed. Don't touch it.
144147
return
145148
}
146-
t.entries[idx].value = val
147-
t.entries[idx].prefixIndex = newPrefixIndex
149+
t.entries[idx] = new
148150
if idx >= firstHostIndex {
149151
// The entry we just updated was a host route, we're at the bottom of
150152
// the binary tree.
151153
return
152154
}
153155
// Propagate the allotment to this node's children.
154156
left := idx << 1
155-
t.allot(left, oldPrefixIndex, newPrefixIndex, val)
157+
t.allot(left, old, new)
156158
right := left + 1
157-
t.allot(right, oldPrefixIndex, newPrefixIndex, val)
159+
t.allot(right, old, new)
158160
}
159161

160162
// insert adds the route addr/prefixLen to t, with value val.
161-
func (t *strideTable[T]) insert(addr uint8, prefixLen int, val *T) {
163+
func (t *strideTable[T]) insert(addr uint8, prefixLen int, val T) {
162164
idx := prefixIndex(addr, prefixLen)
163-
old := t.entries[idx].value
164-
oldIdx := t.entries[idx].prefixIndex
165-
if oldIdx == idx && old == val {
166-
// This exact prefix+value is already in the table.
167-
return
168-
}
169-
t.allot(idx, oldIdx, idx, val)
170-
if oldIdx != idx {
171-
// This route entry was freshly created (not just updated), that's a new
172-
// reference.
165+
if !t.hasPrefixRootedAt(idx) {
166+
// This route entry is being freshly created (not just
167+
// updated), that's a new reference.
173168
t.routeRefs++
174169
}
170+
171+
old := t.entries[idx]
172+
173+
// For allot to work correctly, each distinct prefix in the
174+
// strideTable must have a different value pointer, even if val is
175+
// identical. This new()+assignment guarantees that each inserted
176+
// prefix gets a unique address.
177+
p := new(T)
178+
*p = val
179+
180+
t.allot(idx, old, p)
175181
return
176182
}
177183

178-
// delete removes the route addr/prefixLen from t. Returns the value
179-
// that was associated with the deleted prefix, or nil if the prefix
180-
// wasn't in the strideTable.
181-
func (t *strideTable[T]) delete(addr uint8, prefixLen int) *T {
184+
// delete removes the route addr/prefixLen from t. Reports whether the
185+
// prefix existed in the table prior to deletion.
186+
func (t *strideTable[T]) delete(addr uint8, prefixLen int) (wasPresent bool) {
182187
idx := prefixIndex(addr, prefixLen)
183-
recordedIdx := t.entries[idx].prefixIndex
184-
if recordedIdx != idx {
188+
if !t.hasPrefixRootedAt(idx) {
185189
// Route entry doesn't exist
186-
return nil
190+
return false
187191
}
188-
val := t.entries[idx].value
189192

190-
parentIdx := idx >> 1
191-
t.allot(idx, idx, t.entries[parentIdx].prefixIndex, t.entries[parentIdx].value)
193+
val := t.entries[idx]
194+
var parentVal *T
195+
if parentIdx := parentIndex(idx); parentIdx != 0 {
196+
parentVal = t.entries[parentIdx]
197+
}
198+
199+
t.allot(idx, val, parentVal)
192200
t.routeRefs--
193-
return val
201+
return true
194202
}
195203

196-
// get does a route lookup for addr and returns the associated value, or nil if
197-
// no route matched.
198-
func (t *strideTable[T]) get(addr uint8) *T {
199-
return t.entries[hostIndex(addr)].value
204+
// get does a route lookup for addr and (value, true) if a matching
205+
// route exists, or (zero, false) otherwise.
206+
func (t *strideTable[T]) get(addr uint8) (ret T, ok bool) {
207+
if val := t.entries[hostIndex(addr)]; val != nil {
208+
return *val, true
209+
}
210+
return ret, false
211+
}
212+
213+
// getValAndChild returns both the prefix value and child strideTable
214+
// for addr. valOK reports whether a prefix value exists for addr, and
215+
// child is non-nil if a child exists for addr.
216+
func (t *strideTable[T]) getValAndChild(addr uint8) (val T, valOK bool, child *strideTable[T]) {
217+
vp := t.entries[hostIndex(addr)]
218+
if vp != nil {
219+
val = *vp
220+
valOK = true
221+
}
222+
child = t.children[addr]
223+
return
200224
}
201225

202226
// TableDebugString returns the contents of t, formatted as a table with one
@@ -208,10 +232,10 @@ func (t *strideTable[T]) tableDebugString() string {
208232
continue
209233
}
210234
v := "(nil)"
211-
if ent.value != nil {
212-
v = fmt.Sprint(*ent.value)
235+
if ent != nil {
236+
v = fmt.Sprint(*ent)
213237
}
214-
fmt.Fprintf(&ret, "idx=%3d (%s), parent=%3d (%s), val=%v\n", i, formatPrefixTable(inversePrefixIndex(i)), ent.prefixIndex, formatPrefixTable(inversePrefixIndex((ent.prefixIndex))), v)
238+
fmt.Fprintf(&ret, "idx=%3d (%s), val=%v\n", i, formatPrefixTable(inversePrefixIndex(i)), v)
215239
}
216240
return ret.String()
217241
}
@@ -227,8 +251,8 @@ func (t *strideTable[T]) treeDebugString() string {
227251

228252
func (t *strideTable[T]) treeDebugStringRec(w io.Writer, idx, indent int) {
229253
addr, len := inversePrefixIndex(idx)
230-
if t.entries[idx].prefixIndex != 0 && t.entries[idx].prefixIndex == idx {
231-
fmt.Fprintf(w, "%s%d/%d (%02x/%d) = %v\n", strings.Repeat(" ", indent), addr, len, addr, len, *t.entries[idx].value)
254+
if t.hasPrefixRootedAt(idx) {
255+
fmt.Fprintf(w, "%s%d/%d (%02x/%d) = %v\n", strings.Repeat(" ", indent), addr, len, addr, len, *t.entries[idx])
232256
indent += 2
233257
}
234258
if idx >= firstHostIndex {
@@ -251,6 +275,12 @@ func prefixIndex(addr uint8, prefixLen int) int {
251275
return (int(addr) >> (8 - prefixLen)) + (1 << prefixLen)
252276
}
253277

278+
// parentIndex returns the index of idx's parent prefix, or 0 if idx
279+
// is the index of 0/0.
280+
func parentIndex(idx int) int {
281+
return idx >> 1
282+
}
283+
254284
// hostIndex returns the array index of the host route for addr.
255285
// It is equivalent to prefixIndex(addr, 8).
256286
func hostIndex(addr uint8) int {

0 commit comments

Comments
 (0)