github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/go-weave/models/rescan.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 // rescan is a model of two concurrent stack re-scanning approaches: 8 // transitive mark write barriers, and scan restarting. 9 // 10 // This model is somewhat limited. The mutator is uninteresting and it 11 // doesn't model concurrent write barriers (or the mark quiescence 12 // necessary with concurrent write barriers). This model formed the 13 // basis for the yuasa model, which is much more complete. 14 package main 15 16 import ( 17 "fmt" 18 19 "github.com/aclements/go-misc/go-weave/amb" 20 "github.com/aclements/go-misc/go-weave/weave" 21 ) 22 23 // writeMarks indicates that the write barrier should transitively 24 // mark objects before publishing them. 25 const writeMarks = true 26 27 // writeRestarts indicates that the write barrier should reset the 28 // stack scan. 29 const writeRestarts = false 30 31 // ptr is a memory pointer, as an index into mem. 0 is the nil 32 // pointer. 33 type ptr int 34 35 // obj is an object in memory. An object in the "heap" region of 36 // memory must not point to an object in the "stack" region of memory. 37 type obj struct { 38 l, r ptr 39 } 40 41 // mem is the memory, including both the heap and stacks. mem[0] is 42 // unused (it's the nil slot). mem[stackBase:stackBase+numThreads] are 43 // the stacks. mem[globalRoot:] is the heap. mme[globalRoot] is the 44 // global root. 45 var mem []obj 46 47 var marked []bool 48 49 const numThreads = 2 50 51 const stackBase ptr = 1 52 const globalRoot ptr = stackBase + ptr(numThreads) 53 54 var scanClock int 55 var world weave.RWMutex 56 57 const verbose = false 58 59 var sched = weave.Scheduler{Strategy: &amb.StrategyRandom{}} 60 61 func main() { 62 sched.Run(func() { 63 if verbose { 64 print("start:") 65 } 66 // Create an ambiguous memory. 67 // 68 // TODO: Tons of these are isomorphic. 69 mem = make([]obj, 6) 70 for i := 1; i < len(mem); i++ { 71 mem[i].l = ambHeapPointer() 72 if ptr(i) >= globalRoot { 73 // For stacks we only use l. 74 mem[i].r = ambHeapPointer() 75 } 76 } 77 marked = make([]bool, len(mem)) 78 if verbose { 79 printMem(mem, marked) 80 } 81 scanClock = 0 82 world = weave.RWMutex{} // Belt and suspenders. 83 84 // Mark the global root. 85 mark(globalRoot, marked, "globalRoot") 86 87 // Start mutators. 88 for i := 0; i < numThreads; i++ { 89 i := i 90 sched.Go(func() { mutator(i) }) 91 } 92 93 // Re-scan stacks. 94 for scanClock < numThreads { 95 if verbose { 96 println("scan", scanClock) 97 } 98 scanClock++ 99 mark(mem[stackBase+ptr(scanClock-1)].l, marked, "scan") 100 } 101 102 // Wait for write barriers to complete. 103 world.Lock() 104 defer world.Unlock() 105 106 // Check that everything is marked. 107 if verbose { 108 printMem(mem, marked) 109 } 110 checkmark(globalRoot) 111 for i := 0; i < numThreads; i++ { 112 checkmark(mem[stackBase+ptr(i)].l) 113 } 114 }) 115 } 116 117 // ambHeapPointer returns nil or an ambiguous heap pointer. 118 func ambHeapPointer() ptr { 119 x := sched.Amb(len(mem) - int(globalRoot) + 1) 120 if x == 0 { 121 return 0 122 } 123 return ptr(x-1) + globalRoot 124 } 125 126 // ambReachableHeapPointer returns an ambiguous reachable heap 127 // pointer. Note that the object may not be marked. 128 func ambReachableHeapPointer() ptr { 129 reachable := make([]bool, len(mem)) 130 mark(globalRoot, reachable, "") 131 132 nreachable := 0 133 for _, m := range reachable[globalRoot:] { 134 if m { 135 nreachable++ 136 } 137 } 138 x := sched.Amb(nreachable) 139 for i, m := range reachable[globalRoot:] { 140 if m { 141 if x == 0 { 142 return globalRoot + ptr(i) 143 } 144 x-- 145 } 146 } 147 panic("not reached") 148 } 149 150 func wbarrier(slot, val ptr) { 151 // TODO: Check that GC is still running? 152 153 // TODO: Need to mark val regardless (but doesn't have to be 154 // transitive). 155 156 if val != 0 { 157 if writeMarks { 158 func() { 159 // Block STW termination while marking. 160 world.RLock() 161 defer world.RUnlock() 162 // TODO: In reality, concurrent marks 163 // can collide with each other, so we 164 // need mark quiescence. This doesn't 165 // model that. 166 mark(mem[val].l, marked, "barrier") 167 }() 168 } 169 if writeRestarts { 170 if !marked[val] { 171 scanClock = 0 172 } 173 } 174 } 175 mem[slot].l = mem[val].l 176 sched.Sched() 177 } 178 179 func mutator(id int) { 180 sptr := stackBase + ptr(id) 181 182 // TODO: nil pointer writes? 183 184 // Publish our stack pointer to some live heap object. 185 obj := ambReachableHeapPointer() 186 //mem[obj].l = mem[sptr].l 187 if verbose { 188 print(obj, ".l = ", mem[sptr].l, "\n") 189 } 190 wbarrier(obj, sptr) 191 if verbose { 192 print(obj, ".l = ", mem[sptr].l, " done\n") 193 } 194 195 // Read a pointer from the heap. No write barrier since this 196 // is a stack write. 197 obj = ambReachableHeapPointer() 198 mem[sptr].l = mem[obj].l 199 sched.Sched() 200 } 201 202 func mark(p ptr, marked []bool, name string) { 203 if p == 0 || marked[p] { 204 return 205 } 206 marked[p] = true 207 if name != "" { 208 if verbose { 209 println(name, "marked", p) 210 } 211 } 212 mark(mem[p].l, marked, name) 213 if name != "" { 214 sched.Sched() 215 } 216 mark(mem[p].r, marked, name) 217 if name != "" { 218 sched.Sched() 219 } 220 } 221 222 func checkmark(p ptr) { 223 checkmarked := make([]bool, len(mem)) 224 var mark1 func(p ptr) 225 mark1 = func(p ptr) { 226 if p == 0 { 227 return 228 } 229 if !marked[p] { 230 panic(fmt.Sprintf("object not marked: %d", p)) 231 } 232 if checkmarked[p] { 233 return 234 } 235 checkmarked[p] = true 236 mark1(mem[p].l) 237 mark1(mem[p].r) 238 } 239 mark1(p) 240 } 241 242 func printMem(mem []obj, marked []bool) { 243 for i := 1; i < len(mem); i++ { 244 if marked[i] { 245 print("*") 246 } else { 247 print(" ") 248 } 249 print(i, "->", mem[i].l, ",", mem[i].r, " ") 250 } 251 println() 252 }