|
| 1 | +(** Strategies for widening leaf unknowns *) |
| 2 | + |
| 3 | +open Batteries |
| 4 | +open ConstrSys |
| 5 | +open Messages |
| 6 | + |
| 7 | +module type S = |
| 8 | + functor (S:EqConstrSys) -> |
| 9 | + functor (HM:Hashtbl.S with type key = S.v) -> |
| 10 | + functor (VS:Set.S with type elt = S.v) -> |
| 11 | + sig |
| 12 | + type data |
| 13 | + |
| 14 | + (** Create data required by this widening point selection strategy. |
| 15 | + The parameters are not necessarily used by all strategies. |
| 16 | + @param is_stable This callback should return whether an unknown is stable. |
| 17 | + @param add_infl Allows the strategy to record additional influences. |
| 18 | + This is mainly intended for strategies like unstable-self, |
| 19 | + which records the influence of a side-effecting unknown x to the leaf y. |
| 20 | + *) |
| 21 | + val create_data: (S.v -> bool) -> (S.v -> S.v -> unit) -> data |
| 22 | + |
| 23 | + (** Notifies this strategy that a side-effect has occured. |
| 24 | + This allows the strategy to adapt its internal data structure. |
| 25 | + @param data The internal state of this strategy |
| 26 | + @param x The optional source of the side-effect |
| 27 | + @param y The leaf receiving the side-effect |
| 28 | + *) |
| 29 | + val notify_side: data -> S.v option -> S.v -> unit |
| 30 | + |
| 31 | + (** Whether the destabilization of the side-effected var should record the destabilization |
| 32 | + of called variables and start variables. This information should be passed to [should_mark_wpoint] |
| 33 | + by the solver. |
| 34 | + *) |
| 35 | + val record_destabilized_vs: bool |
| 36 | + |
| 37 | + (** This strategy can decide to prevent widening. |
| 38 | + Note that, if this strategy does not veto, this does not mean that widening |
| 39 | + will necessarily be performed. Nor does a call to this function imply that |
| 40 | + the value of the leaf has grown. |
| 41 | + @param data The internal state of this strategy |
| 42 | + @param called Set of called unknowns |
| 43 | + @param old_sides Prior side-effects to leaf y |
| 44 | + @param x Optional source of the side-effect |
| 45 | + @param y Side-effected leaf y |
| 46 | + @return [true]: widening will not be applied; [false]: widening might be applied |
| 47 | + *) |
| 48 | + val veto_widen: data -> unit HM.t -> VS.t -> S.v option -> S.v -> bool |
| 49 | + |
| 50 | + (** The value of the leaf has grown. Should it be marked a widening point? |
| 51 | + Widening points are widened when their value grows, unless vetoed. |
| 52 | + Even if this function is called, leaf y might already be a widening point |
| 53 | + from an earlier side-effect. |
| 54 | + @param data The internal state of this strategy |
| 55 | + @param called Set of called unknowns |
| 56 | + @param old_sides Prior side-effects to leaf y |
| 57 | + @param x Optional source of the side-effect |
| 58 | + @param y Side-effected leaf y |
| 59 | + @param destabilized_vs Optional destabilization info, described in [record_destabilized_vs] |
| 60 | + @return [true]: mark as widening point; [false]: do not mark as widening point |
| 61 | +
|
| 62 | + *) |
| 63 | + val should_mark_wpoint: data -> unit HM.t -> VS.t -> S.v option -> S.v -> bool option -> bool |
| 64 | + end |
| 65 | + |
| 66 | +(** Any side-effect after the first one will be widened which will unnecessarily lose precision. *) |
| 67 | +module Always : S = |
| 68 | + functor (S:EqConstrSys) -> |
| 69 | + functor (HM:Hashtbl.S with type key = S.v) -> |
| 70 | + functor (VS:Set.S with type elt = S.v) -> |
| 71 | + struct |
| 72 | + type data = unit |
| 73 | + |
| 74 | + let create_data _ _ = () |
| 75 | + let notify_side _ _ _ = () |
| 76 | + let record_destabilized_vs = false |
| 77 | + let veto_widen _ _ _ _ _ = false |
| 78 | + let should_mark_wpoint _ _ _ _ _ _ = true |
| 79 | + end |
| 80 | + |
| 81 | +(* On side-effect cycles, this should terminate via the outer `solver` loop. TODO check. *) |
| 82 | +(** Never widen side-effects. *) |
| 83 | +module Never : S = |
| 84 | + functor (S:EqConstrSys) -> |
| 85 | + functor (HM:Hashtbl.S with type key = S.v) -> |
| 86 | + functor (VS:Set.S with type elt = S.v) -> |
| 87 | + struct |
| 88 | + type data = unit |
| 89 | + |
| 90 | + let create_data _ _ = () |
| 91 | + let notify_side _ _ _ = () |
| 92 | + let record_destabilized_vs = false |
| 93 | + let veto_widen _ _ _ _ _ = false |
| 94 | + let should_mark_wpoint _ _ _ _ _ _ = false |
| 95 | + end |
| 96 | + |
| 97 | +(** Widening check happens by checking sides. |
| 98 | + Only widen if value increases and there has already been a side-effect from the same source *) |
| 99 | +module SidesLocal : S = |
| 100 | + functor (S:EqConstrSys) -> |
| 101 | + functor (HM:Hashtbl.S with type key = S.v) -> |
| 102 | + functor (VS:Set.S with type elt = S.v) -> |
| 103 | + struct |
| 104 | + type data = unit |
| 105 | + |
| 106 | + let create_data _ _ = () |
| 107 | + let notify_side _ _ _ = () |
| 108 | + let record_destabilized_vs = false |
| 109 | + let veto_widen state called old_sides x y = |
| 110 | + match x with |
| 111 | + | None -> false |
| 112 | + | Some x when VS.mem x old_sides -> false |
| 113 | + | _ -> true |
| 114 | + let should_mark_wpoint _ _ _ _ _ _ = true |
| 115 | + end |
| 116 | + |
| 117 | +(** If there was already a `side x y d` from the same program point and now again, make y a widening point. |
| 118 | + Different from `Sides` in that it will not distinguish between side-effects from different contexts, |
| 119 | + only the program point matters. *) |
| 120 | +module SidesPP : S = |
| 121 | + functor (S:EqConstrSys) -> |
| 122 | + functor (HM:Hashtbl.S with type key = S.v) -> |
| 123 | + functor (VS:Set.S with type elt = S.v) -> |
| 124 | + struct |
| 125 | + type data = unit |
| 126 | + let create_data _ _ = () |
| 127 | + let notify_side _ _ _ = () |
| 128 | + let record_destabilized_vs = false |
| 129 | + let veto_widen state called old_sides x y = false |
| 130 | + let should_mark_wpoint state called old_sides x y _ = match x with |
| 131 | + | Some x -> |
| 132 | + let n = S.Var.node x in |
| 133 | + VS.exists (fun v -> Node.equal (S.Var.node v) n) old_sides |
| 134 | + | None -> false |
| 135 | + (* TODO: This is consistent with the previous implementation, but if multiple side-effects come in with x = None, |
| 136 | + the leaf will never be widened. This is different from SidesLocal *) |
| 137 | + end |
| 138 | + |
| 139 | +(** If there already was a `side x y d` that changed rho[y] and now again, we make y a wpoint. |
| 140 | + x caused more than one update to y. >=3 partial context calls will be precise since sides come from different x. TODO this has 8 instead of 5 phases of `solver` for side_cycle.c *) |
| 141 | +module Sides : S = |
| 142 | + functor (S:EqConstrSys) -> |
| 143 | + functor (HM:Hashtbl.S with type key = S.v) -> |
| 144 | + functor (VS:Set.S with type elt = S.v) -> |
| 145 | + struct |
| 146 | + type data = unit |
| 147 | + |
| 148 | + let create_data _ _ = () |
| 149 | + let notify_side _ _ _ = () |
| 150 | + let record_destabilized_vs = false |
| 151 | + let veto_widen state called old_sides x y = false |
| 152 | + let should_mark_wpoint state called old_sides x y _ = match x with | Some(x) -> VS.mem x old_sides | None -> true |
| 153 | + end |
| 154 | + |
| 155 | +(* TODO: The following two don't check if a vs got destabilized which may be a problem. *) |
| 156 | + |
| 157 | +(* TODO test/remove. Check for which examples this is problematic! *) |
| 158 | +(** Side to y destabilized itself via some infl-cycle. Records influences from unknowns to globals *) |
| 159 | +module UnstableSelf : S = |
| 160 | + functor (S:EqConstrSys) -> |
| 161 | + functor (HM:Hashtbl.S with type key = S.v) -> |
| 162 | + functor (VS:Set.S with type elt = S.v) -> |
| 163 | + struct |
| 164 | + type data = { is_stable: S.v -> bool; add_infl: S.v -> S.v -> unit } |
| 165 | + |
| 166 | + let create_data is_stable add_infl = { is_stable; add_infl } |
| 167 | + let notify_side data x y = (match x with None -> () | Some x -> data.add_infl x y) |
| 168 | + let record_destabilized_vs = false |
| 169 | + let veto_widen _ _ _ _ _ = false |
| 170 | + let should_mark_wpoint state called old_sides x y _ = not (state.is_stable y) |
| 171 | + end |
| 172 | + |
| 173 | +(* TODO test/remove. *) |
| 174 | +(** Widen if any called var (not just y) is no longer stable. Expensive! *) |
| 175 | +module UnstableCalled : S = |
| 176 | + functor (S:EqConstrSys) -> |
| 177 | + functor (HM:Hashtbl.S with type key = S.v) -> |
| 178 | + functor (VS:Set.S with type elt = S.v) -> |
| 179 | + struct |
| 180 | + type data = { is_stable: S.v -> bool } |
| 181 | + |
| 182 | + let create_data is_stable _ = { is_stable } |
| 183 | + let notify_side _ _ _ = () |
| 184 | + let record_destabilized_vs = false |
| 185 | + let veto_widen state called old_sides y x = false |
| 186 | + let should_mark_wpoint state called old_sides y x _ = HM.exists (fun k _ -> not (state.is_stable k)) called (* this is very expensive since it iterates over called! see https://github.com/goblint/analyzer/issues/265#issuecomment-880748636 *) |
| 187 | + end |
| 188 | + |
| 189 | +(** Destabilized a called or start var. Problem: two partial context calls will be precise, but third call will widen the state. |
| 190 | + If this side destabilized some of the initial unknowns vs, there may be a side-cycle between vs and we should make y a wpoint *) |
| 191 | +module Cycle : S = |
| 192 | + functor (S:EqConstrSys) -> |
| 193 | + functor (HM:Hashtbl.S with type key = S.v) -> |
| 194 | + functor (VS:Set.S with type elt = S.v) -> |
| 195 | + struct |
| 196 | + type data = unit |
| 197 | + |
| 198 | + let create_data _ _ = () |
| 199 | + let notify_side _ _ _ = () |
| 200 | + let record_destabilized_vs = true |
| 201 | + let veto_widen state called old_sides x y = false |
| 202 | + let should_mark_wpoint state called old_sides x y cycle = |
| 203 | + match cycle with |
| 204 | + | Some cycle -> |
| 205 | + if tracing && cycle then trace "side_widen" "cycle: should mark wpoint %a" S.Var.pretty_trace y; |
| 206 | + cycle |
| 207 | + | None -> |
| 208 | + failwith "destabilize_vs information not provided to side_widen cycle strategy"; |
| 209 | + end |
| 210 | + |
| 211 | +let choose_impl: unit -> (module S) = fun () -> |
| 212 | + let conf = GobConfig.get_string "solvers.td3.side_widen" in |
| 213 | + match conf with |
| 214 | + | "always" -> (module Always) |
| 215 | + | "never" -> (module Never) |
| 216 | + | "sides-local" -> (module SidesLocal) |
| 217 | + | "sides" -> (module Sides) |
| 218 | + | "sides-pp" -> (module SidesPP) |
| 219 | + | "unstable-self" -> (module UnstableSelf) |
| 220 | + | "unstable-called" -> (module UnstableCalled) |
| 221 | + | "cycle" -> (module Cycle) |
| 222 | + | _ -> failwith ("Unknown value '" ^ conf ^ "' for option solvers.td3.side_widen!") |
0 commit comments