| // Copyright 2011 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| /* | |
| model for proc.c as of 2011/07/22. | |
| takes 4900 seconds to explore 1189070 states | |
| with G=3, var_gomaxprocs=1 | |
| on a Core i7 L640 2.13 GHz Lenovo X201s. | |
| rm -f proc.p.trail pan.* pan | |
| spin -a proc.p | |
| gcc -DSAFETY -DREACH -DMEMLIM'='4000 -o pan pan.c | |
| pan -w28 -n -i -m500000 | |
| test -f proc.p.trail && pan -r proc.p.trail | |
| */ | |
| /* | |
| * scheduling parameters | |
| */ | |
| /* | |
| * the number of goroutines G doubles as the maximum | |
| * number of OS threads; the max is reachable when all | |
| * the goroutines are blocked in system calls. | |
| */ | |
| #define G 3 | |
| /* | |
| * whether to allow gomaxprocs to vary during execution. | |
| * enabling this checks the scheduler even when code is | |
| * calling GOMAXPROCS, but it also slows down the verification | |
| * by about 10x. | |
| */ | |
| #define var_gomaxprocs 1 /* allow gomaxprocs to vary */ | |
| /* gomaxprocs */ | |
| #if var_gomaxprocs | |
| byte gomaxprocs = 3; | |
| #else | |
| #define gomaxprocs 3 | |
| #endif | |
| /* queue of waiting M's: sched_mhead[:mwait] */ | |
| byte mwait; | |
| byte sched_mhead[G]; | |
| /* garbage collector state */ | |
| bit gc_lock, gcwaiting; | |
| /* goroutines sleeping, waiting to run */ | |
| byte gsleep, gwait; | |
| /* scheduler state */ | |
| bit sched_lock; | |
| bit sched_stopped; | |
| bit atomic_gwaiting, atomic_waitstop; | |
| byte atomic_mcpu, atomic_mcpumax; | |
| /* M struct fields - state for handing off g to m. */ | |
| bit m_waitnextg[G]; | |
| bit m_havenextg[G]; | |
| bit m_nextg[G]; | |
| /* | |
| * opt_atomic/opt_dstep mark atomic/deterministics | |
| * sequences that are marked only for reasons of | |
| * optimization, not for correctness of the algorithms. | |
| * | |
| * in general any code that runs while holding the | |
| * schedlock and does not refer to or modify the atomic_* | |
| * fields can be marked atomic/dstep without affecting | |
| * the usefulness of the model. since we trust the lock | |
| * implementation, what we really want to test is the | |
| * interleaving of the atomic fast paths with entersyscall | |
| * and exitsyscall. | |
| */ | |
| #define opt_atomic atomic | |
| #define opt_dstep d_step | |
| /* locks */ | |
| inline lock(x) { | |
| d_step { x == 0; x = 1 } | |
| } | |
| inline unlock(x) { | |
| d_step { assert x == 1; x = 0 } | |
| } | |
| /* notes */ | |
| inline noteclear(x) { | |
| x = 0 | |
| } | |
| inline notesleep(x) { | |
| x == 1 | |
| } | |
| inline notewakeup(x) { | |
| opt_dstep { assert x == 0; x = 1 } | |
| } | |
| /* | |
| * scheduler | |
| */ | |
| inline schedlock() { | |
| lock(sched_lock) | |
| } | |
| inline schedunlock() { | |
| unlock(sched_lock) | |
| } | |
| /* | |
| * canaddmcpu is like the C function but takes | |
| * an extra argument to include in the test, to model | |
| * "cannget() && canaddmcpu()" as "canaddmcpu(cangget())" | |
| */ | |
| inline canaddmcpu(g) { | |
| d_step { | |
| g && atomic_mcpu < atomic_mcpumax; | |
| atomic_mcpu++; | |
| } | |
| } | |
| /* | |
| * gput is like the C function. | |
| * instead of tracking goroutines explicitly we | |
| * maintain only the count of the number of | |
| * waiting goroutines. | |
| */ | |
| inline gput() { | |
| /* omitted: lockedm, idlem concerns */ | |
| opt_dstep { | |
| gwait++; | |
| if | |
| :: gwait == 1 -> | |
| atomic_gwaiting = 1 | |
| :: else | |
| fi | |
| } | |
| } | |
| /* | |
| * cangget is a macro so it can be passed to | |
| * canaddmcpu (see above). | |
| */ | |
| #define cangget() (gwait>0) | |
| /* | |
| * gget is like the C function. | |
| */ | |
| inline gget() { | |
| opt_dstep { | |
| assert gwait > 0; | |
| gwait--; | |
| if | |
| :: gwait == 0 -> | |
| atomic_gwaiting = 0 | |
| :: else | |
| fi | |
| } | |
| } | |
| /* | |
| * mput is like the C function. | |
| * here we do keep an explicit list of waiting M's, | |
| * so that we know which ones can be awakened. | |
| * we use _pid-1 because the monitor is proc 0. | |
| */ | |
| inline mput() { | |
| opt_dstep { | |
| sched_mhead[mwait] = _pid - 1; | |
| mwait++ | |
| } | |
| } | |
| /* | |
| * mnextg is like the C function mnextg(m, g). | |
| * it passes an unspecified goroutine to m to start running. | |
| */ | |
| inline mnextg(m) { | |
| opt_dstep { | |
| m_nextg[m] = 1; | |
| if | |
| :: m_waitnextg[m] -> | |
| m_waitnextg[m] = 0; | |
| notewakeup(m_havenextg[m]) | |
| :: else | |
| fi | |
| } | |
| } | |
| /* | |
| * mgetnextg handles the main m handoff in matchmg. | |
| * it is like mget() || new M followed by mnextg(m, g), | |
| * but combined to avoid a local variable. | |
| * unlike the C code, a new M simply assumes it is | |
| * running a g instead of using the mnextg coordination | |
| * to obtain one. | |
| */ | |
| inline mgetnextg() { | |
| opt_atomic { | |
| if | |
| :: mwait > 0 -> | |
| mwait--; | |
| mnextg(sched_mhead[mwait]); | |
| sched_mhead[mwait] = 0; | |
| :: else -> | |
| run mstart(); | |
| fi | |
| } | |
| } | |
| /* | |
| * nextgandunlock is like the C function. | |
| * it pulls a g off the queue or else waits for one. | |
| */ | |
| inline nextgandunlock() { | |
| assert atomic_mcpu <= G; | |
| if | |
| :: m_nextg[_pid-1] -> | |
| m_nextg[_pid-1] = 0; | |
| schedunlock(); | |
| :: canaddmcpu(!m_nextg[_pid-1] && cangget()) -> | |
| gget(); | |
| schedunlock(); | |
| :: else -> | |
| opt_dstep { | |
| mput(); | |
| m_nextg[_pid-1] = 0; | |
| m_waitnextg[_pid-1] = 1; | |
| noteclear(m_havenextg[_pid-1]); | |
| } | |
| if | |
| :: atomic_waitstop && atomic_mcpu <= atomic_mcpumax -> | |
| atomic_waitstop = 0; | |
| notewakeup(sched_stopped) | |
| :: else | |
| fi; | |
| schedunlock(); | |
| opt_dstep { | |
| notesleep(m_havenextg[_pid-1]); | |
| assert m_nextg[_pid-1]; | |
| m_nextg[_pid-1] = 0; | |
| } | |
| fi | |
| } | |
| /* | |
| * stoptheworld is like the C function. | |
| */ | |
| inline stoptheworld() { | |
| schedlock(); | |
| gcwaiting = 1; | |
| atomic_mcpumax = 1; | |
| do | |
| :: d_step { atomic_mcpu > 1 -> | |
| noteclear(sched_stopped); | |
| assert !atomic_waitstop; | |
| atomic_waitstop = 1 } | |
| schedunlock(); | |
| notesleep(sched_stopped); | |
| schedlock(); | |
| :: else -> | |
| break | |
| od; | |
| schedunlock(); | |
| } | |
| /* | |
| * starttheworld is like the C function. | |
| */ | |
| inline starttheworld() { | |
| schedlock(); | |
| gcwaiting = 0; | |
| atomic_mcpumax = gomaxprocs; | |
| matchmg(); | |
| schedunlock(); | |
| } | |
| /* | |
| * matchmg is like the C function. | |
| */ | |
| inline matchmg() { | |
| do | |
| :: canaddmcpu(cangget()) -> | |
| gget(); | |
| mgetnextg(); | |
| :: else -> break | |
| od | |
| } | |
| /* | |
| * ready is like the C function. | |
| * it puts a g on the run queue. | |
| */ | |
| inline ready() { | |
| schedlock(); | |
| gput() | |
| matchmg() | |
| schedunlock() | |
| } | |
| /* | |
| * schedule simulates the C scheduler. | |
| * it assumes that there is always a goroutine | |
| * running already, and the goroutine has entered | |
| * the scheduler for an unspecified reason, | |
| * either to yield or to block. | |
| */ | |
| inline schedule() { | |
| schedlock(); | |
| mustsched = 0; | |
| atomic_mcpu--; | |
| assert atomic_mcpu <= G; | |
| if | |
| :: skip -> | |
| // goroutine yields, still runnable | |
| gput(); | |
| :: gsleep+1 < G -> | |
| // goroutine goes to sleep (but there is another that can wake it) | |
| gsleep++ | |
| fi; | |
| // Find goroutine to run. | |
| nextgandunlock() | |
| } | |
| /* | |
| * schedpend is > 0 if a goroutine is about to committed to | |
| * entering the scheduler but has not yet done so. | |
| * Just as we don't test for the undesirable conditions when a | |
| * goroutine is in the scheduler, we don't test for them when | |
| * a goroutine will be in the scheduler shortly. | |
| * Modeling this state lets us replace mcpu cas loops with | |
| * simpler mcpu atomic adds. | |
| */ | |
| byte schedpend; | |
| /* | |
| * entersyscall is like the C function. | |
| */ | |
| inline entersyscall() { | |
| bit willsched; | |
| /* | |
| * Fast path. Check all the conditions tested during schedlock/schedunlock | |
| * below, and if we can get through the whole thing without stopping, run it | |
| * in one atomic cas-based step. | |
| */ | |
| atomic { | |
| atomic_mcpu--; | |
| if | |
| :: atomic_gwaiting -> | |
| skip | |
| :: atomic_waitstop && atomic_mcpu <= atomic_mcpumax -> | |
| skip | |
| :: else -> | |
| goto Lreturn_entersyscall; | |
| fi; | |
| willsched = 1; | |
| schedpend++; | |
| } | |
| /* | |
| * Normal path. | |
| */ | |
| schedlock() | |
| opt_dstep { | |
| if | |
| :: willsched -> | |
| schedpend--; | |
| willsched = 0 | |
| :: else | |
| fi | |
| } | |
| if | |
| :: atomic_gwaiting -> | |
| matchmg() | |
| :: else | |
| fi; | |
| if | |
| :: atomic_waitstop && atomic_mcpu <= atomic_mcpumax -> | |
| atomic_waitstop = 0; | |
| notewakeup(sched_stopped) | |
| :: else | |
| fi; | |
| schedunlock(); | |
| Lreturn_entersyscall: | |
| skip | |
| } | |
| /* | |
| * exitsyscall is like the C function. | |
| */ | |
| inline exitsyscall() { | |
| /* | |
| * Fast path. If there's a cpu available, use it. | |
| */ | |
| atomic { | |
| // omitted profilehz check | |
| atomic_mcpu++; | |
| if | |
| :: atomic_mcpu >= atomic_mcpumax -> | |
| skip | |
| :: else -> | |
| goto Lreturn_exitsyscall | |
| fi | |
| } | |
| /* | |
| * Normal path. | |
| */ | |
| schedlock(); | |
| d_step { | |
| if | |
| :: atomic_mcpu <= atomic_mcpumax -> | |
| skip | |
| :: else -> | |
| mustsched = 1 | |
| fi | |
| } | |
| schedunlock() | |
| Lreturn_exitsyscall: | |
| skip | |
| } | |
| #if var_gomaxprocs | |
| inline gomaxprocsfunc() { | |
| schedlock(); | |
| opt_atomic { | |
| if | |
| :: gomaxprocs != 1 -> gomaxprocs = 1 | |
| :: gomaxprocs != 2 -> gomaxprocs = 2 | |
| :: gomaxprocs != 3 -> gomaxprocs = 3 | |
| fi; | |
| } | |
| if | |
| :: gcwaiting != 0 -> | |
| assert atomic_mcpumax == 1 | |
| :: else -> | |
| atomic_mcpumax = gomaxprocs; | |
| if | |
| :: atomic_mcpu > gomaxprocs -> | |
| mustsched = 1 | |
| :: else -> | |
| matchmg() | |
| fi | |
| fi; | |
| schedunlock(); | |
| } | |
| #endif | |
| /* | |
| * mstart is the entry point for a new M. | |
| * our model of an M is always running some | |
| * unspecified goroutine. | |
| */ | |
| proctype mstart() { | |
| /* | |
| * mustsched is true if the goroutine must enter the | |
| * scheduler instead of continuing to execute. | |
| */ | |
| bit mustsched; | |
| do | |
| :: skip -> | |
| // goroutine reschedules. | |
| schedule() | |
| :: !mustsched -> | |
| // goroutine does something. | |
| if | |
| :: skip -> | |
| // goroutine executes system call | |
| entersyscall(); | |
| exitsyscall() | |
| :: atomic { gsleep > 0; gsleep-- } -> | |
| // goroutine wakes another goroutine | |
| ready() | |
| :: lock(gc_lock) -> | |
| // goroutine runs a garbage collection | |
| stoptheworld(); | |
| starttheworld(); | |
| unlock(gc_lock) | |
| #if var_gomaxprocs | |
| :: skip -> | |
| // goroutine picks a new gomaxprocs | |
| gomaxprocsfunc() | |
| #endif | |
| fi | |
| od; | |
| assert 0; | |
| } | |
| /* | |
| * monitor initializes the scheduler state | |
| * and then watches for impossible conditions. | |
| */ | |
| active proctype monitor() { | |
| opt_dstep { | |
| byte i = 1; | |
| do | |
| :: i < G -> | |
| gput(); | |
| i++ | |
| :: else -> break | |
| od; | |
| atomic_mcpu = 1; | |
| atomic_mcpumax = 1; | |
| } | |
| run mstart(); | |
| do | |
| // Should never have goroutines waiting with procs available. | |
| :: !sched_lock && schedpend==0 && gwait > 0 && atomic_mcpu < atomic_mcpumax -> | |
| assert 0 | |
| // Should never have gc waiting for stop if things have already stopped. | |
| :: !sched_lock && schedpend==0 && atomic_waitstop && atomic_mcpu <= atomic_mcpumax -> | |
| assert 0 | |
| od | |
| } |