github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/go-weave/models/yuasa.go (about) 1 // Copyright 2016 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 // +build ignore 6 7 // yuasa is a model of several variants of Yuasa-style deletion 8 // barriers intended to eliminate stack re-scanning. 9 package main 10 11 import ( 12 "bytes" 13 "fmt" 14 15 "github.com/aclements/go-misc/go-weave/amb" 16 "github.com/aclements/go-misc/go-weave/weave" 17 ) 18 19 type barrierType int 20 21 const ( 22 // yuasaBarrier is a Yuasa-style deletion barrier. It requires 23 // stackBeforeHeap, but does not require rescanStacks. 24 yuasaBarrier barrierType = iota 25 26 // dijkstraYuasaBarrier is a combined Dijkstra-style insertion 27 // barrier and Yuasa-style deletion barrier. It does not 28 // require stackBeforeHeap or rescanStacks. 29 dijkstraYuasaBarrier 30 31 // conditionalDijkstraYuasaBarrier is like 32 // dijkstraYuasaBarrier before all stacks are blacked, and 33 // like yuasaBarrier after stacks have been blacked. It does 34 // not require stackBeforeHeap or rescanStacks. 35 conditionalDijkstraYuasaBarrier 36 37 // dijkstraBarrier is a Dijkstra-style insertion barrier. It 38 // does not require stackBeforeHeap, but it does require 39 // rescanStacks. 40 dijkstraBarrier 41 ) 42 43 // barrier indicates the type of write barrier to use. 44 const barrier = conditionalDijkstraYuasaBarrier 45 46 // stackBeforeHeap indicates that all stacks must be blackened before 47 // any heap objects are blackened. 48 const stackBeforeHeap = false 49 50 // rescanStacks indicates that stacks must be re-scanned during STW 51 // mark termination. 52 const rescanStacks = false 53 54 // ptr is a memory pointer, as an index into mem. 0 is the nil 55 // pointer. 56 type ptr int 57 58 // obj is an object in memory. An object in the "global" or "heap" 59 // region of memory must not point to an object in the "stack" region 60 // of memory. 61 type obj [2]ptr 62 63 // mem is the memory, including both the heap and stacks. mem[0] is 64 // unused (it's the nil slot) 65 // 66 // mem[stackBase+i] for i < numThreads is the stack for thread i. 67 // 68 // mem[globalRoot] is the global root. 69 // 70 // mem[heapBase:] is the heap. 71 var mem []obj 72 73 // marked is the set of mark bits. marked[i] corresponds to mem[i]. 74 var marked []bool 75 76 // work is the work list. This is the set of grey objects. 77 var work []ptr 78 79 const numThreads = 2 80 81 const stackBase ptr = 1 82 const globalRoot ptr = stackBase + numThreads 83 const heapBase ptr = globalRoot + 1 84 const heapCount = 3 85 86 var world weave.RWMutex 87 var stackLocks [numThreads]weave.Mutex 88 89 // rootCount is the number of unscanned roots. 90 var rootCount int 91 92 const verbose = false 93 94 var sched = weave.Scheduler{Strategy: &amb.StrategyRandom{}} 95 96 func main() { 97 sched.Run(func() { 98 if verbose { 99 print("start:") 100 } 101 // Create an ambiguous memory. 102 // 103 // TODO: Tons of these are isomorphic. 104 mem = make([]obj, heapBase+heapCount) 105 for i := 1; i < len(mem); i++ { 106 mem[i] = obj{ambHeapPointer(), ambHeapPointer()} 107 } 108 marked = make([]bool, len(mem)) 109 if verbose { 110 println(stringMem(mem, marked)) 111 } 112 sched.Tracef("memory: %s", stringMem(mem, marked)) 113 world = weave.RWMutex{} // Belt and suspenders. 114 for i := range stackLocks { 115 stackLocks[i] = weave.Mutex{} 116 } 117 rootCount = numThreads + 1 118 119 // Start mutators. 120 for i := 0; i < numThreads; i++ { 121 i := i 122 sched.Go(func() { mutator(i) }) 123 } 124 125 if stackBeforeHeap { 126 sched.Trace("scanning stacks") 127 // Scan stacks and global roots. Complete this 128 // before allowing any blackening of the heap. 129 for i := stackBase; i < stackBase+numThreads; i++ { 130 scan(i) 131 marked[i] = true 132 } 133 scan(globalRoot) 134 marked[globalRoot] = true 135 sched.Trace("done scanning stacks") 136 } else { 137 // Grey stacks and global roots. Drain will 138 // scan them. 139 for i := stackBase; i < stackBase+numThreads; i++ { 140 shade(i) 141 } 142 shade(globalRoot) 143 } 144 145 // Blacken heap. 146 drain() 147 148 // Wait for write barriers to complete. 149 world.Lock() 150 defer world.Unlock() 151 152 if rescanStacks { 153 sched.Trace("rescanning stacks") 154 // Rescan stacks. (The write barrier applies 155 // to globals, so we don't need to rescan 156 // globalRoot.) 157 for i := stackBase; i < stackBase+numThreads; i++ { 158 marked[i] = false 159 shade(i) 160 } 161 drain() 162 sched.Trace("done rescanning stacks") 163 } 164 165 // Check that everything is marked. 166 if verbose { 167 println(stringMem(mem, marked)) 168 } 169 sched.Tracef("memory: %s", stringMem(mem, marked)) 170 checkmark() 171 }) 172 } 173 174 type pointerSet int 175 176 const ( 177 // pointerNil indicates that ambPointer can return a nil 178 // pointer. 179 pointerNil pointerSet = 1 << iota 180 181 // pointerStack indicates that ambPointer can return a pointer 182 // to the stack. 183 pointerStack 184 185 // pointerReachable indicates that ambPointer can return a 186 // pointer to a reachable heap or global object. 187 pointerReachable 188 189 // pointerHeap indicates that ambPointer can return a pointer 190 // to any global or heap object. 191 pointerHeap 192 ) 193 194 // ambPointer returns an ambiguous pointer from the union of the 195 // specified sets. If ps&(pointerStack|pointerReachable) != 0, tid 196 // must specify the thread ID of the stack. 197 func ambPointer(ps pointerSet, tid int) ptr { 198 if ps&pointerReachable == 0 { 199 // Easy/fast case. 200 count := 0 201 if ps&pointerNil != 0 { 202 count++ 203 } 204 if ps&pointerStack != 0 { 205 count++ 206 } 207 if ps&pointerHeap != 0 { 208 count += 1 + heapCount 209 } 210 x := sched.Amb(count) 211 if ps&pointerNil != 0 { 212 if x == 0 { 213 return 0 214 } 215 x-- 216 } 217 if ps&pointerStack != 0 { 218 if x == 0 { 219 return stackBase + ptr(tid) 220 } 221 x-- 222 } 223 if x == 0 { 224 return globalRoot 225 } 226 return heapBase + ptr(x-1) 227 } 228 229 // Tricky case. Create a mask of the pointers we're interested in. 230 marked := make([]bool, len(mem)) 231 mark(globalRoot, marked) 232 mark(stackBase+ptr(tid), marked) 233 if ps&pointerNil != 0 { 234 marked[0] = true 235 } 236 if ps&pointerStack == 0 { 237 marked[stackBase+ptr(tid)] = false 238 } 239 240 // Select a marked pointer. 241 nmarked := 0 242 for _, m := range marked { 243 if m { 244 nmarked++ 245 } 246 } 247 x := sched.Amb(nmarked) 248 for i, m := range marked { 249 if m { 250 if x == 0 { 251 return ptr(i) 252 } 253 x-- 254 } 255 } 256 panic("not reachable") 257 } 258 259 // ambHeapPointer returns nil or an ambiguous heap or global pointer. 260 func ambHeapPointer() ptr { 261 return ambPointer(pointerNil|pointerHeap, -1) 262 } 263 264 // scan scans obj, shading objects that obj re 265 func scan(obj ptr) { 266 sched.Tracef("scan(%v)", obj) 267 if stackBase <= obj && obj < stackBase+numThreads { 268 stackLocks[obj-stackBase].Lock() 269 defer stackLocks[obj-stackBase].Unlock() 270 } 271 for i := range mem[obj] { 272 p := mem[obj][i] 273 sched.Sched() 274 shade(p) 275 } 276 if stackBase <= obj && obj < stackBase+numThreads || obj == globalRoot { 277 rootCount-- 278 sched.Tracef("roots remaining = %d", rootCount) 279 } 280 } 281 282 // shade makes obj grey if it is white. 283 func shade(obj ptr) { 284 if obj != 0 && !marked[obj] { 285 sched.Tracef("shade(%v)", obj) 286 marked[obj] = true 287 work = append(work, obj) 288 } 289 } 290 291 // drain scans objects in the work queue until the queue is empty. 292 func drain() { 293 for len(work) > 0 { 294 // Pick an arbitrary object to scan. 295 which := sched.Amb(len(work)) 296 p := work[which] 297 copy(work[which:], work[which+1:]) 298 work = work[:len(work)-1] 299 300 scan(p) 301 } 302 } 303 304 // writePointer implements obj[slot] = val. 305 func writePointer(obj ptr, slot int, val ptr) { 306 // TODO: Check that GC is still running? 307 308 // Synchronize with STW. This blocks STW from happening while 309 // we're in the barrier and blocks this goroutine if we're 310 // already in STW. 311 world.RLock() 312 defer world.RUnlock() 313 314 if obj == 0 { 315 panic("nil pointer write") 316 } 317 318 if stackBase <= obj && obj < stackBase+numThreads { 319 mem[obj][slot] = val 320 sched.Tracef("stack write %v[%d] = %v", obj, slot, val) 321 sched.Sched() 322 return 323 } 324 325 sched.Tracef("start %v[%d] = %v", obj, slot, val) 326 327 switch barrier { 328 case yuasaBarrier: 329 old := mem[obj][slot] 330 sched.Sched() 331 shade(old) 332 333 case dijkstraYuasaBarrier: 334 old := mem[obj][slot] 335 sched.Sched() 336 shade(old) 337 shade(val) 338 339 case conditionalDijkstraYuasaBarrier: 340 old := mem[obj][slot] 341 sched.Sched() 342 shade(old) 343 if rootCount > 0 { 344 shade(val) 345 } 346 347 case dijkstraBarrier: 348 shade(val) 349 } 350 351 mem[obj][slot] = val 352 sched.Tracef("done %v[%d] = %v", obj, slot, val) 353 sched.Sched() 354 } 355 356 // mutator is a single mutator goroutine running on stack stackBase+tid. 357 // It shuffles pointers between the heap and stack. 358 func mutator(tid int) { 359 stackptr := stackBase + ptr(tid) 360 361 for i := 0; i < 2; i++ { 362 // Take the stack lock to indicate that we're not at a 363 // safe point. There's no safe point between reading 364 // src and writing pointer since in the model we can't 365 // communicate the pointer we're looking at to the GC. 366 // 367 // Somewhat surprisingly, it's actually necessary to 368 // model this. Otherwise stack writes that race with 369 // the stack scan can hide pointers. 370 stackLocks[tid].Lock() 371 372 // Write a nil, global, or heap pointer to the stack, global, 373 // or heap, or a stack pointer to the stack. 374 src := ambPointer(pointerNil|pointerStack|pointerReachable, tid) 375 sched.Sched() 376 var dst ptr 377 if src == stackptr { 378 // Stack pointers can only be written to the stack. 379 dst = stackptr 380 } else { 381 // Non-stack pointers can be written to stack, global, 382 // or heap. 383 dst = ambPointer(pointerStack|pointerReachable, tid) 384 } 385 writePointer(dst, sched.Amb(2), src) 386 387 // We're at a safe point again. 388 stackLocks[tid].Unlock() 389 } 390 } 391 392 // mark sets marked[i] for every object i reachable from p (including 393 // p itself). This is NOT preemptible. 394 func mark(p ptr, marked []bool) { 395 if p == 0 || marked[p] { 396 return 397 } 398 marked[p] = true 399 for i := range mem[p] { 400 mark(mem[p][i], marked) 401 } 402 } 403 404 // checkmark checks that all objects reachable from the roots are 405 // marked. 406 func checkmark() { 407 checkmarked := make([]bool, len(mem)) 408 for i := stackBase; i < stackBase+numThreads; i++ { 409 mark(i, checkmarked) 410 } 411 mark(globalRoot, checkmarked) 412 413 for i := range marked { 414 if checkmarked[i] && !marked[i] { 415 panic(fmt.Sprintf("object not marked: %v", i)) 416 } 417 } 418 } 419 420 // stringMem stringifies a memory with marks. 421 func stringMem(mem []obj, marked []bool) string { 422 var buf bytes.Buffer 423 for i := 1; i < len(mem); i++ { 424 if marked[i] { 425 buf.WriteString("*") 426 } else { 427 buf.WriteString(" ") 428 } 429 fmt.Fprint(&buf, i, "->", mem[i][0], ",", mem[i][1], " ") 430 } 431 return buf.String() 432 }