blob: bc15e57dd4c9fae63a2fcc073261555121002cea [file] [log] [blame]
// 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;
# Scheduler, timers, netpoll
NONE < pollDesc, cpuprof;
assistQueue,
cpuprof,
forcegc,
pollDesc, # pollDesc can interact with timers, which can lock sched.
scavenge,
sweep,
sweepWaiters
< sched;
sched < allg, allp;
allp < timers;
timers < netpollInit;
# Channels
scavenge, sweep < hchan;
NONE < notifyList;
hchan, notifyList < sudog;
# RWMutex
NONE < rwmutexW;
rwmutexW, sysmon < rwmutexR;
# 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,
hchan,
notifyList,
reflectOffs,
timers,
traceStrings,
userArenaState
# Above MALLOC are things that can allocate memory.
< MALLOC
# Below MALLOC is the malloc implementation.
< fin,
gcBitsArenas,
mheapSpecial,
mspanSpecial,
spanSetSpine,
MPROF;
# 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, rwmutexR < 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.
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;
`
// 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")
}