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  }