Patches/changes to the base Pharo Image
We can make patches in three ways :
They are done by compiling methods from the installer before loading any code. Should be done for core patches that we need before loading GT. Should be done in fewer cases.
Current file with patches: load-patches.st
These are chages that we do as postload actions after loading a baseline.
These are just method/classes moved to a different repo. This is the most common one.
Primitives for int operations in ExternalAddress
ByteArray variableByteSubclass: #ExternalAddress
instanceVariableNames: ''
classVariableNames: ''
package: 'FFI-Kernel'
can randomly crash the VM. As a workaround we remove the primitive:
pragma and make FFI calls which pass.
Raises a broken ClassCommented
event with nil
as values, which confuses Epicea.
The method MCPackageLoader>>#tryToLoad:
tryToLoad: aDefinition
<gtPharoPatch: #Pharo>
(aDefinition isClassDefinition and: [
aDefinition actualClass notNil ]) ifTrue: [ ^ false ].
[aDefinition addMethodAdditionTo: methodAdditions] on: Error do: [errorDefinitions add: aDefinition].
loads classes twice. This is mentioned in in the comment if MCPackageLoader>>#basicLoadDefinitions
basicLoadDefinitions
"FIXME. Do a separate pass on loading class definitions as the very first thing.
This is a workaround for a problem with the so-called 'atomic' loading (you wish!)
which isn't atomic at all but mixes compilation of methods with reshapes of classes.
Since the method is not installed until later, any class reshape in the middle *will*
affect methods in subclasses that have been compiled before. There is probably
a better way of dealing with this by ensuring that the sort order of the definition lists
superclass definitions before methods for subclasses but I need this NOW, and adding
an extra pass ensures that methods are compiled against their new class definitions."
additions do: [ :each | self loadClassDefinition: each ] displayingProgress: 'Loading classes...'.
additions do: [ :each | self tryToLoad: each ] displayingProgress: 'Compiling methods...'.
removals do: [ :each | each unload ] displayingProgress: 'Cleaning up...'.
self shouldWarnAboutErrors ifTrue: [ self warnAboutErrors ].
errorDefinitions do: [ :each | each addMethodAdditionTo: methodAdditions ] displayingProgress: 'Reloading erroneous definitions...'.
methodAdditions do: [ :each | each installMethod ].
methodAdditions do: [ :each | each notifyObservers ].
additions do: [ :each | each postloadOver: (self obsoletionFor: each) ] displayingProgress: 'Initializing...'.
. However, the fact that classes is loaded twice leads to a significant slowdown especially in code that uses traits.
We patch it in the installer by ignoring class definitions in that method.
The method MCMethodDefinition>>#addMethodAdditionTo:
addMethodAdditionTo: aCollection
<gtPharoPatch: #Pharo>
| methodAddition |
methodAddition := MethodAddition new
compile: source
classified: category
withStamp: timeStamp
notifying: nil
logSource: true
inClass: self actualClass.
aCollection add: methodAddition.
when creating a MethodAddition
Object subclass: #MethodAddition
instanceVariableNames: 'text category changeStamp requestor logSource myClass selector compiledMethod priorMethodOrNil priorCategoryOrNil'
classVariableNames: ''
package: 'Monticello-Loading'
uses MethodAddition>>#createCompiledMethod
createCompiledMethod
compiledMethod := myClass compiler
source: text asString;
requestor: requestor;
failBlock: [ ^nil ];
compile.
selector := compiledMethod selector.
self writeSourceToLog.
priorMethodOrNil := myClass compiledMethodAt: selector ifAbsent: [ nil ].
priorCategoryOrNil := myClass organization categoryOfElement: selector
to create the compiled method that will be later added to the class. However, depending on the actual chages, if some class definitions have errors, not all class definitions might be compiled at that point resulting in compiled methods that point to undeclared variables.
This issue is specific to Pharo 10/11.
The method CompiledMethod>>#basicAsMCMethodDefinition
basicAsMCMethodDefinition
<gtPharoPatch: #Pharo>
^ MCMethodDefinition
className: self methodClass instanceSide name
classIsMeta: self isClassSide
selector: self selector
category: self protocol
timeStamp: '' "self stamp"
source: self sourceCode
computes the method timestamp using CompiledCode>>#stamp
stamp
^ self timeStamp
which parses the time from the sources file.
It does not look like MCMethodDefinition
MCDefinition subclass: #MCMethodDefinition
instanceVariableNames: 'classIsMeta source category selector className timeStamp sortKey'
classVariableNames: 'Definitions InitializersEnabled'
package: 'Monticello-Modeling'
needs the timestamp during normal operations.
We patch it in the installer by not setting the timestamp when creating MCMethodDefinition
MCDefinition subclass: #MCMethodDefinition
instanceVariableNames: 'classIsMeta source category selector className timeStamp sortKey'
classVariableNames: 'Definitions InitializersEnabled'
package: 'Monticello-Modeling'
for compiled methods.
We override it to use a WriteStream
PositionableStream subclass: #WriteStream
instanceVariableNames: 'writeLimit'
classVariableNames: ''
package: 'Collections-Streams-Base'
to create the selector, instead of concatenating strings.
We override it to avoid creating a symbol.
We override it to get a faster comparison.
This method was causing crashes. Most likely fixed in the vm for Pharo 11, maybe also Pharo 10.
ExternalData>>#readStringUTF8
readStringUTF8
<gtPharoPatch: #Pharo>
"Assume that the receiver represents a C string containing UTF8 characters and convert
it to a Smalltalk string."
| stream index char |
self isNull ifTrue: [ ^ nil ].
type isPointerType ifFalse: [self error: 'External object is not a pointer type.'].
stream := WriteStream on: ByteArray new.
index := 1.
[(char := handle unsignedByteAt: index) = 0 ] whileFalse: [
stream nextPut: char.
index := index + 1].
^ [ ZnCharacterEncoder utf8 decodeBytes: stream contents ] on: ZnInvalidUTF8 do: [ stream contents asString ]
(which is part of the UnifiedFFI package in the threadedFFI-Plugin repository) is currently strict about interpreting strings as UTF8 encoded.
Moving to threaded FFI means that readStringUTF8 is used to read strings such as the commit message in git, which are not guaranteed to be UTF8 encoded.
Modify the method so that if the UTF8 conversion fails the string is returned as a null encoded string.
Metaclass is not polymorphic with Class in all contexts. We add Metaclass>>#classVariableNamed:ifAbsent:
classVariableNamed: aString ifAbsent: absentBlock
<gtPharoPatch: #Pharo>
"Answer the Class Variable"
^ self instanceSide
ifNil: absentBlock
ifNotNil: [ :class | class classVariableNamed: aString ifAbsent: absentBlock ]
as a patch.
When comparing two compiled methods that have the same code but different selectors, they can either be equal of different depending on whether or not they have pragmas
We added CompiledMethod>>#=
= aCompiledMethod
<gtPharoPatch: #Pharo>
^ (super = aCompiledMethod)
ifTrue: [
self selector = aCompiledMethod selector ]
ifFalse: [ false ]
that just calls super and adds a check for the selector.
Adding an instance variable to a class, which has subclasses with variables and references to those variables, will require recompiling the methods since the instance variable references are index based in the compiled methods. None of the recompiles show up as announcements, so a cache that holds on to compiled methods becomes invalid.
On linux we extend the places for placing libraries with the lib
folder next to the vm directory by changing FFIUnixLibraryFinder>>#basePaths
basePaths
<gtPharoPatch: #Pharo>
^ {
(Smalltalk vm directory asFileReference parent / 'lib') pathString.
Smalltalk imageDirectory fullName.
Smalltalk vm directory }
.
(Smalltalk vm directory asFileReference parent / 'lib')
We change the FFI library names to use names that do not have versions in them:
LGitLibrary>>#macLibraryName
macLibraryName
<gtPharoPatch: #Pharo>
^ FFIMacLibraryFinder findAnyLibrary: #('libgit2.dylib' 'libgit2.1.0.1.dylib' 'libgit2.1.0.0.dylib' 'libgit2.0.25.1.dylib')
: libgit2.dylib
LGitLibrary>>#unix64LibraryName
unix64LibraryName
<gtPharoPatch: #Pharo>
^ FFIUnix64LibraryFinder findAnyLibrary: #(
'libgit2.so'
"This name is wrong, but some versions of the VM has this library shipped with the bad name"
'libgit2.1.0.0.so'
'libgit2.so.1.0.0'
'libgit2.so.1.0'
'libgit2.so.1.1'
'libgit2.so.0.25.1')
: libgit2.so
LGitLibrary>>#unix32LibraryName
unix32LibraryName
^ FFIUnix32LibraryFinder findAnyLibrary: #(
'libgit2.so.1.4.4'
"This name is wrong, but some versions of the VM has this library shipped with the bad name"
'libgit2.1.0.0.so'
'libgit2.so.1.0.0'
'libgit2.so.1.0'
'libgit2.so.0.25.1')
: git2.dll
CairoLibrary>>#macLibraryName
macLibraryName
<gtPharoPatch: #Pharo>
^ FFIMacLibraryFinder findAnyLibrary: #('libcairo.dylib' 'libcairo.2.dylib')
: libcairo.dylib
CairoLibrary>>#win32LibraryName
win32LibraryName
<gtPharoPatch: #Pharo>
^ FFIWindowsLibraryFinder findAnyLibrary: #('cairo.dll' 'libcairo-2.dll')
: cairo.dll
EpBehaviorCategoryChange
EpBehaviorChange subclass: #EpBehaviorCategoryChange
uses: {} + TGtEpBehaviorCategoryChangeOldPackageName
instanceVariableNames: 'oldCategory newCategory class'
classVariableNames: ''
package: 'Epicea-Model'
does not have the name of the old package name. This means we cannot distinguish package/tag name by looking at the event. Makes it harder to group changes by package in Epicea.
Patched in gt4changes in a hack-ish way. In the postload of BaselineOfGToolkit4Changes
BaselineOf subclass: #BaselineOfGToolkit4Changes
instanceVariableNames: ''
classVariableNames: 'PostLoaded'
package: 'BaselineOfGToolkit4Changes'
we add the trait TGtEpBehaviorCategoryChangeOldPackageName
Trait named: #TGtEpBehaviorCategoryChangeOldPackageName
instanceVariableNames: 'oldPackageName'
package: 'GToolkit4Epicea-Model'
to EpBehaviorCategoryChange
EpBehaviorChange subclass: #EpBehaviorCategoryChange
uses: {} + TGtEpBehaviorCategoryChangeOldPackageName
instanceVariableNames: 'oldCategory newCategory class'
classVariableNames: ''
package: 'Epicea-Model'
and override EpBehaviorCategoryChange>>#initializeOldCategory:newCategory:class:
initializeOldCategory: anOldCategory newCategory: aNewCategory class: aClass
self initialize.
oldCategory := anOldCategory.
newCategory := aNewCategory.
class := aClass asEpiceaRingDefinition.
"This is a hack-ish was to determine the name of the old package starting from the name of the old category."
self inferOldPackageNameFromOldCategoryName.
in GToolkit4Epicea
to extract the old package name.
When transforming a method into an extension method, EpMethodModification
EpMethodChange subclass: #EpMethodModification
uses: {} + TGtEpMethodModificationClassPackagename
instanceVariableNames: 'oldMethod newMethod'
classVariableNames: ''
package: 'Epicea-Model'
does not store the previous package name of the method. Makes it harder to group method changes by package.
Patched in gt4changes in a hack-ish way. In the postload of BaselineOfGToolkit4Changes
BaselineOf subclass: #BaselineOfGToolkit4Changes
instanceVariableNames: ''
classVariableNames: 'PostLoaded'
package: 'BaselineOfGToolkit4Changes'
we add the trait TGtEpMethodModificationClassPackagename
Trait named: #TGtEpMethodModificationClassPackagename
instanceVariableNames: 'classPackageName'
package: 'GToolkit4Epicea-Model'
to EpMethodModification
EpMethodChange subclass: #EpMethodModification
uses: {} + TGtEpMethodModificationClassPackagename
instanceVariableNames: 'oldMethod newMethod'
classVariableNames: ''
package: 'Epicea-Model'
and override EpMethodModification>>#initializeWithOldMethod:newMethod:
initializeWithOldMethod: anOldMethod newMethod: aNewMethod
self initialize.
oldMethod := anOldMethod asEpiceaRingDefinition.
newMethod := aNewMethod asEpiceaRingDefinition.
self updateClassPackageName.
in GToolkit4Epicea
to extract the old package name.
Accessors generated by the base image have an extra newline in them.
Transcript is spammed with Returning an object but pool is full
. Changes are linked to the original issue: "Returning an object but pool is full" spammed in Transcript
FastSubscriptionRegistry
Object subclass: #FastSubscriptionRegistry
instanceVariableNames: 'monitor subscriberMap'
classVariableNames: ''
package: 'GToolkit-PharoBasePatch-Subscriptions-Core'
optimises how users can check if an object is registered as a subscriber within an announcer.
This checks happens many times in Bloc when delivering events.
The class side method FastSubscriptionRegistry>>#initialize
initialize
"Replaces the subscription registry of the SystemAnnouncer with an instance of the fast subscription registry"
| newReg |
newReg := FastSubscriptionRegistry new copyFromSlowRegistry: (SystemAnnouncer uniqueInstance instVarNamed: #registry).
SystemAnnouncer uniqueInstance instVarNamed: #registry put: newReg.
registers this registry as default when the class is initialize.
There are current several operations related to classes that lead to methods being recompiled in subclasses without that being announced.
EvaluateCommandLineHandler
STCommandLineHandler subclass: #EvaluateCommandLineHandler
instanceVariableNames: ''
classVariableNames: ''
package: 'CodeImportCommandLineHandlers-Base'
will fail to parse the command to be evaluated correctly if other global options are present, e.g. --pharoDebugLog
.
We add an explicit precondition in PositionableStream>>#on:
on: aCollection
"We override this method to explicitly add a precondition checking that the parameter is not nil."
<gtPharoPatch: #Pharo>
aCollection ifNil: [
^ Error signal: 'Streams cannot be initialized with a nil collection' ].
collection := aCollection.
readLimit := aCollection size.
position := 0.
self reset
for ensuring that the given collection is not nil
. We do this to ensure the method works correctly in case a size
method is defined in UndefinedObject
Object subclass: #UndefinedObject
instanceVariableNames: ''
classVariableNames: ''
package: 'Kernel-Objects'
, for example for compatibility with other Smalltalk dialects.
These are extensions to the basic image, rather than overrides
When reading some PNGs, ImageReadWriter
can trigger errors. We added ZnEasy>>#gtGetPng:
gtGetPng: urlObject
"Use SkiaImage to convert bytes to a Form to avoid possible parsing errors in PNGs images."
"self getPng: 'http://pharo.org/files/pharo.png'."
"(self getPng: 'http://chart.googleapis.com/chart?cht=tx&chl=',
'a^2+b^2=c^2' urlEncoded) asMorph openInHand."
^ self
gtGetImageOfType: ZnMimeType imagePng
fromUrl: urlObject
that uses SkiaImage
SkiaExternalObject subclass: #SkiaImage
uses: TSpartaSurface
instanceVariableNames: 'gpuContext'
classVariableNames: ''
package: 'Sparta-Skia-External'
to create a form.
Patch Commit: Add #gtGetPng: