master: Improve TYPEP for (and some-instance-type (not some-subtype))

snuglas via Sbcl-commits <[email protected]> Mon, 04 May 2026 05:27:57 +0000
Newsgroups gmane.lisp.steel-bank.cvs
Message-ID <[email protected]>
The branch "master" has been updated in SBCL:
       via  b5c57d2d40001c1d4af0c83876ba9bcd7d62af4a (commit)
      from  0c21989ac51f144c127e1b718fd0534d1f9420f9 (commit)

- Log -----------------------------------------------------------------
commit b5c57d2d40001c1d4af0c83876ba9bcd7d62af4a
Author: Douglas Katzman <[email protected]>
Date:   Mon May 4 01:10:07 2026 -0400

    Improve TYPEP for (and some-instance-type (not some-subtype))
    
    Instead of a hierarchical layout test for the ancestor and then a test
    to exclude the child, simply compare for EQness to a single layout.
---
 src/compiler/typetran.lisp       | 24 +++++++++++++++++++++++-
 tests/x86-64-codegen.impure.lisp | 19 +++++++++++++++++++
 2 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/src/compiler/typetran.lisp b/src/compiler/typetran.lisp
index 4f1892c98..1a099e50d 100644
--- a/src/compiler/typetran.lisp
+++ b/src/compiler/typetran.lisp
@@ -1005,7 +1005,26 @@
                                   '(or ,@(mapcar (lambda (x) (if (ctype-p x)
                                                                  (type-specifier x)
                                                                  x))
-                                          negated)))))))
+                                          negated))))))
+                  (maybe-exactly-struct-type (type negated)
+                    ;; If it is "exactly" an ancestral type, it is always more efficient
+                    ;; to test LAYOUT= of the type rather than using STRUCTURE-IS-A
+                    ;; and then ruling out descendant types individually.
+                    (let ((whole (classoid-all-subclassoids type)))
+                      (dolist (neg negated)
+                        (when (typep neg 'structure-classoid)
+                          (dolist (remove (classoid-all-subclassoids neg))
+                            (if (member remove whole)
+                                (setq whole (remove remove whole))
+                                (return-from maybe-exactly-struct-type nil))))) ; just give up
+                      (when (singleton-p whole) ; a winner
+                        (let ((layout (info :type :compiler-layout (classoid-name (car whole)))))
+                          ;; funcallable structures should never get here.
+                          `(and (%instancep ,object)
+                                ,(if (vop-existsp :translate layout-eq)
+                                     `(layout-eq ,object ,layout ,sb-vm:instance-pointer-lowtag)
+                                     `(eq (%instance-layout ,object) ,layout))))))))
+
              (cond
                ;; (and array (not vector))
                ((and (eq (car types) (specifier-type 'array))
@@ -1030,6 +1049,9 @@
                                           (let ((rem (remove nil members)))
                                             (when rem
                                               `((member ,@rem)))))))))))
+               ((and (typep types '(cons structure-classoid null))
+                     (eq (classoid-state (car types)) :sealed)
+                     (maybe-exactly-struct-type (car types) negated)))
                (t
                 (test types negated)))))
           (t
diff --git a/tests/x86-64-codegen.impure.lisp b/tests/x86-64-codegen.impure.lisp
index 01e8074ce..fa2aa9842 100644
--- a/tests/x86-64-codegen.impure.lisp
+++ b/tests/x86-64-codegen.impure.lisp
@@ -1456,3 +1456,22 @@
               (assert (oddp index)))
           ;; No always-thread-local special clashes with *PACKAGE*'s indirection cell
           (assert (/= index (1- index-of-package))))))))
+
+(defstruct frozenthing)
+(defstruct (specialthing (:include frozenthing)))
+(defstruct (subspecialthing (:include specialthing)))
+(defstruct (otherspecialthing (:include frozenthing)))
+(declaim (freeze-type frozenthing))
+(with-test (:name :typep-layout-eq-one-type)
+  (let ((expr `(lambda (x)
+                 (declare (optimize (sb-c::verify-arg-count 0)))
+                 (the (and frozenthing (not (or specialthing otherspecialthing))) x)))
+        (comparisons 0)
+        (saw-layout))
+    ;; there should be exactly one CMP instruction comparing to #<LAYOUT for THING>
+    (dolist (line (disassembly-lines (compile nil expr)))
+      (when (search "CMP " line)
+        (incf comparisons)
+        (when (and (search "#<LAYOUT" line) (search "FROZENTHING" line))
+          (setq saw-layout t))))
+    (assert (and saw-layout (eql comparisons 1)))))

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


hooks/post-receive
-- 
SBCL