Path: utzoo!utgpu!news-server.csri.toronto.edu!rpi!crdgw1!uunet!mcsun!ukc!edcastle!aiai!jeff From: jeff@aiai.ed.ac.uk (Jeff Dalton) Newsgroups: comp.lang.lisp Subject: Re: Eternal fixnum question (was Re: GET / GETF warning.) Message-ID: <4613@skye.ed.ac.uk> Date: 6 May 91 19:37:52 GMT References: <641@zogwarg.etl.army.mil> <4580@skye.ed.ac.uk> <1991May1.015534.27054@cs.cmu.edu> Reply-To: jeff@aiai.UUCP (Jeff Dalton) Organization: AIAI, University of Edinburgh, Scotland Lines: 77 In article <1991May1.015534.27054@cs.cmu.edu> ram+@cs.cmu.edu (Rob MacLachlan) writes: >In article <4580@skye.ed.ac.uk> jeff@aiai.UUCP (Jeff Dalton) writes: >>I suppose this isn't the _best_ place to raise this question, >>but when EQ, fixnums, and FAQ appear in the same place it's >>almost irresistible. >> >>As you all will recall, Common Lisp allows EQ to return false >>for fixnums that are EQL. >> >>[2] (let ((x 5)) (eq x x)) ; CLtL II p 104 >> >>What's hard to see is why in any reasonable implementation [1] or [2] >>would actually turn out to be false. >This is more convincing with floats, since fixnums normally have immediate >representations in modern lisps, but here is the scenario: > -- X is known to be a fixnum, so the compiler allocates in a register > without any tag bits. This is an unboxed (or non-descriptor) > representation. > -- The compiler notices some references to X in contexts that require a > tagged Lisp object. For each such reference, it heap allocates a copy > of X. > -- This results in EQ being passed two difference copies of X. This is a good explanation (just what I was looking for), and it suggests a number of examples. However, it's not clear that expressions such as [2] are among them. >Now, you might argue that this is a rather silly thing for the compiler to >do, especially when both arguments are known to be fixnums. But consider >something like: > (let ((x 1)) (eq x (if yow x (gloob)))) It's still a silly thing to do, isn't it? If the compiler allocates an unboxed fixnum and then needs to create heap copy for every reference, then the compiler's made the code slower rather than faster. What we really need is an example that mixes fixnum (or float) arithmetic with other operations. The problem with examples like [2] is (I still think) that they're there to make it clear that the rule is fairly absolute but are somewhat confusing in themselves. >The only way to ensure that pointer identity is preserved for numbers would >be to always retain the original object pointer, as well as any untagged >value, and then to use the original object pointer in any tagged context. >But this causes nasty problems with set variables: > (let ((x z) > (y z)) > (declare (fixnum x y)) > (when (gloob) (setq x (+ x (the fixnum grue)))) > (eq x y)) > >Now what to you do? Keep a shadow tagged X, but invalidate it whenever it >is set, and then cons X at the EQ when the shadow X is invalidated? Cons >the result of + just so that you can store it into the shadow X? When I was thinking about mixed examples before, it seemed to me that a compiler might follow the rule "if a pointer is ever needed, allocate on the heap". Purely numeric subexpressions that did not contain assignments to the fixnum variables could still be optimized by dereferencing once at the start. This would be simpler than maintaining the shadow X, or at least more like the optimizations compilers normally perform. >If this were an important problem to solve, I'm sure a solution could be >worked out that wouldn't be too terribly inefficient most of the time, but >why bother? I'm inclined to agree, especially since EQ doesn't perform a value comparison on (boxed) numbers in any case. -- jeff