SBCLのeq
の挙動が気になったので。
内部でポインタに変換されないオブジェクトは、値が同一ならeq
で区別できない:
CL-USER> (eq 1f0 1f0)
T
CL-USER> (eq 1d0 1d0)
NIL
CL-USER> (eq 1 1)
T
CL-USER> (eq (1+ most-positive-fixnum) (1+ most-positive-fixnum))
NIL
CL-USER> (eq #\あ #\あ)
T
上の例で言うと、fixnum
やsingle-float
やcharacter
はボクシングされない。double-float
はボクシングされるのでeq
で区別される。
ややこしいのはfixnum
の範囲を超える64ビット整数の扱いで、REPLでは上のように偽になるが、コンパイラがうまくやれば(unsigned-byte 64)
や(signed-byte 64)
はボックス化なしで扱われるため、結果が変わったりする:
(defun number-eq (x)
(eq (+ x 1) (+ x 1)))
;; NUMBER型でコンパイルされているので、
;; FIXNUMの範囲を超えるとボクシングされる
(number-eq most-positive-fixnum)
;; => NIL
;; FIXNUMはボクシングされない
(number-eq 0)
;; => T
;; INTEGER型でもボクシングされる
(defun integer-eq (x)
(declare (integer x))
(eq (+ x 1) (+ x 1)))
(integer-eq most-positive-fixnum)
;; => NIL
;; FIXNUMを宣言すれば(+ X 1)が(SIGNED-BYTE 64)の範囲にあると推論されて、
;; ボクシングなしのコードになる
(defun int64-eq (x)
(declare (fixnum x))
(eq (+ x 1) (+ x 1)))
(int64-eq most-positive-fixnum)
;; => T
また、定数畳み込みも影響することがある:
(defun constant-eq ()
(eq (1+ most-positive-fixnum) (1+ most-positive-fixnum)))
(constant-eq)
;; => T
(disassemble #'constant-eq)
;; そもそも返り値がTに畳み込まれている。
; disassembly for CONSTANT-EQ
; Size: 28 bytes. Origin: #x10026915C3
; C3: 498B4560 MOV RAX, [R13+96] ; no-arg-parsing entry point
; thread.binding-stack-pointer
; C7: 488945F8 MOV [RBP-8], RAX
; CB: 840425F8FF1020 TEST AL, [#x2010FFF8] ; safepoint
; D2: BA4F001120 MOV EDX, #x2011004F ; T
; D7: 488BE5 MOV RSP, RBP
; DA: F8 CLC
; DB: 5D POP RBP
; DC: C3 RET
; DD: CC0F BREAK 15 ; Invalid argument count trap
;; notinlineでコード変換を禁止するとボクシングされる
(defun not-constant-eq ()
(declare (notinline 1+))
(eq (1+ most-positive-fixnum) (1+ most-positive-fixnum)))
(not-constant-eq)
;; => NIL
double-float
型についてはeq
をトランスレートするVOPがないので、基本的にはどうやってもeq
にならない:
(defun double-float-eq (x)
(declare (double-float x))
(eq (+ x 0d0) (+ x 0d0)))
(double-float-eq 1d0)
;; => NIL
;; ゼロ加算を消去するトランスフォーマーは(デフォルトでは無効だが)存在するので、
;; このケースに限ってTにすることは可能。以下は(eq x x)と同等になる。
(defun constant-double-float-eq (x)
(declare (double-float x)
(optimize (sb-c::float-accuracy 0)))
(eq (+ x 0d0) (+ x 0d0)))
(constant-double-float-eq 1d0)
;; => T
また、32ビット環境だといろいろ事情が異なると思われる。
まとめ
SBCLでは、fixnum
に限ればeq
とeql
の動作はたぶん同じで、それ以外のeq
の挙動は複雑、という感じ。