From: Pascal Costanza on
Hi,

I recently submitted a report to the SBCL devel mailing list about what
I believed to be a bug in their compiler, only to learn that my
understanding of what dynamic-extent declarations do was just wrong.

Here is the example:

(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)))

If I run test in SBCL, I get the following output:

((SB-INT:STREAM-ENCODING-ERROR))

....or worse. The problem that causes this is that the dynamic-extent
declaration in 'caller doesn't reach only the list that makes up the
&rest args, but also the list created in 'test - the HyperSpec states
that dynamic-extent declarations cover everything directly or indirectly
reachable that is not 'otherwise accessible', and since the list created
in 'test is freshly consed, it is indeed not 'otherwise accessible' at
the time 'caller is invoked.

This screws up my mental model of the dynamic-extent declaration that I
had, and I currently have problems coming up with a better mental model
of when and how to use it. At the moment it seems to me that
dynamic-extent declarations can be too 'aggressive' to the extent that I
don't know what guarantees I can and cannot give to users of my libraries.

Does this mean that I cannot use dynamic-extent at all? Or only deep
down in the call chain of functions that are not exported to the
outside? When and how do other people use dynamic-extent? What's a good
rule of thumb here?

Help! :)


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: Frode V. Fjeld on
Pascal Costanza <pc(a)p-cos.net> writes:

> [...] The problem that causes this is that the dynamic-extent
> declaration in 'caller doesn't reach only the list that makes up the
> &rest args, but also the list created in 'test - the HyperSpec states
> that dynamic-extent declarations cover everything directly or
> indirectly reachable that is not 'otherwise accessible', and since the
> list created in 'test is freshly consed, it is indeed not 'otherwise
> accessible' at the time 'caller is invoked.

FWIW the sbcl behavior as you describe it I find unreasonable. I would
say that the fresh list is in fact "otherwise accessible" as the
returned value of the function call. The list is clearly "accessible" as
(presumably) it's this access that causes your sbcl to crash, and it's
"otherwise", i.e. not in any sense by going through the binding that was
declared dynamic-extent.

--
Frode V. Fjeld
From: Pascal J. Bourguignon on
Pascal Costanza <pc(a)p-cos.net> writes:

> Hi,
>
> I recently submitted a report to the SBCL devel mailing list about
> what I believed to be a bug in their compiler, only to learn that my
> understanding of what dynamic-extent declarations do was just wrong.
>
> Here is the example:
>
> (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)))
>
> If I run test in SBCL, I get the following output:
>
> ((SB-INT:STREAM-ENCODING-ERROR))
>
> ...or worse. The problem that causes this is that the dynamic-extent
> declaration in 'caller doesn't reach only the list that makes up the
> &rest args, but also the list created in 'test - the HyperSpec states
> that dynamic-extent declarations cover everything directly or
> indirectly reachable that is not 'otherwise accessible', and since the
> list created in 'test is freshly consed, it is indeed not 'otherwise
> accessible' at the time 'caller is invoked.
>
> This screws up my mental model of the dynamic-extent declaration that
> I had, and I currently have problems coming up with a better mental
> model of when and how to use it. At the moment it seems to me that
> dynamic-extent declarations can be too 'aggressive' to the extent that
> I don't know what guarantees I can and cannot give to users of my
> libraries.
>
> Does this mean that I cannot use dynamic-extent at all? Or only deep
> down in the call chain of functions that are not exported to the
> outside? When and how do other people use dynamic-extent? What's a
> good rule of thumb here?
>
> Help! :)

My understand of this declaration is that it means "stack-allocated".

The caveat, is while the declaration is done in the callee, it applies
to the call sites, where the arguments are allocated.

This declaration allows the allocation of these arguments onto the
stack (or other space automatically deallocated on function return),
bypassing the normal heap and garbage collector.


--
__Pascal Bourguignon__
From: Pascal Costanza on
On 02/02/2010 14:56, Pascal J. Bourguignon wrote:
> Pascal Costanza<pc(a)p-cos.net> writes:
>
>> Hi,
>>
>> I recently submitted a report to the SBCL devel mailing list about
>> what I believed to be a bug in their compiler, only to learn that my
>> understanding of what dynamic-extent declarations do was just wrong.
>>
>> Here is the example:
>>
>> (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)))
>>
>> If I run test in SBCL, I get the following output:
>>
>> ((SB-INT:STREAM-ENCODING-ERROR))
>>
>> ...or worse. The problem that causes this is that the dynamic-extent
>> declaration in 'caller doesn't reach only the list that makes up the
>> &rest args, but also the list created in 'test - the HyperSpec states
>> that dynamic-extent declarations cover everything directly or
>> indirectly reachable that is not 'otherwise accessible', and since the
>> list created in 'test is freshly consed, it is indeed not 'otherwise
>> accessible' at the time 'caller is invoked.
>>
>> This screws up my mental model of the dynamic-extent declaration that
>> I had, and I currently have problems coming up with a better mental
>> model of when and how to use it. At the moment it seems to me that
>> dynamic-extent declarations can be too 'aggressive' to the extent that
>> I don't know what guarantees I can and cannot give to users of my
>> libraries.
>>
>> Does this mean that I cannot use dynamic-extent at all? Or only deep
>> down in the call chain of functions that are not exported to the
>> outside? When and how do other people use dynamic-extent? What's a
>> good rule of thumb here?
>>
>> Help! :)
>
> My understand of this declaration is that it means "stack-allocated".
>
> The caveat, is while the declaration is done in the callee, it applies
> to the call sites, where the arguments are allocated.
>
> This declaration allows the allocation of these arguments onto the
> stack (or other space automatically deallocated on function return),
> bypassing the normal heap and garbage collector.

Sure, that is understood. But how are you supposed to use that safely?

Do I add documentation to my APIs stating that "don't allocate fresh
conses directly in argument lists when calling my functions, otherwise
you may get unpredictable results"? That doesn't sound reasonable.

There should be a 'shallow dynamic-extent' declaration that doesn't
reach out for objects allocated at the call site...


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: Pascal J. Bourguignon on
Pascal Costanza <pc(a)p-cos.net> writes:
> Sure, that is understood. But how are you supposed to use that safely?
>
> Do I add documentation to my APIs stating that "don't allocate fresh
> conses directly in argument lists when calling my functions, otherwise
> you may get unpredictable results"? That doesn't sound reasonable.
>
> There should be a 'shallow dynamic-extent' declaration that doesn't
> reach out for objects allocated at the call site...

Basically, you don't keep any reference to the arguments or any object
reachable from them, including returning any of them.

The data declared dynamic-extend, including all data that is reachable
from it, must be considered purely kind of 'input' parameters, and
should not leak out of the function (the dynamic scope actually).


(defvar *example* nil)


(defun f (arg)
(declare (dynamic-extend arg))
(setf *example* (car arg)) ; bad
(print (car arg)) ; ok
(if (zerop (random 2))
(return arg) ; bad
(return (reduce (function +) arg)))) ; ok

The first bad is bad even for numbers because they could be bignums,
allocated on the stack:

(f (list (the bignum 12345678901234567890) 2 3))


--
__Pascal Bourguignon__
 |  Next  |  Last
Pages: 1 2 3 4 5 6 7 8 9 10
Prev: online shopping
Next: python admin abuse complaint