From a20bcac6c2150dacb80839c8542ba2a0bfffe9e4 Mon Sep 17 00:00:00 2001 From: Julien Signoles <julien.signoles@cea.fr> Date: Fri, 20 Aug 2021 19:48:08 +0200 Subject: [PATCH] [interval] improve implementation of \product and \numof --- src/libraries/stdlib/extlib.ml | 4 + src/libraries/stdlib/extlib.mli | 5 + src/plugins/e-acsl/src/analyses/interval.ml | 252 +++++++++----------- 3 files changed, 119 insertions(+), 142 deletions(-) diff --git a/src/libraries/stdlib/extlib.ml b/src/libraries/stdlib/extlib.ml index e6267f35c0f..a0a4b588d40 100644 --- a/src/libraries/stdlib/extlib.ml +++ b/src/libraries/stdlib/extlib.ml @@ -256,6 +256,10 @@ let opt_hash hash v = match v with | None -> 31179 | Some v -> hash v +let opt_map2 f x y = match x, y with + | None, _ | _, None -> None + | Some x, Some y -> Some (f x y) + (* ************************************************************************* *) (** Booleans *) (* ************************************************************************* *) diff --git a/src/libraries/stdlib/extlib.mli b/src/libraries/stdlib/extlib.mli index 1ef17c991ac..5b02633a1b2 100644 --- a/src/libraries/stdlib/extlib.mli +++ b/src/libraries/stdlib/extlib.mli @@ -229,6 +229,11 @@ val the: exn:exn -> 'a option -> 'a val opt_hash: ('a -> int) -> 'a option -> int (** @since Sodium-20150201 *) +val opt_map2: ('a -> 'b -> 'c) -> 'a option -> 'b option -> 'c option +(** @return [f a b] if arguments are [Some a] and [Some b], orelse return + [None]. + @since Frama-C+dev *) + (* ************************************************************************* *) (** {2 Booleans} *) (* ************************************************************************* *) diff --git a/src/plugins/e-acsl/src/analyses/interval.ml b/src/plugins/e-acsl/src/analyses/interval.ml index 08754985e86..91006e19915 100644 --- a/src/plugins/e-acsl/src/analyses/interval.ml +++ b/src/plugins/e-acsl/src/analyses/interval.ml @@ -290,6 +290,24 @@ let cast ~src ~dst = match src, dst with certainly on purpose . *) dst +(* a-b; or 0 if negative *) +let length a b = Z.max Z.zero (Z.add Z.one (Z.sub a b)) + +(* minimal distance between two intervals given by their respective lower and + upper bounds, i.e. the length between the lower bound of the second interval + bound and the upper bound of the first interval. *) +let min_delta (_, max1) (min2, _) = match max1, min2 with + | Some m1, Some m2 -> length m2 m1 + | _, None | None, _ -> Z.zero + +(* maximal distance between between two intervals given by their respective + lower and upper bounds, i.e. the length between the upper bound of the second + interval and the lower bound of the first interval. + @return None for \infty *) +let max_delta (min1, _) (_, max2) = match min1, max2 with + | Some m1, Some m2 -> Some (length m2 m1) + | _, None | None, _ -> None + (* ********************************************************************* *) (* constructors and destructors *) (* ********************************************************************* *) @@ -346,135 +364,6 @@ let extended_interv_of_typ ty = match interv_of_typ ty with | Rational | Real | Nan | Float (_,_) as i -> i -(* -(* Compute the interval of the extended quantifier \sum, \product and \numof. - [lambda] is the interval of the lambda term, [i] (resp. [j]) is the interval - of the lower (resp. upper) bound and [name] is the identifier of the extended - quantifier (\sum, \product or \numof). The returned ival is the interval of - the extended quantifier. *) -let interv_of_extended_quantifier lambda lb up name = - match lambda, lb, up, name.lv_name with - | Ival lbd_iv, Ival lb_iv, Ival ub_iv, "\\sum" -> - (try - let min_lambda, max_lambda = Ival.min_and_max lbd_iv in - let min_lb, min_ub = Ival.min_and_max lb_iv in - let max_lb, max_ub = Ival.min_and_max ub_iv in - (* the number of iterations is the distance between the upper bound and - the lower bound, or 0 if it is negative. *) - let delta a b = Z.max Z.zero (Z.add Z.one (Z.sub a b)) in - let min_iteration_number = - (* the minimum number of iterations is the distance between the - smallest upper bound and the biggest lower bound. *) - match min_ub, max_lb with - | Some mub, Some mlb -> delta mub mlb - | _, None | None, _ -> Z.zero - in - let max_iteration_number = - (* the maximum number of iterations is the distance between the - biggest upper bound and the smallest lower bound. *) - match max_ub, min_lb with - | Some mub, Some mlb -> Some (delta mub mlb) - | _, None | None, _ -> None - in - (* the lower (resp. upper) bound is the min (resp. max) value of - the lambda term times the min (resp. max) number of iterations *) - let lower_bound = match min_lambda with - | None -> None - | Some z -> Some (Z.mul z min_iteration_number) - in - let upper_bound = match max_lambda, max_iteration_number with - | None, _ | _, None -> None - | Some z, Some n -> Some (Z.mul z n) - in - Ival (Ival.inject_range lower_bound upper_bound) - with Abstract_interp.Error_Bottom -> - bottom) - | _ -> Error.not_yet "extended quantifiers with non-integer parameters" -*) - -(* Compute the interval of the extended quantifier \sum, \product and \numof. - [lbd_ival] is the interval of the lambda term, [k_ival] is the interval of - the quantifier and [name] is the identifier of the extended quantifier (\sum - or \product). The returned ival is the interval of the extended quantifier *) -let interv_of_extended_quantifier lambda i j name = - match lambda, i, j with - | Ival lbd_ival, Ival i_ival, Ival j_ival -> - (try - let min_lambda, max_lambda = Ival.min_and_max lbd_ival in - let i_inf, i_sup = Ival.min_and_max i_ival in - let j_inf, j_sup = Ival.min_and_max j_ival in - let compute_bound_sum bound_lambda is_inf_bound = - let cond = - match bound_lambda with - | Some lambda -> - (is_inf_bound && ((Z.compare lambda Z.zero)=1)) || - ((not is_inf_bound) && ((Z.compare lambda Z.zero)=(-1))) - | None -> false - in - match bound_lambda, i_inf, i_sup, j_inf, j_sup with - | Some lambda, _,Some i_sup, Some j_inf, _ when cond-> - Some (Z.mul lambda (Z.max (Z.sub j_inf i_sup) Z.zero)) - | _, _, _, _, _ when cond -> Some Z.zero - | Some lambda, Some i_inf, _, _, Some j_sup -> - Some (Z.mul lambda (Z.max (Z.sub j_sup i_inf) Z.zero)) - | Some lambda, _, _ , _, _ when (Z.compare lambda Z.zero) = 0 -> - Some Z.zero - | None, Some i_inf, _, _, Some j_sup when (Z.compare j_sup i_inf) = 0 -> - Some Z.zero - | _, _, _, _, _ -> None - in - let compute_bound_product = - (try - match min_lambda, max_lambda, i_inf, i_sup, j_inf, j_sup with - | _, _, Some i_inf, _, _, Some j_sup - when (Z.compare i_inf j_sup) = 1 -> - (Ival.inject_range (Some Z.one) (Some Z.one)) - | Some lambda_inf, Some lambda_sup, Some i_inf, _, _, Some j_sup - when (Z.compare i_inf j_sup) <= 0 && - (Z.compare lambda_inf Z.zero) <= 0 && - (Z.compare Z.zero lambda_sup) <= 0 -> - let exponent = Z.to_int (Z.sub j_sup i_inf) in - let bound = - if (Z.compare lambda_sup (Z.neg lambda_inf)) <= 0 then - (Z.pow (Z.neg lambda_inf) exponent) - else - (Z.pow lambda_sup exponent) - in (Ival.inject_range (Some (Z.neg bound)) (Some bound)) - | Some lambda_inf, Some lambda_sup, Some i_inf, Some i_sup, - Some j_inf, Some j_sup - when (Z.compare i_inf j_sup) <= 0 && - (Z.compare lambda_inf Z.zero) = 1 && - (Z.compare lambda_inf lambda_sup) <= 0 -> - let exponent= Z.to_int (Z.sub j_sup i_inf) in - let bound_inf = - if (Z.compare i_sup j_inf <= 0) then - Z.pow lambda_inf (Z.to_int (Z.sub j_inf i_sup)) - else - Z.one - in - Ival.inject_range - (Some bound_inf) (Some (Z.pow lambda_sup exponent)) - | Some lambda_inf, Some lambda_sup, Some i_inf, _, _, Some j_sup - when (Z.compare i_inf j_sup) <= 0 && - (Z.compare lambda_sup Z.zero)=(-1) && - (Z.compare lambda_inf lambda_sup) <= 0 -> - let bound = Z.pow (Z.neg lambda_inf) (Z.to_int (Z.sub j_sup i_inf)) - in - Ival.inject_range (Some (Z.neg bound)) (Some bound) - | _ -> Ival.inject_range None None - with Z.Overflow -> - (Ival.inject_range None None)) - in - match name.lv_name with - | "\\sum" -> Ival (Ival.inject_range - (compute_bound_sum min_lambda true) - (compute_bound_sum max_lambda false)) - | "\\product" -> Ival compute_bound_product - | _ -> assert false (* unreachable branch *) - with Abstract_interp.Error_Bottom -> - bottom) - | _ -> Error.not_yet "extended quantifiers with non-integer parameters" - let interv_of_logic_typ = function | Ctype ty -> interv_of_typ ty | Linteger -> top_ival @@ -643,7 +532,7 @@ end = struct end (* ********************************************************************* *) -(* Main algorithm *) +(* Main functions *) (* ********************************************************************* *) let infer_sizeof ty = @@ -654,6 +543,85 @@ let infer_alignof ty = try singleton_of_int (Cil.bytesAlignOf ty) with Cil.SizeOfError _ -> interv_of_typ Cil.theMachine.Cil.typeOfSizeOf +(* Infer the interval of an extended quantifier \sum or \product. + [lambda] is the interval of the lambda term, [min] (resp. [max]) is the + interval of the minimum (resp. maximum) and [oper] is the identifier of the + extended quantifier (\sum, or \product). The returned ival is the interval of + the extended quantifier. *) +let infer_sum_product oper lambda min max = match lambda, min, max with + | Ival lbd_iv, Ival lb_iv, Ival ub_iv -> + (try + let min_lambda, max_lambda = Ival.min_and_max lbd_iv in + let minmax_lb = Ival.min_and_max lb_iv in + let minmax_ub = Ival.min_and_max ub_iv in + let lb, ub = match oper.lv_name with + | "\\sum" -> + (* the lower (resp. upper) bound is the min (resp. max) value of the + lambda term, times the min (resp. max) distance between them if + the sign is positive, or conversely if the sign is negative *) + let lb = match min_lambda with + | None -> None + | Some z -> + if Z.sign z = -1 + then Option.map (Z.mul z) (max_delta minmax_lb minmax_ub) + else Some (Z.mul z (min_delta minmax_lb minmax_ub)) + in + let ub = match max_lambda with + | None -> None + | Some z -> + if Z.sign z = -1 + then Some (Z.mul z (min_delta minmax_lb minmax_ub)) + else Option.map (Z.mul z) (max_delta minmax_lb minmax_ub) + in + lb, ub + | "\\product" -> + (* the lower (resp. upper) bound is the min (resp. max) value of the + lambda term in absolute value, power the min (resp. max) distance + between them if the sign is positive, or conversely for both the + lambda term and the exponent if the sign is negative. If the sign + is negative, the minimum is also negative. *) + let min, max = + match min_lambda, max_lambda with + | None, None as res -> res + | None, Some m | Some m, None -> Some m, None + | Some min, Some max -> + let abs_min = Z.abs min in + let abs_max = Z.abs max in + Some (Z.min abs_min abs_max), Some (Z.max abs_min abs_max) + in + let lb = match min_lambda with + | None -> None + | Some z -> + if Z.sign z = -1 then + (* the lower bound is (possibly) negative *) + Extlib.opt_map2 + (fun m max -> Z.neg (Z.pow max (Z.to_int m))) + (max_delta minmax_lb minmax_ub) + max + else + (* all numbers are positive: + the lower bound is necessarily positive *) + Option.map + (fun m -> Z.pow m (Z.to_int (min_delta minmax_lb minmax_ub))) + min + in + let ub = + (* the upper bound is always (possibly) positive *) + Extlib.opt_map2 + (fun m max -> Z.pow max (Z.to_int m)) + (max_delta minmax_lb minmax_ub) + max + in + lb, ub + | s -> + Options.fatal "unexpect logic function '%s'" s + in + Ival (Ival.inject_range lb ub) + with + | Abstract_interp.Error_Bottom -> bottom + | Z.Overflow (* if the exponent of \product is too high *) -> top_ival) + | _ -> Error.not_yet "extended quantifiers with non-integer parameters" + let rec infer t = let get_cty t = match t.term_type with Ctype ty -> ty | _ -> assert false in match t.term_node with @@ -776,28 +744,28 @@ let rec infer t = | LBnone when li.l_var_info.lv_name = "\\sum" || li.l_var_info.lv_name = "\\product" -> (match args with - | [ t1; t2; {term_node = Tlambda([ k ], _)} as lambda ] -> - let t1_ival = infer t1 in - let t2_ival = infer t2 in - let k_ival = join t1_ival t2_ival in - Env.add k k_ival; - let lambda_ival = infer lambda in + | [ t1; t2; { term_node = Tlambda([ k ], _) } as lambda ] -> + let t1_iv = infer t1 in + let t2_iv = infer t2 in + let k_iv = join t1_iv t2_iv in + Env.add k k_iv; + let lambda_iv = infer lambda in Env.remove k; let t2incr = - Logic_const.term (TBinOp(PlusA, t2, Cil.lone ())) Linteger in + Logic_const.term (TBinOp(PlusA, t2, Cil.lone ())) Linteger + in (* it is correct and precise to use k_ival to compute lambda_ival, but not during the code generation since the type used for k is the greatest type between the type of t1 and the type of t2+1, that is why the ival associated to k is updated *) - Env.add k (join t1_ival (infer t2incr)); + Env.add k (join t1_iv (infer t2incr)); (* k is removed during code generation, it is needed for generating the code of the lambda term *) - interv_of_extended_quantifier - lambda_ival t1_ival t2_ival li.l_var_info + infer_sum_product li.l_var_info lambda_iv t1_iv t2_iv | _ -> Error.not_yet "extended quantifiers without lambda term") | LBnone when li.l_var_info.lv_name = "\\numof" -> (match args with - | [ t1; t2; {term_node = Tlambda([ k ], predicate)} ] -> + | [ t1; t2; { term_node = Tlambda([ k ], p) } ] -> let logic_info = Cil_const.make_logic_info "\\sum" in logic_info.l_type <- li.l_type; logic_info.l_tparams <- li.l_tparams; @@ -807,7 +775,7 @@ let rec infer t = let numof_as_sum = let conditional_term = Logic_const.term - (Tif(predicate, Cil.lone (), Cil.lzero ())) Linteger + (Tif(p, Cil.lone (), Cil.lzero ())) Linteger in let lambda_term = Logic_const.term (Tlambda([ k ], conditional_term)) Linteger -- GitLab