master: Speed up DO-CONSET-ELEMENTS using CTZ if available

snuglas via Sbcl-commits <[email protected]> Fri, 12 Jun 2026 22:42:47 +0000
Newsgroups gmane.lisp.steel-bank.cvs
Message-ID <[email protected]>
The branch "master" has been updated in SBCL:
       via  9ea58046248223f8a2f8bacc12dcddeb75d55f7d (commit)
      from  ce243bfe33cc46c8df1e4597e8e903754ede6439 (commit)

- Log -----------------------------------------------------------------
commit 9ea58046248223f8a2f8bacc12dcddeb75d55f7d
Author: Douglas Katzman <[email protected]>
Date:   Fri Jun 12 17:58:16 2026 -0400

    Speed up DO-CONSET-ELEMENTS using CTZ if available
    
    Often these vectors grow to 8K bits but have only 5 bits set. The iterator
    naively visited every bit. Instead, skip over nonzero words, and then look
    directly at the 1 bits. Also, supposedly the lowest and highest used bit are
    tracked, but the logic to do it seems incorrect in a safely wrong way,
    meaning we have failed to tighten the bounds in many different places.
    
    This change alone was enough for 2x bottom-line improvement on compile-file in
    some files that spent 5% of their compilation time in JOIN-TYPE-CONSTAINTS.
---
 src/compiler/constraint.lisp | 36 ++++++++++++++++++++++++++++++++----
 1 file changed, 32 insertions(+), 4 deletions(-)

diff --git a/src/compiler/constraint.lisp b/src/compiler/constraint.lisp
index dacdbb836..e98905d4c 100644
--- a/src/compiler/constraint.lisp
+++ b/src/compiler/constraint.lisp
@@ -185,7 +185,9 @@
     ;; Bit-vectors win over lightweight hashes for copy, union,
     ;; intersection, difference, but lose for iteration if you iterate
     ;; over the whole vector.  Tracking extrema helps a bit.
-    (min 0 :type fixnum)
+    ;; [Well, it _should_ help if you do it right. We need sentinel
+    ;; values that are not mistakable for actual indices]
+    (min 0 :type fixnum) ; BUG: min of 0 and %constraint-number is always 0
     (max 0 :type fixnum))
 
   #+sb-devel
@@ -456,12 +458,38 @@
 ;;; optimisation to avoid useless copies in ADD-TEST-CONSTRAINTS and
 ;;; FIND-BLOCK-TYPE-CONSTRAINTS.
 (defmacro do-conset-elements ((constraint conset &optional result) &body body)
-  (let ((index (gensym "INDEX"))
-        (conset-vector (gensym "CONSET-VECTOR"))
-        (universe (gensym "UNIVERSE")))
+  (let ((index '#:index) ; gensym considered harmful
+        (conset-vector '#:conset-vector)
+        (universe '#:universe)
+        (word '#:word)
+        (minword '#:min)
+        (maxword '#:max))
+    (declare (ignorable word minword maxword))
     `(let ((,conset-vector (conset-vector ,conset))
+           ;; WITH-VECTOR-DATA on the universe and treating it as simple-vector
+           ;; henceforth could be a further optimization.
            (,universe *constraint-universe*))
        (declare (optimize speed))
+       ;; Notice that in the algorithm based on CTZ we don't actually have to restrict the scan
+       ;; strictly between CONSET-MIN and CONSET-MAX, because those are just hints where to find
+       ;; nonzero bits. Interestingly CONSET-MIN seems to have a flaw-  we don't maintain it
+       ;; properly so it is stuck at 0 thus entirely defeating that optimization.
+       #+(and (not sb-xc-host) (or arm64 x86-64)) ; ctz is not implemented everywhere
+       (let ((,minword (floor (conset-min ,conset) sb-vm:n-word-bits))
+             (,maxword (floor (1- (conset-max ,conset)) sb-vm:n-word-bits)))
+         (aver (< (1- (conset-max ,conset)) (length ,universe)))
+         (loop for ,index of-type index-or-minus-1 from ,minword to ,maxword
+             do (let ((,word (%vector-raw-bits ,conset-vector ,index)))
+                  (declare (sb-vm:word ,word))
+                  (do-anonymous () ((= ,word 0)) ; no NIL block, so RETURN in body gets totally out
+                    (let ((,constraint
+                           (locally (declare (optimize (insert-array-bounds-checks 0)))
+                             (aref ,universe (count-trailing-zeros ,word)))))
+                      ,@body)
+                    ;; Clear the lowest 1 bit via the Brian Kernighan technique (allegedly)
+                    (setq ,word (logand ,word (sb-vm::+-mod64 ,word -1)))))
+             finally (return ,result)))
+       #+(or sb-xc-host (not (or arm64 x86-64)))
        (loop for ,index from (conset-min ,conset) below (conset-max ,conset)
              do (when (plusp (sbit ,conset-vector ,index))
                   (let ((,constraint (aref ,universe ,index)))

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


hooks/post-receive
-- 
SBCL