master: Don't cons a new list for (length (intersection a b))

stassats via Sbcl-commits <[email protected]> Wed, 10 Jun 2026 15:33:05 +0000
Newsgroups gmane.lisp.steel-bank.cvs
Message-ID <[email protected]>
The branch "master" has been updated in SBCL:
       via  0298a35f891649f62217f5506d91268e00f48a9b (commit)
      from  3991dc73d6871cd88558dee9aab0a37adb5cb18c (commit)

- Log -----------------------------------------------------------------
commit 0298a35f891649f62217f5506d91268e00f48a9b
Author: Stas Boukarev <[email protected]>
Date:   Wed Jun 10 18:10:10 2026 +0300

    Don't cons a new list for (length (intersection a b))
---
 src/code/list.lisp           | 32 ++++++++++++++++++++++++++++++++
 src/compiler/array-tran.lisp |  5 ++---
 src/compiler/fndb.lisp       | 10 ++++++++++
 src/compiler/ir1opt.lisp     | 37 ++++++++++++++++---------------------
 src/compiler/ir1util.lisp    |  7 ++++---
 src/compiler/node.lisp       |  2 +-
 src/compiler/srctran.lisp    | 11 +++++------
 7 files changed, 70 insertions(+), 34 deletions(-)

diff --git a/src/code/list.lisp b/src/code/list.lisp
index 442b9b1a7..4067bb3f2 100644
--- a/src/code/list.lisp
+++ b/src/code/list.lisp
@@ -1131,6 +1131,38 @@
                          (push elt res)))))))
           res)))))
 
+(defun length-intersection (list1 list2
+                            &key key (test nil testp) (test-not nil notp))
+  (declare (dynamic-extent key test test-not)
+           (explicit-check key test test-not)
+           (optimize speed))
+  (when (and testp notp)
+    (error ":TEST and :TEST-NOT were both supplied."))
+  (let ((length 0))
+    (declare (fixnum length))
+    (with-member-test (member-test)
+      (when (and list1 list2)
+        (multiple-value-bind (short long short-length long-nthcdr)
+            (shorter-list-length list1 list2)
+          (let ((hash-table (hashing-p notp testp test short-length long-nthcdr)))
+            (cond (hash-table
+                   (dolist (elt short)
+                     (setf (gethash (apply-key key elt) hash-table) t))
+                   (dolist (elt long)
+                     (when (gethash (apply-key key elt) hash-table)
+                       (incf (truly-the index length)))))
+                  (t
+                   (flet ((swapped-test (x y)
+                            (funcall (truly-the function test) y x)))
+                     (declare (dynamic-extent #'swapped-test))
+                     (let ((test (if (eq list1 short)
+                                     test
+                                     (and test #'swapped-test))))
+                       (dolist (elt short)
+                         (when (funcall member-test elt long key test)
+                           (incf (truly-the index length))))))))))))
+    length))
+
 ;;; A boolean variant
 (defun intersection-p (list1 list2
                        &key key (test nil testp) (test-not nil notp))
diff --git a/src/compiler/array-tran.lisp b/src/compiler/array-tran.lisp
index 7e1adaba5..b3a2ca36f 100644
--- a/src/compiler/array-tran.lisp
+++ b/src/compiler/array-tran.lisp
@@ -2804,9 +2804,8 @@
        (delay-ir1-transform node :ir1-phases)
        ;; Handle (make-array n :element-type `(signed-byte ,x))
        ;; without consing
-       (let ((args (splice-fun-args type 'list nil nil)))
-         (when args
-           (make-transform-lambda 'sb-vm::%vector-widetag-and-n-bits-shift-list args)))))
+       (make-transform-lambda 'sb-vm::%vector-widetag-and-n-bits-shift-list
+                              (splice-fun-args type 'list nil nil))))
     (t
      (give-up-ir1-transform "ELEMENT-TYPE is not constant."))))
 
diff --git a/src/compiler/fndb.lisp b/src/compiler/fndb.lisp
index 622d27a08..618e5dabe 100644
--- a/src/compiler/fndb.lisp
+++ b/src/compiler/fndb.lisp
@@ -1233,6 +1233,16 @@
   list
   (foldable flushable call))
 
+(defknown sb-impl::length-intersection
+    (proper-list proper-list &key (:key (function-designator ((or (nth-arg 0 :sequence t)
+                                                                  (nth-arg 1 :sequence t)))))
+                 (:test (function-designator ((nth-arg 0 :sequence t :key :key)
+                                              (nth-arg 1 :sequence t :key :key))))
+                 (:test-not (function-designator ((nth-arg 0 :sequence t :key :key)
+                                                  (nth-arg 1 :sequence t :key :key)))))
+    index
+    (foldable flushable call))
+
 (defknown sb-impl::intersection-p
   (proper-list proper-list &key (:key (function-designator ((or (nth-arg 0 :sequence t)
                                                   (nth-arg 1 :sequence t)))))
diff --git a/src/compiler/ir1opt.lisp b/src/compiler/ir1opt.lisp
index 2b5c0d684..3a87f5a9a 100644
--- a/src/compiler/ir1opt.lisp
+++ b/src/compiler/ir1opt.lisp
@@ -1732,34 +1732,29 @@
 
 ;;; Wait for the next component-reoptimize-counter
 (defun delay-ir1-optimizer (node reason)
-  (let* ((delayed (basic-combination-delay-to node))
-         (delayed-n (ash delayed -1))
+  (let* ((delay (basic-combination-delay-to node))
          (component *component-being-compiled*)
          (reoptimize-counter (component-reoptimize-counter component))
          (phase-counter (component-phase-counter component)))
     (aver (eq component (node-component node)))
     (ecase reason
       (:constraint
-       (cond ((> phase-counter 0)
-              ;; There will be no more constraint-propagate
-              nil)
-             ((or (= delayed-n -1)
-                  (logbitp 0 delayed)) ;; delayed to :ir1-phases but :constraint takes priority
-              (push node *ir1-transforms-after-constraints*)
-              (setf (basic-combination-delay-to node)
-                    ;; Disambiguate by having different low bits
-                    (logand most-positive-fixnum (ash reoptimize-counter 1)))
-              t)
-             ((= delayed-n reoptimize-counter))))
+       (unless (> phase-counter 0) ;; There will be no more constraint-propagate
+         (let ((constraint-delay (ldb (byte 8 0) delay)))
+           (cond ((= constraint-delay 255)
+                  (push node *ir1-transforms-after-constraints*)
+                  (setf (basic-combination-delay-to node)
+                        (dpb reoptimize-counter (byte 8 0) delay))
+                  t)
+                 ((= constraint-delay reoptimize-counter))))))
       (:ir1-phases
-       (cond ((and (not (logbitp 0 delayed))
-                   (= delayed-n reoptimize-counter))) ;; already delayed to :constraint
-             ((= delayed -1)
-              (push node *ir1-transforms-after-ir1-phases*)
-              (setf (basic-combination-delay-to node)
-                    (logand most-positive-fixnum (1+ (ash phase-counter 1))))
-              t)
-             ((= delayed-n phase-counter)))))))
+       (let ((ir1-phases-delay (ldb (byte 8 8) delay)))
+         (cond ((= ir1-phases-delay 255)
+                (push node *ir1-transforms-after-ir1-phases*)
+                (setf (basic-combination-delay-to node)
+                      (dpb phase-counter (byte 8 8) delay))
+                t)
+               ((= ir1-phases-delay phase-counter))))))))
 
 (defun delay-ir1-transform (node reason)
   (when (delay-ir1-optimizer node reason)
diff --git a/src/compiler/ir1util.lisp b/src/compiler/ir1util.lisp
index d1e03dd84..10a8bfba4 100644
--- a/src/compiler/ir1util.lisp
+++ b/src/compiler/ir1util.lisp
@@ -4231,6 +4231,7 @@ is :ANY, the function name is not checked."
                 (specifier-type 'simple-array))))))))
 
 (defun make-transform-lambda (fun args)
-  (let ((vars (make-gensym-list (length args))))
-    `(lambda ,vars
-       (,fun ,@vars))))
+  (when args
+    (let ((vars (make-gensym-list (length args))))
+      `(lambda ,vars
+         (,fun ,@vars)))))
diff --git a/src/compiler/node.lisp b/src/compiler/node.lisp
index dfee35b3d..989993e8a 100644
--- a/src/compiler/node.lisp
+++ b/src/compiler/node.lisp
@@ -1587,7 +1587,7 @@
   (step-info nil)
   ;; A plist of inline expansions
   (inline-expansions *inline-expansions* :type list :read-only t)
-  ;; Current COMPONENT-REOPTIMIZE-COUNTER set when calling delay-ir1-transform.
+  ;; Current COMPONENT-REOPTIMIZE-COUNTER + -PHASE-COUNTER set in delay-ir1-transform
   (delay-to -1 :type fixnum)
   (constraints-in)
   #+() (constraints-out))
diff --git a/src/compiler/srctran.lisp b/src/compiler/srctran.lisp
index 487fe4f2a..3e921ece4 100644
--- a/src/compiler/srctran.lisp
+++ b/src/compiler/srctran.lisp
@@ -8452,12 +8452,11 @@
              (delay-ir1-optimizer node :constraint))))))
 
 (deftransform length ((sequence) * * :node node)
-  (let ((args (splice-fun-args sequence 'remove-duplicates nil nil)))
-    (cond (args
-           (make-transform-lambda 'sb-impl::length-remove-duplicates args))
-          (t
-           (make-transform-lambda 'sb-impl::length-delete-duplicates
-                                  (splice-fun-args sequence 'delete-duplicates nil))))))
+  (delay-ir1-transform node :ir1-phases)
+  (or (make-transform-lambda 'sb-impl::length-remove-duplicates (splice-fun-args sequence 'remove-duplicates nil nil))
+      (make-transform-lambda 'sb-impl::length-delete-duplicates (splice-fun-args sequence 'delete-duplicates nil nil))
+      (make-transform-lambda 'sb-impl::length-intersection (splice-fun-args sequence 'intersection nil nil))
+      (give-up-ir1-transform)))
 
 ;;; ENDP, NULL and NOT -> %REST-NULL
 ;;;

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


hooks/post-receive
-- 
SBCL