github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/memmodel/main.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Command memmodel is a memory model model checker. 6 // 7 // memmodel compares the relative strengths of different memory models 8 // using model checking techniques. It determines (up to typical 9 // limitations of model checking) which memory models are equivalent, 10 // stronger, and weaker than others, and produces this partial order 11 // as well as example programs that demonstrate the differences 12 // between non-equivalent memory models. 13 // 14 // 15 // Output 16 // 17 // memmodel supports several modes of output. 18 // 19 // With -graph, it generates a dot graph showing the partial order of 20 // memory models. Each node shows a set of equivalently strong memory 21 // models and edges point from the stronger models to the weaker 22 // models. 23 // 24 // With -examples, it outputs programs and outcomes showing the 25 // differences between non-equivalent memory models. 26 // 27 // With -all-progs, it outputs all programs it generates along with 28 // the outcomes allowed by all of the models. This is mostly useful 29 // for debugging. 30 // 31 // 32 // Supported memory models 33 // 34 // memmodel supports strict consistency (SC), x86-style total store 35 // order (TSO), acquire/release, and unordered memory models. 36 // 37 // Some of these memory models have two different, but equivalent 38 // specification strategies. Any model followed by "(HB)" is specified 39 // as a set rules for constructing a happens-before graph. Otherwise, 40 // the model is specified as an non-deterministic operational machine 41 // (e.g., TSO is implemented in terms of store buffers and store 42 // buffer forwarding). Operational machines tend to be easier to 43 // reason about, but the happens-before model is extremely flexible. 44 // Having both helps ensure that our specifications are what we 45 // intended. 46 // 47 // Likewise, some models have options. The operational implementation 48 // of TSO supports optional memory fences around loads and stores. 49 // 50 // 51 // How it works 52 // 53 // memmodel generates a large number of "litmus test" programs, where 54 // each program consists of a set of threads reading and writing 55 // shared variables. For each program, it determines all permissible 56 // outcomes under each memory model. If an outcome is permitted by 57 // memory model A but not memory model B, then A is weaker than B. 58 package main 59 60 import ( 61 "bytes" 62 "flag" 63 "fmt" 64 "io" 65 "os" 66 "strings" 67 ) 68 69 type Model interface { 70 Eval(p *Prog, outcomes *OutcomeSet) 71 String() string 72 } 73 74 var models = []Model{ 75 SCModel{}, 76 TSOModel{}, 77 TSOModel{StoreMFence: true}, 78 TSOModel{MFenceLoad: true}, 79 HBModel{HBSC{}}, 80 HBModel{HBTSO{}}, 81 HBModel{HBAcqRel{}}, 82 HBModel{HBUnordered{}}, 83 } 84 85 type Counterexample struct { 86 p Prog 87 weaker, stronger Model 88 wset, sset OutcomeSet 89 } 90 91 func (c *Counterexample) Print(w io.Writer) { 92 fmt.Fprintf(w, "%s is weaker than %s\n%s\n", c.weaker, c.stronger, &c.p) 93 printOutcomeTable(w, []string{c.weaker.String(), c.stronger.String()}, []OutcomeSet{c.wset, c.sset}) 94 } 95 96 func main() { 97 flagGraph := flag.String("graph", "", "write model graph to `output` dot file") 98 flagNoSimplify := flag.Bool("no-simplify", false, "disable graph simplification") 99 // TODO: If we were to write the examples repeatedly like we 100 // do for the graph, we could do the same graph reduction and 101 // only print a minimal set of examples. 102 flagExamples := flag.Bool("examples", false, "show examples where models differ") 103 // TODO: These big tables would be pretty nice if we could 104 // filter out the noise (e.g., only show them when models 105 // disagree, order the columns from stronger to weaker, 106 // collapse equivalent models). 107 flagAllProgs := flag.Bool("all-progs", false, "show all programs and outcomes") 108 flag.Parse() 109 if flag.NArg() > 0 { 110 flag.Usage() 111 os.Exit(2) 112 } 113 114 // counterexamples[i][j] gives an example program where model 115 // i permits outcomes that model j does not. 116 counterexamples := make([][]*Counterexample, len(models)) 117 for i := range counterexamples { 118 counterexamples[i] = make([]*Counterexample, len(models)) 119 } 120 121 n := 0 122 outcomes := make([]OutcomeSet, len(models)) 123 for p := range GenerateProgs() { 124 if !(*flagAllProgs || *flagExamples) && n%10 == 0 { 125 fmt.Fprintf(os.Stderr, "\r%d progs", n) 126 } 127 n++ 128 129 for i, model := range models { 130 model.Eval(&p, &outcomes[i]) 131 } 132 133 if *flagAllProgs { 134 fmt.Println(&p) 135 names := []string{} 136 for _, model := range models { 137 names = append(names, model.String()) 138 } 139 printOutcomeTable(os.Stdout, names, outcomes) 140 fmt.Println() 141 } 142 143 for i := range counterexamples { 144 for j := range counterexamples[i] { 145 if i == j { 146 continue 147 } 148 if counterexamples[i][j] != nil { 149 // Already have a counterexample. 150 continue 151 } 152 if outcomes[i] == outcomes[j] { 153 continue 154 } 155 if outcomes[i].Contains(&outcomes[j]) { 156 // Model i permits outcomes 157 // that model j does not. (i 158 // is weaker than j.) 159 c := &Counterexample{ 160 p, models[i], models[j], 161 outcomes[i], outcomes[j], 162 } 163 counterexamples[i][j] = c 164 if *flagExamples { 165 c.Print(os.Stdout) 166 fmt.Println() 167 } 168 } 169 // TODO: Prefer smaller 170 // counterexamples. 171 } 172 } 173 174 if n%100 == 0 && *flagGraph != "" { 175 // dot uses inotify wrong, so it doesn't 176 // notice if we write to a temp file and 177 // rename it over the output file. 178 f, err := os.Create(*flagGraph) 179 if err != nil { 180 fmt.Fprintln(os.Stderr, err) 181 os.Exit(1) 182 } 183 writeModelGraph(f, counterexamples, !*flagNoSimplify) 184 f.Close() 185 } 186 } 187 fmt.Fprintf(os.Stderr, "\r%d progs\n", n) 188 189 // Write final graph. 190 if *flagGraph != "" { 191 f, err := os.Create(*flagGraph) 192 if err != nil { 193 fmt.Fprintln(os.Stderr, err) 194 os.Exit(1) 195 } 196 defer f.Close() 197 writeModelGraph(f, counterexamples, !*flagNoSimplify) 198 } 199 } 200 201 func writeModelGraph(w io.Writer, counterexamples [][]*Counterexample, simplify bool) { 202 fmt.Fprintln(w, "digraph memmodel {") 203 if simplify { 204 fmt.Fprintln(w, "label=\"A -> B means A is stronger than B\";") 205 } else { 206 fmt.Fprintln(w, "label=\"A -> B means A is stronger than or equal to B\";") 207 } 208 209 // Create Graph. 210 g := new(Graph) 211 nodes := []*GNode{} 212 for _, model := range models { 213 nodes = append(nodes, g.NewNode(model.String())) 214 } 215 for i := range counterexamples { 216 for j, ce := range counterexamples[i] { 217 if i == j { 218 continue 219 } 220 if ce == nil { 221 // No counterexample. Model i is 222 // stronger than or equal to model j. 223 g.Edge(nodes[i], nodes[j]) 224 } else { 225 var buf bytes.Buffer 226 // Model i is weaker than model j. 227 // Print the counter example. 228 ce.Print(&buf) 229 fmt.Fprintf(w, "# %s\n", strings.Replace(buf.String(), "\n", "\n# ", -1)) 230 } 231 } 232 } 233 234 if simplify { 235 // Reduce equivalence classes to single nodes. Because 236 // this is currently a non-strict partial order, 237 // maximal cliques correspond to equivalence classes 238 // and are unambiguous. This makes the graph a strict 239 // partial order. 240 cliques := g.MaximalCliques() 241 g = g.CollapseNodes(cliques) 242 // Now that we have a strict partial order (a DAG), 243 // remove edges that are implied by other edges. 244 g.TransitiveReduction() 245 } 246 247 // Print graph. 248 g.ToDot(w, "") 249 250 fmt.Fprintln(w, "}") 251 }