~james/reorder

reorder/src/view.ml -rw-r--r-- 16.4 KiB
735c0c8f — james chore(import): snapshot james.dad.town/reorder at c27220bf810c 16 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
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 || not (Float.is_finite delay) || 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