Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FDEFNs #1447

Open
Bike opened this issue Mar 31, 2023 · 0 comments
Open

FDEFNs #1447

Bike opened this issue Mar 31, 2023 · 0 comments

Comments

@Bike
Copy link
Member

Bike commented Mar 31, 2023

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.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant