Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dune-project
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Goblint includes analyses for assertions, overflows, deadlocks, etc and can be e
(goblint-cil (>= 2.1.0)) ; TODO no way to define as pin-depends? Used goblint.opam.template to add it for now. https://github.com/ocaml/dune/issues/3231. Alternatively, removing this line and adding cil as a git submodule and `(vendored_dirs cil)` as ./dune also works. This way, no more need to reinstall the pinned cil opam package on changes. However, then cil is cleaned and has to be rebuild together with goblint.
(batteries (>= 3.9.0))
(patricia-tree (>= 0.14.0))
ocamlgraph
(zarith (>= 1.12))
(yojson (and (>= 2.0.0) (< 3))) ; json-data-encoding has incompatible yojson representation for yojson 3
(qcheck-core (>= 0.90))
Expand Down
1 change: 1 addition & 0 deletions goblint.opam
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ depends: [
"goblint-cil" {>= "2.1.0"}
"batteries" {>= "3.9.0"}
"patricia-tree" {>= "0.14.0"}
"ocamlgraph"
"zarith" {>= "1.12"}
"yojson" {>= "2.0.0" & < "3"}
"qcheck-core" {>= "0.90"}
Expand Down
1 change: 1 addition & 0 deletions goblint.opam.locked
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ depends: [
"ocamlc-loc" {= "3.21.1" & with-dev-setup}
"ocamlfind" {= "1.9.8"}
"ocamlformat-rpc-lib" {= "0.29.0" & with-dev-setup}
"ocamlgraph" {= "2.2.0"}
"ocp-indent" {= "1.8.1" & with-dev-setup}
"odoc" {= "3.0.0" & with-doc}
"odoc-parser" {= "3.0.0" & with-doc}
Expand Down
2 changes: 2 additions & 0 deletions scripts/goblint-lib-modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
src_root_path / "util" / "parallel" / "goblint_parallel.ml",
src_root_path / "solver" / "goblint_solver.ml",
src_root_path / "util" / "std" / "goblint_std.ml",
src_root_path / "util" / "ocamlgraph" / "goblint_ocamlgraph.ml",
]
goblint_lib_modules = set()

Expand Down Expand Up @@ -39,6 +40,7 @@

# libraries
"Goblint_std",
"Goblint_ocamlgraph",
"Goblint_constraint",
"Goblint_parallel",
"Goblint_solver",
Expand Down
7 changes: 7 additions & 0 deletions src/config/options.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2463,6 +2463,13 @@
"type": "integer",
"default": 0
},
"race-coloring": {
"title": "warn.race-coloring",
"description": "Group race warnings by graph coloring (none, greedy, dsatur, rlf, optimal).",
"type": "string",
"enum": ["none", "greedy", "dsatur", "rlf", "optimal"],
"default": "none"
},
Comment on lines +2466 to +2472

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be some explanation of what this would achieve from a user-level perspective.

"deterministic": {
"title": "warn.deterministic",
"description": "Output messages in deterministic order. Useful for cram testing.",
Expand Down
58 changes: 56 additions & 2 deletions src/domains/access.ml
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,41 @@ let incr_summary ~safe ~vulnerable ~unsafe grouped_accs =
| Some n when n >= 100 -> is_all_safe := false; incr unsafe
| Some n -> is_all_safe := false; incr vulnerable

module InterferenceGraph =
struct
include Graph.Imperative.Graph.Concrete (A)

let of_accesses (accs : AS.t) =
let graph = create () in
AS.iter (fun acc -> add_vertex graph acc) accs;
let accs_list = AS.elements accs in
let rec loop = function
| [] -> ()
| a :: rest ->
List.iter (fun b ->
if may_race a b then
add_edge graph a b
) rest;
loop rest
in
loop accs_list;
graph
end
module InterferenceGraphColoring = Goblint_ocamlgraph.Coloring.Make (InterferenceGraph)
module ColorMap = Map.Make (Goblint_ocamlgraph.Coloring.Color)

let coloring_module =
lazy (
let open InterferenceGraphColoring in
match get_string "warn.race-coloring" with
| "none" -> None
| "greedy" -> Some (module Greedy: Algorithm)
| "dsatur" -> Some (module Dsatur)
| "rlf" -> Some (module Rlf)
| "optimal" -> Some (module Optimal)
| _ -> assert false
)

let print_accesses memo grouped_accs =
let allglobs = get_bool "allglobs" in
let race_threshold = get_int "warn.race-threshold" in
Expand All @@ -602,8 +637,27 @@ let print_accesses memo grouped_accs =
let doc = dprintf "%a with %a (conf. %d) (exp: %a)" AccessKind.pretty kind MCPAccess.A.pretty acc conf d_exp exp in
(doc, Some (Messages.Location.Node node))
in
AS.elements race_accs
|> List.map h
match coloring_module with
| lazy None ->
AS.elements race_accs
|> List.map h
| lazy (Some (module Coloring: InterferenceGraphColoring.Algorithm)) ->
let graph = InterferenceGraph.of_accesses race_accs in
let coloring = Coloring.color graph in
let add_to_map acc map =
let c = InterferenceGraphColoring.H.find coloring acc in
ColorMap.update c (function
| None -> Some [acc]
| Some accs -> Some (acc :: accs)
) map
in
let color_map = AS.fold add_to_map race_accs ColorMap.empty in
ColorMap.bindings color_map
|> List.concat_map (fun (color, accs) ->
let header = (dprintf "Color %d" color, None) in
let acc_msgs = accs |> List.rev |> List.map h in
header :: acc_msgs
)
in
let group_loc = match memo with
| (`Var v, _) -> Some (M.Location.CilLocation v.vdecl) (* TODO: offset location *)
Expand Down
2 changes: 1 addition & 1 deletion src/dune
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
(name goblint_lib)
(public_name goblint.lib)
(modules :standard \ goblint goblint_memtrace privPrecCompare apronPrecCompare messagesCompare)
(libraries goblint.sites goblint.build-info goblint-cil goblint-cil.pta goblint-cil.syntacticsearch batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath yaml yaml.unix uuidm goblint_timing catapult goblint_backtrace fileutils goblint_std goblint_config goblint_common goblint_domain goblint_constraint goblint_solver goblint_library goblint_cdomain_value goblint_incremental goblint_tracing goblint_logs domain_shims
(libraries goblint.sites goblint.build-info goblint-cil goblint-cil.pta goblint-cil.syntacticsearch batteries.unthreaded qcheck-core.runner sha json-data-encoding jsonrpc cpu arg-complete fpath yaml yaml.unix uuidm goblint_timing catapult goblint_backtrace fileutils goblint_std goblint_config goblint_common ocamlgraph goblint_ocamlgraph goblint_domain goblint_constraint goblint_solver goblint_library goblint_cdomain_value goblint_incremental goblint_tracing goblint_logs domain_shims
; Conditionally compile based on whether apron optional dependency is installed or not.
; Alternative dependencies seem like the only way to optionally depend on optional dependencies.
; See: https://dune.readthedocs.io/en/stable/reference/library-dependencies.html#alternative-dependencies
Expand Down
3 changes: 3 additions & 0 deletions src/index.mld
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ The following libraries provide extensions to other OCaml libraries.
{2 Library goblint.std}
{!modules:Goblint_std}

{2 Library goblint.ocamlgraph}
{!modules:Goblint_ocamlgraph}


{1 Package utilities}
The following libraries provide [goblint] package metadata for executables.
Expand Down
209 changes: 209 additions & 0 deletions src/util/ocamlgraph/coloring.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
module Color = Int

module ColorSet =
struct
include Set.Make (Color)

let find_unused used =
let rec loop c =
if mem c used then
loop (c + 1)
else
c
in
loop 1
end

module Make (G: Graph.Coloring.G) =
struct
module H = Hashtbl.Make (G.V)
type coloring = Color.t H.t

module type Algorithm =
sig
val color: G.t -> coloring
end

module Greedy: Algorithm =
struct
let color g =
let n = G.nb_vertex g in
let coloring = H.create n in
let vertices =
G.fold_vertex (fun v acc -> (G.out_degree g v, v) :: acc) g []
|> List.sort (fun (d1, _) (d2, _) -> compare d2 d1)
|> List.map snd
in
let next_color v =
(* TODO: use saturation hashtbl like in Dsatur? *)
let used = G.fold_succ (fun u used ->
match H.find_opt coloring u with
| None -> used
| Some c -> ColorSet.add c used
) g v ColorSet.empty
in
ColorSet.find_unused used
in
List.iter (fun v -> H.replace coloring v (next_color v)) vertices;
coloring
end

module Optimal: Algorithm =
struct
module C = Graph.Coloring.Make (G)

let color g =
let n = G.nb_vertex g in
let rec loop k =
assert (k <= n);
try C.coloring g k
with Graph.Coloring.NoColoring -> loop (k + 1)
in
loop 1
end

module Dsatur: Algorithm =
struct
let color g =
let n = G.nb_vertex g in
let coloring = H.create n in
let saturation = H.create n in
let degree = H.create n in
G.iter_vertex (fun v ->
H.replace saturation v ColorSet.empty;
H.replace degree v (G.out_degree g v)
) g;
let is_colored v = H.mem coloring v in
let sat_count v =
ColorSet.cardinal (H.find saturation v)
in
let choose_vertex () =
let pick v best_opt =
if is_colored v then
best_opt
else
match best_opt with
| None -> Some v
| Some best ->
let sv = sat_count v in
let sb = sat_count best in
if sv > sb then
Some v
else if sv < sb then
Some best
else (
let dv = H.find degree v in
let db = H.find degree best in
if dv > db then Some v else Some best
)
in
(* TODO: uncolored set like in Rlf? *)
G.fold_vertex pick g None
in
let pick_color v =
let used = H.find saturation v in
ColorSet.find_unused used
in
let rec loop () =
match choose_vertex () with
| None -> ()
| Some v ->
let c = pick_color v in
H.replace coloring v c;
G.iter_succ (fun u ->
if not (is_colored u) then
let s = H.find saturation u in
H.replace saturation u (ColorSet.add c s)
) g v;
loop ()
in
loop ();
coloring
end

module Rlf: Algorithm =
struct
module VSet =
struct
let create n = H.create n
let mem s v = H.mem s v
let add s v = H.replace s v ()
let remove s v = H.remove s v
end

let color g =
let n = G.nb_vertex g in
let coloring = H.create n in
let uncolored = VSet.create n in
G.iter_vertex (fun v -> VSet.add uncolored v) g;
let degree v = G.out_degree g v in
let pick_start () =
G.fold_vertex (fun v best ->
if not (VSet.mem uncolored v) then
best
else
match best with
| None -> Some v
| Some b ->
if degree v > degree b then Some v else Some b
) g None
in
let add_forbidden forbidden v =
G.iter_succ (fun u ->
if VSet.mem uncolored u then
VSet.add forbidden u
) g v
in
let candidate_score forbidden v =
let count = ref 0 in
G.iter_succ (fun u ->
if VSet.mem forbidden u then
incr count
) g v;
!count
in
let pick_candidate forbidden =
G.fold_vertex (fun v best ->
if not (VSet.mem uncolored v) || VSet.mem forbidden v then
best
else
match best with
| None -> Some v
| Some b ->
let sv = candidate_score forbidden v in
let sb = candidate_score forbidden b in
if sv > sb then
Some v
else if sv < sb then
Some b
else if degree v > degree b then
Some v
else
Some b
) g None
in
let rec color_class color =
match pick_start () with
| None -> ()
| Some v0 ->
let forbidden = VSet.create n in
let add_vertex v =
H.replace coloring v color;
VSet.remove uncolored v;
add_forbidden forbidden v
in
add_vertex v0;
let rec fill () =
match pick_candidate forbidden with
| None -> ()
| Some v ->
add_vertex v;
fill ()
in
fill ();
color_class (color + 1)
in
color_class 1;
coloring
end
end
24 changes: 24 additions & 0 deletions src/util/ocamlgraph/coloring.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
(** Graph coloring.

See also {!Graph.Coloring}. *)

module Color = Int
(** Colors are {e positive} integers. *)

module ColorSet: Set.S with type elt = Color.t

module Make (G: Graph.Coloring.G):
sig
module H: Hashtbl.S with type key = G.V.t
type coloring = Color.t H.t

module type Algorithm =
sig
val color: G.t -> coloring
end

module Greedy: Algorithm
module Optimal: Algorithm
module Dsatur: Algorithm
module Rlf: Algorithm
end
8 changes: 8 additions & 0 deletions src/util/ocamlgraph/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(include_subdirs no)

(library
(name goblint_ocamlgraph)
(public_name goblint.ocamlgraph)
(libraries
ocamlgraph)
(instrumentation (backend bisect_ppx)))
3 changes: 3 additions & 0 deletions src/util/ocamlgraph/goblint_ocamlgraph.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(** OCamlgraph library extensions which are completely independent of Goblint. *)

module Coloring = Coloring
Loading