Skip to content

Commit 48ad3e3

Browse files
Merge pull request #1636 from Red-Panda64/feature/divide-and-narrow
Narrowing of globals in TD
2 parents 308b471 + fa3125d commit 48ad3e3

31 files changed

+1163
-47
lines changed

src/analyses/basePriv.ml

Lines changed: 74 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,47 @@ sig
866866
include AtomicParam
867867
end
868868

869+
module P =
870+
struct
871+
include MustVars
872+
let name () = "P"
873+
end
874+
875+
module Sigma' =
876+
struct
877+
include CPA
878+
let name () = "Protected Changes"
879+
end
880+
881+
module type ProtectionDom =
882+
sig
883+
include Lattice.S
884+
val add: bool -> varinfo -> VD.t -> t -> t
885+
val remove: varinfo -> t -> t
886+
val precise_side: varinfo -> VD.t -> t -> VD.t option
887+
val empty: unit -> t
888+
val getP: t -> P.t
889+
end
890+
891+
module ProtectionCPASide: ProtectionDom =
892+
struct
893+
include P
894+
895+
let add _ x _ t = P.add x t
896+
let precise_side x v t = Some v
897+
let getP t = t
898+
end
899+
900+
module ProtectionChangesOnlySide: ProtectionDom =
901+
struct
902+
include Lattice.Prod (P) (Sigma')
903+
let add invariant x v t = P.add x @@ fst t, if not invariant then Sigma'.add x v @@ snd t else snd t
904+
let remove x t = P.remove x @@ fst t, Sigma'.remove x @@ snd t
905+
let precise_side x v t = Sigma'.find_opt x @@ snd t
906+
let empty () = P.empty (), Sigma'.empty ()
907+
let getP t = fst t
908+
end
909+
869910
(** Protection-Based Reading. *)
870911
module ProtectionBasedV = struct
871912
module VUnprot =
@@ -887,30 +928,24 @@ module ProtectionBasedV = struct
887928
end
888929

889930
(** Protection-Based Reading. *)
890-
module ProtectionBasedPriv (Param: PerGlobalPrivParam)(Wrapper:PrivatizationWrapper): S =
931+
module ProtectionBasedPriv (D: ProtectionDom) (Param: PerGlobalPrivParam)(Wrapper:PrivatizationWrapper): S =
891932
struct
892933
include NoFinalize
893934
include ConfCheck.RequireMutexActivatedInit
894935
open Protection
895936

896-
module P =
897-
struct
898-
include MustVars
899-
let name () = "P"
900-
end
901-
902937
(* W is implicitly represented by CPA domain *)
903-
module D = P
938+
module D = D
904939

905940
module Wrapper = Wrapper (VD)
906941
module G = Wrapper.G
907942
module V = ProtectionBasedV.V
908943

909-
let startstate () = P.empty ()
944+
let startstate () = D.empty ()
910945

911946
let read_global (ask: Queries.ask) getg (st: BaseComponents (D).t) x =
912947
let getg = Wrapper.getg ask getg in
913-
if P.mem x st.priv then
948+
if P.mem x @@ D.getP st.priv then
914949
CPA.find x st.cpa
915950
else if Param.handle_atomic && ask.f MustBeAtomic then
916951
VD.join (CPA.find x st.cpa) (getg (V.unprotected x)) (* Account for previous unpublished unprotected writes in current atomic section. *)
@@ -921,6 +956,7 @@ struct
921956

922957
let write_global ?(invariant=false) (ask: Queries.ask) getg sideg (st: BaseComponents (D).t) x v =
923958
let sideg = Wrapper.sideg ask sideg in
959+
let unprotected = is_unprotected ask x in
924960
if not invariant then (
925961
if not (Param.handle_atomic && ask.f MustBeAtomic) then
926962
sideg (V.unprotected x) v; (* Delay publishing unprotected write in the atomic section. *)
@@ -929,11 +965,11 @@ struct
929965
(* Unlock after invariant will still side effect refined value (if protected) from CPA, because cannot distinguish from non-invariant write since W is implicit. *)
930966
);
931967
if Param.handle_atomic && ask.f MustBeAtomic then
932-
{st with cpa = CPA.add x v st.cpa; priv = P.add x st.priv} (* Keep write local as if it were protected by the atomic section. *)
933-
else if is_unprotected ask x then
968+
{st with cpa = CPA.add x v st.cpa; priv = D.add invariant x v st.priv} (* Keep write local as if it were protected by the atomic section. *)
969+
else if unprotected then
934970
st
935971
else
936-
{st with cpa = CPA.add x v st.cpa; priv = P.add x st.priv}
972+
{st with cpa = CPA.add x v st.cpa; priv = D.add invariant x v st.priv}
937973

938974
let lock ask getg st m = st
939975

@@ -943,16 +979,22 @@ struct
943979
(* TODO: what about G_m globals in cpa that weren't actually written? *)
944980
CPA.fold (fun x v (st: BaseComponents (D).t) ->
945981
if is_protected_by ask m x then ( (* is_in_Gm *)
946-
(* Extra precision in implementation to pass tests:
947-
If global is read-protected by multiple locks,
948-
then inner unlock shouldn't yet publish. *)
949-
if not Param.check_read_unprotected || is_unprotected_without ask ~write:false x m then
950-
sideg (V.protected x) v;
951-
if atomic then
952-
sideg (V.unprotected x) v; (* Publish delayed unprotected write as if it were protected by the atomic section. *)
953-
982+
(* Only apply sides for values that were actually written to globals!
983+
This excludes invariants inferred through guards. *)
984+
begin match D.precise_side x v st.priv with
985+
| Some v -> begin
986+
(* Extra precision in implementation to pass tests:
987+
If global is read-protected by multiple locks,
988+
then inner unlock shouldn't yet publish. *)
989+
if not Param.check_read_unprotected || is_unprotected_without ask ~write:false x m then
990+
sideg (V.protected x) v;
991+
if atomic then
992+
sideg (V.unprotected x) v; (* Publish delayed unprotected write as if it were protected by the atomic section. *)
993+
end
994+
| None -> ()
995+
end;
954996
if is_unprotected_without ask x m then (* is_in_V' *)
955-
{st with cpa = CPA.remove x st.cpa; priv = P.remove x st.priv}
997+
{st with cpa = CPA.remove x st.cpa; priv = D.remove x st.priv}
956998
else
957999
st
9581000
)
@@ -968,7 +1010,7 @@ struct
9681010
if is_global ask x && is_unprotected ask x then (
9691011
sideg (V.unprotected x) v;
9701012
sideg (V.protected x) v; (* must be like enter_multithreaded *)
971-
{st with cpa = CPA.remove x st.cpa; priv = P.remove x st.priv}
1013+
{st with cpa = CPA.remove x st.cpa; priv = D.remove x st.priv}
9721014
)
9731015
else
9741016
st
@@ -1007,7 +1049,7 @@ struct
10071049
if is_global ask x then (
10081050
sideg (V.unprotected x) v;
10091051
sideg (V.protected x) v;
1010-
{st with cpa = CPA.remove x st.cpa; priv = P.remove x st.priv}
1052+
{st with cpa = CPA.remove x st.cpa; priv = D.remove x st.priv}
10111053
)
10121054
else
10131055
st
@@ -2116,19 +2158,21 @@ end
21162158

21172159
let priv_module: (module S) Lazy.t =
21182160
lazy (
2161+
let changes_only = get_bool "ana.base.priv.protection.changes-only" in
2162+
let module ProtDom: ProtectionDom = (val if changes_only then (module ProtectionChangesOnlySide : ProtectionDom) else (module ProtectionCPASide)) in
21192163
let module Priv: S =
21202164
(val match get_string "ana.base.privatization" with
21212165
| "none" -> (module NonePriv: S)
21222166
| "vojdani" -> (module VojdaniPriv: S)
21232167
| "mutex-oplus" -> (module PerMutexOplusPriv)
21242168
| "mutex-meet" -> (module PerMutexMeetPriv)
21252169
| "mutex-meet-tid" -> (module PerMutexMeetTIDPriv (ThreadDigest))
2126-
| "protection" -> (module ProtectionBasedPriv (struct let check_read_unprotected = false let handle_atomic = false end)(NoWrapper))
2127-
| "protection-tid" -> (module ProtectionBasedPriv (struct let check_read_unprotected = false let handle_atomic = false end)(DigestWrapper(ThreadNotStartedDigest)))
2128-
| "protection-atomic" -> (module ProtectionBasedPriv (struct let check_read_unprotected = false let handle_atomic = true end)(NoWrapper)) (* experimental *)
2129-
| "protection-read" -> (module ProtectionBasedPriv (struct let check_read_unprotected = true let handle_atomic = false end)(NoWrapper))
2130-
| "protection-read-tid" -> (module ProtectionBasedPriv (struct let check_read_unprotected = true let handle_atomic = false end)(DigestWrapper(ThreadNotStartedDigest)))
2131-
| "protection-read-atomic" -> (module ProtectionBasedPriv (struct let check_read_unprotected = true let handle_atomic = true end)(NoWrapper)) (* experimental *)
2170+
| "protection" -> (module ProtectionBasedPriv (ProtDom) (struct let check_read_unprotected = false let handle_atomic = false end)(NoWrapper))
2171+
| "protection-tid" -> (module ProtectionBasedPriv (ProtDom) (struct let check_read_unprotected = false let handle_atomic = false end)(DigestWrapper(ThreadNotStartedDigest)))
2172+
| "protection-atomic" -> (module ProtectionBasedPriv (ProtDom) (struct let check_read_unprotected = false let handle_atomic = true end)(NoWrapper)) (* experimental *)
2173+
| "protection-read" -> (module ProtectionBasedPriv (ProtDom) (struct let check_read_unprotected = true let handle_atomic = false end)(NoWrapper))
2174+
| "protection-read-tid" -> (module ProtectionBasedPriv (ProtDom) (struct let check_read_unprotected = true let handle_atomic = false end)(DigestWrapper(ThreadNotStartedDigest)))
2175+
| "protection-read-atomic" -> (module ProtectionBasedPriv (ProtDom) (struct let check_read_unprotected = true let handle_atomic = true end)(NoWrapper)) (* experimental *)
21322176
| "mine" -> (module MinePriv)
21332177
| "mine-nothread" -> (module MineNoThreadPriv)
21342178
| "mine-W" -> (module MineWPriv (struct let side_effect_global_init = true end))

src/config/options.schema.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,20 @@
790790
"title": "ana.base.priv",
791791
"type": "object",
792792
"properties": {
793+
"protection": {
794+
"title": "ana.base.priv.protection",
795+
"description": "Options for protection based privatization schemes.",
796+
"type": "object",
797+
"properties": {
798+
"changes-only": {
799+
"title": "ana.base.priv.protection.changes-only",
800+
"description": "Only propagate changes to globals on unlock, don't side the full value of the privatized global.",
801+
"type": "boolean",
802+
"default": false
803+
}
804+
},
805+
"additionalProperties": false
806+
},
793807
"not-started": {
794808
"title": "ana.base.priv.not-started",
795809
"description":
@@ -2526,6 +2540,43 @@
25262540
"type": "boolean",
25272541
"default": true
25282542
},
2543+
"narrow-globs": {
2544+
"title": "solvers.td3.narrow-globs",
2545+
"type": "object",
2546+
"properties": {
2547+
"enabled": {
2548+
"title": "solvers.td3.narrow-globs.enabled",
2549+
"description": "Individually track side-effects from different unknowns to enable narrowing of globals.",
2550+
"type": "boolean",
2551+
"default": false
2552+
},
2553+
"conservative-widen": {
2554+
"title": "solvers.td3.narrow-globs.conservative-widen",
2555+
"description": "Controls when to widen divided sides. true: Only widen on a side-effect, if joining would affect the overall value of the global. false: Always widen increasing/incomparable side-effects",
2556+
"type": "boolean",
2557+
"default": true
2558+
},
2559+
"immediate-growth": {
2560+
"title": "solvers.td3.narrow-globs.immediate-growth",
2561+
"description": "Controls when growing side-effects are applied. true: immediately when they are triggered. false: after the source unknown has been fully evaluated",
2562+
"type": "boolean",
2563+
"default": true
2564+
},
2565+
"narrow-gas": {
2566+
"title": "solvers.td3.narrow-globs.narrow-gas",
2567+
"description": "Limits the number of times a side-effect can switch from widening to narrowing to enforce termination. 0 disables narrowing, -1 allows narrowing infinitely often.",
2568+
"type": "integer",
2569+
"default": 5
2570+
},
2571+
"eliminate-dead": {
2572+
"title": "solvers.td3.narrow-globs.eliminate-dead",
2573+
"description": "side-effects that no longer occur are narrowed with bot.",
2574+
"type": "boolean",
2575+
"default": false
2576+
}
2577+
},
2578+
"additionalProperties": false
2579+
},
25292580
"remove-wpoint": {
25302581
"title": "solvers.td3.remove-wpoint",
25312582
"description": "Remove widening points after narrowing phase. Enables a form of local restarting which increases precision of nested loops.",

src/constraint/constrSys.ml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,12 @@ sig
4646
(** The system in functional form. *)
4747
val system : v -> ((v -> d) -> (v -> d -> unit) -> d) m
4848

49-
val sys_change: (v -> d) -> v sys_change_info
5049
(** Compute incremental constraint system change from old solution. *)
50+
val sys_change: (v -> d) -> v sys_change_info
51+
52+
(** List of unknowns that should be queried again when the argument unknown has shrunk to bot, to eagerly trigger (analysis-time!) abstract garbage collection idependently of reach-based pruning at the end.
53+
@see <https://arxiv.org/abs/2504.06026> Stemmler, F., Schwarz, M., Erhard, J., Tilscher, S., Seidl, H. Taking out the Toxic Trash: Recovering Precision in Mixed Flow-Sensitive Static Analyses *)
54+
val postmortem: v -> v list
5155
end
5256

5357
(** Any system of side-effecting equations over lattices. *)
@@ -64,6 +68,7 @@ sig
6468
val system : LVar.t -> ((LVar.t -> D.t) -> (LVar.t -> D.t -> unit) -> (GVar.t -> G.t) -> (GVar.t -> G.t -> unit) -> D.t) option
6569
val iter_vars: (LVar.t -> D.t) -> (GVar.t -> G.t) -> VarQuery.t -> LVar.t VarQuery.f -> GVar.t VarQuery.f -> unit
6670
val sys_change: (LVar.t -> D.t) -> (GVar.t -> G.t) -> [`L of LVar.t | `G of GVar.t] sys_change_info
71+
val postmortem: LVar.t -> LVar.t list
6772
end
6873

6974
(** A solver is something that can translate a system into a solution (hash-table).
@@ -205,6 +210,10 @@ struct
205210

206211
let sys_change get =
207212
S.sys_change (getL % get % l) (getG % get % g)
213+
214+
let postmortem = function
215+
| `L g -> List.map (fun x -> `L x) @@ S.postmortem g
216+
| _ -> []
208217
end
209218

210219
(** Splits a [EqConstrSys] solution into a [GlobConstrSys] solution with given [Hashtbl.S] for the [EqConstrSys]. *)

src/framework/constraints.ml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,4 +549,8 @@ struct
549549
in
550550

551551
{obsolete; delete; reluctant; restart}
552+
553+
let postmortem = function
554+
| FunctionEntry fd, c -> [(Function fd, c)]
555+
| _ -> []
552556
end

src/maingoblint.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ let check_arguments () =
141141
if get_bool "ana.base.context.int" && not (get_bool "ana.base.context.non-ptr") then (set_bool "ana.base.context.int" false; warn "ana.base.context.int implicitly disabled by ana.base.context.non-ptr");
142142
(* order matters: non-ptr=false, int=true -> int=false cascades to interval=false with warning *)
143143
if get_bool "ana.base.context.interval" && not (get_bool "ana.base.context.int") then (set_bool "ana.base.context.interval" false; warn "ana.base.context.interval implicitly disabled by ana.base.context.int");
144+
if get_bool "ana.base.priv.protection.changes-only" && not @@ List.mem (get_string "ana.base.privatization") ["protection"; "protection-tid"; "protection-atomic"; "protection-read"; "protection-read-tid"; "protection-read-atomic"] then
145+
warn "ana.base.priv.protection.changes-only requires ana.base.privatization to be protection based";
144146
if get_bool "incremental.only-rename" then (set_bool "incremental.load" true; warn "incremental.only-rename implicitly activates incremental.load. Previous AST is loaded for diff and rename, but analyis results are not reused.");
145147
if get_bool "incremental.restart.sided.enabled" && get_string_list "incremental.restart.list" <> [] then warn "Passing a non-empty list to incremental.restart.list (manual restarting) while incremental.restart.sided.enabled (automatic restarting) is activated.";
146148
if get_bool "ana.autotune.enabled" && get_bool "incremental.load" then (set_bool "ana.autotune.enabled" false; warn "ana.autotune.enabled implicitly disabled by incremental.load");
@@ -164,6 +166,10 @@ let check_arguments () =
164166
);
165167
if get_bool "solvers.td3.space" && get_bool "solvers.td3.remove-wpoint" then fail "solvers.td3.space is incompatible with solvers.td3.remove-wpoint";
166168
if get_bool "solvers.td3.space" && get_string "solvers.td3.side_widen" = "sides-local" then fail "solvers.td3.space is incompatible with solvers.td3.side_widen = 'sides-local'";
169+
if get_bool "solvers.td3.space" && get_bool "solvers.td3.narrow-globs.enabled" then fail "solvers.td3.space is incompatible with solvers.td3.narrow-globs.enabled";
170+
if (get_bool "incremental.load" || get_bool "incremental.save") && get_bool "solvers.td3.narrow-globs.enabled" then (
171+
fail "solvers.td3.space is incompatible with incremental analsyis.";
172+
);
167173
if List.mem "termination" @@ get_string_list "ana.activated" then (
168174
if GobConfig.get_bool "incremental.load" || GobConfig.get_bool "incremental.save" then fail "termination analysis is not compatible with incremental analysis";
169175
set_list "ana.activated" (GobConfig.get_list "ana.activated" @ [`String ("threadflag")]);

src/solver/goblint_solver.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ module SolverStats = SolverStats
3232
module SolverBox = SolverBox
3333

3434
module SideWPointSelect = SideWPointSelect
35+
module Td3UpdateRule = Td3UpdateRule

0 commit comments

Comments
 (0)