[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Issue: DYNAMIC-EXTENT (Version 3)



<<version number wrong, missing line-fold: NO>>
I don't have the time to "wordsmith" this issue, but I think it
is important enough to bring before X3J13; I think the idea
in the proposal is fine, but I just clipped some of David's words
into the old one.

I'd like to release this in DRAFT form, I'm less sure what
we can vote on. 

Opinions? Volunteers to edit this further?
!
Status:	        For Internal Discussion
Forum:	CLEANUP
Issue:          DYNAMIC-EXTENT
References:     Scope and Extent
Category:       ADDITION
Edit history:   27-Jun-88, Version 1 by Pitman (as issue STACK-LET)
		15-Nov-88, Version 2 by Pitman (issue renamed, major revision)
		11-Jan-89, Version 3 by Masinter (Moon's proposal)
Related-Issues: REST-ARGUMENT-EXTENT, WITH-DYNAMIC-EXTENT

Problem Description:

  Sometimes a programmer knows that a particular data structure
  will have only dynamic extent. In some implementations, it is
  possible to allocate such structures in a way that will make them
  easier to reclaim than by general purpose garbage collection
  (eg, on the stack or in some temporary area). Currently, however,
  there is no way to request the use of such an allocation mechanism.

Proposal (DYNAMIC-EXTENT:NEW-DECLARATION):

  Introduce a new declaration called DYNAMIC-EXTENT. The arguments to
  this declaration are names of variables. The declaration asserts that
  the value which is initially held by the indicated variable will have
  dynamic extent. [In the case of an iteration variable, the declaration
  asserts that on every iteration, the initial value of that variable
  for the iteration will have dynamic extent.]

  It is permissible for an implementation to simply ignore this declaration.
  In implementations which do not ignore it, the compiler (or interpreter)
  is free to make whatever optimizations are appropriate given this
  information; the most common optimization is to stack-allocate the
  initial value of the object. What data types (if any) can have dynamic
  extent will can vary from implementation to implementation.

  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 declare DYNAMIC-EXTENT for variables for
  which have no lexically apparent initial value. For example,

	(DEFUN F ()
	  (LET ((X (LIST 1 2 3)))
	    (DECLARE (DYNAMIC-EXTENT X))
	    ...))

  would permit those compilers which wish to do so to stack-allocate the
  list in X. However,

	(DEFUN G (X) (DECLARE (DYNAMIC-EXTENT X)) ...)
	(DEFUN F () (G (LIST 1 2 3)))

  could not typically permit a similar optimization in G because it would
  be a modularity violation for the compiler to assume facts about G from
  within F. Only an implementation which was willing to be responsible for
  recompiling F if G's definition changed incompatibly could stack-allocate
  the list argument to G in F.

  Other interesting cases are:

	(PROCLAIM '(INLINE G))
	(DEFUN G (X) (DECLARE (DYNAMIC-EXTENT X)) ...)
	(DEFUN F () (G (LIST 1 2 3)))

    and	(DEFUN F ()
	  (FLET ((G (X) (DECLARE (DYNAMIC-EXTENT X)) ...))
	    (G (LIST 1 2 3))))

  where some compilers might realize the optimization was possible and others
  might not.

  An interesting variant of this is the so-called `stack allocated rest list'
  which can be achieved (in implementations supporting the optimization) by:

	(DEFUN F (&REST X)
	  (DECLARE (DYNAMIC-EXTENT X))
	  ...)

  Note here that although the initial value of X is not explicit, the F
  function 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
  which support such.

   The meaning of a dynamic extent declaration is that execution of the
   forms in the scope of the declaration will not "save" any "proper part"
   of the initial value of the declared variable.  To "save" an object
   means to cause a reference to that object to be accessible outside the
   dynamic extent of the form at the beginning of whose body the
   declaration appears.  An object can be "saved" by storing it with a
   side-effecting operation and not replacing it with a different value
   before the end of the dynamic extent, by using it as a component of a
   freshly-allocated object that is itself "saved," by capturing it in a
   function closure that is itself "saved," by returning it as a value, or
   by transmitting it outside the dynamic extent with THROW.  A "proper
   part" of an object A is any object that is accessible at the beginning
   of the scope of the declaration -only- by applying a function to A or to
   a "proper part" of A.  This means that any objects freshly allocated
   during the construction of the initial value of the declared variable,
   and not "saved" during the construction of that value, are "proper
   parts" and can be allocated on a stack.

Examples:

In
            (LET ((X (LIST 'A1 'B1 'C1))
                  (Y (CONS 'A2 (CONS 'B2 (CONS 'C2 NIL)))))
              (DECLARE (DYNAMIC-EXTENT X Y))
              ...)
The "proper parts" of X are three conses, and the "proper parts" of Y
are three other conses.  None of the symbols A1, B1, C1, A2, B2, C2, or
NIL is a "proper part" of X or Y.  However, if a freshly allocated
uninterned symbol had been used, it would have been a "proper part."

- - - - - - - -
          (DOTIMES (I N) 
            (DECLARE (DYNAMIC-EXTENT I))

This is particularly instructive.  Since I is an integer by the
definition of DOTIMES, but EQ and EQL are not necessarily equivalent for
integers, what are the "proper parts" of I, which this declaration
requires the body of the DOTIMES not to "save"?  If the value of I is 3,
and the body does (SETQ FOO 3), is that an error?  The answer is no, but
the interesting thing is that it depends on the implementation-dependent
behavior of EQ on numbers.  In an implementation where EQ and EQL are
equivalent for 3, then 3 is not a "proper part" because (EQ I (+ 2 1))
is true, and therefore there is another way to access the object besides
going through I.  On the other hand, in an implementation where EQ and
EQL are not equivalent for 3, then the particular 3 that is the value of
I is a "proper part", but any other 3 is not.  Thus (SETQ FOO 3) is valid
but (SETQ FOO I) is erroneous.  Since (SETQ FOO I) is erroneous in some
implementations, it is erroneous in all portable programs, but some other
implementations may not be able to detect the error.

- - - - - - - -

  (LET ((X (LIST 1 2 3)))
    (DECLARE (DYNAMIC-EXTENT X))
    (PRINT X)
    NIL)
  PRINT does not "save" any part of its input.
  This prints (1 2 3)

- - - - - - - -

  (DO ((L (LIST-ALL-PACKAGES) (CDR L)))
      ((NULL L))
    (DECLARE (DYNAMIC-EXTENT L))
    (PRINT (CAR L)))
  prints all packages; none of the newly-allocated list structures are saved.
- - - - - - - -
  (DEFUN ADD (&REST X) (DECLARE (DYNAMIC-EXTENT X)) (APPLY #'+ X))
  (ADD 1 2 3) => 6

I.e., useful way to declare that &REST lists have dynamic extent
- - - - - - - -
  (DEFUN ZAP (X Y Z)
    (DO ((L (LIST X Y Z) (CDR L)))
	((NULL L))
      (DECLARE (DYNAMIC-EXTENT L))
      (PRIN1 (CAR L))))
  (ZAP 1 2 3)
  prints 123

- - - - - - - -
  (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 5 3) 3) => T

- - - - - - - -
The following are in error, since the value of X is used outside of its
extent:

       (LENGTH (LIST (LET ((X (LIST 1 2 3))) (DECLARE (DYNAMIC-EXTENT X)) X)))

  (PROGN (LET ((X (LIST 1 2 3))) (DECLARE (DYNAMIC-EXTENT X)) X)
         NIL)

- - - - - - - -

Rationale:

  This permits a programmer to offer advice to an implementation about
  what may be stack-allocated for efficiency.

  It may be difficult or impossible for a compiler to infer this
  same information statically.

  Since a number of implementations offer this capability and there
  is demand from users for access to the capability, this ``codifies
  existing practice.''

  Because this approach is purely lexical, it does not interact badly with
  other programs in the way that the macro WITH-DYNAMIC-EXTENT (see issue
  by same name) would.

Current Practice:

  Symbolics Genera and Symbolics Cloe offer stack allocation, though not
  in this strategy.

  [KMP thinks that] Lucid supports the proposal.

Cost to Implementors:

  No cost is forced since implementations are permitted to simply
  ignore the DYNAMIC-EXTENT declaration.

Cost to Users:

  None. This change is upward compatible.

  There may be some hidden costs to debugging using this declaration (or any
  feature which permits the user to access dynamic extent objects without
  the compiler proving that they are appropriate). If the user misdeclares
  something and returns a pointer into the stack (or stores it in the heap),
  an undefined situation may result and the integrity of the Lisp storage
  mechanism may be compromised. Debugging these situations may be tricky,
  but users who have asked for this feature have indicated a willingness
  to deal with such costs. Nevertheless, the perils should be clearly
  documented and casual users should not be encouraged to use this
  declaration.

Cost of Non-Adoption:

  Some portable code would be forced to run more slowly (due to
  GC overhead), or to use non-portable language features.

Benefits:

  The cost of non-adoption is avoided.

Aesthetics:

  This declaration allows a fairly low level optimization to work
  by asking the user to provide only very high level information.
  The alternatives (sharpsign conditionals, some of which may
  lead to more bit-picky abstractions) are far less aesthetic.

Discussion:

  A previous version of this proposal suggested primitives STACK-LET
  and STACK-LET*. Consensus was that the more general declaration facility
  would be more popular.

  Pitman supports the DYNAMIC-EXTENT:NEW-DECLARATION.

  Actually, a blurry issue is whether
   (LENGTH (LIST (LET ((X (LIST 1 2 3))) (DECLARE (DYNAMIC-EXTENT X)) X)))
   => 1
  is well-defined. I refer to these stack-allocated things as being invalid
  return values, but in fact we might want to say that they're ok to return
  but that you just can't do any serious operations on them (ie, you can't
  expect them to still be lists, etc.) Can anyone imagine a pointer into
  unallocated stack causing problems for their GC? If so, we could be more
  clear on this point.

The examples are tricky:

"I hope no one misreads the above as an argument that my proposal is too
complicated, since it does not derive at all from my proposal, but only
 from the way Common Lisp works."