mirror of https://go.googlesource.com/go
407 lines
9.1 KiB
Go
407 lines
9.1 KiB
Go
// 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;
|
|
|
|
NONE < timerSend;
|
|
|
|
# Scheduler, timers, netpoll
|
|
NONE < allocmW, execW, cpuprof, pollCache, pollDesc, wakeableSleep;
|
|
scavenge, sweep, testR, wakeableSleep, timerSend < hchan;
|
|
assistQueue,
|
|
cpuprof,
|
|
forcegc,
|
|
hchan,
|
|
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;
|
|
allocmR, execR, hchan < sched;
|
|
sched < allg, allp;
|
|
|
|
# Channels
|
|
NONE < notifyList;
|
|
hchan, notifyList < sudog;
|
|
|
|
hchan, pollDesc, wakeableSleep < timers;
|
|
timers, timerSend < timer < netpollInit;
|
|
|
|
# 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,
|
|
allp, # procresize
|
|
execR, # May grow stack
|
|
execW, # May allocate after BeforeFork
|
|
hchan,
|
|
notifyList,
|
|
reflectOffs,
|
|
timer,
|
|
traceStrings,
|
|
userArenaState
|
|
# Above MALLOC are things that can allocate memory.
|
|
< MALLOC
|
|
# Below MALLOC is the malloc implementation.
|
|
< fin,
|
|
spanSetSpine,
|
|
mspanSpecial,
|
|
traceTypeTab,
|
|
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,
|
|
pollCache,
|
|
sudog,
|
|
timer
|
|
# 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")
|
|
}
|