mirror of
git://git.code.sf.net/p/sbcl/sbcl
synced 2026-09-10 07:26:40 -04:00
1960 lines
100 KiB
Common Lisp
1960 lines
100 KiB
Common Lisp
;;;; This file contains stuff that implements the portable IR1
|
||
;;;; semantics of type tests and coercion. The main thing we do is
|
||
;;;; convert complex type operations into simpler code that can be
|
||
;;;; compiled inline.
|
||
|
||
;;;; This software is part of the SBCL system. See the README file for
|
||
;;;; more information.
|
||
;;;;
|
||
;;;; This software is derived from the CMU CL system, which was
|
||
;;;; written at Carnegie Mellon University and released into the
|
||
;;;; public domain. The software is in the public domain and is
|
||
;;;; provided with absolutely no warranty. See the COPYING and CREDITS
|
||
;;;; files for more information.
|
||
|
||
(in-package "SB-C")
|
||
|
||
;;;; type predicate translation
|
||
;;;;
|
||
;;;; We maintain a bidirectional association between type predicates
|
||
;;;; and the tested type. The presence of a predicate in this
|
||
;;;; association implies that it is desirable to implement tests of
|
||
;;;; this type using the predicate. These are either predicates that
|
||
;;;; the back end is likely to have special knowledge about, or
|
||
;;;; predicates so complex that the only reasonable implentation is
|
||
;;;; via function call.
|
||
;;;;
|
||
;;;; Some standard types (such as ATOM) are best tested by letting the
|
||
;;;; TYPEP source transform do its thing with the expansion. These
|
||
;;;; types (and corresponding predicates) are not maintained in this
|
||
;;;; association. In this case, there need not be any predicate
|
||
;;;; function unless it is required by the Common Lisp specification.
|
||
;;;;
|
||
;;;; The mapping between predicates and type structures is considered
|
||
;;;; part of the backend; different backends can support different
|
||
;;;; sets of predicates.
|
||
|
||
;;; Establish an association between the type predicate NAME and the
|
||
;;; corresponding TYPE. This causes the type predicate to be
|
||
;;; recognized for purposes of optimization.
|
||
(defmacro define-type-predicate (name type)
|
||
`(%define-type-predicate ',name ',type))
|
||
(defun %define-type-predicate (name specifier)
|
||
(let ((type (specifier-type specifier)))
|
||
(setf (gethash name *backend-predicate-types*) type)
|
||
(setf *backend-type-predicates*
|
||
(cons (cons type name)
|
||
(remove name *backend-type-predicates*
|
||
:key #'cdr)))
|
||
(%deftransform name nil '(function (t) *) #'fold-type-predicate)))
|
||
|
||
|
||
(define-source-transform typep (object spec &optional env)
|
||
(if (and (not env)
|
||
(typep spec '(cons (eql quote) (cons t null))))
|
||
(with-current-source-form (spec)
|
||
;; Decline to do the source transform when seeing an unknown
|
||
;; type immediately while block converting, since it may be
|
||
;; defined later. By waiting for the deftransform to fire
|
||
;; during block compilation, we give ourselves a better chance
|
||
;; at open-coding the type test.
|
||
(let ((type (cadr spec)))
|
||
;;
|
||
#+collect-typep-regression-dataset
|
||
(let ((parse (specifier-type type)))
|
||
;; alien types aren't externalizable as trees of symbols,
|
||
;; and some classoid types aren't defined at the start of warm build,
|
||
;; making it impossible to re-parse a dump produced late in the build.
|
||
;; Luckily there are no cases involving compund types and classoids.
|
||
(unless (or (involves-alien-p parse)
|
||
(or (classoid-p parse)
|
||
(and (cons-type-p parse)
|
||
(classoid-p (cons-type-car-type parse)))))
|
||
(let ((table *interesting-types*))
|
||
(unless (hash-table-p table)
|
||
(setq table (dump/restore-interesting-types 'read)))
|
||
(setf (gethash type table) t))))
|
||
(or (%source-transform-typep-simple object type)
|
||
(values nil t))))
|
||
(values nil t)))
|
||
|
||
(deftransform typep ((object type &optional env) * * :node node)
|
||
(unless (constant-lvar-p type)
|
||
(give-up-ir1-transform "can't open-code test of non-constant type"))
|
||
(unless (unsupplied-or-nil env)
|
||
(give-up-ir1-transform "environment argument present and not null"))
|
||
(let* ((type (lvar-value type))
|
||
(ctype (ir1-transform-specifier-type type))
|
||
(object-type (lvar-type object)))
|
||
(prog1
|
||
(cond ((csubtypep object-type ctype)
|
||
t)
|
||
((not (types-equal-or-intersect object-type ctype))
|
||
nil)
|
||
(t
|
||
(transform-typep 'object object type ctype node)))
|
||
(check-deprecated-type type))))
|
||
|
||
(sb-xc:deftype other-pointer ()
|
||
'(or array
|
||
(and number (not (or fixnum #+64-bit single-float)))
|
||
fdefn (and symbol (not null))
|
||
weak-pointer system-area-pointer code-component))
|
||
|
||
(defun type-other-pointer-p (type)
|
||
(csubtypep type (specifier-type 'other-pointer)))
|
||
|
||
(defun type-not-other-pointer-p (type)
|
||
(csubtypep type (specifier-type '(not other-pointer))))
|
||
|
||
(defun type-other-pointer-widetags (type)
|
||
(let ((type (type-intersection type (specifier-type 'other-pointer))))
|
||
(macrolet ((expand ()
|
||
`(cond ,@(loop for classoid in sb-kernel::*builtin-classoids*
|
||
when
|
||
(destructuring-bind (name &key codes &allow-other-keys) classoid
|
||
(let ((ctype (specifier-type name)))
|
||
(when (and codes
|
||
(csubtypep ctype (specifier-type 'other-pointer))
|
||
(not (csubtypep ctype (specifier-type '(or complex array)))))
|
||
`((eq type (specifier-type ',name))
|
||
',codes))))
|
||
collect it)
|
||
((eq type (specifier-type 'array))
|
||
',sb-vm::+array-widetags+)
|
||
((eq type (specifier-type 'simple-array))
|
||
',sb-vm::+simple-array-widetags+)
|
||
((eq type (specifier-type '(simple-array * (*))))
|
||
',sb-vm::+simple-rank-1-array-widetags+)
|
||
((eq type (specifier-type 'vector))
|
||
',sb-vm::+vector-widetags+)
|
||
((eq type (specifier-type 'string))
|
||
',sb-vm::+string-widetags+)
|
||
((eq type (specifier-type '(and rational other-pointer)))
|
||
'(,sb-vm:bignum-widetag ,sb-vm:ratio-widetag))
|
||
((eq type (specifier-type '(and real other-pointer)))
|
||
'(,sb-vm:bignum-widetag
|
||
#-64-bit ,sb-vm:single-float-widetag
|
||
,sb-vm:double-float-widetag
|
||
,sb-vm:ratio-widetag))
|
||
((eq type (specifier-type '(and number other-pointer)))
|
||
'(,sb-vm:bignum-widetag
|
||
#-64-bit ,sb-vm:single-float-widetag
|
||
,sb-vm:double-float-widetag
|
||
,sb-vm:ratio-widetag
|
||
,sb-vm:complex-rational-widetag
|
||
,sb-vm:complex-single-float-widetag
|
||
,sb-vm:complex-double-float-widetag)))))
|
||
(expand))))
|
||
|
||
;;; If the lvar OBJECT definitely is or isn't of the specified
|
||
;;; type, then return T or NIL as appropriate. Otherwise quietly
|
||
;;; GIVE-UP-IR1-TRANSFORM.
|
||
(defun ir1-transform-type-predicate (object type node)
|
||
(declare (type lvar object) (type ctype type))
|
||
(let ((otype (lvar-type object)))
|
||
(cond ((not (types-equal-or-intersect otype type))
|
||
(return-from ir1-transform-type-predicate nil))
|
||
((csubtypep otype type)
|
||
(return-from ir1-transform-type-predicate t))
|
||
((eq type *empty-type*)
|
||
(return-from ir1-transform-type-predicate nil)))
|
||
(let ((intersect (type-intersection type otype))
|
||
(current-predicate (combination-fun-source-name node)))
|
||
;; If the object type is known to be (OR NULL <type>),
|
||
;; it is almost always cheaper to test for not EQ to NIL.
|
||
;; There is one exception:
|
||
;; - FIXNUMP is possibly cheaper than comparison to NIL, or definitely
|
||
;; not worse. For x86, NIL is a 4-byte immediate operand,
|
||
;; for lack of a null-tn register. FIXNUM-TAG-MASK is only 1 byte.
|
||
(when (type= otype (type-union (specifier-type 'null) type))
|
||
(let ((difference (type-difference type (specifier-type 'null))))
|
||
(unless (type= difference (specifier-type 'fixnum))
|
||
(return-from ir1-transform-type-predicate `(not (null object))))))
|
||
(flet ((memory-type-test-p (type)
|
||
(and (types-equal-or-intersect
|
||
type
|
||
(specifier-type
|
||
'(not (or fixnum #+64-bit single-float
|
||
boolean character
|
||
function list))))
|
||
(not (type= type (specifier-type 'instance))))))
|
||
(cond ((typep type 'alien-type-type)
|
||
;; We don't transform alien type tests until here, because
|
||
;; once we do that the rest of the type system can no longer
|
||
;; reason about them properly -- so we'd miss out on type
|
||
;; derivation, etc.
|
||
(delay-ir1-transform node :ir1-phases)
|
||
(let ((alien-type (alien-type-type-alien-type type)))
|
||
;; If it's a lisp-rep-type, the CTYPE should be one already.
|
||
(aver (not (compute-lisp-rep-type alien-type)))
|
||
`(sb-alien::alien-value-typep object ',alien-type)))
|
||
((and (neq current-predicate 'arrayp)
|
||
(csubtypep intersect (specifier-type 'array))
|
||
(not (types-equal-or-intersect (type-difference otype type)
|
||
(specifier-type 'array))))
|
||
`(arrayp object))
|
||
((and (neq current-predicate 'simple-array-p)
|
||
(neq current-predicate 'arrayp)
|
||
(csubtypep intersect (specifier-type 'simple-array))
|
||
(not (types-equal-or-intersect (type-difference otype type)
|
||
(specifier-type 'simple-array))))
|
||
`(simple-array-p object))
|
||
((and (eq current-predicate 'vectorp)
|
||
(vop-existsp :translate %array-rank=)
|
||
(and (csubtypep otype (specifier-type 'array))))
|
||
`(%array-rank= object 1))
|
||
;; (typep (the (or list fixnum) x) 'integer) =>
|
||
;; (typep x 'fixnum)
|
||
((let ((new-predicate
|
||
(or
|
||
(backend-type-predicate intersect)
|
||
;; Remove bounds from numeric types
|
||
(and (csubtypep intersect (specifier-type 'real))
|
||
(macrolet ((up (&rest types)
|
||
`(cond ,@(loop for (type predicate) on types by #'cddr
|
||
collect
|
||
`((and (csubtypep intersect (specifier-type ',type))
|
||
(csubtypep (specifier-type ',type) type))
|
||
',predicate)))))
|
||
(up fixnum fixnump
|
||
integer integerp
|
||
rational rationalp
|
||
single-float single-float-p
|
||
double-float double-float-p
|
||
float floatp
|
||
real realp))))))
|
||
(when (and new-predicate
|
||
(neq new-predicate current-predicate)
|
||
;; Some subtypes are more expensive to check
|
||
(not (and (eq current-predicate 'listp)
|
||
(eq new-predicate 'consp)))
|
||
(not (and (eq current-predicate 'functionp)
|
||
(eq new-predicate 'compiled-function-p)))
|
||
(not (eq current-predicate 'characterp))
|
||
(not (eq current-predicate 'arrayp))
|
||
(not (eq current-predicate 'simple-array-p))
|
||
(not (and (eq current-predicate 'non-null-symbol-p)
|
||
(eq new-predicate 'keywordp)))
|
||
(not (eq new-predicate #+64-bit 'signed-byte-64-p
|
||
#-64-bit 'signed-byte-32-p))
|
||
(not (eq new-predicate #+64-bit 'unsigned-byte-64-p
|
||
#-64-bit 'unsigned-byte-32-p)))
|
||
`(,new-predicate object))))
|
||
;; (typep (the float x) 'double-float) =>
|
||
;; (not (typep x 'single-float))
|
||
((let* ((diff (type-difference otype type))
|
||
(pred (and (or (eq current-predicate 'sequencep) ;; always expensive
|
||
(not (memory-type-test-p diff)))
|
||
(or (backend-type-predicate diff)
|
||
;; Remove bounds from numeric types
|
||
(and (csubtypep diff (specifier-type 'real))
|
||
(macrolet ((up (&rest types)
|
||
`(cond ,@(loop for (type predicate) on types by #'cddr
|
||
collect
|
||
`((and (csubtypep diff (specifier-type ',type))
|
||
(not (types-equal-or-intersect type (specifier-type ',type))))
|
||
',predicate)))))
|
||
(up fixnum fixnump
|
||
integer integerp
|
||
rational rationalp
|
||
single-float single-float-p
|
||
double-float double-float-p
|
||
float floatp
|
||
real realp)))))))
|
||
(cond ((and pred
|
||
;; Testing for fixnum is usually the cheapest
|
||
(or (eq pred 'fixnump)
|
||
(and (eq type (specifier-type 'instance))
|
||
(eq pred 'null))
|
||
(memory-type-test-p type)))
|
||
`(not (,pred object)))
|
||
((and (memory-type-test-p type)
|
||
(cond ((and (type-not-other-pointer-p diff)
|
||
(type-other-pointer-p type))
|
||
`(%other-pointer-p object))
|
||
((and (type-other-pointer-p diff)
|
||
(type-not-other-pointer-p type))
|
||
`(not (%other-pointer-p object)))
|
||
((and (csubtypep diff (specifier-type 'instance))
|
||
(not (types-equal-or-intersect type (specifier-type 'instance))))
|
||
`(not (%instancep object)))))))))
|
||
(t
|
||
(give-up-ir1-transform)))))))
|
||
|
||
;;; Flush %TYPEP tests whose result is known at compile time.
|
||
(deftransform %typep ((object type) * * :node node)
|
||
(unless (constant-lvar-p type)
|
||
(give-up-ir1-transform))
|
||
(ir1-transform-type-predicate
|
||
object
|
||
(ir1-transform-specifier-type (lvar-value type))
|
||
node))
|
||
|
||
;;; This is the IR1 transform for simple type predicates. It checks
|
||
;;; whether the single argument is known to (not) be of the
|
||
;;; appropriate type, expanding to T or NIL as appropriate.
|
||
(deftransform fold-type-predicate ((object) * * :node node :defun-only t)
|
||
(let ((ctype (gethash (leaf-source-name
|
||
(ref-leaf
|
||
(lvar-uses
|
||
(basic-combination-fun node))))
|
||
*backend-predicate-types*)))
|
||
(aver ctype)
|
||
(ir1-transform-type-predicate object ctype node)))
|
||
|
||
;;; If FIND-CLASSOID is called on a constant class, locate the
|
||
;;; CLASSOID-CELL at load time.
|
||
(deftransform find-classoid ((name) ((constant-arg symbol)) *)
|
||
(let* ((name (lvar-value name))
|
||
(cell (find-classoid-cell name :create t)))
|
||
`(or (classoid-cell-classoid ',cell)
|
||
(error "Class not yet defined: ~S" name))))
|
||
|
||
(deftransform %type-constraint ((x type) * * :node node)
|
||
(delay-ir1-transform node :constraint)
|
||
nil)
|
||
|
||
(defoptimizer (%type-constraint constraint-propagate) ((x type) node gen)
|
||
(let ((var (ok-lvar-lambda-var x gen)))
|
||
(when var
|
||
(let ((type (lvar-value type)))
|
||
(list (list 'typep var
|
||
(if (ctype-p type)
|
||
type
|
||
(handler-case (careful-specifier-type type)
|
||
(t () nil)))
|
||
nil))))))
|
||
|
||
;;;; standard type predicates, i.e. those defined in package COMMON-LISP,
|
||
;;;; plus at least one oddball (%INSTANCEP)
|
||
;;;;
|
||
;;;; Various other type predicates (e.g. low-level representation
|
||
;;;; stuff like SIMPLE-ARRAY-SINGLE-FLOAT-P) are defined elsewhere.
|
||
|
||
;;; FIXME: This function is only called once, at top level. Why not
|
||
;;; just expand all its operations into toplevel code?
|
||
(defun !define-standard-type-predicates ()
|
||
(define-type-predicate arrayp array)
|
||
; (The ATOM predicate is handled separately as (NOT CONS).)
|
||
(define-type-predicate bit-vector-p bit-vector)
|
||
(define-type-predicate characterp character)
|
||
#+(and sb-unicode (or x86-64 arm64)) ;; others have a source-transform
|
||
(define-type-predicate base-char-p base-char)
|
||
(define-type-predicate compiled-function-p compiled-function)
|
||
(define-type-predicate complexp complex)
|
||
(define-type-predicate complex-rational-p (complex rational))
|
||
(define-type-predicate complex-float-p (complex float))
|
||
(define-type-predicate consp cons)
|
||
(define-type-predicate floatp float)
|
||
(define-type-predicate functionp function)
|
||
(define-type-predicate integerp integer)
|
||
(define-type-predicate keywordp keyword)
|
||
(define-type-predicate listp list)
|
||
(define-type-predicate null null)
|
||
(define-type-predicate numberp number)
|
||
(define-type-predicate rationalp rational)
|
||
(define-type-predicate realp real)
|
||
(define-type-predicate sequencep sequence)
|
||
(define-type-predicate extended-sequence-p extended-sequence)
|
||
(define-type-predicate simple-bit-vector-p simple-bit-vector)
|
||
(define-type-predicate simple-string-p simple-string)
|
||
(define-type-predicate simple-vector-p simple-vector)
|
||
(define-type-predicate stringp string)
|
||
(define-type-predicate %instancep instance)
|
||
(define-type-predicate simple-fun-p simple-fun)
|
||
(define-type-predicate closurep closure)
|
||
(define-type-predicate funcallable-instance-p funcallable-instance)
|
||
(define-type-predicate symbolp symbol)
|
||
(define-type-predicate vectorp vector))
|
||
(!define-standard-type-predicates)
|
||
|
||
;;;; transforms for type predicates not implemented primitively
|
||
;;;;
|
||
;;;; See also VM dependent transforms.
|
||
|
||
(define-source-transform atom (x)
|
||
`(not (consp ,x)))
|
||
|
||
#+(and sb-unicode (not (or x86-64 arm64)))
|
||
(define-source-transform base-char-p (x)
|
||
`(typep ,x 'base-char))
|
||
;; CONS is implemented as (and list (not (eql nil))) where the 'and' is
|
||
;; built-in to the consp vop. Reduce to just LISTP if possible.
|
||
(deftransform consp ((x) ((not null)) * :important nil)
|
||
'(listp x))
|
||
|
||
;;; If X is known non-nil, then testing SYMBOLP can skip the "= NIL" part.
|
||
(deftransform symbolp ((x) ((not null)) * :important nil)
|
||
'(non-null-symbol-p x))
|
||
(deftransform non-null-symbol-p ((object) (symbol) * :important nil)
|
||
`(not (eq object nil)))
|
||
;;; CLHS: http://www.lispworks.com/documentation/HyperSpec/Body/t_symbol.htm#symbol
|
||
;;; "The consequences are undefined if an attempt is made to alter the home package
|
||
;;; of a symbol external in the COMMON-LISP package or the KEYWORD package."
|
||
;;; Therefore, we can constant-fold if the symbol-package is one of those two.
|
||
;;; Interestingly, we don't need any transform for (NOT SYMBOL)
|
||
;;; because IR1-TRANSFORM-TYPE-PREDICATE knows that the intersection of the type
|
||
;;; implied by KEYWORDP with any type that does not intersect SYMBOL is NIL.
|
||
(deftransform keywordp ((x) ((constant-arg symbol)))
|
||
(let ((pkg (sb-xc:symbol-package (lvar-value x))))
|
||
(cond ((eq pkg *cl-package*) 'nil)
|
||
((eq pkg *keyword-package*) 't)
|
||
(t (give-up-ir1-transform)))))
|
||
|
||
;;;; TYPEP source transform
|
||
|
||
;;; Return a form that tests the variable N-OBJECT for being in the
|
||
;;; binds specified by TYPE. BASE is the name of the base type, for
|
||
;;; declaration.
|
||
(defun transform-numeric-bound-test (n-object type base)
|
||
(declare (type numeric-type type))
|
||
(let ((low (numeric-type-low type))
|
||
(high (numeric-type-high type)))
|
||
`(and ,@(when low
|
||
(if (consp low)
|
||
`((> (truly-the ,base ,n-object) ,(car low)))
|
||
`((>= (truly-the ,base ,n-object) ,low))))
|
||
,@(when high
|
||
(if (consp high)
|
||
`((< (truly-the ,base ,n-object) ,(car high)))
|
||
`((<= (truly-the ,base ,n-object) ,high)))))))
|
||
|
||
;;; Do source transformation of a test of a known numeric type. We can
|
||
;;; assume that the type doesn't have a corresponding predicate, since
|
||
;;; those types have already been picked off. In particular, CLASS
|
||
;;; must be specified, since it is unspecified only in NUMBER and
|
||
;;; COMPLEX. Similarly, we assume that COMPLEXP is always specified.
|
||
;;;
|
||
;;; For non-complex types, we just test that the number belongs to the
|
||
;;; base type, and then test that it is in bounds. When CLASS is
|
||
;;; INTEGER, we check to see whether the range is no bigger than
|
||
;;; FIXNUM. If so, we check for FIXNUM instead of INTEGER. This allows
|
||
;;; us to use fixnum comparison to test the bounds.
|
||
;;;
|
||
;;; For complex types, we must test for complex, then do the above on
|
||
;;; both the real and imaginary parts. When CLASS is float, we need
|
||
;;; only check the type of the realpart, since the format of the
|
||
;;; realpart and the imagpart must be the same.
|
||
(defun source-transform-numeric-typep (object type)
|
||
(let* ((class (numeric-type-class type))
|
||
(base (ecase class
|
||
(integer (containing-integer-type
|
||
(if (numeric-type-complexp type)
|
||
(modified-numeric-type type
|
||
:complexp :real)
|
||
type)))
|
||
(rational (if (csubtypep type (specifier-type 'ratio))
|
||
'ratio
|
||
'rational))
|
||
(float (or (numeric-type-format type) 'float))
|
||
((nil) 'real)))
|
||
(low (numeric-type-low type))
|
||
(high (numeric-type-high type)))
|
||
(ecase (numeric-type-complexp type)
|
||
(:real
|
||
(cond ((and (vop-existsp :translate check-range<=)
|
||
(eql (numeric-type-class type) 'integer)
|
||
(fixnump low)
|
||
(fixnump high))
|
||
`(check-range<= ,low ,object ,high))
|
||
((type= type (specifier-type '(or word sb-vm:signed-word)))
|
||
`(or (typep ,object 'sb-vm:signed-word)
|
||
(typep ,object 'word)))
|
||
((and (vop-existsp :translate unsigned-byte-x-p)
|
||
(eql (numeric-type-class type) 'integer)
|
||
(eql low 0)
|
||
(integerp high)
|
||
(= (logcount (1+ high)) 1)
|
||
(zerop (rem (integer-length high) sb-vm:n-word-bits)))
|
||
`(unsigned-byte-x-p ,object ,(integer-length high)))
|
||
(t
|
||
`(and (typep ,object ',base)
|
||
,(transform-numeric-bound-test object type base)))))
|
||
(:complex
|
||
(let ((part-type (second (type-specifier type))))
|
||
`(and (typep ,object '(complex ,(case base
|
||
((double-float single-float rational) base)
|
||
(t (if (eq class 'integer)
|
||
'rational
|
||
'*)))))
|
||
(typep (realpart ,object) ',part-type)
|
||
(typep (imagpart ,object) ',part-type)))))))
|
||
|
||
;;; Do the source transformation for a test of a hairy type.
|
||
;;; SATISFIES is converted into the obvious. Otherwise, we convert
|
||
;;; to CACHED-TYPEP an possibly print an efficiency note.
|
||
(defun source-transform-hairy-typep (object type node)
|
||
(declare (type hairy-type type))
|
||
(let ((spec (hairy-type-specifier type)))
|
||
(cond ((and (unknown-type-p type)
|
||
(symbolp spec)
|
||
(eq (info :type :kind spec) :forthcoming-defclass-type))
|
||
;; Knowing that it was DEFCLASSed is enough to emit a CLASSOID-CELL-TYPEP test.
|
||
;; Combinators involving this - e.g. (OR A-NEW-CLASS OTHER-CLASS) -
|
||
;; are handled correctly, because we don't punt on everything in the expression
|
||
;; as soon as any unknown is present.
|
||
`(classoid-cell-typep ,(find-classoid-cell spec :create t) ,object))
|
||
((unknown-type-p type)
|
||
`(let ((object ,object)
|
||
(cache (load-time-value (cons #'sb-kernel::cached-typep ',spec)
|
||
t)))
|
||
(truly-the (values t &optional)
|
||
(funcall (truly-the function (car (truly-the cons cache)))
|
||
cache object))))
|
||
(t
|
||
(ecase (first spec)
|
||
(satisfies
|
||
(let* ((name (second spec))
|
||
(expansion (fun-name-inline-expansion name)))
|
||
(check-global-fun name nil)
|
||
;; Lambda without lexenv can easily be handled here.
|
||
;; This fixes the issue that LEGAL-FUN-NAME-P which is
|
||
;; just a renaming of VALID-FUNCTION-NAME-P would not
|
||
;; be inlined when testing the FUNCTION-NAME type.
|
||
`(if ,(if (and (typep expansion '(cons (eql lambda)))
|
||
(not (fun-lexically-notinline-p name (node-lexenv node))))
|
||
`(,expansion ,object)
|
||
`(funcall (global-function ,name) ,object))
|
||
t nil))))))))
|
||
|
||
(defun source-transform-negation-typep (object type)
|
||
(declare (type negation-type type))
|
||
(let ((spec (type-specifier (negation-type-type type))))
|
||
`(not (typep ,object ',spec))))
|
||
|
||
;;; Check the type of a group of equally specialized but of
|
||
;;; different length simple arrays once
|
||
(defun group-vector-type-length-tests (object types)
|
||
(let (groups
|
||
any-grouped)
|
||
(loop for type in types
|
||
do
|
||
(if (and (array-type-p type)
|
||
(not (array-type-complexp type))
|
||
(typep (array-type-dimensions type) '(cons integer null))
|
||
(or (eq (array-type-element-type type) *wild-type*)
|
||
(neq (array-type-specialized-element-type type) *wild-type*)))
|
||
(push type
|
||
(getf groups
|
||
(array-type-specialized-element-type type)))
|
||
(push type (getf groups :other))))
|
||
(loop for (el-type types) on groups by #'cddr
|
||
do
|
||
(cond ((eq el-type :other))
|
||
((> (length types) 1)
|
||
(setf any-grouped t))
|
||
(t
|
||
(push (car types)
|
||
(getf groups :other)))))
|
||
(when any-grouped
|
||
(let ((other (getf groups :other)))
|
||
`(or
|
||
,@(loop for (el-type types) on groups by #'cddr
|
||
when (and (neq el-type :other)
|
||
(> (length types) 1))
|
||
collect `(and (typep ,object
|
||
'(simple-array ,(type-specifier el-type) (*)))
|
||
(typep (vector-length
|
||
(truly-the (simple-array * (*)) ,object))
|
||
'(member ,@(loop for type in types
|
||
collect (car (array-type-dimensions type)))))))
|
||
,@(and
|
||
other
|
||
`((typep ,object '(or ,@(mapcar #'type-specifier other))))))))))
|
||
|
||
;;; Test the length of multiple arrays types once
|
||
(defun group-vector-length-type-tests (object types)
|
||
(let (groups
|
||
any-grouped)
|
||
(loop for type in types
|
||
do
|
||
(if (and (array-type-p type)
|
||
(typep (array-type-dimensions type) '(cons integer null)))
|
||
(push type (getf groups (car (array-type-dimensions type))))
|
||
(push type (getf groups :other))))
|
||
(loop for (length types) on groups by #'cddr
|
||
do
|
||
(cond ((eq length :other))
|
||
((> (length types) 1)
|
||
(setf any-grouped t))
|
||
(t
|
||
(push (car types)
|
||
(getf groups :other)))))
|
||
(when any-grouped
|
||
(let ((other (getf groups :other)))
|
||
`(or
|
||
,@(loop for (length types) on groups by #'cddr
|
||
for any-complex = nil
|
||
for any-simple = nil
|
||
when (and (neq length :other)
|
||
(> (length types) 1))
|
||
collect `(and (typep ,object
|
||
'(or
|
||
,@(loop for type in types
|
||
for complex = (array-type-complexp type)
|
||
do (cond (complex
|
||
(setf any-complex t)
|
||
(when (eq complex :maybe)
|
||
(setf any-simple t)))
|
||
(t
|
||
(setf any-simple t)))
|
||
|
||
collect
|
||
(type-specifier
|
||
(make-array-type '(*)
|
||
:complexp complex
|
||
:element-type
|
||
(array-type-element-type type)
|
||
:specialized-element-type
|
||
(array-type-specialized-element-type type))))))
|
||
,(cond
|
||
((not any-complex)
|
||
`(= (vector-length (truly-the (simple-array * (*)) ,object))
|
||
,length))
|
||
((not any-simple)
|
||
`(= (%array-dimension (truly-the vector ,object) 0)
|
||
,length))
|
||
(t
|
||
`(if (array-header-p (truly-the vector ,object))
|
||
(= (%array-dimension (truly-the vector ,object) 0)
|
||
,length)
|
||
(= (vector-length (truly-the vector ,object))
|
||
,length))))))
|
||
,@(and
|
||
other
|
||
`((typep ,object '(or ,@(mapcar #'type-specifier other))))))))))
|
||
|
||
(defun source-transform-union-numeric-typep (object types)
|
||
(cond ((and (= (length types) 2)
|
||
;; Transform (or (double-float * (0d0)) (eql -0d0))
|
||
(destructuring-bind (a b) types
|
||
(multiple-value-bind (member numeric) (cond ((member-type-p a)
|
||
(values a b))
|
||
((member-type-p b)
|
||
(values b a)))
|
||
(let ((double))
|
||
(and (numeric-type-p numeric)
|
||
(= (member-type-size member) 1)
|
||
(or (setf double (sb-kernel::member-type-member-p -0d0 member))
|
||
(sb-kernel::member-type-member-p -0f0 member))
|
||
(let ((low (numeric-type-low numeric))
|
||
(high (numeric-type-high numeric))
|
||
(type (if double
|
||
'double-float
|
||
'single-float)))
|
||
(when (and (eq (numeric-type-class numeric) 'float)
|
||
(eq (numeric-type-complexp numeric) :real)
|
||
(equal high (if double
|
||
'(0d0)
|
||
'(0f0))))
|
||
`(and ,(if double
|
||
`(double-float-p ,object)
|
||
`(single-float-p ,object))
|
||
,(if low
|
||
`(and (float-sign-bit-set-p (truly-the ,type ,object))
|
||
(,@(if (consp low)
|
||
`(< ,(car low))
|
||
`(<= ,low))
|
||
(truly-the ,type ,object)))
|
||
`(float-sign-bit-set-p (truly-the ,type ,object))))))))))))
|
||
((not (every #'numeric-type-p types))
|
||
nil)
|
||
((and (= (length types) 2)
|
||
;; (and subtype-of-integer (not (eql x)))
|
||
;; don't test a range.
|
||
;; (and subtype-of-integer (not (integer x y)))
|
||
(destructuring-bind (b a) types
|
||
(and (integer-type-p a)
|
||
(integer-type-p b)
|
||
(flet ((check (a b)
|
||
(let* ((a-hi (numeric-type-high a))
|
||
(a-lo (numeric-type-low a))
|
||
(b-hi (numeric-type-high b))
|
||
(b-lo (numeric-type-low b)))
|
||
(when (and a-hi b-lo
|
||
(not (eql a-lo a-hi))
|
||
(not (eql b-lo b-hi))
|
||
(> b-lo a-hi))
|
||
(let* (typecheck
|
||
(a
|
||
`(typep object '(integer ,(or a-lo '*) ,(or b-hi '*))))
|
||
(b `(not
|
||
,(cond ((= (1+ a-hi)
|
||
(1- b-lo))
|
||
`(eql ,object ,(1+ a-hi)))
|
||
(t
|
||
(setf typecheck t)
|
||
`(typep object '(integer (,a-hi) (,b-lo))))))))
|
||
(if typecheck
|
||
`(and ,a ,b)
|
||
`(and ,b ,a)))))))
|
||
(or (check a b)
|
||
(check b a)))))))
|
||
((and (= (length types) 2)
|
||
;; (or (integer * fixnum-x) (integer fixnum-y))
|
||
;; only check for bignump and not its value.
|
||
(destructuring-bind (b a) types
|
||
(and (integer-type-p a)
|
||
(integer-type-p b)
|
||
(flet ((check (a b)
|
||
(let* ((a-hi (numeric-type-high a))
|
||
(a-lo (numeric-type-low a))
|
||
(b-hi (numeric-type-high b))
|
||
(b-lo (numeric-type-low b)))
|
||
(when (and (fixnump a-hi)
|
||
(fixnump b-lo)
|
||
(not a-lo)
|
||
(not b-hi))
|
||
`(or (and (fixnump ,object)
|
||
(or (>= ,object ,b-lo)
|
||
(<= ,object ,a-hi)))
|
||
(bignump ,object))))))
|
||
(or (check a b)
|
||
(check b a)))))))))
|
||
|
||
;; If TYPE is a strict subtype of a frozen classoid specified as
|
||
;; (AND someclassoid (NOT somesubclassoid)) then return the "exact" set
|
||
;; of classoids is is. i.e. pretend that membership in the resulting set
|
||
;; is determined by the classoid of an object being EQ to one of the classoids
|
||
;; and that the SUBTYPEP relation is irrelevant. Practically speaking: the
|
||
;; instance-layout of a candidate object must be EQ to the layout for one
|
||
;; of the classoids in the answer. The consumer of this output should not
|
||
;; take a union of the set for purposes of constructing a type.
|
||
(defun frozen-struct-classoid-carve-out (type)
|
||
(flet ((matchp (a b) ; does this type match (AND (NOT a) B)
|
||
(and (negation-type-p a)
|
||
(structure-classoid-p b)
|
||
(structure-classoid-p (negation-type-type a))
|
||
(eq (classoid-state (negation-type-type a)) :sealed)
|
||
(eq (classoid-state b) :sealed)
|
||
;; I think this has gotta be true. Why would the type algebra
|
||
;; leave it in if it weren't possible? It would just delete the
|
||
;; negation, and not represent it as an intersection at all.
|
||
(csubtypep (negation-type-type a) b)))
|
||
(difference (super sub)
|
||
(set-difference (classoid-all-subclassoids super)
|
||
(classoid-all-subclassoids (negation-type-type sub)))))
|
||
(when (intersection-type-p type)
|
||
(let ((types (compound-type-types type)))
|
||
;; We could try to match C - c1 - c2 - ... cN but I don't care to do it.
|
||
(when (= (length types) 2)
|
||
(let ((first (first types)) (second (second types)))
|
||
;; intersection is commutative so try both ways
|
||
(cond ((matchp first second) (difference second first))
|
||
((matchp second first) (difference first second)))))))))
|
||
|
||
(defun transform-frozen-struct-union-typep (object types)
|
||
;; If at least 4 sealed structs (before accounting for hierarchy), try to use
|
||
;; a test based on layout-clos-hash.
|
||
(let ((count 0))
|
||
(dolist (type types)
|
||
(incf count
|
||
(if (and (structure-classoid-p type) (eq (classoid-state type) :sealed))
|
||
1
|
||
(length (frozen-struct-classoid-carve-out type)))))
|
||
(when (< count 4)
|
||
(return-from transform-frozen-struct-union-typep nil)))
|
||
(collect ((structs) (other))
|
||
(flet ((add (list)
|
||
(dolist (type list)
|
||
(unless (member type (structs)) (structs type)))))
|
||
(dolist (type types)
|
||
(acond ((and (structure-classoid-p type) (eq (classoid-state type) :sealed))
|
||
(add (classoid-all-subclassoids type)))
|
||
((frozen-struct-classoid-carve-out type)
|
||
(add it))
|
||
(t (other type)))))
|
||
(flet ((typehash (x) (ldb (byte 32 0) (layout-clos-hash (classoid-layout x)))))
|
||
(let* ((hashes (map '(array (unsigned-byte 32) 1) #'typehash (structs)))
|
||
(lexpr (or (make-perfect-hash-lambda hashes (mapcar 'classoid-name (structs)))
|
||
(return-from transform-frozen-struct-union-typep nil)))
|
||
(phashfun (compile-perfect-hash lexpr hashes))
|
||
(min-size (length (structs)))
|
||
(pow2-size (power-of-two-ceiling min-size))
|
||
(deficit (- pow2-size min-size))
|
||
;; number of elts we're willing to waste in the table to avoid a range check
|
||
(rangecheck (> deficit 2)) ; probably should base this on percentage of min-size
|
||
(array (make-array (if rangecheck min-size pow2-size) :initial-element 0)))
|
||
(dolist (type (structs))
|
||
(let ((h (funcall phashfun (typehash type))))
|
||
(setf (aref array h) (classoid-layout type))))
|
||
(let ((test `(and (%instancep ,object)
|
||
(let* ((l (%instance-layout ,object))
|
||
(h (,lexpr (ldb (byte 32 0) (layout-clos-hash l)))))
|
||
,(if rangecheck
|
||
`(and (< h ,(length array)) (eq (aref ,array h) l))
|
||
`(eq (aref ,array h) l))))))
|
||
(if (other)
|
||
`(or ,test (typep ,object (or ',@(mapcar #'type-specifier (other)))))
|
||
test))))))
|
||
|
||
;;; Do source transformation for TYPEP of a known union type. If a
|
||
;;; union type contains LIST, then we pull that out and make it into a
|
||
;;; single LISTP call.
|
||
(defun source-transform-union-typep (object type)
|
||
(let* ((types (sb-kernel::flatten-numeric-union-types type))
|
||
(type-cons (specifier-type 'cons))
|
||
(type-symbol (specifier-type 'symbol))
|
||
(mtype (find-if #'member-type-p types))
|
||
(members (when mtype (member-type-members mtype))))
|
||
(cond ((and mtype
|
||
(memq nil members)
|
||
(memq type-cons types))
|
||
`(or (listp ,object)
|
||
(typep ,object
|
||
'(or ,@(mapcar #'type-specifier
|
||
(remove type-cons
|
||
(remove mtype types)))
|
||
(member ,@(remove nil members))))))
|
||
((and (memq type-cons types)
|
||
(memq type-symbol types))
|
||
`(or (listp ,object)
|
||
(non-null-symbol-p ,object)
|
||
(typep ,object
|
||
'(or ,@(mapcar #'type-specifier
|
||
(remove type-cons
|
||
(remove type-symbol types)))))))
|
||
;; Check for NULL before consp, then consp can be reduced to listp.
|
||
((and mtype
|
||
(memq nil members)
|
||
(find-if #'cons-type-p types))
|
||
`(or (null ,object)
|
||
(typep ,object
|
||
'(or ,@(mapcar #'type-specifier
|
||
(remove mtype types))
|
||
(member ,@(remove nil members))))))
|
||
;; The same as above but for all symbols
|
||
((and (find (specifier-type 'symbol) types)
|
||
(find-if #'cons-type-p types))
|
||
`(or (symbolp ,object)
|
||
(typep ,object
|
||
'(or ,@(mapcar #'type-specifier (remove (specifier-type 'symbol) types))))))
|
||
((transform-frozen-struct-union-typep object types))
|
||
((group-vector-type-length-tests object types))
|
||
((group-vector-length-type-tests object types))
|
||
((source-transform-union-numeric-typep object types))
|
||
;; Check for CONSP/SINGLE/DOUBLE-FLOAT-P once.
|
||
((let* (single-floats
|
||
double-floats
|
||
conses)
|
||
(loop for type in types
|
||
do
|
||
(cond
|
||
((numeric-type-p type)
|
||
(cond ((numtype-aspects-eq type (specifier-type 'double-float))
|
||
(push type double-floats))
|
||
((numtype-aspects-eq type (specifier-type 'single-float))
|
||
(push type single-floats))))
|
||
((cons-type-p type)
|
||
(push type conses))))
|
||
(when (or (cdr single-floats)
|
||
(cdr double-floats)
|
||
(cdr conses))
|
||
(flet ((check (sub-types test)
|
||
(when (cdr sub-types)
|
||
`((and (,test ,object)
|
||
(or
|
||
,@(loop for type in sub-types
|
||
do (setf types (remove type types :test #'eq :count 1))
|
||
collect `(typep ,object ',(type-specifier type)))))))))
|
||
`(or
|
||
,@(check single-floats 'single-float-p)
|
||
,@(check double-floats 'double-float-p)
|
||
,@(check conses 'consp)
|
||
,@(loop for type in types
|
||
collect `(typep ,object ',(type-specifier type))))))))
|
||
(t
|
||
(multiple-value-bind (widetags more-types)
|
||
(sb-kernel::widetags-from-union-type types)
|
||
(multiple-value-bind (predicate more-union-types)
|
||
(split-union-type-tests types)
|
||
(cond ((and predicate
|
||
(< (length more-union-types)
|
||
(length more-types)))
|
||
`(or (,predicate ,object)
|
||
(typep ,object '(or ,@(mapcar #'type-specifier more-union-types)))))
|
||
(widetags
|
||
`(or (%other-pointer-subtype-p ,object ',widetags)
|
||
(typep ,object '(or ,@(mapcar #'type-specifier more-types)))))
|
||
((and (cdr more-types)
|
||
(every #'intersection-type-p more-types)
|
||
(let ((common (intersection-type-types (car more-types))))
|
||
(loop for type in (cdr more-types)
|
||
for types = (intersection-type-types type)
|
||
for int = (intersection common types :test #'type=)
|
||
always int
|
||
do (setf common int)
|
||
finally
|
||
(return `(and
|
||
(typep ,object '(and ,@(mapcar #'type-specifier common)))
|
||
(or ,@(loop for type in more-types
|
||
for types = (intersection-type-types type)
|
||
collect
|
||
`(typep ,object '(and ,@(mapcar #'type-specifier
|
||
(set-difference types common))))))))))))
|
||
(t
|
||
`(or
|
||
,@(mapcar (lambda (x)
|
||
`(typep ,object ',(type-specifier x)))
|
||
more-types))))))))))
|
||
|
||
(defun source-transform-intersection-typep (object type)
|
||
(let (types
|
||
negated)
|
||
;; Group negated types into a union type which might be better
|
||
;; handled by source-transform-union-typep above.
|
||
(loop for type in (intersection-type-types type)
|
||
do
|
||
(cond ((hairy-type-p type)
|
||
;; These might impose some sort of an order.
|
||
(setf negated nil)
|
||
(return))
|
||
((typep type 'negation-type)
|
||
(push (negation-type-type type) negated))
|
||
(t
|
||
(push type types))))
|
||
(cond (negated
|
||
(flet (#+nil ;; not always more compact
|
||
(widetag-test (base-tags)
|
||
(and (every #'array-type-p negated)
|
||
(multiple-value-bind (widetags more)
|
||
(sb-kernel::widetags-from-union-type negated)
|
||
(cond ((not more)
|
||
`(%other-pointer-subtype-p ,object ',(set-difference base-tags widetags)))))))
|
||
(test (types negated)
|
||
`(and ,@(and types
|
||
`((typep ,object
|
||
'(and ,@(mapcar #'type-specifier types)))))
|
||
(not
|
||
(typep ,object
|
||
'(or ,@(mapcar (lambda (x) (if (ctype-p x)
|
||
(type-specifier x)
|
||
x))
|
||
negated)))))))
|
||
(cond
|
||
;; (and array (not vector))
|
||
((and (eq (car types) (specifier-type 'array))
|
||
(not (cdr types))
|
||
(if (and (eq (car negated) (specifier-type 'vector))
|
||
(not (cdr negated)))
|
||
`(%other-pointer-subtype-p ,object '(,sb-vm:simple-array-widetag ,sb-vm:complex-array-widetag))
|
||
#+nil
|
||
(widetag-test sb-vm::+array-widetags+))))
|
||
#+nil
|
||
((and (eq (car types) (specifier-type 'vector))
|
||
(not (cdr types))
|
||
(widetag-test sb-vm::+vector-widetags+)))
|
||
;; Extract (and symbol (not null))
|
||
((and (memq (specifier-type 'symbol) types)
|
||
(let* ((mtype (find-if #'member-type-p negated))
|
||
(members (when mtype (member-type-members mtype))))
|
||
(when (member nil members)
|
||
`(and (non-null-symbol-p ,object)
|
||
,(test (delq1 (specifier-type 'symbol) types)
|
||
(append (delq1 mtype negated)
|
||
(let ((rem (remove nil members)))
|
||
(when rem
|
||
`((member ,@rem)))))))))))
|
||
(t
|
||
(test types negated)))))
|
||
(t
|
||
`(and ,@(mapcar (lambda (x)
|
||
`(typep ,object ',(type-specifier x)))
|
||
(intersection-type-types type)))))))
|
||
|
||
;;; If necessary recurse to check the cons type.
|
||
(defun source-transform-cons-typep
|
||
(object type &aux (car-type (cons-type-car-type type))
|
||
(cdr-type (cons-type-cdr-type type))
|
||
(car-test-p (not (type= car-type *universal-type*)))
|
||
(cdr-test-p (not (type= cdr-type *universal-type*))))
|
||
;; CONSP can be safely weakened to LISTP if either of the CAR
|
||
;; or CDR test (or both) can distinguish LIST from CONS
|
||
;; by never returning T when given an input of NIL.
|
||
(labels ((safely-weakened (ctype)
|
||
(typecase ctype
|
||
(member-type
|
||
(not (member nil (member-type-members ctype))))
|
||
(classoid
|
||
;; can't weaken if the specifier is (CONS SYMBOL)
|
||
(not (ctypep nil ctype)))
|
||
;; these are disjoint from NIL
|
||
((or cons-type numeric-type array-type character-set-type)
|
||
t)
|
||
(intersection-type
|
||
;; at least one of them must not spuriously return T
|
||
(some #'safely-weakened (compound-type-types ctype)))
|
||
(union-type
|
||
;; require that none spuriously return T
|
||
(every #'safely-weakened (compound-type-types ctype)))
|
||
(hairy-type
|
||
;; hack - (CONS KEYWORD) is weakenable
|
||
;; because NIL is not a keyword.
|
||
(equal (hairy-type-specifier ctype)
|
||
'(satisfies keywordp))))))
|
||
(cond
|
||
((and (not car-test-p) (not cdr-test-p))
|
||
`(consp ,object))
|
||
((and (not cdr-test-p)
|
||
(member-type-p car-type)
|
||
(vop-existsp :translate car-eq-if-listp)
|
||
(type-singleton-p car-type)
|
||
(typep (first (member-type-members car-type)) '(and symbol (not null))))
|
||
`(car-eq-if-listp ,object ',(first (member-type-members car-type))))
|
||
(t
|
||
(let ((car-test
|
||
(and car-test-p
|
||
`((typep (car ,object) ',(type-specifier car-type)))))
|
||
(cdr-test
|
||
(and cdr-test-p
|
||
`((typep (cdr ,object) ',(type-specifier cdr-type))))))
|
||
;; Being paranoid, perform the safely weakenable test first
|
||
;; so that the other part doesn't execute on an object that
|
||
;; it would not have gotten, were the CONSP test not weakened.
|
||
(cond ((and car-test-p (safely-weakened car-type))
|
||
`(and (listp ,object) ,@car-test ,@cdr-test))
|
||
((and cdr-test-p (safely-weakened cdr-type))
|
||
`(and (listp ,object) ,@cdr-test ,@car-test))
|
||
(t
|
||
`(and (consp ,object) ,@car-test ,@cdr-test))))))))
|
||
|
||
(defun source-transform-character-set-typep (object type)
|
||
(let ((pairs (character-set-type-pairs type)))
|
||
(or (and (= (length pairs) 1)
|
||
(= (caar pairs) 0)
|
||
(cond
|
||
#+(and sb-unicode (or x86-64 arm64))
|
||
((= (cdar pairs) (1- base-char-code-limit))
|
||
`(base-char-p ,object))
|
||
((= (cdar pairs) (1- char-code-limit))
|
||
`(characterp ,object))))
|
||
(let ((n-code (gensym "CODE")))
|
||
`(and (characterp ,object)
|
||
(let ((,n-code (char-code ,object)))
|
||
(or
|
||
,@(loop for pair in pairs
|
||
collect
|
||
`(<= ,(car pair) ,n-code ,(cdr pair))))))))))
|
||
|
||
#+sb-simd-pack
|
||
(defun source-transform-simd-pack-typep (object type)
|
||
(let ((mask (simd-pack-type-tag-mask type)))
|
||
(if (= mask sb-kernel::+simd-pack-wild+)
|
||
`(simd-pack-p ,object)
|
||
`(and (simd-pack-p ,object)
|
||
,(if (= (logcount mask) 1)
|
||
`(eql (%simd-pack-tag ,object) ,(sb-vm::simd-pack-mask->tag mask))
|
||
`(logbitp (%simd-pack-tag ,object) ,mask))))))
|
||
|
||
#+sb-simd-pack-256
|
||
(defun source-transform-simd-pack-256-typep (object type)
|
||
(let ((mask (simd-pack-256-type-tag-mask type)))
|
||
(if (= mask sb-kernel::+simd-pack-wild+)
|
||
`(simd-pack-256-p ,object)
|
||
`(and (simd-pack-256-p ,object)
|
||
,(if (= (logcount mask) 1)
|
||
`(eql (%simd-pack-256-tag ,object) ,(sb-vm::simd-pack-mask->tag mask))
|
||
`(logbitp (%simd-pack-256-tag ,object) ,mask))))))
|
||
|
||
;;; Return the predicate and type from the most specific entry in
|
||
;;; *TYPE-PREDICATES* that is a supertype of TYPE.
|
||
(defun find-supertype-predicate (type)
|
||
(declare (type ctype type))
|
||
(let ((res nil)
|
||
(res-type nil))
|
||
(dolist (x *backend-type-predicates*)
|
||
(let ((stype (car x)))
|
||
(when (and (csubtypep type stype)
|
||
(or (not res-type)
|
||
(csubtypep stype res-type)))
|
||
(setq res-type stype)
|
||
(setq res (cdr x)))))
|
||
(values res res-type)))
|
||
|
||
;;; Return forms to test that OBJ has the rank and dimensions
|
||
;;; specified by TYPE, where STYPE is the type we have checked against
|
||
;;; (which is the same but for dimensions and element type).
|
||
;;;
|
||
;;; Secondary return value is true if passing the generated tests implies that
|
||
;;; the array has a header.
|
||
(defun test-array-dimensions (original-obj type stype simple-array-header-p)
|
||
(declare (type array-type type stype))
|
||
(let ((obj `(truly-the ,(type-specifier stype) ,original-obj))
|
||
(dims (array-type-dimensions type))
|
||
(header-test (if simple-array-header-p
|
||
`(simple-array-header-p ,original-obj)
|
||
`(array-header-p ,original-obj))))
|
||
(unless (or (eq dims '*)
|
||
(equal dims (array-type-dimensions stype)))
|
||
(cond ((cdr dims)
|
||
(values `(,@(if (and simple-array-header-p
|
||
(vop-existsp :translate simple-array-header-of-rank-p)
|
||
(eq (array-type-dimensions stype) '*))
|
||
`((simple-array-header-of-rank-p ,original-obj ,(length dims)))
|
||
`(,(if simple-array-header-p
|
||
`(simple-array-header-p ,original-obj)
|
||
`(arrayp ,original-obj))
|
||
,@(when (eq (array-type-dimensions stype) '*)
|
||
(if (vop-existsp :translate %array-rank=)
|
||
`((%array-rank= ,obj ,(length dims)))
|
||
`((= (%array-rank ,obj) ,(length dims)))))))
|
||
,@(loop for d in dims
|
||
for i from 0
|
||
unless (eq '* d)
|
||
collect `(= (%array-dimension ,obj ,i) ,d)))
|
||
t))
|
||
((not dims)
|
||
(values `(,header-test
|
||
(= (%array-rank ,obj) 0))
|
||
t))
|
||
((not (array-type-complexp type))
|
||
(if (csubtypep stype (specifier-type 'vector))
|
||
(values (unless (eq '* (car dims))
|
||
`((= (vector-length ,obj) ,@dims)))
|
||
nil)
|
||
(values (if (eq '* (car dims))
|
||
`((simple-rank-1-array-*-p ,original-obj))
|
||
`((simple-rank-1-array-*-p ,original-obj)
|
||
(= (vector-length (truly-the (simple-array * (*)) ,original-obj)) ,@dims)))
|
||
nil
|
||
nil
|
||
t)))
|
||
(t
|
||
(values (unless (eq '* (car dims))
|
||
`((= (if ,header-test
|
||
(%array-dimension ,obj 0)
|
||
(vector-length ,obj))
|
||
,@dims)))
|
||
nil
|
||
(car dims)))))))
|
||
|
||
;;; Return forms to test that OBJ has the element-type specified by type
|
||
;;; specified by TYPE, where STYPE is the type we have checked against (which
|
||
;;; is the same but for dimensions and element type). If HEADERP is true, OBJ
|
||
;;; is guaranteed to be an array-header.
|
||
(defun test-array-element-type (obj type stype headerp pred length object-type)
|
||
(declare (type array-type type stype))
|
||
(let ((eltype (array-type-specialized-element-type type)))
|
||
(unless (or (type= eltype (array-type-specialized-element-type stype))
|
||
(eq eltype *wild-type*)
|
||
(csubtypep object-type
|
||
(make-array-type '*
|
||
:element-type eltype
|
||
:specialized-element-type eltype)))
|
||
(let* ((typecode (sb-vm:saetp-typecode (find-saetp-by-ctype eltype)))
|
||
(complexp (array-type-complexp type)))
|
||
(cond ((and headerp (not complexp))
|
||
(let ((obj `(truly-the ,(type-specifier stype) ,obj)))
|
||
;; If we know OBJ is an array header, and that the array is
|
||
;; simple, we also know there is exactly one indirection to
|
||
;; follow.
|
||
`(#-x86-64
|
||
(eq (%other-pointer-widetag (%array-data ,obj)) ,typecode)
|
||
#+x86-64
|
||
(widetag= (%array-data ,obj) ,typecode))))
|
||
((not complexp)
|
||
(values
|
||
`((and (%other-pointer-p ,obj)
|
||
(let ((widetag (%other-pointer-widetag ,obj)))
|
||
(or (eq widetag ,typecode)
|
||
(and (eq widetag sb-vm:simple-array-widetag)
|
||
(eq (%other-pointer-widetag (%array-data ,obj)) ,typecode))))))
|
||
;; skip checking for array.
|
||
t))
|
||
(t
|
||
(ecase pred
|
||
(arrayp
|
||
(values `((and (%other-pointer-p ,obj)
|
||
(let ((data ,obj))
|
||
(and
|
||
,@(when (eq complexp t)
|
||
`((/= (%other-pointer-widetag data)
|
||
,@(unless headerp
|
||
`(,typecode))
|
||
sb-vm:simple-array-widetag)))
|
||
(loop
|
||
(let ((widetag (%other-pointer-widetag data)))
|
||
(if (eq widetag ,typecode)
|
||
(return t)
|
||
(if (or (eq widetag sb-vm:simple-array-widetag)
|
||
(>= widetag sb-vm:complex-base-string-widetag))
|
||
(setf data (%array-data data))
|
||
(return nil)))))))))
|
||
t))
|
||
(vectorp
|
||
(if length
|
||
(values `((and (%other-pointer-p ,obj)
|
||
(let ((widetag (%other-pointer-widetag ,obj)))
|
||
(,@(if (eq complexp t)
|
||
'(progn)
|
||
`(if (eq widetag ,typecode)
|
||
(= (vector-length (truly-the (simple-array * (*)) ,obj)) ,length)))
|
||
(and (= widetag sb-vm:complex-vector-widetag)
|
||
(= (%array-dimension (truly-the (and (array * (*))
|
||
(not simple-array)) ,obj) 0)
|
||
,length)
|
||
(let ((data ,obj))
|
||
(loop
|
||
(setf data (%array-data data))
|
||
(let ((widetag (%other-pointer-widetag data)))
|
||
(if (eq widetag ,typecode)
|
||
(return t)
|
||
(unless (or (eq widetag sb-vm:simple-array-widetag)
|
||
(>= widetag sb-vm:complex-vector-widetag))
|
||
(return nil)))))))))))
|
||
t
|
||
t)
|
||
(values `((and (%other-pointer-p ,obj)
|
||
(let ((widetag (%other-pointer-widetag ,obj)))
|
||
(,@(if (eq complexp t)
|
||
'(progn)
|
||
`(if (eq widetag ,typecode)
|
||
t))
|
||
(and (= widetag sb-vm:complex-vector-widetag)
|
||
(let ((data ,obj))
|
||
(loop
|
||
(setf data (%array-data data))
|
||
(let ((widetag (%other-pointer-widetag data)))
|
||
(if (eq widetag ,typecode)
|
||
(return t)
|
||
(unless (or
|
||
(eq widetag sb-vm:simple-array-widetag)
|
||
(>= widetag sb-vm:complex-vector-widetag))
|
||
(return nil)))))))))))
|
||
t))))))))))
|
||
|
||
;;; If we can find a type predicate that tests for the type without
|
||
;;; dimensions, then use that predicate and test for dimensions.
|
||
;;; Otherwise, just do %TYPEP.
|
||
(defun source-transform-array-typep (object type &optional (object-type *universal-type*))
|
||
;; Intercept (SIMPLE-ARRAY * (*)) because otherwise it tests
|
||
;; (AND SIMPLE-ARRAY (NOT ARRAY-HEADER)) to weed out rank 0 and >1.
|
||
;; By design the simple arrays of of rank 1 occupy a contiguous
|
||
;; range of widetags, and unlike the arbitrary-widetags code for unions,
|
||
;; this nonstandard predicate can be generically defined for all backends.
|
||
(let ((dims (array-type-dimensions type))
|
||
(et (array-type-element-type type)))
|
||
(if (and (not (array-type-complexp type))
|
||
(eq et *wild-type*)
|
||
(equal dims '(*)))
|
||
`(simple-rank-1-array-*-p ,object)
|
||
(multiple-value-bind (pred stype) (find-supertype-predicate type)
|
||
(if (and (array-type-p stype)
|
||
;; (If the element type hasn't been defined yet, it's
|
||
;; not safe to assume here that it will eventually
|
||
;; have (UPGRADED-ARRAY-ELEMENT-TYPE type)=T, so punt.)
|
||
(not (unknown-type-p (array-type-element-type type)))
|
||
(or (eq (array-type-complexp stype) (array-type-complexp type))
|
||
(and (eql (array-type-complexp stype) :maybe)
|
||
(eql (array-type-complexp type) t))))
|
||
(let ((complex-tag (and
|
||
(eql (array-type-complexp type) t)
|
||
(singleton-p dims)
|
||
(and (neq et *wild-type*)
|
||
(sb-vm:saetp-complex-typecode
|
||
(find-saetp-by-ctype (array-type-element-type type))))))
|
||
(simple-array-header-p
|
||
(and (null (array-type-complexp stype))
|
||
(listp dims)
|
||
(cdr dims)))
|
||
(complexp (and (eql (array-type-complexp stype) :maybe)
|
||
(eql (array-type-complexp type) t))))
|
||
(if complex-tag
|
||
`(and (%other-pointer-p ,object)
|
||
(eq (%other-pointer-widetag ,object) ,complex-tag)
|
||
,@(unless (eq (car dims) '*)
|
||
`((= (%array-dimension ,object 0) ,(car dims)))))
|
||
(multiple-value-bind (dim-tests headerp length no-check-for-array1)
|
||
(test-array-dimensions object type stype simple-array-header-p)
|
||
(multiple-value-bind (type-test no-check-for-array2 length-checked)
|
||
(test-array-element-type object type stype headerp pred length object-type)
|
||
(if (or no-check-for-array1 no-check-for-array2)
|
||
`(and ,@type-test
|
||
,@(unless length-checked
|
||
dim-tests))
|
||
`(and
|
||
,@(cond ((and (eql pred 'vectorp)
|
||
complexp)
|
||
`((%other-pointer-subtype-p ,object
|
||
',(list sb-vm:complex-base-string-widetag
|
||
#+sb-unicode sb-vm:complex-character-string-widetag
|
||
sb-vm:complex-bit-vector-widetag
|
||
sb-vm:complex-vector-widetag))))
|
||
((and (eql pred 'arrayp)
|
||
complexp)
|
||
`((%other-pointer-subtype-p ,object
|
||
',(list sb-vm:complex-base-string-widetag
|
||
#+sb-unicode sb-vm:complex-character-string-widetag
|
||
sb-vm:complex-bit-vector-widetag
|
||
sb-vm:complex-vector-widetag
|
||
sb-vm:complex-array-widetag))))
|
||
(t
|
||
`(,@(unless (or (and headerp (eql pred 'arrayp))
|
||
simple-array-header-p)
|
||
;; ARRAY-HEADER-P from DIM-TESTS will test for that
|
||
`((,pred ,object)))
|
||
,@(when complexp
|
||
`((typep ,object '(not simple-array)))))))
|
||
,@dim-tests
|
||
,@type-test))))))
|
||
`(%typep ,object ',(type-specifier type)))))))
|
||
|
||
;;; Transform a type test against some instance type. The type test is
|
||
;;; flushed if the result is known at compile time. If not properly
|
||
;;; named, error. If sealed and has no subclasses, just test for
|
||
;;; layout-EQ. If a structure then test for layout-EQ and then a
|
||
;;; general test based on layout-inherits. Otherwise, look up the indirect
|
||
;;; class-cell and call CLASS-CELL-TYPEP at runtime.
|
||
(deftransform %instance-typep ((object spec) * * :node node)
|
||
(aver (constant-lvar-p spec))
|
||
(let* ((spec (lvar-value spec))
|
||
(class (specifier-type spec))
|
||
(name (classoid-name class))
|
||
(otype (lvar-type object)))
|
||
(cond
|
||
;; Flush tests whose result is known at compile time.
|
||
((not (types-equal-or-intersect otype class))
|
||
nil)
|
||
((csubtypep otype class)
|
||
t)
|
||
;; If not properly named, error.
|
||
((not (and name (eq (find-classoid name) class)))
|
||
(compiler-error "can't compile TYPEP of anonymous or undefined ~
|
||
class:~% ~S"
|
||
class))
|
||
((eq class (type-intersection otype (specifier-type 'instance)))
|
||
`(%instancep object))
|
||
(t
|
||
;; Delay the type transform to give type propagation a chance.
|
||
(delay-ir1-transform node :constraint)
|
||
(transform-instance-typep class)))))
|
||
|
||
;;; Notice that there are some instance types for which it is almost impossible
|
||
;;; to create. One such is SEQUENCE, viz: (make-instance 'sequence) =>
|
||
;;; "Cannot allocate an instance of #<BUILT-IN-CLASS SEQUENCE>."
|
||
;;; We should not need to check for that, just the 'inherits' vector.
|
||
;;; However, bootstrap code does a sleazy thing, making an instance of
|
||
;;; the abstract base type which is impossible for user code to do.
|
||
;;;
|
||
;;; Preferably the prototype instance for SEQUENCE would be one that could
|
||
;;; exist, so it would be a STANDARD-OBJECT and SEQUENCE. But it's not.
|
||
;;; Hence we would have to check for a layout that no code using the documented
|
||
;;; sequence API would ever see, just to get the boundary case right.
|
||
;;; The for STREAM and FILE-STREAM.
|
||
;;; But there was precedent for builtin class prototype instances
|
||
;;; failing their type predicate, i.e. (TYPEP (CLASS-PROTOTYPE X) X) => NIL
|
||
;;; which was fixed in git rev d60a6d30.
|
||
;;; Also for what it's worth, some builtins use a prototype object that is strictly
|
||
;;; deeper than layout of the named class because it is indeed the case that no
|
||
;;; object's layout can ever be EQ to that of the ancestor.
|
||
;;; e.g. a fixnum as representative of class REAL.
|
||
;;; So in actual practice, you can't make something that is a pure STREAM, etc.
|
||
|
||
;;; TODOs:
|
||
;;; 1. There is an additional tweak that can potentially return false in one fewer
|
||
;;; conditional branch if the layout being tested has depthoid 8 (or 9 if #+64-bit).
|
||
;;; In that scenario, if the ID word of the candidate structure's layout does not
|
||
;;; exist, then it's the 0th bitmap word and safe to read always. Therefore
|
||
;;; STRUCTURE-IS-A and depthoid can be tested in that order. If there is no ID match,
|
||
;;; there's no depthoid test. If there is an ID match, it's the same as before.
|
||
;;; 2. Since all backends implement STRUCTURE-IS-A, is there any reason that the
|
||
;;; depthoid test is in the transform's expansion and not baked into that vop?
|
||
;;; Putting it in the vop could be better for some backends,
|
||
;;; and would eliminate the ad-hoc LAYOUT-DEPTHOID-GE vop.
|
||
|
||
#-(or x86 x86-64) ; vop-translated for these 2
|
||
(defmacro layout-depthoid-ge (layout depthoid)
|
||
`(>= (layout-depthoid ,layout) ,depthoid))
|
||
(symbol-macrolet ((get-hash 'layout-clos-hash)
|
||
(get-flags 'layout-flags))
|
||
(defun transform-instance-typep (classoid)
|
||
(binding*
|
||
((name (classoid-name classoid))
|
||
(layout (let ((res (info :type :compiler-layout name)))
|
||
(when (and res (not (layout-invalid res))) res)))
|
||
((lowtag lowtag-test slot-reader)
|
||
(cond ((csubtypep classoid (specifier-type 'funcallable-instance))
|
||
(values sb-vm:fun-pointer-lowtag
|
||
'(function-with-layout-p object) '(%fun-layout object)))
|
||
((csubtypep classoid (specifier-type 'instance))
|
||
(values sb-vm:instance-pointer-lowtag
|
||
'(%instancep object) '(%instance-layout object)))))
|
||
(depthoid (if layout (layout-depthoid layout) -1))
|
||
(type (make-symbol "TYPE")))
|
||
(declare (ignorable layout))
|
||
|
||
;; Easiest case first: single bit test.
|
||
(cond ((member name '(condition pathname structure-object))
|
||
(let ((flag (case name
|
||
(condition +condition-layout-flag+)
|
||
(pathname +pathname-layout-flag+)
|
||
(t +structure-layout-flag+))))
|
||
(if (vop-existsp :translate structure-typep)
|
||
`(structure-typep object ,layout)
|
||
`(and (%instancep object)
|
||
(logtest (,get-flags (%instance-layout object)) ,flag)))))
|
||
|
||
;; Next easiest: Sealed and no subtypes. Typically for DEFSTRUCT only.
|
||
;; Even if you don't seal a DEFCLASS, we're allowed to assume that things
|
||
;; won't change, as per CLHS 3.2.2.3 on Semantic Constraints:
|
||
;; "Classes defined by defclass in the compilation environment must be defined
|
||
;; at run time to have the same superclasses and same metaclass."
|
||
;; I think that means we should know the lowtag always. Nonetheless, this isn't
|
||
;; an important scenario, and only if you _do_ seal a class could this case be
|
||
;; reached; users rarely seal their classes since the standard doesn't say how.
|
||
((and layout
|
||
(eq (classoid-state classoid) :sealed)
|
||
(not (classoid-subclasses classoid)))
|
||
(cond ((and (eq lowtag sb-vm:instance-pointer-lowtag)
|
||
(vop-existsp :translate structure-typep))
|
||
`(structure-typep object ,layout))
|
||
(lowtag-test
|
||
`(and ,lowtag-test
|
||
,(if (vop-existsp :translate layout-eq)
|
||
`(layout-eq object ,layout ,lowtag)
|
||
`(eq ,slot-reader ,layout))))
|
||
(t
|
||
;; `(eq ,layout
|
||
;; (if-vop-existsp (:translate %instanceoid-layout)
|
||
;; (%instanceoid-layout object)
|
||
;; ;; Slightly quicker than LAYOUT-OF. See also %PCL-INSTANCE-P
|
||
;; (cond ((%instancep object) (%instance-layout object))
|
||
;; ((funcallable-instance-p object) (%fun-layout object))
|
||
;; (t ,(find-layout 't)))))
|
||
(bug "Unexpected metatype for ~S" layout))))
|
||
|
||
;; All other structure types
|
||
((and (typep classoid 'structure-classoid) layout)
|
||
;; structure type tests; hierarchical layout depths
|
||
(aver (eql lowtag sb-vm:instance-pointer-lowtag))
|
||
;; we used to check for invalid layouts here, but in fact that's both unnecessary and
|
||
;; wrong; it's unnecessary because structure classes can't be redefined, and it's wrong
|
||
;; because it is quite legitimate to pass an object with an invalid layout
|
||
;; to a structure type test.
|
||
(if (vop-existsp :translate structure-typep)
|
||
;; A single VOP is easier to optimize later
|
||
`(structure-typep object ,layout)
|
||
`(and (%instancep object)
|
||
,(if (<= depthoid sb-kernel::layout-id-vector-fixed-capacity)
|
||
`(%structure-is-a (%instance-layout object) ,layout)
|
||
`(let ((,type (%instance-layout object)))
|
||
(and (layout-depthoid-ge ,type ,depthoid)
|
||
(%structure-is-a ,type ,layout)))))))
|
||
|
||
((> depthoid 0)
|
||
;; fixed-depth ancestors of non-structure types:
|
||
;; STREAM, FILE-STREAM, STRING-STREAM, and SEQUENCE.
|
||
#+sb-xc-host (when (typep classoid 'static-classoid)
|
||
;; should have use :SEALED code above
|
||
(bug "Non-frozen static classoids ~S" name))
|
||
(let ((guts `((when (zerop (,get-hash ,type))
|
||
(setq ,type (update-object-layout object)))
|
||
,(ecase name
|
||
(stream
|
||
`(logtest (,get-flags ,type) ,+stream-layout-flag+))
|
||
(file-stream
|
||
`(logtest (,get-flags ,type) ,+file-stream-layout-flag+))
|
||
(string-stream
|
||
`(logtest (,get-flags ,type) ,+string-stream-layout-flag+))
|
||
;; Testing the type EXTENDED-SEQUENCE tests for #<LAYOUT of SEQUENCE>.
|
||
;; It can only arise from a direct invocation of TRANSFORM-INSTANCE-TYPEP,
|
||
;; because the lisp type is not a classoid. It's done this way to define
|
||
;; the logic once only, instead of both here and src/code/pred.lisp.
|
||
(sequence
|
||
`(logtest (,get-flags ,type) ,+sequence-layout-flag+))))))
|
||
(if lowtag-test
|
||
`(and ,lowtag-test (let ((,type ,slot-reader)) ,@guts))
|
||
(if-vop-existsp (:translate %instanceoid-layout)
|
||
`(let ((,type (%instanceoid-layout object))) ,@guts)
|
||
`(block typep
|
||
(let ((,type (cond ((%instancep object) (%instance-layout object))
|
||
((funcallable-instance-p object) (%fun-layout object))
|
||
(t (return-from typep nil)))))
|
||
,@guts))))))
|
||
|
||
(t
|
||
`(classoid-cell-typep ',(find-classoid-cell name :create t)
|
||
object))))))
|
||
|
||
;;; Transform to backend predicates without delaying, they have their
|
||
;;; own optimizers and are visible to constraints.
|
||
;;; Should evaluate OBJECT once.
|
||
(defun %source-transform-typep-simple (object type &optional ctype)
|
||
(let ((ctype (or ctype
|
||
(handler-bind
|
||
(((or parse-unknown-type sb-kernel::parse-deprecated-type)
|
||
(lambda (c)
|
||
c
|
||
(return-from %source-transform-typep-simple))))
|
||
(careful-specifier-type type)))))
|
||
(when ctype
|
||
(or
|
||
(cond ((eq ctype *universal-type*) `(progn ,object t))
|
||
((eq ctype *empty-type*) `(progn ,object nil)))
|
||
(and (not (intersection-type-p ctype))
|
||
(multiple-value-bind (constantp value) (type-singleton-p ctype)
|
||
(and constantp
|
||
`(eql ,object ',value))))
|
||
(handler-case
|
||
(or
|
||
(let ((pred (backend-type-predicate ctype)))
|
||
(when pred `(,pred ,object)))
|
||
(let* ((negated (type-negation ctype))
|
||
(pred (backend-type-predicate negated)))
|
||
(cond (pred
|
||
`(not (,pred ,object)))
|
||
((numeric-type-p negated)
|
||
`(not (typep ,object ',(type-specifier negated)))))))
|
||
#+sb-xc-host
|
||
(sb-kernel::cross-type-warning
|
||
nil))))))
|
||
|
||
;;; If the specifier argument is a quoted constant, then we consider
|
||
;;; converting into a simple predicate or other stuff. If the type is
|
||
;;; constant, but we can't transform the call, then we convert to
|
||
;;; %TYPEP. We only pass when the type is non-constant. This allows us
|
||
;;; to recognize between calls that might later be transformed
|
||
;;; successfully when a constant type is discovered. We don't give an
|
||
;;; efficiency note when we pass, since the IR1 transform will give
|
||
;;; one if necessary and appropriate.
|
||
;;;
|
||
;;; If the type is TYPE= to a type that has a predicate, then expand
|
||
;;; to that predicate. Otherwise, we dispatch off of the type's type.
|
||
;;; These transformations can increase space, but it is hard to tell
|
||
;;; when, so we ignore policy and always do them.
|
||
(defun transform-typep (object object-lvar type ctype node)
|
||
(or
|
||
(%source-transform-typep-simple object type ctype)
|
||
(progn
|
||
(delay-ir1-transform node :constraint)
|
||
(typecase ctype
|
||
(hairy-type
|
||
(source-transform-hairy-typep object ctype node))
|
||
(negation-type
|
||
(source-transform-negation-typep object ctype))
|
||
(numeric-type
|
||
(source-transform-numeric-typep object ctype))
|
||
((or union-type numeric-union-type)
|
||
(source-transform-union-typep object ctype))
|
||
(intersection-type
|
||
(source-transform-intersection-typep object ctype))
|
||
(member-type
|
||
`(if (member ,object ',(member-type-members ctype)) t))
|
||
(args-type
|
||
(compiler-warn "illegal type specifier for TYPEP: ~S" type)
|
||
(return-from transform-typep (values nil t)))
|
||
(classoid
|
||
`(%instance-typep ,object ',type))
|
||
(array-type
|
||
(source-transform-array-typep object ctype (lvar-type object-lvar)))
|
||
(cons-type
|
||
(source-transform-cons-typep object ctype))
|
||
(character-set-type
|
||
(source-transform-character-set-typep object ctype))
|
||
#+sb-simd-pack
|
||
(simd-pack-type
|
||
(source-transform-simd-pack-typep object ctype))
|
||
#+sb-simd-pack-256
|
||
(simd-pack-256-type
|
||
(source-transform-simd-pack-256-typep object ctype))
|
||
(t nil)))
|
||
`(%typep ,object ',type)))
|
||
|
||
;;; These things will be removed by the tree shaker, so no #+ needed.
|
||
(defvar *interesting-types* nil)
|
||
(defun involves-alien-p (ctype)
|
||
(sb-kernel::map-type
|
||
(lambda (type)
|
||
(when (alien-type-type-p type) (return-from involves-alien-p t)))
|
||
ctype))
|
||
(defun dump/restore-interesting-types (op)
|
||
(declare (ignorable op))
|
||
#+collect-typep-regression-dataset
|
||
(ecase op
|
||
(write
|
||
(when *interesting-types*
|
||
(let ((list (sort (loop for k being each hash-key of *interesting-types* collect k)
|
||
#'string< :key #'write-to-string)))
|
||
(with-open-file (f "interesting-types.lisp-expr" :direction :output
|
||
:if-exists :supersede :if-does-not-exist :create)
|
||
(let ((*package* #+sb-xc-host (find-package "XC-STRICT-CL")
|
||
#-sb-xc-host #.(find-package "SB-KERNEL"))
|
||
(*print-pretty* nil)
|
||
(*print-length* nil)
|
||
(*print-level* nil)
|
||
(*print-readably* t))
|
||
(dolist (item list)
|
||
(write (uncross item) :stream f)
|
||
(terpri f)))))))
|
||
(read
|
||
(unless (hash-table-p *interesting-types*)
|
||
(setq *interesting-types* (make-hash-table :test 'equal :synchronized t)))
|
||
(with-open-file (f "interesting-types.lisp-expr" :if-does-not-exist nil)
|
||
(when f
|
||
(let ((*package* (find-package "SB-KERNEL")))
|
||
(loop (let ((expr (read f nil f)))
|
||
(when (eq expr f) (return))
|
||
(format t "Read ~a~%" expr)
|
||
(setf (gethash expr *interesting-types*) t))))))
|
||
*interesting-types*)))
|
||
|
||
;;;; coercion
|
||
|
||
;;; Constant-folding.
|
||
;;;
|
||
#-sb-xc-host
|
||
(defoptimizer (coerce optimizer) ((x type) node)
|
||
(when (and (constant-lvar-p x) (constant-lvar-p type))
|
||
(let ((value (lvar-value x)))
|
||
(when (or (numberp value) (characterp value))
|
||
(constant-fold-call node)
|
||
t))))
|
||
|
||
;;; Drops dimension information from vector types.
|
||
;;; Returns four values
|
||
;;; * vector ctype
|
||
;;; * upgraded-element ctype or requsted element
|
||
;;; * T if the upgraded-element is upgraded, i.e. it
|
||
;;; does not contain any unknown types.
|
||
;;; * T if there were any dimensions
|
||
(defun simplify-vector-type (type)
|
||
(labels ((process-compound-type (types)
|
||
(let (array-types
|
||
element-types
|
||
(upgraded t)
|
||
dimensions-removed)
|
||
(dolist (type types)
|
||
(unless (or (hairy-type-p type)
|
||
(negation-type-p type))
|
||
(multiple-value-bind (type et upgraded dimensions) (simplify type)
|
||
(push type array-types)
|
||
(push et element-types)
|
||
(when dimensions
|
||
(setf dimensions-removed t))
|
||
(unless upgraded
|
||
(setf upgraded nil)))))
|
||
(values (apply #'type-union array-types)
|
||
(if (member *wild-type* element-types)
|
||
*wild-type*
|
||
(apply #'type-union element-types))
|
||
upgraded
|
||
dimensions-removed)))
|
||
(simplify (type)
|
||
(cond ((and (array-type-p type)
|
||
(singleton-p (array-type-dimensions type)))
|
||
(let* ((upgraded t)
|
||
(et (array-type-specialized-element-type type))
|
||
(et (cond ((neq et *wild-type*)
|
||
et)
|
||
((eq (array-type-element-type type) *wild-type*)
|
||
et)
|
||
(t
|
||
(setf upgraded nil)
|
||
(array-type-element-type type)))))
|
||
(values (specifier-type
|
||
(list (if (array-type-complexp type)
|
||
'array
|
||
'simple-array)
|
||
(type-specifier et)
|
||
'(*)))
|
||
et
|
||
upgraded
|
||
(not (eq (car (array-type-dimensions type)) '*)))))
|
||
((union-type-p type)
|
||
(process-compound-type (union-type-types type)))
|
||
((intersection-type-p type)
|
||
(process-compound-type (intersection-type-types type)))
|
||
((member-type-p type)
|
||
(process-compound-type
|
||
(mapcar #'ctype-of (member-type-members type))))
|
||
(t
|
||
(error "~a is not a subtype of VECTOR." type)))))
|
||
(simplify type)))
|
||
|
||
(defun strip-array-dimensions-and-complexity (type &optional simple)
|
||
(labels ((process-compound-type (types)
|
||
(let (array-types)
|
||
(dolist (type types)
|
||
(unless (or (hairy-type-p type)
|
||
(sb-kernel::negation-type-p type))
|
||
(push (strip type) array-types)))
|
||
(apply #'type-union array-types)))
|
||
(strip (type)
|
||
(cond ((array-type-p type)
|
||
(let ((dim (array-type-dimensions type)))
|
||
(make-array-type
|
||
(if (eq dim '*)
|
||
dim
|
||
(make-list (length dim)
|
||
:initial-element '*))
|
||
:complexp (if simple
|
||
nil
|
||
:maybe)
|
||
:element-type (array-type-element-type type)
|
||
:specialized-element-type (array-type-specialized-element-type type))))
|
||
((union-type-p type)
|
||
(process-compound-type (union-type-types type)))
|
||
((intersection-type-p type)
|
||
(process-compound-type (intersection-type-types type)))
|
||
((member-type-p type)
|
||
(process-compound-type
|
||
(mapcar #'ctype-of (member-type-members type))))
|
||
(t
|
||
(error "~a is not a subtype of ARRAY." type)))))
|
||
(strip type)))
|
||
|
||
(defun check-coerce (value-type to-type type-specifier node)
|
||
(flet ((fail ()
|
||
(compiler-warn "Cannot coerce ~s to ~s"
|
||
(type-specifier value-type)
|
||
(type-specifier to-type))
|
||
(setf (combination-kind node) :error)
|
||
(give-up-ir1-transform)))
|
||
(cond ((eq to-type *empty-type*)
|
||
(fail))
|
||
((types-equal-or-intersect value-type to-type))
|
||
((csubtypep to-type (specifier-type 'sequence))
|
||
(unless (csubtypep to-type (specifier-type 'sequence))
|
||
(fail)))
|
||
((eql type-specifier 'character)
|
||
(unless (types-equal-or-intersect value-type
|
||
(specifier-type 'string))
|
||
(fail)))
|
||
((csubtypep to-type (specifier-type 'complex))
|
||
(unless (types-equal-or-intersect value-type
|
||
(specifier-type 'number))
|
||
(fail)))
|
||
|
||
((csubtypep to-type (specifier-type 'float))
|
||
(unless (types-equal-or-intersect value-type
|
||
(specifier-type 'real))
|
||
(fail)))
|
||
((eq type-specifier 'function)
|
||
(unless (types-equal-or-intersect value-type
|
||
(specifier-type '(or symbol cons)))
|
||
(fail)))
|
||
(t
|
||
(fail)))))
|
||
|
||
(deftransform coerce ((x type) * * :node node)
|
||
(unless (constant-lvar-p type)
|
||
(give-up-ir1-transform))
|
||
(let* ((tval (lvar-value type))
|
||
(tspec (ir1-transform-specifier-type tval))
|
||
(value-type (lvar-type x)))
|
||
(check-coerce value-type tspec tval node)
|
||
;; Note: The THE forms we use to wrap the results make sure that
|
||
;; specifiers like (SINGLE-FLOAT 0.0 1.0) can raise a TYPE-ERROR.
|
||
(cond
|
||
((csubtypep value-type tspec)
|
||
'x)
|
||
((csubtypep tspec (specifier-type 'double-float))
|
||
`(the ,tval (%double-float x)))
|
||
((csubtypep tspec (specifier-type 'single-float))
|
||
`(the ,tval (%single-float x)))
|
||
;; FIXME: #+long-float (t ,(error "LONG-FLOAT case needed"))
|
||
((csubtypep tspec (specifier-type 'float))
|
||
(if (types-equal-or-intersect value-type (specifier-type 'float))
|
||
`(the ,tval (if (floatp x)
|
||
x
|
||
(let ((r (the* (real :silent-conflict t) x)))
|
||
(declare (muffle-conditions code-deletion-note))
|
||
(sb-kernel:%single-float r))))
|
||
`(the ,tval (%single-float x))))
|
||
((csubtypep tspec (specifier-type 'complex))
|
||
(multiple-value-bind (part-type result-type)
|
||
(cond ((and (numeric-type-p tspec)
|
||
(numeric-type-format tspec))) ; specific FLOAT type
|
||
((csubtypep tspec (specifier-type '(complex float)))
|
||
;; unspecific FLOAT type
|
||
'float)
|
||
((csubtypep tspec (specifier-type '(complex rational)))
|
||
(values 'rational `(or ,tval rational)))
|
||
(t
|
||
(values t `(or ,tval rational))))
|
||
(let ((result-type (or result-type tval)))
|
||
`(cond
|
||
((not (typep x 'complex))
|
||
(the ,result-type (complex (coerce x ',part-type))))
|
||
((typep x ',tval)
|
||
x)
|
||
(t ; X is COMPLEX, but not of the requested type
|
||
,(if (eq part-type 'rational)
|
||
;; Can't coerce non-rational to a rational and
|
||
;; CHECK-COERCE will warn, so just full call
|
||
;; COERCE and let it signal an error.
|
||
`(locally (declare (notinline coerce))
|
||
(coerce x ',tval))
|
||
`(the ,result-type
|
||
(complex (coerce (realpart x) ',part-type)
|
||
(coerce (imagpart x) ',part-type)))))))))
|
||
((eq tval 'character)
|
||
`(character x))
|
||
;; Handle specialized element types for 1D arrays.
|
||
((multiple-value-bind (result already-type-p dimension specialization)
|
||
(cond ((and (array-type-p tspec)
|
||
(neq (array-type-complexp tspec) t) ; :MAYBE and NIL are good
|
||
(not (contains-unknown-type-p (array-type-element-type tspec)))
|
||
;; just for requesting (array nil (*)), you lose
|
||
(neq (array-type-specialized-element-type tspec) *empty-type*)
|
||
(consp (array-type-dimensions tspec)))
|
||
(values tspec
|
||
`(typep x ',(type-specifier tspec))
|
||
(car (array-type-dimensions tspec))
|
||
(let ((et (array-type-specialized-element-type tspec)))
|
||
(unless (or (eq et *universal-type*) ; don't need
|
||
;; * is illegal as :element-type; in this context
|
||
;; it means to produce a SIMPLE-VECTOR
|
||
(eq et *wild-type*))
|
||
`(:element-type ',(type-specifier et))))))
|
||
;; Check for string types. This loses on (STRING 1) and such.
|
||
#+sb-unicode
|
||
((type= tspec (specifier-type 'simple-string))
|
||
(values 'simple-string '(simple-string-p x) '* '(:element-type 'character)))
|
||
#+sb-unicode
|
||
((type= tspec (specifier-type 'string))
|
||
(values 'string '(stringp x) '* '(:element-type 'character))))
|
||
(when result
|
||
;; If the dimension is in the type, we check the input length if safety > 0,
|
||
;; though technically CLHS would allow not checking in safety < 3.
|
||
;; And if mismatch occurs in unsafe code, the results accords with the
|
||
;; specifier, NOT the dimension of the input. This is a rational choice
|
||
;; because one could not argue that incorrect code should have taken the
|
||
;; bad input's length when COERCE was asked for an exact type of output.
|
||
`(truly-the ,result
|
||
(if ,already-type-p
|
||
x
|
||
,(cond ((eq dimension '*)
|
||
(cond ((and (lvar-matches x :fun-names '(reverse nreverse
|
||
sb-impl::list-reverse
|
||
sb-impl::vector-reverse
|
||
sb-impl::list-nreverse
|
||
sb-impl::vector-nreverse))
|
||
(almost-immediately-used-p x (lvar-use x) :flushable t))
|
||
(splice-fun-args x :any 1)
|
||
;; The make-array transform can handle this
|
||
`(make-array (length x) ,@specialization :initial-contents (reverse x)))
|
||
(t
|
||
#+ubsan
|
||
;; Passing :INITIAL-CONTENTS avoids allocating ubsan shadow bits,
|
||
;; but redundantly checks the length of the input in MAKE-ARRAY's
|
||
;; transform because we don't or can't infer that LENGTH gives the
|
||
;; same answer each time it is called on X. There may be a way to
|
||
;; extract more efficiency - at least eliminate the unreachable
|
||
;; error-signaling code on mismatch - but I don't care to try.
|
||
`(make-array (length x) ,@specialization :initial-contents x)
|
||
#-ubsan ; better: do not generate a redundant LENGTH check
|
||
`(replace (make-array (length x) ,@specialization) x))))
|
||
((policy node (= safety 0)) ; Disregard the input length
|
||
`(replace (make-array ,dimension ,@specialization) x))
|
||
(t
|
||
`(make-array ,dimension ,@specialization :initial-contents x))))))))
|
||
((type= tspec (specifier-type 'list))
|
||
`(coerce-to-list x))
|
||
((csubtypep tspec (specifier-type 'extended-sequence))
|
||
(let ((class (and (symbolp tval) (find-class tval nil))))
|
||
(if (null class)
|
||
(give-up-ir1-transform)
|
||
`(coerce-to-extended-sequence x (load-time-value (find-class ',tval) t)))))
|
||
((type= tspec (specifier-type 'function))
|
||
(if (csubtypep (lvar-type x) (specifier-type 'symbol))
|
||
`(coerce-symbol-to-fun x)
|
||
;; if X can later be derived as FUNCTION then we don't want
|
||
;; to call COERCE-TO-FUN, because there's no smartness
|
||
;; that can undo that and see that it's really (IDENTITY X).
|
||
(progn (delay-ir1-transform node :constraint)
|
||
`(coerce-to-fun x))))
|
||
((multiple-value-bind (p really)
|
||
(csubtypep tspec
|
||
(specifier-type '(or sequence character complex float function)))
|
||
(and really
|
||
(not p)))
|
||
`(the* (,tspec :context coerce-context) x))
|
||
(t
|
||
(give-up-ir1-transform
|
||
"~@<open coding coercion to ~S not implemented.~:@>"
|
||
tval)))))
|
||
|
||
(deftransform #+64-bit unsigned-byte-64-p #-64-bit unsigned-byte-32-p
|
||
((value) (sb-vm:signed-word) * :important nil)
|
||
`(>= value 0))
|
||
|
||
(when-vop-existsp (:translate unsigned-byte-x-p)
|
||
(deftransform unsigned-byte-x-p
|
||
((value x) (t t) * :important nil :node node)
|
||
(ir1-transform-type-predicate value (specifier-type `(unsigned-byte ,(lvar-value x))) node))
|
||
|
||
(deftransform unsigned-byte-x-p
|
||
((value x) ((integer * #.most-positive-word) t) * :important nil)
|
||
`(#+64-bit unsigned-byte-64-p #-64-bit unsigned-byte-32-p x)))
|
||
|
||
(deftransform %other-pointer-p ((object))
|
||
(let ((type (lvar-type object)))
|
||
(cond ((not (types-equal-or-intersect type (specifier-type 'other-pointer)))
|
||
nil)
|
||
((or (csubtypep type (specifier-type 'other-pointer))
|
||
;; It doesn't negate to this type, so check both
|
||
(csubtypep type (specifier-type '(not (or fixnum #+64-bit single-float
|
||
list function instance character)))))
|
||
t)
|
||
((give-up-ir1-transform)))))
|
||
|
||
;;; BIGNUMP is simpler than INTEGERP, so if we can rule out FIXNUM then ...
|
||
(deftransform integerp ((x) ((not fixnum)) * :important nil) '(bignump x))
|
||
|
||
(deftransform structure-typep ((object type) (t t) * :node node)
|
||
(if (types-equal-or-intersect (lvar-type object) (specifier-type 'instance))
|
||
(give-up-ir1-transform)
|
||
nil))
|
||
|
||
(deftransform structure-typep ((object type) (t (constant-arg t)))
|
||
(let* ((layout (lvar-value type))
|
||
(type (layout-classoid layout))
|
||
(diff (type-difference (lvar-type object) type))
|
||
(pred (backend-type-predicate diff)))
|
||
(cond ((not (types-equal-or-intersect (lvar-type object) type))
|
||
nil)
|
||
((csubtypep (lvar-type object) type)
|
||
t)
|
||
(pred
|
||
`(not (,pred object)))
|
||
(t
|
||
(give-up-ir1-transform)))))
|
||
|
||
(deftransform classoid-cell-typep ((cell object) ((constant-arg t) t))
|
||
(let* ((type (specifier-type (classoid-cell-name (lvar-value cell))))
|
||
(diff (type-difference (lvar-type object) type))
|
||
(pred (backend-type-predicate diff)))
|
||
(if pred
|
||
`(not (,pred object))
|
||
(give-up-ir1-transform))))
|
||
|
||
(when-vop-existsp (:translate signed-byte-8-p)
|
||
(macrolet ((def (bits)
|
||
`(deftransform ,(symbolicate "SIGNED-BYTE-" (princ-to-string bits) "-P")
|
||
((x) (unsigned-byte) * :important nil)
|
||
'(typep x '(unsigned-byte ,(1- bits))))))
|
||
(def 8)
|
||
(def 16)
|
||
#+64-bit
|
||
(def 32)))
|
||
|
||
;;; source-transform-union-typep would generate the same thing but
|
||
;;; it's too complicated to be optimized later, hence the delay.
|
||
(deftransform string-designator-p ((x) * * :node node)
|
||
(delay-ir1-transform node :constraint)
|
||
`(or (%other-pointer-subtype-p x '(,sb-vm:symbol-widetag ,@sb-vm::+string-widetags+))
|
||
(null (truly-the (not (or (and symbol (not null)) string)) x))
|
||
(characterp (truly-the (not (or symbol string)) x))))
|
||
|
||
(defoptimizer (check-type-error-trap derive-type) ((place place-value type/string))
|
||
(when (constant-lvar-p type/string)
|
||
(let ((type (lvar-value type/string)))
|
||
(if (stringp type)
|
||
(careful-specifier-type (cdr (lvar-value place)))
|
||
(careful-specifier-type type)))))
|
||
|
||
(deftransform sequencep ((x) ((not extended-sequence)))
|
||
`(typep x '(or list vector)))
|