diff options
Diffstat (limited to '')
-rw-r--r-- | src/runtime/mklockrank.go | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/src/runtime/mklockrank.go b/src/runtime/mklockrank.go new file mode 100644 index 0000000..c8e34d8 --- /dev/null +++ b/src/runtime/mklockrank.go @@ -0,0 +1,403 @@ +// Copyright 2022 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. + +//go:build ignore + +// mklockrank records the static rank graph of the locks in the +// runtime and generates the rank checking structures in lockrank.go. +package main + +import ( + "bytes" + "flag" + "fmt" + "go/format" + "internal/dag" + "io" + "log" + "os" + "strings" +) + +// ranks describes the lock rank graph. See "go doc internal/dag" for +// the syntax. +// +// "a < b" means a must be acquired before b if both are held +// (or, if b is held, a cannot be acquired). +// +// "NONE < a" means no locks may be held when a is acquired. +// +// If a lock is not given a rank, then it is assumed to be a leaf +// lock, which means no other lock can be acquired while it is held. +// Therefore, leaf locks do not need to be given an explicit rank. +// +// Ranks in all caps are pseudo-nodes that help define order, but do +// not actually define a rank. +// +// TODO: It's often hard to correlate rank names to locks. Change +// these to be more consistent with the locks they label. +const ranks = ` +# Sysmon +NONE +< sysmon +< scavenge, forcegc; + +# Defer +NONE < defer; + +# GC +NONE < + sweepWaiters, + assistQueue, + sweep; + +# Test only +NONE < testR, testW; + +# Scheduler, timers, netpoll +NONE < + allocmW, + execW, + cpuprof, + pollDesc, + wakeableSleep; +assistQueue, + cpuprof, + forcegc, + pollDesc, # pollDesc can interact with timers, which can lock sched. + scavenge, + sweep, + sweepWaiters, + testR, + wakeableSleep +# Above SCHED are things that can call into the scheduler. +< SCHED +# Below SCHED is the scheduler implementation. +< allocmR, + execR +< sched; +sched < allg, allp; +allp, wakeableSleep < timers; +timers < netpollInit; + +# Channels +scavenge, sweep, testR, wakeableSleep < hchan; +NONE < notifyList; +hchan, notifyList < sudog; + +# Semaphores +NONE < root; + +# Itabs +NONE +< itab +< reflectOffs; + +# User arena state +NONE < userArenaState; + +# Tracing without a P uses a global trace buffer. +scavenge +# Above TRACEGLOBAL can emit a trace event without a P. +< TRACEGLOBAL +# Below TRACEGLOBAL manages the global tracing buffer. +# Note that traceBuf eventually chains to MALLOC, but we never get that far +# in the situation where there's no P. +< traceBuf; +# Starting/stopping tracing traces strings. +traceBuf < traceStrings; + +# Malloc +allg, + allocmR, + execR, # May grow stack + execW, # May allocate after BeforeFork + hchan, + notifyList, + reflectOffs, + timers, + traceStrings, + userArenaState +# Above MALLOC are things that can allocate memory. +< MALLOC +# Below MALLOC is the malloc implementation. +< fin, + spanSetSpine, + mspanSpecial, + MPROF; + +# We can acquire gcBitsArenas for pinner bits, and +# it's guarded by mspanSpecial. +MALLOC, mspanSpecial < gcBitsArenas; + +# Memory profiling +MPROF < profInsert, profBlock, profMemActive; +profMemActive < profMemFuture; + +# Stack allocation and copying +gcBitsArenas, + netpollInit, + profBlock, + profInsert, + profMemFuture, + spanSetSpine, + fin, + root +# Anything that can grow the stack can acquire STACKGROW. +# (Most higher layers imply STACKGROW, like MALLOC.) +< STACKGROW +# Below STACKGROW is the stack allocator/copying implementation. +< gscan; +gscan < stackpool; +gscan < stackLarge; +# Generally, hchan must be acquired before gscan. But in one case, +# where we suspend a G and then shrink its stack, syncadjustsudogs +# can acquire hchan locks while holding gscan. To allow this case, +# we use hchanLeaf instead of hchan. +gscan < hchanLeaf; + +# Write barrier +defer, + gscan, + mspanSpecial, + sudog +# Anything that can have write barriers can acquire WB. +# Above WB, we can have write barriers. +< WB +# Below WB is the write barrier implementation. +< wbufSpans; + +# Span allocator +stackLarge, + stackpool, + wbufSpans +# Above mheap is anything that can call the span allocator. +< mheap; +# Below mheap is the span allocator implementation. +# +# Specials: we're allowed to allocate a special while holding +# an mspanSpecial lock, and they're part of the malloc implementation. +# Pinner bits might be freed by the span allocator. +mheap, mspanSpecial < mheapSpecial; +mheap, mheapSpecial < globalAlloc; + +# Execution tracer events (with a P) +hchan, + mheap, + root, + sched, + traceStrings, + notifyList, + fin +# Above TRACE is anything that can create a trace event +< TRACE +< trace +< traceStackTab; + +# panic is handled specially. It is implicitly below all other locks. +NONE < panic; +# deadlock is not acquired while holding panic, but it also needs to be +# below all other locks. +panic < deadlock; +# raceFini is only held while exiting. +panic < raceFini; + +# RWMutex internal read lock + +allocmR, + allocmW +< allocmRInternal; + +execR, + execW +< execRInternal; + +testR, + testW +< testRInternal; +` + +// cyclicRanks lists lock ranks that allow multiple locks of the same +// rank to be acquired simultaneously. The runtime enforces ordering +// within these ranks using a separate mechanism. +var cyclicRanks = map[string]bool{ + // Multiple timers are locked simultaneously in destroy(). + "timers": true, + // Multiple hchans are acquired in hchan.sortkey() order in + // select. + "hchan": true, + // Multiple hchanLeafs are acquired in hchan.sortkey() order in + // syncadjustsudogs(). + "hchanLeaf": true, + // The point of the deadlock lock is to deadlock. + "deadlock": true, +} + +func main() { + flagO := flag.String("o", "", "write to `file` instead of stdout") + flagDot := flag.Bool("dot", false, "emit graphviz output instead of Go") + flag.Parse() + if flag.NArg() != 0 { + fmt.Fprintf(os.Stderr, "too many arguments") + os.Exit(2) + } + + g, err := dag.Parse(ranks) + if err != nil { + log.Fatal(err) + } + + var out []byte + if *flagDot { + var b bytes.Buffer + g.TransitiveReduction() + // Add cyclic edges for visualization. + for k := range cyclicRanks { + g.AddEdge(k, k) + } + // Reverse the graph. It's much easier to read this as + // a "<" partial order than a ">" partial order. This + // ways, locks are acquired from the top going down + // and time moves forward over the edges instead of + // backward. + g.Transpose() + generateDot(&b, g) + out = b.Bytes() + } else { + var b bytes.Buffer + generateGo(&b, g) + out, err = format.Source(b.Bytes()) + if err != nil { + log.Fatal(err) + } + } + + if *flagO != "" { + err = os.WriteFile(*flagO, out, 0666) + } else { + _, err = os.Stdout.Write(out) + } + if err != nil { + log.Fatal(err) + } +} + +func generateGo(w io.Writer, g *dag.Graph) { + fmt.Fprintf(w, `// Code generated by mklockrank.go; DO NOT EDIT. + +package runtime + +type lockRank int + +`) + + // Create numeric ranks. + topo := g.Topo() + for i, j := 0, len(topo)-1; i < j; i, j = i+1, j-1 { + topo[i], topo[j] = topo[j], topo[i] + } + fmt.Fprintf(w, ` +// Constants representing the ranks of all non-leaf runtime locks, in rank order. +// Locks with lower rank must be taken before locks with higher rank, +// in addition to satisfying the partial order in lockPartialOrder. +// A few ranks allow self-cycles, which are specified in lockPartialOrder. +const ( + lockRankUnknown lockRank = iota + +`) + for _, rank := range topo { + if isPseudo(rank) { + fmt.Fprintf(w, "\t// %s\n", rank) + } else { + fmt.Fprintf(w, "\t%s\n", cname(rank)) + } + } + fmt.Fprintf(w, `) + +// lockRankLeafRank is the rank of lock that does not have a declared rank, +// and hence is a leaf lock. +const lockRankLeafRank lockRank = 1000 +`) + + // Create string table. + fmt.Fprintf(w, ` +// lockNames gives the names associated with each of the above ranks. +var lockNames = []string{ +`) + for _, rank := range topo { + if !isPseudo(rank) { + fmt.Fprintf(w, "\t%s: %q,\n", cname(rank), rank) + } + } + fmt.Fprintf(w, `} + +func (rank lockRank) String() string { + if rank == 0 { + return "UNKNOWN" + } + if rank == lockRankLeafRank { + return "LEAF" + } + if rank < 0 || int(rank) >= len(lockNames) { + return "BAD RANK" + } + return lockNames[rank] +} +`) + + // Create partial order structure. + fmt.Fprintf(w, ` +// lockPartialOrder is the transitive closure of the lock rank graph. +// An entry for rank X lists all of the ranks that can already be held +// when rank X is acquired. +// +// Lock ranks that allow self-cycles list themselves. +var lockPartialOrder [][]lockRank = [][]lockRank{ +`) + for _, rank := range topo { + if isPseudo(rank) { + continue + } + list := []string{} + for _, before := range g.Edges(rank) { + if !isPseudo(before) { + list = append(list, cname(before)) + } + } + if cyclicRanks[rank] { + list = append(list, cname(rank)) + } + + fmt.Fprintf(w, "\t%s: {%s},\n", cname(rank), strings.Join(list, ", ")) + } + fmt.Fprintf(w, "}\n") +} + +// cname returns the Go const name for the given lock rank label. +func cname(label string) string { + return "lockRank" + strings.ToUpper(label[:1]) + label[1:] +} + +func isPseudo(label string) bool { + return strings.ToUpper(label) == label +} + +// generateDot emits a Graphviz dot representation of g to w. +func generateDot(w io.Writer, g *dag.Graph) { + fmt.Fprintf(w, "digraph g {\n") + + // Define all nodes. + for _, node := range g.Nodes { + fmt.Fprintf(w, "%q;\n", node) + } + + // Create edges. + for _, node := range g.Nodes { + for _, to := range g.Edges(node) { + fmt.Fprintf(w, "%q -> %q;\n", node, to) + } + } + + fmt.Fprintf(w, "}\n") +} |