Skip to content

Commit 0a2a617

Browse files
trace tracker
create trace tracker, add two tests with conditionals and dump traces for all tests
1 parent ba085b9 commit 0a2a617

17 files changed

+23655
-26
lines changed

dscheck.opam

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ depends: [
1010
"ocaml" {>= "5.0.0"}
1111
"dune" {>= "2.9"}
1212
"containers"
13+
"tsort"
1314
"oseq"
1415
"alcotest" {>= "1.6.0" & with-test}
1516
"odoc" {with-doc}

dune-project

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@
1111
(package
1212
(name dscheck)
1313
(synopsis "Traced Atomics")
14-
(depends (ocaml (>= 5.0.0)) dune containers oseq (alcotest (and (>= 1.6.0) :with-test))))
14+
(depends (ocaml (>= 5.0.0)) dune containers tsort oseq (alcotest (and (>= 1.6.0) :with-test))))
1515

1616

gen_traces.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
set -eux pipefail
2+
3+
dscheck_trace_file="tests/traces/ms_queue" dune exec tests/test_michael_scott_queue.exe
4+
dscheck_trace_file="tests/traces/naive_counter" dune exec tests/test_naive_counter.exe
5+
dscheck_trace_file="tests/traces/list" dune exec tests/test_list.exe
6+
dscheck_trace_file="tests/traces/conditional1" dune exec tests/test_conditional1.exe
7+
dscheck_trace_file="tests/traces/conditional2" dune exec tests/test_conditional2.exe

src/trace_tracker.ml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
module Op = struct
2+
type t = { proc : int; variable : int; step : int }
3+
4+
let is_dependent t1 t2 = t1.variable == t2.variable
5+
6+
let compare_proc_step t1 t2 =
7+
let c1 = Int.compare t1.proc t2.proc in
8+
if c1 <> 0 then c1 else Int.compare t1.step t2.step
9+
10+
let to_str t = Printf.sprintf "(%d,%c)" t.proc (Char.chr (t.variable + 96))
11+
end
12+
13+
module Trace = struct
14+
module Key = struct
15+
type t = (Op.t * Op.t option) list
16+
17+
let compare t1 t2 =
18+
List.compare
19+
(fun (op1, dep1) (op2, dep2) ->
20+
let c1 = Op.compare_proc_step op1 op2 in
21+
if c1 <> 0 then c1 else Option.compare Op.compare_proc_step dep1 dep2)
22+
t1 t2
23+
end
24+
25+
type t = Op.t List.t
26+
27+
let of_schedule_for_checks schedule_for_checks : t =
28+
let steps = Hashtbl.create 10 in
29+
List.map
30+
(fun (proc, _, variable) ->
31+
Option.map
32+
(fun variable : Op.t ->
33+
(match Hashtbl.find_opt steps proc with
34+
| None -> Hashtbl.add steps proc 1
35+
| Some v ->
36+
Hashtbl.remove steps proc;
37+
Hashtbl.add steps proc (v + 1));
38+
39+
let step = Hashtbl.find steps proc in
40+
41+
{ proc; variable; step })
42+
variable)
43+
schedule_for_checks
44+
|> List.filter_map Fun.id
45+
46+
let to_string t = List.map Op.to_str t |> String.concat ","
47+
48+
let tag_with_deps (t : t) : Key.t =
49+
let next_dep op t = List.find_opt (Op.is_dependent op) t in
50+
let rec f t =
51+
match t with
52+
| [] -> []
53+
| hd :: [] -> [ (hd, None) ]
54+
| hd :: tl -> (hd, next_dep hd tl) :: f tl
55+
in
56+
let tagged = f t in
57+
List.sort (fun (op1, _) (op2, _) -> Op.compare_proc_step op1 op2) tagged
58+
59+
let deps_to_str (key : Key.t) : string =
60+
List.map
61+
(fun (op, dep) ->
62+
Op.to_str op ^ "-"
63+
^ (Option.map Op.to_str dep |> Option.value ~default:"none"))
64+
key
65+
|> String.concat ","
66+
end
67+
68+
module TraceMap = Map.Make (Trace.Key)
69+
70+
type t = Trace.t TraceMap.t
71+
72+
let traces = ref TraceMap.empty
73+
74+
let add_trace trace =
75+
let trace = Trace.of_schedule_for_checks trace in
76+
let key = Trace.tag_with_deps trace in
77+
traces :=
78+
TraceMap.update key
79+
(function Some v -> Some v | None -> Some trace)
80+
!traces
81+
82+
let print traces channel =
83+
Printf.fprintf channel "----\n";
84+
TraceMap.iter
85+
(fun _ trace -> Printf.fprintf channel "%s\n" (Trace.to_string trace))
86+
traces;
87+
Printf.fprintf channel "----\n";
88+
flush channel
89+
90+
let print_traces chan = print !traces chan
91+
let clear_traces () = traces := TraceMap.empty
92+
let get_traces () = !traces
93+
94+
let get_deps_str traces =
95+
TraceMap.to_seq traces |> List.of_seq
96+
|> List.map (fun (_, value) -> value)
97+
|> List.map Trace.tag_with_deps
98+
|> List.map Trace.deps_to_str |> String.concat "\n"
99+
100+
let equal t1 t2 =
101+
TraceMap.compare
102+
(fun _ _ ->
103+
0
104+
(* any values under the same key are known to be equivalent, even if the exact sequence is not identical *))
105+
t1 t2
106+
== 0

src/trace_tracker.mli

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
type t
2+
3+
val add_trace : (int * 'a * int option) list -> unit
4+
val clear_traces : unit -> unit
5+
val get_traces : unit -> t
6+
val print_traces : out_channel -> unit
7+
val print : t -> out_channel -> unit
8+
val equal : t -> t -> bool
9+
val get_deps_str : t -> string

src/tracedAtomic.ml

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ let var_name i =
205205
let c = Char.chr (i + 96) in
206206
Printf.sprintf "%c" c
207207

208-
let print_execution_sequence () =
208+
let print_execution_sequence chan =
209209
let highest_proc =
210210
List.fold_left
211211
(fun highest (curr_proc, _, _) ->
@@ -216,11 +216,12 @@ let print_execution_sequence () =
216216
let bar =
217217
List.init ((highest_proc * 20) + 20) (fun _ -> "-") |> String.concat ""
218218
in
219-
Printf.printf "\nsequence %d\n" !num_interleavings;
220-
Printf.printf "%s\n" bar;
221-
List.init (highest_proc + 1) (fun proc -> Printf.printf "P%d\t\t\t" proc)
219+
Printf.fprintf chan "\nsequence %d\n" !num_interleavings;
220+
Printf.fprintf chan "%s\n" bar;
221+
List.init (highest_proc + 1) (fun proc ->
222+
Printf.fprintf chan "P%d\t\t\t" proc)
222223
|> ignore;
223-
Printf.printf "\n%s\n" bar;
224+
Printf.fprintf chan "\n%s\n" bar;
224225

225226
List.iter
226227
(fun s ->
@@ -230,13 +231,14 @@ let print_execution_sequence () =
230231
let tabs =
231232
List.init last_run_proc (fun _ -> "\t\t\t") |> String.concat ""
232233
in
233-
Printf.printf "%s%s %s\n" tabs
234+
Printf.fprintf chan "%s%s %s\n" tabs
234235
(atomic_op_str last_run_op)
235236
last_run_ptr)
236237
!schedule_for_checks;
237-
Printf.printf "%s\n%!" bar
238+
Printf.fprintf chan "%s\n%!" bar
238239

239-
let print_max_exec_seq = ref false
240+
let interleavings_chan = (ref None : out_channel option ref)
241+
let record_traces_flag = ref false
240242

241243
let do_run init_func init_schedule =
242244
init_func ();
@@ -254,8 +256,15 @@ let do_run init_func init_schedule =
254256
| [] ->
255257
if !finished_processes == num_processes then (
256258
tracing := false;
259+
257260
num_interleavings := !num_interleavings + 1;
258-
if !print_max_exec_seq then print_execution_sequence ();
261+
if !record_traces_flag then
262+
Trace_tracker.add_trace !schedule_for_checks;
263+
264+
(match !interleavings_chan with
265+
| None -> ()
266+
| Some chan -> print_execution_sequence chan);
267+
259268
!final_func ();
260269
tracing := true)
261270
| (process_id_to_run, next_op, next_ptr) :: schedule ->
@@ -274,7 +283,7 @@ let do_run init_func init_schedule =
274283
finished_processes := 0;
275284
tracing := false;
276285
num_states := !num_states + 1;
277-
if !num_states mod 1000 == 0 then Printf.printf "run: %d\n%!" !num_states;
286+
(* if !num_states mod 1000 == 0 then Printf.printf "run: %d\n%!" !num_states; *)
278287
let procs =
279288
CCVector.mapi
280289
(fun i p -> { proc_id = i; op = p.next_op; obj_ptr = p.next_repr })
@@ -347,7 +356,7 @@ let check f =
347356
tracing := false;
348357
if not (f ()) then (
349358
Printf.printf "Found assertion violation at run %d:\n" !num_interleavings;
350-
print_execution_sequence ();
359+
print_execution_sequence stdout;
351360
assert false);
352361
tracing := tracing_at_start
353362

@@ -357,15 +366,34 @@ let reset_state () =
357366
num_states := 0;
358367
num_interleavings := 0;
359368
schedule_for_checks := [];
369+
Trace_tracker.clear_traces ();
360370
CCVector.clear processes
361371

362-
let trace ?(print_interleavings = false) func =
363-
print_max_exec_seq := print_interleavings;
372+
let dscheck_trace_file_env = Sys.getenv_opt "dscheck_trace_file"
373+
374+
let dpor func =
364375
reset_state ();
365376
let empty_state = do_run func [ (0, Start, None) ] :: [] in
366377
let empty_clock = IntMap.empty in
367378
let empty_last_access = IntMap.empty in
368-
explore func empty_state empty_clock empty_last_access;
369-
if print_interleavings then
370-
Printf.printf "\nexplored %d maximal interleavings and %d states\n"
371-
!num_interleavings !num_states
379+
explore func empty_state empty_clock empty_last_access
380+
381+
let trace ?interleavings ?(record_traces = false) func =
382+
record_traces_flag := record_traces || Option.is_some dscheck_trace_file_env;
383+
interleavings_chan := interleavings;
384+
385+
dpor func;
386+
387+
(* print reports *)
388+
(match !interleavings_chan with
389+
| None -> ()
390+
| Some chan ->
391+
Printf.fprintf chan "\nexplored %d maximal interleavings and %d states\n"
392+
!num_interleavings !num_states);
393+
394+
match dscheck_trace_file_env with
395+
| None -> ()
396+
| Some path ->
397+
let chan = open_out path in
398+
Trace_tracker.print_traces chan;
399+
close_out chan

src/tracedAtomic.mli

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,9 @@ val incr : int t -> unit
4646
val decr : int t -> unit
4747
(** [decr r] atomically decrements the value of [r] by [1]. *)
4848

49-
val trace : ?print_interleavings:bool -> (unit -> unit) -> unit
50-
(** start the simulation trace
51-
52-
print_interleavings - print out explored interleavings
53-
*)
49+
val trace :
50+
?interleavings:out_channel -> ?record_traces:bool -> (unit -> unit) -> unit
51+
(** start the simulation trace *)
5452

5553
val spawn : (unit -> unit) -> unit
5654
(** spawn [f] as a new 'thread' *)

tests/dune

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,27 @@
1313
(libraries dscheck alcotest)
1414
(modules test_michael_scott_queue michael_scott_queue))
1515

16-
1716
(test
1817
(name test_trace)
1918
(libraries dscheck alcotest)
20-
(modules test_trace))
19+
(modules test_trace))
20+
21+
(rule
22+
(with-stdout-to
23+
report_trace.output
24+
(run ./test_trace.exe)))
25+
26+
(rule
27+
(alias runtest)
28+
(action
29+
(diff report_trace.expected report_trace.output)))
30+
31+
(test
32+
(name test_conditional1)
33+
(libraries dscheck alcotest)
34+
(modules test_conditional1))
35+
36+
(test
37+
(name test_conditional2)
38+
(libraries dscheck alcotest)
39+
(modules test_conditional2))

tests/report_trace.expected

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
sequence 1
3+
----------------------------------------
4+
P0 P1
5+
----------------------------------------
6+
start
7+
fetch_and_add a
8+
start
9+
fetch_and_add a
10+
fetch_and_add b
11+
----------------------------------------
12+
13+
sequence 2
14+
----------------------------------------
15+
P0 P1
16+
----------------------------------------
17+
start
18+
start
19+
fetch_and_add a
20+
fetch_and_add a
21+
fetch_and_add b
22+
----------------------------------------
23+
24+
sequence 3
25+
----------------------------------------
26+
P0 P1
27+
----------------------------------------
28+
start
29+
start
30+
fetch_and_add a
31+
fetch_and_add a
32+
fetch_and_add b
33+
----------------------------------------
34+
35+
explored 3 maximal interleavings and 12 states
36+
----
37+
(1,a),(0,a),(1,b)
38+
(0,a),(1,a),(1,b)
39+
----

tests/test_conditional1.ml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module Atomic = Dscheck.TracedAtomic
2+
3+
let test () =
4+
let b = Atomic.make 0 in
5+
let c = Atomic.make 0 in
6+
let ok = Atomic.make false in
7+
let seen_b = ref (-1) in
8+
9+
Atomic.spawn (fun () -> Atomic.set b 1);
10+
Atomic.spawn (fun () ->
11+
Atomic.set c 1;
12+
Atomic.set b 2);
13+
Atomic.spawn (fun () ->
14+
if Atomic.get c = 0 then (
15+
seen_b := Atomic.get b;
16+
if !seen_b = 0 then Atomic.set ok true))
17+
18+
(* Atomic.final (fun () ->
19+
Format.printf "seen_b=%i b=%i c=%i ok=%b@." (!seen_b) (Atomic.get b) (Atomic.get c)
20+
(Atomic.get ok)) *)
21+
22+
let () = Atomic.trace test

0 commit comments

Comments
 (0)