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  }