Frode Vatvedt Fjeld <frodef@cs.uit.no> wrote:
+---------------
| Marcin 'Qrczak' Kowalczyk <qrczak@knm.org.pl> writes:
| > But there should be only one foo, as mkfoo is called only once.
|
| Right you are, I read the original post too quickly. I suspect it's a
| corner case in a compiler that is easy to miss.
+---------------
Actually, it would seem that in CMUCL (nothing just 19c) that the
FUNCTION special operator result in "executable code" (if you will)
that creates a closure at the moment the FUNCTION form is "executed".
And that in the OP's test code, the optimizer is not *quite* smart
enough to realize that the original #'FOO can be re-used. Here's the
OP's case, with MKFOO compiled [simplifies printouts]:
cmu> (defun mkfoo (x)
(labels ((foo ()
(values (function foo) x)))
(function foo)))
MKFOO
cmu> (compile 'mkfoo)
; Compiling LAMBDA (X):
; Compiling Top-Level Form:
MKFOO
NIL
NIL
cmu> (deflex foo (mkfoo "xyz")) ; Pardon my DEFLEX... ;-}
FOO
cmu> (funcall foo)
#<Closure Over Function (LABELS FOO
MKFOO)
{5890B689}>
"xyz"
cmu> (funcall foo)
#<Closure Over Function (LABELS FOO
MKFOO)
{5890C001}>
"xyz"
cmu>
And a (DISASSEMBLE FOO) reveals that the (FUNCTION FOO) in the VALUES
call *is* allocating a new closure object (size = 16 bytes) inline:
...
87: MOV EDX, 16
8C: ADD EDX, [#x2800057C] ; X86::*CURRENT-REGION-FREE-POINTER*
92: CMP EDX, [#x28000594] ; X86::*CURRENT-REGION-END-ADDR*
98: JBE L0
9A: CALL #xBE000010 ; #xBE000010: alloc_overflow_edx
9F: L0: XCHG EDX, [#x2800057C] ; X86::*CURRENT-REGION-FREE-POINTER*
A5: LEA EDX, [EDX+1]
A8: MOV DWORD PTR [EDX-1], 642
...fill in rest of struct & return...
The "+1" offset gives it a function pointer low-tag. The "642" (#x282)
is a header word for a "type_ClosureHeader" (#x82) with two additional
data words, a function (#'FOO itself) and a single closed-over value
[the string "xyz", as it happens in this case]:
cmu> (dump32 (1- (kernel:get-lisp-obj-address foo)) 12)
#x58964E60: #x00000282 #x5895C960 #x58962127
cmu> (dump32 (1- (kernel:get-lisp-obj-address (funcall foo))) 12)
#x589AFD20: #x00000282 #x5895C960 #x58962127
cmu> (dump32 (1- (kernel:get-lisp-obj-address (funcall foo))) 12)
#x589B5B00: #x00000282 #x5895C960 #x58962127
cmu> (kernel:make-lisp-obj #x58962127)
"xyz"
cmu>
As you can see, the *contents* of all three closure objects are entirely
the same, but they are still very different objects (in the EQ sense).
A slight tweak to MKFOO to avoid run-time creation of a closure makes
the "problem"(?) go away:
cmu> (defun mkfoo (x)
(let ((foo nil))
(labels ((foo ()
(values foo x)))
(setf foo (function foo)))))
cmu> (compile *)
; Compiling LAMBDA (X):
; Compiling Top-Level Form:
MKFOO
NIL
NIL
cmu> (deflex foo (mkfoo "abc"))
FOO
cmu> (funcall foo)
#<Closure Over Function (LABELS FOO
MKFOO)
{589D2F81}>
"abc"
cmu> (funcall foo)
#<Closure Over Function (LABELS FOO
MKFOO)
{589D2F81}>
"abc"
cmu> (dump32 (1- (kernel:get-lisp-obj-address foo)) 12)
#x589D2F80: #x00000382 #x589CAB88 #x589D2F7F
cmu> (dump32 (1- (kernel:get-lisp-obj-address (funcall foo))) 12)
#x589D2F80: #x00000382 #x589CAB88 #x589D2F7F
cmu> (dump32 (1- (kernel:get-lisp-obj-address (funcall foo))) 12)
#x589D2F80: #x00000382 #x589CAB88 #x589D2F7F
cmu>
To reprise the OP's test [well, with EQ instead of EQL]:
cmu> (let ((foo (mkfoo "defghi")))
(eq foo (funcall foo)))
T
cmu>
-Rob
-----
Rob Warnock <rpw3@rpw3.org>
627 26th Avenue <URL:http://rpw3.org/>
San Mateo, CA 94403 (650)572-2607