Hm, ok. That's an interesting view on this. You're basically suggesting to introduce what is called in some languages a "value" type (records/structs that can only be used by value, not by reference). That's more or less what structs in C/C++ are when passed to and returned from functions as values.
I guess that should be possible in principle. But I'm wondering if this would go too far in the direction of requiring support from the language. What I had in mind was more some form of optimization declaration, that might as well be safely ignored by the compiler (or disabled in debug mode)…
Pascal
On 7 May 2012, at 14:28, Peter Seibel wrote:
Yes, I'm aware that EQL is defined for all objects. That's what I'm getting at. It's like EQ except in the case of numbers and chars because those are immutable objects that can be copied willy-nilly under the covers. EQL is an abstraction that hides those under the covers shenanigans making equivalently valued numbers and chars "the same" object (in the terminology of the spec). It seems to me that in your example since you only made one object it should be "the same" from the point of view of EQL even if the compiler has the latitude to make copies that render *x* and the return value of (foo) not EQ. Or to put it another way, it seems to me that what your proposing is a mechanism for users to define new types that have some of the efficiency advantages that currently only numbers and chars are allowed so I think they should be fit into the language in a similar way.
-Peter
On Sun, May 6, 2012 at 11:25 PM, Pascal Costanza pc@p-cos.net wrote: Hi,
I have something like the following in mind:
(defvar *x*)
(defun foo () (let ((x (make-bar …))) (declare (dynamic-extent x)) (setq *x* x) (return-with-dynamic-extent x)))
(defun test () (eql *x* (foo)))
If the result of test is not guaranteed to be t, implementations should have more freedom to move stack-allocated values up and down the stack, which could potentially simplify things internally. For users, I don't think this would be a great restriction, because it seems to me it's unlikely that would want to rely on eq/eql in the first place here…
[eql is not only defined on numbers and chars, but also other objects, and is the same as eq in most cases…]
Pascal
On 7 May 2012, at 01:41, Peter Seibel wrote:
This is verging on worrying about the color of the bikeshed but why should EQL not necessarily work with these objects. I can see EQ not working, by analogy with numbers and characters but EQL should perhaps do a value comparison. (Hmmm, are you assuming that these objects are immutable? Sort of seems like they'd better be.)
-Peter
On Sun, May 6, 2012 at 12:27 PM, Pascal Costanza pc@p-cos.net wrote: Hi,
Here is a description of what I think is a feature missing in Common Lisp, both the standard and current implementations, namely a more complete way to ensure stack allocation of objects.
Based on some recent experience with translating some C++ benchmarks to Common Lisp - more specifically the fluid animate benchmark from the PARSEC benchmark suite - being able to allocate objects on the stack can be a major performance win. Unfortunately, this is harder than necessary to achieve in Common Lisp.
Here is a simplified example. Assume you have the following class definition in C++:
struct vec { int x; int y; int z; vec(int x, int y, int z): x(x), y(y), z(z) {} };
Then you can define the following operations on such a class:
vec vplus(vec v1, vec v2) { vec v(v1.x+v2.x, v1.y+v2.y, v1.z+v2.z); return vec; }
In such a function definition, the local variable v is stack allocated, plus the return value is returned by value, not by reference. This holds for any intermediate values as well, so if you have other operations like vminus, vmul and vdiv, etc., defined accordingly, you can write expressions like this:
vplus(v1, vmul(v2, v3));
…and rest assured that all intermediate values are allocated on the stack as well. (C++ loses some performance in some cases because it can happen that it relies too much on copying of data structures, but this has been largely resolved in C++11, where move operations are supported really well.)
In Common Lisp, you can declare local variables with dynamic extent:
(let ((v (make-vec :x 1 :y 2 :z 3))) (declare (dynamic-extent v)) …)
This has the effect that (in Common Lisp that support this) the struct that v refers to is stack allocated. Unfortunately, Common Lisp implementations can only do this for data structure constructors that the implementation knows about - there is no way for users to add additional operations to the set of known dynamic extent 'allocators.'
For example, if you say the following:
(defun vplus (v1 v2) (let ((v (make-vec :x (+ (vec-x v1) (vec-x v2)) :y (+ (vec-y v1) (vec-y v2)) :z (+ (vec-z v1) (vec-z v2))))) (declare (dynamic-extent v)) v))
…you can be very sure that the resulting program will fail, because the reference to the stack allocated data structure is returned, but the data will be gone afterwards. This had the consequence that in our attempts at translating the fluid animate benchmark, we had to introduce all intermediate results explicitly in separate variables, and be very explicit about all the necessary computational steps. This was very tedious and the result is not nice to look at.
What would be nice is, if we could instead have some form of saying that we want to return a data structure on the stack:
(defun vplus (v1 v2) (let ((v (make-vec :x (+ (vec-x v1) (vec-x v2)) :y (+ (vec-y v1) (vec-y v2)) :z (+ (vec-z v1) (vec-z v2))))) (declare (dynamic-extent v)) (return-with-dynamic-extent v))
The idea is that now, the data that v refers to is returned by value on the stack. If the call site receives the value with a matching dynamic-extent declaration, then v can just stay on the stack. Same (hopefully) for intermediate results in more complex expressions.
Just like dynamic-extent declarations, an implementation should be allowed to ignore return-with-dynamic-extent and replace it with a mere return form.
To be able to support move operations on the stack, it may be interesting to specify that eq and eql are not reliable anymore for data that is return by return-with-dynamic-extent.
I presented the idea at ELS'12 in Zadar, and Christophe Rhodes commented that it may also be necessary to have some form of global declaim that states that a function will always return something with return-with-dynamic-extent, so it's easier for the compiler to know what to do with a return value.
I don't claim that I have completely thought this through, but I think this should be doable and shouldn't have too many negative consequences for the rest of a system that doesn't use dynamic extents.
I won't be able to implement this, but I just wanted to make sure that the idea is on the table, so someone who is interested may pick it up…
Pascal
-- Pascal Costanza The views expressed in this email are my own, and not those of my employer.
pro mailing list pro@common-lisp.net http://lists.common-lisp.net/cgi-bin/mailman/listinfo/pro
-- Peter Seibel http://www.gigamonkeys.com/
-- Pascal Costanza
-- Peter Seibel http://www.gigamonkeys.com/
-- Pascal Costanza