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
|
(**************************************************************************)
(* Sail *)
(* *)
(* Copyright (c) 2013-2017 *)
(* Kathyrn Gray *)
(* Shaked Flur *)
(* Stephen Kell *)
(* Gabriel Kerneis *)
(* Robert Norton-Wright *)
(* Christopher Pulte *)
(* Peter Sewell *)
(* Alasdair Armstrong *)
(* Brian Campbell *)
(* Thomas Bauereiss *)
(* Anthony Fox *)
(* Jon French *)
(* Dominic Mulligan *)
(* Stephen Kell *)
(* Mark Wassell *)
(* *)
(* All rights reserved. *)
(* *)
(* This software was developed by the University of Cambridge Computer *)
(* Laboratory as part of the Rigorous Engineering of Mainstream Systems *)
(* (REMS) project, funded by EPSRC grant EP/K008528/1. *)
(* *)
(* Redistribution and use in source and binary forms, with or without *)
(* modification, are permitted provided that the following conditions *)
(* are met: *)
(* 1. Redistributions of source code must retain the above copyright *)
(* notice, this list of conditions and the following disclaimer. *)
(* 2. Redistributions in binary form must reproduce the above copyright *)
(* notice, this list of conditions and the following disclaimer in *)
(* the documentation and/or other materials provided with the *)
(* distribution. *)
(* *)
(* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' *)
(* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED *)
(* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *)
(* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR *)
(* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, *)
(* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *)
(* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF *)
(* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *)
(* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, *)
(* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT *)
(* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF *)
(* SUCH DAMAGE. *)
(**************************************************************************)
module type OrderedType =
sig
type t
val compare : t -> t -> int
end
module type S =
sig
type node
type graph
type node_set
val leaves : graph -> node_set
val empty : graph
(** Add an edge from the first node to the second node, creating
the nodes if they do not exist. *)
val add_edge : node -> node -> graph -> graph
val add_edges : node -> node list -> graph -> graph
val children : graph -> node -> node list
(** Return the set of nodes that are reachable from the first set
of nodes (roots), without passing through the second set of
nodes (cuts). *)
val reachable : node_set -> node_set -> graph -> node_set
(** Prune a graph from roots to cuts. *)
val prune : node_set -> node_set -> graph -> graph
val remove_self_loops : graph -> graph
val reverse : graph -> graph
exception Not_a_DAG of node * graph;;
(** Topologically sort a graph. Throws Not_a_DAG if the graph is
not directed acyclic. *)
val topsort : graph -> node list
(** Find strongly connected components using Tarjan's algorithm.
This algorithm also returns a topological sorting of the graph
components. *)
val scc : ?original_order:(node list) -> graph -> node list list
val make_dot : (node -> string) -> (node -> node -> string) -> (node -> string) -> out_channel -> graph -> unit
end
module Make(Ord: OrderedType) = struct
type node = Ord.t
(* Node set *)
module NS = Set.Make(Ord)
(* Node map *)
module NM = Map.Make(Ord)
type graph = NS.t NM.t
type node_set = NS.t
let empty = NM.empty
let leaves cg =
List.fold_left (fun acc (fn, callees) -> NS.filter (fun callee -> callee <> fn) (NS.union acc callees)) NS.empty (NM.bindings cg)
let children cg caller =
try
NS.elements (NM.find caller cg)
with
| Not_found -> []
let fix_some_leaves cg nodes =
NS.fold (fun leaf cg -> if NM.mem leaf cg then cg else NM.add leaf NS.empty cg) nodes cg
let fix_leaves cg = fix_some_leaves cg (leaves cg)
let add_edge caller callee cg =
let cg = fix_some_leaves cg (NS.singleton callee) in
try
NM.add caller (NS.add callee (NM.find caller cg)) cg
with
| Not_found -> NM.add caller (NS.singleton callee) cg
let add_edges caller callees cg =
let callees = List.fold_left (fun s c -> NS.add c s) NS.empty callees in
let cg = fix_some_leaves cg callees in
try
NM.add caller (NS.union callees (NM.find caller cg)) cg
with
| Not_found -> NM.add caller callees cg
let reachable roots cuts cg =
let visited = ref NS.empty in
let rec reachable' node =
if NS.mem node !visited then ()
else if NS.mem node cuts then visited := NS.add node !visited
else
begin
visited := NS.add node !visited;
let children =
try NM.find node cg with
| Not_found -> NS.empty
in
NS.iter reachable' children
end
in
NS.iter reachable' roots; !visited
let prune roots cuts cg =
let rs = reachable roots cuts cg in
let cg = NM.filter (fun fn _ -> NS.mem fn rs) cg in
let cg = NM.mapi (fun fn children -> if NS.mem fn cuts then NS.empty else children) cg in
fix_leaves cg
let remove_self_loops cg =
NM.mapi (fun fn callees -> NS.remove fn callees) cg
let reverse cg =
let rcg = ref NM.empty in
let find_default fn cg = try NM.find fn cg with Not_found -> NS.empty in
NM.iter (fun caller -> NS.iter (fun callee -> rcg := NM.add callee (NS.add caller (find_default callee !rcg)) !rcg)) cg;
fix_leaves !rcg
exception Not_a_DAG of node * graph;;
let prune_loop node cg =
let down = prune (NS.singleton node) NS.empty cg in
let up = prune (NS.singleton node) NS.empty (reverse down) in
reverse up
let topsort cg =
let marked = ref NS.empty in
let temp_marked = ref NS.empty in
let list = ref [] in
let keys = NM.bindings cg |> List.map fst in
let find_unmarked keys = List.find (fun node -> not (NS.mem node !marked)) keys in
let rec visit node =
if NS.mem node !temp_marked
then raise (let lcg = prune_loop node cg in Not_a_DAG (node, lcg))
else if NS.mem node !marked
then ()
else
begin
let children =
try NM.find node cg with
| Not_found -> NS.empty
in
temp_marked := NS.add node !temp_marked;
NS.iter (fun child -> visit child) children;
marked := NS.add node !marked;
temp_marked := NS.remove node !temp_marked;
list := node :: !list
end
in
let rec topsort' () =
try
let unmarked = find_unmarked keys in
visit unmarked; topsort' ()
with
| Not_found -> ()
in topsort' (); !list
type node_idx = { index : int; root : int }
let scc ?(original_order : node list option) (cg : graph) =
let components = ref [] in
let index = ref 0 in
let stack = ref [] in
let push v = (stack := v :: !stack) in
let pop () =
begin
let v = List.hd !stack in
stack := List.tl !stack;
v
end
in
let node_indices = Hashtbl.create (NM.cardinal cg) in
let get_index v = (Hashtbl.find node_indices v).index in
let get_root v = (Hashtbl.find node_indices v).root in
let set_root v r =
Hashtbl.replace node_indices v { (Hashtbl.find node_indices v) with root = r } in
let rec visit_node v =
begin
Hashtbl.add node_indices v { index = !index; root = !index };
index := !index + 1;
push v;
if NM.mem v cg then NS.iter (visit_edge v) (NM.find v cg);
if get_root v = get_index v then (* v is the root of a SCC *)
begin
let component = ref [] in
let finished = ref false in
while not !finished do
let w = pop () in
component := w :: !component;
if Ord.compare v w = 0 then finished := true;
done;
components := !component :: !components;
end
end
and visit_edge v w =
if not (Hashtbl.mem node_indices w) then
begin
visit_node w;
if Hashtbl.mem node_indices w then set_root v (min (get_root v) (get_root w));
end else begin
if List.mem w !stack then set_root v (min (get_root v) (get_index w))
end
in
let nodes = match original_order with
| Some nodes -> nodes
| None -> List.map fst (NM.bindings cg)
in
List.iter (fun v -> if not (Hashtbl.mem node_indices v) then visit_node v) nodes;
List.rev !components
let make_dot node_color edge_color string_of_node out_chan graph =
Util.opt_colors := false;
let to_string node = String.escaped (string_of_node node) in
output_string out_chan "digraph DEPS {\n";
let make_node from_node =
output_string out_chan (Printf.sprintf " \"%s\" [fillcolor=%s;style=filled];\n" (to_string from_node) (node_color from_node))
in
let make_line from_node to_node =
output_string out_chan (Printf.sprintf " \"%s\" -> \"%s\" [color=%s];\n" (to_string from_node) (to_string to_node) (edge_color from_node to_node))
in
NM.bindings graph |> List.iter (fun (from_node, _) -> make_node from_node);
NM.bindings graph |> List.iter (fun (from_node, to_nodes) -> NS.iter (make_line from_node) to_nodes);
output_string out_chan "}\n";
Util.opt_colors := true
end
|