master: Implement choice of using fastrem in IR1 transform

snuglas via Sbcl-commits <[email protected]>
Newsgroups gmane.lisp.steel-bank.cvs
Message-ID <[email protected]>
The branch "master" has been updated in SBCL:
       via  914726b494c081ae0a4cfe5e21c92f7532ff7f98 (commit)
      from  cfdbd482aa0e894d85f23664bfeece658df325f1 (commit)

- Log -----------------------------------------------------------------
commit 914726b494c081ae0a4cfe5e21c92f7532ff7f98
Author: Douglas Katzman <[email protected]>
Date:   Sun Apr 19 16:30:24 2026 -0400

    Implement choice of using fastrem in IR1 transform
---
 src/compiler/generic/utils.lisp |  7 -------
 src/compiler/srctran.lisp       | 37 +++++++++++++++++++++++++++++++++++++
 src/compiler/x86-64/arith.lisp  | 40 +---------------------------------------
 tests/integerdiv.pure.lisp      |  7 ++++---
 4 files changed, 42 insertions(+), 49 deletions(-)

diff --git a/src/compiler/generic/utils.lisp b/src/compiler/generic/utils.lisp
index cdff56dab..9a865c658 100644
--- a/src/compiler/generic/utils.lisp
+++ b/src/compiler/generic/utils.lisp
@@ -589,13 +589,6 @@
                     smallest-f d n))
            (values (ceiling (expt 2 fraction-bits) d) fraction-bits)))))
 
-(defun type-width-in-bits (ctype)
-  ;; Return (integer-length upper-bound) of numeric type. This is NOT a theoretical
-  ;; smallest N bits needed to encode an element of type in a packed representation
-  ;; (e.g. the interval 5..8 _could_ be stored in 2 bits) but we don't do that.
-  (if (typep ctype 'numeric-union-type)
-      (integer-length (sb-c::interval-high (sb-c::numeric-type->interval ctype)))))
-
 (defun env-system-tlab-p (env)
   #-system-tlabs (declare (ignore env))
   #+system-tlabs
diff --git a/src/compiler/srctran.lisp b/src/compiler/srctran.lisp
index 4c722a27c..d48a4d38c 100644
--- a/src/compiler/srctran.lisp
+++ b/src/compiler/srctran.lisp
@@ -5123,6 +5123,30 @@
                     (define-source-transform %multiply-high (x y)
                       `(values (sb-bignum:%multiply ,x ,y))))
 
+(defun try-fastrem-algorithm (numerator-precision divisor)
+  ;; The reason for the concern about inputs is that for the general algorithm to accept
+  ;; any 64-bit integer (if FIXNUM, 62 bits) it needs 4 MUL instructions
+  ;; (confirmed by looking at https://github.com/lemire/fastmod)
+  ;; But 2 MUL instructions are enough for reasonable inputs. Both the divisor and the
+  ;; maximum input affect how many bits are needed in intermediate results.
+  (binding* (((magic smallest-nbits)
+              (sb-c:compute-fastrem-coefficient divisor numerator-precision :minimum))
+             (frac-bits
+              ;; "smallest" is how many bits you need for the algorithm to work, assuming
+              ;; you could actually calculate on bit fields of arbitrary size. But to make it
+              ;; efficient, you need a fixed-binary-point reciprocal in exactly 32 bits
+              ;; or exactly 64 bits (or 128, if fully supporting 64-bit ints)
+              (cond ((<= smallest-nbits 32) 32)
+                    #+64-bit
+                    ((<= smallest-nbits 64) 64))))
+    (when frac-bits
+      (when (/= smallest-nbits frac-bits) ; recompute MAGIC to required number of bits
+        (setq magic (sb-c:compute-fastrem-coefficient divisor numerator-precision frac-bits)))
+      (case frac-bits
+        (32 `(values 0 (sb-vm::fastrem-32 x ,magic ,divisor)))
+        #+64-bit
+        (64 `(values 0 (sb-vm::fastrem-64 x ,magic ,divisor)))))))
+
 ;;; If the divisor is constant and both args are positive and fit in a
 ;;; machine word, replace the division by a multiplication and possibly
 ;;; some shifts and an addition. Calculate the remainder by a second
@@ -5152,7 +5176,15 @@
     (let ((rem (and (mv-bind-unused-p result 0)
                     (mv-bind-dest result 1 t)))
           plusp)
+      ;; When only a remainder is needed, there are two ways of going about it:
+      ;; * Using the well-entrenched divide-by-multiplying technique, compute the quotient and
+      ;;   then subtract the product of (quotient x divisor) from the original input.
+      ;; * The Lemire et. al. technique avoids subtracting.  It also offers a way to determine
+      ;;   divisibility (0 remainder) with 1 fewer step, though our way is fairly good too.
+      ;; Therefore, if just getting a remainder and neither COMBINATION-MATCHES-P is true,
+      ;; we try the Lemire transform before falling back to the traditional way.
       (or
+       ;; Choice 1
        (when (and rem
                   (or (combination-matches 'eq '(* 0) rem)
                       (and (combination-matches '> '(* 0) rem)
@@ -5183,6 +5215,11 @@
                                          `(> (rotate-right-word (truly-the word (logand (* x ,inv) ,max-x))
                                                                 ,zeros)
                                              ,(truncate max-x y)))))))))
+       ;; Choice 2
+       (and rem (binding* ((form (try-fastrem-algorithm (integer-length max-x) y) :exit-if-null))
+                  (erase-node-type node t 0)
+                  form))
+       ;; Choice 3
        `(let* ((quot (truly-the (integer 0 ,(truncate max-x y))
                                 ,(gen-unsigned-div-by-constant-expr y max-x)))
                (rem (truly-the (mod ,y)
diff --git a/src/compiler/x86-64/arith.lisp b/src/compiler/x86-64/arith.lisp
index 4f7da4e0c..74209f64a 100644
--- a/src/compiler/x86-64/arith.lisp
+++ b/src/compiler/x86-64/arith.lisp
@@ -2071,44 +2071,6 @@
   (:note "inline fixnum arithmetic")
   (:vop-var vop)
   (:generator 30
-   (block nil
-    ;; This ought to be done machine-independently, but there are already 23 transforms
-    ;; on TRUNCATE and I don't understand how they get tried in the optimal order.
-    ;; The benefit of that is we could just invoke the fastrem-32 or fastrem-64 vop
-    ;; rather than having to replicate them inside here.
-    (when (and (eq (tn-kind quo) :unused)
-               (typep y '(unsigned-byte 64))
-               (csubtypep (tn-ref-type (vop-args vop)) (specifier-type '(unsigned-byte 64))))
-      ;; The reason for the concern about inputs is that the general algorithm to accept
-      ;; any 64-bit integer (which as FIXNUM would limit be 62 bits) needs 4 MUL instructions
-      ;; (confirmed by looking at https://github.com/lemire/fastmod)
-      ;; But 2 MUL instructions is enough for reasonable inputs. Both the divisor and the
-      ;; maximum input affect how many bits are needed in intermediate results.
-      (binding* ((numerator-precision
-                  (type-width-in-bits (tn-ref-type (vop-args vop))) :exit-if-null)
-                 ((magic smallest-nbits)
-                  (sb-c:compute-fastrem-coefficient y numerator-precision :minimum))
-                 ((frac-bits operand-size)
-                  ;; "smallest" is how many bits you need for the algorithm to work, assuming
-                  ;; you could actually calculate on bit fields of arbitrary size. But to make it
-                  ;; efficient, you need a fixed-binary-point reciprocal in exactly 32 bits
-                  ;; or exactly 64 bits (or 128, if fully supporting 64-bit ints)
-                  (cond ((<= smallest-nbits 32) (values 32 :dword))
-                        ((<= smallest-nbits 64) (values 64 :qword)))))
-        (when operand-size
-          (when (/= smallest-nbits frac-bits) ; recompute MAGIC to required number of bits
-            (setq magic (sb-c:compute-fastrem-coefficient y numerator-precision frac-bits)))
-          (let ((arg-size (if (<= numerator-precision 31) :dword :qword)))
-            (move rax x arg-size)
-            (inst shr arg-size rax n-fixnum-tag-bits)) ; untag it
-          (inst mul operand-size (register-inline-constant operand-size magic))
-          (inst mul operand-size (register-inline-constant operand-size y))
-          ;; The result might fit into a :DWORD depending on the divisor
-          (let ((res-size (if (<= (integer-length (1- y)) 31) :dword :qword))) ; (tagged)
-            (if (location= rem rdx)
-                (inst shl res-size rem 1)
-                (inst lea res-size rem (ea rdx rdx))))
-          (return))))
     (move rax x)
     (inst cqo)
     (inst mov y-arg (fixnumize y))
@@ -2119,7 +2081,7 @@
           (if (= n-fixnum-tag-bits 1)
               (inst lea quo (ea rax rax))
               (inst lea quo (ea nil rax (ash 1 n-fixnum-tag-bits))))))
-    (move rem rdx))))
+    (move rem rdx)))
 
 (define-vop (fast-truncate/unsigned=>unsigned fast-safe-arith-op)
   (:translate truncate)
diff --git a/tests/integerdiv.pure.lisp b/tests/integerdiv.pure.lisp
index e28763756..c43cf7b6f 100644
--- a/tests/integerdiv.pure.lisp
+++ b/tests/integerdiv.pure.lisp
@@ -152,12 +152,13 @@
             (try-fastrem divisor 18)))
 
 (declaim (ftype function remN))
-#+x86-64 ; test the TRUNCATE -> fastrem optimization
-(with-test (:name :test-rem-transform)
+(with-test (:name :test-rem-transform :skipped-on (:not :64-bit)) ; incomplete support for 32-bit
   (dolist (dividend-bits '(24 58))
     ;; a divisor of 1235 needs too many intermediate bits for dividend-bits=58
     (dolist (divisor `(3 7 133 ,(if (= dividend-bits 24) 1235 149)))
-      (compile 'remN `(lambda (x) (rem (the (unsigned-byte ,dividend-bits) x) ,divisor)))
+      (compile 'remN `(lambda (x)
+                        (declare (optimize speed))
+                        (rem (the (unsigned-byte ,dividend-bits) x) ,divisor)))
       ;; should not use DIV or IDIV instructions
       (assert (not (search "DIV" (with-output-to-string (s) (disassemble 'remN :stream s)))))
       (dotimes (i 10000)

-----------------------------------------------------------------------


hooks/post-receive
-- 
SBCL
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.