A => .gitignore +2 -0
@@ 1,2 @@
+_build/
+build/
A => Makefile +11 -0
@@ 1,11 @@
+.PHONY: all check demo
+
+all:
+ dune build @all
+
+check:
+ dune runtest --force --no-buffer
+
+demo: all
+ _build/default/main.exe repair cases/peterson.sexp --out build/repair --animate
+ _build/default/main.exe contrast build/repair/witness.sexp build/repair/repair.sexp --animate
A => cases/buffering.sexp +11 -0
@@ 1,11 @@
+(case
+ (name "store buffering")
+ (names x y)
+ (bound 8)
+ (threads
+ ((store 0 1)
+ (load 1 0))
+ ((store 1 1)
+ (load 0 0)))
+ (candidates (0 1) (1 1))
+ (goal (outcome (registers (0 0 0) (1 0 0)) (memory ) (drained false))))
A => cases/fenced.sexp +13 -0
@@ 1,13 @@
+(case
+ (name "store buffering with explicit fences")
+ (names x y)
+ (bound 10)
+ (threads
+ ((store 0 1)
+ (fence)
+ (load 1 0))
+ ((store 1 1)
+ (fence)
+ (load 0 0)))
+ (candidates )
+ (goal (outcome (registers (0 0 0) (1 0 0)) (memory ) (drained false))))
A => cases/fifo.sexp +11 -0
@@ 1,11 @@
+(case
+ (name "FIFO publication order")
+ (names x y)
+ (bound 8)
+ (threads
+ ((store 0 1)
+ (store 1 1))
+ ((load 1 0)
+ (load 0 1)))
+ (candidates )
+ (goal (outcome (registers (1 0 1) (1 1 0)) (memory ) (drained false))))
A => cases/forwarding.sexp +11 -0
@@ 1,11 @@
+(case
+ (name "newest buffered store wins")
+ (names x)
+ (bound 7)
+ (threads
+ ((store 0 1)
+ (store 0 2)
+ (load 0 0))
+ ((move 0 0)))
+ (candidates )
+ (goal (outcome (registers (0 0 1)) (memory ) (drained false))))
A => cases/peterson.sexp +25 -0
@@ 1,25 @@
+(case
+ (name "Peterson mutual exclusion")
+ (names flag0 flag1 turn)
+ (bound 14)
+ (threads
+ ((store 0 1)
+ (store 2 1)
+ (load 1 0)
+ (branch 0 0 6)
+ (load 2 1)
+ (branch 1 1 2)
+ (enter)
+ (leave)
+ (store 0 0))
+ ((store 1 1)
+ (store 2 0)
+ (load 0 0)
+ (branch 0 0 6)
+ (load 2 1)
+ (branch 1 0 2)
+ (enter)
+ (leave)
+ (store 1 0)))
+ (candidates (0 1) (0 2) (1 1) (1 2))
+ (goal overlap))
A => cases/spinlock.sexp +17 -0
@@ 1,17 @@
+(case
+ (name "atomic exchange spinlock")
+ (names lock)
+ (bound 14)
+ (threads
+ ((exchange 0 0 1)
+ (branch 0 1 0)
+ (enter)
+ (leave)
+ (store 0 0))
+ ((exchange 0 0 1)
+ (branch 0 1 0)
+ (enter)
+ (leave)
+ (store 0 0)))
+ (candidates )
+ (goal overlap))
A => check.ml +168 -0
@@ 1,168 @@
+let checks = ref 0
+let visited = ref 0
+
+let require condition message =
+ if not condition then failwith message;
+ incr checks
+
+let rejects action message =
+ let failed = try action (); false with Invalid_argument _ | Failure _ -> true in
+ require failed message
+
+let read name = Syntax.load ("cases/" ^ name ^ ".sexp")
+
+let laws () =
+ let source = Layout.generate ~timeout:300000 ^ Store.read "smt/machine.smt2" ^
+ "\n" ^ Store.read "smt/laws.smt2" in
+ let answers = Sexp.parse (Solver.script source) in
+ require (answers = List.init 16 (fun _ -> Sexp.Atom "unsat")) "SMT machine laws";
+ print_endline "16 SMT machine laws passed"
+
+let codec () =
+ List.iter (fun text ->
+ let value = Sexp.Atom text in
+ require (Sexp.one (Sexp.print value) = value) "string round trip")
+ [""; "hello"; "with spaces"; "a\"b\\c"; "a\nb\tc"; "lambda \206\187"];
+ List.iter (fun text -> rejects (fun () -> ignore (Sexp.one text)) "malformed expression")
+ ["("; ")"; "(a"; "\"unfinished"; "a b"; "\"\\z\""];
+ require (Sexp.word (Sexp.Atom "#xffffffffffffffff") = -1L) "unsigned 64-bit model value";
+ List.iter (fun name ->
+ let case = read name in
+ require (Syntax.decode (Syntax.encode case) = case) "case round trip")
+ ["buffering"; "forwarding"; "fifo"; "fenced"; "peterson"; "spinlock"]
+
+let fixtures () =
+ List.iter (fun (name, expected) ->
+ let case = read name in
+ let result = Engine.check case Syntax.Tso in
+ let reachable, count = Machine.reachable case Syntax.Tso [] case.bound in
+ visited := !visited + count;
+ require ((result.witness <> None) = expected && reachable = expected) ("fixture " ^ name);
+ Option.iter (fun witness ->
+ let states, _ = Engine.replay witness in
+ require (Machine.bad case states.(witness.bound)) "witness final state";
+ require (List.length (View.frame ~width:148 witness witness.bound) < 52) "trace display height") result.witness;
+ Printf.printf "%-12s %-5s %5d reference states\n%!" name
+ (if result.witness = None then "unsat" else "sat") count)
+ ["buffering", true; "forwarding", false; "fifo", false;
+ "fenced", false; "peterson", true; "spinlock", false]
+
+let differential () =
+ let random = Random.State.make [|731|] in
+ let choose n = Random.State.int random n in
+ let instruction () = match choose 5 with
+ | 0 -> Syntax.Store (choose 2, Int64.of_int (choose 3))
+ | 1 -> Syntax.Load (choose 2, choose 2)
+ | 2 -> Syntax.Move (choose 2, Int64.of_int (choose 3))
+ | 3 -> Syntax.Fence
+ | _ -> Syntax.Exchange (choose 2, choose 2, 1L) in
+ for number = 0 to 17 do
+ let case = {
+ Syntax.name=Printf.sprintf "generated%d" number; names=[|"x"; "y"|]; bound=6;
+ threads=Array.init 2 (fun _ -> Array.init 2 (fun _ -> instruction ()));
+ candidates=[]; goal=Syntax.Outcome {
+ registers=[0, 0, Int64.of_int (choose 2); 1, 0, Int64.of_int (choose 2)];
+ memory=[]; drained=false};
+ } in
+ List.iter (fun model ->
+ let expected, count = Machine.reachable case model [] case.bound in
+ visited := !visited + count;
+ let result = Engine.check case model in
+ require ((result.witness <> None) = expected) "seeded differential query") [Syntax.Sc; Syntax.Tso]
+ done;
+ print_endline "36 seeded SMT queries matched exhaustive reference execution"
+
+let buffers () =
+ let stores = Array.init 10 (fun value -> Syntax.Store (0, Int64.of_int value)) in
+ let case = {Syntax.name="ring wrap"; names=[|"x"|]; bound=22;
+ threads=[|Array.append stores [|Syntax.Load (0, 0)|]; [|Syntax.Move (0, -1L)|]|];
+ candidates=[]; goal=Syntax.Outcome {registers=[0, 0, 9L; 1, 0, -1L]; memory=[]; drained=false}} in
+ List.iter (fun drained ->
+ let actors = Array.of_list (List.concat (List.init drained (fun _ -> [0; 2])) @
+ List.init (11 - drained) (fun _ -> 0) @ [1]) in
+ let result = Engine.check ~bound:(Array.length actors) ~fixed:actors case Syntax.Tso in
+ require (result.witness <> None) "ring reuse and last buffered write") [9; 10];
+ let initial = Machine.initial case in
+ let state = ref initial in
+ for _ = 0 to 7 do
+ match Machine.step case Syntax.Tso [] !state 0 with
+ | Some (next, _) -> state := next | None -> failwith "unexpected full buffer"
+ done;
+ require (Machine.step case Syntax.Tso [] !state 0 = None) "full FIFO blocks ninth store";
+ require (initial.pc = [|0; 0|] && initial.buffers = [|[]; []|]) "transition preserves previous state";
+ require (Machine.step case Syntax.Tso [] initial 2 = None) "empty FIFO cannot drain"
+
+let outcomes () =
+ let case = read "buffering" in
+ let sc = Engine.outcomes case Syntax.Sc in
+ let tso = Engine.outcomes case Syntax.Tso in
+ require (sc = [0L, 1L; 1L, 0L; 1L, 1L]) "SC outcome enumeration";
+ require (tso = [0L, 0L; 0L, 1L; 1L, 0L; 1L, 1L]) "TSO outcome enumeration";
+ print_endline "complete register outcome sets: SC=3, TSO=4"
+
+let graph () =
+ let case = read "peterson" in
+ let graph = Machine.explore case Syntax.Tso [] in
+ require (List.length graph.violations > 0) "unfenced graph exposes overlap";
+ let seen = Hashtbl.create (Array.length graph.states) in
+ Array.iter (fun state -> Hashtbl.add seen state ()) graph.states;
+ let edges = ref 0 in
+ Array.iter (fun state ->
+ for actor = 0 to 3 do
+ match Machine.step case Syntax.Tso [] state actor with
+ | None -> ()
+ | Some (next, _) -> incr edges; require (Hashtbl.mem seen next) "closed graph successor"
+ done) graph.states;
+ require (!edges = graph.edges) "graph edge count";
+ rejects (fun () -> ignore (Machine.explore ~limit:10 case Syntax.Tso [])) "exploration limit is explicit";
+ Printf.printf "unfenced graph closed: %d states, %d edges, %d violating states\n%!"
+ (Array.length graph.states) graph.edges (List.length graph.violations)
+
+let validation () =
+ let case = read "buffering" in
+ List.iter (fun case -> rejects (fun () -> Syntax.validate case) "invalid case") [
+ {case with bound=0}; {case with bound=65}; {case with names=[||]};
+ {case with threads=[||]}; {case with candidates=[0, 9]};
+ {case with candidates=[0, 1; 0, 1]};
+ {case with threads=[|[|Syntax.Load (0, 4)|]; case.threads.(1)|]};
+ {case with threads=[|[|Syntax.Branch (0, 0L, 3)|]; case.threads.(1)|]}];
+ rejects (fun () -> ignore (Engine.check ~fixed:[|0|] case Syntax.Tso)) "fixed schedule bound mismatch"
+
+let repair folder =
+ let case = read "peterson" in
+ let report = match Engine.repair case folder with
+ | Some report -> report | None -> failwith "expected repair" in
+ require (List.length report.fences = 2) "minimum repair has two fences";
+ let checked = Engine.audit (Filename.concat folder "repair.sexp") (Filename.concat folder "audit") in
+ require (report = checked) "repair certificate audit";
+ List.iter (fun fences ->
+ let found, _ = Machine.reachable case Syntax.Tso fences case.bound in
+ require found "every cheaper fence placement still fails") ([] :: List.map (fun location -> [location]) case.candidates);
+ let graph = Machine.explore case Syntax.Tso report.fences in
+ require (graph.violations = []) "repaired complete state graph excludes overlap";
+ let witness = Engine.load_witness (Filename.concat folder "witness.sexp") in
+ let fixed = Machine.reschedule case report.fences witness.actors in
+ Engine.validate_schedule case report.fences fixed;
+ let states, _ = Machine.replay case Syntax.Tso report.fences fixed in
+ require (not (Machine.bad case states.(Array.length states - 1))) "repaired requested schedule avoids overlap";
+ rejects (fun () -> ignore (Engine.replay {witness with actors=[|2|]})) "tampered witness rejected";
+ let unrepairable = {(read "buffering") with candidates=[]} in
+ require (Engine.repair unrepairable (Filename.concat folder "impossible") = None) "unrepairable case terminates";
+ Printf.printf "minimum repair checked: 2 fences; repaired graph closed with %d states and no overlap\n%!"
+ (Array.length graph.states)
+
+let () =
+ let started = Unix.gettimeofday () in
+ let folder = Filename.concat (Filename.get_temp_dir_name ()) (Printf.sprintf "reorder%d" (Unix.getpid ())) in
+ Store.mkdir folder;
+ codec ();
+ laws ();
+ fixtures ();
+ differential ();
+ buffers ();
+ outcomes ();
+ graph ();
+ validation ();
+ repair folder;
+ Printf.printf "%d checks passed; %d bounded reference states; %.1fs\n%!"
+ !checks !visited (Unix.gettimeofday () -. started)
A => dune +17 -0
@@ 1,17 @@
+(library
+ (name core)
+ (wrapped false)
+ (modules store sexp syntax machine layout solver engine view)
+ (libraries unix))
+
+(executable
+ (name main)
+ (public_name reorder)
+ (modules main)
+ (libraries core unix))
+
+(test
+ (name check)
+ (modules check)
+ (libraries core unix)
+ (deps (glob_files cases/*.sexp) (glob_files smt/*.smt2)))
A => dune-project +7 -0
@@ 1,7 @@
+(lang dune 3.20)
+(name reorder)
+(generate_opam_files true)
+(package
+ (name reorder)
+ (synopsis "Bounded x86-TSO checking and minimum fence synthesis")
+ (depends (ocaml (>= 5.5))))
A => engine.ml +241 -0
@@ 1,241 @@
+type witness = {
+ case : Syntax.t;
+ model : Syntax.model;
+ bound : int;
+ fences : Syntax.location list;
+ actors : int array;
+}
+type result = { witness : witness option; query : Solver.query }
+type phase = Searching of int * Syntax.location list
+ | Counterexample of witness | Excluded of int | Refining of int
+ | Verified of Syntax.location list * int * int
+type repair = {
+ case : Syntax.t;
+ fences : Syntax.location list;
+ clauses : int list list;
+ rounds : int;
+ bound : int;
+ states : int;
+}
+
+let decision = function
+ | Solver.Unknown reason -> failwith ("solver returned unknown: " ^ reason)
+ | answer -> answer
+
+let verify case model fences bound values =
+ let rec split count prefix = function
+ | rest when count = 0 -> List.rev prefix, rest
+ | value :: rest -> split (count - 1) (value :: prefix) rest
+ | [] -> failwith "incomplete scheduler model"
+ in
+ let actors, observations = split bound [] values in
+ let actors = Array.of_list (List.map (fun value -> Int64.to_int (Solver.bits value)) actors) in
+ let states, _ = Machine.replay case model fences actors in
+ let expected = List.concat (Array.to_list (Array.map Machine.observations states)) in
+ if List.map Solver.bits observations <> expected then
+ failwith "solver model disagrees with independent state-by-state replay";
+ actors, states
+
+let witness_sexp (witness : witness) =
+ let open Sexp in
+ List [Atom "witness"; List [Atom "version"; i 1];
+ List [Atom "case"; Syntax.encode witness.case];
+ List [Atom "model"; a (Syntax.model_name witness.model)];
+ List [Atom "bound"; i witness.bound];
+ List (Atom "fences" :: List.map (fun (t, pc) -> List [i t; i pc]) witness.fences);
+ List (Atom "actors" :: Array.to_list (Array.map i witness.actors))]
+
+let fields tag = function
+ | Sexp.List (Sexp.Atom name :: fields) when name = tag -> fields
+ | _ -> invalid_arg ("expected " ^ tag)
+let single key fields = match Sexp.field key fields with
+ | [value] -> value | _ -> invalid_arg ("invalid field " ^ key)
+
+let replay (witness : witness) =
+ Syntax.validate witness.case;
+ Syntax.validate {witness.case with candidates=witness.fences};
+ if witness.bound < 1 || witness.bound > 64 || Array.length witness.actors <> witness.bound then
+ invalid_arg "witness length differs from its bound";
+ let states, events = Machine.replay witness.case witness.model witness.fences witness.actors in
+ if not (Machine.bad witness.case states.(Array.length states - 1)) then
+ invalid_arg "witness does not reach the requested outcome";
+ states, events
+
+let load_witness path =
+ let fields = fields "witness" (Sexp.one (Store.read path)) in
+ if Sexp.integer (single "version" fields) <> 1 then invalid_arg "unsupported witness version";
+ let witness = {
+ case = Syntax.decode (single "case" fields);
+ model = Syntax.model (Sexp.atom (single "model" fields));
+ bound = Sexp.integer (single "bound" fields);
+ fences = List.map Syntax.location (Sexp.field "fences" fields);
+ actors = Array.of_list (List.map Sexp.integer (Sexp.field "actors" fields));
+ } in
+ ignore (replay witness);
+ witness
+
+let save folder result =
+ Store.mkdir folder;
+ Store.write (Filename.concat folder "query.smt2") (Solver.standalone result.query);
+ let path = Filename.concat folder "witness.sexp" in
+ match result.witness with
+ | Some witness -> Store.write path (Sexp.print (witness_sexp witness) ^ "\n")
+ | None -> if Sys.file_exists path then Sys.remove path
+
+let check ?(timeout=300000) ?(tick=fun () -> ()) ?bound ?(fences=[]) ?folder ?fixed case model =
+ let bound = Option.value bound ~default:case.Syntax.bound in
+ let query = Solver.compile ~timeout ?fixed case model fences bound in
+ let witness = match decision (Solver.solve ~timeout ~tick query) with
+ | Solver.Unsat -> None
+ | Solver.Sat values ->
+ let actors, states = verify case model fences bound values in
+ if not (Machine.bad case states.(bound)) then failwith "invalid final model state";
+ Some {case; model; bound; fences; actors}
+ | Solver.Unknown _ -> assert false in
+ let result = {witness; query} in
+ Option.iter (fun folder -> save folder result) folder;
+ result
+
+let blockers (witness : witness) =
+ let states, _ = Machine.replay witness.case Syntax.Tso [] witness.actors in
+ let result = ref [] in
+ Array.iteri (fun i actor ->
+ if actor < 2 && states.(i).buffers.(actor) <> [] then
+ List.iteri (fun candidate location ->
+ if location = (actor, states.(i).pc.(actor)) then result := candidate :: !result)
+ witness.case.candidates) witness.actors;
+ List.sort_uniq compare !result
+
+let repair_sexp (repair : repair) =
+ let open Sexp in
+ List [Atom "repair"; List [Atom "case"; Syntax.encode repair.case];
+ List [Atom "rounds"; i repair.rounds];
+ List [Atom "bound"; i repair.bound]; List [Atom "states"; i repair.states];
+ List (Atom "fences" :: List.map (fun (t, pc) -> List [i t; i pc]) repair.fences);
+ List (Atom "clauses" :: List.map (fun clause -> List (List.map i clause)) repair.clauses)]
+
+let load_repair path =
+ let fields = fields "repair" (Sexp.one (Store.read path)) in
+ let repair = {
+ case = Syntax.decode (single "case" fields);
+ rounds = Sexp.integer (single "rounds" fields);
+ bound = Sexp.integer (single "bound" fields);
+ states = Sexp.integer (single "states" fields);
+ fences = List.map Syntax.location (Sexp.field "fences" fields);
+ clauses = List.map (fun clause -> List.map Sexp.integer (Sexp.list clause)) (Sexp.field "clauses" fields);
+ } in
+ if repair.bound < repair.case.bound || repair.bound > 64 || repair.states < 1 ||
+ repair.rounds <> List.length repair.clauses + 1 ||
+ List.sort_uniq compare repair.fences <> List.sort compare repair.fences ||
+ not (List.for_all (fun location -> List.mem location repair.case.candidates) repair.fences)
+ then invalid_arg "invalid repair certificate";
+ repair
+
+let repair ?(timeout=300000) ?(tick=fun () -> ()) ?(notify=fun _ -> ()) case folder =
+ Store.mkdir folder;
+ List.iter (fun name ->
+ let path = Filename.concat folder name in
+ if Sys.file_exists path then Sys.remove path) ["repair.sexp"; "witness.sexp"];
+ let count = List.length case.Syntax.candidates in
+ let clauses = ref [] in
+ let rounds = ref 0 in
+ let search_bound = ref case.bound in
+ let rec search budget =
+ if budget > count then failwith "fence search exhausted";
+ let candidate = Solver.candidate ~count ~budget !clauses in
+ Store.write (Filename.concat folder "synthesis.smt2") (Solver.standalone candidate);
+ match decision (Solver.solve ~timeout ~tick candidate) with
+ | Solver.Unsat -> notify (Excluded budget); search (budget + 1)
+ | Solver.Sat values ->
+ let selected = List.filter_map Fun.id (List.mapi (fun i value ->
+ if Solver.truth value then Some (List.nth case.candidates i) else None) values) in
+ notify (Searching (budget, selected));
+ let target = Filename.concat folder (Printf.sprintf "round%02d" !rounds) in
+ let bounded = check ~timeout ~tick ~bound:!search_bound ~fences:selected ~folder:target case Syntax.Tso in
+ let result, closure = match bounded.witness with
+ | Some _ -> bounded, None
+ | None ->
+ let graph = Machine.explore case Syntax.Tso selected in
+ match Machine.counterexample graph with
+ | None -> bounded, Some graph
+ | Some actors ->
+ search_bound := max !search_bound (Array.length actors);
+ notify (Refining !search_bound);
+ let deeper = check ~timeout ~tick ~bound:(Array.length actors)
+ ~fixed:actors ~fences:selected ~folder:target case Syntax.Tso in
+ if deeper.witness = None then failwith "graph counterexample has no SMT execution";
+ deeper, None
+ in
+ incr rounds;
+ begin match result.witness with
+ | None ->
+ let lower = Solver.candidate ~count ~budget:(List.length selected - 1) !clauses in
+ begin match decision (Solver.solve ~timeout ~tick lower) with
+ | Solver.Unsat -> () | _ -> failwith "minimum fence certificate is satisfiable"
+ end;
+ Store.write (Filename.concat folder "minimal.smt2") (Solver.standalone lower);
+ let graph = match closure with Some graph -> graph | None -> assert false in
+ let report = {case; fences = selected; clauses = !clauses; rounds = !rounds;
+ bound= !search_bound; states=Array.length graph.states} in
+ Store.write (Filename.concat folder "repair.sexp") (Sexp.print (repair_sexp report) ^ "\n");
+ notify (Verified (selected, report.bound, report.states));
+ Some report
+ | Some witness ->
+ if !rounds = 1 then Store.write (Filename.concat folder "witness.sexp")
+ (Sexp.print (witness_sexp witness) ^ "\n");
+ notify (Counterexample witness);
+ let clause = blockers witness in
+ if clause = [] then None else begin
+ if List.mem clause !clauses then failwith "repeated excluded counterexample";
+ clauses := !clauses @ [clause];
+ search budget
+ end
+ end
+ | Solver.Unknown _ -> assert false
+ in
+ search 0
+
+let certify ?(timeout=300000) ?(tick=fun () -> ()) path =
+ let report = load_repair path in
+ List.iteri (fun i clause ->
+ let path = Filename.concat (Filename.dirname path) (Printf.sprintf "round%02d/witness.sexp" i) in
+ let witness = load_witness path in
+ if witness.case <> report.case || witness.model <> Syntax.Tso || witness.bound > report.bound || blockers witness <> clause then
+ invalid_arg "counterexample does not support its fence constraint") report.clauses;
+ let lower = Solver.candidate ~count:(List.length report.case.candidates)
+ ~budget:(List.length report.fences - 1) report.clauses in
+ begin match decision (Solver.solve ~timeout ~tick lower) with
+ | Solver.Unsat -> () | _ -> invalid_arg "minimum fence count is unsupported"
+ end;
+ let graph = Machine.explore report.case Syntax.Tso report.fences in
+ if graph.violations <> [] || Array.length graph.states <> report.states then
+ invalid_arg "repair state graph does not support the certificate";
+ report
+
+let audit ?(timeout=300000) ?(tick=fun () -> ()) path folder =
+ let report = certify ~timeout ~tick path in
+ let verified = check ~timeout ~tick ~bound:report.bound ~fences:report.fences ~folder report.case Syntax.Tso in
+ if verified.witness <> None then invalid_arg "repair still admits the forbidden outcome";
+ report
+
+let validate_schedule ?(timeout=300000) case fences actors =
+ let bound = Array.length actors in
+ let query = Solver.compile ~timeout ~fixed:actors ~require_goal:false case Syntax.Tso fences bound in
+ match decision (Solver.solve ~timeout query) with
+ | Solver.Sat values -> ignore (verify case Syntax.Tso fences bound values)
+ | _ -> failwith "repaired schedule has no matching SMT execution"
+
+let outcomes ?(timeout=300000) ?(tick=fun () -> ()) case model =
+ let case = {case with Syntax.goal = Syntax.Outcome {registers=[]; memory=[]; drained=false}} in
+ let rec loop found =
+ if List.length found = 16 then failwith "outcome enumeration exceeded sixteen distinct register pairs";
+ let query = Solver.compile ~timeout ~blocks:found case model [] case.bound in
+ match decision (Solver.solve ~timeout ~tick query) with
+ | Solver.Unsat -> List.sort_uniq compare found
+ | Solver.Sat values ->
+ let _, states = verify case model [] case.bound values in
+ let final = states.(case.bound) in
+ loop ((final.regs.(0).(0), final.regs.(1).(0)) :: found)
+ | Solver.Unknown _ -> assert false
+ in
+ loop []
A => engine.mli +33 -0
@@ 1,33 @@
+type witness = {
+ case : Syntax.t;
+ model : Syntax.model;
+ bound : int;
+ fences : Syntax.location list;
+ actors : int array;
+}
+type result = { witness : witness option; query : Solver.query }
+type phase = Searching of int * Syntax.location list
+ | Counterexample of witness | Excluded of int | Refining of int
+ | Verified of Syntax.location list * int * int
+type repair = {
+ case : Syntax.t;
+ fences : Syntax.location list;
+ clauses : int list list;
+ rounds : int;
+ bound : int;
+ states : int;
+}
+val check : ?timeout:int -> ?tick:(unit -> unit) -> ?bound:int ->
+ ?fences:Syntax.location list -> ?folder:string -> ?fixed:int array ->
+ Syntax.t -> Syntax.model -> result
+val witness_sexp : witness -> Sexp.t
+val load_witness : string -> witness
+val replay : witness -> Machine.t array * Machine.event array
+val save : string -> result -> unit
+val repair : ?timeout:int -> ?tick:(unit -> unit) -> ?notify:(phase -> unit) ->
+ Syntax.t -> string -> repair option
+val load_repair : string -> repair
+val certify : ?timeout:int -> ?tick:(unit -> unit) -> string -> repair
+val audit : ?timeout:int -> ?tick:(unit -> unit) -> string -> string -> repair
+val validate_schedule : ?timeout:int -> Syntax.t -> Syntax.location list -> int array -> unit
+val outcomes : ?timeout:int -> ?tick:(unit -> unit) -> Syntax.t -> Syntax.model -> (int64 * int64) list
A => layout.ml +53 -0
@@ 1,53 @@
+let join = String.concat " "
+
+let structure name constructor fields =
+ let width = List.fold_left (fun sum (_, _, size) -> sum + size) 0 fields in
+ let arguments = join (List.map (fun (field, kind, _) -> Printf.sprintf "(%s %s)" field kind) fields) in
+ let values = join (List.map (fun (field, _, _) -> field) fields) in
+ let high = ref (width - 1) in
+ let selectors = List.map (fun (field, kind, size) ->
+ let low = !high - size + 1 in
+ let result = Printf.sprintf
+ "(define-fun %s ((cells %s)) %s ((_ extract %d %d) cells))" field name kind !high low in
+ high := low - 1;
+ result) fields in
+ [Printf.sprintf "(define-sort %s () (_ BitVec %d))" name width;
+ Printf.sprintf "(define-fun %s (%s) %s (concat %s))" constructor arguments name values] @ selectors
+
+let record name prefix element count size =
+ let fields = List.init count (fun i -> Printf.sprintf "%s%d" prefix i, element, size) in
+ let lines = structure name prefix fields in
+ let value = ref (Printf.sprintf "(%s%d cells)" prefix (count - 1)) in
+ for i = count - 2 downto 0 do
+ value := Printf.sprintf "(ite (= i #x%02x) (%s%d cells) %s)" i prefix i !value
+ done;
+ let fields = List.init count (fun i ->
+ Printf.sprintf "(ite (= i #x%02x) v (%s%d cells))" i prefix i) in
+ lines @ [Printf.sprintf "(define-fun at ((cells %s) (i Index)) %s\n %s)" name element !value;
+ Printf.sprintf "(define-fun update ((cells %s) (i Index) (v %s)) %s\n (%s %s))"
+ name element name prefix (join fields)]
+
+let generate ~timeout =
+ if timeout <= 0 then invalid_arg "timeout must be positive";
+ let lines = ["(set-logic QF_BV)"; "(set-option :produce-models true)";
+ Printf.sprintf "(set-option :timeout %d)" timeout;
+ "(define-sort Index () (_ BitVec 8))"; "(define-sort Word () (_ BitVec 64))"] @
+ record "Slots" "indices" "Index" 16 8 @
+ record "Words" "words" "Word" 16 64 @
+ record "Queues" "queues" "Words" 2 1024 @
+ record "Positions" "positions" "Slots" 2 128 @
+ structure "State" "state" [
+ "memory", "Words", 1024; "registers", "Words", 1024;
+ "counters", "Slots", 128; "heads", "Slots", 128; "tails", "Slots", 128;
+ "addresses", "Positions", 256; "values", "Queues", 2048;
+ "positions", "Positions", 256; "critical", "Slots", 128] in
+ let constants = List.map (fun (name, kind, constructor, value, count) ->
+ Printf.sprintf "(define-fun %s () %s\n (%s %s))" name kind constructor
+ (join (List.init count (fun _ -> value)))) [
+ "zeros", "Words", "words", "#x0000000000000000", 16;
+ "slots", "Slots", "indices", "#x00", 16;
+ "absent", "Slots", "indices", "#xff", 16;
+ "emptyqueues", "Queues", "queues", "zeros", 2;
+ "emptypositions", "Positions", "positions", "slots", 2;
+ "unseen", "Positions", "positions", "absent", 2] in
+ String.concat "\n\n" (lines @ constants) ^ "\n"
A => layout.mli +1 -0
@@ 1,1 @@
+val generate : timeout:int -> string
A => machine.ml +183 -0
@@ 1,183 @@
+type origin = Memory | Buffer
+type event = Idle | Drain of int * int * int64
+ | Issue of int * int * Syntax.instruction * origin option
+type t = {
+ pc : int array;
+ regs : int64 array array;
+ memory : int64 array;
+ buffers : (int * int64) list array;
+ critical : bool array;
+}
+
+let initial (case : Syntax.t) = {
+ pc = [|0; 0|]; regs = Array.init 2 (fun _ -> Array.make 4 0L);
+ memory = Array.make (Array.length case.names) 0L;
+ buffers = [|[]; []|]; critical = [|false; false|];
+}
+
+let copy state = {
+ pc = Array.copy state.pc; regs = Array.map Array.copy state.regs;
+ memory = Array.copy state.memory; buffers = Array.copy state.buffers;
+ critical = Array.copy state.critical;
+}
+
+let step (case : Syntax.t) memory_model fences state actor =
+ let open Syntax in
+ if actor < 0 || actor > 4 then invalid_arg "invalid scheduler choice";
+ if actor = 4 then Some (state, Idle) else
+ let t = actor mod 2 in
+ let out = copy state in
+ if actor >= 2 then
+ match state.buffers.(t) with
+ | [] -> None
+ | (a, v) :: tail ->
+ out.buffers.(t) <- tail;
+ out.memory.(a) <- v;
+ Some (out, Drain (t, a, v))
+ else
+ let pc = state.pc.(t) in
+ if pc >= Array.length case.threads.(t) ||
+ (List.mem (t, pc) fences && state.buffers.(t) <> []) then None
+ else begin
+ let ins = case.threads.(t).(pc) in
+ out.pc.(t) <- pc + 1;
+ let done_ origin = Some (out, Issue (t, pc, ins, origin)) in
+ match ins with
+ | Store (a, v) ->
+ if List.length state.buffers.(t) = 8 then None
+ else begin
+ if memory_model = Sc then out.memory.(a) <- v
+ else out.buffers.(t) <- state.buffers.(t) @ [a, v];
+ done_ None
+ end
+ | Load (a, r) ->
+ let value, origin = List.fold_left (fun (value, origin) (address, stored) ->
+ if address = a then stored, Buffer else value, origin)
+ (state.memory.(a), Memory) state.buffers.(t) in
+ out.regs.(t).(r) <- value;
+ done_ (Some origin)
+ | Fence -> if state.buffers.(t) <> [] then None else done_ None
+ | Move (r, v) -> out.regs.(t).(r) <- v; done_ None
+ | Branch (r, v, target) ->
+ if state.regs.(t).(r) = v then out.pc.(t) <- target;
+ done_ None
+ | Enter -> out.critical.(t) <- true; done_ None
+ | Leave -> out.critical.(t) <- false; done_ None
+ | Exchange (a, r, v) ->
+ if state.buffers.(t) <> [] then None else begin
+ out.regs.(t).(r) <- state.memory.(a);
+ out.memory.(a) <- v;
+ done_ None
+ end
+ end
+
+let replay case model fences actors =
+ let states = Array.make (Array.length actors + 1) (initial case) in
+ let events = Array.make (Array.length actors) Idle in
+ Array.iteri (fun i actor ->
+ match step case model fences states.(i) actor with
+ | None -> invalid_arg (Printf.sprintf "disabled transition at step %d" i)
+ | Some (state, event) -> states.(i + 1) <- state; events.(i) <- event) actors;
+ states, events
+
+let observations state =
+ List.map Int64.of_int (Array.to_list state.pc) @
+ Array.to_list state.regs.(0) @ Array.to_list state.regs.(1) @
+ Array.to_list state.memory @
+ Array.to_list (Array.map (fun queue -> Int64.of_int (List.length queue)) state.buffers) @
+ Array.to_list (Array.map (fun active -> if active then 1L else 0L) state.critical)
+
+let bad (case : Syntax.t) state =
+ match case.goal with
+ | Syntax.Overlap -> state.critical.(0) && state.critical.(1)
+ | Syntax.Outcome goal ->
+ Array.for_all (fun t -> state.pc.(t) = Array.length case.threads.(t)) [|0; 1|] &&
+ List.for_all (fun (t, r, v) -> state.regs.(t).(r) = v) goal.registers &&
+ List.for_all (fun (a, v) -> state.memory.(a) = v) goal.memory &&
+ (not goal.drained || Array.for_all ((=) []) state.buffers)
+
+let reachable case model fences bound =
+ let start = initial case in
+ let todo = Queue.create () in
+ let seen = Hashtbl.create 512 in
+ Queue.add (start, 0) todo;
+ Hashtbl.add seen start ();
+ let rec search () =
+ if Queue.is_empty todo then false, Hashtbl.length seen else
+ let state, depth = Queue.take todo in
+ if bad case state then true, Hashtbl.length seen else begin
+ if depth < bound then
+ for actor = 0 to 3 do
+ match step case model fences state actor with
+ | Some (next, _) when not (Hashtbl.mem seen next) ->
+ Hashtbl.add seen next ();
+ Queue.add (next, depth + 1) todo
+ | _ -> ()
+ done;
+ search ()
+ end
+ in
+ search ()
+
+let reschedule case fences requested =
+ let state = ref (initial case) in
+ let actors = ref [] in
+ let execute actor = match step case Syntax.Tso fences !state actor with
+ | None -> false
+ | Some (next, _) -> state := next; actors := actor :: !actors; true
+ in
+ Array.iter (fun actor ->
+ if not (execute actor) then begin
+ if actor < 2 then begin
+ while (!state).buffers.(actor) <> [] do
+ if not (execute (actor + 2)) then failwith "cannot drain pending store"
+ done
+ end;
+ if not (execute actor) then ignore (execute 4)
+ end) requested;
+ Array.of_list (List.rev !actors)
+
+type graph = { states : t array; parents : (int * int) array; edges : int; violations : int list }
+
+let explore ?(limit=250000) ?(visit=fun _ _ -> ()) case model fences =
+ let start = initial case in
+ let todo = Queue.create () in
+ let seen = Hashtbl.create 2048 in
+ let states = ref [] in
+ let violations = ref [] in
+ let edges = ref 0 in
+ let insert state parent actor =
+ let id = Hashtbl.length seen in
+ if id >= limit then failwith "state graph exceeds the exploration limit";
+ Hashtbl.add seen state id;
+ states := (id, state, parent, actor) :: !states;
+ Queue.add (id, state) todo;
+ visit state (id + 1)
+ in
+ insert start (-1) 4;
+ while not (Queue.is_empty todo) do
+ let id, state = Queue.take todo in
+ if bad case state then violations := id :: !violations;
+ for actor = 0 to 3 do
+ match step case model fences state actor with
+ | None -> ()
+ | Some (next, _) ->
+ incr edges;
+ if not (Hashtbl.mem seen next) then insert next id actor
+ done
+ done;
+ let values = Array.make (Hashtbl.length seen) start in
+ let parents = Array.make (Array.length values) (-1, 4) in
+ List.iter (fun (id, state, parent, actor) ->
+ values.(id) <- state; parents.(id) <- parent, actor) !states;
+ {states=values; parents; edges= !edges; violations=List.rev !violations}
+
+let counterexample graph =
+ match graph.violations with
+ | [] -> None
+ | id :: _ ->
+ let rec path id actors =
+ let parent, actor = graph.parents.(id) in
+ if parent < 0 then Array.of_list actors else path parent (actor :: actors)
+ in
+ Some (path id [])
A => machine.mli +21 -0
@@ 1,21 @@
+type origin = Memory | Buffer
+type event = Idle | Drain of int * int * int64
+ | Issue of int * int * Syntax.instruction * origin option
+type t = {
+ pc : int array;
+ regs : int64 array array;
+ memory : int64 array;
+ buffers : (int * int64) list array;
+ critical : bool array;
+}
+val initial : Syntax.t -> t
+val step : Syntax.t -> Syntax.model -> Syntax.location list -> t -> int -> (t * event) option
+val replay : Syntax.t -> Syntax.model -> Syntax.location list -> int array -> t array * event array
+val observations : t -> int64 list
+val bad : Syntax.t -> t -> bool
+val reachable : Syntax.t -> Syntax.model -> Syntax.location list -> int -> bool * int
+val reschedule : Syntax.t -> Syntax.location list -> int array -> int array
+type graph = { states : t array; parents : (int * int) array; edges : int; violations : int list }
+val explore : ?limit:int -> ?visit:(t -> int -> unit) -> Syntax.t ->
+ Syntax.model -> Syntax.location list -> graph
+val counterexample : graph -> int array option
A => main.ml +130 -0
@@ 1,130 @@
+let () =
+ let exit_code = ref 0 in
+ try
+ if Array.length Sys.argv < 2 then
+ invalid_arg "usage: reorder check|repair|replay|contrast|compare|graph|audit|laws|layout|emit [file] [options]";
+ let command = Sys.argv.(1) in
+ let files = ref [] in
+ let model = ref Syntax.Tso in
+ let bound = ref None in
+ let fences = ref [] in
+ let folder = ref None in
+ let animate = ref false in
+ let delay = ref 0.7 in
+ let width = ref 148 in
+ let timeout = ref 300000 in
+ let location text = match String.split_on_char ':' text with
+ | [t; pc] -> int_of_string t, int_of_string pc
+ | _ -> invalid_arg "fence locations use thread:instruction" in
+ let options = [
+ "--model", Arg.String (fun text -> model := Syntax.model text), "sc or tso";
+ "--bound", Arg.Int (fun value -> bound := Some value), "maximum scheduler transitions";
+ "--fence", Arg.String (fun text -> fences := !fences @ [location text]), "thread:instruction";
+ "--out", Arg.String (fun path -> folder := Some path), "output file or directory";
+ "--animate", Arg.Set animate, "animate actual execution and solver progress";
+ "--delay", Arg.Set_float delay, "seconds per displayed transition";
+ "--width", Arg.Set_int width, "terminal width between 78 and 180 columns";
+ "--timeout", Arg.Set_int timeout, "solver budget in milliseconds";
+ ] in
+ let current = ref 1 in
+ Arg.parse_argv ~current Sys.argv options (fun path -> files := !files @ [path]) "reorder";
+ if !timeout < 1 || !timeout > 3600000 then invalid_arg "timeout outside 1..3600000 milliseconds";
+ let file () = match !files with path :: _ -> path | [] -> invalid_arg "missing input file" in
+ let output fallback = Option.value !folder ~default:fallback in
+ let load () =
+ let case = Syntax.load (file ()) in
+ let case = match !bound with None -> case | Some bound -> {case with Syntax.bound} in
+ Syntax.validate case;
+ case in
+ let with_view case action =
+ let view = View.create ~width:!width ~delay:!delay ~animate:!animate case in
+ Fun.protect ~finally:(fun () -> View.finish view) (fun () -> action view) in
+ let report case result =
+ Printf.printf "%s\nmodel=%s bound=%d buffer<=8\n" (View.headline case.Syntax.name)
+ (Syntax.model_name !model) case.bound;
+ match result.Engine.witness with
+ | None -> print_endline "UNSAT requested outcome excluded within the bound"
+ | Some witness ->
+ Printf.printf "SAT %d transitions; independent replay passed\n"
+ (Array.fold_left (fun n actor -> if actor = 4 then n else n + 1) 0 witness.actors);
+ exit_code := 1 in
+ begin match command with
+ | "check" ->
+ let case = load () in
+ let result = with_view case (fun view ->
+ Engine.check ~timeout:!timeout ~tick:(fun () -> View.tick view)
+ ~fences:!fences ~folder:(output "build/check") case !model) in
+ report case result
+ | "repair" ->
+ let case = load () in
+ let result = with_view case (fun view ->
+ Engine.repair ~timeout:!timeout ~tick:(fun () -> View.tick view)
+ ~notify:(View.notify view) case (output "build/repair")) in
+ begin match result with
+ | None -> print_endline "No repair exists at the candidate locations"; exit_code := 1
+ | Some result ->
+ Printf.printf "Minimum repair: %d fences; bound %d; %d checked counterexamples\n"
+ (List.length result.fences) result.bound (List.length result.clauses)
+ end
+ | "replay" ->
+ let witness = Engine.load_witness (file ()) in
+ with_view witness.case (fun view -> View.play view witness)
+ | "contrast" ->
+ let witness_path, repair_path = match !files with
+ | [left; right] -> left, right | _ -> invalid_arg "contrast needs a witness and a repair" in
+ let witness = Engine.load_witness witness_path in
+ let repair = Engine.certify ~timeout:!timeout repair_path in
+ with_view witness.case (fun view -> View.contrast view witness repair)
+ | "compare" ->
+ let case = load () in
+ with_view case (fun view ->
+ View.phase view "enumerating sequentially consistent outcomes";
+ let sc = Engine.outcomes ~timeout:!timeout ~tick:(fun () -> View.tick view) case Syntax.Sc in
+ View.phase view "enumerating TSO outcomes";
+ let tso = Engine.outcomes ~timeout:!timeout ~tick:(fun () -> View.tick view) case Syntax.Tso in
+ View.outcomes view sc tso)
+ | "graph" ->
+ let case = load () in
+ let fences = match !files with
+ | [_; path] ->
+ let repair = Engine.certify ~timeout:!timeout path in
+ if case <> repair.case then invalid_arg "graph and repair cases differ";
+ repair.fences
+ | [_] -> !fences
+ | _ -> invalid_arg "graph expects a case and an optional repair" in
+ with_view case (fun view -> ignore (View.graph view fences))
+ | "audit" ->
+ let repair = Engine.audit ~timeout:!timeout (file ()) (output "build/audit") in
+ Printf.printf "repair verified at bound %d\n%d witnesses replayed; lower-budget certificate UNSAT\n"
+ repair.bound (List.length repair.clauses)
+ | "layout" ->
+ let path = output "build/layout.smt2" in
+ Store.write path (Layout.generate ~timeout:!timeout);
+ print_endline path
+ | "emit" ->
+ let case = load () in
+ let query = Solver.compile ~timeout:!timeout case !model !fences case.bound in
+ let path = output "build/query.smt2" in
+ Store.write path (Solver.standalone query);
+ print_endline path
+ | "laws" ->
+ let root = Store.root () in
+ let text = Layout.generate ~timeout:!timeout ^
+ Store.read (Filename.concat root "smt/machine.smt2") ^ "\n" ^
+ Store.read (Filename.concat root "smt/laws.smt2") in
+ Store.write (output "build/laws.smt2") text;
+ let responses = Sexp.parse (Solver.script ~timeout:!timeout text) in
+ if responses <> List.init 16 (fun _ -> Sexp.Atom "unsat") then failwith "machine law check failed";
+ print_endline "16 SMT machine laws verified"
+ | _ -> invalid_arg "unknown command"
+ end;
+ exit !exit_code
+ with
+ | Invalid_argument message | Failure message | Sys_error message ->
+ Printf.eprintf "reorder: %s\n" message;
+ exit 2
+ | Unix.Unix_error (error, operation, _) ->
+ Printf.eprintf "reorder: %s: %s\n" operation (Unix.error_message error);
+ exit 2
+ | Arg.Bad message -> prerr_string message; exit 2
+ | Arg.Help message -> print_string message
A => reorder.opam +23 -0
@@ 1,23 @@
+# This file is generated by dune, edit dune-project instead
+opam-version: "2.0"
+synopsis: "Bounded x86-TSO checking and minimum fence synthesis"
+depends: [
+ "dune" {>= "3.20"}
+ "ocaml" {>= "5.5"}
+ "odoc" {with-doc}
+]
+build: [
+ ["dune" "subst"] {dev}
+ [
+ "dune"
+ "build"
+ "-p"
+ name
+ "-j"
+ jobs
+ "@install"
+ "@runtest" {with-test}
+ "@doc" {with-doc}
+ ]
+]
+x-maintenance-intent: ["(latest)"]
A => sexp.ml +111 -0
@@ 1,111 @@
+type t = Atom of string | List of t list
+
+let parse text =
+ let length = String.length text in
+ let position = ref 0 in
+ let fail message = invalid_arg (Printf.sprintf "%s at byte %d" message !position) in
+ let rec space () =
+ if !position < length then
+ match text.[!position] with
+ | ' ' | '\n' | '\r' | '\t' -> incr position; space ()
+ | _ -> ()
+ in
+ let quoted () =
+ incr position;
+ let buffer = Buffer.create 32 in
+ let rec loop () =
+ if !position = length then fail "unterminated string";
+ let character = text.[!position] in
+ incr position;
+ match character with
+ | '"' -> Buffer.contents buffer
+ | '\\' ->
+ if !position = length then fail "incomplete escape";
+ let escaped = match text.[!position] with
+ | 'n' -> '\n' | 'r' -> '\r' | 't' -> '\t'
+ | '"' -> '"' | '\\' -> '\\'
+ | _ -> fail "unsupported escape"
+ in
+ incr position;
+ Buffer.add_char buffer escaped;
+ loop ()
+ | character -> Buffer.add_char buffer character; loop ()
+ in
+ loop ()
+ in
+ let rec value () =
+ space ();
+ if !position = length then fail "expected expression";
+ match text.[!position] with
+ | '(' ->
+ incr position;
+ let rec items result =
+ space ();
+ if !position = length then fail "unterminated list";
+ if text.[!position] = ')' then begin
+ incr position;
+ List (List.rev result)
+ end else items (value () :: result)
+ in
+ items []
+ | ')' -> fail "unexpected closing parenthesis"
+ | '"' -> Atom (quoted ())
+ | _ ->
+ let start = !position in
+ while !position < length && not (List.mem text.[!position] ['('; ')'; ' '; '\n'; '\r'; '\t']) do
+ incr position
+ done;
+ Atom (String.sub text start (!position - start))
+ in
+ let rec values result =
+ space ();
+ if !position = length then List.rev result else values (value () :: result)
+ in
+ values []
+
+let one text = match parse text with
+ | [value] -> value
+ | _ -> invalid_arg "expected one expression"
+
+let rec print = function
+ | Atom text ->
+ if text <> "" && not (String.exists (fun c -> List.mem c ['('; ')'; ' '; '\n'; '\r'; '\t'; '"'; '\\']) text)
+ then text else begin
+ let buffer = Buffer.create (String.length text + 2) in
+ Buffer.add_char buffer '"';
+ String.iter (function
+ | '"' -> Buffer.add_string buffer "\\\""
+ | '\\' -> Buffer.add_string buffer "\\\\"
+ | '\n' -> Buffer.add_string buffer "\\n"
+ | '\r' -> Buffer.add_string buffer "\\r"
+ | '\t' -> Buffer.add_string buffer "\\t"
+ | c -> Buffer.add_char buffer c) text;
+ Buffer.add_char buffer '"';
+ Buffer.contents buffer
+ end
+ | List values -> "(" ^ String.concat " " (List.map print values) ^ ")"
+
+let atom = function Atom text -> text | _ -> invalid_arg "expected atom"
+let list = function List values -> values | _ -> invalid_arg "expected list"
+let field name values =
+ let matches = List.filter_map (function
+ | List (Atom key :: values) when key = name -> Some values
+ | _ -> None) values
+ in
+ match matches with
+ | [values] -> values
+ | [] -> invalid_arg ("missing field " ^ name)
+ | _ -> invalid_arg ("duplicate field " ^ name)
+
+let integer value = int_of_string (atom value)
+let word = function
+ | Atom text when String.starts_with ~prefix:"#x" text ->
+ Int64.of_string ("0x" ^ String.sub text 2 (String.length text - 2))
+ | Atom text when String.starts_with ~prefix:"#b" text ->
+ Int64.of_string ("0b" ^ String.sub text 2 (String.length text - 2))
+ | Atom text -> Int64.of_string text
+ | List [Atom "-"; Atom text] -> Int64.neg (Int64.of_string text)
+ | _ -> invalid_arg "invalid scalar"
+let a text = Atom text
+let i value = Atom (string_of_int value)
+let w value = Atom (Printf.sprintf "0x%016Lx" value)
A => sexp.mli +12 -0
@@ 1,12 @@
+type t = Atom of string | List of t list
+val parse : string -> t list
+val one : string -> t
+val print : t -> string
+val atom : t -> string
+val list : t -> t list
+val field : string -> t list -> t list
+val integer : t -> int
+val word : t -> int64
+val a : string -> t
+val i : int -> t
+val w : int64 -> t
A => smt/fences.smt2 +6 -0
@@ 1,6 @@
+(set-logic QF_LIA)
+(set-option :produce-models true)
+(set-option :timeout 60000)
+
+(define-fun weight ((enabled Bool)) Int
+ (ite enabled 1 0))
A => smt/laws.smt2 +98 -0
@@ 1,98 @@
+(declare-const s State)
+(declare-const t Index)
+(declare-const other Index)
+(declare-const a Index)
+(declare-const v Word)
+(declare-const r Index)
+(declare-const destination Index)
+
+(assert (bvult t #x02))
+(assert (bvult other #x02))
+(assert (bvult a #x10))
+(assert (bvult r #x04))
+(assert (bvule (at (heads s) t) (at (tails s) t)))
+(assert (bvult (at (tails s) t) #x40))
+(assert (bvult (pending s t) #x08))
+(assert (distinct other t))
+
+(push 1)
+(assert (distinct (read (write s t a v) t a) v))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (memory (write s t a v)) (memory s)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (pending (write s t a v) t) (bvadd (pending s t) #x01)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (pending (write s t a v) other) (pending s other)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (bvugt (pending s t) #x00))
+(assert (distinct (pending (drain s t) t) (bvsub (pending s t) #x01)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (pending (drain s t) other) (pending s other)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (reg (put s t r v) t r) v))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (pc (advance s t destination) t) destination))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (pc (advance s t destination) other) (pc s other)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (at (critical (mark s t #x01)) t) #x01))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (at (memory (exchange s t a r v)) a) v))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (reg (exchange s t a r v) t r) (at (memory s) a)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (reg (load-step s t a r) t r) (read s t a)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (memory (load-step s t a r)) (memory s)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (distinct (pending (fence-step s t) t) (pending s t)))
+(check-sat)
+(pop 1)
+
+(push 1)
+(assert (= (pending s t) #x00))
+(assert (distinct (at (memory (store-step s t a v true)) a) v))
+(check-sat)
+(pop 1)
A => smt/machine.smt2 +88 -0
@@ 1,88 @@
+(define-fun initial () State
+ (state zeros zeros slots slots slots emptypositions emptyqueues unseen slots))
+
+(define-fun pc ((s State) (t Index)) Index
+ (at (counters s) t))
+
+(define-fun reg ((s State) (t Index) (r Index)) Word
+ (at (registers s) (bvadd (bvmul #x08 t) r)))
+
+(define-fun pending ((s State) (t Index)) Index
+ (bvsub (at (tails s) t) (at (heads s) t)))
+
+(define-fun read ((s State) (t Index) (a Index)) Word
+ (let ((index (at (at (positions s) t) a)))
+ (ite (and (bvuge index (at (heads s) t))
+ (bvult index (at (tails s) t)))
+ (at (at (values s) t) (bvand index #x07))
+ (at (memory s) a))))
+
+(define-fun advance ((s State) (t Index) (p Index)) State
+ (state (memory s) (registers s) (update (counters s) t p)
+ (heads s) (tails s) (addresses s) (values s) (positions s) (critical s)))
+
+(define-fun put ((s State) (t Index) (r Index) (v Word)) State
+ (state (memory s) (update (registers s) (bvadd (bvmul #x08 t) r) v)
+ (counters s) (heads s) (tails s)
+ (addresses s) (values s) (positions s) (critical s)))
+
+(define-fun write ((s State) (t Index) (a Index) (v Word)) State
+ (let ((index (at (tails s) t)))
+ (state (memory s) (registers s) (counters s) (heads s)
+ (update (tails s) t (bvadd index #x01))
+ (update (addresses s) t (update (at (addresses s) t) (bvand index #x07) a))
+ (update (values s) t (update (at (values s) t) (bvand index #x07) v))
+ (update (positions s) t (update (at (positions s) t) a index))
+ (critical s))))
+
+(define-fun drain ((s State) (t Index)) State
+ (let ((index (at (heads s) t)))
+ (state
+ (update (memory s)
+ (at (at (addresses s) t) (bvand index #x07))
+ (at (at (values s) t) (bvand index #x07)))
+ (registers s) (counters s)
+ (update (heads s) t (bvadd index #x01)) (tails s)
+ (addresses s) (values s) (positions s) (critical s))))
+
+(define-fun mark ((s State) (t Index) (v Index)) State
+ (state (memory s) (registers s) (counters s)
+ (heads s) (tails s) (addresses s) (values s) (positions s)
+ (update (critical s) t v)))
+
+(define-fun exchange ((s State) (t Index) (a Index) (r Index) (v Word)) State
+ (state (update (memory s) a v)
+ (update (registers s) (bvadd (bvmul #x08 t) r) (at (memory s) a))
+ (counters s) (heads s) (tails s)
+ (addresses s) (values s) (positions s) (critical s)))
+
+(define-fun store-step ((s State) (t Index) (a Index) (v Word) (sc Bool)) State
+ (let ((next (write s t a v)))
+ (advance (ite sc (drain next t) next) t (bvadd (pc s t) #x01))))
+
+(define-fun load-step ((s State) (t Index) (a Index) (r Index)) State
+ (advance (put s t r (read s t a)) t (bvadd (pc s t) #x01)))
+
+(define-fun fence-step ((s State) (t Index)) State
+ (advance s t (bvadd (pc s t) #x01)))
+
+(define-fun move-step ((s State) (t Index) (r Index) (v Word)) State
+ (advance (put s t r v) t (bvadd (pc s t) #x01)))
+
+(define-fun branch-step
+ ((s State) (t Index) (r Index) (v Word) (target Index)) State
+ (advance s t (ite (= (reg s t r) v) target (bvadd (pc s t) #x01))))
+
+(define-fun enter-step ((s State) (t Index)) State
+ (advance (mark s t #x01) t (bvadd (pc s t) #x01)))
+
+(define-fun leave-step ((s State) (t Index)) State
+ (advance (mark s t #x00) t (bvadd (pc s t) #x01)))
+
+(define-fun exchange-step
+ ((s State) (t Index) (a Index) (r Index) (v Word)) State
+ (advance (exchange s t a r v) t (bvadd (pc s t) #x01)))
+
+(define-fun overlap ((s State)) Bool
+ (and (= (at (critical s) #x00) #x01)
+ (= (at (critical s) #x01) #x01)))
A => solver.ml +208 -0
@@ 1,208 @@
+type value = Bits of int64 | Truth of bool
+type answer = Sat of value list | Unsat | Unknown of string
+type query = { source : string; probes : (string * string) list }
+
+let strategy = "(check-sat-using (then simplify solve-eqs bit-blast sat))"
+let bits = function Bits value -> value | _ -> invalid_arg "expected bitvector"
+let truth = function Truth value -> value | _ -> invalid_arg "expected boolean"
+let index value = Printf.sprintf "#x%02x" value
+let word value = Printf.sprintf "#x%016Lx" value
+let all = function [] -> "true" | values -> "(and " ^ String.concat " " values ^ ")"
+let any = function [] -> "false" | values -> "(or " ^ String.concat " " values ^ ")"
+let equal_state left right = all (List.map (fun field ->
+ Printf.sprintf "(= (%s %s) (%s %s))" field left field right)
+ ["memory"; "registers"; "counters"; "heads"; "tails"; "addresses"; "values"; "positions"; "critical"])
+
+let command source =
+ if String.starts_with ~prefix:"(set-logic QF_BV)" source then strategy else "(check-sat)"
+
+let connection ~timeout ~tick action =
+ let input_read, input_write = Unix.pipe ~cloexec:true () in
+ let output_read, output_write = Unix.pipe ~cloexec:true () in
+ let executable = Option.value (Sys.getenv_opt "Z3") ~default:"z3" in
+ let pid = Unix.create_process executable [|executable; "-in"; "-smt2"|]
+ input_read output_write output_write in
+ Unix.close input_read;
+ Unix.close output_write;
+ let input = Unix.out_channel_of_descr input_write in
+ let pending = ref "" in
+ let deadline = Unix.gettimeofday () +. float_of_int timeout /. 1000. +. 30. in
+ let chunk = Bytes.create 8192 in
+ let read () =
+ if Unix.gettimeofday () > deadline then failwith "solver transport timed out";
+ tick ();
+ let ready, _, _ = Unix.select [output_read] [] [] 0.1 in
+ if ready = [] then None else
+ let size = Unix.read output_read chunk 0 (Bytes.length chunk) in
+ if size = 0 then Some "" else Some (Bytes.sub_string chunk 0 size)
+ in
+ let rec line () = match String.index_opt !pending '\n' with
+ | Some ending ->
+ let result = String.sub !pending 0 ending in
+ pending := String.sub !pending (ending + 1) (String.length !pending - ending - 1);
+ result
+ | None -> match read () with
+ | None -> line ()
+ | Some "" -> failwith ("solver closed its output: " ^ !pending)
+ | Some text -> pending := !pending ^ text; line ()
+ in
+ let finish () =
+ let buffer = Buffer.create 4096 in
+ Buffer.add_string buffer !pending;
+ pending := "";
+ let rec loop () = match read () with
+ | None -> loop ()
+ | Some "" -> Buffer.contents buffer
+ | Some text -> Buffer.add_string buffer text; loop ()
+ in
+ loop ()
+ in
+ let reaped = ref false in
+ Fun.protect ~finally:(fun () ->
+ close_out_noerr input;
+ Unix.close output_read;
+ if not !reaped then begin
+ (try Unix.kill pid Sys.sigkill with Unix.Unix_error (Unix.ESRCH, _, _) -> ());
+ ignore (Unix.waitpid [] pid)
+ end) (fun () ->
+ let result = action input line finish in
+ let _, status = Unix.waitpid [] pid in
+ reaped := true;
+ if status <> Unix.WEXITED 0 then failwith "solver process failed";
+ result)
+
+let script ?(timeout=300000) ?(tick=fun () -> ()) source =
+ connection ~timeout ~tick (fun input _ finish ->
+ output_string input (source ^ "\n(exit)\n");
+ flush input;
+ finish ())
+
+let solve ?(timeout=300000) ?(tick=fun () -> ()) query =
+ connection ~timeout ~tick (fun input line finish ->
+ output_string input query.source;
+ List.iteri (fun i (kind, term) ->
+ Printf.fprintf input "(define-fun q%d () %s %s)\n" i kind term) query.probes;
+ output_string input (command query.source ^ "\n");
+ flush input;
+ let status = String.trim (line ()) in
+ begin match status with
+ | "sat" when query.probes <> [] ->
+ let names = List.init (List.length query.probes) (Printf.sprintf "q%d") in
+ Printf.fprintf input "(get-value (%s))\n" (String.concat " " names)
+ | "unknown" -> output_string input "(get-info :reason-unknown)\n"
+ | "sat" | "unsat" -> ()
+ | _ -> failwith ("invalid solver response: " ^ status)
+ end;
+ output_string input "(exit)\n";
+ flush input;
+ let response = finish () in
+ match status with
+ | "unsat" -> Unsat
+ | "unknown" -> Unknown response
+ | _ when query.probes = [] -> Sat []
+ | _ ->
+ let entries = Sexp.list (Sexp.one response) in
+ let table = Hashtbl.create (List.length entries) in
+ List.iter (function
+ | Sexp.List [Sexp.Atom name; value] ->
+ let value = match value with
+ | Sexp.Atom "true" -> Truth true
+ | Sexp.Atom "false" -> Truth false
+ | value -> Bits (Sexp.word value) in
+ Hashtbl.add table name value
+ | _ -> failwith "invalid model entry") entries;
+ Sat (List.init (List.length query.probes) (fun i -> Hashtbl.find table (Printf.sprintf "q%d" i))))
+
+let instruction state t =
+ let open Syntax in
+ let call name values = Printf.sprintf "(%s-step %s %s%s)" name state (index t)
+ (if values = [] then "" else " " ^ String.concat " " values) in
+ function
+ | Store (a, v) -> [Printf.sprintf "(bvult (pending %s %s) #x08)" state (index t)],
+ (fun sc -> call "store" [index a; word v; string_of_bool sc])
+ | Load (a, r) -> [], (fun _ -> call "load" [index a; index r])
+ | Fence -> [Printf.sprintf "(= (pending %s %s) #x00)" state (index t)],
+ (fun _ -> call "fence" [])
+ | Move (r, v) -> [], (fun _ -> call "move" [index r; word v])
+ | Branch (r, v, pc) -> [], (fun _ -> call "branch" [index r; word v; index pc])
+ | Enter -> [], (fun _ -> call "enter" [])
+ | Leave -> [], (fun _ -> call "leave" [])
+ | Exchange (a, r, v) -> [Printf.sprintf "(= (pending %s %s) #x00)" state (index t)],
+ (fun _ -> call "exchange" [index a; index r; word v])
+
+let property (case : Syntax.t) state =
+ match case.goal with
+ | Syntax.Overlap -> Printf.sprintf "(overlap %s)" state
+ | Syntax.Outcome goal ->
+ let finished = List.init 2 (fun t -> Printf.sprintf "(= (pc %s %s) %s)"
+ state (index t) (index (Array.length case.threads.(t)))) in
+ let registers = List.map (fun (t, r, v) -> Printf.sprintf "(= (reg %s %s %s) %s)"
+ state (index t) (index r) (word v)) goal.registers in
+ let memory = List.map (fun (a, v) -> Printf.sprintf "(= (at (memory %s) %s) %s)"
+ state (index a) (word v)) goal.memory in
+ let drained = if not goal.drained then [] else List.init 2 (fun t ->
+ Printf.sprintf "(= (pending %s %s) #x00)" state (index t)) in
+ all (finished @ registers @ memory @ drained)
+
+let observations (case : Syntax.t) state =
+ List.init 2 (fun t -> "Index", Printf.sprintf "(pc %s %s)" state (index t)) @
+ List.init 8 (fun i -> "Word", Printf.sprintf "(reg %s %s %s)" state (index (i / 4)) (index (i mod 4))) @
+ List.init (Array.length case.names) (fun a -> "Word", Printf.sprintf "(at (memory %s) %s)" state (index a)) @
+ List.init 2 (fun t -> "Index", Printf.sprintf "(pending %s %s)" state (index t)) @
+ List.init 2 (fun t -> "Index", Printf.sprintf "(at (critical %s) %s)" state (index t))
+
+let compile ?(timeout=300000) ?fixed ?(require_goal=true) ?(blocks=[]) case model fences bound =
+ Syntax.validate case;
+ if bound < 1 || bound > 64 then invalid_arg "step bound outside 1..64";
+ List.iter (fun (t, pc) ->
+ if t < 0 || t > 1 || pc < 0 || pc >= Array.length case.Syntax.threads.(t)
+ then invalid_arg "invalid fence location") fences;
+ Option.iter (fun actors ->
+ if Array.length actors <> bound || Array.exists (fun a -> a < 0 || a > 4) actors
+ then invalid_arg "invalid fixed schedule") fixed;
+ let buffer = Buffer.create 65536 in
+ Buffer.add_string buffer (Layout.generate ~timeout);
+ Buffer.add_string buffer (Store.read (Filename.concat (Store.root ()) "smt/machine.smt2"));
+ let add line = Buffer.add_string buffer (line ^ "\n") in
+ add ("(declare-const s0 State)\n(assert " ^ equal_state "s0" "initial" ^ ")");
+ for step = 0 to bound - 1 do
+ let state = Printf.sprintf "s%d" step in
+ let next = Printf.sprintf "s%d" (step + 1) in
+ let actor = Printf.sprintf "a%d" step in
+ add (Printf.sprintf "(declare-const %s State)\n(declare-const %s Index)" next actor);
+ let alternatives = List.concat (List.init 2 (fun t ->
+ let instructions = Array.to_list (Array.mapi (fun pc ins ->
+ let guards, call = instruction state t ins in
+ let guards = if List.mem (t, pc) fences then
+ Printf.sprintf "(= (pending %s %s) #x00)" state (index t) :: guards else guards in
+ all (Printf.sprintf "(= (pc %s %s) %s)" state (index t) (index pc) ::
+ equal_state next (call (model = Syntax.Sc)) :: guards)) case.threads.(t)) in
+ [all [Printf.sprintf "(= %s %s)" actor (index t); any instructions];
+ all [Printf.sprintf "(= %s %s)" actor (index (t + 2));
+ Printf.sprintf "(bvugt (pending %s %s) #x00)" state (index t);
+ equal_state next (Printf.sprintf "(drain %s %s)" state (index t))]])) in
+ add ("(assert " ^ any (alternatives @ [all ["(= " ^ actor ^ " #x04)"; equal_state next state]]) ^ ")");
+ if step > 0 && fixed = None then
+ add (Printf.sprintf "(assert (=> (= %s #x04) (= a%d #x04)))" actor (step - 1));
+ Option.iter (fun actors -> add (Printf.sprintf "(assert (= %s %s))" actor (index actors.(step)))) fixed
+ done;
+ let final = Printf.sprintf "s%d" bound in
+ if require_goal then add ("(assert " ^ property case final ^ ")");
+ List.iter (fun (left, right) -> add ("(assert " ^ any [
+ Printf.sprintf "(distinct (reg %s #x00 #x00) %s)" final (word left);
+ Printf.sprintf "(distinct (reg %s #x01 #x00) %s)" final (word right)] ^ ")")) blocks;
+ let probes = List.init bound (fun i -> "Index", Printf.sprintf "a%d" i) @
+ List.concat (List.init (bound + 1) (fun i -> observations case (Printf.sprintf "s%d" i))) in
+ {source = Buffer.contents buffer; probes}
+
+let candidate ~count ~budget clauses =
+ let lines = ref [Store.read (Filename.concat (Store.root ()) "smt/fences.smt2")] in
+ let add line = lines := line :: !lines in
+ for i = 0 to count - 1 do add (Printf.sprintf "(declare-const f%d Bool)" i) done;
+ let weights = List.init count (Printf.sprintf "(weight f%d)") in
+ add (Printf.sprintf "(assert (<= (+ 0 %s) %d))" (String.concat " " weights) budget);
+ List.iter (fun clause -> add ("(assert " ^ any (List.map (Printf.sprintf "f%d") clause) ^ ")")) clauses;
+ {source = String.concat "\n" (List.rev !lines) ^ "\n";
+ probes = List.init count (fun i -> "Bool", Printf.sprintf "f%d" i)}
+
+let standalone query = query.source ^ command query.source ^ "\n"
A => solver.mli +12 -0
@@ 1,12 @@
+type value = Bits of int64 | Truth of bool
+type answer = Sat of value list | Unsat | Unknown of string
+type query = { source : string; probes : (string * string) list }
+val strategy : string
+val solve : ?timeout:int -> ?tick:(unit -> unit) -> query -> answer
+val script : ?timeout:int -> ?tick:(unit -> unit) -> string -> string
+val compile : ?timeout:int -> ?fixed:int array -> ?require_goal:bool ->
+ ?blocks:(int64 * int64) list -> Syntax.t -> Syntax.model -> Syntax.location list -> int -> query
+val candidate : count:int -> budget:int -> int list list -> query
+val standalone : query -> string
+val bits : value -> int64
+val truth : value -> bool
A => store.ml +32 -0
@@ 1,32 @@
+let read path =
+ let channel = open_in_bin path in
+ Fun.protect ~finally:(fun () -> close_in_noerr channel) (fun () ->
+ really_input_string channel (in_channel_length channel))
+
+let rec mkdir path =
+ if path <> "." && not (Sys.file_exists path) then begin
+ mkdir (Filename.dirname path);
+ Unix.mkdir path 0o755
+ end
+
+let write path text =
+ mkdir (Filename.dirname path);
+ let channel = open_out_bin path in
+ Fun.protect ~finally:(fun () -> close_out_noerr channel) (fun () ->
+ output_string channel text)
+
+let root () =
+ let rec find path =
+ if Sys.file_exists (Filename.concat path "smt/machine.smt2") then path
+ else
+ let parent = Filename.dirname path in
+ if parent = path then invalid_arg "cannot locate smt/machine.smt2"
+ else find parent
+ in
+ match Sys.getenv_opt "REORDER_ROOT" with
+ | Some path -> find path
+ | None ->
+ try find (Sys.getcwd ()) with Invalid_argument _ ->
+ let executable = if Filename.is_relative Sys.executable_name then
+ Filename.concat (Sys.getcwd ()) Sys.executable_name else Sys.executable_name in
+ find (Filename.dirname executable)
A => store.mli +4 -0
@@ 1,4 @@
+val read : string -> string
+val write : string -> string -> unit
+val mkdir : string -> unit
+val root : unit -> string
A => syntax.ml +132 -0
@@ 1,132 @@
+type instruction =
+ | Store of int * int64 | Load of int * int | Fence
+ | Move of int * int64 | Branch of int * int64 * int
+ | Enter | Leave | Exchange of int * int * int64
+
+type goal = Overlap | Outcome of {
+ registers : (int * int * int64) list;
+ memory : (int * int64) list;
+ drained : bool;
+}
+
+type model = Sc | Tso
+type location = int * int
+type t = {
+ name : string;
+ names : string array;
+ bound : int;
+ threads : instruction array array;
+ candidates : location list;
+ goal : goal;
+}
+
+let model = function "sc" -> Sc | "tso" -> Tso | _ -> invalid_arg "unknown memory model"
+let model_name = function Sc -> "sc" | Tso -> "tso"
+let location value = match Sexp.list value with
+ | [t; pc] -> Sexp.integer t, Sexp.integer pc
+ | _ -> invalid_arg "invalid fence location"
+
+let instruction value =
+ let open Sexp in
+ match value with
+ | Store (a, v) -> List [Atom "store"; i a; w v]
+ | Load (a, r) -> List [Atom "load"; i a; i r]
+ | Fence -> List [Atom "fence"]
+ | Move (r, v) -> List [Atom "move"; i r; w v]
+ | Branch (r, v, target) -> List [Atom "branch"; i r; w v; i target]
+ | Enter -> List [Atom "enter"]
+ | Leave -> List [Atom "leave"]
+ | Exchange (a, r, v) -> List [Atom "exchange"; i a; i r; w v]
+
+let decode_instruction value =
+ let open Sexp in
+ match list value with
+ | [Atom "store"; a; v] -> Store (integer a, word v)
+ | [Atom "load"; a; r] -> Load (integer a, integer r)
+ | [Atom "fence"] -> Fence
+ | [Atom "move"; r; v] -> Move (integer r, word v)
+ | [Atom "branch"; r; v; target] -> Branch (integer r, word v, integer target)
+ | [Atom "enter"] -> Enter
+ | [Atom "leave"] -> Leave
+ | [Atom "exchange"; a; r; v] -> Exchange (integer a, integer r, word v)
+ | _ -> invalid_arg "invalid instruction"
+
+let validate case =
+ let check condition message = if not condition then invalid_arg message in
+ check (Array.length case.threads = 2) "exactly two threads required";
+ check (Array.length case.names > 0 && Array.length case.names <= 16) "address count outside 1..16";
+ check (case.bound > 0 && case.bound <= 64) "step bound outside 1..64";
+ let address a = check (a >= 0 && a < Array.length case.names) "address outside model" in
+ let register r = check (r >= 0 && r < 4) "register outside model" in
+ Array.iter (fun program ->
+ check (Array.length program > 0 && Array.length program <= 64) "thread length outside 1..64";
+ Array.iter (function
+ | Store (a, _) -> address a
+ | Load (a, r) | Exchange (a, r, _) -> address a; register r
+ | Move (r, _) -> register r
+ | Branch (r, _, target) -> register r;
+ check (target >= 0 && target <= Array.length program) "branch target outside thread"
+ | Fence | Enter | Leave -> ()) program) case.threads;
+ let thread t = check (t >= 0 && t < 2) "thread outside model" in
+ List.iter (fun (t, pc) -> thread t;
+ check (pc >= 0 && pc < Array.length case.threads.(t)) "fence outside thread") case.candidates;
+ check (List.length case.candidates = List.length (List.sort_uniq compare case.candidates)) "duplicate fence";
+ match case.goal with
+ | Overlap -> ()
+ | Outcome goal ->
+ List.iter (fun (t, r, _) -> thread t; register r) goal.registers;
+ List.iter (fun (a, _) -> address a) goal.memory
+
+let singleton = function [value] -> value | _ -> invalid_arg "expected one field value"
+
+let decode value =
+ let open Sexp in
+ let fields = match value with
+ | List (Atom "case" :: fields) -> fields
+ | _ -> invalid_arg "expected case"
+ in
+ let get key = singleton (field key fields) in
+ let goal = match field "goal" fields with
+ | [Atom "overlap"] -> Overlap
+ | [List (Atom "outcome" :: fields)] ->
+ let registers = List.map (fun value -> match list value with
+ | [t; r; v] -> integer t, integer r, word v
+ | _ -> invalid_arg "invalid register goal") (field "registers" fields) in
+ let memory = List.map (fun value -> match list value with
+ | [a; v] -> integer a, word v
+ | _ -> invalid_arg "invalid memory goal") (field "memory" fields) in
+ let drained = match field "drained" fields with
+ | [Atom "true"] -> true | [Atom "false"] -> false
+ | _ -> invalid_arg "invalid drain flag" in
+ Outcome {registers; memory; drained}
+ | _ -> invalid_arg "invalid goal"
+ in
+ let case = {
+ name = atom (get "name");
+ names = Array.of_list (List.map atom (field "names" fields));
+ bound = integer (get "bound");
+ threads = Array.of_list (List.map (fun thread ->
+ Array.of_list (List.map decode_instruction (list thread))) (field "threads" fields));
+ candidates = List.map location (field "candidates" fields);
+ goal;
+ } in
+ validate case;
+ case
+
+let encode case =
+ let open Sexp in
+ let goal = match case.goal with
+ | Overlap -> Atom "overlap"
+ | Outcome goal -> List [Atom "outcome";
+ List (Atom "registers" :: List.map (fun (t, r, v) -> List [i t; i r; w v]) goal.registers);
+ List (Atom "memory" :: List.map (fun (a, v) -> List [i a; w v]) goal.memory);
+ List [Atom "drained"; Atom (string_of_bool goal.drained)]] in
+ List [Atom "case"; List [Atom "name"; Atom case.name];
+ List (Atom "names" :: Array.to_list (Array.map a case.names));
+ List [Atom "bound"; i case.bound];
+ List (Atom "threads" :: Array.to_list (Array.map (fun thread ->
+ List (Array.to_list (Array.map instruction thread))) case.threads));
+ List (Atom "candidates" :: List.map (fun (t, pc) -> List [i t; i pc]) case.candidates);
+ List [Atom "goal"; goal]]
+
+let load path = decode (Sexp.one (Store.read path))
A => syntax.mli +30 -0
@@ 1,30 @@
+type instruction =
+ | Store of int * int64 | Load of int * int | Fence
+ | Move of int * int64 | Branch of int * int64 * int
+ | Enter | Leave | Exchange of int * int * int64
+
+type goal = Overlap | Outcome of {
+ registers : (int * int * int64) list;
+ memory : (int * int64) list;
+ drained : bool;
+}
+
+type model = Sc | Tso
+type location = int * int
+type t = {
+ name : string;
+ names : string array;
+ bound : int;
+ threads : instruction array array;
+ candidates : location list;
+ goal : goal;
+}
+
+val validate : t -> unit
+val decode : Sexp.t -> t
+val encode : t -> Sexp.t
+val load : string -> t
+val instruction : instruction -> Sexp.t
+val location : Sexp.t -> location
+val model : string -> model
+val model_name : model -> string
A => toolchain.json +19 -0
@@ 1,19 @@
+{
+ "solver": "Z3",
+ "version": "5.1.0",
+ "language": "SMT-LIB 2",
+ "basis": "https://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf",
+ "semantics": "two threads, atomic 64-bit cells, FIFO store buffers, local forwarding, mfence and atomic exchange",
+ "bounds": {
+ "threads": 2,
+ "registers": 4,
+ "buffer": 8,
+ "steps": 64
+ },
+ "ocaml": "5.5.0",
+ "dune": "3.24.2",
+ "driver": "OCaml",
+ "solver_strategy": "simplify, solve-eqs, bit-blast, sat",
+ "timeout_ms": 300000,
+ "repair": "counterexample-guided synthesis refined by complete finite execution graphs"
+}
A => view.ml +319 -0
@@ 1,319 @@
+type t = {
+ width : int;
+ delay : float;
+ animate : bool;
+ case : Syntax.t;
+ started : float;
+ mutable last_draw : float;
+ mutable last_rows : int;
+ mutable title : string;
+ mutable fences : Syntax.location list;
+ mutable budget : int;
+ mutable round : int;
+ mutable excluded : int list;
+}
+
+let headline text =
+ String.split_on_char ' ' text
+ |> List.map (fun word ->
+ match String.uppercase_ascii word with
+ | "SC" | "TSO" | "SMT" | "FIFO" | "CPU" as acronym -> acronym
+ | _ -> String.capitalize_ascii word)
+ |> String.concat " "
+
+let fit width text =
+ if String.length text <= width then text ^ String.make (width - String.length text) ' '
+ else String.sub text 0 (max 0 (width - 3)) ^ "..."
+let line width = String.make width '-'
+let columns width left right =
+ let size = (width - 3) / 2 in
+ let height = max (List.length left) (List.length right) in
+ List.init height (fun i ->
+ let at values = if i < List.length values then List.nth values i else "" in
+ fit size (at left) ^ " | " ^ fit size (at right))
+let label (case : Syntax.t) address = case.names.(address)
+let number value = if value >= 0L && value < 1000000L then Int64.to_string value
+ else Printf.sprintf "0x%016Lx" value
+
+let instruction (case : Syntax.t) =
+ let open Syntax in
+ function
+ | Store (a, v) -> Printf.sprintf "mov [%s], %s" (label case a) (number v)
+ | Load (a, r) -> Printf.sprintf "mov r%d, [%s]" r (label case a)
+ | Fence -> "mfence"
+ | Move (r, v) -> Printf.sprintf "mov r%d, %s" r (number v)
+ | Branch (r, v, target) -> Printf.sprintf "if r%d == %s goto %02d" r (number v) target
+ | Enter -> "enter critical section"
+ | Leave -> "leave critical section"
+ | Exchange (a, r, v) -> Printf.sprintf "xchg [%s], %s -> r%d" (label case a) (number v) r
+
+let registers state t = String.concat " " (List.init 4 (fun r ->
+ Printf.sprintf "r%d=%s" r (number state.Machine.regs.(t).(r))))
+let queue case entries = match entries with
+ | [] -> "[ empty ]"
+ | entries -> String.concat " -> " (List.map (fun (a, v) ->
+ Printf.sprintf "[ %s=%s ]" (label case a) (number v)) entries)
+let memory case state = String.concat " " (List.init (Array.length case.Syntax.names) (fun a ->
+ Printf.sprintf "[ %s = %s ]" (label case a) (number state.Machine.memory.(a))))
+
+let program case fences state t =
+ let pc = state.Machine.pc.(t) in
+ let active = if state.critical.(t) then " Inside Critical Section" else "" in
+ let head = [Printf.sprintf "Thread %d pc=%02d%s" t pc active; registers state t; ""] in
+ let code = case.Syntax.threads.(t) in
+ let first = max 0 (min (pc - 3) (Array.length code - 9)) in
+ head @ List.concat (List.init (min 9 (Array.length code)) (fun offset ->
+ let i = first + offset in
+ let fence = if not (List.mem (t, i) fences) then [] else
+ [Printf.sprintf " mfence%s" (if i = pc && state.buffers.(t) <> [] then " < waiting for store buffer" else "")] in
+ fence @ [Printf.sprintf "%s %02d %s" (if i = pc then ">" else " ") i (instruction case code.(i))])) @
+ [""; "FIFO Store Buffer oldest -> newest"; queue case state.buffers.(t)]
+
+let event case before after = function
+ | Machine.Idle -> "scheduler idle"
+ | Machine.Drain (t, a, v) ->
+ Printf.sprintf "T%d drains %s=%s from its FIFO buffer into shared memory" t (label case a) (number v)
+ | Machine.Issue (t, _, Syntax.Load (a, r), Some origin) ->
+ let source = match origin with Machine.Memory -> "shared memory" | Machine.Buffer -> "its latest buffered write" in
+ let stale = List.exists (fun (address, _) -> address = a) before.Machine.buffers.(1 - t) in
+ Printf.sprintf "T%d reads %s=%s into r%d from %s%s" t (label case a)
+ (number after.Machine.regs.(t).(r)) r source
+ (if stale && origin = Machine.Memory then " while the other thread has a pending write" else "")
+ | Machine.Issue (t, pc, ins, _) ->
+ Printf.sprintf "T%d issues pc=%02d %s" t pc (instruction case ins)
+
+let timeline actors count =
+ let first = max 0 (count - 24) in
+ let cells = List.init (count - first) (fun i ->
+ let actor = actors.(first + i) in
+ match actor with 0 -> "T0" | 1 -> "T1" | 2 -> "F0" | 3 -> "F1" | _ -> "..") in
+ ["Schedule T=Instruction F=FIFO Drain";
+ String.concat " " (List.init (count - first) (fun i -> Printf.sprintf "%02d" (first + i + 1)));
+ String.concat " " cells]
+
+let frame ~width (witness : Engine.witness) index =
+ let states, events = Engine.replay witness in
+ if index < 0 || index >= Array.length states then invalid_arg "trace frame outside execution";
+ let state = states.(index) in
+ let status = if state.critical.(0) && state.critical.(1) then "Mutual Exclusion Violated"
+ else if index = witness.bound then "Requested Outcome Reached" else "Execution in Progress" in
+ let description = if index = 0 then "All registers, memory cells and store buffers start empty"
+ else event witness.case states.(index - 1) state events.(index - 1) in
+ let heading = ["Reorder " ^ headline witness.case.name;
+ Printf.sprintf "model=%s transition=%02d/%02d bound=%d independent replay=passed"
+ (Syntax.model_name witness.model) index witness.bound witness.bound;
+ line width; status; ""] in
+ heading @ columns width (program witness.case witness.fences state 0) (program witness.case witness.fences state 1) @
+ [""; line width; "Shared Memory"; memory witness.case state; "";
+ "Current Transition"; description; ""] @ timeline witness.actors index
+
+let colour text =
+ let paint code text = "\027[" ^ code ^ "m" ^ text ^ "\027[0m" in
+ let code =
+ if String.contains text '>' && String.starts_with ~prefix:">" (String.trim text) then "1;34"
+ else if String.starts_with ~prefix:"Mutual Exclusion Violated" text then "1;31"
+ else if String.starts_with ~prefix:"Minimum Repair Verified" text then "1;32"
+ else if String.starts_with ~prefix:"Thread 0" text then "1;34"
+ else if String.starts_with ~prefix:"Thread 1" text then "1;35"
+ else if String.starts_with ~prefix:"Reorder" text then "1" else "30" in
+ match String.index_opt text '|' with
+ | None -> paint code text
+ | Some middle ->
+ let left = String.sub text 0 middle in
+ let right = String.sub text (middle + 1) (String.length text - middle - 1) in
+ paint "34" left ^ paint "30" "|" ^ paint "35" right
+
+let draw view lines =
+ let buffer = Buffer.create 16384 in
+ let write text =
+ let text = fit view.width text in
+ let last = ref (String.length text) in
+ while !last > 0 && text.[!last - 1] = ' ' do decr last done;
+ Buffer.add_string buffer (if view.animate then colour text else String.sub text 0 !last)
+ in
+ if view.animate then begin
+ let rows = Array.of_list lines in
+ for row = 0 to max view.last_rows (Array.length rows) - 1 do
+ Printf.bprintf buffer "\027[%d;1H\027[2K" (row + 1);
+ if row < Array.length rows then write rows.(row)
+ done;
+ Buffer.add_string buffer "\027[1;1H"
+ end else
+ List.iter (fun text -> write text; Buffer.add_char buffer '\n') lines;
+ output_string stdout (Buffer.contents buffer);
+ flush stdout;
+ view.last_rows <- List.length lines;
+ view.last_draw <- Unix.gettimeofday ()
+
+let create ?(width=140) ?(delay=0.7) ~animate case =
+ if width < 78 || width > 180 || delay < 0. || delay > 10. then invalid_arg "invalid display dimensions or delay";
+ if animate then begin print_string "\027[2J\027[H\027[?25l"; flush stdout end;
+ {width; delay; animate; case; started=Unix.gettimeofday (); last_draw=0.; last_rows=0;
+ title="Preparing SMT query"; fences=[]; budget=0; round=0; excluded=[]}
+
+let tick view =
+ let now = Unix.gettimeofday () in
+ if view.animate && now -. view.last_draw >= 0.35 then begin
+ let state = Machine.initial view.case in
+ let selected = String.concat " " (List.map (fun (t, pc) -> Printf.sprintf "T%d@%02d" t pc) view.fences) in
+ draw view (["Reorder " ^ headline view.case.name; line view.width;
+ Printf.sprintf "%s elapsed %.1fs" view.title (now -. view.started);
+ Printf.sprintf "verification round %d fence budget %d selected %s" view.round view.budget
+ (if selected = "" then "none" else selected);
+ ""] @ columns view.width (program view.case view.fences state 0) (program view.case view.fences state 1) @
+ [""; line view.width;
+ "Counterexample Constraints";
+ "Lower budgets excluded: " ^ String.concat ", " (List.map string_of_int view.excluded);
+ "SMT query running; elapsed time measures actual solver work"])
+ end
+
+let finish view =
+ if view.animate then begin
+ Printf.printf "\027[%d;1H\027[0m\027[?25h" (view.last_rows + 1);
+ flush stdout
+ end
+
+let phase view title =
+ view.title <- title;
+ view.last_draw <- 0.;
+ tick view
+
+let play view witness =
+ let _, events = Engine.replay witness in
+ for index = 0 to witness.Engine.bound do
+ if index = 0 || index = witness.bound || events.(index - 1) <> Machine.Idle then begin
+ draw view (frame ~width:view.width witness index);
+ if view.animate then Unix.sleepf view.delay
+ end
+ done
+
+let notify view = function
+ | Engine.Searching (budget, fences) ->
+ view.round <- view.round + 1;
+ view.budget <- budget;
+ view.fences <- fences;
+ view.title <- "Searching for a violating execution";
+ if view.animate then (view.last_draw <- 0.; tick view)
+ else Printf.printf "round %d budget %d fences %s\n%!" view.round budget
+ (String.concat ", " (List.map (fun (t, pc) -> Printf.sprintf "T%d@%d" t pc) fences))
+ | Engine.Counterexample witness ->
+ if view.animate then begin
+ let faster = {view with delay=min 0.18 view.delay} in
+ play faster witness;
+ view.last_rows <- faster.last_rows;
+ view.last_draw <- faster.last_draw
+ end else print_endline "SAT counterexample replay checked"
+ | Engine.Excluded budget ->
+ view.excluded <- view.excluded @ [budget];
+ if not view.animate then Printf.printf "budget %d excluded\n%!" budget
+ | Engine.Refining bound ->
+ view.title <- Printf.sprintf "Graph counterexample extends the SMT bound to %d" bound;
+ if not view.animate then print_endline view.title
+ | Engine.Verified (fences, bound, states) ->
+ view.fences <- fences;
+ view.title <- "Minimum Repair Verified";
+ let state = Machine.initial view.case in
+ draw view (["Reorder " ^ headline view.case.name; line view.width;
+ "Minimum Repair Verified";
+ Printf.sprintf "%d fences bound %d %d actual verification queries"
+ (List.length fences) bound view.round;
+ Printf.sprintf "Closed graph: %d reachable states, no violating state" states; ""] @
+ columns view.width (program view.case fences state 0) (program view.case fences state 1) @
+ [""; "Counterexample replay passed; lower-budget SMT certificate is UNSAT"])
+
+let compact case state =
+ [Printf.sprintf "T0 pc=%02d %s" state.Machine.pc.(0)
+ (if state.critical.(0) then "Inside" else "Outside"); registers state 0;
+ "T0 buffer " ^ queue case state.buffers.(0); "";
+ Printf.sprintf "T1 pc=%02d %s" state.pc.(1)
+ (if state.critical.(1) then "Inside" else "Outside"); registers state 1;
+ "T1 buffer " ^ queue case state.buffers.(1); ""; memory case state;
+ ""; if state.critical.(0) && state.critical.(1) then "Overlap" else "No overlap in this state"]
+
+let contrast view (witness : Engine.witness) (repair : Engine.repair) =
+ if witness.case <> repair.case then invalid_arg "comparison cases differ";
+ let original, _ = Engine.replay witness in
+ let fixed = Machine.reschedule witness.case repair.fences witness.actors in
+ Engine.validate_schedule witness.case repair.fences fixed;
+ let repaired, _ = Machine.replay witness.case Syntax.Tso repair.fences fixed in
+ for index = 0 to witness.bound do
+ if index = 0 || index = witness.bound || witness.actors.(index - 1) <> 4 then begin
+ let prefix = Machine.reschedule witness.case repair.fences (Array.sub witness.actors 0 index) in
+ let corrected = repaired.(Array.length prefix) in
+ let left = ["Original Execution"; line ((view.width - 3) / 2)] @ compact witness.case original.(index) in
+ let right = ["Minimum Fence Repair"; line ((view.width - 3) / 2)] @ compact witness.case corrected in
+ draw view (["Reorder Original and Repaired Executions";
+ Printf.sprintf "requested transition %02d/%02d pending writes drain before fenced instructions" index witness.bound;
+ line view.width; ""] @ columns view.width left right @
+ [""; line view.width;
+ "left: replayed solver counterexample right: separately SMT-checked repaired execution";
+ Printf.sprintf "Minimum repair certificate: %d fences, UNSAT at bound %d"
+ (List.length repair.fences) repair.bound]);
+ if view.animate then Unix.sleepf view.delay
+ end
+ done
+
+let outcomes view sc tso =
+ let values = List.sort_uniq compare (sc @ tso) in
+ let rows = List.map (fun ((left, right) as pair) -> Printf.sprintf "%-12s%-12s%-12s%s"
+ (number left) (number right)
+ (if List.mem pair sc then "allowed" else "excluded")
+ (if List.mem pair tso then "allowed" else "excluded")) values in
+ let initial = Machine.initial view.case in
+ draw view (["Reorder Sequential Consistency Versus TSO"; line view.width;
+ "Register Outcome Enumeration Complete"; ""] @
+ columns view.width (program view.case [] initial 0) (program view.case [] initial 1) @
+ [""; line view.width; "Each row is a distinct pair of final register values";
+ "T0:r0 T1:r0 SC TSO"] @ rows)
+
+let graph view fences =
+ if view.case.goal <> Syntax.Overlap then invalid_arg "graph view requires a mutual-exclusion property";
+ let rows = Array.length view.case.threads.(0) + 1 in
+ let cols = Array.length view.case.threads.(1) + 1 in
+ let counts = Array.init rows (fun _ -> Array.make cols 0) in
+ let unsafe = Array.init rows (fun _ -> Array.make cols false) in
+ let occupancy = Array.make 4 0 in
+ let depths = Array.init 2 (fun _ -> Array.make 9 0) in
+ let render count complete edges violations =
+ let grid = ["PC0 vs PC1 " ^ String.concat "" (List.init cols (fun pc -> Printf.sprintf "%6d" pc))] @
+ List.init rows (fun row -> Printf.sprintf "%9d " row ^
+ String.concat "" (List.init cols (fun col ->
+ if counts.(row).(col) = 0 then " ."
+ else Printf.sprintf "%5d%s" counts.(row).(col) (if unsafe.(row).(col) then "!" else " ")))) in
+ let selected = String.concat ", " (List.map (fun (t, pc) -> Printf.sprintf "T%d@%02d" t pc) fences) in
+ let occupancy = [""; "Critical Section Occupancy"] @
+ List.mapi (fun i name -> Printf.sprintf "%-18s %5d states" name occupancy.(i))
+ ["Both Outside"; "T0 Inside"; "T1 Inside"; "Both Inside"] in
+ let queues = [""; "FIFO Depth T0 States T1 States"] @
+ List.init 9 (fun depth -> Printf.sprintf "%10d %12d %12d" depth depths.(0).(depth) depths.(1).(depth)) in
+ let programs = ["Fence Placement"] @
+ program view.case fences (Machine.initial view.case) 0 @ [""] @
+ program view.case fences (Machine.initial view.case) 1 in
+ draw view (["Reorder Finite Execution State Exploration"; line view.width;
+ "Program Counter Occupancy each cell counts distinct reachable machine states";
+ "! marks a cell containing a mutual-exclusion violation"; ""] @ columns view.width (grid @ occupancy @ queues) programs @
+ [""; line view.width;
+ (if complete then Printf.sprintf "Reachable states %d transitions %d violating states %d" count edges violations
+ else Printf.sprintf "Reachable states %d transition totals available at graph closure" count);
+ "Fences: " ^ (if selected = "" then "none" else selected);
+ if complete then "State Graph Closed every enabled successor is already represented"
+ else "Exploring actual enabled instruction and FIFO-drain transitions"])
+ in
+ let visit state count =
+ let row, col = state.Machine.pc.(0), state.pc.(1) in
+ counts.(row).(col) <- counts.(row).(col) + 1;
+ let active = (if state.critical.(0) then 1 else 0) + (if state.critical.(1) then 2 else 0) in
+ occupancy.(active) <- occupancy.(active) + 1;
+ for thread = 0 to 1 do
+ let depth = List.length state.buffers.(thread) in
+ depths.(thread).(depth) <- depths.(thread).(depth) + 1
+ done;
+ if state.critical.(0) && state.critical.(1) then unsafe.(row).(col) <- true;
+ if view.animate && count mod 25 = 0 then begin
+ render count false 0 0;
+ Unix.sleepf (min 0.12 view.delay)
+ end
+ in
+ let graph = Machine.explore ~visit view.case Syntax.Tso fences in
+ render (Array.length graph.states) true graph.edges (List.length graph.violations);
+ graph
A => view.mli +12 -0
@@ 1,12 @@
+type t
+val headline : string -> string
+val create : ?width:int -> ?delay:float -> animate:bool -> Syntax.t -> t
+val tick : t -> unit
+val notify : t -> Engine.phase -> unit
+val frame : width:int -> Engine.witness -> int -> string list
+val play : t -> Engine.witness -> unit
+val contrast : t -> Engine.witness -> Engine.repair -> unit
+val outcomes : t -> (int64 * int64) list -> (int64 * int64) list -> unit
+val finish : t -> unit
+val phase : t -> string -> unit
+val graph : t -> Syntax.location list -> Machine.graph