wanted: defglobal (or variant thereof) that doesn't eval at compile-time

Bug #1253688 reported by Douglas Katzman on 2013-11-21
6
This bug affects 1 person
Affects Status Importance Assigned to Milestone
SBCL
Undecided
Unassigned

Bug Description

DEFGLOBAL evaluates the initialization form at compile-time.
It would seem conventional (imo) to delay the initialization, but I understand why it's done this way - having proclaimed the symbol both always-bound and global, it must hold a value that is neither the unbound-marker nor inconsistent with its declaimed type.
[And recognizing the orthogonal aspects of ALWAYS-BOUND from :GLOBAL :KIND, this problem is really only about the former, because it is not an error to proclaim a symbol as global that currently has no value - we could do that today if we wanted, which would not achieve the ultimate effect of never having to check for 'unbound-marker' in our frequently-accessed globals]

With compile-time evaluation though, we:
- produce uncollectable compile-time garbage if allocating hairy datastructures
- rely at compile-time on all of the code needed to construct the hairy datastructures
- can't simply just change defvar to defglobal to tweak performance without recognizing the prior two points

A workaround is to do something like (defglobal *baz* nil) (setq *baz* (construct-it)) which is easily macroized but nonetheless a little silly, and also losing in the case where you really don't want *baz* to hold NIL. [You could write (OR null <type>) in the type proclamation, but making the derived type weaker is worse for performance if you frequently have (LENGTH *myvar*) which was supposed to be SIMPLE-VECTOR but you were forced to write (OR NULL SIMPLE-VECTOR)]

Here are three possible resolutions in increasing order of difficulty:

1. Don't do the initialization, just allow proclaiming something as :ALWAYS-BOUND even when it isn't currently bound, and let stuff break at compile-time if there is a compile-time read of the symbol - unless of course the defglobal was itself inside a toplevel (eval-when (:compile-toplevel)). But the load-time proclaim _should_ check for boundness, so we need a back-door into PROCLAIM to do this approach. The compile-time breakage can be real, though subtle and possibly confusing in cases like:
    (defglobal **foo** 3) ; people expect this to give **foo** a value "now"
    (defmacro some-macro (...) (use-**foo**-while-in-my-expander))
Note that it achieves nothing to have defglobal put its (proclaim `(always-bound )) outside the eval-when :compile because then the compiler doesn't know anything, so the "duh" solution is really a non-solution.

2. Have the :ALWAYS-BOUND piece of info be 3-valued: {;KNOWN,T,NIL}. The compile-time effect of defglobal should be to make the variable :GLOBAL but only :KNOWN as :ALWAYS-BOUND, not actually always bound. The load-time effect of executing defglobal should be to change :always-bound to T.
The added state allows for no error at compile-time, but it's still an error if you try to read the value when it is unset. It touches more code than I'd like, but it's entirely feasible. While technically not 100% compatible, it lets you signal errors because the compile-time SOME-MACRO in my example did not "wire in" the assumption about **foo** never having 'unbound-marker' in it, the runtime SOME-MACRO did. And macros aren't really a genuine use-case, but the point is to catch the error.
Also it would have to be decided whether CLTL2 introspection should fold the new compile-time-only state back into NIL - as I think it should - versus tell the truth which is that there is an extra possibility.
We could of course easily make a variation on DEFGLOBAL that has the incompatible behavior of not evaling at compile-time, while leaving DEFGLOBAL itself alone.

3. Same as #2 but don't add a crutch to the runtime to support the compiler. Make a distinct compile-time-only global environment which exists alongside the runtime information and which indicates what *will* be. This avoids corrupting the pristine environment and avoids teaching it new tricks- this idea generalizes "known forthcoming defclass". It's nowhere near as easy to implement as the middle-of-the-road solution. Either a parallel global environment, or an ad-hoc approach that only understands "variables" would work here. But what is the scope of the pending information? Larger than a file certainly, so a compilation unit perhaps; but at present we think of the compilation unit only as deferring warnings and scoping policy changes, not introducing even more behavior alteration. But doing this allows the compiler to accurately reflect anything that will be in global environment without actually doing it, and is not unheard of.

#3 seems overkill. #2 is not so bad, with the additional proviso that compile-time should never change the state of :always-bound from T to :known if it was already *really* always-bound due to a prior genuine effect of a load.

Changed in sbcl:
status: New → Fix Released
To post a comment you must log in.
This report contains Public information  Edit
Everyone can see this information.

Other bug subscribers