diff --git a/infer/src/pulse/PulseFormula.ml b/infer/src/pulse/PulseFormula.ml index eff4319ee..240149d01 100644 --- a/infer/src/pulse/PulseFormula.ml +++ b/infer/src/pulse/PulseFormula.ml @@ -1168,29 +1168,50 @@ end = struct Atom.eval atom' |> sat_of_eval_result + (** return [(new_linear_equalities, phi ∧ atom)], where [new_linear_equalities] is [true] if + [phi.linear_eqs] was changed as a result *) let and_atom atom phi = normalize_atom phi atom >>= function | None -> - Sat phi + Sat (false, phi) | Some (Atom.Equal (Linear l, Const c)) | Some (Atom.Equal (Const c, Linear l)) -> (* NOTE: {!normalize_atom} calls {!Atom.eval}, which normalizes linear equalities so they end up only on one side, hence only this match case is needed to detect linear equalities *) - solve_eq l (LinArith.of_q c) phi + let+ phi' = solve_eq l (LinArith.of_q c) phi in + (true, phi') | Some atom' -> - Sat {phi with atoms= Atom.Set.add atom' phi.atoms} + Sat (false, {phi with atoms= Atom.Set.add atom' phi.atoms}) let normalize_atoms phi = let atoms0 = phi.atoms in - let init = Sat {phi with atoms= Atom.Set.empty} in + let init = Sat (false, {phi with atoms= Atom.Set.empty}) in IContainer.fold_of_pervasives_set_fold Atom.Set.fold atoms0 ~init ~f:(fun acc atom -> - let* phi = acc in - and_atom atom phi ) + let* changed, phi = acc in + let+ changed', phi = and_atom atom phi in + (changed || changed', phi) ) + + + let normalize phi = + (* NOTE: we may consume a quadratic amount of [fuel] here since the fuel here is not consumed by + [normalize_linear_eqs] (i.e. [normalize_linear_eqs] does not return the remaining + fuel). That's ok because there's not much fuel to begin with, and as long as we're making + progress it's probably worth it anyway. *) + let rec normalize_with_fuel ~fuel phi = + let* new_linear_eqs, phi = normalize_linear_eqs ~fuel phi >>= normalize_atoms in + if new_linear_eqs && fuel > 0 then ( + L.d_printfln "new linear equalities, consuming fuel (from %d)" fuel ; + normalize_with_fuel ~fuel:(fuel - 1) phi ) + else ( + L.d_printfln "ran out of fuel when normalizing" ; + Sat phi ) + in + normalize_with_fuel ~fuel:base_fuel phi - let normalize phi = normalize_linear_eqs ~fuel:base_fuel phi >>= normalize_atoms + let and_atom atom phi = and_atom atom phi >>| snd end let and_mk_atom mk_atom op1 op2 phi = diff --git a/infer/src/pulse/unit/PulseFormulaTest.ml b/infer/src/pulse/unit/PulseFormulaTest.ml index 5e554dc5f..bcf3fb964 100644 --- a/infer/src/pulse/unit/PulseFormulaTest.ml +++ b/infer/src/pulse/unit/PulseFormulaTest.ml @@ -198,8 +198,8 @@ let%test_module "normalization" = {| true (no var=var) && - x = -v6 + v8 -1 ∧ y = 1/3·v6 ∧ z = 12 ∧ w = 7 ∧ v = 3 ∧ v7 = v8 -1 - ∧ v8 = 1/12·v9 ∧ v9 = 0 ∧ v10 = 0 + x = -v6 -1 ∧ y = 1/3·v6 ∧ z = 12 ∧ w = 7 ∧ v = 3 ∧ v7 = -1 + ∧ v8 = 0 ∧ v9 = 0 ∧ v10 = 0 && true (no atoms)|}] end ) @@ -242,4 +242,9 @@ let%test_module "non-linear simplifications" = let%expect_test "constant propagation: bitshift" = simplify ~keep:[x_var] (of_binop Shiftlt (of_binop Shiftrt (i 0b111) (i 2)) (i 2) = x) ; [%expect {|true (no var=var) && x = 4 && true (no atoms)|}] + + let%expect_test "non-linear becomes linear" = + normalize (w = (i 2 * z) - i 3 && z = x * y && y = i 2) ; + [%expect + {|z=v8 ∧ w=v7 && x = 1/4·v6 ∧ y = 2 ∧ z = 1/2·v6 ∧ w = v6 -3 && true (no atoms)|}] end )