[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Issue DEFCONSTANT-VALUE (V2)
- To: CL-Compiler@SAIL.Stanford.edu
- Subject: Issue DEFCONSTANT-VALUE (V2)
- From: David N Gray <Gray@DSG.csc.ti.com>
- Date: Thu, 6 Oct 88 16:24:53 CDT
- Sender: GRAY@Kelvin.csc.ti.com
I have updated the "proposal" and "current practice" sections of this in
response to comments received. A companion proposal to address the
issue of constants computed at load-time will follow in a separate
message.
Issue: DEFCONSTANT-VALUE
References: Proposal COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY
section (2)
Category: CHANGE to previous proposal; CLARIFICATION to CLtL
Edit History: 29 Sept 1988, V1 by David Gray
6 Oct 1988, V2 by David Gray - Update proposal to remove
prohibition against re-evaluating at load time, and
specify null environment. Add Lucid to current
practice.
Status: For internal discussion
Problem Description:
CLtL does not specify when the value form of a DEFCONSTANT is evaluated;
this is clarified in COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY
but in an unacceptable way.
The DEFCONSTANT macro does two things beyond what DEFPARAMETER does:
1. It makes it possible for the compiler to replaces references with
the value at compile time instead of at run time.
2. It causes the compiler to complain if the user attempts to SETQ
or re-bind the constant name.
These two things give rise to two different views of what the purpose
of DEFCONSTANT is:
1. The purpose of DEFCONSTANT is to facilitate compile-time
substitution of the values of named constants. A consequence of
this is that alteration by SETQ or binding should not be
permitted.
or
2. The purpose of DEFCONSTANT is to declare that the value must not
be changed at run time. A side-effect of this is that, under
certain circumstances, the compiler may be able to substitute the
value for the reference at compile time.
The specification of DEFCONSTANT in proposal
COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY seems to assume the
second purpose above, which has the effect of severely limiting its
usefulness for the first purpose. I contend that this is a mistaken
viewpoint and that the definition of DEFCONSTANT should be guided by
the first purpose above.
The concept of named constants was introduced into programming
languages largely to avoid having "magic numbers" scattered throughout
the code. By giving the value a meaningful name and defining it in
just one place, the code is easier to understand and it is much easier
to change the value if necessary. In order to encourage this
programming style of using named constants, it has always been assumed
that there should be no loss of efficiency from "doing it the right
way". Thus, the compiler should substitute the value for each
reference and the resulting object code will be just as efficient as
if the number had been used directly.
For example, Pascal has constant declarations that look like this:
const MaxIndex = 100;
MaxCount = 101;
Since there are often cases where the value of one constant depends on
another, many Pascal implementations extend this to permit the value
to be an expression that depends on previous constants and is
evaluated at compile time:
const MaxIndex = 100;
MaxCount = MaxIndex+1;
This extension has been carried over into Modula and Ada, and even
FORTRAN 77 has this functionality in its PARAMETER statement. (On the
other hand I don't know of any language that has functionality that
says to compute a name's value at load time but don't permit it to be
changed at run time.) Now the obvious equivalent in Common Lisp would
be:
(defconstant max-index 100)
(defconstant max-count (1+ max-index))
But, according to the specification of DEFCONSTANT in proposal
COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY, in order to get the
same effect (i.e. ensuring compile-time substitution of the value),
the user would have to write:
(eval-when (eval compile load)
(defconstant max-index 100))
(defconstant max-count #.(1+ max-index))
The #. is needed to cause the value to be evaluated at compile time
and the EVAL-WHEN is needed to ensure that the first constant will be
defined for use by #. . However, if the second constant were going
to be referenced by another constant definition later, then it would
need to be:
(eval-when (eval compile load)
(defconstant max-index 100))
(eval-when (eval compile load)
(defconstant max-count #.(1+ max-index)))
Note that separate EVAL-WHEN forms are required in order for the #. to
work right. I contend that this is getting much too complicated for
something that started out as a simple and very typical example.
It is true that a smart compiler would be able to optimize the simpler
example (without EVAL-WHEN and #.) in certain limited cases, but if
the user can't rely on this, then the effect would be to either
discourage the use of named constants or to encourage frequent use of
EVAL-WHEN and #. .
Proposal DEFCONSTANT-VALUE:COMPILE-TIME:
Replace the paragraph describing DEFCONSTANT in proposal
COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY with the following.
DEFCONSTANT: The compiler must recognize the symbol as being constant
(for example, to suppress warnings about references to the symbolic
constant as an unbound variable or to enable warnings about binding or
SETQ'ing the constant in the code being compiled). The value form may
be evaluated at compile-time in the null lexical environment in order to
be be available for the compiler to substitute in when compiling
references to the constant name (as described on pages 68-69 of CLtL)
and when evaluating the value forms of subsequent DEFCONSTANTs. (The
value may also be useful to guide optimization even in cases where value
substitution is not permitted.) It is implementation dependent whether
or not the symbol value is actually set at compile time or whether
COMPILE-FILE temporarily remembers the value in some other way. It is
an error for the value expression to have a different value at load time
than it had at compile-time, so it is implementation-dependent whether
the expression is re-evaluated at load time or loads the compile-time
value.
Rationale:
This makes the semantics of DEFCONSTANT consistent with constant
declarations in other languages; thus this is a tried and proven
approach and will reduce confusion for programmers moving from one
language to another.
Run-time efficiency of compiled code seems a more important
consideration than the ability to protect a load-time value from
re-assignment at run-time.
Current Practice:
The Explorer has done it this way since release 3.0 and we have not
received any complaints saying that this was a mistake. Before then,
the heuristic used to decide whether it was safe to compute and
propagate the value at compile time led to inconsistent and confusing
behavior. In order to minimize incompatibility, if there is an error
during evaluation of the value form, the error is reported as a
compiler warning and the compiler causes the evaluation to be tried
again at load time, so missing information at compile time is not a
fatal error.
Lucid also conforms to this proposal.
Cost to implementors:
The simple approach of just having DEFCONSTANT expand to (EVAL-WHEN
(EVAL COMPILE LOAD) ...) would be trivial to implement. A little more
work in the compiler would be needed if the value were to be remembered
temporarily during COMPILE-FILE, but this would be less effort than it
would take to implement the optimizations permitted by the previous
proposal.
Cost to users:
Some changes to user's code are likely to be required, such as wrapping
an EVAL-WHEN around a DEFUN that is used later in the same file to
compute the value of a DEFCONSTANT. For the most part these changes
will be obvious enough from the errors reported from attempting to
evaluate the value form. In some cases, the user may decide that using
DEFPARAMETER is more appropriate. There might be more subtle
differences in behavior in cases where the compile-time and load-time
values of the constant differ, but they are likely to be a problem
regardless of which direction the issue is clarified.
Benefits:
The use of DEFCONSTANT will be simplified since efficient code can be
generated without requiring the user to explicitly specify what needs
to be done when.
Costs of Non-Adoption:
Letting COMPILE-FILE-HANDLING-OF-TOP-LEVEL-FORMS:CLARIFY stand will mean
that it will be difficult for users to understand how to use DEFCONSTANT
to get it to do what they want.
Aesthetics:
Are improved since the need to use EVAL-WHEN and #. is minimized.
Discussion: