~james/reorder

fix(cli): reject invalid delays and surplus inputs

commit
c27220bf810cb9167da90cf4de94f91648e5e46d browse tree
parent
2efa23040a7a92f0fc1ff8413586104ed1c3814d
author
james <j@cpl0.zip>
signature
Verified ~james signed with a key registered by ~james
date
2026-11-06 06:00 UTC

24 files changed, +166 −9

.gitattributes added

@@ -0,0 +1,2 @@
1+* linguist-detectable=false
2+*.smt2 linguist-detectable=true linguist-language=SMT

Makefile modified

@@ -7,5 +7,5 @@ check:
77 dune runtest --force --no-buffer
88
99 demo: all
10- _build/default/main.exe repair cases/peterson.sexp --out build/repair --animate
11- _build/default/main.exe contrast build/repair/witness.sexp build/repair/repair.sexp --animate
10+ dune exec reorder -- repair cases/peterson.sexp --out build/repair --animate
11+ dune exec reorder -- contrast build/repair/witness.sexp build/repair/repair.sexp --animate

README.md added

@@ -0,0 +1,13 @@
1+Reorder synthesises minimum fence placements for programs with two threads under a finite [x86-TSO](https://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf) model, using SMT queries to find counterexamples and an independent OCaml interpreter to replay each execution before adding its fence constraint.
2+
3+![A replayed Peterson execution yields fence constraints whose minimum solution excludes every overlapping critical section](media/repair.svg)
4+
5+On the Peterson fixture it places `mfence` before each flag load and closes the complete graph of 365 reachable states without overlapping critical sections, while a separate SMT query excludes every cheaper placement among the declared candidate locations.
6+
7+## Example
8+
9+```sh
10+dune exec reorder -- repair cases/peterson.sexp --out build/repair
11+dune exec reorder -- contrast build/repair/witness.sexp build/repair/repair.sexp --animate
12+dune exec reorder -- graph cases/peterson.sexp build/repair/repair.sexp --animate
13+```

media/repair.svg added

@@ -0,0 +1,103 @@
1+<svg xmlns="http://www.w3.org/2000/svg" width="1080" height="610" viewBox="0 0 1080 610" role="img" aria-labelledby="title description">
2+ <title id="title">A minimum fence repair for Peterson mutual exclusion</title>
3+ <desc id="description">Selected events from an independently replayed execution show both threads reading zero from the other flag before entering their critical sections. Six counterexamples give constraints over four candidate fence positions. Choosing T0 at instruction 2 and T1 at instruction 2 covers all six constraints, while a budget of one fence is unsatisfiable. The original complete graph has 1315 states, 3483 edges and 12 violating states. The repaired complete graph has 365 states, 678 edges and no violating state. All figures come from the Peterson fixture</desc>
4+ <defs>
5+ <marker id="arrow" viewBox="0 0 8 8" refX="7" refY="4" markerWidth="7" markerHeight="7" orient="auto">
6+ <path d="M1 1 L7 4 L1 7" fill="none" stroke="#68737b" stroke-width="1"/>
7+ </marker>
8+ </defs>
9+ <rect width="1080" height="610" fill="#fff"/>
10+ <g font-family="Latin Modern Roman, STIX Two Text, Times New Roman, serif" fill="#20272c">
11+ <text x="34" y="38" font-size="27">Peterson under TSO</text>
12+ <text x="34" y="65" font-size="17" fill="#4b5563">From a buffered execution to a checked minimum repair</text>
13+ <text x="265" y="103" text-anchor="middle" font-size="20">Replayed counterexample</text>
14+ <text x="779" y="103" text-anchor="middle" font-size="20">Blocking constraints</text>
15+ <g text-anchor="middle" font-size="18">
16+ <text x="150" y="136">Thread 0</text>
17+ <text x="380" y="136">Thread 1</text>
18+ </g>
19+ <g fill="none" stroke="#88939a" stroke-width="1" marker-end="url(#arrow)">
20+ <path d="M150 148 V350"/>
21+ <path d="M380 148 V350"/>
22+ </g>
23+ <g fill="#fff">
24+ <path d="M65 151 H235 V175 H65 Z M65 199 H235 V223 H65 Z M49 223 H251 V247 H49 Z M101 295 H199 V319 H101 Z"/>
25+ <path d="M295 175 H465 V199 H295 Z M295 247 H465 V271 H295 Z M279 271 H481 V295 H279 Z M331 319 H429 V343 H331 Z"/>
26+ </g>
27+ <g text-anchor="middle" font-family="DejaVu Sans Mono, monospace" font-size="16">
28+ <text x="150" y="169">flag0 ← 1</text>
29+ <text x="380" y="193">flag1 ← 1</text>
30+ <text x="150" y="217">turn ← 1</text>
31+ <text x="380" y="265">turn ← 0</text>
32+ <text x="150" y="241">r0 ← flag1 = 0</text>
33+ <text x="380" y="289">r0 ← flag0 = 0</text>
34+ <text x="150" y="313">enter</text>
35+ <text x="380" y="337">enter</text>
36+ </g>
37+ <path d="M113 354 V361 H417 V354" fill="none" stroke="#68737b" stroke-width="1"/>
38+ <text x="265" y="385" font-size="20" text-anchor="middle">Both threads enter</text>
39+ <text x="265" y="409" font-size="16" fill="#4b5563" text-anchor="middle">The flag stores are still buffered at the reads</text>
40+ <path d="M491 257 H549" fill="none" stroke="#68737b" stroke-width="1" marker-end="url(#arrow)"/>
41+ <text x="520" y="243" font-size="15" text-anchor="middle" fill="#4b5563">learn</text>
42+ <g fill="#edf4f1">
43+ <rect x="711" y="152" width="58" height="200"/>
44+ <rect x="891" y="152" width="58" height="200"/>
45+ </g>
46+ <g text-anchor="middle" font-family="DejaVu Sans Mono, monospace" font-size="16">
47+ <text x="650" y="137">T0@1</text>
48+ <text x="740" y="137" fill="#176654">T0@2</text>
49+ <text x="830" y="137">T1@1</text>
50+ <text x="920" y="137" fill="#176654">T1@2</text>
51+ </g>
52+ <path d="M716 144 H764 M896 144 H944" fill="none" stroke="#176654" stroke-width="1.5"/>
53+ <g fill="none" stroke="#68737b" stroke-width="1">
54+ <path d="M610 155 H600 V349 H610 M960 155 H970 V349 H960"/>
55+ </g>
56+ <g text-anchor="end" font-size="17" font-style="italic" fill="#4b5563">
57+ <text x="581" y="179">c₀</text>
58+ <text x="581" y="211">c₁</text>
59+ <text x="581" y="243">c₂</text>
60+ <text x="581" y="275">c₃</text>
61+ <text x="581" y="307">c₄</text>
62+ <text x="581" y="339">c₅</text>
63+ </g>
64+ <g fill="#20272c">
65+ <circle cx="650" cy="173" r="4"/><circle cx="830" cy="173" r="4"/>
66+ <circle cx="830" cy="205" r="4"/>
67+ <circle cx="650" cy="237" r="4"/><circle cx="830" cy="237" r="4"/>
68+ <circle cx="650" cy="269" r="4"/>
69+ <circle cx="650" cy="301" r="4"/>
70+ </g>
71+ <g fill="#176654">
72+ <circle cx="740" cy="173" r="5"/><circle cx="920" cy="173" r="5"/>
73+ <circle cx="920" cy="205" r="5"/>
74+ <circle cx="740" cy="237" r="5"/>
75+ <circle cx="740" cy="269" r="5"/>
76+ <circle cx="920" cy="301" r="5"/>
77+ <circle cx="740" cy="333" r="5"/><circle cx="920" cy="333" r="5"/>
78+ </g>
79+ <text x="785" y="385" font-size="18" text-anchor="middle">Every constraint needs a selected fence</text>
80+ <text x="785" y="409" font-size="16" fill="#4b5563" text-anchor="middle">No single column covers all six rows</text>
81+ <path d="M34 436 H1046" fill="none" stroke="#88939a" stroke-width="1"/>
82+ <text x="34" y="474" font-size="24">Minimum repair</text>
83+ <text x="34" y="507" font-family="DejaVu Sans Mono, monospace" font-size="21" fill="#176654">T0@2 ∧ T1@2</text>
84+ <text x="34" y="535" font-size="18">mfence before each flag load</text>
85+ <text x="34" y="578" font-size="17">Fence budget ≤ 1 is UNSAT</text>
86+ <g text-anchor="middle" font-size="17">
87+ <text x="746" y="474">States</text>
88+ <text x="863" y="474">Edges</text>
89+ <text x="990" y="474">Violating states</text>
90+ <text x="746" y="507">1315</text>
91+ <text x="863" y="507">3483</text>
92+ <text x="990" y="507">12</text>
93+ <g fill="#176654">
94+ <text x="746" y="539">365</text>
95+ <text x="863" y="539">678</text>
96+ <text x="990" y="539">0</text>
97+ </g>
98+ </g>
99+ <text x="523" y="507" font-size="17">Without fences</text>
100+ <text x="523" y="539" font-size="17" fill="#176654">With two fences</text>
101+ <text x="523" y="578" font-size="17">Reachable graph closed under every transition</text>
102+ </g>
103+</svg>

dune → src/dune renamed

@@ -9,9 +9,3 @@
99 (public_name reorder)
1010 (modules main)
1111 (libraries core unix))
12-
13-(test
14- (name check)
15- (modules check)
16- (libraries core unix)
17- (deps (glob_files cases/*.sexp) (glob_files smt/*.smt2)))

engine.ml → src/engine.ml renamed

engine.mli → src/engine.mli renamed

layout.ml → src/layout.ml renamed

layout.mli → src/layout.mli renamed

machine.ml → src/machine.ml renamed

machine.mli → src/machine.mli renamed

main.ml → src/main.ml renamed

@@ -29,6 +29,17 @@ let () =
2929 let current = ref 1 in
3030 Arg.parse_argv ~current Sys.argv options (fun path -> files := !files @ [path]) "reorder";
3131 if !timeout < 1 || !timeout > 3600000 then invalid_arg "timeout outside 1..3600000 milliseconds";
32+ if not (Float.is_finite !delay) || !delay < 0. || !delay > 10. then
33+ invalid_arg "delay outside 0..10 seconds";
34+ let minimum, maximum = match command with
35+ | "layout" | "laws" -> 0, 0
36+ | "contrast" -> 2, 2
37+ | "graph" -> 1, 2
38+ | "check" | "repair" | "replay" | "compare" | "audit" | "emit" -> 1, 1
39+ | _ -> invalid_arg "unknown command" in
40+ let count = List.length !files in
41+ if count < minimum || count > maximum then
42+ invalid_arg (Printf.sprintf "%s expects %d..%d input files" command minimum maximum);
3243 let file () = match !files with path :: _ -> path | [] -> invalid_arg "missing input file" in
3344 let output fallback = Option.value !folder ~default:fallback in
3445 let load () =

sexp.ml → src/sexp.ml renamed

sexp.mli → src/sexp.mli renamed

solver.ml → src/solver.ml renamed

solver.mli → src/solver.mli renamed

store.ml → src/store.ml renamed

store.mli → src/store.mli renamed

syntax.ml → src/syntax.ml renamed

syntax.mli → src/syntax.mli renamed

view.ml → src/view.ml renamed

@@ -146,7 +146,8 @@ let draw view lines =
146146 view.last_draw <- Unix.gettimeofday ()
147147
148148 let create ?(width=140) ?(delay=0.7) ~animate case =
149- if width < 78 || width > 180 || delay < 0. || delay > 10. then invalid_arg "invalid display dimensions or delay";
149+ if width < 78 || width > 180 || not (Float.is_finite delay) || delay < 0. || delay > 10.
150+ then invalid_arg "invalid display dimensions or delay";
150151 if animate then begin print_string "\027[2J\027[H\027[?25l"; flush stdout end;
151152 {width; delay; animate; case; started=Unix.gettimeofday (); last_draw=0.; last_rows=0;
152153 title="Preparing SMT query"; fences=[]; budget=0; round=0; excluded=[]}

view.mli → src/view.mli renamed

check.ml → test/check.ml renamed

@@ -184,6 +184,32 @@ let validation () =
184184 {case with threads=[|[|Syntax.Branch (0, 0L, 3)|]; case.threads.(1)|]}];
185185 rejects (fun () -> ignore (Engine.check ~fixed:[|0|] case Syntax.Tso)) "fixed schedule bound mismatch"
186186
187+let cli folder =
188+ let descriptor = Unix.openfile "/dev/null" [Unix.O_RDWR; Unix.O_CLOEXEC] 0 in
189+ Fun.protect ~finally:(fun () -> Unix.close descriptor) (fun () ->
190+ let run arguments =
191+ let arguments = Array.of_list ("./src/main.exe" :: arguments) in
192+ let pid = Unix.create_process "./src/main.exe" arguments descriptor descriptor descriptor in
193+ snd (Unix.waitpid [] pid) in
194+ List.iter (fun arguments ->
195+ require (run arguments = Unix.WEXITED 2) "CLI rejects invalid input count")
196+ [["layout"; "extra"]; ["laws"; "extra"];
197+ ["check"; "cases/buffering.sexp"; "extra"];
198+ ["graph"; "cases/buffering.sexp"; "a"; "b"];
199+ ["replay"]; ["contrast"; "one"]];
200+ List.iter (fun delay ->
201+ require (run ["layout"; "--delay"; delay] = Unix.WEXITED 2)
202+ "CLI rejects invalid delay") ["nan"; "infinity"; "-infinity"; "-0.1"; "10.1"];
203+ let path = Filename.concat folder "layout.smt2" in
204+ require (run ["layout"; "--delay"; "0"; "--out"; path] = Unix.WEXITED 0)
205+ "CLI accepts zero delay";
206+ require (String.starts_with ~prefix:"(set-logic QF_BV)" (Store.read path))
207+ "CLI writes the requested layout");
208+ let case = read "buffering" in
209+ List.iter (fun delay ->
210+ rejects (fun () -> ignore (View.create ~animate:false ~delay case))
211+ "view rejects nonfinite delay") [Float.nan; Float.infinity; Float.neg_infinity]
212+
187213 let repair folder =
188214 let case = read "peterson" in
189215 let report = match Engine.repair case folder with
@@ -222,6 +248,7 @@ let () =
222248 graph ();
223249 constraints ();
224250 validation ();
251+ cli folder;
225252 repair folder;
226253 Printf.printf "%d checks passed; %d bounded reference states; %.1fs\n%!"
227254 !checks !visited (Unix.gettimeofday () -. started)

test/dune added

@@ -0,0 +1,6 @@
1+(test
2+ (name check)
3+ (modules check)
4+ (libraries core unix)
5+ (deps ../src/main.exe (glob_files ../cases/*.sexp) (glob_files ../smt/*.smt2))
6+ (action (chdir .. (run %{test}))))