summaryrefslogtreecommitdiff
path: root/src/pkg/runtime/proc.p
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2011-09-13 13:13:40 +0200
committerOndřej Surý <ondrej@sury.org>2011-09-13 13:13:40 +0200
commit5ff4c17907d5b19510a62e08fd8d3b11e62b431d (patch)
treec0650497e988f47be9c6f2324fa692a52dea82e1 /src/pkg/runtime/proc.p
parent80f18fc933cf3f3e829c5455a1023d69f7b86e52 (diff)
downloadgolang-5ff4c17907d5b19510a62e08fd8d3b11e62b431d.tar.gz
Imported Upstream version 60upstream/60
Diffstat (limited to 'src/pkg/runtime/proc.p')
-rw-r--r--src/pkg/runtime/proc.p526
1 files changed, 526 insertions, 0 deletions
diff --git a/src/pkg/runtime/proc.p b/src/pkg/runtime/proc.p
new file mode 100644
index 000000000..f0b46de61
--- /dev/null
+++ b/src/pkg/runtime/proc.p
@@ -0,0 +1,526 @@
+// 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
+}