You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We store global function bindings directly in the naming symbols. Every symbol has _Function and _SetfFunction slots. cl:fdefinition consults these slots, as does the lower level mechanism used for function lookups when we know we're about to call the result. In order to do this latter efficiently, whenever any symbol is made, new functions are allocated for the two slots, which simply ignore all their arguments and signal undefined-function. (There is an actual allocation of new functions here, because they need to refer to the undefined function name.)
A better option would be to store functions in cells, analogous to the class-holders we use for find-class. This has a few advantages. First, it decouples function bindings from cells, so cells could be stored in some other way, e.g. a more flexible first-class environment object. Second, it removes some slots from symbols, saving us space in the many symbols (variable names, gensyms...) that are never bound to functions. Third, it saves time, in that new symbols do not need their slots initialized with a new undefined-function signaling function. This last has been an actual problem in profiling of the bytecode compiler - it's fast enough that macroexpansion time is relevant, and it turns out that making gensyms is slow, in large part because we allocate these functions.
The disadvantage is that fdefinition and friends become a bit more involved to implement, and need to access some kind of global hash table. But this should only be a performance issue in the rare case where the function name is not known at compile time. It should be a win for most cases.
SICL calls these cells cells and uses conses, so that (funcall 'foo ...) can be compiled as essentially (funcall (car (load-time-value (function-cell 'foo))) ...). This is simple. SBCL calls them FDEFNs (short for fdefinitions, probably). FDEFNs are funcallable instances. This is more complicated, but has the advantage of saving a car at the call site, which improves code size a teensie bit. Since we can use get-funcallable-instance-function internally there should be no problem with using funcallable instances.
In order to do fboundp, we can do something a little like we do for no-next-method: Make the undefined-function signaler an instance of another funcallable standard class that we can test for. Then (fboundp 'foo) becomes (typep (get-funcallable-instance-function (load-time-value (function-cell 'foo))) 'undefined-function-signaler), which should go faster than any kind of runtime hash lookup would. (fdefinition 'foo) and global #'foo, which ought to signal errors when foo is unbound, can be (let ((fun (get-funcallable-instance-function ...))) (when (typep fun 'undefined-function-signaler) (funcall fun)) fun).
(Lisp code used for demonstration. Practically speaking we will not use load-time-value.)
The text was updated successfully, but these errors were encountered:
We store global function bindings directly in the naming symbols. Every symbol has
_Function
and_SetfFunction
slots.cl:fdefinition
consults these slots, as does the lower level mechanism used for function lookups when we know we're about to call the result. In order to do this latter efficiently, whenever any symbol is made, new functions are allocated for the two slots, which simply ignore all their arguments and signalundefined-function
. (There is an actual allocation of new functions here, because they need to refer to the undefined function name.)A better option would be to store functions in cells, analogous to the
class-holder
s we use forfind-class
. This has a few advantages. First, it decouples function bindings from cells, so cells could be stored in some other way, e.g. a more flexible first-class environment object. Second, it removes some slots from symbols, saving us space in the many symbols (variable names, gensyms...) that are never bound to functions. Third, it saves time, in that new symbols do not need their slots initialized with a newundefined-function
signaling function. This last has been an actual problem in profiling of the bytecode compiler - it's fast enough that macroexpansion time is relevant, and it turns out that making gensyms is slow, in large part because we allocate these functions.The disadvantage is that
fdefinition
and friends become a bit more involved to implement, and need to access some kind of global hash table. But this should only be a performance issue in the rare case where the function name is not known at compile time. It should be a win for most cases.SICL calls these cells cells and uses conses, so that
(funcall 'foo ...)
can be compiled as essentially(funcall (car (load-time-value (function-cell 'foo))) ...)
. This is simple. SBCL calls them FDEFNs (short for fdefinitions, probably). FDEFNs are funcallable instances. This is more complicated, but has the advantage of saving acar
at the call site, which improves code size a teensie bit. Since we can useget-funcallable-instance-function
internally there should be no problem with using funcallable instances.In order to do
fboundp
, we can do something a little like we do forno-next-method
: Make theundefined-function
signaler an instance of another funcallable standard class that we can test for. Then(fboundp 'foo)
becomes(typep (get-funcallable-instance-function (load-time-value (function-cell 'foo))) 'undefined-function-signaler)
, which should go faster than any kind of runtime hash lookup would.(fdefinition 'foo)
and global#'foo
, which ought to signal errors whenfoo
is unbound, can be(let ((fun (get-funcallable-instance-function ...))) (when (typep fun 'undefined-function-signaler) (funcall fun)) fun)
.(Lisp code used for demonstration. Practically speaking we will not use
load-time-value
.)The text was updated successfully, but these errors were encountered: