From: Frode V. Fjeld on
> On 03/02/2010 09:26, Frode V. Fjeld wrote:
>> Where, precisely? I fail to see anything relevant on the dynamic-extent
>> reference page. I suspect that the CLHS fails to address the perhaps
>> somewhat esoteric problem we're dealing with here.

Pascal Costanza <pc(a)p-cos.net> writes:

> There is an example in the entry for dynamic-extent. Look for the
> paragraph starting with "Most compilers would probably not stack
> allocate the argument to g in f [...]"

That's this example:

(defun g (x) (declare (dynamic-extent x)) ...)
(defun f () (g (list 1 2 3)))

I don't think this is equivalent to your example. Here, the value of X
is declared not to escape from G, and so long as that is true there is
no problem. Your example was effectively this:

(defun i (&rest args) (declare (dynamic-extent args)) ...)
(defun h () (i (list 1 2 3)))

Here, even if ARGS (i.e. the cdr-chain of cons-cells) never exits I,
you'll still have a problem if using sbcl.

I don't think this CLHS example was intended to adress the problem at
hand. (Also, I believe the CLHS examples are not to be considered
"letters of the law", strictly speaking.)


It seems to me the issue narrows down to the precise semantics of
dynamic-extent for &rest-lists. Consider first:

(let ((x (list <foo>)))
(declare (dynamic-extent x))
...)

The singleton list can clearly be stack-allocated, but what about
whatever is allocated in <foo>? If <foo> can be somehow shown to be
otherwise inaccessible, it can also be stack-allocated, which is
reasonable. Now, here's a similar case, assuming inlining:

(defun f (&rest args)
(declare (dynamic-extent args))
...)

(defun g ()
(f <bar>))

It seems to me that Paul Khuong's position is that <bar> can be
stack-allocated, because the CLHS wording that allows <foo> to be
stack-allocated also applies to <bar>. If this is true, I would wager
that this was not intentional on the part of the CLHS editors (because
of the way it uncharacteristically breaks function modularity), and that
the CL community would do well to consider it one of the CLHS buglets.

--
Frode V. Fjeld
From: Tim Bradshaw on
On 2010-02-02 22:57:46 +0000, Pascal J. Bourguignon said:

> Tim Bradshaw <tfb(a)tfeb.org> writes:
>>
>> It needs to know that LIST (or, really
>> MY-NON-CL-DEFINED-FUNCTION-RETURNING-A-LIST) does not hang onto any of
>> the structure it returns. If it does then that part of the structure
>> is not otherwise inaccessible. Obviously it can know this for LIST,
>> but it can not know this in general.
>
> Yes, correct.

I spent some time (failing to sleep and) thinking about this last
night, and I think I am now somewhat confused.

If we consider something like this:

(defun dxfc (f &rest l)
(declare (dynamic-extent l))
(funcall f l))

Then I think it is clear that:

(dxfc #'car 1 2 3) => 1 ; and this is all fine

(dxfc #'identity 1 2 3) => error ; and this is not fine

But there are then all sorts of odd cases:

(apply #'dxfc #'identity (list 1 2 3)) => ?

I think this would be bad in two cases:
1. If the compiler is really aggressive, and works out that it knows
all about LIST, and arranges life so that a stack-allocated list is
created.
2. If the rest list does not share with what LIST returns (it is
allowed but not required to share).

Then for something like:

(apply #'dxfc #'identity (my-function-returning-a-list ...)) => ?

I think this would be bad only if the rest list does not share with
what the function returns, or (I suppose) if the compiler can do enough
analysis to work out that the function returns a result which is
otherwise inaccessible.

Does that seem right?

--tim

From: Alessio Stalla on
On Feb 3, 9:52 am, Pascal Costanza <p...(a)p-cos.net> wrote:
> On 02/02/2010 17:38, Paul Khuong wrote:
>
>
>
> > In article<82aavrskg8....(a)netfonds.no>,
> >   "Frode V. Fjeld"<fr...(a)netfonds.no>  wrote:
>
> >> Paul Khuong<p...(a)pvk.ca>  writes:
>
> >>> At the time the&rest list is bound, everything it contains is
> >>> "otherwise accessible".
>
> >> Agreed, my statement was quite mistaken. Thanks for the explanation.
>
> > [...]
>
> >> I agree wrt. your example. However, Pascal's original example was this:
>
> >> (defun definer (x)
> >>    (list x))
>
> >> (declaim (inline caller))
>
> >> (defun caller (&rest args)
> >>    (declare (dynamic-extent args)
> >>             (optimize (speed 3) (debug 0) (safety 0)
> >>                       (compilation-speed 0)))
> >>    (apply #'definer args))
>
> >> (defun test ()
> >>    (caller (list 0)))
>
> >> The question as I understand it is whether the last (list 0) can be
> >> stack-allocated. This form is outside the (lexical) scope of the
> >> dynamic-extent declaration. I suspect what happens is that inlining
> >> "caller" causes (list 0) to be treated as it's in that lexical scope? So
> >> if there was no inlining, there would be no error?
>
> > This is a side-effect of inlining. As the CLHS points out, the limit is
> > only what static analyses and the runtime allow you to guarantee.
>
> >      (defun g (x) (declare (dynamic-extent x)) ...)
> >      (defun f () (g (list 1 2 3)))
>
> >      Most compilers would probably not stack allocate the argument to g
> >      in f because it would be a modularity violation for the compiler to
> >      assume facts about g from within f. Only an implementation that was
> >      willing to be responsible for recompiling f if the definition of g
> >      changed incompatibly could legitimately stack allocate the list
> >      argument to g in f.
>
> > Here, because of, e.g., automatic recompilation, F can assume that G's
> > one argument has been declared dynamic-extent. In Costanza's orignal
> > example, it is because of inlining that SBCL can assume that the&rest
> > list in CALLER has been declared dynamic-extent. However, even without
> > inlining, a sufficiently flexible runtime would allow for the exact same
> > optimisation. The bug lies not in our compilers but in our assumptions.
>
> Ok, understood by now. Thanks a lot for your excellent explanations.
>
> But this effectively means that I can never use dynamic-extent
> declarations for &rest arguments, can I?
>
> (defun foo (... &rest args)
>    (declare (dynamic-extent args))
>    ...)
>
> Since it is always possible that client code calls this with freshly
> allocated data:
>
> (foo ... (list 1) (list 2))
>
> And since compilers are allowed to cross module (function) boundaries
> when optimizing for dynamic-extent, this is never safe.

Never say never ;)

> Or are there circumstances where I can use dynamic-extent for &rest args
> safely?

You can indeed use it safely, provided that you guarantee that no rest
argument (or part of its structure) escapes from your function.
There's really no difference from other arguments. I.e. the problem is
not &rest nor inlining per se, it's that your function returns a value
that shares structure with something which has been stack-allocated.

Alessio

From: Pascal Costanza on
On 03/02/2010 11:45, Alessio Stalla wrote:
> On Feb 3, 9:52 am, Pascal Costanza<p...(a)p-cos.net> wrote:
>> On 02/02/2010 17:38, Paul Khuong wrote:
>>
>>
>>
>>> In article<82aavrskg8....(a)netfonds.no>,
>>> "Frode V. Fjeld"<fr...(a)netfonds.no> wrote:
>>
>>>> Paul Khuong<p...(a)pvk.ca> writes:
>>
>>>>> At the time the&rest list is bound, everything it contains is
>>>>> "otherwise accessible".
>>
>>>> Agreed, my statement was quite mistaken. Thanks for the explanation.
>>
>>> [...]
>>
>>>> I agree wrt. your example. However, Pascal's original example was this:
>>
>>>> (defun definer (x)
>>>> (list x))
>>
>>>> (declaim (inline caller))
>>
>>>> (defun caller (&rest args)
>>>> (declare (dynamic-extent args)
>>>> (optimize (speed 3) (debug 0) (safety 0)
>>>> (compilation-speed 0)))
>>>> (apply #'definer args))
>>
>>>> (defun test ()
>>>> (caller (list 0)))
>>
>>>> The question as I understand it is whether the last (list 0) can be
>>>> stack-allocated. This form is outside the (lexical) scope of the
>>>> dynamic-extent declaration. I suspect what happens is that inlining
>>>> "caller" causes (list 0) to be treated as it's in that lexical scope? So
>>>> if there was no inlining, there would be no error?
>>
>>> This is a side-effect of inlining. As the CLHS points out, the limit is
>>> only what static analyses and the runtime allow you to guarantee.
>>
>>> (defun g (x) (declare (dynamic-extent x)) ...)
>>> (defun f () (g (list 1 2 3)))
>>
>>> Most compilers would probably not stack allocate the argument to g
>>> in f because it would be a modularity violation for the compiler to
>>> assume facts about g from within f. Only an implementation that was
>>> willing to be responsible for recompiling f if the definition of g
>>> changed incompatibly could legitimately stack allocate the list
>>> argument to g in f.
>>
>>> Here, because of, e.g., automatic recompilation, F can assume that G's
>>> one argument has been declared dynamic-extent. In Costanza's orignal
>>> example, it is because of inlining that SBCL can assume that the&rest
>>> list in CALLER has been declared dynamic-extent. However, even without
>>> inlining, a sufficiently flexible runtime would allow for the exact same
>>> optimisation. The bug lies not in our compilers but in our assumptions.
>>
>> Ok, understood by now. Thanks a lot for your excellent explanations.
>>
>> But this effectively means that I can never use dynamic-extent
>> declarations for&rest arguments, can I?
>>
>> (defun foo (...&rest args)
>> (declare (dynamic-extent args))
>> ...)
>>
>> Since it is always possible that client code calls this with freshly
>> allocated data:
>>
>> (foo ... (list 1) (list 2))
>>
>> And since compilers are allowed to cross module (function) boundaries
>> when optimizing for dynamic-extent, this is never safe.
>
> Never say never ;)
>
>> Or are there circumstances where I can use dynamic-extent for&rest args
>> safely?
>
> You can indeed use it safely, provided that you guarantee that no rest
> argument (or part of its structure) escapes from your function.
> There's really no difference from other arguments. I.e. the problem is
> not&rest nor inlining per se, it's that your function returns a value
> that shares structure with something which has been stack-allocated.

OK, sounds reasonable.

I have used dynamic-extent declarations on &rest arguments a lot for the
kind of forwarding functions I sketched in my OP. If dynamic-extent (or
a similar declaration) would be guaranteed to cover only the conses that
make up the &rest argument itself, this would be a useful idiom with
what I believe to be a potentially interesting performance gain. Since
in general I don't know what the functions that I forward to do with
their arguments, I can't use dynamic-extent in those cases. That's a
pity. But I now understand why.


Pascal

--
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
From: Waldek Hebisch on
Paul Khuong <pvk(a)pvk.ca> wrote:
> > "otherwise", i.e. not in any sense by going through the binding that was
> > declared dynamic-extent.
>
> No. At the time the &rest list is bound, everything it contains is
> "otherwise accessible".

What about the following variation:

(defmacro safe-caller(x) `(let ((xx ,x)) (caller ,x)))

(defun test () (safe-caller (list 0)))

in sbcl (at least 1.0.32) calling test still gives error. My
interetation of HyperSpec is that `(list 0)' is accesible via xx,
so compiler should not stack allocate it. Of course one
possible meaning of "otherwise inaccessible part" could be
"compiler optimized out all other visible references to it",
but that IMHO would make dynamict extent inherently unsafe.

--
Waldek Hebisch
hebisch(a)math.uni.wroc.pl
First  |  Prev  |  Next  |  Last
Pages: 1 2 3 4 5 6 7 8 9 10
Prev: online shopping
Next: python admin abuse complaint