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)
---
 libgcc/config/microblaze/moddi3.S | 221 ++++++++++++++++++++----------
 1 file changed, 145 insertions(+), 76 deletions(-)

diff --git a/libgcc/config/microblaze/moddi3.S 
b/libgcc/config/microblaze/moddi3.S
index 71a67c11c..da607f5b8 100644
--- a/libgcc/config/microblaze/moddi3.S
+++ b/libgcc/config/microblaze/moddi3.S
@@ -23,9 +23,29 @@
 #  see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
 #  <http://www.gnu.org/licenses/>. 
 #
-#  modsi3.S
-# 
-#  modulo operation for 64 bit integers.
+#  moddi3.S
+#
+#  64-bit signed remainder.
+#
+#  Binary restoring division, one quotient bit per iteration:
+#
+#      r[i] = 2*r[i-1] - q[i]*D          (r[0] = X)
+#
+#  Each step shifts one dividend bit into the partial remainder and
+#  subtracts the divisor, keeping the result only when it does not go
+#  negative.  The restore is implicit: the trial subtraction lands in
+#  scratch and is copied back only when kept, so a rejected step costs
+#  nothing.  The shift needs one bit more than the register width, which is
+#  the carry flag -- hence the ADD/ADDC chain rather than a shift.
+#
+#  The quotient the recurrence produces is not kept: this function returns
+#  the remainder, so the bit is tested and discarded.
+#
+#  The operands are reduced to unsigned magnitudes first, and the remainder
+#  takes the sign of the dividend alone, not the exclusive or of the operand
+#  signs -- that is the quotient's rule.
+#
+#  Koren, Computer Arithmetic Algorithms, section 3.2 is the reference.
 # 
 #######################################
 
@@ -36,86 +56,135 @@
 .previous
 #endif
 
+/* A 64-bit value passed in a register pair keeps its high word in the
+   lower-numbered register on big-endian and the higher-numbered one on
+   little-endian, so name the halves and let the body read the same either
+   way.  Letters are Koren's: X dividend, D divisor, R remainder.
+
+     OPX   X.  The loop consumes it from the top, so once it is running OPX
+           holds only the bits of X not yet shifted into the remainder.
+     OPD   D.  Not modified after the sign fix-up.
+     RES   R, the return value; during the loop the partial remainder r[i],
+           with RES:OPX the double-length remainder register.  */
+#ifdef __MICROBLAZEEL__
+# define OPX_L r5
+# define OPX_H r6
+# define OPD_L r7
+# define OPD_H r8
+# define RES_L r3
+# define RES_H r4
+#else
+# define OPX_H r5
+# define OPX_L r6
+# define OPD_H r7
+# define OPD_L r8
+# define RES_H r3
+# define RES_L r4
+#endif
+
+/* Working registers.  A pair only needs the endianness treatment above when
+   it crosses an ABI boundary; TRIAL does not, so its halves are fixed.  What
+   does matter is instruction order: low half first, since ADD and RSUB set
+   the carry ADDC and RSUBC consume.  */
+#define TRIAL_H        r25     /* The trial subtraction 2*r[i-1] - D.  */
+#define TRIAL_L        r26
+#define SIGN   r27     /* Sign of X, kept as the whole high word because only
+                          BGEI reads it and that tests the sign.  */
+#define COUNT  r28     /* Iterations remaining, 64 less whatever
+                          normalization skipped.  */
+#define ZERO_T r9      /* Scratch for the two zero tests.  An argument
+                          register, so caller-saved: free to clobber, and it
+                          needs no slot in the frame below.  */
+
        .globl  __moddi3
        .ent    __moddi3
 __moddi3:
-       .frame  r1,0,r15        
+       .frame  r1,0,r15
 
-#Change the stack pointer value and Save callee saved regs
-       addik   r1,r1,-24
-       swi     r25,r1,0
-       swi     r26,r1,4
-       swi     r27,r1,8        # used for sign
-       swi     r28,r1,12       # used for loop count
-       swi     r29,r1,16       # Used for div value High
-       swi     r30,r1,20       # Used for div value Low
+       # Allocate the stack frame and save callee-saved registers.
+       addik   r1,r1,-16
+       swi     TRIAL_H,r1,0
+       swi     TRIAL_L,r1,4
+       swi     SIGN,r1,8
+       swi     COUNT,r1,12
 
-#Check for Zero Value in the divisor/dividend
-       OR      r9,r5,r6                        # Check for the op1 being zero
-       BEQID   r9,$LaResult_Is_Zero            # Result is zero
-       OR      r9,r7,r8                        # Check for the dividend being 
zero
-       BEQI    r9,$LaDiv_By_Zero               # Div_by_Zero   # Division Error
-       BGEId   r5,$La1_Pos 
-       XOR     r27,r5,r7                       # Get the sign of the result
-       RSUBI   r6,r6,0                         # Make dividend positive
-       RSUBIC  r5,r5,0                         # Make dividend positive
-$La1_Pos:
-       BGEI    r7,$La2_Pos
-       RSUBI   r8,r8,0                         # Make Divisor Positive
-       RSUBIC  r9,r9,0                         # Make Divisor Positive
-$La2_Pos:
-       ADDIK   r4,r0,0                         # Clear mod low
-       ADDIK   r3,r0,0                         # Clear mod high
-       ADDIK   r29,r0,0                        # clear div high
-       ADDIK   r30,r0,0                        # clear div low
-       ADDIK   r28,r0,64                       # Initialize the loop count
-   # First part try to find the first '1' in the r5/r6
-$LaDIV1:
-       ADD     r6,r6,r6
-       ADDC    r5,r5,r5                        # left shift logical r5
-       BGEID   r5,$LaDIV1                      
-       ADDIK   r28,r28,-1
-$LaDIV2:
-       ADD     r6,r6,r6
-       ADDC    r5,r5,r5        # left shift logical r5/r6 get the '1' into the 
Carry
-       ADDC    r4,r4,r4        # Move that bit into the Mod register
-       ADDC    r3,r3,r3        # Move carry into high mod register
-       rsub    r18,r7,r3       # Compare the High Parts of Mod and Divisor
-       bnei    r18,$L_High_EQ
-       rsub    r18,r6,r4       # Compare Low Parts only if Mod[h] == Divisor[h]
-$L_High_EQ:    
-       rSUB    r26,r8,r4       # Subtract divisor[L] from Mod[L]
-       rsubc   r25,r7,r3       # Subtract divisor[H] from Mod[H]
-       BLTi    r25,$LaMOD_TOO_SMALL
-       OR      r3,r0,r25       # move r25 to mod [h]
-       OR      r4,r0,r26       # move r26 to mod [l]
-       ADDI    r30,r30,1
-       ADDC    r29,r29,r0
-$LaMOD_TOO_SMALL:
-       ADDIK   r28,r28,-1
-       BEQi    r28,$LaLOOP_END
-       ADD     r30,r30,r30             # Shift in the '1' into div [low]
-       ADDC    r29,r29,r29             # Move the carry generated into high
-       BRI     $LaDIV2   # Div2
-$LaLOOP_END:
-       BGEI    r27,$LaRETURN_HERE
-       rsubi   r30,r30,0
-       rsubc   r29,r29,r0
-       BRI     $LaRETURN_HERE
+       ADDIK   RES_L,r0,0      # Clear the remainder.
+       ADDIK   RES_H,r0,0
+
+       # Handle a zero dividend and a zero divisor.
+       OR      ZERO_T,OPX_H,OPX_L
+       BEQID   ZERO_T,$LaRETURN_HERE   # 0 % n == 0.  The delay-slot OR only
+                                       # clobbers ZERO_T, dead at the target.
+       OR      ZERO_T,OPD_H,OPD_L
+       BEQI    ZERO_T,$LaDiv_By_Zero
+       # Reduce to unsigned magnitudes.  Negating LLONG_MIN leaves it negative
+       # as a signed value, but the bit pattern is 2^63, which is the
+       # magnitude the loop wants -- so "Abs" holds when read as unsigned.
+       BGEId   OPX_H,$LaX_Abs
+       OR      SIGN,r0,OPX_H           # Save the sign of X.
+       RSUBI   OPX_L,OPX_L,0
+       RSUBIC  OPX_H,OPX_H,0
+$LaX_Abs:
+       BGEI    OPD_H,$LaD_Abs
+       RSUBI   OPD_L,OPD_L,0
+       RSUBIC  OPD_H,OPD_H,0
+$LaD_Abs:
+       ADDIK   COUNT,r0,64             # One iteration per bit of X.
+       # Already normalized: all 64 bits count.  Skipping this check would
+       # hang on LLONG_MIN, whose magnitude has only the top bit set --
+       # normalization would shift it out and never see a clear sign bit.
+       BLTi    OPX_H,$LaRecurrence
+
+       # Skip the dividend's leading zero bits, dropping the loop count to
+       # match.  This changes no value; it only passes over iterations whose
+       # quotient bits are known to be zero.
+$LaNormalize:
+       ADD     OPX_L,OPX_L,OPX_L
+       ADDC    OPX_H,OPX_H,OPX_H
+       BGEID   OPX_H,$LaNormalize
+       ADDIK   COUNT,COUNT,-1
+$LaRecurrence:
+       # Form 2*r[i-1]: shift the RES:OPX pair left one bit, so the dividend's
+       # top bit leaves OPX through the carry and enters RES at the bottom.
+       # Order matters -- the carry flows low half to high.
+       ADD     OPX_L,OPX_L,OPX_L
+       ADDC    OPX_H,OPX_H,OPX_H
+       ADDC    RES_L,RES_L,RES_L
+       ADDC    RES_H,RES_H,RES_H
+       # Subtract D into scratch and read the sign: q[i] = 1 when the result
+       # is non-negative.  BLTi is a signed test settling an unsigned
+       # comparison, which holds because D <= 2^63 and the loop keeps
+       # 0 <= RES < D, so RES - D lands in [-D, D-1] -- always in signed
+       # range, where the sign bit is the sign of the true difference.  That
+       # bound is also why RES needs no 65th bit.
+       RSUB    TRIAL_L,OPD_L,RES_L
+       RSUBC   TRIAL_H,OPD_H,RES_H
+       BLTi    TRIAL_H,$LaQ_Bit_Is_Zero        # q[i] = 0; RES still holds
+                                       # r[i-1], nothing to restore.
+       OR      RES_H,r0,TRIAL_H        # q[i] = 1.  Keep 2*r[i-1] - D.
+       OR      RES_L,r0,TRIAL_L
+$LaQ_Bit_Is_Zero:
+       ADDIK   COUNT,COUNT,-1
+       BNEi    COUNT,$LaRecurrence
+       BGEI    SIGN,$LaRETURN_HERE
+       # The remainder takes the sign of the dividend, not the exclusive or
+       # of the operand signs -- that is the quotient's rule.
+       RSUBI   RES_L,RES_L,0
+       RSUBIC  RES_H,RES_H,0
+       # Falls through: $LaDiv_By_Zero holds no instructions.
 $LaDiv_By_Zero:
-$LaResult_Is_Zero:
-       or      r29,r0,r0       # set result to 0 [High]
-       or      r30,r0,r0       # set result to 0 [Low]
+       # Division by zero is undefined in C.  Return zero, as divsi3.S,
+       # modsi3.S, udivsi3.S and umodsi3.S do.  Note this does not match a
+       # core with hardware divide, where a % b is a - (a / b) * b and a
+       # zero quotient makes the remainder a; that divergence predates this
+       # file and is discussed in the commit message.
 $LaRETURN_HERE:
-# Restore values of CSRs and that of r29 and the divisor and the dividend
-       
-       lwi     r25,r1,0
-       lwi     r26,r1,4
-       lwi     r27,r1,8
-       lwi     r28,r1,12
-       lwi     r29,r1,16
-       lwi     r30,r1,20
+       # Restore the callee-saved registers and release the stack frame.
+       lwi     TRIAL_H,r1,0
+       lwi     TRIAL_L,r1,4
+       lwi     SIGN,r1,8
+       lwi     COUNT,r1,12
        rtsd    r15,8
-       addik r1,r1,24
+       addik   r1,r1,16
         .end __moddi3
        
-- 
2.39.5 (Apple Git-154)

Reply via email to