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 storage folder = let folder = Filename.concat folder "storage" in let path = Filename.concat folder "nested/artefact" in Store.write path "first"; let previous = open_in_bin path in Fun.protect ~finally:(fun () -> close_in_noerr previous) (fun () -> Store.write path "replacement"; require (Store.read path = "replacement") "artefact replacement"; require (really_input_string previous 5 = "first") "open reader retains previous artefact"); let target = Filename.concat folder "directory" in Store.mkdir target; let failed = try Store.write target "invalid"; false with Sys_error _ -> true in require failed "directory cannot be replaced by an artefact"; require (Sys.is_directory target) "failed replacement preserves destination"; require (List.sort compare (Array.to_list (Sys.readdir folder)) = ["directory"; "nested"]) "failed replacement removes temporary file" let transport folder = let previous = Option.value (Sys.getenv_opt "Z3") ~default:"z3" in let descriptors () = Array.length (Sys.readdir "/proc/self/fd") in let before = descriptors () in Fun.protect ~finally:(fun () -> Unix.putenv "Z3" previous) (fun () -> Unix.putenv "Z3" (Filename.concat folder "missing"); for _ = 1 to 32 do let failed = try ignore (Solver.script ""); false with | Unix.Unix_error (Unix.ENOENT, _, _) -> true in require failed "missing solver reports process creation failure" done); require (descriptors () = before) "failed solver spawn closes every pipe" 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 constraints () = List.iter (fun (count, clauses) -> rejects (fun () -> ignore (Solver.candidate ~count ~budget:1 clauses)) "invalid fence constraint") [-1, []; 1, [[-1]]; 1, [[1]]; 0, [[0]]]; let solve count budget clauses = Solver.solve (Solver.candidate ~count ~budget clauses) in require (solve 0 0 [] = Solver.Sat []) "empty candidate set satisfies zero budget"; require (solve 0 (-1) [] = Solver.Unsat) "negative lower budget remains unsatisfiable"; require (solve 2 0 [[0; 1]] = Solver.Unsat) "zero budget cannot hit a nonempty clause"; require (solve 2 1 [[0]; [1]] = Solver.Unsat) "independent singleton clauses require two fences"; require (solve 2 2 [[0]; [1]] = Solver.Sat [Solver.Truth true; Solver.Truth true]) "candidate values follow clause indices" let validation () = let case = read "buffering" in let open Sexp in let entries = List.tl (list (Syntax.encode case)) in let decode entries = ignore (Syntax.decode (List (Atom "case" :: entries))) in List.iter (fun extra -> rejects (fun () -> decode (extra :: entries)) "unexpected case field") [List [Atom "bounds"; i 8]; Atom "extra"; List []; List [List []]]; List.iter (fun extra -> let entries = List.map (function | List [Atom "goal"; List (Atom "outcome" :: goal)] -> List [Atom "goal"; List (Atom "outcome" :: extra :: goal)] | field -> field) entries in rejects (fun () -> decode entries) "unexpected outcome field") [List [Atom "drain"; Atom "true"]; Atom "extra"; List []]; 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 cli folder = let descriptor = Unix.openfile "/dev/null" [Unix.O_RDWR; Unix.O_CLOEXEC] 0 in Fun.protect ~finally:(fun () -> Unix.close descriptor) (fun () -> let run arguments = let arguments = Array.of_list ("./src/main.exe" :: arguments) in let pid = Unix.create_process "./src/main.exe" arguments descriptor descriptor descriptor in snd (Unix.waitpid [] pid) in List.iter (fun arguments -> require (run arguments = Unix.WEXITED 2) "CLI rejects invalid input count") [["layout"; "extra"]; ["laws"; "extra"]; ["check"; "cases/buffering.sexp"; "extra"]; ["graph"; "cases/buffering.sexp"; "a"; "b"]; ["replay"]; ["contrast"; "one"]]; List.iter (fun delay -> require (run ["layout"; "--delay"; delay] = Unix.WEXITED 2) "CLI rejects invalid delay") ["nan"; "infinity"; "-infinity"; "-0.1"; "10.1"]; let path = Filename.concat folder "layout.smt2" in require (run ["layout"; "--delay"; "0"; "--out"; path] = Unix.WEXITED 0) "CLI accepts zero delay"; require (String.starts_with ~prefix:"(set-logic QF_BV)" (Store.read path)) "CLI writes the requested layout"); let case = read "buffering" in List.iter (fun delay -> rejects (fun () -> ignore (View.create ~animate:false ~delay case)) "view rejects nonfinite delay") [Float.nan; Float.infinity; Float.neg_infinity] 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; storage folder; transport folder; codec (); laws (); fixtures (); differential (); buffers (); outcomes (); graph (); constraints (); validation (); cli folder; repair folder; Printf.printf "%d checks passed; %d bounded reference states; %.1fs\n%!" !checks !visited (Unix.gettimeofday () -. started)