This patch makes defstructs able to be redefined. #498
+114
−61
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Currently, ABCL catches the case where the defstruct has been changed and throws an
error. The main issue with defstruct is that when compiling, the
accessors are inlined as position-based accessors. So
(defstruct a b)
will compile a-b into lispObject.getSlotValue_0().
If another slot is added at the beginning
(defstruct a first b)
Then using a-b will get the value of the slot first instead of b.
The mechanism that ABCL uses to do inline is a source
transform. Allowing redefinition prevents that source transformation
from being created. Without the source transform, the compiled accessors
get the correct slot, at the cost of some performance.
Redefinition is controlled by a global variable
allow-defstruct-redefinition and a new option to defstruct :optimize.
If allow-defstruct-redefinition is t then
(defstruct (a :optimize nil) b)
Will prevent the source transform from being created. Otherwise the
current ABCL behavior is replicated.
The way ABCL detects that a change has been made is by saving the slot
definitions in the structure class, in a map of class names to
class-object defined in LispClass. The class object has a copy of the
slot definitions. One change is that we make is to make that map public.
There's an accessor findClass, but the result can't be cast to a
StructureClass, which the it needs to be in order to do the redefinition.
Then we add a new java function in StructureClass.java
reinitialize-structure-class, which replicates most of the logic of
make-structure-class, except that it reuses the old structure class
object. It's necessary to use the old value so that generic methods
specialized on the class will still work.
In defstruct.lisp there are changes to recognize the new option, to
check it before defining source transforms, and to call reinitialize-structure-class
instead of make-structure-class if the defstruct is already defined.
In the case that a defstruct is first created without the :optimize
option then subseqently modified to use :optimize nil, a warning is
given that previously compiled functions that use the accessors may need
to be recompiled and the previously defined source transforms are removed.
However, as long as the order of previously defined slots in the newly
defined defstruct remains the same as the order in the old structure,
inlined accessors will continue to work and recompilation is not
necessary.
The intended use of redefinition is during development, where structures
may evolve. There's no reason to use this mechanism in production code.