https://gcc.gnu.org/bugzilla/show_bug.cgi?id=50865

--- Comment #22 from GCC Commits <cvs-commit at gcc dot gnu.org> ---
The releases/gcc-15 branch has been updated by Michael Eager
<[email protected]>:

https://gcc.gnu.org/g:382046ec124a92d5a4b589622cf47db4a3189cb9

commit r15-11701-g382046ec124a92d5a4b589622cf47db4a3189cb9
Author: Sam Price <[email protected]>
Date:   Sun Oct 11 02:17:27 2026 -0400

    microblaze: fix the hand-written 64-bit signed modulo

    libgcc/config/microblaze/moddi3.S has been in the tree unchanged since the
    port landed in 2010 (809201325afb); the only commits touching it since are
    copyright-year bumps and the two libgcc reorganizations.  It is wrong for
    almost every input, and for an LLONG_MIN dividend it either fails to
    terminate or returns nonsense, depending on the byte order.

    The fix is Ramin Moussavi's, carried in OpenADK as
    toolchain/gcc/patches/16.2.0/0010; he has agreed to its being submitted
here.

    Four defects:

    1. The register pair order is hard-coded for big-endian.  A 64-bit value
       passed in a register pair keeps its high word in the lower-numbered
       register on big-endian and in the higher-numbered one on little-endian,
       but the file treats r5 as the dividend's high word and r7 as the
       divisor's unconditionally.  On microblazeel it therefore operates on
       both operands with their halves swapped.  microblazeel support arrived
       in 4.8 (76ef61fbd9ed) and this file was never adjusted.

    2. "Make Divisor Positive" negated the high word into r9 instead of r7,
       so a negative divisor kept its high word.  r9 had just been used as the
       scratch for the zero test.

    3. The result sign came from the dividend XOR the divisor; a remainder
       takes the sign of the dividend alone.  Worse, both the sign fix-up and
       the "result is zero" paths wrote r29:r30 -- the quotient, which this
       function does not return -- while the return value is r3:r4.  The zero
       paths also branched in before r3:r4 were cleared, so 0 % v returned
       whatever the caller happened to leave in those registers.

    4. The normalization loop looks for a set MSB after shifting, so a
       dividend of LLONG_MIN -- whose negation stays negative -- shifts its
       only set bit out on the first pass and the loop never terminates.

    Name the halves with macros picked by __MICROBLAZEEL__ so one file serves
    both, clear the remainder before the early exits, take the sign from the
    dividend, negate the divisor's own high word, and skip normalization when
    the dividend is already normalized.

    Defect 3 is also why the quotient goes.  The original computed it in
r29:r30
    and then returned it by mistake; with the return value corrected, nothing
    reads it.  Removing it takes four instructions out of the loop body, two
out
    of the setup and the four that spilled and reloaded it, and lets the loop
tail
    collapse to a single BNEi for one more -- eleven in all.  That is 44 static
    instructions against the original's 60, and over a 4701-value check 2777735
    executed against 3605340 for the same fix keeping the quotient, 23% fewer.
    The dead comparison into r18, whose result was never tested and which
    compared against the dividend's low word rather than the divisor's, goes
too,
    along with the label that no longer has a reference and the branch that
only
    existed to jump over it.

    The keep-or-skip decision reads the sign bit of a 64-bit subtraction to
    settle an unsigned comparison.  That is sound, and the file now says why
    where the comparison is: D <= 2^63 because it is the magnitude of a signed
    64-bit value, and the loop keeps 0 <= RES < D, so RES - D lies in [-D, D-1]
    and is always in signed range, where the sign bit means what the test takes
    it to mean.  The same bound is why a 64-bit partial remainder suffices with
    no 65th bit.

    Division by zero keeps returning zero, matching divsi3.S, modsi3.S,
    udivsi3.S and umodsi3.S.  That differs from a core with hardware divide,
    where a % b is a - (a / b) * b and a zero quotient makes a % 0 give a, but
    the divergence predates this patch; the reasoning is now recorded next to
    the label.

    The comments are rewritten, and the registers and labels are renamed after
    the algorithm rather than their position in the file -- restoring division,
    Koren, Computer Arithmetic Algorithms, section 3.2, whose notation the
    comments now use.

    Verified on qemu-system-microblaze and qemu-system-microblazeel, machine
    petalogix-s3adsp1800:

      - All 1685 tests in gcc.c-torture/execute at -O2, on both byte orders,
        1633 passing either way.  Two of them separate the unpatched routine
        from this one:
        pr50865.c, which is INT64_MIN % 1LL and has been in the tree since PR
        middle-end/50865 in 2011, hangs on big-endian and aborts on
        little-endian before this patch; arith-rand-ll.c aborts on both.  Both
        pass on both afterwards.  The rest are unchanged: the patched routine
        gives the same verdict as libgcc's generic C on every one of the 1685,
        on both byte orders.

      - A 4701-value check -- every sign combination, the 32-bit word
        boundaries, LLONG_MAX, LLONG_MIN and 4000 random pairs, with expected
        values computed on the host -- passes on both.

      - An 8192-value set built to drive the partial remainder to 2^63, which
        is the only place the signed comparison above could have gone wrong,
        passes on both.  arith-rand-ll.c never reaches that state.

    The visible symptom that led here: uClibc-ng's gmtime() reduces a 64-bit
    time_t with a signed modulo, so on microblazeel every timestamp past 2^31
    came back with the wrong seconds field, and some year boundaries were a
    day early.

    libgcc/
            * config/microblaze/moddi3.S: Name the register-pair halves by
            endianness.  Fix the divisor negation, the result sign and the
            zero paths.  Do not normalize an already-normalized dividend.
            Do not compute the quotient, which is not returned.  Drop the
            dead comparison, the unreferenced label and the branch over it.
            Name the working registers and the labels after the reference
            algorithm, and state the invariant the trial subtraction relies
            on.

    Signed-off-by: Sam Price <[email protected]>
            Assisted-by: Claude (Anthropic)
    Signed-off-by: Michael J. Eager <[email protected]>

Reply via email to