diff --git a/Makefile b/Makefile
index d434977cc7305f3d03a6a55f36e40ebabe4c9c77..c20cc4211159b2fee1d40043b7e9d1625cbfc63c 100644
--- a/Makefile
+++ b/Makefile
@@ -900,6 +900,7 @@ PLUGIN_CMO:= partitioning/split_strategy value_parameters \
 	engine/subdivided_evaluation engine/evaluation engine/abstractions \
 	engine/recursion engine/transfer_stmt engine/transfer_specification \
 	engine/partitioning_index engine/mem_exec \
+	partitioning/auto_loop_unroll \
 	engine/partition partitioning/partitioning_parameters \
 	engine/trace_partitioning \
 	engine/iterator \
diff --git a/src/plugins/value/engine/partition.ml b/src/plugins/value/engine/partition.ml
index 644e3bf20b5a617f34df40a686b12dadabab3bf4..eb378dc20bbd738c1af9512c6a722d57450098c7 100644
--- a/src/plugins/value/engine/partition.ml
+++ b/src/plugins/value/engine/partition.ml
@@ -164,6 +164,7 @@ let to_list (p : 'a partition) : 'a list =
 type unroll_limit =
   | ExpLimit of Cil_types.exp
   | IntLimit of int
+  | AutoUnroll of Cil_types.stmt * int * int
 
 type split_kind = Static | Dynamic
 
@@ -219,7 +220,11 @@ struct
   let union (p1 : t) (p2 : t) : t =
     p1 @ p2
 
-  (* --- Evalution and split functions -------------------------------------- *)
+  (* --- Automatic loop unrolling ------------------------------------------- *)
+
+  module AutoLoopUnroll = Auto_loop_unroll.Make (Abstract)
+
+  (* --- Evaluation and split functions ------------------------------------- *)
 
   (* Domains transfer functions. *)
   module TF = Abstract.Dom.Transfer (Abstract.Eval.Valuation)
@@ -403,6 +408,14 @@ struct
           let limit = try match limit_kind with
             | ExpLimit exp -> eval_exp_to_int x exp
             | IntLimit i -> i
+            | AutoUnroll (stmt, min_unroll, max_unroll) ->
+              match AutoLoopUnroll.compute ~max_unroll x stmt with
+              | None -> min_unroll
+              | Some i ->
+                Value_parameters.warning ~once:true ~current:true
+                  ~wkey:Value_parameters.wkey_loop_unroll
+                  "Automatic loop unrolling.";
+                i
             with
             | Operation_failed -> 0
           in
diff --git a/src/plugins/value/engine/partition.mli b/src/plugins/value/engine/partition.mli
index 2a5177c756849b8abeb1bebb32e939066a11374f..cf1c7426b1d5a4dffcf9f10ce5b9278579ca45a9 100644
--- a/src/plugins/value/engine/partition.mli
+++ b/src/plugins/value/engine/partition.mli
@@ -76,12 +76,16 @@ type rationing
 (** Creates a new rationing, that can be used successively on several flows. *)
 val new_rationing: limit:int -> merge:bool -> rationing
 
-(** The unroll limit of a loop can be specified as an integer, or as a C
-    expression, which is evaluated when entering the loop in each incoming
-    state. The expression must always evaluate to a singleton integer. *)
+(** The unroll limit of a loop. *)
 type unroll_limit =
   | ExpLimit of Cil_types.exp
+  (** Value of the expression for each incoming state. The expression must
+      evaluate to a singleton integer in each state.  *)
   | IntLimit of int
+  (** Integer limit. *)
+  | AutoUnroll of Cil_types.stmt * int * int
+  (** [AutoUnroll(stmt, min, max)] requests to find a "good" unrolling limit
+      between [min] and [max] for the loop [stmt]. *)
 
 (** Splits on an expression can be static or dynamic:
     - static splits are processed once: the expression is only evaluated at the
diff --git a/src/plugins/value/partitioning/auto_loop_unroll.ml b/src/plugins/value/partitioning/auto_loop_unroll.ml
new file mode 100644
index 0000000000000000000000000000000000000000..9620f6e21f7bd9d26cc01c79b9f8ebfb1e5355b6
--- /dev/null
+++ b/src/plugins/value/partitioning/auto_loop_unroll.ml
@@ -0,0 +1,422 @@
+(**************************************************************************)
+(*                                                                        *)
+(*  This file is part of Frama-C.                                         *)
+(*                                                                        *)
+(*  Copyright (C) 2007-2019                                               *)
+(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
+(*         alternatives)                                                  *)
+(*                                                                        *)
+(*  you can redistribute it and/or modify it under the terms of the GNU   *)
+(*  Lesser General Public License as published by the Free Software       *)
+(*  Foundation, version 2.1.                                              *)
+(*                                                                        *)
+(*  It is distributed in the hope that it will be useful,                 *)
+(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
+(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
+(*  GNU Lesser General Public License for more details.                   *)
+(*                                                                        *)
+(*  See the GNU Lesser General Public License version 2.1                 *)
+(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
+(*                                                                        *)
+(**************************************************************************)
+
+(* Heuristic for automatic loop unrolling: when the number of iterations of a
+   loop can be bounded under a given limit, then unroll the loop.
+   The limit is defined by the option -eva-auto-loop-unroll. *)
+
+(* Gist of the heuristic:
+   - find a loop exit condition, in the form of a statement "if(cond) break;".
+     such that exactly one lvalue [lval] in the condition [cond] is modified
+     within the loop; all other lvalues must be constant in the loop.
+   - find a value [v_exit] such that [lval] ∈ [v_exit] ⇒ [cond] holds.
+   - evaluate [lval] to its initial value [v_init] in the loop entry state.
+   - compute an over-approximation of the increment [v_delta] of [lval] in one
+     iteration of the loop.
+
+   If [v_init] + k × [v_delta] ⊂ [v_exit], then the number of iterations
+   is bounded by the limit [k].
+
+   The heuristic is syntactic and limited to the current function: it does not
+   handle assignment through pointers or function calls.
+   Thus, the condition [cond] must only contains direct accesses to variables
+   whose address is never taken (they cannot be modified through pointers). If
+   the loop contains a function call, the condition [cond] must not contain any
+   global variable (as they may be modified in the function called).
+   A first analyze of the loop gathers all such variables modified within the
+   loop; all others are constant, and can be evaluated in the loop entry state.
+
+   When computing the increment [v_delta] of a lvalue [v] in the loop, the
+   heuristic searches assignments "v = v ± i;". Any other assignment of [v]
+   cancels the heuristic. *)
+
+open Cil_types
+
+(* Is a statement a loop exit condition? If so, returns the condition and
+   whether the condition must hold to exit the loop. Otherwise, returns None. *)
+let is_conditional_break stmt =
+  match stmt.skind with
+  | If (cond, {bstmts=[{skind=Break _}]}, _, _) -> Some (cond, true)
+  | If (cond, _, {bstmts=[{skind=Break _}]}, _) -> Some (cond, false)
+  | _ -> None
+
+(* Returns a loop exit condition, as the conditional expression and whether
+   the condition must be zero or non-zero to exit the loop. *)
+let find_loop_exit_condition loop =
+  let rec aux = function
+    | [] -> None
+    | stmt :: tl ->
+      match is_conditional_break stmt with
+      | Some _ as x -> x
+      | None -> aux tl
+  in
+  aux loop.bstmts
+
+(* Effects of a loop:
+   - set of varinfos that are directly modified within the loop. Pointer
+     accesses are ignored.
+   - does the loop contain a call? If so, any global variable may also be
+     modified in the loop. *)
+type loop_effect =
+  { written_vars: Cil_datatype.Varinfo.Set.t;
+    call: bool; }
+
+(* Visitor to compute the effects of a loop. *)
+let loop_effect_visitor = object (self)
+  inherit Visitor.frama_c_inplace
+
+  val mutable written_vars = Cil_datatype.Varinfo.Set.empty
+  val mutable call = false
+  val mutable assembly = false
+
+  (* Returns None if the loop contains assembly code. *)
+  method compute_effect block =
+    written_vars <- Cil_datatype.Varinfo.Set.empty;
+    call <- false;
+    assembly <- false;
+    ignore Visitor.(visitFramacBlock (self :> frama_c_inplace) block);
+    if assembly then None else Some { written_vars; call; }
+
+  method !vinst instr =
+    let () = match instr with
+      | Set ((Var varinfo, _), _, _)
+      | Call (Some (Var varinfo, _), _, _, _) ->
+        written_vars <- Cil_datatype.Varinfo.Set.add varinfo written_vars;
+      | _ -> ()
+    in
+    let () = match instr with
+      | Asm _ -> assembly <- true
+      | Call _ -> call <- true
+      | _ -> ()
+    in
+    Cil.SkipChildren
+end
+
+(* The status of a lvalue for the automatic loop unroll heuristic. *)
+type var_status =
+  | Constant  (* The lvalue is constant within the loop. *)
+  | Candidate (* The lvalue is a good candidate for the heuristic:
+                 integer type, access to a varinfo whose address is not taken,
+                 modified within the loop but not in another function called
+                 in the loop. *)
+  | Unsuitable (* Cannot be used for the heuristic. *)
+
+let is_integer lval = Cil.isIntegralType (Cil.typeOfLval lval)
+
+(* Computes the status of a lvalue for the heuristic, according to the
+   loop effects. *)
+let classify loop_effect lval =
+  let rec is_const_expr expr =
+    match expr.enode with
+    | Lval lval -> classify_lval lval = Constant
+    | UnOp (_, e, _) | CastE (_, e) | Info (e, _) -> is_const_expr e
+    | BinOp (_, e1, e2, _) -> is_const_expr e1 && is_const_expr e2
+    | Const _ | SizeOf _ | SizeOfE _ | SizeOfStr _
+    | AlignOf _ | AlignOfE _ | AddrOf _ | StartOf _ -> true
+  and classify_lval = function
+    | Var varinfo, offset ->
+      if varinfo.vaddrof
+      || (varinfo.vglob && loop_effect.call)
+      || not (is_const_offset offset)
+      then Unsuitable
+      else if Cil_datatype.Varinfo.Set.mem varinfo loop_effect.written_vars
+      then if is_integer lval then Candidate else Unsuitable
+      else Constant
+    | Mem _, _ -> Unsuitable (* Pointers are not supported by the heuristic. *)
+  and is_const_offset = function
+    | NoOffset -> true
+    | Field (_, offset) -> is_const_offset offset
+    | Index (e, offset) -> is_const_expr e && is_const_offset offset
+  in
+  classify_lval lval
+
+(* Returns the list of all lvalues appearing in an expression. *)
+let rec get_lvalues expr =
+  match expr.enode with
+  | Lval lval -> [ lval ]
+  | UnOp (_, e, _) | CastE (_, e) | Info (e, _) -> get_lvalues e
+  | BinOp (_op, e1, e2, _typ) -> get_lvalues e1 @ get_lvalues e2
+  | Const _ | SizeOf _ | SizeOfE _ | SizeOfStr _
+  | AlignOf _ | AlignOfE _ | AddrOf _ | StartOf _ -> []
+
+(* Finds the unique candidate lvalue for the automatic loop unrolling
+   heuristic in the expression [expr], if it exists. Returns None otherwise.  *)
+let find_lonely_candidate loop_effect expr =
+  let lvalues = get_lvalues expr in
+  let rec aux acc list =
+    match list with
+    | [] -> acc
+    | lval :: tl ->
+      match classify loop_effect lval with
+      | Unsuitable -> None
+      | Constant   -> aux acc tl
+      | Candidate  -> if acc = None then aux (Some lval) tl else None
+  in
+  aux None lvalues
+
+(* Returns true if the instruction assigns [lval]. *)
+let is_safe_instruction lval = function
+  | Set (lv, _, _)
+  | Call (Some lv, _, _, _) -> not (Cil_datatype.LvalStructEq.equal lval lv)
+  | Call (None, _, _, _) | Local_init _ | Skip _ | Code_annot _ -> true
+  | Asm _ -> false
+
+(* Returns true if the statement may assign [lval] during an iteration of the
+   loop [loop]. [lval] is a candidate for the automatic loop unroll heuristic,
+   and thus is modified within the loop. *)
+let is_safe lval ~loop stmt =
+  (* The current block being checked for a goto statement. *)
+  let current_block = ref None in
+  let rec is_safe_stmt stmt =
+    match stmt.skind with
+    | Instr instr -> is_safe_instruction lval instr
+    | Return _ | Break _ | Continue _ -> true
+    | If (_, b_then, b_else, _) -> is_safe_block b_then && is_safe_block b_else
+    | Block b
+    | Switch (_, b, _, _)
+    | Loop (_, b, _, _, _) -> is_safe_block b
+    | UnspecifiedSequence list ->
+      List.for_all (fun (stmt, _, _, _, _) -> is_safe_stmt stmt) list
+    | Goto (dest, _) -> begin
+        let dest_blocks = Kernel_function.find_all_enclosing_blocks !dest in
+        (* If the goto leaves the loop, then it is safe. *)
+        if List.mem loop dest_blocks then true else
+          (* If the goto moves into the block currently being checked, then it
+             is safe if the block is safe (which we are currently checking). *)
+          match !current_block with
+          | Some current_block when List.mem current_block dest_blocks -> true
+          | _ ->
+            (* Otherwise, we need to check that the whole block englobing
+               both the source and the destination of the goto is safe. *)
+            let block = Kernel_function.common_block !dest stmt in
+            current_block := Some block;
+            (* If this block is the loop itself, then it is not safe, as [lval]
+               is modified within the loop. *)
+            not (block = loop) && is_safe_block block
+      end
+    | _ -> false
+  (* A block is safe if all its statements are safe. *)
+  and is_safe_block block = List.for_all is_safe_stmt block.bstmts in
+  is_safe_stmt stmt
+
+
+module Make (Abstract: Abstractions.Eva) = struct
+
+  open Eval
+  open Abstract
+  module Valuation = Abstract.Eval.Valuation
+  module Clear_Valuation = Clear_Valuation (Valuation)
+
+  let (>>) v f = match v with `Value v -> f v | _ -> None
+  let (>>=) v f = match v with Some v -> f v | None -> None
+
+  (* Reduces the condition "[condition] = [positive]" to a sufficient hypothesis
+     on the value of the expression [expr]: computes a value [v] such that
+     if the expression [expr] evaluates to [v], then [condition] = [positive].
+     [valuation] contains additional hypotheses, i.e. the value of some constant
+     lvalues of the [condition]. All computations must be done in the top state
+     and in the given valuation. *)
+  let reduce_to_expr valuation ~expr ~condition ~positive =
+    let state = Abstract.Dom.top in
+    (* Assumes that the [condition] is [positive]. *)
+    fst (Eval.reduce ~valuation state condition positive)
+    >> fun resulting_valuation ->
+    (* Finds the value of [expr] in the resulting valuation. *)
+    Valuation.find resulting_valuation expr >> fun record ->
+    record.value.v >> fun value ->
+    (* If the value of [expr] is top, no reduction has been performed. *)
+    if Val.(equal top value) then None
+    else
+      (* Otherwise, re-evaluate the condition with the hypothesis that [expr]
+         evaluates to [value]. If the condition is satisfied, then
+         [expr] ∈ [value] ⇒ [condition] = [positive]. *)
+      let valuation = Valuation.add valuation expr record in
+      fst (Eval.evaluate ~valuation ~reduction:false state condition)
+      >> fun (_valuation, v) ->
+      let satisfied =
+        if positive
+        then not Val.(is_included zero v)
+        else Val.(equal zero v)
+      in
+      if satisfied then Some value else None
+
+  (* Same as [reduce_to_expr] above, but builds the proper valuation from the
+     [state]. [state] is the entry state of the loop, and [expr] is the only
+     part of [condition] that is not constant within the loop. [state] can thus
+     be used to evaluate all other subparts of [condition], before computing
+     the value of [expr] that satisfies [condition]. *)
+  let reduce_to_lval_from_state state lval condition positive =
+    let expr = Cil.new_exp ~loc:condition.eloc (Lval lval) in
+    (* Evaluate the [condition] in the given [state]. *)
+    fst (Eval.evaluate state condition) >> fun (valuation, _v) ->
+    (* In the resulting valuation, replace the value of [expr] by [top_int]
+       and removes all expressions depending on [expr]. *)
+    Valuation.find valuation expr >> fun record ->
+    let value = { record.value with v = `Value Val.top_int } in
+    let record = { record with value } in
+    let valuation =
+      Clear_Valuation.clear_englobing_exprs
+        valuation ~expr:condition ~subexpr:expr
+    in
+    let valuation = Valuation.add valuation expr record in
+    reduce_to_expr valuation ~expr ~condition ~positive
+
+  (* Over-approximation of the increment of a lvalue in one loop iteration.*)
+  type delta =
+    { current: Val.t or_bottom; (* current delta being computed*)
+      final: Val.t or_bottom;   (* final delta after a continue statement. *)
+    }
+
+  let join_delta d1 d2 =
+    { current = Bottom.join Val.join d1.current d2.current;
+      final = Bottom.join Val.join d1.final d2.final; }
+
+  let final_delta delta = Bottom.join Val.join delta.current delta.final
+
+  (* Raised when no increment can be computed for the given lvalue in one
+     loop iteration. *)
+  exception NoIncrement
+
+  (* Adds or subtracts the integer value of [expr] to the current delta
+     [delta.current], according to [binop] which can be PlusA or MinusA.
+     Raises NoIncrement if [expr] is not a constant integer expression. *)
+  let add_to_delta binop delta expr =
+    let typ = Cil.typeOf expr in
+    match Cil.constFoldToInt expr with
+    | None -> raise NoIncrement
+    | Some i ->
+      let value = Val.inject_int typ i in
+      let current = match delta.current with
+        | `Bottom -> `Value value
+        | `Value v -> Val.forward_binop typ binop v value
+      in
+      { delta with current }
+
+  (* Adds to [delta] the increment from the assignement of [lval] to the value
+     of [expr]. Raises NoIncrement if this is not an increment of [lval]. *)
+  let rec delta_assign lval delta expr =
+    (* Is the expression [e] equal to the lvalue [lval] (modulo cast)? *)
+    let rec is_lval e = match e.enode with
+      | Lval lv -> Cil_datatype.LvalStructEq.equal lval lv
+      | CastE (typ, e) -> Cil.isIntegralType typ && is_lval e
+      | Info (e, _) -> is_lval e
+      | _ -> false
+    in
+    match expr.enode with
+    | BinOp ((PlusA | MinusA) as binop, e1, e2, _) ->
+      if is_lval e1
+      then add_to_delta binop delta e2
+      else if is_lval e2 && binop = PlusA
+      then add_to_delta binop delta e1
+      else raise NoIncrement
+    | CastE (typ, e) when Cil.isIntegralType typ -> delta_assign lval delta e
+    | Info (e, _) -> delta_assign lval delta e
+    | _ -> raise NoIncrement
+
+  let delta_instruction lval delta = function
+    | Set (lv, expr, _loc) ->
+      if Cil_datatype.LvalStructEq.equal lval lv
+      then delta_assign lval delta expr
+      else delta
+    | Call (Some lv, _, _, _) ->
+      if Cil_datatype.LvalStructEq.equal lval lv
+      then raise NoIncrement (* No increment can be computed for a call. *)
+      else delta
+    | Call (None, _, _, _) | Local_init _ | Skip _ | Code_annot _ -> delta
+    | Asm _ -> raise NoIncrement
+
+  (* Computes an over-approximation of the increment of [lval] in the block
+     [loop]. Only syntactic assignments of [lval] are considered, so [lval]
+     should be a direct access to a variable whose address is not taken,
+     and which should not be global if the loop contains function calls.
+     Returns None if no increment can be computed. *)
+  let compute_delta lval loop =
+    let rec delta_stmt acc stmt =
+      match stmt.skind with
+      | Instr instr -> delta_instruction lval acc instr
+      | Break _ ->
+        (* No increment, as the statement leaves the loop. *)
+        { current = `Bottom; final = `Bottom }
+      | Continue _ ->
+        (* The current increment becomes the final increment. *)
+        { current = `Bottom; final = final_delta acc }
+      | If (_e, b1, b2, _loc) ->
+        join_delta (delta_block acc b1) (delta_block acc b2)
+      | Block b -> delta_block acc b
+      | _ ->
+        (* For other statements, we only check that they do not modify [lval]. *)
+        if is_safe lval ~loop stmt then acc else raise NoIncrement
+    and delta_block acc block =
+      List.fold_left delta_stmt acc block.bstmts
+    in
+    try
+      let zero_delta = { current = `Value Val.zero; final = `Bottom; } in
+      let delta = delta_block zero_delta loop in
+      final_delta delta >> fun d -> Some d
+    with NoIncrement -> None
+
+  (* Evaluates the lvalue [lval] in the state [state]. Returns None if the value
+     may be undeterminate. *)
+  let evaluate_lvalue state lval =
+    fst (Eval.copy_lvalue state lval) >> fun (_valuation, flagged_value) ->
+    if not flagged_value.initialized || flagged_value.escaping
+    then None
+    else flagged_value.v >> fun v -> Some v
+
+  (* Is the number of iterations of a loop bounded by [limit]?
+     [state] is the loop entry state, and [loop_block] the block of the loop. *)
+  let is_bounded_loop state limit loop_block =
+    (* Computes the effect of the loop. Stops if it contains assembly code. *)
+    loop_effect_visitor#compute_effect loop_block >>= fun loop_effect ->
+    (* Finds the first loop exit condition, or stops. *)
+    find_loop_exit_condition loop_block >>= fun (condition, positive) ->
+    (* Finds the unique integer lvalue modified within the loop in [condition].
+       Stops if it does not exist is not a good candidate for the heuristic. *)
+    find_lonely_candidate loop_effect condition >>= fun lval ->
+    (* Reduce [condition] to a sufficient hypothesis over the [lval] value:
+       if [lval] ∈ [v_exit] then [condition = positive]. *)
+    reduce_to_lval_from_state state lval condition positive >>= fun v_exit ->
+    (* Evaluates the initial value [v_init] of [lval] in the loop entry state. *)
+    evaluate_lvalue state lval >>= fun v_init ->
+    (* Computes an over-approximation [v_delta] of the increment of [lval]
+       in one iteration of the loop. *)
+    compute_delta lval loop_block >>= fun v_delta ->
+    let typ = Cil.typeOfLval lval in
+    let limit = Val.inject_int typ (Integer.of_int limit) in
+    (* Checks whether [v_init] + [limit] × [v_delta] ⊂ [v_exit]. *)
+    let binop op v1 v2 = Bottom.non_bottom (Val.forward_binop typ op v1 v2) in
+    let value = binop PlusA v_init (binop Mult limit v_delta) in
+    Some (Val.is_included value v_exit)
+
+  (* Computes an automatic loop unrolling for statement [stmt] in state [state],
+     with a maximum limit. Returns None for no automatic loop unrolling. *)
+  let compute ~max_unroll state stmt =
+    try
+      let kf = Kernel_function.find_englobing_kf stmt in
+      let loop_stmt = Kernel_function.find_enclosing_loop kf stmt in
+      match loop_stmt.skind with
+      | Loop (_code_annot, block, _loc, _, _) ->
+        is_bounded_loop state max_unroll block >>= fun bounded ->
+        if bounded then Some max_unroll else None
+      | _ -> None
+    with Not_found -> None
+end
diff --git a/src/plugins/value/partitioning/auto_loop_unroll.mli b/src/plugins/value/partitioning/auto_loop_unroll.mli
new file mode 100644
index 0000000000000000000000000000000000000000..e16a7ec2e1d1f7e3567de026e8d35441c9e1cd75
--- /dev/null
+++ b/src/plugins/value/partitioning/auto_loop_unroll.mli
@@ -0,0 +1,30 @@
+(**************************************************************************)
+(*                                                                        *)
+(*  This file is part of Frama-C.                                         *)
+(*                                                                        *)
+(*  Copyright (C) 2007-2019                                               *)
+(*    CEA (Commissariat à l'énergie atomique et aux énergies              *)
+(*         alternatives)                                                  *)
+(*                                                                        *)
+(*  you can redistribute it and/or modify it under the terms of the GNU   *)
+(*  Lesser General Public License as published by the Free Software       *)
+(*  Foundation, version 2.1.                                              *)
+(*                                                                        *)
+(*  It is distributed in the hope that it will be useful,                 *)
+(*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *)
+(*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *)
+(*  GNU Lesser General Public License for more details.                   *)
+(*                                                                        *)
+(*  See the GNU Lesser General Public License version 2.1                 *)
+(*  for more details (enclosed in the file licenses/LGPLv2.1).            *)
+(*                                                                        *)
+(**************************************************************************)
+
+(** Heuristic for automatic loop unrolling. *)
+
+module Make (Abstract: Abstractions.Eva) : sig
+
+  val compute:
+    max_unroll:int -> Abstract.Dom.t -> Cil_types.stmt -> int option
+
+end
diff --git a/src/plugins/value/partitioning/partitioning_parameters.ml b/src/plugins/value/partitioning/partitioning_parameters.ml
index b480f4e425da2b3ce6f1dd62384c0ffd326a1151..33ad321a5cba2cdbbf4c053b7f77fb153a91656b 100644
--- a/src/plugins/value/partitioning/partitioning_parameters.ml
+++ b/src/plugins/value/partitioning/partitioning_parameters.ml
@@ -58,7 +58,7 @@ struct
     !Db.Properties.Interp.term_to_exp ~result:None term
 
   let min_loop_unroll = MinLoopUnroll.get ()
-
+  let auto_loop_unroll = AutoLoopUnroll.get ()
   let default_loop_unroll = DefaultLoopUnroll.get ()
 
   let warn_no_loop_unroll stmt =
@@ -76,7 +76,11 @@ struct
         "%s loop without unroll annotation" loop_kind
 
   let unroll stmt =
-    let default = Partition.IntLimit min_loop_unroll in
+    let default =
+      if auto_loop_unroll > min_loop_unroll
+      then Partition.AutoUnroll (stmt, min_loop_unroll, auto_loop_unroll)
+      else Partition.IntLimit min_loop_unroll
+    in
     match get_unroll_annot stmt with
     | [] -> warn_no_loop_unroll stmt; default
     | [None] -> Partition.IntLimit default_loop_unroll
diff --git a/src/plugins/value/value_parameters.ml b/src/plugins/value/value_parameters.ml
index 0f972433fe475cecf2c6f1a3981bafea11ed233f..cd94d41a2ba0eb7db47daf8cc7d43ed7f61d92df 100644
--- a/src/plugins/value/value_parameters.ml
+++ b/src/plugins/value/value_parameters.ml
@@ -812,6 +812,20 @@ module MinLoopUnroll =
 let () = add_precision_dep MinLoopUnroll.parameter
 let () = MinLoopUnroll.set_range 0 max_int
 
+let () = Parameter_customize.set_group precision_tuning
+module AutoLoopUnroll =
+  Int
+    (struct
+      let option_name = "-eva-auto-loop-unroll"
+      let arg_name = "n"
+      let default = 128
+      let help = "limit of the automatic loop unrolling: all loops whose \
+                  number of iterations can be easily bounded by <n> \
+                  are completely unrolled."
+    end)
+let () = add_precision_dep AutoLoopUnroll.parameter
+let () = AutoLoopUnroll.set_range 0 max_int
+
 let () = Parameter_customize.set_group precision_tuning
 module DefaultLoopUnroll =
   Int
diff --git a/src/plugins/value/value_parameters.mli b/src/plugins/value/value_parameters.mli
index 8cda781691b11799c8f0277f35bab8803cc53206..7ead8310a01f463bd9cd8f75d995a2ecce1af21a 100644
--- a/src/plugins/value/value_parameters.mli
+++ b/src/plugins/value/value_parameters.mli
@@ -93,6 +93,7 @@ module SlevelFunction:
 module SlevelMergeAfterLoop: Parameter_sig.Kernel_function_set
 
 module MinLoopUnroll : Parameter_sig.Int
+module AutoLoopUnroll : Parameter_sig.Int
 module DefaultLoopUnroll : Parameter_sig.Int
 module HistoryPartitioning : Parameter_sig.Int
 module ValuePartitioning : Parameter_sig.String_set