Skip to content

Commit d13a931

Browse files
committed
runtime: add tests for runtime mTreap
This change exports the runtime mTreap in export_test.go and then adds a series of tests which check that the invariants of the treap are maintained under different operations. These tests also include tests for the treap iterator type. Also, we note that the find() operation on the treap never actually was best-fit, so the tests just ensure that it returns an appropriately sized span. For #30333. Change-Id: If81f7c746dda6677ebca925cb0a940134701b894 Reviewed-on: https://go-review.googlesource.com/c/go/+/164100 Run-TryBot: Michael Knyszek <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Austin Clements <[email protected]>
1 parent 2ab6d01 commit d13a931

File tree

3 files changed

+329
-3
lines changed

3 files changed

+329
-3
lines changed

src/runtime/export_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,3 +515,116 @@ func MapTombstoneCheck(m map[int]int) {
515515
}
516516
}
517517
}
518+
519+
// Span is a safe wrapper around an mspan, whose memory
520+
// is managed manually.
521+
type Span struct {
522+
*mspan
523+
}
524+
525+
func AllocSpan(base, npages uintptr) Span {
526+
lock(&mheap_.lock)
527+
s := (*mspan)(mheap_.spanalloc.alloc())
528+
unlock(&mheap_.lock)
529+
s.init(base, npages)
530+
return Span{s}
531+
}
532+
533+
func (s *Span) Free() {
534+
lock(&mheap_.lock)
535+
mheap_.spanalloc.free(unsafe.Pointer(s.mspan))
536+
unlock(&mheap_.lock)
537+
s.mspan = nil
538+
}
539+
540+
func (s Span) Base() uintptr {
541+
return s.mspan.base()
542+
}
543+
544+
func (s Span) Pages() uintptr {
545+
return s.mspan.npages
546+
}
547+
548+
type TreapIter struct {
549+
treapIter
550+
}
551+
552+
func (t TreapIter) Span() Span {
553+
return Span{t.span()}
554+
}
555+
556+
func (t TreapIter) Valid() bool {
557+
return t.valid()
558+
}
559+
560+
func (t TreapIter) Next() TreapIter {
561+
return TreapIter{t.next()}
562+
}
563+
564+
func (t TreapIter) Prev() TreapIter {
565+
return TreapIter{t.prev()}
566+
}
567+
568+
// Treap is a safe wrapper around mTreap for testing.
569+
//
570+
// It must never be heap-allocated because mTreap is
571+
// notinheap.
572+
//
573+
//go:notinheap
574+
type Treap struct {
575+
mTreap
576+
}
577+
578+
func (t *Treap) Start() TreapIter {
579+
return TreapIter{t.start()}
580+
}
581+
582+
func (t *Treap) End() TreapIter {
583+
return TreapIter{t.end()}
584+
}
585+
586+
func (t *Treap) Insert(s Span) {
587+
// mTreap uses a fixalloc in mheap_ for treapNode
588+
// allocation which requires the mheap_ lock to manipulate.
589+
// Locking here is safe because the treap itself never allocs
590+
// or otherwise ends up grabbing this lock.
591+
lock(&mheap_.lock)
592+
t.insert(s.mspan)
593+
unlock(&mheap_.lock)
594+
t.CheckInvariants()
595+
}
596+
597+
func (t *Treap) Find(npages uintptr) TreapIter {
598+
return TreapIter{t.find(npages)}
599+
}
600+
601+
func (t *Treap) Erase(i TreapIter) {
602+
// mTreap uses a fixalloc in mheap_ for treapNode
603+
// freeing which requires the mheap_ lock to manipulate.
604+
// Locking here is safe because the treap itself never allocs
605+
// or otherwise ends up grabbing this lock.
606+
lock(&mheap_.lock)
607+
t.erase(i.treapIter)
608+
unlock(&mheap_.lock)
609+
t.CheckInvariants()
610+
}
611+
612+
func (t *Treap) RemoveSpan(s Span) {
613+
// See Erase about locking.
614+
lock(&mheap_.lock)
615+
t.removeSpan(s.mspan)
616+
unlock(&mheap_.lock)
617+
t.CheckInvariants()
618+
}
619+
620+
func (t *Treap) Size() int {
621+
i := 0
622+
t.mTreap.treap.walkTreap(func(t *treapNode) {
623+
i++
624+
})
625+
return i
626+
}
627+
628+
func (t *Treap) CheckInvariants() {
629+
t.mTreap.treap.walkTreap(checkTreapNode)
630+
}

src/runtime/mgclarge.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,16 +134,19 @@ func checkTreapNode(t *treapNode) {
134134
return t.npagesKey < npages
135135
}
136136
// t.npagesKey == npages
137-
return uintptr(unsafe.Pointer(t.spanKey)) < uintptr(unsafe.Pointer(s))
137+
return t.spanKey.base() < s.base()
138138
}
139139

140140
if t == nil {
141141
return
142142
}
143-
if t.spanKey.npages != t.npagesKey || t.spanKey.next != nil {
143+
if t.spanKey.next != nil || t.spanKey.prev != nil || t.spanKey.list != nil {
144+
throw("span may be on an mSpanList while simultaneously in the treap")
145+
}
146+
if t.spanKey.npages != t.npagesKey {
144147
println("runtime: checkTreapNode treapNode t=", t, " t.npagesKey=", t.npagesKey,
145148
"t.spanKey.npages=", t.spanKey.npages)
146-
throw("why does span.npages and treap.ngagesKey do not match?")
149+
throw("span.npages and treap.npagesKey do not match")
147150
}
148151
if t.left != nil && lessThan(t.left.npagesKey, t.left.spanKey) {
149152
throw("t.lessThan(t.left.npagesKey, t.left.spanKey) is not false")
@@ -301,6 +304,9 @@ func (root *mTreap) removeNode(t *treapNode) {
301304
// This is slightly more complicated than a simple binary tree search
302305
// since if an exact match is not found the next larger node is
303306
// returned.
307+
// TODO(mknyszek): It turns out this routine does not actually find the
308+
// best-fit span, so either fix that or move to something else first, and
309+
// evaluate the performance implications of doing so.
304310
func (root *mTreap) find(npages uintptr) treapIter {
305311
t := root.treap
306312
for t != nil {

src/runtime/treap_test.go

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
// Copyright 2019 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_test
6+
7+
import (
8+
"runtime"
9+
"testing"
10+
)
11+
12+
var spanDesc = map[uintptr]uintptr{
13+
0xc0000000: 2,
14+
0xc0006000: 1,
15+
0xc0010000: 8,
16+
0xc0022000: 7,
17+
0xc0034000: 4,
18+
0xc0040000: 5,
19+
0xc0050000: 5,
20+
0xc0060000: 5000,
21+
}
22+
23+
// Wrap the Treap one more time because go:notinheap doesn't
24+
// actually follow a structure across package boundaries.
25+
//
26+
//go:notinheap
27+
type treap struct {
28+
runtime.Treap
29+
}
30+
31+
// This test ensures that the treap implementation in the runtime
32+
// maintains all stated invariants after different sequences of
33+
// insert, removeSpan, find, and erase. Invariants specific to the
34+
// treap data structure are checked implicitly: after each mutating
35+
// operation, treap-related invariants are checked for the entire
36+
// treap.
37+
func TestTreap(t *testing.T) {
38+
// Set up a bunch of spans allocated into mheap_.
39+
spans := make([]runtime.Span, 0, len(spanDesc))
40+
for base, pages := range spanDesc {
41+
s := runtime.AllocSpan(base, pages)
42+
defer s.Free()
43+
spans = append(spans, s)
44+
}
45+
t.Run("Insert", func(t *testing.T) {
46+
tr := treap{}
47+
// Test just a very basic insert/remove for sanity.
48+
tr.Insert(spans[0])
49+
tr.RemoveSpan(spans[0])
50+
})
51+
t.Run("FindTrivial", func(t *testing.T) {
52+
tr := treap{}
53+
// Test just a very basic find operation for sanity.
54+
tr.Insert(spans[0])
55+
i := tr.Find(1)
56+
if i.Span() != spans[0] {
57+
t.Fatal("found unknown span in treap")
58+
}
59+
tr.RemoveSpan(spans[0])
60+
})
61+
t.Run("Find", func(t *testing.T) {
62+
// Note that Find doesn't actually find the best-fit
63+
// element, so just make sure it always returns an element
64+
// that is at least large enough to satisfy the request.
65+
//
66+
// Run this 10 times, recreating the treap each time.
67+
// Because of the non-deterministic structure of a treap,
68+
// we'll be able to test different structures this way.
69+
for i := 0; i < 10; i++ {
70+
tr := treap{}
71+
for _, s := range spans {
72+
tr.Insert(s)
73+
}
74+
i := tr.Find(5)
75+
if i.Span().Pages() < 5 {
76+
t.Fatalf("expected span of size at least 5, got size %d", i.Span().Pages())
77+
}
78+
for _, s := range spans {
79+
tr.RemoveSpan(s)
80+
}
81+
}
82+
})
83+
t.Run("Iterate", func(t *testing.T) {
84+
t.Run("StartToEnd", func(t *testing.T) {
85+
// Ensure progressing an iterator actually goes over the whole treap
86+
// from the start and that it iterates over the elements in order.
87+
// Also ensures that Start returns a valid iterator.
88+
tr := treap{}
89+
for _, s := range spans {
90+
tr.Insert(s)
91+
}
92+
nspans := 0
93+
lastSize := uintptr(0)
94+
for i := tr.Start(); i.Valid(); i = i.Next() {
95+
nspans++
96+
if lastSize > i.Span().Pages() {
97+
t.Fatalf("not iterating in correct order: encountered size %d before %d", lastSize, i.Span().Pages())
98+
}
99+
lastSize = i.Span().Pages()
100+
}
101+
if nspans != len(spans) {
102+
t.Fatal("failed to iterate forwards over full treap")
103+
}
104+
for _, s := range spans {
105+
tr.RemoveSpan(s)
106+
}
107+
})
108+
t.Run("EndToStart", func(t *testing.T) {
109+
// Ensure progressing an iterator actually goes over the whole treap
110+
// from the end and that it iterates over the elements in reverse
111+
// order. Also ensures that End returns a valid iterator.
112+
tr := treap{}
113+
for _, s := range spans {
114+
tr.Insert(s)
115+
}
116+
nspans := 0
117+
lastSize := ^uintptr(0)
118+
for i := tr.End(); i.Valid(); i = i.Prev() {
119+
nspans++
120+
if lastSize < i.Span().Pages() {
121+
t.Fatalf("not iterating in correct order: encountered size %d before %d", lastSize, i.Span().Pages())
122+
}
123+
lastSize = i.Span().Pages()
124+
}
125+
if nspans != len(spans) {
126+
t.Fatal("failed to iterate backwards over full treap")
127+
}
128+
for _, s := range spans {
129+
tr.RemoveSpan(s)
130+
}
131+
})
132+
t.Run("Prev", func(t *testing.T) {
133+
// Test the iterator invariant that i.prev().next() == i.
134+
tr := treap{}
135+
for _, s := range spans {
136+
tr.Insert(s)
137+
}
138+
i := tr.Start().Next().Next()
139+
p := i.Prev()
140+
if !p.Valid() {
141+
t.Fatal("i.prev() is invalid")
142+
}
143+
if p.Next().Span() != i.Span() {
144+
t.Fatal("i.prev().next() != i")
145+
}
146+
for _, s := range spans {
147+
tr.RemoveSpan(s)
148+
}
149+
})
150+
t.Run("Next", func(t *testing.T) {
151+
// Test the iterator invariant that i.next().prev() == i.
152+
tr := treap{}
153+
for _, s := range spans {
154+
tr.Insert(s)
155+
}
156+
i := tr.Start().Next().Next()
157+
n := i.Next()
158+
if !n.Valid() {
159+
t.Fatal("i.next() is invalid")
160+
}
161+
if n.Prev().Span() != i.Span() {
162+
t.Fatal("i.next().prev() != i")
163+
}
164+
for _, s := range spans {
165+
tr.RemoveSpan(s)
166+
}
167+
})
168+
})
169+
t.Run("EraseOne", func(t *testing.T) {
170+
// Test that erasing one iterator correctly retains
171+
// all relationships between elements.
172+
tr := treap{}
173+
for _, s := range spans {
174+
tr.Insert(s)
175+
}
176+
i := tr.Start().Next().Next().Next()
177+
s := i.Span()
178+
n := i.Next()
179+
p := i.Prev()
180+
tr.Erase(i)
181+
if n.Prev().Span() != p.Span() {
182+
t.Fatal("p, n := i.Prev(), i.Next(); n.prev() != p after i was erased")
183+
}
184+
if p.Next().Span() != n.Span() {
185+
t.Fatal("p, n := i.Prev(), i.Next(); p.next() != n after i was erased")
186+
}
187+
tr.Insert(s)
188+
for _, s := range spans {
189+
tr.RemoveSpan(s)
190+
}
191+
})
192+
t.Run("EraseAll", func(t *testing.T) {
193+
// Test that erasing iterators actually removes nodes from the treap.
194+
tr := treap{}
195+
for _, s := range spans {
196+
tr.Insert(s)
197+
}
198+
for i := tr.Start(); i.Valid(); {
199+
n := i.Next()
200+
tr.Erase(i)
201+
i = n
202+
}
203+
if size := tr.Size(); size != 0 {
204+
t.Fatalf("should have emptied out treap, %d spans left", size)
205+
}
206+
})
207+
}

0 commit comments

Comments
 (0)