data-ref: Rework integer handling in split_constant_offset [PR98069]
authorRichard Sandiford <richard.sandiford@arm.com>
Thu, 10 Dec 2020 12:10:00 +0000 (12:10 +0000)
committerRichard Sandiford <richard.sandiford@arm.com>
Thu, 10 Dec 2020 12:10:00 +0000 (12:10 +0000)
PR98069 is about a case in which split_constant_offset miscategorises
an expression of the form:

  int foo;
  …
  POINTER_PLUS_EXPR<base, (sizetype)(INT_MIN - foo) * size>

as:

  base: base
  offset: (sizetype) (-foo) * size
  init: INT_MIN * size

“-foo” overflows when “foo” is INT_MIN, whereas the original expression
didn't overflow in that case.

As discussed in the PR trail, we could simply ignore the fact that
int overflow is undefined and treat it as a wrapping type, but that
is likely to pessimise quite a few cases.

This patch instead reworks split_constant_offset so that:

- it treats integer operations as having an implicit cast to sizetype
- for integer operations, the returned VAR has type sizetype

In other words, the problem becomes to express:

  (sizetype) (OP0 CODE OP1)

as:

  VAR:sizetype + (sizetype) OFF:ssizetype

The top-level integer split_constant_offset will (usually) be a sizetype
POINTER_PLUS operand, so the extra cast to sizetype disappears.  But adding
the cast allows the conversion handling to defer a lot of the difficult
cases to the recursive split_constant_offset call, which can detect
overflow on individual operations.

The net effect is to analyse the access above as:

  base: base
  offset: -(sizetype) foo * size
  init: INT_MIN * size

See the comments in the patch for more details.

gcc/
PR tree-optimization/98069
* tree-data-ref.c (compute_distributive_range): New function.
(nop_conversion_for_offset_p): Likewise.
(split_constant_offset): In the internal overload, treat integer
expressions as having an implicit cast to sizetype and express
them accordingly.  Pass back the range of the original (uncast)
expression in a new range parameter.
(split_constant_offset_1): Likewise.  Rework the handling of
conversions to account for the implicit sizetype casts.

gcc/testsuite/gcc.dg/vect/pr98069.c [new file with mode: 0644]
gcc/tree-data-ref.c

diff --git a/gcc/testsuite/gcc.dg/vect/pr98069.c b/gcc/testsuite/gcc.dg/vect/pr98069.c
new file mode 100644 (file)
index 0000000..e60549f
--- /dev/null
@@ -0,0 +1,22 @@
+long long int var_3 = -166416893043554447LL;
+short var_8 = (short)27092;
+unsigned int var_17 = 75036300U;
+short arr_165[23];
+
+static long c(long e, long f) { return f ? e : f; }
+void __attribute((noipa)) test()
+{
+  for (int b = 0; b < 19; b = var_17)
+    for (int d = (int)(~c(-2147483647 - 1, var_3)) - 2147483647; d < 22; d++)
+      arr_165[d] = var_8;
+}
+
+int main()
+{
+  for (unsigned i_3 = 0; i_3 < 23; ++i_3)
+    arr_165[i_3] = (short)-8885;
+  test();
+  if (arr_165[0] != 27092)
+    __builtin_abort ();
+  return 0;
+}
index e8308ce8250625ee467aac6aa864899b1adc196b..926553b5cac81d20d4a324a2bd6e1547dae0ad61 100644 (file)
@@ -97,6 +97,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-eh.h"
 #include "ssa.h"
 #include "internal-fn.h"
+#include "range-op.h"
+#include "vr-values.h"
 
 static struct datadep_stats
 {
@@ -581,26 +583,196 @@ debug_ddrs (vec<ddr_p> ddrs)
   dump_ddrs (stderr, ddrs);
 }
 
+/* If RESULT_RANGE is nonnull, set *RESULT_RANGE to the range of
+   OP0 CODE OP1, where:
+
+   - OP0 CODE OP1 has integral type TYPE
+   - the range of OP0 is given by OP0_RANGE and
+   - the range of OP1 is given by OP1_RANGE.
+
+   Independently of RESULT_RANGE, try to compute:
+
+     DELTA = ((sizetype) OP0 CODE (sizetype) OP1)
+            - (sizetype) (OP0 CODE OP1)
+
+   as a constant and subtract DELTA from the ssizetype constant in *OFF.
+   Return true on success, or false if DELTA is not known at compile time.
+
+   Truncation and sign changes are known to distribute over CODE, i.e.
+
+     (itype) (A CODE B) == (itype) A CODE (itype) B
+
+   for any integral type ITYPE whose precision is no greater than the
+   precision of A and B.  */
+
+static bool
+compute_distributive_range (tree type, value_range &op0_range,
+                           tree_code code, value_range &op1_range,
+                           tree *off, value_range *result_range)
+{
+  gcc_assert (INTEGRAL_TYPE_P (type) && !TYPE_OVERFLOW_TRAPS (type));
+  if (result_range)
+    {
+      range_operator *op = range_op_handler (code, type);
+      op->fold_range (*result_range, type, op0_range, op1_range);
+    }
+
+  /* The distributive property guarantees that if TYPE is no narrower
+     than SIZETYPE,
+
+       (sizetype) (OP0 CODE OP1) == (sizetype) OP0 CODE (sizetype) OP1
+
+     and so we can treat DELTA as zero.  */
+  if (TYPE_PRECISION (type) >= TYPE_PRECISION (sizetype))
+    return true;
+
+  /* If overflow is undefined, we can assume that:
+
+       X == (ssizetype) OP0 CODE (ssizetype) OP1
+
+     is within the range of TYPE, i.e.:
+
+       X == (ssizetype) (TYPE) X
+
+     Distributing the (TYPE) truncation over X gives:
+
+       X == (ssizetype) (OP0 CODE OP1)
+
+     Casting both sides to sizetype and distributing the sizetype cast
+     over X gives:
+
+       (sizetype) OP0 CODE (sizetype) OP1 == (sizetype) (OP0 CODE OP1)
+
+     and so we can treat DELTA as zero.  */
+  if (TYPE_OVERFLOW_UNDEFINED (type))
+    return true;
+
+  /* Compute the range of:
+
+       (ssizetype) OP0 CODE (ssizetype) OP1
+
+     The distributive property guarantees that this has the same bitpattern as:
+
+       (sizetype) OP0 CODE (sizetype) OP1
+
+     but its range is more conducive to analysis.  */
+  range_cast (op0_range, ssizetype);
+  range_cast (op1_range, ssizetype);
+  value_range wide_range;
+  range_operator *op = range_op_handler (code, ssizetype);
+  bool saved_flag_wrapv = flag_wrapv;
+  flag_wrapv = 1;
+  op->fold_range (wide_range, ssizetype, op0_range, op1_range);
+  flag_wrapv = saved_flag_wrapv;
+  if (wide_range.num_pairs () != 1 || !range_int_cst_p (&wide_range))
+    return false;
+
+  wide_int lb = wide_range.lower_bound ();
+  wide_int ub = wide_range.upper_bound ();
+
+  /* Calculate the number of times that each end of the range overflows or
+     underflows TYPE.  We can only calculate DELTA if the numbers match.  */
+  unsigned int precision = TYPE_PRECISION (type);
+  if (!TYPE_UNSIGNED (type))
+    {
+      wide_int type_min = wi::mask (precision - 1, true, lb.get_precision ());
+      lb -= type_min;
+      ub -= type_min;
+    }
+  wide_int upper_bits = wi::mask (precision, true, lb.get_precision ());
+  lb &= upper_bits;
+  ub &= upper_bits;
+  if (lb != ub)
+    return false;
+
+  /* OP0 CODE OP1 overflows exactly arshift (LB, PRECISION) times, with
+     negative values indicating underflow.  The low PRECISION bits of LB
+     are clear, so DELTA is therefore LB (== UB).  */
+  *off = wide_int_to_tree (ssizetype, wi::to_wide (*off) - lb);
+  return true;
+}
+
+/* Return true if (sizetype) OP == (sizetype) (TO_TYPE) OP,
+   given that OP has type FROM_TYPE and range RANGE.  Both TO_TYPE and
+   FROM_TYPE are integral types.  */
+
+static bool
+nop_conversion_for_offset_p (tree to_type, tree from_type, value_range &range)
+{
+  gcc_assert (INTEGRAL_TYPE_P (to_type)
+             && INTEGRAL_TYPE_P (from_type)
+             && !TYPE_OVERFLOW_TRAPS (to_type)
+             && !TYPE_OVERFLOW_TRAPS (from_type));
+
+  /* Converting to something no narrower than sizetype and then to sizetype
+     is equivalent to converting directly to sizetype.  */
+  if (TYPE_PRECISION (to_type) >= TYPE_PRECISION (sizetype))
+    return true;
+
+  /* Check whether TO_TYPE can represent all values that FROM_TYPE can.  */
+  if (TYPE_PRECISION (from_type) < TYPE_PRECISION (to_type)
+      && (TYPE_UNSIGNED (from_type) || !TYPE_UNSIGNED (to_type)))
+    return true;
+
+  /* For narrowing conversions, we could in principle test whether
+     the bits in FROM_TYPE but not in TO_TYPE have a fixed value
+     and apply a constant adjustment.
+
+     For other conversions (which involve a sign change) we could
+     check that the signs are always equal, and apply a constant
+     adjustment if the signs are negative.
+
+     However, both cases should be rare.  */
+  return range_fits_type_p (&range, TYPE_PRECISION (to_type),
+                           TYPE_SIGN (to_type));
+}
+
 static void
-split_constant_offset (tree exp, tree *var, tree *off,
+split_constant_offset (tree type, tree *var, tree *off,
+                      value_range *result_range,
                       hash_map<tree, std::pair<tree, tree> > &cache,
                       unsigned *limit);
 
-/* Helper function for split_constant_offset.  Expresses OP0 CODE OP1
-   (the type of the result is TYPE) as VAR + OFF, where OFF is a nonzero
-   constant of type ssizetype, and returns true.  If we cannot do this
-   with OFF nonzero, OFF and VAR are set to NULL_TREE instead and false
-   is returned.  */
+/* Helper function for split_constant_offset.  If TYPE is a pointer type,
+   try to express OP0 CODE OP1 as:
+
+     POINTER_PLUS <*VAR, (sizetype) *OFF>
+
+   where:
+
+   - *VAR has type TYPE
+   - *OFF is a constant of type ssizetype.
+
+   If TYPE is an integral type, try to express (sizetype) (OP0 CODE OP1) as:
+
+     *VAR + (sizetype) *OFF
+
+   where:
+
+   - *VAR has type sizetype
+   - *OFF is a constant of type ssizetype.
+
+   In both cases, OP0 CODE OP1 has type TYPE.
+
+   Return true on success.  A false return value indicates that we can't
+   do better than set *OFF to zero.
+
+   When returning true, set RESULT_RANGE to the range of OP0 CODE OP1,
+   if RESULT_RANGE is nonnull and if we can do better than assume VR_VARYING.
+
+   CACHE caches {*VAR, *OFF} pairs for SSA names that we've previously
+   visited.  LIMIT counts down the number of SSA names that we are
+   allowed to process before giving up.  */
 
 static bool
 split_constant_offset_1 (tree type, tree op0, enum tree_code code, tree op1,
-                        tree *var, tree *off,
+                        tree *var, tree *off, value_range *result_range,
                         hash_map<tree, std::pair<tree, tree> > &cache,
                         unsigned *limit)
 {
   tree var0, var1;
   tree off0, off1;
-  enum tree_code ocode = code;
+  value_range op0_range, op1_range;
 
   *var = NULL_TREE;
   *off = NULL_TREE;
@@ -608,35 +780,42 @@ split_constant_offset_1 (tree type, tree op0, enum tree_code code, tree op1,
   switch (code)
     {
     case INTEGER_CST:
-      *var = build_int_cst (type, 0);
+      *var = size_int (0);
       *off = fold_convert (ssizetype, op0);
+      if (result_range)
+       result_range->set (op0, op0);
       return true;
 
     case POINTER_PLUS_EXPR:
-      ocode = PLUS_EXPR;
-      /* FALLTHROUGH */
+      split_constant_offset (op0, &var0, &off0, nullptr, cache, limit);
+      split_constant_offset (op1, &var1, &off1, nullptr, cache, limit);
+      *var = fold_build2 (POINTER_PLUS_EXPR, type, var0, var1);
+      *off = size_binop (PLUS_EXPR, off0, off1);
+      return true;
+
     case PLUS_EXPR:
     case MINUS_EXPR:
-      if (TREE_CODE (op1) == INTEGER_CST)
-       {
-         split_constant_offset (op0, &var0, &off0, cache, limit);
-         *var = var0;
-         *off = size_binop (ocode, off0, fold_convert (ssizetype, op1));
-         return true;
-       }
-      split_constant_offset (op0, &var0, &off0, cache, limit);
-      split_constant_offset (op1, &var1, &off1, cache, limit);
-      *var = fold_build2 (code, type, var0, var1);
-      *off = size_binop (ocode, off0, off1);
+      split_constant_offset (op0, &var0, &off0, &op0_range, cache, limit);
+      split_constant_offset (op1, &var1, &off1, &op1_range, cache, limit);
+      *off = size_binop (code, off0, off1);
+      if (!compute_distributive_range (type, op0_range, code, op1_range,
+                                      off, result_range))
+       return false;
+      *var = fold_build2 (code, sizetype, var0, var1);
       return true;
 
     case MULT_EXPR:
       if (TREE_CODE (op1) != INTEGER_CST)
        return false;
 
-      split_constant_offset (op0, &var0, &off0, cache, limit);
-      *var = fold_build2 (MULT_EXPR, type, var0, op1);
+      split_constant_offset (op0, &var0, &off0, &op0_range, cache, limit);
+      op1_range.set (op1, op1);
       *off = size_binop (MULT_EXPR, off0, fold_convert (ssizetype, op1));
+      if (!compute_distributive_range (type, op0_range, code, op1_range,
+                                      off, result_range))
+       return false;
+      *var = fold_build2 (MULT_EXPR, sizetype, var0,
+                         fold_convert (sizetype, op1));
       return true;
 
     case ADDR_EXPR:
@@ -658,13 +837,10 @@ split_constant_offset_1 (tree type, tree op0, enum tree_code code, tree op1,
 
        if (poffset)
          {
-           split_constant_offset (poffset, &poffset, &off1, cache, limit);
+           split_constant_offset (poffset, &poffset, &off1, nullptr,
+                                  cache, limit);
            off0 = size_binop (PLUS_EXPR, off0, off1);
-           if (POINTER_TYPE_P (TREE_TYPE (base)))
-             base = fold_build_pointer_plus (base, poffset);
-           else
-             base = fold_build2 (PLUS_EXPR, TREE_TYPE (base), base,
-                                 fold_convert (TREE_TYPE (base), poffset));
+           base = fold_build_pointer_plus (base, poffset);
          }
 
        var0 = fold_convert (type, base);
@@ -723,6 +899,7 @@ split_constant_offset_1 (tree type, tree op0, enum tree_code code, tree op1,
                  return false;
                *var = e.first;
                *off = e.second;
+               /* The caller sets the range in this case.  */
                return true;
              }
            e = std::make_pair (op0, ssize_int (0));
@@ -736,72 +913,80 @@ split_constant_offset_1 (tree type, tree op0, enum tree_code code, tree op1,
        var1 = gimple_assign_rhs2 (def_stmt);
 
        bool res = split_constant_offset_1 (type, var0, subcode, var1,
-                                           var, off, cache, limit);
+                                           var, off, nullptr, cache, limit);
        if (res && use_cache)
          *cache.get (op0) = std::make_pair (*var, *off);
+       /* The caller sets the range in this case.  */
        return res;
       }
     CASE_CONVERT:
       {
-       /* We must not introduce undefined overflow, and we must not change
-          the value.  Hence we're okay if the inner type doesn't overflow
-          to start with (pointer or signed), the outer type also is an
-          integer or pointer and the outer precision is at least as large
-          as the inner.  */
+       /* We can only handle the following conversions:
+
+          - Conversions from one pointer type to another pointer type.
+
+          - Conversions from one non-trapping integral type to another
+            non-trapping integral type.  In this case, the recursive
+            call makes sure that:
+
+              (sizetype) OP0
+
+            can be expressed as a sizetype operation involving VAR and OFF,
+            and all we need to do is check whether:
+
+              (sizetype) OP0 == (sizetype) (TYPE) OP0
+
+          - Conversions from a non-trapping sizetype-size integral type to
+            a like-sized pointer type.  In this case, the recursive call
+            makes sure that:
+
+              (sizetype) OP0 == *VAR + (sizetype) *OFF
+
+            and we can convert that to:
+
+              POINTER_PLUS <(TYPE) *VAR, (sizetype) *OFF>
+
+          - Conversions from a sizetype-sized pointer type to a like-sized
+            non-trapping integral type.  In this case, the recursive call
+            makes sure that:
+
+              OP0 == POINTER_PLUS <*VAR, (sizetype) *OFF>
+
+            where the POINTER_PLUS and *VAR have the same precision as
+            TYPE (and the same precision as sizetype).  Then:
+
+              (sizetype) (TYPE) OP0 == (sizetype) *VAR + (sizetype) *OFF.  */
        tree itype = TREE_TYPE (op0);
        if ((POINTER_TYPE_P (itype)
             || (INTEGRAL_TYPE_P (itype) && !TYPE_OVERFLOW_TRAPS (itype)))
-           && TYPE_PRECISION (type) >= TYPE_PRECISION (itype)
-           && (POINTER_TYPE_P (type) || INTEGRAL_TYPE_P (type)))
+           && (POINTER_TYPE_P (type)
+               || (INTEGRAL_TYPE_P (type) && !TYPE_OVERFLOW_TRAPS (type)))
+           && (POINTER_TYPE_P (type) == POINTER_TYPE_P (itype)
+               || (TYPE_PRECISION (type) == TYPE_PRECISION (sizetype)
+                   && TYPE_PRECISION (itype) == TYPE_PRECISION (sizetype))))
          {
-           if (INTEGRAL_TYPE_P (itype) && TYPE_OVERFLOW_WRAPS (itype)
-               && (TYPE_PRECISION (type) > TYPE_PRECISION (itype)
-                   || TYPE_UNSIGNED (itype) != TYPE_UNSIGNED (type)))
+           if (POINTER_TYPE_P (type))
+             {
+               split_constant_offset (op0, var, off, nullptr, cache, limit);
+               *var = fold_convert (type, *var);
+             }
+           else if (POINTER_TYPE_P (itype))
+             {
+               split_constant_offset (op0, var, off, nullptr, cache, limit);
+               *var = fold_convert (sizetype, *var);
+             }
+           else
              {
-               /* Split the unconverted operand and try to prove that
-                  wrapping isn't a problem.  */
-               tree tmp_var, tmp_off;
-               split_constant_offset (op0, &tmp_var, &tmp_off, cache, limit);
-
-               /* See whether we have an known range [A, B] for tmp_var.  */
-               wide_int var_min, var_max;
-               signop sgn = TYPE_SIGN (itype);
-               if (TREE_CODE (tmp_var) == SSA_NAME)
+               split_constant_offset (op0, var, off, &op0_range,
+                                      cache, limit);
+               if (!nop_conversion_for_offset_p (type, itype, op0_range))
+                 return false;
+               if (result_range)
                  {
-                   value_range_kind vr_type
-                     = get_range_info (tmp_var, &var_min, &var_max);
-                   wide_int var_nonzero = get_nonzero_bits (tmp_var);
-                   if (intersect_range_with_nonzero_bits (vr_type, &var_min,
-                                                          &var_max,
-                                                          var_nonzero,
-                                                          sgn) != VR_RANGE)
-                     return false;
+                   *result_range = op0_range;
+                   range_cast (*result_range, type);
                  }
-               else if (determine_value_range (tmp_var, &var_min, &var_max)
-                        != VR_RANGE)
-                 return false;
-
-               /* See whether the range of OP0 (i.e. TMP_VAR + TMP_OFF)
-                  is known to be [A + TMP_OFF, B + TMP_OFF], with all
-                  operations done in ITYPE.  The addition must overflow
-                  at both ends of the range or at neither.  */
-               wi::overflow_type overflow[2];
-               unsigned int prec = TYPE_PRECISION (itype);
-               wide_int woff = wi::to_wide (tmp_off, prec);
-               wide_int op0_min = wi::add (var_min, woff, sgn, &overflow[0]);
-               wi::add (var_max, woff, sgn, &overflow[1]);
-               if ((overflow[0] != wi::OVF_NONE) != (overflow[1] != wi::OVF_NONE))
-                 return false;
-
-               /* Calculate (ssizetype) OP0 - (ssizetype) TMP_VAR.  */
-               widest_int diff = (widest_int::from (op0_min, sgn)
-                                  - widest_int::from (var_min, sgn));
-               var0 = tmp_var;
-               *off = wide_int_to_tree (ssizetype, diff);
              }
-           else
-             split_constant_offset (op0, &var0, off, cache, limit);
-           *var = fold_convert (type, var0);
            return true;
          }
        return false;
@@ -812,33 +997,80 @@ split_constant_offset_1 (tree type, tree op0, enum tree_code code, tree op1,
     }
 }
 
-/* Expresses EXP as VAR + OFF, where off is a constant.  The type of OFF
-   will be ssizetype.  */
+/* If EXP has pointer type, try to express it as:
+
+     POINTER_PLUS <*VAR, (sizetype) *OFF>
+
+   where:
+
+   - *VAR has the same type as EXP
+   - *OFF is a constant of type ssizetype.
+
+   If EXP has an integral type, try to express (sizetype) EXP as:
+
+     *VAR + (sizetype) *OFF
+
+   where:
+
+   - *VAR has type sizetype
+   - *OFF is a constant of type ssizetype.
+
+   If EXP_RANGE is nonnull, set it to the range of EXP.
+
+   CACHE caches {*VAR, *OFF} pairs for SSA names that we've previously
+   visited.  LIMIT counts down the number of SSA names that we are
+   allowed to process before giving up.  */
 
 static void
-split_constant_offset (tree exp, tree *var, tree *off,
+split_constant_offset (tree exp, tree *var, tree *off, value_range *exp_range,
                       hash_map<tree, std::pair<tree, tree> > &cache,
                       unsigned *limit)
 {
-  tree type = TREE_TYPE (exp), op0, op1, e, o;
+  tree type = TREE_TYPE (exp), op0, op1;
   enum tree_code code;
 
-  *var = exp;
-  *off = ssize_int (0);
+  code = TREE_CODE (exp);
+  if (exp_range)
+    {
+      *exp_range = type;
+      if (code == SSA_NAME)
+       {
+         wide_int var_min, var_max;
+         value_range_kind vr_kind = get_range_info (exp, &var_min, &var_max);
+         wide_int var_nonzero = get_nonzero_bits (exp);
+         vr_kind = intersect_range_with_nonzero_bits (vr_kind,
+                                                      &var_min, &var_max,
+                                                      var_nonzero,
+                                                      TYPE_SIGN (type));
+         if (vr_kind == VR_RANGE)
+           *exp_range = value_range (type, var_min, var_max);
+       }
+    }
 
-  if (tree_is_chrec (exp)
-      || get_gimple_rhs_class (TREE_CODE (exp)) == GIMPLE_TERNARY_RHS)
-    return;
+  if (!tree_is_chrec (exp)
+      && get_gimple_rhs_class (TREE_CODE (exp)) != GIMPLE_TERNARY_RHS)
+    {
+      extract_ops_from_tree (exp, &code, &op0, &op1);
+      if (split_constant_offset_1 (type, op0, code, op1, var, off,
+                                  exp_range, cache, limit))
+       return;
+    }
 
-  code = TREE_CODE (exp);
-  extract_ops_from_tree (exp, &code, &op0, &op1);
-  if (split_constant_offset_1 (type, op0, code, op1, &e, &o, cache, limit))
+  *var = exp;
+  if (INTEGRAL_TYPE_P (type))
+    *var = fold_convert (sizetype, *var);
+  *off = ssize_int (0);
+  if (exp_range && code != SSA_NAME)
     {
-      *var = e;
-      *off = o;
+      wide_int var_min, var_max;
+      if (determine_value_range (exp, &var_min, &var_max) == VR_RANGE)
+       *exp_range = value_range (type, var_min, var_max);
     }
 }
 
+/* Expresses EXP as VAR + OFF, where OFF is a constant.  VAR has the same
+   type as EXP while OFF has type ssizetype.  */
+
 void
 split_constant_offset (tree exp, tree *var, tree *off)
 {
@@ -846,7 +1078,8 @@ split_constant_offset (tree exp, tree *var, tree *off)
   static hash_map<tree, std::pair<tree, tree> > *cache;
   if (!cache)
     cache = new hash_map<tree, std::pair<tree, tree> > (37);
-  split_constant_offset (exp, var, off, *cache, &limit);
+  split_constant_offset (exp, var, off, nullptr, *cache, &limit);
+  *var = fold_convert (TREE_TYPE (exp), *var);
   cache->empty ();
 }