dynamic-extent (original) (raw)
Since stack allocation of the initial value entails knowing at the_object_'s creation time that the object can be stack-allocated, it is not generally useful to make a dynamic-extent declaration for _variables_which have no lexically apparent initial value. For example, it is probably useful to write:
(defun f () (let ((x (list 1 2 3))) (declare (dynamic-extent x)) ...))
This would permit those compilers that wish to do so to _stack allocate_the list held by the local variable x. It is permissible, but in practice probably not as useful, to write:
(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 gchanged incompatibly could legitimately stack allocate the _list_argument to g in f.
Here is another example:
(declaim (inline g)) (defun g (x) (declare (dynamic-extent x)) ...) (defun f () (g (list 1 2 3)))
(defun f () (flet ((g (x) (declare (dynamic-extent x)) ...)) (g (list 1 2 3))))
In the previous example, some compilers might determine that optimization was possible and others might not.
A variant of this is the so-called "stack allocated rest list" that can be achieved (in implementations supporting the optimization) by:
(defun f (&rest x) (declare (dynamic-extent x)) ...)
Note that although the initial value of x is not explicit, the ffunction is responsible for assembling the list x from the passed arguments, so the f function can be optimized by the compiler to construct a stack-allocated list instead of a heap-allocated list in implementations that support such.
In the following example,
(let ((x (list 'a1 'b1 'c1)) (y (cons 'a2 (cons 'b2 (cons 'c2 nil))))) (declare (dynamic-extent x y)) ...)
The otherwise inaccessible parts of x are three conses, and the otherwise inaccessible partsof y are three other conses. None of the symbols a1, b1, c1, a2,b2, c2, or nil is anotherwise inaccessible part of x or y because each is interned and hence accessible by the package(or packages) in which it is interned. However, if a freshly allocated uninterned symbol had been used, it would have been an otherwise inaccessible part of the list which contained it.
;; In this example, the implementation is permitted to stack allocate
;; the list that is bound to X.
(let ((x (list 1 2 3)))
(declare (dynamic-extent x))
(print x)
:done)
(1 2 3)
:DONE
;; In this example, the list to be bound to L can be stack-allocated.
(defun zap (x y z)
(do ((l (list x y z) (cdr l)))
((null l))
(declare (dynamic-extent l))
(prin1 (car l)))) ZAP
(zap 1 2 3)
123
NIL
;; Some implementations might open-code LIST-ALL-PACKAGES in a way
;; that permits using stack allocation of the list to be bound to L.
(do ((l (list-all-packages) (cdr l)))
((null l))
(declare (dynamic-extent l))
(let ((name (package-name (car l))))
(when (string-search "COMMON-LISP" name) (print name))))
"COMMON-LISP"
"COMMON-LISP-USER"
NIL
;; Some implementations might have the ability to stack allocate
;; rest lists. A declaration such as the following should be a cue
;; to such implementations that stack-allocation of the rest list
;; would be desirable.
(defun add (&rest x)
(declare (dynamic-extent x))
(apply #'+ x)) ADD
(add 1 2 3)
6
(defun zap (n m)
;; Computes (RANDOM (+ M 1)) at relative speed of roughly O(N).
;; It may be slow, but with a good compiler at least it
;; doesn't waste much heap storage. :-}
(let ((a (make-array n)))
(declare (dynamic-extent a))
(dotimes (i n)
(declare (dynamic-extent i))
(setf (aref a i) (random (+ i 1))))
(aref a m))) ZAP
(< (zap 5 3) 3)
true
The following are in error, since the value of x is used outside of its_extent_:
(length (list (let ((x (list 1 2 3))) ; Invalid (declare (dynamic-extent x)) x)))
(progn (let ((x (list 1 2 3))) ; Invalid (declare (dynamic-extent x)) x) nil)