On 9/27/26 7:26 AM, Nguyen Tran wrote:
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]>
Thanks for reraising this, it got lost and when you reraised you did so right as folks were traveling for Cauldron/LPC.  Sorry for all that.

---
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.




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
So I know this isn't something you introduced, but I'm not a fan of default arguments like this.  Especially when there aren't a ton of call sites.

While you did the right thing and updated every call site to include the new dividend precision argument, it's possible there's out of tree ports/languages we can't see and which would still build now -- but they run the risk of their optab_methods argument being interpreted as dividend precision.

Given you're adjusting every known call site to handle the new required argument, can you make sure every site as an optab_methods argument and drop the default from the prototype (defaulting to OPTAB_LIB_WIDEN if none is already provided).  At least with that setup an out of tree language or target would fail to build rather than silently doing the wrong thing.  If you'd prefer that to be a followup patch, that's fine with me too, but given this patch touches them all, it seems more natural as part of this patch.

Let me know your preference.  I'm ready to ACK this if you commit to do the followup or to ACK a V4 with that change.

Jeff

Reply via email to