[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Issue: EXIT-EXTENT (Version 1)
- To: CL-Cleanup@SAIL.STANFORD.EDU
- Subject: Issue: EXIT-EXTENT (Version 1)
- From: David A. Moon <Moon@STONY-BROOK.SCRC.Symbolics.COM>
- Date: Wed, 7 Sep 88 19:32 EDT
Here's the issue writeup that I promised months ago, but never had time
to do. I hope it's not too late to be of any use to anyone.
References: CATCH, THROW,
BLOCK, RETURN, RETURN-FROM,
TAGBODY, GO, UNWIND-PROTECT,
Dynamic extent (CLtL p.37),
Nested dynamic extents (CLtL p.38),
Blocks can only be exited once (CLtL p.120),
Catch is disestablished just before the values
are returned (CLtL p.139).
Cleanup issue UNWIND-PROTECT-NON-LOCAL-EXIT is superseded
by this one.
Edit history: Version 1, 5-Sep-88, by Moon, for discussion
CLtL does not specify precisely when the dynamic extent (aka lifetime)
of a nonlocal exit such as a CATCH, BLOCK, or TAGBODY ends.
There are three cases of interest:
(1) Normal exit from a CATCH, BLOCK, TAGBODY, or equivalent such as PROG.
(2) Nonlocal exit from the target of a THROW or RETURN.
(3) Abandonment of an exit passed over by THROW, RETURN, or GO.
The terms "normal exit", "target", and "passed over" will be used with
these meanings for the remainder of the discussion.
CLtL is unambiguous about case 1. In cases 2 and 3, the extent could
end anywhere from the time the THROW, RETURN, or GO commences, until the
time the transfer of control is completed. In case 2, it is clear that
the extent of the target ends before the transfer of control completes,
since a block cannot be exited twice, but it is not made clear whether
the extent ends before or after execution of UNWIND-PROTECT cleanup
forms. CLtL says nothing about case 3, although a note on p.38 implies
that the extent of a passed-over exit should end no later than the end
of the extent of the target exit.
The dynamic extent of an exit, whether target or passed-over, ends as
soon as the THROW, RETURN, or GO commences. In the language of the
implementation note on p.142, the extent ends at the beginning of the
second pass. It is an error for an UNWIND-PROTECT cleanup form executed
during a nonlocal transfer of control to attempt to use an exit whose
dynamic extent ended when the nonlocal transfer of control commenced.
This proposal is called "minimal" because it gives exits the shortest
extent consistent with CLtL.
Each of the following programs is an error:
(funcall (block nil #'(lambda () (return)))) ;case 1
(block nil ;case 2
(block a ;case 3
(unwind-protect (return-from a)
(let ((a nil)) ;case 1
(tagbody t (setq a #'(lambda () (go t))))
(funcall (block nil ;case 3
(tagbody a (return #'(lambda () (go a))))))
(catch nil ;case 2
(unwind-protect (throw nil t)
(throw nil t)))
(catch 'a ;case 3
(unwind-protect (throw 'a t)
(throw 'b t))))
The above program is an error because the catch of b is passed over by
the first throw, hence portable programs must assume its dynamic extent
is terminated. The catch is not yet disestablished and therefore it
is the target of the second throw.
The following program is not an error. It returns 10. The inner
catch of a is passed over, but this is not case 3 because that catch
is disestablished before the throw to a is executed.
(unwind-protect (1+ (catch 'a (throw 'b 1)))
(throw 'a 10))))
Giving exits the shortest extent consistent with CLtL maximizes freedom
for implementations and takes away no useful capability from users.
Both implementations of Symbolics Genera (3600 and Ivory) end the extent
of a target exit at the moment the values are returned, and end the
extent of a passed-over exit at the moment the THROW, RETURN, or GO
commences. This choice of extent maximizes efficiency within the
particular stack structure used by these implementations, by avoiding
the need to retain the control information needed to use a passed over
exit through the transfer of control. Genera signals an error if an
attempt is made to use an exit that has been passed over.
I have not surveyed any other implementations.
Cost to Implementors:
There is no cost to implementors, as no currently valid implementation
will be made invalid by this proposal.
Cost to Users:
There is no cost to users, as no existing portable program is made invalid
by this proposal. This proposal will clarify why some existing nonportable
programs are nonportable, which could be viewed as either a cost or a benefit
Cost of non-adoption:
The semantics of exits will remain ambiguous.
Common Lisp will be more precisely defined, and the precise definition will
be consistent with current practice in a way that has no cost for implementors
nor for users.
Precisely specifying the meaning of dynamic extent improves the language.
Leaving implementations free to implement a longer extent if they choose
can be regarded as unesthetic, but consistent with Common Lisp philosophy.
Having a CATCH that is in scope even though its extent has ended may
seem unesthetic, but it is consistent with how BLOCK behaves.
The goal of this proposal is to clarify the ambiguity in CLtL while
minimizing changes to the current situation. An alternative proposal
would define the extent of an exit to end at the last moment possible
within some particular reference implementation. That alternative would
have a cost to implementors whose implementation is not identical to the
reference implementation. Another alternative proposal would duck the
issue by outlawing all nonlocal exits from UNWIND-PROTECT cleanup forms.
That alternative would have a substantial cost to some users.
Scheme is cleaner: it ducks this issue by specifying that the extent
of an exit never ends.
CLtL never says in what dynamic environment cleanup forms of
UNWIND-PROTECT are executed. The implementation note on p.142 may have
been intended to cover this, but since it doesn't define the term
"frame" that it uses, it doesn't actually say anything. This proposal
could be expanded to cover that issue by discussing the extent of
dynamic-extent entities other than exits, or that issue could be the
subject of a separate proposal.