Shrinking of the image is a process that is occasionally performed to remove unwanted classes. The intention is often to get a small image that can be used on equipment with very limited memory. In principle you can remove classes manually one by one, but shrinking does more: It finds unsent methods and removes them, too. (It would be difficult to do that manually: Squeak 3.4 contains more than 41000 methods!)
Methods that support shrinking are located in the instance protocol of SystemDictionary in the message categories 'housekeeping' and 'shrinking'. (we do not currently have a Shrinker class.) Dan Ingalls, Scott Wallace, Michael Rueger, Stefan Matthias Aust, Andreas Raab, Tim Rowledge, Jon Hylands, Bob Arning, Gerald Leeb and possibly others contributed ideas and code to the shrinking procedure. Jon Hylands published a shrinking script for Squeak 3.4 that removes the Morphic interface and some other features. (It is known that Jon uses Squeak on equipment with very limited resources.) Jons script removes also the development tools which are not needed on his equipment. I used Jons code to derive a script that creates a MVC-only image that keeps the development tools. That image is designed to meet the needs of schools that are not equipped with the most powerful (and most expensive) computers.
Often the image shrinking produces an image that contains obsolete classes. An obsolete class is the remnant of a removed class. An obsolete class continues to exist in the image for as long as
There are two approaches to this problem: One can try to remove obsolete classes from a shrunken image - this is difficult, but possible - and one can try to modify the image prior to shrinking it in such a way that the shrinking will not leave obsolete classes in the image. This second approach is to be preferred.
Compiled methods that reference removed classes cannot successfully be recompiled - a recompilation helps to find such methods very quickly. References to instances of removed classes are much more difficult to find. It is a good idea to inspect global dictionaries, pool dictionaries, class variables and class instance variables before you shrink the image.
During a preparatory step to shrinking one has to modify all methods that contain references to classes that are to be removed. This is not an easy task: It requires a lot of patience to disentangle MVC and Morphic - which is necessary when you want to remove the Morphic interface.
It is certainly helpful to give an example. The instance protocol of Model contains this method:
containingWindow "Answer the window that holds the receiver. The dependents technique is odious and may not be airtight, if multiple windows have the same model." ^self dependents detect: [:d | ((d isKindOf: SystemWindow orOf: StandardSystemView) or: [d isKindOf: MVCWiWPasteUpMorph]) and: [d model == self] ] ifNone: [nil]
The classes shown in red are part of the Morphic system. Assume that we want to remove the Morphic interface and that we leave this method as it is. This method alone would obstacle the complete removal of the classes SystemWindow and MVCWiWPasteUpMorph. It is also impossible to remove the superclasses of classes that cannot be removed. Therefore the shown method alone will cause the retention of obsolete classes for all these classes:
Morph BorderedMorph MorphicModel SystemWindow PasteUpMorph WiWPasteUpMorph MVCWiWPasteUpMorph
When we wish to remove the Morphic system and to keep the MVC interface, we have to rewrite the shown method in this way:
containingWindow "Answer the window that holds the receiver. The dependents technique is odious and may not be airtight, if multiple windows have the same model." ^self dependents detect: [:d | (d isKindOf: StandardSystemView) and: [d model == self]] ifNone: [nil]
StandardSystemView is part of the MVC interface.
When we wish to remove the MVC interface and to keep only the Morphic interface, a different modification is required:
containingWindow "Answer the window that holds the receiver. The dependents technique is odious and may not be airtight, if multiple windows have the same model." ^self dependents detect: [:d | ((d isKindOf: SystemWindow) or: [d isKindOf: MVCWiWPasteUpMorph]) and: [d model == self] ] ifNone: [nil]
Your see that this is real work.
A newer idea, the ToolBuilder, will greatly simplfy this problem: The ToolBuilder allows you to describe a window in terms of class-independent specifications. A builder is used to translate these specifications into a MVC window or into a Morphic window - whatever is needed.
Change sets typically contain refrences to classes, even to deleted classes. Classes that are referenced from a change set cannot be completely removed during shrinking and will converted into obsolete classes. To avoid this, one has to close all change sorters and to destroy all change sets before shrinking starts.
It is necessary to set all preferences to values that can be used in the shrunken image. To give an example, you have to set all font preferences to strike fonts before you can remove the TTF font support.
The shrinking script is designed to completely remove the Morphic interface. It does several things:
First, it replaces a lot of methods that contain references to Morphic classes with newer version that do not reference classes that are later removed. Finding this methods was quite time consuming, but this is something that has to be done manually. The first step of shrinking also deletes some classes, but this is something that could be done later as well.
Second, the postscript of the script removes references to instances of Morphic classes from some global data structures. This is discussed in detail later on.
The last part of the shrinking is done by the user. To start it, the user executes these statements in a MVC-project:
Smalltalk preMajorShrink. Smalltalk majorShrink.
After execution of these statements, the expression
Smalltalk obsoleteClasses size
should return zero. If it does not, you can inspect the expression Smalltalk obsoleteClasses to find out which classes left remnants in the image.
It is a good idea to merge the sources and the changes into one new sources file. You do this with:
Smalltalk condenseSources.
A very simple and helpful first step is to load the condensed sources into a text editor and to search the names of the obsolete classes one by one. This helps you to find methods that you have to rewrite. It is prudent to collect all changes that you do into a new change set. You can later file out that change set, merge it with the shrinking script and file out a modified (and normally improved) shrinking script.
When you cannot find references to obsoleted classes in the current sources, you have reason to assume that some global variable or some pool dictionary contains a reference to an instance of the obsoleted class. This is difficult to find in a shrunken image. You will better open the full image and search all instances of the class in question. Once you have all instances, you will have - for every instance - to follow all references to the instance. This can be really difficult. As an alternative, you may search sources and changes of the full image for methods that refer the class in question. With a bit of good luck, you will find a class method that initializes a pool dictionary or a global variable.
preMajorShrink
This method removes two morphic toys from the global dictionary References
and it removes all projects
with the exception of the current one.
majorShrink
This method does removes all packages and classes that are designated
for removal. It also removes all unused classes and all unsent methods.
presumedSentMethods
This method returns a set with the names of methods that must not be
removed. When a method is mentioned here, it will be kept both in the
instance protocol and in the class protocol of all classes that have
it. Check these methods carefully: To avoid obsolete classes, none
of these methods is allowed to contain a reference to a class that will
be removed. You may conclude that you will have to modify or to remove
methods that do not conform to that requirement.
majorShrinkClassesToLeave
An array with the names of classes that must not be removed.
majorShrinkClassesToZap
An array with the names of classes that shall be removed.
Flaps disableGlobalFlaps: false. Preferences setParameter: #HaloSpecs to: IdentityDictionary new. Preferences removeFlaps. ParagraphEditor initializeTextEditorMenus. Smalltalk removeKey: #ActiveEvent; removeKey: #ActiveHand; removeKey: #ActiveWorld; removeKey: #ChessConstants. Vocabulary resetVocabulary. ExternalDropHandler resetRegisteredHandlers. DocLibrary resetServers. ExternalSettings shutDown. ExternalSettings registeredClients asArray do: [:item | ExternalSettings registeredClients remove: item]. ServerDirectory resetLocalProjectDirectories. ServerDirectory resetServers. Flaps dropStandardFlaps.
Here, the classes ServerDirectory and ExternalSettings are important:
Note that the statements of the postscript are executed when the change set is filed in. When you can edit the postscript, things are already done! To remove unwanted statements from the postscript, you proceed as follows:
First, packages and classes are deleted. This is the simple part of shrinking and it is something that you could do manually as well. The sophisticated part comes later. It is a loop. During the loop, all unsent messages are searched and deleted. Deletion of all unsent messages may remove all references to a previously referenced class. When a class is not referenced from any method, it is considered unused if it has no subclasses. These unused classes are searched and removed. Removal of classes may render methods in other classes unused and therefore the search-and-remove algorithm is repeated until no unused methods are found.
A method is considered as used, if it is sent from at least one other method or if its selector is referenced from at least one other method. Regrettably, there are programming styles, that hide the use of methods and the image contains some examples of code that hide the use of methods from the shrinker. Here are some examples:
In PackageLoader>>fileInFrom: we read:
fileInFrom: aStream
| chgRec changes |
changes := (ChangeList new
scanFile: aStream
from: 0
to: aStream size) changeList.
aStream close.
('Processing ', self packageName)
displayProgressAt: Sensor cursorPoint
from: 1
to: changes size
during:[:bar|
1 to: changes size do:
[:i|
bar value: i.
chgRec := changes at: i.
self perform: (chgRec type copyWith: $:) asSymbol
with: chgRec.
].
].
The statement shown in red constructs a message selector by appending a colon to a string which is then converted into a symbol. The shrinking algorithm is unable to find out what methods are really used and it is not a surprise that an earlier version of the shrinker removed the method method: which is needed, but not sent from any other place in the image. To fix that problem, we have to include the selector #method: into the shrink support method presumedSentMethods
The following example is taken from class Debugger:
preDebugButtonQuads ^#(('Proceed' proceed blue 'continue execution' ) ('Abandon' abandon black 'abandon this execution by closing this window') ('Debug' debug red 'bring up a debugger'))
This array of arrays is used elsewhere to create the buttons of the error notification window. The parts shown in read are message selectors that the shrinking algorithm cannot find. The messages proceed and debug are sent elsewhere and will not be removed, but method abandon will be removed as unsent. To avoid that, we have to include its selector into the shrink support method presumedSentMethods
Some very important methods are not referenced from methods in the image and have to be included into method presumedSentMethods to ensure their inclusion into the image. These are:
The inclusion of other unsent methods is desirable. Candidates for inclusion are:
It is recommended to include all subclasses of class FileDirectory into a shrunken image. These are:
FileDriectory AcornFileDirectoy DosFileDirectory MacFileDirectory MacHFSPlusFileDirectory UnixFileDirectory
The method SystemDictionary>>majorShrinkClassesToZap enumerates the classes that will be removed from the image. Ensure that for a cross-platform portable Squeak, none of the directory classes is mentioned there.
Squeak 3.4 | Shrinking scripts for Squeak 3.4 |
Squeak 3.6 | Shrinking scripts for Squeak 3.6 |
Squeak 3.7 #5989 | Shrinking scripts for Squeak 3.7 |