For divisions by a compile-time-constant divisor, expand_divmod calls
choose_multiplier with the dividend's full type precision (size or
size - 1). When the dividend's value range is known to be narrower
than the type, choose_multiplier could pick a smaller multiplier and
post-shift, but expand_divmod has no path to receive that range
information.
This patch threads the VRP-derived value range from the GIMPLE side
into expand_divmod and forwards it to choose_multiplier:
* expand_expr_divmod (gcc/expr.cc) computes a dividend precision from
treeop0's range via a new helper determine_value_range, which
queries the active range_query during pass_expand.
* For unsigned (or non-negative-known signed) operands the precision
is wi::min_precision(max_val, UNSIGNED). For signed operands with
possibly negative values it is wi::min_precision of the larger
of -(min + 1) and max under unsigned interpretation, the bound
choose_multiplier itself relies on.
* expand_divmod gets a new dividend_prec parameter and an inline
helper adjusted_dividend_prec that passes it to choose_multiplier
raised to LGUP, so the lgup <= precision precondition is preserved.
The helper is invoked at three call sites: unsigned TRUNC, signed
TRUNC, and signed FLOOR. Passing -1 selects the dividend's full
type precision and reproduces the baseline codegen. dividend_prec
is a required parameter, placed before the optional methods
argument; all callers other than expand_expr_divmod pass -1.
For the example in PR target/91883,
unsigned g (unsigned a) {
if (a > 1000000) __builtin_unreachable ();
return a / 10;
}
baseline -O2 emits
movl %edi, %eax
movl $3435973837, %edx
imulq %rdx, %rax
shrq $35, %rax
with this patch:
movl %edi, %eax
imulq $429497139, %rax, %rax
shrq $32, %rax
The smaller multiplier fits imulq's 32-bit signed immediate, saving
the separate movl, and the post-shift drops from 35 to 32. Similar
effects for /3, /7, /100. For signed /7, codegen also shifts from
the long path (addl-fixup) to the short path, reducing seven
instructions to five.
Functions without VRP range information pass -1 and are unaffected.
gcc.target/i386/pr115910.c is updated. The test has two functions
both doing x / 3U: foo with __builtin_unreachable when x < 0, and
bar with no range information. Previously both produced identical
codegen; the test asserted 2 imulq + 2 shrq $33. With this patch foo's
__builtin_unreachable narrows x to [0, INT_MAX] giving VRP-derived
precision 31, so choose_multiplier picks a smaller M (1431655766,
fits imulq's 32-bit immediate) and post-shift 32 instead of 33.
bar is unchanged. The dg-final assertions are split: still 2 imulq,
plus 1 shrq $32 (foo) and 1 shrq $33 (bar).
One drawback of this patch is some code bloat: the same
divisor at two call sites with different ranges produces different
assembly instructions. Unmodified GCC produces the same assembly
instructions regardless of range, allowing the linker's identical-
code-folding (ICF) pass to merge the two function bodies into a
single copy in the final binary. With this patch the bodies differ,
so ICF cannot fold them. Clang has the same drawback: it also
produces different assembly instructions for the same divisor at
different ranges, which ICF cannot fold.
For example, consider /3 at two different ranges:
unsigned int f3 (unsigned int a) {
if (a > 65535) __builtin_unreachable ();
return a / 3;
}
unsigned int f (unsigned int a) {
if (a > 655356) __builtin_unreachable ();
return a / 3;
}
Unmodified GCC produces byte-identical assembly instructions for
both functions, which ICF folds into a single copy:
f3:
mov eax, edi
mov edx, 2863311531
imul rax, rdx
shr rax, 33
ret
f:
mov eax, edi
mov edx, 2863311531
imul rax, rdx
shr rax, 33
ret
Clang produces different assembly instructions; ICF cannot fold them:
f3:
imul eax, edi, 43691
shr eax, 17
ret
f:
mov ecx, edi
mov eax, 2863311531
imul rax, rcx
shr rax, 33
ret
Patched GCC produces different assembly instructions; ICF cannot
fold them:
f3:
mov eax, edi
imul rax, rax, 1431677610
shr rax, 32
ret
f:
mov eax, edi
imul rax, rax, 1431657130
shr rax, 32
ret
A live demonstration: https://godbolt.org/z/75r6axnvG, compiled with
-O2 -ffunction-sections -fuse-ld=gold -Wl,--icf=all
The flags:
-ffunction-sections put each function into its own section, so
the linker can compare and fold them
individually
-fuse-ld=gold use the gold linker (the default linker
does not support ICF)
-Wl,--icf=all enable ICF in the linker
Under unmodified GCC, f3 and f end up at the same address. Under
clang and patched GCC, they end up at different addresses.
Under default compiler/linker flags this patch likely has no meaningful
downside: ICF is not enabled by default, so unmodified and patched
both keep two copies of the body in .text. We consider this an
acceptable tradeoff: each individual division is at least as small
as before, and clang accepts the same one.
Bootstrapped on x86_64-pc-linux-gnu with the default language set
(c, c++, fortran, objc, obj-c++, lto): "make bootstrap" followed by
"make -k check" from the top of the build tree. Stage-2 vs. stage-3
binary comparison successful (the two compilers GCC produces of itself
are byte-identical). No new regressions in the testsuite, and all
dg-final assertions across the seven new gcc.target/i386/divmod-range-*.c
tests pass.
gcc/ChangeLog:
PR target/91883
* explow.cc (round_push, align_dynamic_address): Pass -1 for
dividend_prec.
* expmed.cc (choose_multiplier): Prepend comment stating the
multiply-shift formula it computes.
(adjusted_dividend_prec): New helper.
(expand_divmod): Add dividend_prec parameter. Forward to
adjusted_dividend_prec at the unsigned TRUNC, signed TRUNC,
and signed FLOOR XOR-sign-flip call sites. Pass -1 at the
recursive calls.
* expmed.h (expand_divmod): Add dividend_prec parameter.
* expr.cc (force_operand): Pass -1 for dividend_prec.
(determine_value_range): New helper, queries range_query
during pass_expand.
(expand_expr_divmod): Compute dividend_prec from treeop0's
range and forward to expand_divmod.
* optabs.cc (expand_doubleword_mod, expand_doubleword_divmod):
Pass -1 for dividend_prec.
gcc/testsuite/ChangeLog:
PR target/91883
* gcc.target/i386/pr115910.c: Adjust shrq assertions for
VRP-narrowed multiplier on foo.
* gcc.target/i386/divmod-range-1.c: New test: VRP-narrowed
multiplier for unsigned /10.
* gcc.target/i386/divmod-range-2.c: New test: VRP-narrowed
multiplier for unsigned %10.
* gcc.target/i386/divmod-range-3.c: New test: VRP-narrowed
multiplier for signed /10.
* gcc.target/i386/divmod-range-4.c: New test: VRP shifts
signed /7 from the long (addl-fixup) path to the short path.
* gcc.target/i386/divmod-range-5.c: New test: regression guard,
no narrower range preserves baseline unsigned divmod codegen.
* gcc.target/i386/divmod-range-6.c: New test: regression guard,
no narrower range preserves signed /7 long-path codegen.
* gcc.target/i386/divmod-range-7.c: New test: VRP-narrowed
multiplier for signed FLOOR_DIV (uses GIMPLE Front End since
FLOOR_DIV_EXPR cannot be produced from C source).
Signed-off-by: Nguyen Tran <[email protected]>
---
This is v3 of the patch. v2 was posted here:
https://gcc.gnu.org/pipermail/gcc-patches/2026-June/721044.html
Jeff asked for a rebase and repost. v3 changes:
1. Rebased onto current trunk. The two expand_divmod calls added by
r17-2970 (near-power-of-two divisor split) pass -1 for dividend_prec.
2. Dropped the value-query.h / value-range.h includes from expr.cc.
r17-2970 added gimple-range.h there, which includes value-query.h,
and value-range.h already comes in through gimple.h.
3. Bootstrapped and regression-tested on x86_64-pc-linux-gnu (languages:
c, c++, fortran, objc, obj-c++, lto). "make bootstrap" + "make -k
check" from the top of the build tree; stage-2 vs. stage-3 comparison
is clean (byte-identical), no new regressions.
gcc/explow.cc | 4 +-
gcc/expmed.cc | 50 ++++++++++--
gcc/expmed.h | 3 +-
gcc/expr.cc | 78 ++++++++++++++++---
gcc/optabs.cc | 6 +-
.../gcc.target/i386/divmod-range-1.c | 15 ++++
.../gcc.target/i386/divmod-range-2.c | 14 ++++
.../gcc.target/i386/divmod-range-3.c | 14 ++++
.../gcc.target/i386/divmod-range-4.c | 14 ++++
.../gcc.target/i386/divmod-range-5.c | 13 ++++
.../gcc.target/i386/divmod-range-6.c | 13 ++++
.../gcc.target/i386/divmod-range-7.c | 34 ++++++++
gcc/testsuite/gcc.target/i386/pr115910.c | 3 +-
13 files changed, 239 insertions(+), 22 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/i386/divmod-range-1.c
create mode 100644 gcc/testsuite/gcc.target/i386/divmod-range-2.c
create mode 100644 gcc/testsuite/gcc.target/i386/divmod-range-3.c
create mode 100644 gcc/testsuite/gcc.target/i386/divmod-range-4.c
create mode 100644 gcc/testsuite/gcc.target/i386/divmod-range-5.c
create mode 100644 gcc/testsuite/gcc.target/i386/divmod-range-6.c
create mode 100644 gcc/testsuite/gcc.target/i386/divmod-range-7.c
diff --git a/gcc/explow.cc b/gcc/explow.cc
index ec2bc9bc9b6..e69f4ea1aaf 100644
--- a/gcc/explow.cc
+++ b/gcc/explow.cc
@@ -1117,7 +1117,7 @@ round_push (rtx size)
size = expand_binop (Pmode, add_optab, size, alignm1_rtx,
NULL_RTX, 1, OPTAB_LIB_WIDEN);
size = expand_divmod (0, TRUNC_DIV_EXPR, Pmode, size, align_rtx,
- NULL_RTX, 1);
+ NULL_RTX, 1, -1);
size = expand_mult (Pmode, size, align_rtx, NULL_RTX, 1);
return size;
@@ -1288,7 +1288,7 @@ align_dynamic_address (rtx target, unsigned
required_align)
target = expand_divmod (0, TRUNC_DIV_EXPR, Pmode, target,
gen_int_mode (required_align / BITS_PER_UNIT,
Pmode),
- NULL_RTX, 1);
+ NULL_RTX, 1, -1);
target = expand_mult (Pmode, target,
gen_int_mode (required_align / BITS_PER_UNIT,
Pmode),
diff --git a/gcc/expmed.cc b/gcc/expmed.cc
index b82c0bb10db..05f71dfcd7b 100644
--- a/gcc/expmed.cc
+++ b/gcc/expmed.cc
@@ -3796,6 +3796,15 @@ expand_widening_mult (machine_mode mode, rtx op0, rtx
op1, rtx target,
unsignedp, OPTAB_LIB_WIDEN);
}
+/* For any x satisfying 0 <= x < 2^PRECISION, this function picks
+ integers M and K satisfying
+
+ floor (x / D) = floor ((x * M) / 2^K)
+
+ so an unsigned divide by the constant D can be replaced by one
+ multiply and one right shift. K is internal, recovered from the outputs as
+ K = N + (*POST_SHIFT_PTR). */
+
/* Choose a minimal N + 1 bit approximation to 2**K / D that can be used to
replace division by D, put the least significant N bits of the result in
*MULTIPLIER_PTR, the value K - N in *POST_SHIFT_PTR, and return the most
@@ -4307,6 +4316,20 @@ expand_sdiv_pow2 (scalar_int_mode mode, rtx op0,
HOST_WIDE_INT d)
return expand_shift (RSHIFT_EXPR, mode, temp, logd, NULL_RTX, 0);
}
+/* Return the precision to pass to choose_multiplier. When DIVIDEND_PREC
+ is non-negative, use it as the value-range-derived precision, raised
+ to at least LGUP since choose_multiplier asserts lgup <= precision.
+ When -1, the value range was not used; return DEFAULT_PREC. By
+ construction in the caller, DIVIDEND_PREC never exceeds DEFAULT_PREC. */
+
+static inline int
+adjusted_dividend_prec (int dividend_prec, int default_prec, int lgup)
+{
+ if (dividend_prec >= 0)
+ return dividend_prec < lgup ? lgup : dividend_prec;
+ return default_prec;
+}
+
/* Emit the code to divide OP0 by OP1, putting the result in TARGET
if that is convenient, and returning where the result is.
You may request either the quotient or the remainder as the result;
@@ -4411,7 +4434,7 @@ expand_wide_mulh_udiv (scalar_int_mode int_mode, rtx op0,
rtx
expand_divmod (int rem_flag, enum tree_code code, machine_mode mode,
rtx op0, rtx op1, rtx target, int unsignedp,
- enum optab_methods methods)
+ int dividend_prec, enum optab_methods methods)
{
machine_mode compute_mode;
rtx tquotient;
@@ -4653,7 +4676,12 @@ expand_divmod (int rem_flag, enum tree_code code,
machine_mode mode,
{
/* Find a suitable multiplier and right shift count
instead of directly dividing by D. */
- mh = choose_multiplier (d, size, size,
+
+ /* Use reduced precision if range info available. */
+ int prec = adjusted_dividend_prec (dividend_prec, size,
+ ceil_log2 (d));
+
+ mh = choose_multiplier (d, size, prec,
&ml, &post_shift);
/* If the suggested multiplier is more than SIZE bits,
@@ -4662,6 +4690,9 @@ expand_divmod (int rem_flag, enum tree_code code,
machine_mode mode,
if (mh != 0 && (d & 1) == 0)
{
pre_shift = ctz_or_zero (d);
+
+ /* Only reached when the prior call used full SIZE,
+ so DIVIDEND_PREC here is saturated. */
mh = choose_multiplier (d >> pre_shift, size,
size - pre_shift,
&ml, &post_shift);
@@ -4816,7 +4847,7 @@ expand_divmod (int rem_flag, enum tree_code code,
machine_mode mode,
int_mode, op0,
gen_int_mode (abs_d,
int_mode),
- NULL_RTX, 0);
+ NULL_RTX, 0, -1);
else
quotient = expand_sdiv_pow2 (int_mode, op0, abs_d);
@@ -4841,8 +4872,12 @@ expand_divmod (int rem_flag, enum tree_code code,
machine_mode mode,
}
else if (size <= HOST_BITS_PER_WIDE_INT)
{
- choose_multiplier (abs_d, size, size - 1,
+ int prec = adjusted_dividend_prec (dividend_prec, size - 1,
+ ceil_log2 (abs_d));
+
+ choose_multiplier (abs_d, size, prec,
&ml, &post_shift);
+
if (ml < HOST_WIDE_INT_1U << (size - 1))
{
rtx t1, t2, t3;
@@ -4962,7 +4997,10 @@ expand_divmod (int rem_flag, enum tree_code code,
machine_mode mode,
{
rtx t1, t2, t3, t4;
- mh = choose_multiplier (d, size, size - 1,
+ int prec = adjusted_dividend_prec (dividend_prec, size - 1,
+ ceil_log2 (d));
+
+ mh = choose_multiplier (d, size, prec,
&ml, &post_shift);
gcc_assert (!mh);
@@ -5004,7 +5042,7 @@ expand_divmod (int rem_flag, enum tree_code code,
machine_mode mode,
t3 = force_operand (gen_rtx_MINUS (int_mode, t1, nsign),
NULL_RTX);
t4 = expand_divmod (0, TRUNC_DIV_EXPR, int_mode, t3, op1,
- NULL_RTX, 0);
+ NULL_RTX, 0, -1);
if (t4)
{
rtx t5;
diff --git a/gcc/expmed.h b/gcc/expmed.h
index b4b44bba1d7..0ea14724c5b 100644
--- a/gcc/expmed.h
+++ b/gcc/expmed.h
@@ -713,7 +713,8 @@ extern rtx maybe_expand_shift (enum tree_code,
machine_mode, rtx, int, rtx,
int);
#ifdef GCC_OPTABS_H
extern rtx expand_divmod (int, enum tree_code, machine_mode, rtx, rtx,
- rtx, int, enum optab_methods = OPTAB_LIB_WIDEN);
+ rtx, int, int,
+ enum optab_methods = OPTAB_LIB_WIDEN);
#endif
#endif
diff --git a/gcc/expr.cc b/gcc/expr.cc
index a9157dc5d41..8d3dbc4615b 100644
--- a/gcc/expr.cc
+++ b/gcc/expr.cc
@@ -8847,16 +8847,16 @@ force_operand (rtx value, rtx target)
return expand_divmod (0,
FLOAT_MODE_P (GET_MODE (value))
? RDIV_EXPR : TRUNC_DIV_EXPR,
- GET_MODE (value), op1, op2, target, 0);
+ GET_MODE (value), op1, op2, target, 0, -1);
case MOD:
return expand_divmod (1, TRUNC_MOD_EXPR, GET_MODE (value), op1, op2,
- target, 0);
+ target, 0, -1);
case UDIV:
return expand_divmod (0, TRUNC_DIV_EXPR, GET_MODE (value), op1, op2,
- target, 1);
+ target, 1, -1);
case UMOD:
return expand_divmod (1, TRUNC_MOD_EXPR, GET_MODE (value), op1, op2,
- target, 1);
+ target, 1, -1);
case ASHIFTRT:
return expand_simple_binop (GET_MODE (value), code, op1, op2,
target, 0, OPTAB_LIB_WIDEN);
@@ -9823,6 +9823,35 @@ near_pow2_divisor_range_p (tree op, wide_int &lower)
&& (wi::popcount (lower) == 1 || wi::popcount (upper) == 1));
}
+/* Determine the value range of OP at the current statement.
+ Returns true if range is known, stores bounds in MIN_VAL and MAX_VAL. */
+
+static bool
+determine_value_range (tree op, wide_int *min_val, wide_int *max_val)
+{
+ if (!currently_expanding_gimple_stmt)
+ return false;
+
+ if (TREE_CODE (op) != SSA_NAME)
+ return false;
+
+ tree type = TREE_TYPE (op);
+ if (!INTEGRAL_TYPE_P (type))
+ return false;
+
+ int_range_max r;
+ if (!get_range_query (cfun)->range_of_expr (r, op,
+ currently_expanding_gimple_stmt))
+ return false;
+
+ if (r.undefined_p () || r.varying_p ())
+ return false;
+
+ *min_val = r.lower_bound ();
+ *max_val = r.upper_bound ();
+ return true;
+}
+
/* Helper function of expand_expr_2, expand a division or modulo.
op0 and op1 should be already expanded treeop0 and treeop1, using
expand_operands. */
@@ -9856,9 +9885,9 @@ expand_expr_divmod (tree_code code, machine_mode mode,
tree treeop0,
do_pending_stack_adjust ();
start_sequence ();
rtx q_lower = expand_divmod (0, TRUNC_DIV_EXPR, mode, op0, op_lower,
- NULL_RTX, unsignedp);
+ NULL_RTX, unsignedp, -1);
rtx q_upper = expand_divmod (0, TRUNC_DIV_EXPR, mode, op0, op_upper,
- NULL_RTX, unsignedp);
+ NULL_RTX, unsignedp, -1);
rtx split_ret
= emit_conditional_move (target, { EQ, op1, op_lower, int_mode },
q_lower, q_upper, int_mode, unsignedp);
@@ -9874,6 +9903,34 @@ expand_expr_divmod (tree_code code, machine_mode mode,
tree treeop0,
}
}
+ /* Calculate dividend precision from value range if available. */
+ int dividend_prec = -1;
+
+ if (SCALAR_INT_MODE_P (mode)
+ && optimize >= 2
+ && TREE_CODE (treeop1) == INTEGER_CST)
+ {
+ wide_int min_val, max_val;
+ if (determine_value_range (treeop0, &min_val, &max_val))
+ {
+ if (unsignedp || wi::ges_p (min_val, 0))
+ {
+ /* Unsigned or known non-negative: precision from upper bound. */
+ dividend_prec = wi::min_precision (max_val, UNSIGNED);
+ }
+ else
+ {
+ /* Signed with possible negative values. Take the unsigned
+ precision of the larger of -(min + 1) and max (or just
+ -(min + 1) when max < 0, since -(min + 1) dominates then). */
+ wide_int neg_side = -(min_val + 1);
+ wide_int pos_side = wi::ges_p (max_val, 0) ? max_val : neg_side;
+ wide_int worst = wi::umax (neg_side, pos_side);
+ dividend_prec = wi::min_precision (worst, UNSIGNED);
+ }
+ }
+ }
+
if (SCALAR_INT_MODE_P (mode)
&& optimize >= 2
&& get_range_pos_neg (treeop0, currently_expanding_gimple_stmt) == 1
@@ -9884,10 +9941,12 @@ expand_expr_divmod (tree_code code, machine_mode mode,
tree treeop0,
division or modulo. Choose the cheaper sequence in that case. */
do_pending_stack_adjust ();
start_sequence ();
- rtx uns_ret = expand_divmod (mod_p, code, mode, op0, op1, target, 1);
+ rtx uns_ret = expand_divmod (mod_p, code, mode, op0, op1, target, 1,
+ dividend_prec);
rtx_insn *uns_insns = end_sequence ();
start_sequence ();
- rtx sgn_ret = expand_divmod (mod_p, code, mode, op0, op1, target, 0);
+ rtx sgn_ret = expand_divmod (mod_p, code, mode, op0, op1, target, 0,
+ dividend_prec);
rtx_insn *sgn_insns = end_sequence ();
unsigned uns_cost = seq_cost (uns_insns, speed_p);
unsigned sgn_cost = seq_cost (sgn_insns, speed_p);
@@ -9915,7 +9974,8 @@ expand_expr_divmod (tree_code code, machine_mode mode,
tree treeop0,
emit_insn (sgn_insns);
return sgn_ret;
}
- return expand_divmod (mod_p, code, mode, op0, op1, target, unsignedp);
+ return expand_divmod (mod_p, code, mode, op0, op1, target, unsignedp,
+ dividend_prec);
}
/* Return true if EXP has a range of values [0..1], false
diff --git a/gcc/optabs.cc b/gcc/optabs.cc
index 628500f8c87..ebd20b04d0e 100644
--- a/gcc/optabs.cc
+++ b/gcc/optabs.cc
@@ -1143,7 +1143,7 @@ expand_doubleword_mod (machine_mode mode, rtx op0, rtx
op1, bool unsignedp)
}
rtx remainder = expand_divmod (1, TRUNC_MOD_EXPR, word_mode, sum,
gen_int_mode (INTVAL (op1), word_mode),
- NULL_RTX, 1, OPTAB_DIRECT);
+ NULL_RTX, 1, -1, OPTAB_DIRECT);
if (remainder == NULL_RTX)
return NULL_RTX;
@@ -1246,7 +1246,7 @@ expand_doubleword_divmod (machine_mode mode, rtx op0, rtx
op1, rtx *rem,
if (op11 != const1_rtx)
{
rtx rem2 = expand_divmod (1, TRUNC_MOD_EXPR, mode, quot1, op11,
- NULL_RTX, unsignedp, OPTAB_DIRECT);
+ NULL_RTX, unsignedp, -1, OPTAB_DIRECT);
if (rem2 == NULL_RTX)
return NULL_RTX;
@@ -1261,7 +1261,7 @@ expand_doubleword_divmod (machine_mode mode, rtx op0, rtx
op1, rtx *rem,
return NULL_RTX;
rtx quot2 = expand_divmod (0, TRUNC_DIV_EXPR, mode, quot1, op11,
- NULL_RTX, unsignedp, OPTAB_DIRECT);
+ NULL_RTX, unsignedp, -1, OPTAB_DIRECT);
if (quot2 == NULL_RTX)
return NULL_RTX;
diff --git a/gcc/testsuite/gcc.target/i386/divmod-range-1.c
b/gcc/testsuite/gcc.target/i386/divmod-range-1.c
new file mode 100644
index 00000000000..57ab5149374
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/divmod-range-1.c
@@ -0,0 +1,15 @@
+/* Verify that VRP-derived range info reduces the unsigned divmod multiplier.
*/
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -march=x86-64 -mtune=generic -masm=att" } */
+
+unsigned int
+foo (unsigned int a)
+{
+ if (a > 1000000) __builtin_unreachable ();
+ return a / 10;
+}
+
+/* { dg-final { scan-assembler {\timulq\t\$429497139,} } } */
+/* { dg-final { scan-assembler {\tshrq\t\$32,} } } */
+/* { dg-final { scan-assembler-not {\$3435973837} } } */
+/* { dg-final { scan-assembler-not {\tshrq\t\$35,} } } */
diff --git a/gcc/testsuite/gcc.target/i386/divmod-range-2.c
b/gcc/testsuite/gcc.target/i386/divmod-range-2.c
new file mode 100644
index 00000000000..ac5e9edc7aa
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/divmod-range-2.c
@@ -0,0 +1,14 @@
+/* Verify that VRP-derived range info reduces the unsigned modulo multiplier.
*/
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -march=x86-64 -mtune=generic -masm=att" } */
+
+unsigned int
+foo (unsigned int a)
+{
+ if (a > 1000000) __builtin_unreachable ();
+ return a % 10;
+}
+
+/* { dg-final { scan-assembler {\timulq\t\$429497139,} } } */
+/* { dg-final { scan-assembler {\tshrq\t\$32,} } } */
+/* { dg-final { scan-assembler-not {\$3435973837} } } */
diff --git a/gcc/testsuite/gcc.target/i386/divmod-range-3.c
b/gcc/testsuite/gcc.target/i386/divmod-range-3.c
new file mode 100644
index 00000000000..bbca89b42c0
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/divmod-range-3.c
@@ -0,0 +1,14 @@
+/* Verify that VRP-derived range info reduces the signed-divide-by-10
multiplier. */
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -march=x86-64 -mtune=generic -masm=att" } */
+
+int
+foo (int a)
+{
+ if (a > 1000000 || a < -1000000) __builtin_unreachable ();
+ return a / 10;
+}
+
+/* { dg-final { scan-assembler {\timulq\t\$429497139,} } } */
+/* { dg-final { scan-assembler-not {\$1717986919} } } */
+/* { dg-final { scan-assembler-not {\tsarq\t\$34,} } } */
diff --git a/gcc/testsuite/gcc.target/i386/divmod-range-4.c
b/gcc/testsuite/gcc.target/i386/divmod-range-4.c
new file mode 100644
index 00000000000..ea9b7926862
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/divmod-range-4.c
@@ -0,0 +1,14 @@
+/* Verify that VRP-derived range info shifts signed /7 from LONG to SHORT
codegen. */
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -march=x86-64 -mtune=generic -masm=att" } */
+
+int
+foo (int a)
+{
+ if (a > 1000000 || a < -1000000) __builtin_unreachable ();
+ return a / 7;
+}
+
+/* { dg-final { scan-assembler {\timulq\t\$613567341,} } } */
+/* { dg-final { scan-assembler-not {\$-1840700269} } } */
+/* { dg-final { scan-assembler-not {\taddl\t%edi, %eax} } } */
diff --git a/gcc/testsuite/gcc.target/i386/divmod-range-5.c
b/gcc/testsuite/gcc.target/i386/divmod-range-5.c
new file mode 100644
index 00000000000..195f41bca9b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/divmod-range-5.c
@@ -0,0 +1,13 @@
+/* Regression test: dividend with no narrower range must preserve baseline
+ codegen. */
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -march=x86-64 -mtune=generic -masm=att" } */
+
+unsigned int
+foo (unsigned int a)
+{
+ return a / 10;
+}
+
+/* { dg-final { scan-assembler {\$3435973837} } } */
+/* { dg-final { scan-assembler {\tshrq\t\$35,} } } */
diff --git a/gcc/testsuite/gcc.target/i386/divmod-range-6.c
b/gcc/testsuite/gcc.target/i386/divmod-range-6.c
new file mode 100644
index 00000000000..b1a178bca9f
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/divmod-range-6.c
@@ -0,0 +1,13 @@
+/* Regression test: dividend with no narrower range must preserve LONG-path
+ codegen. */
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -march=x86-64 -mtune=generic -masm=att" } */
+
+int
+foo (int a)
+{
+ return a / 7;
+}
+
+/* { dg-final { scan-assembler {\$-1840700269} } } */
+/* { dg-final { scan-assembler {\taddl\t%edi, %eax} } } */
diff --git a/gcc/testsuite/gcc.target/i386/divmod-range-7.c
b/gcc/testsuite/gcc.target/i386/divmod-range-7.c
new file mode 100644
index 00000000000..524b14b824c
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/divmod-range-7.c
@@ -0,0 +1,34 @@
+/* Verify that VRP-derived range info reduces the signed FLOOR_DIV multiplier.
+ Uses __GIMPLE FE because FLOOR_DIV_EXPR cannot be produced from C source.
*/
+/* { dg-do compile { target { ! ia32 } } } */
+/* { dg-options "-O2 -fgimple -march=x86-64 -mtune=generic -masm=att" } */
+
+int __GIMPLE (ssa)
+foo (int a)
+{
+ int t_2;
+ unsigned int au_4;
+ unsigned int range_5;
+
+__BB(2):
+ au_4 = (unsigned int) a_3(D);
+ range_5 = au_4 + 1000000u;
+ if (range_5 > 2000000u)
+ goto __BB3;
+ else
+ goto __BB4;
+
+__BB(3):
+ __builtin_unreachable ();
+
+__BB(4):
+ t_2 = a_3(D) __FLOOR_DIV 7;
+ return t_2;
+}
+
+/* { dg-final { scan-assembler {\timulq\t\$613567341,} } } */
+/* { dg-final { scan-assembler {\tshrq\t\$32,} } } */
+/* { dg-final { scan-assembler {\tsarl\t\$31,} } } */
+/* { dg-final { scan-assembler-times {\txorl\t} 2 } } */
+/* { dg-final { scan-assembler-not {\$2454267027} } } */
+/* { dg-final { scan-assembler-not {\tshrq\t\$34,} } } */
diff --git a/gcc/testsuite/gcc.target/i386/pr115910.c
b/gcc/testsuite/gcc.target/i386/pr115910.c
index 5f1cd9aa010..41e94142392 100644
--- a/gcc/testsuite/gcc.target/i386/pr115910.c
+++ b/gcc/testsuite/gcc.target/i386/pr115910.c
@@ -2,7 +2,8 @@
/* { dg-do compile { target { ! ia32 } } } */
/* { dg-options "-O2 -march=x86-64 -mtune=generic -masm=att" } */
/* { dg-final { scan-assembler-times {\timulq\t} 2 } } */
-/* { dg-final { scan-assembler-times {\tshrq\t\$33,} 2 } } */
+/* { dg-final { scan-assembler-times {\tshrq\t\$32,} 1 } } */
+/* { dg-final { scan-assembler-times {\tshrq\t\$33,} 1 } } */
/* { dg-final { scan-assembler-not {\tsarl\t} } } */
int
--
2.25.1