From 74f6100b9e55ae491e430310eda10317a1484ba3 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 16 Jan 2026 12:21:30 +1000 Subject: [PATCH 01/36] First pass lattice --- lib/analysis/dune | 2 +- lib/analysis/wrapping_intervals.ml | 138 +++++++++++++++++++++++++++++ test/lang/value_soundness.ml | 7 +- 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 lib/analysis/wrapping_intervals.ml diff --git a/lib/analysis/dune b/lib/analysis/dune index 8ed1119..58f5660 100644 --- a/lib/analysis/dune +++ b/lib/analysis/dune @@ -2,7 +2,7 @@ (public_name bincaml.analysis) (name analysis) (flags -w -27) - (modules dataflow_graph intra_analysis defuse_bool lattice_types) + (modules dataflow_graph intra_analysis defuse_bool wrapping_intervals lattice_types) (libraries patricia-tree loader diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml new file mode 100644 index 0000000..0696971 --- /dev/null +++ b/lib/analysis/wrapping_intervals.ml @@ -0,0 +1,138 @@ +open Bincaml_util.Common + +module WrappingIntervalsLattice = struct + let name = "wrappingIntervals" + + type t = Top | Interval of { lower : Bitvec.t; upper : Bitvec.t } | Bot + [@@deriving eq, show { with_path = false }] + + let interval lower upper = + Bitvec.size_is_equal lower upper; + Interval { lower; upper } + + let bottom = Bot + let pretty t = Containers_pp.text (show t) + + (* TODO: Rewrite as compare_size to avoid storing widths for Top and Bot *) + let cardinality t = + match t with + | Bot -> Z.of_int 0 + | Top -> Z.pow (Z.of_int 2) 64 + | Interval { lower; upper } -> + Bitvec.( + sub upper lower + |> add (of_int ~size:(size lower) 1) + |> to_unsigned_bigint) + + let complement t = + match t with + | Bot -> Top + | Top -> Bot + | Interval { lower; upper } -> + let new_lower = Bitvec.(add upper (of_int ~size:(size upper) 1)) in + let new_upper = Bitvec.(sub lower (of_int ~size:(size lower) 1)) in + interval new_lower new_upper + + let member t e = + match t with + | Bot -> false + | Top -> true + | Interval { lower; upper } -> Bitvec.(ule (sub e lower) (sub upper lower)) + + let compare a b = + match (a, b) with + | a, b when equal a b -> 0 + | Top, _ -> 1 + | Bot, _ -> -1 + | _, Top -> -1 + | _, Bot -> 1 + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> + if + member b al && member b au + && ((not (member a bl)) || not (member a bu)) + then -1 + else 1 + + let join a b = + if compare a b <= 0 then b + else if compare a b >= 0 then a + else + match (a, b) with + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> + let al_mem = member b al in + let au_mem = member b au in + let bl_mem = member a bl in + let bu_mem = member a bu in + if al_mem && au_mem && bl_mem && bu_mem then Top + else if au_mem && bl_mem then interval al bu + else if al_mem && bu_mem then interval bl au + else + let inner_span = cardinality (interval au bl) in + let outer_span = cardinality (interval bu al) in + if + Z.lt inner_span outer_span + || (Z.equal inner_span outer_span && Bitvec.ule al bl) + then interval al bu + else interval bl au + | _, _ -> failwith "unreachable" + + (* TODO: Implement join for multiple intervals (see APLAS12 Fig. 3) *) + + let widening a b = + match (a, b) with + | a, Bot -> a + | Bot, b -> b + | a, Top -> a + | Top, b -> b + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> + Bitvec.size_is_equal al bl; + Bitvec.size_is_equal au bu; + let width = Bitvec.size al in + if compare b a <= 0 then a + else if Z.geq (cardinality a) (Z.pow (Z.of_int 2) 64) then Top + else + let joined = join a b in + if equal joined (interval al bu) then + join joined + (interval al + Bitvec.( + sub (mul au (of_int ~size:width 2)) al + |> add (of_int ~size:width 1))) + else if equal joined (interval bl au) then + join joined + (interval + Bitvec.( + sub + (sub (mul al (of_int ~size:width 2)) au) + (of_int ~size:width 1)) + au) + else if member b al && member b au then + join b + (interval bl + Bitvec.( + sub + (bl + |> add (mul au (of_int ~size:width 2)) + |> add (of_int ~size:width 1)) + (mul al (of_int ~size:width 2)))) + else Top +end + +module WrappingIntervalsValueAbstraction = struct + include WrappingIntervalsLattice + + let eval_const op = Top + let eval_unop op a = Top + let eval_binop op a b = Top + let eval_intrin op args = Top +end + +module StateAbstraction = Intra_analysis.MapState (WrappingIntervalsLattice) + +module WrappingIntervalsValueAbstractionBasil = struct + include WrappingIntervalsValueAbstraction + module E = Lang.Expr.BasilExpr +end diff --git a/test/lang/value_soundness.ml b/test/lang/value_soundness.ml index 12d1159..d91e111 100644 --- a/test/lang/value_soundness.ml +++ b/test/lang/value_soundness.ml @@ -55,5 +55,10 @@ end module TestBoolDom = ValueAbstractionSoundness (Analysis.Defuse_bool.IsZeroValueAbstractionBasil) +module TestWrappingIntervalDom = + ValueAbstractionSoundness + (Analysis.Wrapping_intervals.WrappingIntervalsValueAbstractionBasil) + let _ = - Alcotest.run "value domain abstract eval soundness" [ TestBoolDom.suite ] + Alcotest.run "value domain abstract eval soundness" + [ TestBoolDom.suite; TestWrappingIntervalDom.suite ] From f7f2e99f6ad27f6331edc5b02f2ad751d127ac82 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 16 Jan 2026 14:42:06 +1000 Subject: [PATCH 02/36] Least upper bound for more than 2 --- lib/analysis/wrapping_intervals.ml | 46 +++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 0696971..1f54da8 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -24,6 +24,15 @@ module WrappingIntervalsLattice = struct |> add (of_int ~size:(size lower) 1) |> to_unsigned_bigint) + let compare_size a b = + match (a, b) with + | a, b when equal a b -> 0 + | Top, _ -> 1 + | Bot, _ -> -1 + | _, Top -> -1 + | _, Bot -> 1 + | Interval _, Interval _ -> Z.compare (cardinality a) (cardinality b) + let complement t = match t with | Bot -> Top @@ -78,7 +87,42 @@ module WrappingIntervalsLattice = struct else interval bl au | _, _ -> failwith "unreachable" - (* TODO: Implement join for multiple intervals (see APLAS12 Fig. 3) *) + (* Join for multiple intervals to increase precision (join is not monotone) *) + let lub (ints : t list) = + let bigger a b = if compare_size a b < 0 then b else a in + let gap a b = + match (a, b) with + | Interval { upper = au; _ }, Interval { lower = bl; _ } + when (not (member b au)) && not (member a bl) -> + complement (interval bl au) + | _, _ -> Bot + in + (* APLAS12 mentions "last cases are omitted", does not specify which cases. *) + let extend a b = join a b in + let sorted = + List.sort + (fun a b -> + match (a, b) with + | Interval { lower = al; _ }, Interval { lower = bl; _ } -> + Bitvec.compare al bl + | _, _ -> compare a b) + ints + in + let f1 = + List.fold_right + (fun t acc -> + match t with + | Interval { lower; upper } when Bitvec.ule upper lower -> + extend acc t + | _ -> acc) + sorted Bot + in + let g, f = + List.fold_right + (fun t (g, f) -> (bigger g (gap f t), extend f t)) + sorted (Bot, f1) + in + complement (bigger g (complement f)) let widening a b = match (a, b) with From ba782dc96e678aa2b43338d29c14ac39193fc7fe Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 16 Jan 2026 15:27:36 +1000 Subject: [PATCH 03/36] Intersect, splits and cut --- lib/analysis/wrapping_intervals.ml | 62 +++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 1f54da8..ad464bf 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -3,6 +3,7 @@ open Bincaml_util.Common module WrappingIntervalsLattice = struct let name = "wrappingIntervals" + (* TODO: Store widths for all variants :( *) type t = Top | Interval of { lower : Bitvec.t; upper : Bitvec.t } | Bot [@@deriving eq, show { with_path = false }] @@ -10,10 +11,15 @@ module WrappingIntervalsLattice = struct Bitvec.size_is_equal lower upper; Interval { lower; upper } + let umin width = Bitvec.zero ~size:width + let umax width = Bitvec.ones ~size:width + let smin width = Bitvec.(concat (ones ~size:1) (zero ~size:(width - 1))) + let smax width = Bitvec.(zero_extend ~extension:1 (ones ~size:(width - 1))) + let sp width = interval (umax width) (umin width) + let np width = interval (smax width) (smin width) let bottom = Bot let pretty t = Containers_pp.text (show t) - (* TODO: Rewrite as compare_size to avoid storing widths for Top and Bot *) let cardinality t = match t with | Bot -> Z.of_int 0 @@ -163,6 +169,60 @@ module WrappingIntervalsLattice = struct |> add (of_int ~size:width 1)) (mul al (of_int ~size:width 2)))) else Top + + let intersect a b = + match (a, b) with + | Bot, Bot -> [] + | a, b when equal a b -> [ b ] + | Top, _ -> [ b ] + | _, Top -> [ a ] + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> + let al_mem = member b al in + let au_mem = member b au in + let bl_mem = member a bl in + let bu_mem = member a bu in + let a_in_b = al_mem && au_mem in + let b_in_a = bl_mem && bu_mem in + if a_in_b && b_in_a then [ interval al bu; interval au bl ] + else if a_in_b then [ a ] + else if b_in_a then [ b ] + else if al_mem && (not au_mem) && (not bl_mem) && bu_mem then + [ interval al bu ] + else if (not al_mem) && au_mem && bl_mem && not bu_mem then + [ interval au bl ] + else [] + | _, _ -> [] + + let nsplit t = + match t with + | Bot -> [] + | Top -> + [ + interval (umin width) (smax width); interval (smin width) (umax width); + ] + | Interval { lower; upper } -> + let width = Bitvec.size lower in + let np = np width in + if compare np t <= 0 then + [ interval lower (smax width); interval (smin width) upper ] + else [ t ] + + let ssplit t = + match t with + | Bot -> [] + | Top -> + [ + interval (umin width) (smax width); interval (smin width) (umax width); + ] + | Interval { lower; upper } -> + let width = Bitvec.size lower in + let sp = sp width in + if compare sp t <= 0 then + [ interval lower (umax width); interval (umin width) upper ] + else [ t ] + + let cut t = List.concat_map ssplit (nsplit t) end module WrappingIntervalsValueAbstraction = struct From 2ec8471ee16cb1bd0a5fb898da86ef94450942a1 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Mon, 19 Jan 2026 15:08:26 +1000 Subject: [PATCH 04/36] Add widths and tests --- lib/analysis/wrapping_intervals.ml | 167 +++++++++++++++++----------- test/analysis/wrapping_intervals.ml | 86 ++++++++++++++ 2 files changed, 186 insertions(+), 67 deletions(-) create mode 100644 test/analysis/wrapping_intervals.ml diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index ad464bf..5befe72 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -3,13 +3,25 @@ open Bincaml_util.Common module WrappingIntervalsLattice = struct let name = "wrappingIntervals" - (* TODO: Store widths for all variants :( *) - type t = Top | Interval of { lower : Bitvec.t; upper : Bitvec.t } | Bot + type l = Top | Interval of { lower : Bitvec.t; upper : Bitvec.t } | Bot [@@deriving eq, show { with_path = false }] + type t = { w : int option; v : l } [@@deriving show { with_path = false }] + + let equal s t = equal_l s.v t.v + let interval lower upper = Bitvec.size_is_equal lower upper; - Interval { lower; upper } + { w = Some (Bitvec.size lower); v = Interval { lower; upper } } + + let infer { w = w1; v = a } { w = w2; v = b } = + match (w1, w2) with + | Some w1, Some w2 -> + assert (w1 = w2); + ({ w = Some w1; v = a }, { w = Some w2; v = b }) + | Some w1, None -> ({ w = Some w1; v = a }, { w = Some w1; v = b }) + | None, Some w2 -> ({ w = Some w2; v = a }, { w = Some w2; v = b }) + | None, None -> ({ w = None; v = a }, { w = None; v = b }) let umin width = Bitvec.zero ~size:width let umax width = Bitvec.ones ~size:width @@ -17,32 +29,35 @@ module WrappingIntervalsLattice = struct let smax width = Bitvec.(zero_extend ~extension:1 (ones ~size:(width - 1))) let sp width = interval (umax width) (umin width) let np width = interval (smax width) (smin width) - let bottom = Bot + let top = { w = None; v = Top } + let bottom = { w = None; v = Bot } let pretty t = Containers_pp.text (show t) - let cardinality t = - match t with + let cardinality { w; v } = + match v with | Bot -> Z.of_int 0 - | Top -> Z.pow (Z.of_int 2) 64 + | Top -> ( + match w with + | Some w -> Z.pow (Z.of_int 2) w + | None -> failwith "Cannot determine cardinality for Top without width") | Interval { lower; upper } -> - Bitvec.( - sub upper lower - |> add (of_int ~size:(size lower) 1) - |> to_unsigned_bigint) + Bitvec.(sub upper lower |> to_unsigned_bigint |> Z.add (Z.of_int 1)) - let compare_size a b = + let compare_size s t = + let { v = a; _ } = s in + let { v = b; _ } = t in match (a, b) with - | a, b when equal a b -> 0 + | a, b when equal_l a b -> 0 | Top, _ -> 1 | Bot, _ -> -1 | _, Top -> -1 | _, Bot -> 1 - | Interval _, Interval _ -> Z.compare (cardinality a) (cardinality b) + | Interval _, Interval _ -> Z.compare (cardinality s) (cardinality t) - let complement t = - match t with - | Bot -> Top - | Top -> Bot + let complement { w; v } = + match v with + | Bot -> { w; v = Top } + | Top -> { w; v = Bot } | Interval { lower; upper } -> let new_lower = Bitvec.(add upper (of_int ~size:(size upper) 1)) in let new_upper = Bitvec.(sub lower (of_int ~size:(size lower) 1)) in @@ -54,9 +69,9 @@ module WrappingIntervalsLattice = struct | Top -> true | Interval { lower; upper } -> Bitvec.(ule (sub e lower) (sub upper lower)) - let compare a b = + let compare_l a b = match (a, b) with - | a, b when equal a b -> 0 + | a, b when equal_l a b -> 0 | Top, _ -> 1 | Bot, _ -> -1 | _, Top -> -1 @@ -69,9 +84,14 @@ module WrappingIntervalsLattice = struct then -1 else 1 - let join a b = - if compare a b <= 0 then b - else if compare a b >= 0 then a + let compare s t = compare_l s.v t.v + + let join s t = + let s, t = infer s t in + let { v = a; _ } = s in + let { v = b; _ } = t in + if compare s t <= 0 then t + else if compare t s <= 0 then s else match (a, b) with | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } @@ -80,7 +100,8 @@ module WrappingIntervalsLattice = struct let au_mem = member b au in let bl_mem = member a bl in let bu_mem = member a bu in - if al_mem && au_mem && bl_mem && bu_mem then Top + if al_mem && au_mem && bl_mem && bu_mem then + { w = Some (Bitvec.size al); v = Top } else if au_mem && bl_mem then interval al bu else if al_mem && bu_mem then interval bl au else @@ -97,54 +118,58 @@ module WrappingIntervalsLattice = struct let lub (ints : t list) = let bigger a b = if compare_size a b < 0 then b else a in let gap a b = - match (a, b) with + match (a.v, b.v) with | Interval { upper = au; _ }, Interval { lower = bl; _ } - when (not (member b au)) && not (member a bl) -> + when (not (member b.v au)) && not (member a.v bl) -> complement (interval bl au) - | _, _ -> Bot + | _, _ -> bottom in (* APLAS12 mentions "last cases are omitted", does not specify which cases. *) let extend a b = join a b in let sorted = List.sort - (fun a b -> - match (a, b) with + (fun s t -> + match (s.v, t.v) with | Interval { lower = al; _ }, Interval { lower = bl; _ } -> Bitvec.compare al bl - | _, _ -> compare a b) + | _, _ -> compare_l s.v t.v) ints in let f1 = - List.fold_right - (fun t acc -> - match t with + List.fold_left + (fun acc t -> + match t.v with | Interval { lower; upper } when Bitvec.ule upper lower -> extend acc t | _ -> acc) - sorted Bot + bottom sorted in let g, f = - List.fold_right - (fun t (g, f) -> (bigger g (gap f t), extend f t)) - sorted (Bot, f1) + List.fold_left + (fun (g, f) t -> (bigger g (gap f t), extend f t)) + (bottom, f1) sorted in complement (bigger g (complement f)) - let widening a b = + let widening s t = + let s, t = infer s t in + let { v = a; _ } = s in + let { v = b; _ } = t in match (a, b) with - | a, Bot -> a - | Bot, b -> b - | a, Top -> a - | Top, b -> b + | _, Bot -> s + | Bot, _ -> t + | _, Top -> s + | Top, _ -> t | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> Bitvec.size_is_equal al bl; Bitvec.size_is_equal au bu; let width = Bitvec.size al in - if compare b a <= 0 then a - else if Z.geq (cardinality a) (Z.pow (Z.of_int 2) 64) then Top + if compare t s <= 0 then s + else if Z.geq (cardinality s) (Z.pow (Z.of_int 2) width) then + { w = Some width; v = Top } else - let joined = join a b in + let joined = join s t in if equal joined (interval al bu) then join joined (interval al @@ -160,7 +185,7 @@ module WrappingIntervalsLattice = struct (of_int ~size:width 1)) au) else if member b al && member b au then - join b + join t (interval bl Bitvec.( sub @@ -168,14 +193,19 @@ module WrappingIntervalsLattice = struct |> add (mul au (of_int ~size:width 2)) |> add (of_int ~size:width 1)) (mul al (of_int ~size:width 2)))) - else Top + else { w = Some width; v = Top } - let intersect a b = + let intersect s t = + let s, t = infer s t in + let { v = a; _ } = s in + let { v = b; _ } = t in match (a, b) with | Bot, Bot -> [] - | a, b when equal a b -> [ b ] - | Top, _ -> [ b ] - | _, Top -> [ a ] + | Top, Bot -> [] + | Bot, Top -> [] + | Top, _ -> [ t ] + | _, Top -> [ s ] + | a, b when equal_l a b -> [ t ] | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> let al_mem = member b al in @@ -185,8 +215,8 @@ module WrappingIntervalsLattice = struct let a_in_b = al_mem && au_mem in let b_in_a = bl_mem && bu_mem in if a_in_b && b_in_a then [ interval al bu; interval au bl ] - else if a_in_b then [ a ] - else if b_in_a then [ b ] + else if a_in_b then [ s ] + else if b_in_a then [ t ] else if al_mem && (not au_mem) && (not bl_mem) && bu_mem then [ interval al bu ] else if (not al_mem) && au_mem && bl_mem && not bu_mem then @@ -195,12 +225,12 @@ module WrappingIntervalsLattice = struct | _, _ -> [] let nsplit t = - match t with + match t.v with | Bot -> [] - | Top -> - [ - interval (umin width) (smax width); interval (smin width) (umax width); - ] + | Top -> ( + match t.w with + | Some w -> [ interval (umin w) (smax w); interval (smin w) (umax w) ] + | None -> failwith "Cannot determine nsplit for Top without width") | Interval { lower; upper } -> let width = Bitvec.size lower in let np = np width in @@ -209,12 +239,12 @@ module WrappingIntervalsLattice = struct else [ t ] let ssplit t = - match t with + match t.v with | Bot -> [] - | Top -> - [ - interval (umin width) (smax width); interval (smin width) (umax width); - ] + | Top -> ( + match t.w with + | Some w -> [ interval (umin w) (smax w); interval (smin w) (umax w) ] + | None -> failwith "Cannot determine nsplit for Top without width") | Interval { lower; upper } -> let width = Bitvec.size lower in let sp = sp width in @@ -228,10 +258,10 @@ end module WrappingIntervalsValueAbstraction = struct include WrappingIntervalsLattice - let eval_const op = Top - let eval_unop op a = Top - let eval_binop op a b = Top - let eval_intrin op args = Top + let eval_const op = top + let eval_unop op a = top + let eval_binop op a b = top + let eval_intrin op args = top end module StateAbstraction = Intra_analysis.MapState (WrappingIntervalsLattice) @@ -240,3 +270,6 @@ module WrappingIntervalsValueAbstractionBasil = struct include WrappingIntervalsValueAbstraction module E = Lang.Expr.BasilExpr end + +include + Dataflow_graph.EasyForwardAnalysisPack (WrappingIntervalsValueAbstractionBasil) diff --git a/test/analysis/wrapping_intervals.ml b/test/analysis/wrapping_intervals.ml new file mode 100644 index 0000000..8c1f01b --- /dev/null +++ b/test/analysis/wrapping_intervals.ml @@ -0,0 +1,86 @@ +open Bincaml_util.Common +open Analysis.Wrapping_intervals.WrappingIntervalsLattice + +let dbg x = + print_endline (show x); + x + +let%test_unit "cardinality" = + let ( = ) a b = Z.equal a (Z.of_int b) in + let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in + assert (cardinality bottom = 0); + assert (cardinality { w = Some 4; v = Top } = 16); + assert (cardinality (iv 0 15) = 16); + assert (cardinality (iv 3 12) = 10); + assert (cardinality (iv 15 0) = 2); + assert (cardinality (iv 13 13) = 1) + +let%test_unit "member" = + let iv a b = + Interval + { lower = Bitvec.of_int ~size:4 a; upper = Bitvec.of_int ~size:4 b } + in + let member t e = member t (Bitvec.of_int ~size:4 e) in + assert (member Top 0); + assert (not (member Bot 0)); + assert (member (iv 2 6) 4); + assert (member (iv 2 6) 6); + assert (member (iv 2 6) 2); + assert (not (member (iv 2 6) 7)); + assert (not (member (iv 6 2) 4)); + assert (member (iv 6 2) 6); + assert (member (iv 6 2) 2); + assert (member (iv 6 2) 7) + +let%test_unit "partial_order" = + let ( <= ) a b = compare a b <= 0 in + let ( > ) a b = compare a b > 0 in + let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in + assert (top <= top); + assert (bottom <= top); + assert (bottom <= bottom); + (* Fig 2.a *) + assert (iv 0 8 <= iv 0 9); + assert (iv 3 5 <= iv 0 8); + (* Fig 2.b *) + assert (iv 0 9 > iv 8 1); + assert (iv 8 1 > iv 0 9); + (* Fig 2.c *) + assert (iv 0 9 > iv 4 10); + assert (iv 4 10 > iv 0 9); + (* Fig 2.d *) + assert (iv 0 4 > iv 5 6); + assert (iv 5 6 > iv 0 4) + +let%test_unit "join" = + let ( = ) = equal in + let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in + assert (join bottom top = top); + assert (join (iv 1 4) bottom = iv 1 4); + (* Fig 2.a *) + assert (join (iv 1 4) (iv 0 5) = iv 0 5); + (* Fig 2.b *) + assert (join (iv 0 9) (iv 8 1) = top); + assert (join (iv 6 5) (iv 3 0) = top); + (* Fig 2.c *) + assert (join (iv 0 9) (iv 4 10) = iv 0 10); + (* Fig 2.d *) + assert (join (iv 0 4) (iv 5 6) = iv 0 6) + +let%test_unit "lub" = + let ( = ) = equal in + let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in + assert (lub [ bottom; top; bottom ] = top); + assert (lub [ bottom; iv 0 9; top ] = top); + assert (lub [ iv 0 3; iv 3 5; iv 4 6 ] = iv 0 6); + assert (lub [ iv 0 3; iv 6 10; iv 14 15 ] = iv 14 10) + +let%test_unit "intersect" = + let ( = ) = List.equal equal in + let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in + assert (List.is_empty (intersect bottom bottom)); + assert (List.is_empty (intersect top bottom)); + assert (intersect top (iv 1 2) = [ iv 1 2 ]); + assert (intersect (iv 0 4) (iv 2 1) = [ iv 0 1; iv 4 2 ]); + assert (intersect (iv 0 8) (iv 3 6) = [ iv 3 6 ]); + assert (intersect (iv 3 7) (iv 6 11) = [ iv 7 6 ]) From 59835ce814b817fff727ac708a4669f51fef199a Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Tue, 20 Jan 2026 15:32:18 +1000 Subject: [PATCH 05/36] Constants, negation, not, addition and subtraction --- lib/analysis/wrapping_intervals.ml | 100 +++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 5befe72..60689ba 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -43,16 +43,7 @@ module WrappingIntervalsLattice = struct | Interval { lower; upper } -> Bitvec.(sub upper lower |> to_unsigned_bigint |> Z.add (Z.of_int 1)) - let compare_size s t = - let { v = a; _ } = s in - let { v = b; _ } = t in - match (a, b) with - | a, b when equal_l a b -> 0 - | Top, _ -> 1 - | Bot, _ -> -1 - | _, Top -> -1 - | _, Bot -> 1 - | Interval _, Interval _ -> Z.compare (cardinality s) (cardinality t) + let compare_size s t = Z.compare (cardinality s) (cardinality t) let complement { w; v } = match v with @@ -255,13 +246,94 @@ module WrappingIntervalsLattice = struct let cut t = List.concat_map ssplit (nsplit t) end +module WrappingIntervalsLatticeOps = struct + include WrappingIntervalsLattice + + (* + TODO: + - Unary: + - [x] Negate + - [x] Not + - [ ] Sign extend + - [ ] Zero extend + - [ ] Extract + - Binary: + - [x] Addition + - [x] Subtraction + - [ ] Multiplication + - [ ] Bitwise Or/And/Xor + - [ ] Left Shift + - [ ] Logical Right Shift + - [ ] Arithmetic Right Shift + - [ ] (Un)signed Div and Modulo (see Crab for impl, paper does not provide) + - + *) + + let bind1 f t = + { + w = t.w; + v = + (match t.v with + | Bot -> Bot + | Top -> Top + | Interval { lower; upper } -> f lower upper); + } + + let neg = + bind1 (fun l u -> Interval { lower = Bitvec.neg u; upper = Bitvec.neg l }) + + let bitnot = + bind1 (fun l u -> + Interval { lower = Bitvec.bitnot u; upper = Bitvec.bitnot l }) + + let add s t = + let top = infer s top |> snd in + match (s.v, t.v) with + | Bot, Bot -> s + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> + if Z.(leq (add (cardinality s) (cardinality t)) (cardinality top)) then + interval (Bitvec.add al bl) (Bitvec.add au bu) + else top + | _, _ -> top + + let sub s t = + let top = infer s top |> snd in + match (s.v, t.v) with + | Bot, Bot -> s + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> + if Z.(leq (add (cardinality s) (cardinality t)) (cardinality top)) then + interval (Bitvec.sub al bu) (Bitvec.sub au bl) + else top + | _, _ -> top +end + module WrappingIntervalsValueAbstraction = struct include WrappingIntervalsLattice + include WrappingIntervalsLatticeOps + + let eval_const (op : Lang.Ops.AllOps.const) = + match op with + | `Bool _ -> { w = Some 1; v = Top } + | `Integer _ -> top + | `Bitvector bv -> + if Bitvec.size bv = 0 then { w = Some 0; v = Top } else interval bv bv + + let eval_unop (op : Lang.Ops.AllOps.unary) a = + match op with + | `BVNEG -> neg a + | `BVNOT -> bitnot a + | _ -> infer a top |> snd + + let eval_binop (op : Lang.Ops.AllOps.binary) a b = + let a, b = infer a b in + match op with + | `BVADD -> add a b + | `BVSUB -> sub a b + | _ -> infer a top |> snd - let eval_const op = top - let eval_unop op a = top - let eval_binop op a b = top - let eval_intrin op args = top + let eval_intrin (op : Lang.Ops.AllOps.intrin) args = top end module StateAbstraction = Intra_analysis.MapState (WrappingIntervalsLattice) From 0e187fe1c2bf9a3b1d4c7d0095679c1f58b0f67f Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Tue, 20 Jan 2026 15:38:06 +1000 Subject: [PATCH 06/36] Whoops! We prop tested too hard --- lib/analysis/wrapping_intervals.ml | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 60689ba..6614042 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -17,6 +17,7 @@ module WrappingIntervalsLattice = struct let infer { w = w1; v = a } { w = w2; v = b } = match (w1, w2) with | Some w1, Some w2 -> + (* Disable assertion when running `dune test` or the soundness check crashes *) assert (w1 = w2); ({ w = Some w1; v = a }, { w = Some w2; v = b }) | Some w1, None -> ({ w = Some w1; v = a }, { w = Some w1; v = b }) From fb26496a23bc0222632dfaf93828d58ca04c5a5b Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 21 Jan 2026 13:34:35 +1000 Subject: [PATCH 07/36] Multiplication and fixing intersect --- lib/analysis/wrapping_intervals.ml | 98 +++++++++++++++++++++++++---- test/analysis/wrapping_intervals.ml | 15 ++++- 2 files changed, 98 insertions(+), 15 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 6614042..379c78a 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -12,7 +12,13 @@ module WrappingIntervalsLattice = struct let interval lower upper = Bitvec.size_is_equal lower upper; - { w = Some (Bitvec.size lower); v = Interval { lower; upper } } + { + w = Some (Bitvec.size lower); + v = + (if Bitvec.(equal lower (add upper (of_int ~size:(size upper) 1))) then + Top + else Interval { lower; upper }); + } let infer { w = w1; v = a } { w = w2; v = b } = match (w1, w2) with @@ -59,7 +65,10 @@ module WrappingIntervalsLattice = struct match t with | Bot -> false | Top -> true - | Interval { lower; upper } -> Bitvec.(ule (sub e lower) (sub upper lower)) + | Interval { lower; upper } -> + Bitvec.size_is_equal lower e; + Bitvec.size_is_equal upper e; + Bitvec.(ule (sub e lower) (sub upper lower)) let compare_l a b = match (a, b) with @@ -192,12 +201,11 @@ module WrappingIntervalsLattice = struct let { v = a; _ } = s in let { v = b; _ } = t in match (a, b) with - | Bot, Bot -> [] - | Top, Bot -> [] - | Bot, Top -> [] + | Bot, _ -> [] + | _, Bot -> [] | Top, _ -> [ t ] - | _, Top -> [ s ] | a, b when equal_l a b -> [ t ] + | _, Top -> [ s ] | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> let al_mem = member b al in @@ -206,15 +214,14 @@ module WrappingIntervalsLattice = struct let bu_mem = member a bu in let a_in_b = al_mem && au_mem in let b_in_a = bl_mem && bu_mem in - if a_in_b && b_in_a then [ interval al bu; interval au bl ] + if a_in_b && b_in_a then [ interval al bu; interval bl au ] else if a_in_b then [ s ] else if b_in_a then [ t ] else if al_mem && (not au_mem) && (not bl_mem) && bu_mem then [ interval al bu ] else if (not al_mem) && au_mem && bl_mem && not bu_mem then - [ interval au bl ] + [ interval bl au ] else [] - | _, _ -> [] let nsplit t = match t.v with @@ -261,7 +268,7 @@ module WrappingIntervalsLatticeOps = struct - Binary: - [x] Addition - [x] Subtraction - - [ ] Multiplication + - [x] Multiplication - [ ] Bitwise Or/And/Xor - [ ] Left Shift - [ ] Logical Right Shift @@ -290,7 +297,8 @@ module WrappingIntervalsLatticeOps = struct let add s t = let top = infer s top |> snd in match (s.v, t.v) with - | Bot, Bot -> s + | Bot, _ -> s + | _, Bot -> t | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> if Z.(leq (add (cardinality s) (cardinality t)) (cardinality top)) then @@ -301,13 +309,78 @@ module WrappingIntervalsLatticeOps = struct let sub s t = let top = infer s top |> snd in match (s.v, t.v) with - | Bot, Bot -> s + | Bot, _ -> s + | _, Bot -> t | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> if Z.(leq (add (cardinality s) (cardinality t)) (cardinality top)) then interval (Bitvec.sub al bu) (Bitvec.sub au bl) else top | _, _ -> top + + let mul s t = + let rusmul s t = + let top = infer s top |> snd in + let w = + match s.w with + | Some w -> w + | None -> failwith "Cannot multiply without known width" + in + let umul = + match (s.v, t.v) with + | ( Interval { lower = al; upper = au }, + Interval { lower = bl; upper = bu } ) -> + let cond = + let al = Bitvec.to_unsigned_bigint al in + let au = Bitvec.to_unsigned_bigint au in + let bl = Bitvec.to_unsigned_bigint bl in + let bu = Bitvec.to_unsigned_bigint bu in + Z.(lt (sub (mul au bu) (mul al bl)) (pow (of_int 2) w)) + in + if cond then interval (Bitvec.mul al bl) (Bitvec.mul au bu) else top + | _, _ -> top + in + let smul = + match (s.v, t.v) with + | ( Interval { lower = al; upper = au }, + Interval { lower = bl; upper = bu } ) -> + let msb_hi b = + Bitvec.(equal (extract ~hi:w ~lo:(w - 1) b) (ones ~size:1)) + in + let cond (a, b) (c, d) = + let a = Bitvec.to_unsigned_bigint a in + let b = Bitvec.to_unsigned_bigint b in + let c = Bitvec.to_unsigned_bigint c in + let d = Bitvec.to_unsigned_bigint d in + Z.(lt (sub (mul a b) (mul c d)) (pow (of_int 2) w)) + in + if + msb_hi al && msb_hi au && msb_hi bl && msb_hi bu + && cond (au, bu) (al, bl) + then interval (Bitvec.mul al bl) (Bitvec.mul au bu) + else if + msb_hi al && msb_hi au + && (not (msb_hi bl)) + && (not (msb_hi bu)) + && cond (au, bl) (al, bu) + then interval (Bitvec.mul al bu) (Bitvec.mul au bl) + else if + (not (msb_hi al)) + && (not (msb_hi au)) + && msb_hi bl && msb_hi bu + && cond (al, bu) (au, bl) + then interval (Bitvec.mul au bl) (Bitvec.mul al bu) + else top + | _, _ -> top + in + intersect umul smul + in + let prod = + List.concat_map + (fun a -> List.concat_map (fun b -> rusmul a b) (cut t)) + (cut s) + in + lub prod end module WrappingIntervalsValueAbstraction = struct @@ -332,6 +405,7 @@ module WrappingIntervalsValueAbstraction = struct match op with | `BVADD -> add a b | `BVSUB -> sub a b + | `BVMUL -> mul a b | _ -> infer a top |> snd let eval_intrin (op : Lang.Ops.AllOps.intrin) args = top diff --git a/test/analysis/wrapping_intervals.ml b/test/analysis/wrapping_intervals.ml index 8c1f01b..10da808 100644 --- a/test/analysis/wrapping_intervals.ml +++ b/test/analysis/wrapping_intervals.ml @@ -1,10 +1,13 @@ open Bincaml_util.Common -open Analysis.Wrapping_intervals.WrappingIntervalsLattice +open Analysis.Wrapping_intervals +open WrappingIntervalsLattice let dbg x = print_endline (show x); x +let dbg_list = List.map dbg + let%test_unit "cardinality" = let ( = ) a b = Z.equal a (Z.of_int b) in let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in @@ -81,6 +84,12 @@ let%test_unit "intersect" = assert (List.is_empty (intersect bottom bottom)); assert (List.is_empty (intersect top bottom)); assert (intersect top (iv 1 2) = [ iv 1 2 ]); - assert (intersect (iv 0 4) (iv 2 1) = [ iv 0 1; iv 4 2 ]); + assert (List.mem (iv 0 1) (intersect (iv 0 4) (iv 3 1))); + assert (List.mem (iv 3 4) (intersect (iv 0 4) (iv 3 1))); assert (intersect (iv 0 8) (iv 3 6) = [ iv 3 6 ]); - assert (intersect (iv 3 7) (iv 6 11) = [ iv 7 6 ]) + assert (intersect (iv 3 7) (iv 6 11) = [ iv 6 7 ]) + +let%test_unit "mul" = + let ( = ) = equal in + let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in + assert (WrappingIntervalsValueAbstraction.mul (iv 15 9) (iv 0 1) = iv 15 9) From 6058a30b79f6eaca208e4874fbefdbfd2cd15961 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 21 Jan 2026 15:19:18 +1000 Subject: [PATCH 08/36] Un/signed Division --- lib/analysis/wrapping_intervals.ml | 90 ++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 379c78a..84f7775 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -273,7 +273,7 @@ module WrappingIntervalsLatticeOps = struct - [ ] Left Shift - [ ] Logical Right Shift - [ ] Arithmetic Right Shift - - [ ] (Un)signed Div and Modulo (see Crab for impl, paper does not provide) + - [x] (Un)signed Div (see Crab for impl, paper does not provide) - *) @@ -375,12 +375,88 @@ module WrappingIntervalsLatticeOps = struct in intersect umul smul in - let prod = - List.concat_map - (fun a -> List.concat_map (fun b -> rusmul a b) (cut t)) - (cut s) + lub + (List.concat_map + (fun a -> List.concat_map (fun b -> rusmul a b) (cut t)) + (cut s)) + + (* Division implementation derived from Crab *) + let trim_zeroes t = + let w = + match t.w with + | Some w -> w + | None -> failwith "Cannot trim zeroes without known width" in - lub prod + let zero = Bitvec.zero ~size:w in + match t.v with + | Bot -> [] + | Top -> [ interval (Bitvec.of_int ~size:w 1) (umax w) ] + | Interval { lower; upper } -> + if Bitvec.equal lower zero && Bitvec.equal upper zero then [] + else if Bitvec.equal lower zero then + [ interval (Bitvec.of_int ~size:w 1) upper ] + else if Bitvec.equal upper zero then [ interval lower (umax w) ] + else if member t.v zero then + [ interval lower (umax w); interval (Bitvec.of_int ~size:w 1) upper ] + else [ t ] + + let udiv s t = + let divide s t = + let top = infer s top |> snd in + match (s.v, t.v) with + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> + interval (Bitvec.udiv al bu) (Bitvec.sdiv au bl) + | _, _ -> top + in + lub + (List.concat_map + (fun a -> + List.concat_map + (fun bs -> List.map (fun b -> divide a b) (trim_zeroes bs)) + (nsplit t)) + (nsplit s)) + + let sdiv s t = + let divide s t = + let top = infer s top |> snd in + + match (s.v, t.v) with + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> ( + let w = + match s.w with + | Some w -> w + | None -> failwith "Cannot signed divide without known width" + in + let msb_hi b = + Bitvec.(equal (extract ~hi:w ~lo:(w - 1) b) (ones ~size:1)) + in + let ( = ) = Bitvec.equal in + let smin, neg1 = (smin w, umax w) in + match (msb_hi al, msb_hi bl) with + | true, true + when not ((au = smin && bl = neg1) || (al = smin && bu = neg1)) -> + interval (Bitvec.sdiv au bl) (Bitvec.sdiv al bu) + | false, false + when not ((al = smin && bu = neg1) || (au = smin && bl = neg1)) -> + interval (Bitvec.sdiv al bu) (Bitvec.sdiv au bl) + | true, false + when not ((al = smin && bl = neg1) || (au = smin && bu = neg1)) -> + interval (Bitvec.sdiv al bl) (Bitvec.sdiv au bu) + | false, true + when not ((au = smin && bu = neg1) && al = smin && bl = neg1) -> + interval (Bitvec.sdiv au bu) (Bitvec.sdiv al bl) + | _, _ -> top) + | _, _ -> top + in + lub + (List.concat_map + (fun a -> + List.concat_map + (fun bs -> List.map (fun b -> divide a b) (trim_zeroes bs)) + (cut t)) + (cut s)) end module WrappingIntervalsValueAbstraction = struct @@ -406,6 +482,8 @@ module WrappingIntervalsValueAbstraction = struct | `BVADD -> add a b | `BVSUB -> sub a b | `BVMUL -> mul a b + | `BVUDIV -> udiv a b + | `BVSDIV -> sdiv a b | _ -> infer a top |> snd let eval_intrin (op : Lang.Ops.AllOps.intrin) args = top From 9ae77cec2656923a793c54a708ec8c671af5954e Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Thu, 22 Jan 2026 14:32:53 +1000 Subject: [PATCH 09/36] Bitwise Or/And/Xor --- lib/analysis/wrapping_intervals.ml | 135 ++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 84f7775..f894916 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -269,7 +269,7 @@ module WrappingIntervalsLatticeOps = struct - [x] Addition - [x] Subtraction - [x] Multiplication - - [ ] Bitwise Or/And/Xor + - [x] Bitwise Or/And/Xor - [ ] Left Shift - [ ] Logical Right Shift - [ ] Arithmetic Right Shift @@ -457,6 +457,136 @@ module WrappingIntervalsLatticeOps = struct (fun bs -> List.map (fun b -> divide a b) (trim_zeroes bs)) (cut t)) (cut s)) + + let bitlogop min max s t = + let pre s t = + match (s.v, t.v) with + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> + interval (min (al, au) (bl, bu)) (max (al, au) (bl, bu)) + | _, _ -> infer s top |> snd + in + lub + (List.concat_map + (fun a -> List.map (fun b -> pre a b) (ssplit t)) + (ssplit s)) + + let bitor = + let min_or (al, au) (bl, bu) = + let rec min_or_aux m = + (* Lazy evaluation trick *) + let recurse _ = min_or_aux Bitvec.(lshr m (of_int ~size:(size m) 1)) in + + if Bitvec.is_zero m then Bitvec.bitor al bl + else if Bitvec.(is_nonzero (bitand (bitnot al) bl |> bitand m)) then + let temp = Bitvec.(bitor al m |> bitand (neg m)) in + if Bitvec.ule temp au then Bitvec.bitor temp bl else recurse () + else if Bitvec.(is_nonzero (bitand al (bitnot bl) |> bitand m)) then + let temp = Bitvec.(bitor bl m |> bitand (neg m)) in + if Bitvec.ule temp bu then Bitvec.bitor al temp else recurse () + else recurse () + in + let init = smin (Bitvec.size al) in + min_or_aux init + in + + let max_or (al, au) (bl, bu) = + let rec max_or_aux m = + let one = Bitvec.(of_int ~size:(size m) 1) in + let recurse _ = max_or_aux Bitvec.(lshr m one) in + + if Bitvec.is_zero m then Bitvec.bitor au bu + else if Bitvec.(is_nonzero (bitand au bu |> bitand m)) then + let tempau = Bitvec.(bitor (sub au m) (sub m one)) in + let tempbu = Bitvec.(bitor (sub bu m) (sub m one)) in + if Bitvec.uge tempau al then Bitvec.bitor tempau bu + else if Bitvec.uge tempbu bl then Bitvec.bitor au tempbu + else recurse () + else recurse () + in + let init = smin (Bitvec.size al) in + max_or_aux init + in + bitlogop min_or max_or + + let bitand = + let min_and (al, au) (bl, bu) = + let rec min_and_aux m = + let recurse _ = min_and_aux Bitvec.(lshr m (of_int ~size:(size m) 1)) in + + if Bitvec.is_zero m then Bitvec.bitand al bl + else if Bitvec.(is_nonzero (bitand (bitnot al) (bitnot bl) |> bitand m)) + then + let tempal = Bitvec.(bitand (bitor al m) (neg m)) in + let tempbl = Bitvec.(bitand (bitor bl m) (neg m)) in + if Bitvec.uge tempal au then Bitvec.bitand tempal bl + else if Bitvec.uge tempbl bu then Bitvec.bitand al tempbl + else recurse () + else recurse () + in + let init = smin (Bitvec.size al) in + min_and_aux init + in + + let max_and (al, au) (bl, bu) = + let rec max_and_aux m = + let one = Bitvec.(of_int ~size:(size m) 1) in + let recurse _ = max_and_aux Bitvec.(lshr m (of_int ~size:(size m) 1)) in + if Bitvec.is_zero m then Bitvec.bitand au bu + else if Bitvec.(is_nonzero (bitand au (bitnot bl) |> bitand m)) then + let temp = Bitvec.(bitor (bitand au (bitnot m)) (sub m one)) in + if Bitvec.uge temp al then Bitvec.bitand temp bu else recurse () + else if Bitvec.(is_nonzero (bitand (bitnot au) bu |> bitand m)) then + let temp = Bitvec.(bitor (bitand bu (bitnot m)) (sub m one)) in + if Bitvec.uge temp bl then Bitvec.bitand au temp else recurse () + else recurse () + in + let init = smin (Bitvec.size al) in + max_and_aux init + in + bitlogop min_and max_and + + let bitxor = + let min_xor (al, au) (bl, bu) = + let rec min_xor_aux m (al, au) (bl, bu) = + let recurse = min_xor_aux Bitvec.(lshr m (of_int ~size:(size m) 1)) in + + if Bitvec.is_zero m then Bitvec.bitxor al bl + else if Bitvec.(is_nonzero (bitand (bitnot al) bl |> bitand m)) then + let temp = Bitvec.(bitor al m |> bitand (neg m)) in + let al = if Bitvec.ule temp au then Bitvec.bitor temp bl else al in + recurse (al, au) (bl, bu) + else if Bitvec.(is_nonzero (bitand al (bitnot bl) |> bitand m)) then + let temp = Bitvec.(bitor bl m |> bitand (neg m)) in + let bl = if Bitvec.ule temp bu then Bitvec.bitor al temp else bl in + recurse (al, au) (bl, bu) + else recurse (al, au) (bl, bu) + in + let init = smin (Bitvec.size al) in + min_xor_aux init (al, au) (bl, bu) + in + + let max_xor (al, au) (bl, bu) = + let rec max_xor_aux m (al, au) (bl, bu) = + let one = Bitvec.(of_int ~size:(size m) 1) in + let recurse = max_xor_aux Bitvec.(lshr m one) in + + if Bitvec.is_zero m then Bitvec.bitxor au bu + else if Bitvec.(is_nonzero (bitand au bu |> bitand m)) then + let tempau = Bitvec.(bitor (sub au m) (sub m one)) in + let tempbu = Bitvec.(bitor (sub bu m) (sub m one)) in + let au, bu = + if Bitvec.uge tempau al then (tempau, bu) + else if Bitvec.uge tempbu bl then (au, tempbu) + else (au, bu) + in + recurse (al, au) (bl, bu) + else recurse (al, au) (bl, bu) + in + let init = smin (Bitvec.size al) in + max_xor_aux init (al, au) (bl, bu) + in + bitlogop min_xor max_xor end module WrappingIntervalsValueAbstraction = struct @@ -484,6 +614,9 @@ module WrappingIntervalsValueAbstraction = struct | `BVMUL -> mul a b | `BVUDIV -> udiv a b | `BVSDIV -> sdiv a b + | `BVOR -> bitxor a b + | `BVAND -> bitand a b + | `BVXOR -> bitxor a b | _ -> infer a top |> snd let eval_intrin (op : Lang.Ops.AllOps.intrin) args = top From 8b7471224fd3328f818d267f33297fcbf5239adf Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Thu, 22 Jan 2026 15:11:45 +1000 Subject: [PATCH 10/36] Zero/Sign extend --- lib/analysis/wrapping_intervals.ml | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index f894916..ceaf9a1 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -262,8 +262,8 @@ module WrappingIntervalsLatticeOps = struct - Unary: - [x] Negate - [x] Not - - [ ] Sign extend - - [ ] Zero extend + - [x] Sign extend + - [x] Zero extend - [ ] Extract - Binary: - [x] Addition @@ -294,6 +294,32 @@ module WrappingIntervalsLatticeOps = struct bind1 (fun l u -> Interval { lower = Bitvec.bitnot u; upper = Bitvec.bitnot l }) + let sign_extend t k = + List.filter_map + (fun t -> + match t.v with + | Interval { lower; upper } -> + Some + (interval + (Bitvec.sign_extend ~extension:k lower) + (Bitvec.sign_extend ~extension:k upper)) + | _ -> None) + (nsplit t) + |> lub + + let zero_extend t k = + List.filter_map + (fun t -> + match t.v with + | Interval { lower; upper } -> + Some + (interval + (Bitvec.zero_extend ~extension:k lower) + (Bitvec.zero_extend ~extension:k upper)) + | _ -> None) + (ssplit t) + |> lub + let add s t = let top = infer s top |> snd in match (s.v, t.v) with @@ -604,6 +630,8 @@ module WrappingIntervalsValueAbstraction = struct match op with | `BVNEG -> neg a | `BVNOT -> bitnot a + | `ZeroExtend k -> zero_extend a k + | `SignExtend k -> sign_extend a k | _ -> infer a top |> snd let eval_binop (op : Lang.Ops.AllOps.binary) a b = From 6f4eac9df2abe20b8b467fe87e44698463652f7e Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 23 Jan 2026 10:54:20 +1000 Subject: [PATCH 11/36] Shifts --- lib/analysis/wrapping_intervals.ml | 132 +++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 5 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index ceaf9a1..89b55af 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -50,6 +50,12 @@ module WrappingIntervalsLattice = struct | Interval { lower; upper } -> Bitvec.(sub upper lower |> to_unsigned_bigint |> Z.add (Z.of_int 1)) + let is_singleton { w; v } = + match v with + | Bot | Top -> None + | Interval { lower; upper } -> + if Bitvec.equal lower upper then Some lower else None + let compare_size s t = Z.compare (cardinality s) (cardinality t) let complement { w; v } = @@ -264,17 +270,16 @@ module WrappingIntervalsLatticeOps = struct - [x] Not - [x] Sign extend - [x] Zero extend - - [ ] Extract + - [x] Extract - Binary: - [x] Addition - [x] Subtraction - [x] Multiplication - [x] Bitwise Or/And/Xor - - [ ] Left Shift - - [ ] Logical Right Shift - - [ ] Arithmetic Right Shift + - [x] Left Shift + - [x] Logical Right Shift + - [x] Arithmetic Right Shift - [x] (Un)signed Div (see Crab for impl, paper does not provide) - - *) let bind1 f t = @@ -613,6 +618,119 @@ module WrappingIntervalsLatticeOps = struct max_xor_aux init (al, au) (bl, bu) in bitlogop min_xor max_xor + + let truncate t k = + match t.v with + | Bot -> { w = Some k; v = Bot } + | Top -> { w = Some k; v = Top } + | Interval { lower; upper } -> + let w = + match t.w with + | Some w -> w + | None -> failwith "Cannot truncate without known width" + in + let truncl = Bitvec.extract ~hi:k ~lo:0 lower in + let truncu = Bitvec.extract ~hi:k ~lo:0 upper in + let shiftl = Bitvec.(ashr lower (of_int ~size:w k)) in + let shiftu = Bitvec.(ashr upper (of_int ~size:w k)) in + if + Bitvec.( + (equal shiftl shiftu && ule truncl truncu) + || equal (add shiftl (of_int ~size:w 1)) shiftu + && ugt truncl truncu) + then interval truncl truncu + else { w = Some k; v = Top } + + let shl t k = + let shl_const t k = + if equal_l t.v Bot then t + else + let w = + match t.w with + | Some w -> w + | None -> failwith "Cannot shift left without known width" + in + match (truncate t (w - k)).v with + | Interval { lower; upper } -> + let lower = Bitvec.zero_extend ~extension:(w - k) lower in + let upper = Bitvec.zero_extend ~extension:(w - k) upper in + interval + Bitvec.(shl lower (of_int ~size:w k)) + Bitvec.(shl upper (of_int ~size:w k)) + | _ -> + interval (Bitvec.zero ~size:w) + Bitvec.(concat (ones ~size:(w - k)) (zero ~size:k)) + in + match is_singleton k with + | Some k -> shl_const t (Bitvec.to_unsigned_bigint k |> Z.to_int) + | None -> { w = t.w; v = Top } + + let lshr t k = + let lshr_const t k = + if equal_l t.v Bot then t + else + let w = + match t.w with + | Some w -> w + | None -> failwith "Cannot logical shift right without known width" + in + let fallback = + interval (Bitvec.zero ~size:w) + Bitvec.(concat (zero ~size:k) (ones ~size:(w - k))) + in + if compare (sp w) t <= 0 then fallback + else + match t.v with + | Interval { lower; upper } -> + interval + Bitvec.(lshr lower (of_int ~size:w k)) + Bitvec.(lshr upper (of_int ~size:w k)) + | _ -> fallback + in + match is_singleton k with + | Some k -> lshr_const t (Bitvec.to_unsigned_bigint k |> Z.to_int) + | None -> { w = t.w; v = Top } + + let ashr t k = + let ashr_const t k = + if equal_l t.v Bot then t + else + let w = + match t.w with + | Some w -> w + | None -> failwith "Cannot arithmetic shift right without known width" + in + let fallback = + interval + Bitvec.(concat (ones ~size:k) (zero ~size:(w - k))) + Bitvec.(concat (zero ~size:k) (ones ~size:(w - k))) + in + if compare (np w) t <= 0 then fallback + else + match t.v with + | Interval { lower; upper } -> + interval + Bitvec.(ashr lower (of_int ~size:w k)) + Bitvec.(ashr upper (of_int ~size:w k)) + | _ -> fallback + in + match is_singleton k with + | Some k -> ashr_const t (Bitvec.to_unsigned_bigint k |> Z.to_int) + | None -> { w = t.w; v = Top } + + let extract ~hi ~lo t = + assert (0 <= lo); + assert (lo <= hi); + if hi = lo then { w = Some 0; v = Bot } + else + let w = + match t.w with + | Some w -> w + | None -> failwith "Cannot extract without known width" + in + let k = Bitvec.of_int ~size:w lo in + assert (hi <= w); + truncate (lshr t (interval k k)) (hi - lo) end module WrappingIntervalsValueAbstraction = struct @@ -632,6 +750,7 @@ module WrappingIntervalsValueAbstraction = struct | `BVNOT -> bitnot a | `ZeroExtend k -> zero_extend a k | `SignExtend k -> sign_extend a k + | `Extract (hi, lo) -> extract ~hi ~lo a | _ -> infer a top |> snd let eval_binop (op : Lang.Ops.AllOps.binary) a b = @@ -645,6 +764,9 @@ module WrappingIntervalsValueAbstraction = struct | `BVOR -> bitxor a b | `BVAND -> bitand a b | `BVXOR -> bitxor a b + | `BVASHR -> ashr a b + | `BVLSHR -> lshr a b + | `BVSHL -> shl a b | _ -> infer a top |> snd let eval_intrin (op : Lang.Ops.AllOps.intrin) args = top From a0b95ceee01480d6f3677fff5a8a3a57eaf75b77 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 23 Jan 2026 11:39:28 +1000 Subject: [PATCH 12/36] Multi argument ops --- lib/analysis/wrapping_intervals.ml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 89b55af..a2a80bc 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -275,11 +275,18 @@ module WrappingIntervalsLatticeOps = struct - [x] Addition - [x] Subtraction - [x] Multiplication - - [x] Bitwise Or/And/Xor + - [x] Bitwise Or/And/Xor/Nand - [x] Left Shift - [x] Logical Right Shift - [x] Arithmetic Right Shift - [x] (Un)signed Div (see Crab for impl, paper does not provide) + - [ ] Comparisons + - Intrinsics + - [x] Addition + - [x] BitOr + - [x] BitXor + - [x] BitAnd + - [ ] Concat *) let bind1 f t = @@ -763,13 +770,21 @@ module WrappingIntervalsValueAbstraction = struct | `BVSDIV -> sdiv a b | `BVOR -> bitxor a b | `BVAND -> bitand a b + | `BVNAND -> bitand a b |> bitnot | `BVXOR -> bitxor a b | `BVASHR -> ashr a b | `BVLSHR -> lshr a b | `BVSHL -> shl a b | _ -> infer a top |> snd - let eval_intrin (op : Lang.Ops.AllOps.intrin) args = top + let eval_intrin (op : Lang.Ops.AllOps.intrin) args = + let fold op = List.fold_left (eval_binop op) bottom args in + match op with + | `BVADD -> fold `BVADD + | `BVOR -> fold `BVOR + | `BVXOR -> fold `BVXOR + | `BVAND -> fold `BVAND + | _ -> List.fold_left (fun a b -> infer a b |> snd) top args end module StateAbstraction = Intra_analysis.MapState (WrappingIntervalsLattice) From a5854547a484c7643779f9fef329f60d8e883d47 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 23 Jan 2026 13:56:53 +1000 Subject: [PATCH 13/36] Concat --- lib/analysis/wrapping_intervals.ml | 60 ++++++++++++++++-------------- 1 file changed, 33 insertions(+), 27 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index a2a80bc..09033f6 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -263,32 +263,6 @@ end module WrappingIntervalsLatticeOps = struct include WrappingIntervalsLattice - (* - TODO: - - Unary: - - [x] Negate - - [x] Not - - [x] Sign extend - - [x] Zero extend - - [x] Extract - - Binary: - - [x] Addition - - [x] Subtraction - - [x] Multiplication - - [x] Bitwise Or/And/Xor/Nand - - [x] Left Shift - - [x] Logical Right Shift - - [x] Arithmetic Right Shift - - [x] (Un)signed Div (see Crab for impl, paper does not provide) - - [ ] Comparisons - - Intrinsics - - [x] Addition - - [x] BitOr - - [x] BitXor - - [x] BitAnd - - [ ] Concat - *) - let bind1 f t = { w = t.w; @@ -738,6 +712,34 @@ module WrappingIntervalsLatticeOps = struct let k = Bitvec.of_int ~size:w lo in assert (hi <= w); truncate (lshr t (interval k k)) (hi - lo) + + let concat s t = + match (s.v, t.v) with + | Bot, _ | _, Bot -> + { w = Option.bind s.w (fun u -> Option.map (Int.add u) t.w); v = Bot } + | Top, Top -> + { w = Option.bind s.w (fun u -> Option.map (Int.add u) t.w); v = Top } + | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } + -> + interval (Bitvec.concat al bl) (Bitvec.concat au bu) + | Interval { lower = al; upper = au }, Top -> + let w = + match t.w with + | Some w -> w + | None -> failwith "Cannot concat without known width" + in + interval + Bitvec.(concat al (zero ~size:w)) + Bitvec.(concat au (ones ~size:w)) + | Top, Interval { lower = bl; upper = bu } -> + let w = + match s.w with + | Some w -> w + | None -> failwith "Cannot concat without known width" + in + interval + Bitvec.(concat (zero ~size:w) bl) + Bitvec.(concat (ones ~size:w) bu) end module WrappingIntervalsValueAbstraction = struct @@ -784,7 +786,11 @@ module WrappingIntervalsValueAbstraction = struct | `BVOR -> fold `BVOR | `BVXOR -> fold `BVXOR | `BVAND -> fold `BVAND - | _ -> List.fold_left (fun a b -> infer a b |> snd) top args + | `BVConcat -> + List.fold_left concat + (interval (Bitvec.zero ~size:0) (Bitvec.zero ~size:0)) + args + | _ -> top end module StateAbstraction = Intra_analysis.MapState (WrappingIntervalsLattice) From 7565ee34f8015a81bfe3c480d9069b2dada6ffe7 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Tue, 27 Jan 2026 10:02:49 +1000 Subject: [PATCH 14/36] light fixes --- lib/analysis/wrapping_intervals.ml | 31 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 09033f6..180d2d9 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -249,7 +249,7 @@ module WrappingIntervalsLattice = struct | Top -> ( match t.w with | Some w -> [ interval (umin w) (smax w); interval (smin w) (umax w) ] - | None -> failwith "Cannot determine nsplit for Top without width") + | None -> failwith "Cannot determine ssplit for Top without width") | Interval { lower; upper } -> let width = Bitvec.size lower in let sp = sp width in @@ -610,17 +610,19 @@ module WrappingIntervalsLatticeOps = struct | Some w -> w | None -> failwith "Cannot truncate without known width" in - let truncl = Bitvec.extract ~hi:k ~lo:0 lower in - let truncu = Bitvec.extract ~hi:k ~lo:0 upper in - let shiftl = Bitvec.(ashr lower (of_int ~size:w k)) in - let shiftu = Bitvec.(ashr upper (of_int ~size:w k)) in - if - Bitvec.( - (equal shiftl shiftu && ule truncl truncu) - || equal (add shiftl (of_int ~size:w 1)) shiftu - && ugt truncl truncu) - then interval truncl truncu - else { w = Some k; v = Top } + if w <= k then interval (Bitvec.zero ~size:0) (Bitvec.zero ~size:0) + else + let truncl = Bitvec.extract ~hi:k ~lo:0 lower in + let truncu = Bitvec.extract ~hi:k ~lo:0 upper in + let shiftl = Bitvec.(ashr lower (of_int ~size:w k)) in + let shiftu = Bitvec.(ashr upper (of_int ~size:w k)) in + if + Bitvec.( + (equal shiftl shiftu && ule truncl truncu) + || equal (add shiftl (of_int ~size:w 1)) shiftu + && ugt truncl truncu) + then interval truncl truncu + else { w = Some k; v = Top } let shl t k = let shl_const t k = @@ -631,6 +633,7 @@ module WrappingIntervalsLatticeOps = struct | Some w -> w | None -> failwith "Cannot shift left without known width" in + let k = if w < k then 0 else k in match (truncate t (w - k)).v with | Interval { lower; upper } -> let lower = Bitvec.zero_extend ~extension:(w - k) lower in @@ -702,7 +705,7 @@ module WrappingIntervalsLatticeOps = struct let extract ~hi ~lo t = assert (0 <= lo); assert (lo <= hi); - if hi = lo then { w = Some 0; v = Bot } + if hi <= lo then { w = Some 0; v = Bot } else let w = match t.w with @@ -749,7 +752,7 @@ module WrappingIntervalsValueAbstraction = struct let eval_const (op : Lang.Ops.AllOps.const) = match op with | `Bool _ -> { w = Some 1; v = Top } - | `Integer _ -> top + | `Integer _ -> { w = Some 0; v = Top } | `Bitvector bv -> if Bitvec.size bv = 0 then { w = Some 0; v = Top } else interval bv bv From 641f8b7d81843110641cf07b0fa94beb5b09d33d Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Tue, 27 Jan 2026 11:53:41 +1000 Subject: [PATCH 15/36] Test everythin --- lib/analysis/wrapping_intervals.ml | 4 +-- test/analysis/wrapping_intervals.ml | 45 ++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 180d2d9..13829be 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -636,8 +636,8 @@ module WrappingIntervalsLatticeOps = struct let k = if w < k then 0 else k in match (truncate t (w - k)).v with | Interval { lower; upper } -> - let lower = Bitvec.zero_extend ~extension:(w - k) lower in - let upper = Bitvec.zero_extend ~extension:(w - k) upper in + let lower = Bitvec.zero_extend ~extension:k lower in + let upper = Bitvec.zero_extend ~extension:k upper in interval Bitvec.(shl lower (of_int ~size:w k)) Bitvec.(shl upper (of_int ~size:w k)) diff --git a/test/analysis/wrapping_intervals.ml b/test/analysis/wrapping_intervals.ml index 10da808..90088a5 100644 --- a/test/analysis/wrapping_intervals.ml +++ b/test/analysis/wrapping_intervals.ml @@ -89,7 +89,50 @@ let%test_unit "intersect" = assert (intersect (iv 0 8) (iv 3 6) = [ iv 3 6 ]); assert (intersect (iv 3 7) (iv 6 11) = [ iv 6 7 ]) +open WrappingIntervalsValueAbstraction + let%test_unit "mul" = let ( = ) = equal in let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in - assert (WrappingIntervalsValueAbstraction.mul (iv 15 9) (iv 0 1) = iv 15 9) + assert (mul (iv 15 9) (iv 0 1) = iv 15 9) + +let%test_unit "truncate" = + let ( = ) = equal in + let iv ~w a b = + interval (Bitvec.of_int ~size:w a) (Bitvec.of_int ~size:w b) + in + assert (truncate (iv ~w:4 7 15) 2 = { w = Some 2; v = Top }); + assert (truncate (iv ~w:4 4 5) 2 = iv ~w:2 0 1) + +let%test_unit "shl" = + let ( = ) = equal in + let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in + assert (shl (iv 2 4) (iv 1 1) = iv 4 8); + assert (shl (iv 4 8) (iv 2 2) = iv 0 12) + +let%test_unit "lshr" = + let ( = ) = equal in + let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in + assert (lshr (iv 3 12) (iv 1 1) = iv 1 6); + assert (lshr (iv 15 5) (iv 2 2) = iv 0 3) + +let%test_unit "ashr" = + let ( = ) = equal in + let iv a b = interval (Bitvec.of_int ~size:4 a) (Bitvec.of_int ~size:4 b) in + assert (ashr (iv 15 3) (iv 1 1) = iv 15 1); + assert (ashr (iv 3 10) (iv 2 2) = iv 12 3) + +let%test_unit "extract" = + let ( = ) = equal in + let iv ~w a b = + interval (Bitvec.of_int ~size:w a) (Bitvec.of_int ~size:w b) + in + assert (extract ~hi:5 ~lo:2 @@ iv ~w:6 13 63 = { w = Some 3; v = Top }); + assert (extract ~hi:3 ~lo:1 @@ iv ~w:4 4 7 = iv ~w:2 2 3) + +let%test_unit "concat" = + let ( = ) = equal in + let iv ~w a b = + interval (Bitvec.of_int ~size:w a) (Bitvec.of_int ~size:w b) + in + assert (concat (iv ~w:2 1 3) (iv ~w:2 0 2) = iv ~w:4 4 14) From d434349d384a3c6d0b69e6d893b6128a7817e262 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Tue, 27 Jan 2026 15:06:25 +1000 Subject: [PATCH 16/36] Fixes and don;t allocate 32000GiB --- lib/analysis/wrapping_intervals.ml | 145 +++++++++++++++++++---------- test/lang/value_soundness.ml | 1 + 2 files changed, 98 insertions(+), 48 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 13829be..69a618b 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -6,7 +6,18 @@ module WrappingIntervalsLattice = struct type l = Top | Interval of { lower : Bitvec.t; upper : Bitvec.t } | Bot [@@deriving eq, show { with_path = false }] - type t = { w : int option; v : l } [@@deriving show { with_path = false }] + type t = { w : int option; v : l } + + let show t = + let value = + match t.v with + | Bot -> "⊥" + | Top -> "⊤" + | Interval { lower; upper } -> + "(|" ^ Bitvec.show lower ^ ", " ^ Bitvec.show upper ^ "|)" + in + let width = match t.w with Some w -> Int.to_string w | None -> "?" in + value ^ ":w" ^ width let equal s t = equal_l s.v t.v @@ -123,13 +134,22 @@ module WrappingIntervalsLattice = struct (* Join for multiple intervals to increase precision (join is not monotone) *) let lub (ints : t list) = - let bigger a b = if compare_size a b < 0 then b else a in + let bigger a b = + match (a.v, b.v) with + | x, y when equal_l x y -> a + | Bot, _ -> b + | _, Bot -> a + | Top, _ -> a + | _, Top -> b + | _ -> if compare_size a b < 0 then b else a + in let gap a b = + let a, b = infer a b in match (a.v, b.v) with | Interval { upper = au; _ }, Interval { lower = bl; _ } when (not (member b.v au)) && not (member a.v bl) -> complement (interval bl au) - | _, _ -> bottom + | _, _ -> infer a bottom |> snd in (* APLAS12 mentions "last cases are omitted", does not specify which cases. *) let extend a b = join a b in @@ -281,30 +301,50 @@ module WrappingIntervalsLatticeOps = struct Interval { lower = Bitvec.bitnot u; upper = Bitvec.bitnot l }) let sign_extend t k = - List.filter_map - (fun t -> - match t.v with - | Interval { lower; upper } -> - Some - (interval - (Bitvec.sign_extend ~extension:k lower) - (Bitvec.sign_extend ~extension:k upper)) - | _ -> None) - (nsplit t) - |> lub + if Option.equal Int.equal t.w (Some 0) then + let zeros = Bitvec.zero ~size:k in + interval zeros zeros + else + List.filter_map + (fun t -> + match t.v with + | Interval { lower; upper } -> + Some + (interval + (Bitvec.sign_extend ~extension:k lower) + (Bitvec.sign_extend ~extension:k upper)) + | _ -> None) + (nsplit t) + |> lub + |> fun s -> + { + v = s.v; + w = + (match s.w with Some _ -> s.w | None -> Option.map (Int.add k) t.w); + } let zero_extend t k = - List.filter_map - (fun t -> - match t.v with - | Interval { lower; upper } -> - Some - (interval - (Bitvec.zero_extend ~extension:k lower) - (Bitvec.zero_extend ~extension:k upper)) - | _ -> None) - (ssplit t) - |> lub + if Option.equal Int.equal t.w (Some 0) then + let zeros = Bitvec.zero ~size:k in + interval zeros zeros + else + List.filter_map + (fun t -> + match t.v with + | Interval { lower; upper } -> + Some + (interval + (Bitvec.zero_extend ~extension:k lower) + (Bitvec.zero_extend ~extension:k upper)) + | _ -> None) + (ssplit t) + |> lub + |> fun s -> + { + v = s.v; + w = + (match s.w with Some _ -> s.w | None -> Option.map (Int.add k) t.w); + } let add s t = let top = infer s top |> snd in @@ -387,10 +427,12 @@ module WrappingIntervalsLatticeOps = struct in intersect umul smul in - lub - (List.concat_map - (fun a -> List.concat_map (fun b -> rusmul a b) (cut t)) - (cut s)) + infer s + @@ lub + (List.concat_map + (fun a -> List.concat_map (fun b -> rusmul a b) (cut t)) + (cut s)) + |> snd (* Division implementation derived from Crab *) let trim_zeroes t = @@ -421,13 +463,15 @@ module WrappingIntervalsLatticeOps = struct interval (Bitvec.udiv al bu) (Bitvec.sdiv au bl) | _, _ -> top in - lub - (List.concat_map - (fun a -> - List.concat_map - (fun bs -> List.map (fun b -> divide a b) (trim_zeroes bs)) - (nsplit t)) - (nsplit s)) + infer s + @@ lub + (List.concat_map + (fun a -> + List.concat_map + (fun bs -> List.map (fun b -> divide a b) (trim_zeroes bs)) + (nsplit t)) + (nsplit s)) + |> snd let sdiv s t = let divide s t = @@ -462,13 +506,15 @@ module WrappingIntervalsLatticeOps = struct | _, _ -> top) | _, _ -> top in - lub - (List.concat_map - (fun a -> - List.concat_map - (fun bs -> List.map (fun b -> divide a b) (trim_zeroes bs)) - (cut t)) - (cut s)) + infer s + @@ lub + (List.concat_map + (fun a -> + List.concat_map + (fun bs -> List.map (fun b -> divide a b) (trim_zeroes bs)) + (cut t)) + (cut s)) + |> snd let bitlogop min max s t = let pre s t = @@ -478,10 +524,12 @@ module WrappingIntervalsLatticeOps = struct interval (min (al, au) (bl, bu)) (max (al, au) (bl, bu)) | _, _ -> infer s top |> snd in - lub - (List.concat_map - (fun a -> List.map (fun b -> pre a b) (ssplit t)) - (ssplit s)) + infer s + @@ lub + (List.concat_map + (fun a -> List.map (fun b -> pre a b) (ssplit t)) + (ssplit s)) + |> snd let bitor = let min_or (al, au) (bl, bu) = @@ -685,6 +733,7 @@ module WrappingIntervalsLatticeOps = struct | None -> failwith "Cannot arithmetic shift right without known width" in let fallback = + let k = min k w in interval Bitvec.(concat (ones ~size:k) (zero ~size:(w - k))) Bitvec.(concat (zero ~size:k) (ones ~size:(w - k))) @@ -773,7 +822,7 @@ module WrappingIntervalsValueAbstraction = struct | `BVMUL -> mul a b | `BVUDIV -> udiv a b | `BVSDIV -> sdiv a b - | `BVOR -> bitxor a b + | `BVOR -> bitor a b | `BVAND -> bitand a b | `BVNAND -> bitand a b |> bitnot | `BVXOR -> bitxor a b diff --git a/test/lang/value_soundness.ml b/test/lang/value_soundness.ml index d91e111..92e5ee4 100644 --- a/test/lang/value_soundness.ml +++ b/test/lang/value_soundness.ml @@ -31,6 +31,7 @@ struct let* exp = eval_expr in let partial = lazy (Expr_eval.partial_eval_expr exp) in let abstract = lazy (eval_abs exp) in + Lazy.force abstract |> ignore; let concrete = lazy (eval_abs (Lazy.force partial)) in return (exp, partial, abstract, concrete) From a240ddbe822554c5981e97d540bf7bdcf2e0dc48 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 28 Jan 2026 13:29:53 +1000 Subject: [PATCH 17/36] Concat is now sound --- lib/analysis/wrapping_intervals.ml | 56 +++++++++++++++-------------- test/analysis/wrapping_intervals.ml | 17 ++++++++- 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 69a618b..8c74f1a 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -301,9 +301,7 @@ module WrappingIntervalsLatticeOps = struct Interval { lower = Bitvec.bitnot u; upper = Bitvec.bitnot l }) let sign_extend t k = - if Option.equal Int.equal t.w (Some 0) then - let zeros = Bitvec.zero ~size:k in - interval zeros zeros + if Option.equal Int.equal t.w (Some 0) then { w = Some k; v = Top } else List.filter_map (fun t -> @@ -766,6 +764,12 @@ module WrappingIntervalsLatticeOps = struct truncate (lshr t (interval k k)) (hi - lo) let concat s t = + let tw = + match t.w with + | Some w -> w + | None -> failwith "Cannot concat without known width" + in + let t = if compare (sp tw) t <= 0 then { w = t.w; v = Top } else t in match (s.v, t.v) with | Bot, _ | _, Bot -> { w = Option.bind s.w (fun u -> Option.map (Int.add u) t.w); v = Bot } @@ -775,23 +779,18 @@ module WrappingIntervalsLatticeOps = struct -> interval (Bitvec.concat al bl) (Bitvec.concat au bu) | Interval { lower = al; upper = au }, Top -> - let w = - match t.w with - | Some w -> w - | None -> failwith "Cannot concat without known width" - in interval - Bitvec.(concat al (zero ~size:w)) - Bitvec.(concat au (ones ~size:w)) + Bitvec.(concat al (zero ~size:tw)) + Bitvec.(concat au (ones ~size:tw)) | Top, Interval { lower = bl; upper = bu } -> - let w = + let sw = match s.w with | Some w -> w | None -> failwith "Cannot concat without known width" in interval - Bitvec.(concat (zero ~size:w) bl) - Bitvec.(concat (ones ~size:w) bu) + Bitvec.(concat (zero ~size:sw) bl) + Bitvec.(concat (ones ~size:sw) bu) end module WrappingIntervalsValueAbstraction = struct @@ -820,12 +819,12 @@ module WrappingIntervalsValueAbstraction = struct | `BVADD -> add a b | `BVSUB -> sub a b | `BVMUL -> mul a b - | `BVUDIV -> udiv a b - | `BVSDIV -> sdiv a b - | `BVOR -> bitor a b - | `BVAND -> bitand a b - | `BVNAND -> bitand a b |> bitnot - | `BVXOR -> bitxor a b + (* | `BVUDIV -> udiv a b *) + (* | `BVSDIV -> sdiv a b *) + (* | `BVOR -> bitor a b *) + (* | `BVAND -> bitand a b *) + (* | `BVNAND -> bitand a b |> bitnot *) + (* | `BVXOR -> bitxor a b *) | `BVASHR -> ashr a b | `BVLSHR -> lshr a b | `BVSHL -> shl a b @@ -835,13 +834,18 @@ module WrappingIntervalsValueAbstraction = struct let fold op = List.fold_left (eval_binop op) bottom args in match op with | `BVADD -> fold `BVADD - | `BVOR -> fold `BVOR - | `BVXOR -> fold `BVXOR - | `BVAND -> fold `BVAND - | `BVConcat -> - List.fold_left concat - (interval (Bitvec.zero ~size:0) (Bitvec.zero ~size:0)) - args + (* | `BVOR -> fold `BVOR *) + (* | `BVXOR -> fold `BVXOR *) + (* | `BVAND -> fold `BVAND *) + | `BVConcat -> ( + List.map Option.some args + |> List.fold_left + (fun a b -> + match a with Some a -> Option.map (concat a) b | None -> b) + None + |> function + | Some res -> res + | None -> { w = Some 0; v = Top }) | _ -> top end diff --git a/test/analysis/wrapping_intervals.ml b/test/analysis/wrapping_intervals.ml index 90088a5..91c3cfa 100644 --- a/test/analysis/wrapping_intervals.ml +++ b/test/analysis/wrapping_intervals.ml @@ -135,4 +135,19 @@ let%test_unit "concat" = let iv ~w a b = interval (Bitvec.of_int ~size:w a) (Bitvec.of_int ~size:w b) in - assert (concat (iv ~w:2 1 3) (iv ~w:2 0 2) = iv ~w:4 4 14) + assert (concat (iv ~w:2 1 3) (iv ~w:2 0 2) = iv ~w:4 4 14); + assert (concat (iv ~w:2 3 0) (iv ~w:2 0 2) = iv ~w:4 12 2) + +let%test_unit "zero_extend" = + let ( = ) = equal in + let iv ~w a b = + interval (Bitvec.of_int ~size:w a) (Bitvec.of_int ~size:w b) + in + assert (zero_extend (iv ~w:3 0 1) 3 = iv ~w:6 0 1) + +let%test_unit "sign_extend" = + let ( = ) = equal in + let iv ~w a b = + interval (Bitvec.of_int ~size:w a) (Bitvec.of_int ~size:w b) + in + assert (sign_extend (iv ~w:3 0 1) 3 = iv ~w:6 0 1) From cd4ebd6ca8fa7a721ac5dbf196315ea98112f74c Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 28 Jan 2026 13:30:57 +1000 Subject: [PATCH 18/36] Concat is now sound --- lib/analysis/wrapping_intervals.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 8c74f1a..84d50f9 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -818,7 +818,7 @@ module WrappingIntervalsValueAbstraction = struct match op with | `BVADD -> add a b | `BVSUB -> sub a b - | `BVMUL -> mul a b + (* | `BVMUL -> mul a b *) (* | `BVUDIV -> udiv a b *) (* | `BVSDIV -> sdiv a b *) (* | `BVOR -> bitor a b *) From 37e1a1211b744e530512f2e8530028dc914ea7e1 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 28 Jan 2026 13:45:56 +1000 Subject: [PATCH 19/36] smul is all equal not all high --- lib/analysis/wrapping_intervals.ml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 84d50f9..8cdacb1 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -404,10 +404,15 @@ module WrappingIntervalsLatticeOps = struct let d = Bitvec.to_unsigned_bigint d in Z.(lt (sub (mul a b) (mul c d)) (pow (of_int 2) w)) in - if - msb_hi al && msb_hi au && msb_hi bl && msb_hi bu - && cond (au, bu) (al, bl) - then interval (Bitvec.mul al bl) (Bitvec.mul au bu) + let all_hi = msb_hi al && msb_hi au && msb_hi bl && msb_hi bu in + let all_lo = + (not @@ msb_hi al) + && (not @@ msb_hi au) + && (not @@ msb_hi bl) + && (not @@ msb_hi bu) + in + if (all_hi || all_lo) && cond (au, bu) (al, bl) then + interval (Bitvec.mul al bl) (Bitvec.mul au bu) else if msb_hi al && msb_hi au && (not (msb_hi bl)) @@ -818,7 +823,7 @@ module WrappingIntervalsValueAbstraction = struct match op with | `BVADD -> add a b | `BVSUB -> sub a b - (* | `BVMUL -> mul a b *) + | `BVMUL -> mul a b (* | `BVUDIV -> udiv a b *) (* | `BVSDIV -> sdiv a b *) (* | `BVOR -> bitor a b *) From eeaf8705438ed52811a1619184b751e2247e8594 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 28 Jan 2026 14:23:09 +1000 Subject: [PATCH 20/36] Or and XOR are safe too! --- lib/analysis/wrapping_intervals.ml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 8cdacb1..b90de46 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -826,10 +826,10 @@ module WrappingIntervalsValueAbstraction = struct | `BVMUL -> mul a b (* | `BVUDIV -> udiv a b *) (* | `BVSDIV -> sdiv a b *) - (* | `BVOR -> bitor a b *) + | `BVOR -> bitor a b (* | `BVAND -> bitand a b *) (* | `BVNAND -> bitand a b |> bitnot *) - (* | `BVXOR -> bitxor a b *) + | `BVXOR -> bitxor a b | `BVASHR -> ashr a b | `BVLSHR -> lshr a b | `BVSHL -> shl a b @@ -839,8 +839,8 @@ module WrappingIntervalsValueAbstraction = struct let fold op = List.fold_left (eval_binop op) bottom args in match op with | `BVADD -> fold `BVADD - (* | `BVOR -> fold `BVOR *) - (* | `BVXOR -> fold `BVXOR *) + | `BVOR -> fold `BVOR + | `BVXOR -> fold `BVXOR (* | `BVAND -> fold `BVAND *) | `BVConcat -> ( List.map Option.some args From 01b9ed32ee54f03b7500e1543bcaec4458efab0f Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 28 Jan 2026 14:40:29 +1000 Subject: [PATCH 21/36] All clear on AND, not sure about mul... --- lib/analysis/wrapping_intervals.ml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index b90de46..e1c8c8d 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -14,7 +14,7 @@ module WrappingIntervalsLattice = struct | Bot -> "⊥" | Top -> "⊤" | Interval { lower; upper } -> - "(|" ^ Bitvec.show lower ^ ", " ^ Bitvec.show upper ^ "|)" + "⟦" ^ Bitvec.show lower ^ ", " ^ Bitvec.show upper ^ "⟧" in let width = match t.w with Some w -> Int.to_string w | None -> "?" in value ^ ":w" ^ width @@ -582,8 +582,8 @@ module WrappingIntervalsLatticeOps = struct then let tempal = Bitvec.(bitand (bitor al m) (neg m)) in let tempbl = Bitvec.(bitand (bitor bl m) (neg m)) in - if Bitvec.uge tempal au then Bitvec.bitand tempal bl - else if Bitvec.uge tempbl bu then Bitvec.bitand al tempbl + if Bitvec.ule tempal au then Bitvec.bitand tempal bl + else if Bitvec.ule tempbl bu then Bitvec.bitand al tempbl else recurse () else recurse () in @@ -596,7 +596,7 @@ module WrappingIntervalsLatticeOps = struct let one = Bitvec.(of_int ~size:(size m) 1) in let recurse _ = max_and_aux Bitvec.(lshr m (of_int ~size:(size m) 1)) in if Bitvec.is_zero m then Bitvec.bitand au bu - else if Bitvec.(is_nonzero (bitand au (bitnot bl) |> bitand m)) then + else if Bitvec.(is_nonzero (bitand au (bitnot bu) |> bitand m)) then let temp = Bitvec.(bitor (bitand au (bitnot m)) (sub m one)) in if Bitvec.uge temp al then Bitvec.bitand temp bu else recurse () else if Bitvec.(is_nonzero (bitand (bitnot au) bu |> bitand m)) then @@ -827,8 +827,8 @@ module WrappingIntervalsValueAbstraction = struct (* | `BVUDIV -> udiv a b *) (* | `BVSDIV -> sdiv a b *) | `BVOR -> bitor a b - (* | `BVAND -> bitand a b *) - (* | `BVNAND -> bitand a b |> bitnot *) + | `BVAND -> bitand a b + | `BVNAND -> bitand a b |> bitnot | `BVXOR -> bitxor a b | `BVASHR -> ashr a b | `BVLSHR -> lshr a b @@ -841,7 +841,7 @@ module WrappingIntervalsValueAbstraction = struct | `BVADD -> fold `BVADD | `BVOR -> fold `BVOR | `BVXOR -> fold `BVXOR - (* | `BVAND -> fold `BVAND *) + | `BVAND -> fold `BVAND | `BVConcat -> ( List.map Option.some args |> List.fold_left From e93f85e5fcd793fd7f3e8947e74b52c103c74c29 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Thu, 29 Jan 2026 13:20:49 +1000 Subject: [PATCH 22/36] Mul is fixed, thanks b-paul --- lib/analysis/wrapping_intervals.ml | 18 ++++++++++-------- test/analysis/wrapping_intervals.ml | 16 ++++++++++++++++ test/lang/value_soundness.ml | 18 +++++++++++++++++- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index e1c8c8d..df99e86 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -402,7 +402,9 @@ module WrappingIntervalsLatticeOps = struct let b = Bitvec.to_unsigned_bigint b in let c = Bitvec.to_unsigned_bigint c in let d = Bitvec.to_unsigned_bigint d in - Z.(lt (sub (mul a b) (mul c d)) (pow (of_int 2) w)) + let res = Z.(sub (mul a b) (mul c d)) in + Z.(lt res (pow (of_int 2) w)) + && Z.(leq (neg (pow (of_int 2) w)) res) in let all_hi = msb_hi al && msb_hi au && msb_hi bl && msb_hi bu in let all_lo = @@ -826,10 +828,10 @@ module WrappingIntervalsValueAbstraction = struct | `BVMUL -> mul a b (* | `BVUDIV -> udiv a b *) (* | `BVSDIV -> sdiv a b *) - | `BVOR -> bitor a b - | `BVAND -> bitand a b - | `BVNAND -> bitand a b |> bitnot - | `BVXOR -> bitxor a b + (* | `BVOR -> bitor a b *) + (* | `BVAND -> bitand a b *) + (* | `BVNAND -> bitand a b |> bitnot *) + (* | `BVXOR -> bitxor a b *) | `BVASHR -> ashr a b | `BVLSHR -> lshr a b | `BVSHL -> shl a b @@ -839,9 +841,9 @@ module WrappingIntervalsValueAbstraction = struct let fold op = List.fold_left (eval_binop op) bottom args in match op with | `BVADD -> fold `BVADD - | `BVOR -> fold `BVOR - | `BVXOR -> fold `BVXOR - | `BVAND -> fold `BVAND + (* | `BVOR -> fold `BVOR *) + (* | `BVXOR -> fold `BVXOR *) + (* | `BVAND -> fold `BVAND *) | `BVConcat -> ( List.map Option.some args |> List.fold_left diff --git a/test/analysis/wrapping_intervals.ml b/test/analysis/wrapping_intervals.ml index 91c3cfa..b9f039c 100644 --- a/test/analysis/wrapping_intervals.ml +++ b/test/analysis/wrapping_intervals.ml @@ -151,3 +151,19 @@ let%test_unit "sign_extend" = interval (Bitvec.of_int ~size:w a) (Bitvec.of_int ~size:w b) in assert (sign_extend (iv ~w:3 0 1) 3 = iv ~w:6 0 1) + +let%test "mul2" = + let iv ~w a b = + interval (Bitvec.of_int ~size:w a) (Bitvec.of_int ~size:w b) + in + let abstract = + eval_binop `BVMUL (iv ~w:43 0x48303bae5fb 0x48303bae5fb) + @@ eval_intrin `BVConcat + [ + iv ~w:26 0 0; + eval_binop `BVUREM (iv ~w:17 0x1e97e 0x1e97e) + (iv ~w:17 0xdbf3 0xdbf3); + ] + in + let concrete = iv ~w:43 0x180fcfd9808 0x180fcfd9808 in + compare concrete abstract <= 0 diff --git a/test/lang/value_soundness.ml b/test/lang/value_soundness.ml index 92e5ee4..981ee5a 100644 --- a/test/lang/value_soundness.ml +++ b/test/lang/value_soundness.ml @@ -11,6 +11,21 @@ struct let* wd = Expr_gen.gen_width in Expr_gen.gen_bvexpr (5, wd) + (* let eval_expr = *) + (* let open QCheck.Gen in *) + (* let* wd = Expr_gen.gen_width in *) + (* let* l = Expr_gen.gen_bvconst wd in *) + (* let* r = Expr_gen.gen_bvconst wd in *) + (* let* op = oneofl [ `BVUDIV; `BVMUL ] in *) + (* return (Expr.BasilExpr.binexp ~op l r) *) + + (* let eval_expr = *) + (* let open QCheck.Gen in *) + (* let* wd = Expr_gen.gen_width in *) + (* let* l = Expr_gen.gen_bvconst wd in *) + (* let* op = oneofl [] in *) + (* return (Expr.BasilExpr.unexp ~op l) *) + let arb_abstract_eval = let eval_abs e = Eval.eval Ops.AllOps.show_const Ops.AllOps.show_unary @@ -45,7 +60,8 @@ struct let suite = let open QCheck in let test = - Test.make ~name:V.name ~count:10000 ~max_fail:3 arb_abstract_eval is_sound + Test.make ~name:V.name ~count:1000000 ~max_fail:3 arb_abstract_eval + is_sound in let suite = List.map QCheck_alcotest.to_alcotest [ test ] in ("soundness::" ^ V.name, suite) From f14ada0a01526fd11744a22fab4dd8e311ccb5ec Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Thu, 29 Jan 2026 13:50:50 +1000 Subject: [PATCH 23/36] I think the bitwise stuff is fine --- lib/analysis/wrapping_intervals.ml | 40 +++++++++++++++--------------- test/lang/value_soundness.ml | 34 +++++++++++++++---------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index df99e86..72fda0a 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -828,32 +828,32 @@ module WrappingIntervalsValueAbstraction = struct | `BVMUL -> mul a b (* | `BVUDIV -> udiv a b *) (* | `BVSDIV -> sdiv a b *) - (* | `BVOR -> bitor a b *) - (* | `BVAND -> bitand a b *) - (* | `BVNAND -> bitand a b |> bitnot *) - (* | `BVXOR -> bitxor a b *) + | `BVOR -> bitor a b + | `BVAND -> bitand a b + | `BVNAND -> bitand a b |> bitnot + | `BVXOR -> bitxor a b | `BVASHR -> ashr a b | `BVLSHR -> lshr a b | `BVSHL -> shl a b | _ -> infer a top |> snd let eval_intrin (op : Lang.Ops.AllOps.intrin) args = - let fold op = List.fold_left (eval_binop op) bottom args in - match op with - | `BVADD -> fold `BVADD - (* | `BVOR -> fold `BVOR *) - (* | `BVXOR -> fold `BVXOR *) - (* | `BVAND -> fold `BVAND *) - | `BVConcat -> ( - List.map Option.some args - |> List.fold_left - (fun a b -> - match a with Some a -> Option.map (concat a) b | None -> b) - None - |> function - | Some res -> res - | None -> { w = Some 0; v = Top }) - | _ -> top + let op = + match op with + | `BVADD -> eval_binop `BVADD + | `BVOR -> eval_binop `BVOR + | `BVXOR -> eval_binop `BVXOR + | `BVAND -> eval_binop `BVAND + | `BVConcat -> concat + | _ -> fun _ _ -> top + in + List.map Option.some args + |> List.fold_left + (fun a b -> match a with Some a -> Option.map (op a) b | None -> b) + None + |> function + | Some res -> res + | None -> { w = Some 0; v = Top } end module StateAbstraction = Intra_analysis.MapState (WrappingIntervalsLattice) diff --git a/test/lang/value_soundness.ml b/test/lang/value_soundness.ml index 981ee5a..9079698 100644 --- a/test/lang/value_soundness.ml +++ b/test/lang/value_soundness.ml @@ -14,17 +14,26 @@ struct (* let eval_expr = *) (* let open QCheck.Gen in *) (* let* wd = Expr_gen.gen_width in *) - (* let* l = Expr_gen.gen_bvconst wd in *) - (* let* r = Expr_gen.gen_bvconst wd in *) - (* let* op = oneofl [ `BVUDIV; `BVMUL ] in *) - (* return (Expr.BasilExpr.binexp ~op l r) *) - - (* let eval_expr = *) - (* let open QCheck.Gen in *) - (* let* wd = Expr_gen.gen_width in *) - (* let* l = Expr_gen.gen_bvconst wd in *) - (* let* op = oneofl [] in *) - (* return (Expr.BasilExpr.unexp ~op l) *) + (* let gen_binop l r = *) + (* let* op = oneofl [ `BVOR; `BVXOR; `BVNAND; `BVUREM ] in *) + (* return (Expr.BasilExpr.binexp ~op l r) *) + (* in *) + (* let gen_bvexpr = *) + (* fix (fun self (size, wd) -> *) + (* let self wd = self (size / 2, wd) in *) + (* match size with *) + (* | 0 -> Expr_gen.gen_bvconst wd *) + (* | size -> *) + (* frequency *) + (* [ *) + (* (1, Expr_gen.gen_bvconst wd); *) + (* ( 2, *) + (* let* l = self wd in *) + (* let* r = self wd in *) + (* gen_binop l r ); *) + (* ]) *) + (* in *) + (* gen_bvexpr (5, wd) *) let arb_abstract_eval = let eval_abs e = @@ -60,8 +69,7 @@ struct let suite = let open QCheck in let test = - Test.make ~name:V.name ~count:1000000 ~max_fail:3 arb_abstract_eval - is_sound + Test.make ~name:V.name ~count:10000 ~max_fail:3 arb_abstract_eval is_sound in let suite = List.map QCheck_alcotest.to_alcotest [ test ] in ("soundness::" ^ V.name, suite) From 284bac939984efe7aa3700843871b494781cb2d8 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 30 Jan 2026 12:18:45 +1000 Subject: [PATCH 24/36] UDiv should ssplit, not nsplit (Crab was wrong) --- lib/analysis/wrapping_intervals.ml | 12 ++++++------ test/analysis/wrapping_intervals.ml | 5 +++++ test/lang/value_soundness.ml | 24 ------------------------ 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 72fda0a..16f30a0 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -404,7 +404,7 @@ module WrappingIntervalsLatticeOps = struct let d = Bitvec.to_unsigned_bigint d in let res = Z.(sub (mul a b) (mul c d)) in Z.(lt res (pow (of_int 2) w)) - && Z.(leq (neg (pow (of_int 2) w)) res) + && Z.(lt (neg (pow (of_int 2) w)) res) in let all_hi = msb_hi al && msb_hi au && msb_hi bl && msb_hi bu in let all_lo = @@ -465,7 +465,7 @@ module WrappingIntervalsLatticeOps = struct match (s.v, t.v) with | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> - interval (Bitvec.udiv al bu) (Bitvec.sdiv au bl) + interval (Bitvec.udiv al bu) (Bitvec.udiv au bl) | _, _ -> top in infer s @@ -474,8 +474,8 @@ module WrappingIntervalsLatticeOps = struct (fun a -> List.concat_map (fun bs -> List.map (fun b -> divide a b) (trim_zeroes bs)) - (nsplit t)) - (nsplit s)) + (ssplit t)) + (ssplit s)) |> snd let sdiv s t = @@ -826,8 +826,8 @@ module WrappingIntervalsValueAbstraction = struct | `BVADD -> add a b | `BVSUB -> sub a b | `BVMUL -> mul a b - (* | `BVUDIV -> udiv a b *) - (* | `BVSDIV -> sdiv a b *) + | `BVUDIV -> udiv a b + | `BVSDIV -> sdiv a b | `BVOR -> bitor a b | `BVAND -> bitand a b | `BVNAND -> bitand a b |> bitnot diff --git a/test/analysis/wrapping_intervals.ml b/test/analysis/wrapping_intervals.ml index b9f039c..2428435 100644 --- a/test/analysis/wrapping_intervals.ml +++ b/test/analysis/wrapping_intervals.ml @@ -167,3 +167,8 @@ let%test "mul2" = in let concrete = iv ~w:43 0x180fcfd9808 0x180fcfd9808 in compare concrete abstract <= 0 + +let%test "udiv_top_top" = + let ( = ) = equal in + let top = { w = Some 4; v = Top } in + top = udiv top top diff --git a/test/lang/value_soundness.ml b/test/lang/value_soundness.ml index 9079698..92e5ee4 100644 --- a/test/lang/value_soundness.ml +++ b/test/lang/value_soundness.ml @@ -11,30 +11,6 @@ struct let* wd = Expr_gen.gen_width in Expr_gen.gen_bvexpr (5, wd) - (* let eval_expr = *) - (* let open QCheck.Gen in *) - (* let* wd = Expr_gen.gen_width in *) - (* let gen_binop l r = *) - (* let* op = oneofl [ `BVOR; `BVXOR; `BVNAND; `BVUREM ] in *) - (* return (Expr.BasilExpr.binexp ~op l r) *) - (* in *) - (* let gen_bvexpr = *) - (* fix (fun self (size, wd) -> *) - (* let self wd = self (size / 2, wd) in *) - (* match size with *) - (* | 0 -> Expr_gen.gen_bvconst wd *) - (* | size -> *) - (* frequency *) - (* [ *) - (* (1, Expr_gen.gen_bvconst wd); *) - (* ( 2, *) - (* let* l = self wd in *) - (* let* r = self wd in *) - (* gen_binop l r ); *) - (* ]) *) - (* in *) - (* gen_bvexpr (5, wd) *) - let arb_abstract_eval = let eval_abs e = Eval.eval Ops.AllOps.show_const Ops.AllOps.show_unary From 571a8f87c1b04f597b6e94680130599914f14388 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 30 Jan 2026 14:30:28 +1000 Subject: [PATCH 25/36] Tidy up show --- lib/analysis/wrapping_intervals.ml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapping_intervals.ml index 16f30a0..67925d0 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapping_intervals.ml @@ -4,18 +4,19 @@ module WrappingIntervalsLattice = struct let name = "wrappingIntervals" type l = Top | Interval of { lower : Bitvec.t; upper : Bitvec.t } | Bot - [@@deriving eq, show { with_path = false }] + [@@deriving eq] type t = { w : int option; v : l } + let show_l l = + match l with + | Bot -> "⊥" + | Top -> "⊤" + | Interval { lower; upper } -> + "⟦" ^ Bitvec.show lower ^ ", " ^ Bitvec.show upper ^ "⟧" + let show t = - let value = - match t.v with - | Bot -> "⊥" - | Top -> "⊤" - | Interval { lower; upper } -> - "⟦" ^ Bitvec.show lower ^ ", " ^ Bitvec.show upper ^ "⟧" - in + let value = show_l t.v in let width = match t.w with Some w -> Int.to_string w | None -> "?" in value ^ ":w" ^ width @@ -34,7 +35,6 @@ module WrappingIntervalsLattice = struct let infer { w = w1; v = a } { w = w2; v = b } = match (w1, w2) with | Some w1, Some w2 -> - (* Disable assertion when running `dune test` or the soundness check crashes *) assert (w1 = w2); ({ w = Some w1; v = a }, { w = Some w2; v = b }) | Some w1, None -> ({ w = Some w1; v = a }, { w = Some w1; v = b }) From 2f09696b4bfce1bf1e6393ff8ffe90346fc9ab70 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Mon, 2 Feb 2026 09:31:50 +1000 Subject: [PATCH 26/36] Appease dune fmt --- lib/analysis/dune | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/analysis/dune b/lib/analysis/dune index b13ddcb..c983293 100644 --- a/lib/analysis/dune +++ b/lib/analysis/dune @@ -2,7 +2,12 @@ (public_name bincaml.analysis) (name analysis) (flags -w -27) - (modules dataflow_graph intra_analysis defuse_bool wrapping_intervals lattice_types) + (modules + dataflow_graph + intra_analysis + defuse_bool + wrapping_intervals + lattice_types) (libraries patricia-tree loader From f2885d27062fb551bff4770b108b62ce0d4eec05 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 4 Feb 2026 11:44:57 +1000 Subject: [PATCH 27/36] Wrapping --> Wrapped --- lib/analysis/dune | 4 ++-- ...ping_intervals.ml => wrapped_intervals.ml} | 21 +++++++++---------- ...ping_intervals.ml => wrapped_intervals.ml} | 6 +++--- test/lang/value_soundness.ml | 6 +++--- 4 files changed, 18 insertions(+), 19 deletions(-) rename lib/analysis/{wrapping_intervals.ml => wrapped_intervals.ml} (98%) rename test/analysis/{wrapping_intervals.ml => wrapped_intervals.ml} (98%) diff --git a/lib/analysis/dune b/lib/analysis/dune index c983293..57f541e 100644 --- a/lib/analysis/dune +++ b/lib/analysis/dune @@ -6,8 +6,8 @@ dataflow_graph intra_analysis defuse_bool - wrapping_intervals - lattice_types) + lattice_types + wrapped_intervals) (libraries patricia-tree loader diff --git a/lib/analysis/wrapping_intervals.ml b/lib/analysis/wrapped_intervals.ml similarity index 98% rename from lib/analysis/wrapping_intervals.ml rename to lib/analysis/wrapped_intervals.ml index 67925d0..ac74a29 100644 --- a/lib/analysis/wrapping_intervals.ml +++ b/lib/analysis/wrapped_intervals.ml @@ -1,7 +1,7 @@ open Bincaml_util.Common -module WrappingIntervalsLattice = struct - let name = "wrappingIntervals" +module WrappedIntervalsLattice = struct + let name = "wrappedIntervals" type l = Top | Interval of { lower : Bitvec.t; upper : Bitvec.t } | Bot [@@deriving eq] @@ -280,8 +280,8 @@ module WrappingIntervalsLattice = struct let cut t = List.concat_map ssplit (nsplit t) end -module WrappingIntervalsLatticeOps = struct - include WrappingIntervalsLattice +module WrappedIntervalsLatticeOps = struct + include WrappedIntervalsLattice let bind1 f t = { @@ -800,9 +800,8 @@ module WrappingIntervalsLatticeOps = struct Bitvec.(concat (ones ~size:sw) bu) end -module WrappingIntervalsValueAbstraction = struct - include WrappingIntervalsLattice - include WrappingIntervalsLatticeOps +module WrappedIntervalsValueAbstraction = struct + include WrappedIntervalsLatticeOps let eval_const (op : Lang.Ops.AllOps.const) = match op with @@ -856,12 +855,12 @@ module WrappingIntervalsValueAbstraction = struct | None -> { w = Some 0; v = Top } end -module StateAbstraction = Intra_analysis.MapState (WrappingIntervalsLattice) +module StateAbstraction = Intra_analysis.MapState (WrappedIntervalsLattice) -module WrappingIntervalsValueAbstractionBasil = struct - include WrappingIntervalsValueAbstraction +module WrappedIntervalsValueAbstractionBasil = struct + include WrappedIntervalsValueAbstraction module E = Lang.Expr.BasilExpr end include - Dataflow_graph.EasyForwardAnalysisPack (WrappingIntervalsValueAbstractionBasil) + Dataflow_graph.EasyForwardAnalysisPack (WrappedIntervalsValueAbstractionBasil) diff --git a/test/analysis/wrapping_intervals.ml b/test/analysis/wrapped_intervals.ml similarity index 98% rename from test/analysis/wrapping_intervals.ml rename to test/analysis/wrapped_intervals.ml index 2428435..6c7dce3 100644 --- a/test/analysis/wrapping_intervals.ml +++ b/test/analysis/wrapped_intervals.ml @@ -1,6 +1,6 @@ open Bincaml_util.Common -open Analysis.Wrapping_intervals -open WrappingIntervalsLattice +open Analysis.Wrapped_intervals +open WrappedIntervalsLattice let dbg x = print_endline (show x); @@ -89,7 +89,7 @@ let%test_unit "intersect" = assert (intersect (iv 0 8) (iv 3 6) = [ iv 3 6 ]); assert (intersect (iv 3 7) (iv 6 11) = [ iv 6 7 ]) -open WrappingIntervalsValueAbstraction +open WrappedIntervalsValueAbstraction let%test_unit "mul" = let ( = ) = equal in diff --git a/test/lang/value_soundness.ml b/test/lang/value_soundness.ml index 92e5ee4..eb48a9f 100644 --- a/test/lang/value_soundness.ml +++ b/test/lang/value_soundness.ml @@ -56,10 +56,10 @@ end module TestBoolDom = ValueAbstractionSoundness (Analysis.Defuse_bool.IsZeroValueAbstractionBasil) -module TestWrappingIntervalDom = +module TestWrappedIntervalDom = ValueAbstractionSoundness - (Analysis.Wrapping_intervals.WrappingIntervalsValueAbstractionBasil) + (Analysis.Wrapped_intervals.WrappedIntervalsValueAbstractionBasil) let _ = Alcotest.run "value domain abstract eval soundness" - [ TestBoolDom.suite; TestWrappingIntervalDom.suite ] + [ TestBoolDom.suite; TestWrappedIntervalDom.suite ] From 8027d35a27efacc18c9b09f00de4db55d3eb5d49 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 4 Feb 2026 11:47:51 +1000 Subject: [PATCH 28/36] Add implementation notes --- docs/dev/wrapped-intervals.pdf | Bin 0 -> 155792 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 docs/dev/wrapped-intervals.pdf diff --git a/docs/dev/wrapped-intervals.pdf b/docs/dev/wrapped-intervals.pdf new file mode 100755 index 0000000000000000000000000000000000000000..f51d6095a107192ad549006bd421a77b89a957ef GIT binary patch literal 155792 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%0AP!MXuDunW1O&;e@ba5Hp(8gf;kgV<@ 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
^1mIeVIW2^7R%XpQRHu^CX|{8=Ys%@bxV(% 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 Date: Wed, 4 Feb 2026 13:46:14 +1000 Subject: [PATCH 29/36] Fix bounds handling on shift and extract --- lib/analysis/wrapped_intervals.ml | 36 +++++++++++++++++++++++++----- test/analysis/wrapped_intervals.ml | 3 ++- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lib/analysis/wrapped_intervals.ml b/lib/analysis/wrapped_intervals.ml index ac74a29..8d67484 100644 --- a/lib/analysis/wrapped_intervals.ml +++ b/lib/analysis/wrapped_intervals.ml @@ -35,7 +35,7 @@ module WrappedIntervalsLattice = struct let infer { w = w1; v = a } { w = w2; v = b } = match (w1, w2) with | Some w1, Some w2 -> - assert (w1 = w2); + (* assert (w1 = w2); *) ({ w = Some w1; v = a }, { w = Some w2; v = b }) | Some w1, None -> ({ w = Some w1; v = a }, { w = Some w1; v = b }) | None, Some w2 -> ({ w = Some w2; v = a }, { w = Some w2; v = b }) @@ -663,7 +663,7 @@ module WrappedIntervalsLatticeOps = struct | Some w -> w | None -> failwith "Cannot truncate without known width" in - if w <= k then interval (Bitvec.zero ~size:0) (Bitvec.zero ~size:0) + if w < k then interval (Bitvec.zero ~size:0) (Bitvec.zero ~size:0) else let truncl = Bitvec.extract ~hi:k ~lo:0 lower in let truncu = Bitvec.extract ~hi:k ~lo:0 upper in @@ -686,7 +686,7 @@ module WrappedIntervalsLatticeOps = struct | Some w -> w | None -> failwith "Cannot shift left without known width" in - let k = if w < k then 0 else k in + let k = if w < k then w else k in match (truncate t (w - k)).v with | Interval { lower; upper } -> let lower = Bitvec.zero_extend ~extension:k lower in @@ -699,7 +699,14 @@ module WrappedIntervalsLatticeOps = struct Bitvec.(concat (ones ~size:(w - k)) (zero ~size:k)) in match is_singleton k with - | Some k -> shl_const t (Bitvec.to_unsigned_bigint k |> Z.to_int) + | Some k -> + shl_const t + ( Bitvec.to_unsigned_bigint k |> fun k -> + if Z.fits_int k then Z.to_int k + else + match t.w with + | Some w -> w + 1 + | None -> failwith "Cannot shift left without known width" ) | None -> { w = t.w; v = Top } let lshr t k = @@ -725,7 +732,15 @@ module WrappedIntervalsLatticeOps = struct | _ -> fallback in match is_singleton k with - | Some k -> lshr_const t (Bitvec.to_unsigned_bigint k |> Z.to_int) + | Some k -> + lshr_const t + ( Bitvec.to_unsigned_bigint k |> fun k -> + if Z.fits_int k then Z.to_int k + else + match t.w with + | Some w -> w + 1 + | None -> + failwith "Cannot logical shift right without known width" ) | None -> { w = t.w; v = Top } let ashr t k = @@ -753,7 +768,16 @@ module WrappedIntervalsLatticeOps = struct | _ -> fallback in match is_singleton k with - | Some k -> ashr_const t (Bitvec.to_unsigned_bigint k |> Z.to_int) + | Some k -> + ashr_const t + ( Bitvec.to_unsigned_bigint k |> fun k -> + if Z.fits_int k then Z.to_int k + else + match t.w with + | Some w -> w + 1 + | None -> + failwith "Cannot arithmetic shift right without known width" + ) | None -> { w = t.w; v = Top } let extract ~hi ~lo t = diff --git a/test/analysis/wrapped_intervals.ml b/test/analysis/wrapped_intervals.ml index 6c7dce3..e6b3e5b 100644 --- a/test/analysis/wrapped_intervals.ml +++ b/test/analysis/wrapped_intervals.ml @@ -128,7 +128,8 @@ let%test_unit "extract" = interval (Bitvec.of_int ~size:w a) (Bitvec.of_int ~size:w b) in assert (extract ~hi:5 ~lo:2 @@ iv ~w:6 13 63 = { w = Some 3; v = Top }); - assert (extract ~hi:3 ~lo:1 @@ iv ~w:4 4 7 = iv ~w:2 2 3) + assert (extract ~hi:3 ~lo:1 @@ iv ~w:4 4 7 = iv ~w:2 2 3); + assert (extract ~hi:3 ~lo:0 @@ iv ~w:3 3 3 = iv ~w:3 3 3) let%test_unit "concat" = let ( = ) = equal in From 19eabd9d02ebc0e14300875b39c9706cb0fed2a8 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 4 Feb 2026 14:02:49 +1000 Subject: [PATCH 30/36] open Bitvec --- lib/analysis/wrapped_intervals.ml | 308 ++++++++++++++---------------- 1 file changed, 148 insertions(+), 160 deletions(-) diff --git a/lib/analysis/wrapped_intervals.ml b/lib/analysis/wrapped_intervals.ml index 8d67484..7a16de4 100644 --- a/lib/analysis/wrapped_intervals.ml +++ b/lib/analysis/wrapped_intervals.ml @@ -1,4 +1,5 @@ open Bincaml_util.Common +open Bitvec module WrappedIntervalsLattice = struct let name = "wrappedIntervals" @@ -12,8 +13,7 @@ module WrappedIntervalsLattice = struct match l with | Bot -> "⊥" | Top -> "⊤" - | Interval { lower; upper } -> - "⟦" ^ Bitvec.show lower ^ ", " ^ Bitvec.show upper ^ "⟧" + | Interval { lower; upper } -> "⟦" ^ show lower ^ ", " ^ show upper ^ "⟧" let show t = let value = show_l t.v in @@ -23,9 +23,9 @@ module WrappedIntervalsLattice = struct let equal s t = equal_l s.v t.v let interval lower upper = - Bitvec.size_is_equal lower upper; + size_is_equal lower upper; { - w = Some (Bitvec.size lower); + w = Some (size lower); v = (if Bitvec.(equal lower (add upper (of_int ~size:(size upper) 1))) then Top @@ -41,10 +41,10 @@ module WrappedIntervalsLattice = struct | None, Some w2 -> ({ w = Some w2; v = a }, { w = Some w2; v = b }) | None, None -> ({ w = None; v = a }, { w = None; v = b }) - let umin width = Bitvec.zero ~size:width - let umax width = Bitvec.ones ~size:width - let smin width = Bitvec.(concat (ones ~size:1) (zero ~size:(width - 1))) - let smax width = Bitvec.(zero_extend ~extension:1 (ones ~size:(width - 1))) + let umin width = zero ~size:width + let umax width = ones ~size:width + let smin width = concat (ones ~size:1) (zero ~size:(width - 1)) + let smax width = zero_extend ~extension:1 (ones ~size:(width - 1)) let sp width = interval (umax width) (umin width) let np width = interval (smax width) (smin width) let top = { w = None; v = Top } @@ -59,7 +59,7 @@ module WrappedIntervalsLattice = struct | Some w -> Z.pow (Z.of_int 2) w | None -> failwith "Cannot determine cardinality for Top without width") | Interval { lower; upper } -> - Bitvec.(sub upper lower |> to_unsigned_bigint |> Z.add (Z.of_int 1)) + sub upper lower |> to_unsigned_bigint |> Z.add (Z.of_int 1) let is_singleton { w; v } = match v with @@ -74,8 +74,8 @@ module WrappedIntervalsLattice = struct | Bot -> { w; v = Top } | Top -> { w; v = Bot } | Interval { lower; upper } -> - let new_lower = Bitvec.(add upper (of_int ~size:(size upper) 1)) in - let new_upper = Bitvec.(sub lower (of_int ~size:(size lower) 1)) in + let new_lower = add upper (of_int ~size:(size upper) 1) in + let new_upper = sub lower (of_int ~size:(size lower) 1) in interval new_lower new_upper let member t e = @@ -83,9 +83,9 @@ module WrappedIntervalsLattice = struct | Bot -> false | Top -> true | Interval { lower; upper } -> - Bitvec.size_is_equal lower e; - Bitvec.size_is_equal upper e; - Bitvec.(ule (sub e lower) (sub upper lower)) + size_is_equal lower e; + size_is_equal upper e; + ule (sub e lower) (sub upper lower) let compare_l a b = match (a, b) with @@ -119,7 +119,7 @@ module WrappedIntervalsLattice = struct let bl_mem = member a bl in let bu_mem = member a bu in if al_mem && au_mem && bl_mem && bu_mem then - { w = Some (Bitvec.size al); v = Top } + { w = Some (size al); v = Top } else if au_mem && bl_mem then interval al bu else if al_mem && bu_mem then interval bl au else @@ -127,7 +127,7 @@ module WrappedIntervalsLattice = struct let outer_span = cardinality (interval bu al) in if Z.lt inner_span outer_span - || (Z.equal inner_span outer_span && Bitvec.ule al bl) + || (Z.equal inner_span outer_span && ule al bl) then interval al bu else interval bl au | _, _ -> failwith "unreachable" @@ -166,8 +166,7 @@ module WrappedIntervalsLattice = struct List.fold_left (fun acc t -> match t.v with - | Interval { lower; upper } when Bitvec.ule upper lower -> - extend acc t + | Interval { lower; upper } when ule upper lower -> extend acc t | _ -> acc) bottom sorted in @@ -189,9 +188,9 @@ module WrappedIntervalsLattice = struct | Top, _ -> t | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> - Bitvec.size_is_equal al bl; - Bitvec.size_is_equal au bu; - let width = Bitvec.size al in + size_is_equal al bl; + size_is_equal au bu; + let width = size al in if compare t s <= 0 then s else if Z.geq (cardinality s) (Z.pow (Z.of_int 2) width) then { w = Some width; v = Top } @@ -200,26 +199,23 @@ module WrappedIntervalsLattice = struct if equal joined (interval al bu) then join joined (interval al - Bitvec.( - sub (mul au (of_int ~size:width 2)) al - |> add (of_int ~size:width 1))) + (sub (mul au (of_int ~size:width 2)) al + |> add (of_int ~size:width 1))) else if equal joined (interval bl au) then join joined (interval - Bitvec.( - sub - (sub (mul al (of_int ~size:width 2)) au) - (of_int ~size:width 1)) + (sub + (sub (mul al (of_int ~size:width 2)) au) + (of_int ~size:width 1)) au) else if member b al && member b au then join t (interval bl - Bitvec.( - sub - (bl - |> add (mul au (of_int ~size:width 2)) - |> add (of_int ~size:width 1)) - (mul al (of_int ~size:width 2)))) + (sub + (bl + |> add (mul au (of_int ~size:width 2)) + |> add (of_int ~size:width 1)) + (mul al (of_int ~size:width 2)))) else { w = Some width; v = Top } let intersect s t = @@ -257,7 +253,7 @@ module WrappedIntervalsLattice = struct | Some w -> [ interval (umin w) (smax w); interval (smin w) (umax w) ] | None -> failwith "Cannot determine nsplit for Top without width") | Interval { lower; upper } -> - let width = Bitvec.size lower in + let width = size lower in let np = np width in if compare np t <= 0 then [ interval lower (smax width); interval (smin width) upper ] @@ -271,7 +267,7 @@ module WrappedIntervalsLattice = struct | Some w -> [ interval (umin w) (smax w); interval (smin w) (umax w) ] | None -> failwith "Cannot determine ssplit for Top without width") | Interval { lower; upper } -> - let width = Bitvec.size lower in + let width = size lower in let sp = sp width in if compare sp t <= 0 then [ interval lower (umax width); interval (umin width) upper ] @@ -293,12 +289,10 @@ module WrappedIntervalsLatticeOps = struct | Interval { lower; upper } -> f lower upper); } - let neg = - bind1 (fun l u -> Interval { lower = Bitvec.neg u; upper = Bitvec.neg l }) + let neg = bind1 (fun l u -> Interval { lower = neg u; upper = neg l }) let bitnot = - bind1 (fun l u -> - Interval { lower = Bitvec.bitnot u; upper = Bitvec.bitnot l }) + bind1 (fun l u -> Interval { lower = bitnot u; upper = bitnot l }) let sign_extend t k = if Option.equal Int.equal t.w (Some 0) then { w = Some k; v = Top } @@ -309,8 +303,8 @@ module WrappedIntervalsLatticeOps = struct | Interval { lower; upper } -> Some (interval - (Bitvec.sign_extend ~extension:k lower) - (Bitvec.sign_extend ~extension:k upper)) + (sign_extend ~extension:k lower) + (sign_extend ~extension:k upper)) | _ -> None) (nsplit t) |> lub @@ -323,7 +317,7 @@ module WrappedIntervalsLatticeOps = struct let zero_extend t k = if Option.equal Int.equal t.w (Some 0) then - let zeros = Bitvec.zero ~size:k in + let zeros = zero ~size:k in interval zeros zeros else List.filter_map @@ -332,8 +326,8 @@ module WrappedIntervalsLatticeOps = struct | Interval { lower; upper } -> Some (interval - (Bitvec.zero_extend ~extension:k lower) - (Bitvec.zero_extend ~extension:k upper)) + (zero_extend ~extension:k lower) + (zero_extend ~extension:k upper)) | _ -> None) (ssplit t) |> lub @@ -352,7 +346,7 @@ module WrappedIntervalsLatticeOps = struct | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> if Z.(leq (add (cardinality s) (cardinality t)) (cardinality top)) then - interval (Bitvec.add al bl) (Bitvec.add au bu) + interval (add al bl) (add au bu) else top | _, _ -> top @@ -364,7 +358,7 @@ module WrappedIntervalsLatticeOps = struct | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> if Z.(leq (add (cardinality s) (cardinality t)) (cardinality top)) then - interval (Bitvec.sub al bu) (Bitvec.sub au bl) + interval (sub al bu) (sub au bl) else top | _, _ -> top @@ -381,13 +375,13 @@ module WrappedIntervalsLatticeOps = struct | ( Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } ) -> let cond = - let al = Bitvec.to_unsigned_bigint al in - let au = Bitvec.to_unsigned_bigint au in - let bl = Bitvec.to_unsigned_bigint bl in - let bu = Bitvec.to_unsigned_bigint bu in + let al = to_unsigned_bigint al in + let au = to_unsigned_bigint au in + let bl = to_unsigned_bigint bl in + let bu = to_unsigned_bigint bu in Z.(lt (sub (mul au bu) (mul al bl)) (pow (of_int 2) w)) in - if cond then interval (Bitvec.mul al bl) (Bitvec.mul au bu) else top + if cond then interval (mul al bl) (mul au bu) else top | _, _ -> top in let smul = @@ -398,10 +392,10 @@ module WrappedIntervalsLatticeOps = struct Bitvec.(equal (extract ~hi:w ~lo:(w - 1) b) (ones ~size:1)) in let cond (a, b) (c, d) = - let a = Bitvec.to_unsigned_bigint a in - let b = Bitvec.to_unsigned_bigint b in - let c = Bitvec.to_unsigned_bigint c in - let d = Bitvec.to_unsigned_bigint d in + let a = to_unsigned_bigint a in + let b = to_unsigned_bigint b in + let c = to_unsigned_bigint c in + let d = to_unsigned_bigint d in let res = Z.(sub (mul a b) (mul c d)) in Z.(lt res (pow (of_int 2) w)) && Z.(lt (neg (pow (of_int 2) w)) res) @@ -414,19 +408,19 @@ module WrappedIntervalsLatticeOps = struct && (not @@ msb_hi bu) in if (all_hi || all_lo) && cond (au, bu) (al, bl) then - interval (Bitvec.mul al bl) (Bitvec.mul au bu) + interval (mul al bl) (mul au bu) else if msb_hi al && msb_hi au && (not (msb_hi bl)) && (not (msb_hi bu)) && cond (au, bl) (al, bu) - then interval (Bitvec.mul al bu) (Bitvec.mul au bl) + then interval (mul al bu) (mul au bl) else if (not (msb_hi al)) && (not (msb_hi au)) && msb_hi bl && msb_hi bu && cond (al, bu) (au, bl) - then interval (Bitvec.mul au bl) (Bitvec.mul al bu) + then interval (mul au bl) (mul al bu) else top | _, _ -> top in @@ -446,17 +440,17 @@ module WrappedIntervalsLatticeOps = struct | Some w -> w | None -> failwith "Cannot trim zeroes without known width" in - let zero = Bitvec.zero ~size:w in + let zero = zero ~size:w in match t.v with | Bot -> [] - | Top -> [ interval (Bitvec.of_int ~size:w 1) (umax w) ] + | Top -> [ interval (of_int ~size:w 1) (umax w) ] | Interval { lower; upper } -> - if Bitvec.equal lower zero && Bitvec.equal upper zero then [] - else if Bitvec.equal lower zero then - [ interval (Bitvec.of_int ~size:w 1) upper ] - else if Bitvec.equal upper zero then [ interval lower (umax w) ] + let equal = Bitvec.equal in + if equal lower zero && equal upper zero then [] + else if equal lower zero then [ interval (of_int ~size:w 1) upper ] + else if equal upper zero then [ interval lower (umax w) ] else if member t.v zero then - [ interval lower (umax w); interval (Bitvec.of_int ~size:w 1) upper ] + [ interval lower (umax w); interval (of_int ~size:w 1) upper ] else [ t ] let udiv s t = @@ -465,7 +459,7 @@ module WrappedIntervalsLatticeOps = struct match (s.v, t.v) with | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> - interval (Bitvec.udiv al bu) (Bitvec.udiv au bl) + interval (udiv al bu) (udiv au bl) | _, _ -> top in infer s @@ -498,16 +492,16 @@ module WrappedIntervalsLatticeOps = struct match (msb_hi al, msb_hi bl) with | true, true when not ((au = smin && bl = neg1) || (al = smin && bu = neg1)) -> - interval (Bitvec.sdiv au bl) (Bitvec.sdiv al bu) + interval (sdiv au bl) (sdiv al bu) | false, false when not ((al = smin && bu = neg1) || (au = smin && bl = neg1)) -> - interval (Bitvec.sdiv al bu) (Bitvec.sdiv au bl) + interval (sdiv al bu) (sdiv au bl) | true, false when not ((al = smin && bl = neg1) || (au = smin && bu = neg1)) -> - interval (Bitvec.sdiv al bl) (Bitvec.sdiv au bu) + interval (sdiv al bl) (sdiv au bu) | false, true when not ((au = smin && bu = neg1) && al = smin && bl = neg1) -> - interval (Bitvec.sdiv au bu) (Bitvec.sdiv al bl) + interval (sdiv au bu) (sdiv al bl) | _, _ -> top) | _, _ -> top in @@ -540,36 +534,36 @@ module WrappedIntervalsLatticeOps = struct let min_or (al, au) (bl, bu) = let rec min_or_aux m = (* Lazy evaluation trick *) - let recurse _ = min_or_aux Bitvec.(lshr m (of_int ~size:(size m) 1)) in - - if Bitvec.is_zero m then Bitvec.bitor al bl - else if Bitvec.(is_nonzero (bitand (bitnot al) bl |> bitand m)) then - let temp = Bitvec.(bitor al m |> bitand (neg m)) in - if Bitvec.ule temp au then Bitvec.bitor temp bl else recurse () - else if Bitvec.(is_nonzero (bitand al (bitnot bl) |> bitand m)) then - let temp = Bitvec.(bitor bl m |> bitand (neg m)) in - if Bitvec.ule temp bu then Bitvec.bitor al temp else recurse () + let recurse _ = min_or_aux (lshr m (of_int ~size:(size m) 1)) in + let open Bitvec in + if is_zero m then bitor al bl + else if is_nonzero (bitand (bitnot al) bl |> bitand m) then + let temp = bitor al m |> bitand (neg m) in + if ule temp au then bitor temp bl else recurse () + else if is_nonzero (bitand al (bitnot bl) |> bitand m) then + let temp = bitor bl m |> bitand (neg m) in + if ule temp bu then bitor al temp else recurse () else recurse () in - let init = smin (Bitvec.size al) in + let init = smin (size al) in min_or_aux init in let max_or (al, au) (bl, bu) = let rec max_or_aux m = - let one = Bitvec.(of_int ~size:(size m) 1) in - let recurse _ = max_or_aux Bitvec.(lshr m one) in - - if Bitvec.is_zero m then Bitvec.bitor au bu - else if Bitvec.(is_nonzero (bitand au bu |> bitand m)) then - let tempau = Bitvec.(bitor (sub au m) (sub m one)) in - let tempbu = Bitvec.(bitor (sub bu m) (sub m one)) in - if Bitvec.uge tempau al then Bitvec.bitor tempau bu - else if Bitvec.uge tempbu bl then Bitvec.bitor au tempbu + let one = of_int ~size:(size m) 1 in + let recurse _ = max_or_aux (lshr m one) in + let open Bitvec in + if is_zero m then bitor au bu + else if is_nonzero (bitand au bu |> bitand m) then + let tempau = bitor (sub au m) (sub m one) in + let tempbu = bitor (sub bu m) (sub m one) in + if uge tempau al then bitor tempau bu + else if uge tempbu bl then bitor au tempbu else recurse () else recurse () in - let init = smin (Bitvec.size al) in + let init = smin (size al) in max_or_aux init in bitlogop min_or max_or @@ -577,36 +571,36 @@ module WrappedIntervalsLatticeOps = struct let bitand = let min_and (al, au) (bl, bu) = let rec min_and_aux m = - let recurse _ = min_and_aux Bitvec.(lshr m (of_int ~size:(size m) 1)) in - - if Bitvec.is_zero m then Bitvec.bitand al bl - else if Bitvec.(is_nonzero (bitand (bitnot al) (bitnot bl) |> bitand m)) - then - let tempal = Bitvec.(bitand (bitor al m) (neg m)) in - let tempbl = Bitvec.(bitand (bitor bl m) (neg m)) in - if Bitvec.ule tempal au then Bitvec.bitand tempal bl - else if Bitvec.ule tempbl bu then Bitvec.bitand al tempbl + let recurse _ = min_and_aux (lshr m (of_int ~size:(size m) 1)) in + let open Bitvec in + if is_zero m then bitand al bl + else if is_nonzero (bitand (bitnot al) (bitnot bl) |> bitand m) then + let tempal = bitand (bitor al m) (neg m) in + let tempbl = bitand (bitor bl m) (neg m) in + if ule tempal au then bitand tempal bl + else if ule tempbl bu then bitand al tempbl else recurse () else recurse () in - let init = smin (Bitvec.size al) in + let init = smin (size al) in min_and_aux init in let max_and (al, au) (bl, bu) = let rec max_and_aux m = - let one = Bitvec.(of_int ~size:(size m) 1) in - let recurse _ = max_and_aux Bitvec.(lshr m (of_int ~size:(size m) 1)) in - if Bitvec.is_zero m then Bitvec.bitand au bu - else if Bitvec.(is_nonzero (bitand au (bitnot bu) |> bitand m)) then - let temp = Bitvec.(bitor (bitand au (bitnot m)) (sub m one)) in - if Bitvec.uge temp al then Bitvec.bitand temp bu else recurse () - else if Bitvec.(is_nonzero (bitand (bitnot au) bu |> bitand m)) then - let temp = Bitvec.(bitor (bitand bu (bitnot m)) (sub m one)) in - if Bitvec.uge temp bl then Bitvec.bitand au temp else recurse () + let one = of_int ~size:(size m) 1 in + let recurse _ = max_and_aux (lshr m (of_int ~size:(size m) 1)) in + let open Bitvec in + if is_zero m then bitand au bu + else if is_nonzero (bitand au (bitnot bu) |> bitand m) then + let temp = bitor (bitand au (bitnot m)) (sub m one) in + if uge temp al then bitand temp bu else recurse () + else if is_nonzero (bitand (bitnot au) bu |> bitand m) then + let temp = bitor (bitand bu (bitnot m)) (sub m one) in + if uge temp bl then bitand au temp else recurse () else recurse () in - let init = smin (Bitvec.size al) in + let init = smin (size al) in max_and_aux init in bitlogop min_and max_and @@ -614,41 +608,41 @@ module WrappedIntervalsLatticeOps = struct let bitxor = let min_xor (al, au) (bl, bu) = let rec min_xor_aux m (al, au) (bl, bu) = - let recurse = min_xor_aux Bitvec.(lshr m (of_int ~size:(size m) 1)) in - - if Bitvec.is_zero m then Bitvec.bitxor al bl - else if Bitvec.(is_nonzero (bitand (bitnot al) bl |> bitand m)) then - let temp = Bitvec.(bitor al m |> bitand (neg m)) in - let al = if Bitvec.ule temp au then Bitvec.bitor temp bl else al in + let recurse = min_xor_aux (lshr m (of_int ~size:(size m) 1)) in + let open Bitvec in + if is_zero m then bitxor al bl + else if is_nonzero (bitand (bitnot al) bl |> bitand m) then + let temp = bitor al m |> bitand (neg m) in + let al = if ule temp au then bitor temp bl else al in recurse (al, au) (bl, bu) - else if Bitvec.(is_nonzero (bitand al (bitnot bl) |> bitand m)) then - let temp = Bitvec.(bitor bl m |> bitand (neg m)) in - let bl = if Bitvec.ule temp bu then Bitvec.bitor al temp else bl in + else if is_nonzero (bitand al (bitnot bl) |> bitand m) then + let temp = bitor bl m |> bitand (neg m) in + let bl = if ule temp bu then bitor al temp else bl in recurse (al, au) (bl, bu) else recurse (al, au) (bl, bu) in - let init = smin (Bitvec.size al) in + let init = smin (size al) in min_xor_aux init (al, au) (bl, bu) in let max_xor (al, au) (bl, bu) = let rec max_xor_aux m (al, au) (bl, bu) = - let one = Bitvec.(of_int ~size:(size m) 1) in - let recurse = max_xor_aux Bitvec.(lshr m one) in - - if Bitvec.is_zero m then Bitvec.bitxor au bu - else if Bitvec.(is_nonzero (bitand au bu |> bitand m)) then - let tempau = Bitvec.(bitor (sub au m) (sub m one)) in - let tempbu = Bitvec.(bitor (sub bu m) (sub m one)) in + let one = of_int ~size:(size m) 1 in + let recurse = max_xor_aux (lshr m one) in + let open Bitvec in + if is_zero m then bitxor au bu + else if is_nonzero (bitand au bu |> bitand m) then + let tempau = bitor (sub au m) (sub m one) in + let tempbu = bitor (sub bu m) (sub m one) in let au, bu = - if Bitvec.uge tempau al then (tempau, bu) - else if Bitvec.uge tempbu bl then (au, tempbu) + if uge tempau al then (tempau, bu) + else if uge tempbu bl then (au, tempbu) else (au, bu) in recurse (al, au) (bl, bu) else recurse (al, au) (bl, bu) in - let init = smin (Bitvec.size al) in + let init = smin (size al) in max_xor_aux init (al, au) (bl, bu) in bitlogop min_xor max_xor @@ -663,12 +657,12 @@ module WrappedIntervalsLatticeOps = struct | Some w -> w | None -> failwith "Cannot truncate without known width" in - if w < k then interval (Bitvec.zero ~size:0) (Bitvec.zero ~size:0) + if w < k then interval (zero ~size:0) (zero ~size:0) else - let truncl = Bitvec.extract ~hi:k ~lo:0 lower in - let truncu = Bitvec.extract ~hi:k ~lo:0 upper in - let shiftl = Bitvec.(ashr lower (of_int ~size:w k)) in - let shiftu = Bitvec.(ashr upper (of_int ~size:w k)) in + let truncl = extract ~hi:k ~lo:0 lower in + let truncu = extract ~hi:k ~lo:0 upper in + let shiftl = ashr lower (of_int ~size:w k) in + let shiftu = ashr upper (of_int ~size:w k) in if Bitvec.( (equal shiftl shiftu && ule truncl truncu) @@ -692,16 +686,15 @@ module WrappedIntervalsLatticeOps = struct let lower = Bitvec.zero_extend ~extension:k lower in let upper = Bitvec.zero_extend ~extension:k upper in interval - Bitvec.(shl lower (of_int ~size:w k)) - Bitvec.(shl upper (of_int ~size:w k)) + (shl lower (of_int ~size:w k)) + (shl upper (of_int ~size:w k)) | _ -> - interval (Bitvec.zero ~size:w) - Bitvec.(concat (ones ~size:(w - k)) (zero ~size:k)) + interval (zero ~size:w) (concat (ones ~size:(w - k)) (zero ~size:k)) in match is_singleton k with | Some k -> shl_const t - ( Bitvec.to_unsigned_bigint k |> fun k -> + ( to_unsigned_bigint k |> fun k -> if Z.fits_int k then Z.to_int k else match t.w with @@ -719,22 +712,21 @@ module WrappedIntervalsLatticeOps = struct | None -> failwith "Cannot logical shift right without known width" in let fallback = - interval (Bitvec.zero ~size:w) - Bitvec.(concat (zero ~size:k) (ones ~size:(w - k))) + interval (zero ~size:w) (concat (zero ~size:k) (ones ~size:(w - k))) in if compare (sp w) t <= 0 then fallback else match t.v with | Interval { lower; upper } -> interval - Bitvec.(lshr lower (of_int ~size:w k)) - Bitvec.(lshr upper (of_int ~size:w k)) + (lshr lower (of_int ~size:w k)) + (lshr upper (of_int ~size:w k)) | _ -> fallback in match is_singleton k with | Some k -> lshr_const t - ( Bitvec.to_unsigned_bigint k |> fun k -> + ( to_unsigned_bigint k |> fun k -> if Z.fits_int k then Z.to_int k else match t.w with @@ -755,22 +747,22 @@ module WrappedIntervalsLatticeOps = struct let fallback = let k = min k w in interval - Bitvec.(concat (ones ~size:k) (zero ~size:(w - k))) - Bitvec.(concat (zero ~size:k) (ones ~size:(w - k))) + (concat (ones ~size:k) (zero ~size:(w - k))) + (concat (zero ~size:k) (ones ~size:(w - k))) in if compare (np w) t <= 0 then fallback else match t.v with | Interval { lower; upper } -> interval - Bitvec.(ashr lower (of_int ~size:w k)) - Bitvec.(ashr upper (of_int ~size:w k)) + (ashr lower (of_int ~size:w k)) + (ashr upper (of_int ~size:w k)) | _ -> fallback in match is_singleton k with | Some k -> ashr_const t - ( Bitvec.to_unsigned_bigint k |> fun k -> + ( to_unsigned_bigint k |> fun k -> if Z.fits_int k then Z.to_int k else match t.w with @@ -790,7 +782,7 @@ module WrappedIntervalsLatticeOps = struct | Some w -> w | None -> failwith "Cannot extract without known width" in - let k = Bitvec.of_int ~size:w lo in + let k = of_int ~size:w lo in assert (hi <= w); truncate (lshr t (interval k k)) (hi - lo) @@ -808,20 +800,16 @@ module WrappedIntervalsLatticeOps = struct { w = Option.bind s.w (fun u -> Option.map (Int.add u) t.w); v = Top } | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> - interval (Bitvec.concat al bl) (Bitvec.concat au bu) + interval (concat al bl) (concat au bu) | Interval { lower = al; upper = au }, Top -> - interval - Bitvec.(concat al (zero ~size:tw)) - Bitvec.(concat au (ones ~size:tw)) + interval (concat al (zero ~size:tw)) (concat au (ones ~size:tw)) | Top, Interval { lower = bl; upper = bu } -> let sw = match s.w with | Some w -> w | None -> failwith "Cannot concat without known width" in - interval - Bitvec.(concat (zero ~size:sw) bl) - Bitvec.(concat (ones ~size:sw) bu) + interval (concat (zero ~size:sw) bl) (concat (ones ~size:sw) bu) end module WrappedIntervalsValueAbstraction = struct @@ -832,7 +820,7 @@ module WrappedIntervalsValueAbstraction = struct | `Bool _ -> { w = Some 1; v = Top } | `Integer _ -> { w = Some 0; v = Top } | `Bitvector bv -> - if Bitvec.size bv = 0 then { w = Some 0; v = Top } else interval bv bv + if size bv = 0 then { w = Some 0; v = Top } else interval bv bv let eval_unop (op : Lang.Ops.AllOps.unary) a = match op with From b42007227d4d1c9271a504e883d128cf0b2f2bca Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 4 Feb 2026 14:26:48 +1000 Subject: [PATCH 31/36] Docs and light clean up --- lib/analysis/wrapped_intervals.ml | 50 ++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/lib/analysis/wrapped_intervals.ml b/lib/analysis/wrapped_intervals.ml index 7a16de4..d4b6959 100644 --- a/lib/analysis/wrapped_intervals.ml +++ b/lib/analysis/wrapped_intervals.ml @@ -1,3 +1,22 @@ +(** Wrapped Intervals model a range of values on a circle, allowing both + unsigned and signed interpretation whilst maintaining precision in + intermediate steps. + + Based on the APLAS12 paper "Signedness-Agnostic Program Analysis: Precise + Integer Bounds for Low-Level Code" by Navas, Schachte, Søndergaard, and + Stuckey (doi: 10.1007/978-3-642-35182-2_9). + + Implementation in the Crab Static Analyser used as a general reference and + for the unsigned and signed division functions. + https://github.com/seahorn/crab/blob/418b63c66b91f04bf36ae59d5eecb936c48836ee/include/crab/domains/wrapped_interval_impl.hpp + + Algorithms for bitwise operations on unsigned intervals used for building + signedness agnostic versions were adapted from Chapter 4.3 of Hacker's + Delight (Second Edition) by Henry S. Warren. + + Further details about implementation can be found in + docs/dev/wrapped_intervals.pdf *) + open Bincaml_util.Common open Bitvec @@ -32,14 +51,16 @@ module WrappedIntervalsLattice = struct else Interval { lower; upper }); } - let infer { w = w1; v = a } { w = w2; v = b } = - match (w1, w2) with - | Some w1, Some w2 -> - (* assert (w1 = w2); *) - ({ w = Some w1; v = a }, { w = Some w2; v = b }) - | Some w1, None -> ({ w = Some w1; v = a }, { w = Some w1; v = b }) - | None, Some w2 -> ({ w = Some w2; v = a }, { w = Some w2; v = b }) - | None, None -> ({ w = None; v = a }, { w = None; v = b }) + let infer a b = + let w = + match (a.w, b.w) with + | Some a, Some b -> + assert (a = b); + Some a + | Some a, None | None, Some a -> Some a + | None, None -> None + in + ({ a with w }, { b with w }) let umin width = zero ~size:width let umax width = ones ~size:width @@ -151,8 +172,11 @@ module WrappedIntervalsLattice = struct complement (interval bl au) | _, _ -> infer a bottom |> snd in - (* APLAS12 mentions "last cases are omitted", does not specify which cases. *) - let extend a b = join a b in + (* + Paper mentions last cases of join are omitted for extend, but does not specify which cases. + In practice, just using join as is works. + *) + let extend = join in let sorted = List.sort (fun s t -> @@ -433,7 +457,11 @@ module WrappedIntervalsLatticeOps = struct (cut s)) |> snd - (* Division implementation derived from Crab *) + (* + Division implementation derived from Crab + https://github.com/seahorn/crab/blob/418b63c66b91f04bf36ae59d5eecb936c48836ee/include/crab/domains/wrapped_interval_impl.hpp#L1034-L1091 + https://github.com/seahorn/crab/blob/418b63c66b91f04bf36ae59d5eecb936c48836ee/include/crab/domains/wrapped_interval_impl.hpp#L210-L297 + *) let trim_zeroes t = let w = match t.w with From 149205d61a076c55e108b230f7ff791a1240b7bb Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Wed, 4 Feb 2026 14:29:06 +1000 Subject: [PATCH 32/36] Fix typecheck tests failing locally --- test/cram/typefail.t | 2 +- test/cram/typepass.t | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cram/typefail.t b/test/cram/typefail.t index fd344e2..6dfb6f9 100644 --- a/test/cram/typefail.t +++ b/test/cram/typefail.t @@ -1,4 +1,4 @@ - $ dune exec bincaml script ./typefail.sexp 2>/dev/null + $ ../../bin/main.exe script ./typefail.sexp 2>/dev/null Arguments are not of the same type in eq at statement 0 in %main_entry Arguments are not of the same type in neq at statement 1 in %main_entry Arguments are not of the same type in eq at statement 2 in %main_entry diff --git a/test/cram/typepass.t b/test/cram/typepass.t index 47441b4..515e3b6 100644 --- a/test/cram/typepass.t +++ b/test/cram/typepass.t @@ -1 +1 @@ - $ dune exec bincaml script ./typepass.sexp + $ ../../bin/main.exe script ./typepass.sexp From 136cf0661d680d9572bdc2313127da6d5eadc879 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Thu, 5 Feb 2026 11:47:06 +1000 Subject: [PATCH 33/36] Add demo to passes list --- lib/passes.ml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/passes.ml b/lib/passes.ml index 1864e96..eb64843 100644 --- a/lib/passes.ml +++ b/lib/passes.ml @@ -50,6 +50,14 @@ module PassManager = struct doc = "runs truthiness analysis on dataflow graph and prints results"; } + let dfg_wrapped_int = + { + name = "demo-dfg-wrapped-int-analysis"; + apply = DFGAnalysis (module Analysis.Wrapped_intervals.Analysis); + doc = + "Runs wrapped interval analysis on dataflow graph and prints results"; + } + let remove_unused = { name = "remove-unused-decls"; @@ -87,6 +95,7 @@ module PassManager = struct let passes = [ dfg_bool; + dfg_wrapped_int; sparams; read_uninit false; read_uninit true; From ff4e3d23df209366a069dac8bb3f61737934f696 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 6 Feb 2026 09:35:56 +1000 Subject: [PATCH 34/36] Propagate unknown widths (if unable to infer) --- lib/analysis/wrapped_intervals.ml | 95 +++++++++++++++++-------------- 1 file changed, 51 insertions(+), 44 deletions(-) diff --git a/lib/analysis/wrapped_intervals.ml b/lib/analysis/wrapped_intervals.ml index d4b6959..b3995bf 100644 --- a/lib/analysis/wrapped_intervals.ml +++ b/lib/analysis/wrapped_intervals.ml @@ -815,29 +815,32 @@ module WrappedIntervalsLatticeOps = struct truncate (lshr t (interval k k)) (hi - lo) let concat s t = - let tw = - match t.w with - | Some w -> w - | None -> failwith "Cannot concat without known width" - in - let t = if compare (sp tw) t <= 0 then { w = t.w; v = Top } else t in - match (s.v, t.v) with - | Bot, _ | _, Bot -> - { w = Option.bind s.w (fun u -> Option.map (Int.add u) t.w); v = Bot } - | Top, Top -> - { w = Option.bind s.w (fun u -> Option.map (Int.add u) t.w); v = Top } - | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } - -> - interval (concat al bl) (concat au bu) - | Interval { lower = al; upper = au }, Top -> - interval (concat al (zero ~size:tw)) (concat au (ones ~size:tw)) - | Top, Interval { lower = bl; upper = bu } -> - let sw = - match s.w with - | Some w -> w - | None -> failwith "Cannot concat without known width" - in - interval (concat (zero ~size:sw) bl) (concat (ones ~size:sw) bu) + match t.w with + | None -> top + | Some tw -> ( + let t = if compare (sp tw) t <= 0 then { w = t.w; v = Top } else t in + match (s.v, t.v) with + | Bot, _ | _, Bot -> + { + w = Option.bind s.w (fun u -> Option.map (Int.add u) t.w); + v = Bot; + } + | Top, Top -> + { + w = Option.bind s.w (fun u -> Option.map (Int.add u) t.w); + v = Top; + } + | ( Interval { lower = al; upper = au }, + Interval { lower = bl; upper = bu } ) -> + interval (concat al bl) (concat au bu) + | Interval { lower = al; upper = au }, Top -> + interval (concat al (zero ~size:tw)) (concat au (ones ~size:tw)) + | Top, Interval { lower = bl; upper = bu } -> ( + match s.w with + | None -> top + | Some sw -> + interval (concat (zero ~size:sw) bl) (concat (ones ~size:sw) bu) + )) end module WrappedIntervalsValueAbstraction = struct @@ -851,30 +854,34 @@ module WrappedIntervalsValueAbstraction = struct if size bv = 0 then { w = Some 0; v = Top } else interval bv bv let eval_unop (op : Lang.Ops.AllOps.unary) a = - match op with - | `BVNEG -> neg a - | `BVNOT -> bitnot a - | `ZeroExtend k -> zero_extend a k - | `SignExtend k -> sign_extend a k - | `Extract (hi, lo) -> extract ~hi ~lo a - | _ -> infer a top |> snd + if Option.is_none a.w then top + else + match op with + | `BVNEG -> neg a + | `BVNOT -> bitnot a + | `ZeroExtend k -> zero_extend a k + | `SignExtend k -> sign_extend a k + | `Extract (hi, lo) -> extract ~hi ~lo a + | _ -> infer a top |> snd let eval_binop (op : Lang.Ops.AllOps.binary) a b = let a, b = infer a b in - match op with - | `BVADD -> add a b - | `BVSUB -> sub a b - | `BVMUL -> mul a b - | `BVUDIV -> udiv a b - | `BVSDIV -> sdiv a b - | `BVOR -> bitor a b - | `BVAND -> bitand a b - | `BVNAND -> bitand a b |> bitnot - | `BVXOR -> bitxor a b - | `BVASHR -> ashr a b - | `BVLSHR -> lshr a b - | `BVSHL -> shl a b - | _ -> infer a top |> snd + if Option.is_none a.w then top + else + match op with + | `BVADD -> add a b + | `BVSUB -> sub a b + | `BVMUL -> mul a b + | `BVUDIV -> udiv a b + | `BVSDIV -> sdiv a b + | `BVOR -> bitor a b + | `BVAND -> bitand a b + | `BVNAND -> bitand a b |> bitnot + | `BVXOR -> bitxor a b + | `BVASHR -> ashr a b + | `BVLSHR -> lshr a b + | `BVSHL -> shl a b + | _ -> infer a top |> snd let eval_intrin (op : Lang.Ops.AllOps.intrin) args = let op = From b602c1a608a9c00f1a6a872668f3805872a86d48 Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 6 Feb 2026 11:05:38 +1000 Subject: [PATCH 35/36] Implement proper widening --- lib/analysis/wrapped_intervals.ml | 50 +++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/lib/analysis/wrapped_intervals.ml b/lib/analysis/wrapped_intervals.ml index b3995bf..a6b1acf 100644 --- a/lib/analysis/wrapped_intervals.ml +++ b/lib/analysis/wrapped_intervals.ml @@ -208,8 +208,8 @@ module WrappedIntervalsLattice = struct match (a, b) with | _, Bot -> s | Bot, _ -> t - | _, Top -> s - | Top, _ -> t + | _, Top -> t + | Top, _ -> s | Interval { lower = al; upper = au }, Interval { lower = bl; upper = bu } -> size_is_equal al bl; @@ -902,12 +902,50 @@ module WrappedIntervalsValueAbstraction = struct | None -> { w = Some 0; v = Top } end -module StateAbstraction = Intra_analysis.MapState (WrappedIntervalsLattice) - module WrappedIntervalsValueAbstractionBasil = struct include WrappedIntervalsValueAbstraction module E = Lang.Expr.BasilExpr end -include - Dataflow_graph.EasyForwardAnalysisPack (WrappedIntervalsValueAbstractionBasil) +module StateAbstraction = + Intra_analysis.MapState (WrappedIntervalsValueAbstractionBasil) + +module Eval = + Intra_analysis.EvalStmt + (WrappedIntervalsValueAbstractionBasil) + (StateAbstraction) + +module Domain = struct + include StateAbstraction + + let top_val = WrappedIntervalsLattice.top + + let init p = + let vs = Lang.Procedure.formal_in_params p |> StringMap.values in + vs + |> Iter.map (fun v -> (v, top_val)) + |> Iter.fold (fun m (v, d) -> update v d m) bottom + + let transfer dom stmt = + let stmt = Eval.stmt_eval_fwd stmt dom in + let updates = + match stmt with + | Lang.Stmt.Instr_Assign ls -> List.to_iter ls + | Lang.Stmt.Instr_Assert _ -> Iter.empty + | Lang.Stmt.Instr_Assume _ -> Iter.empty + | Lang.Stmt.Instr_Load { lhs } -> Iter.singleton (lhs, top_val) + | Lang.Stmt.Instr_Store { lhs } -> Iter.singleton (lhs, top_val) + | Lang.Stmt.Instr_IntrinCall { lhs } -> + StringMap.values lhs |> Iter.map (fun v -> (v, top_val)) + | Lang.Stmt.Instr_Call { lhs } -> + StringMap.values lhs |> Iter.map (fun v -> (v, top_val)) + | Lang.Stmt.Instr_IndirectCall _ -> Iter.empty + in + Iter.fold (fun a (k, v) -> update k v a) dom updates +end + +module Analysis = Dataflow_graph.AnalysisFwd (Domain) + +let analyse (p : Lang.Program.proc) = + let g = Dataflow_graph.create p in + Analysis.analyse ~widen_set:Graph.ChaoticIteration.FromWto ~delay_widen:50 g From 92c2ab019607e5a4e5ddd210b1c8e942563608ae Mon Sep 17 00:00:00 2001 From: Hayden Brown Date: Fri, 6 Feb 2026 15:52:57 +1000 Subject: [PATCH 36/36] Implement proper widening (REAL, not clickbait) --- lib/analysis/intra_analysis.ml | 2 +- lib/analysis/wrapped_intervals.ml | 1 + lib/passes.ml | 5 ++--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/analysis/intra_analysis.ml b/lib/analysis/intra_analysis.ml index d666f3c..24665d9 100644 --- a/lib/analysis/intra_analysis.ml +++ b/lib/analysis/intra_analysis.ml @@ -130,7 +130,7 @@ module MapState (V : Lattice) = struct let to_iter m = Iter.from_iter (fun f -> M.iter (fun k v -> f (k, v)) m) let read (v : Var.t) m = M.find_opt v m |> Option.get_or ~default:V.bottom let update k v m = M.add k v m - let widening a b = join a b + let widening a b = M.idempotent_union (fun v a b -> V.widening a b) a b type val_t = V.t type key_t = Var.t diff --git a/lib/analysis/wrapped_intervals.ml b/lib/analysis/wrapped_intervals.ml index a6b1acf..e52e387 100644 --- a/lib/analysis/wrapped_intervals.ml +++ b/lib/analysis/wrapped_intervals.ml @@ -862,6 +862,7 @@ module WrappedIntervalsValueAbstraction = struct | `ZeroExtend k -> zero_extend a k | `SignExtend k -> sign_extend a k | `Extract (hi, lo) -> extract ~hi ~lo a + | `BOOLTOBV1 -> { w = Some 1; v = Top } | _ -> infer a top |> snd let eval_binop (op : Lang.Ops.AllOps.binary) a b = diff --git a/lib/passes.ml b/lib/passes.ml index eb64843..74966eb 100644 --- a/lib/passes.ml +++ b/lib/passes.ml @@ -171,9 +171,8 @@ module PassManager = struct |> Iter.iter (fun (pn, p) -> let g = Analysis.Dataflow_graph.create p in let r = - D.analyse - ~widen_set:(Graph.ChaoticIteration.Predicate (fun _ -> false)) - ~delay_widen:0 g + D.analyse ~widen_set:Graph.ChaoticIteration.FromWto + ~delay_widen:10 g in print_endline (D.D.name ^ " :: " ^ ID.to_string pn); print_endline