Patches/changes to the base Pharo Image

We can make patches in three ways :

Installer Patches

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

PostLoad Patches

These are chages that we do as postload actions after loading a baseline.

InImage Patches

These are just method/classes moved to a different repo. This is the most common one.

Installer Patches

ExternalAddress int primitives

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.

RBCommentChange>>#primitiveExecute

Raises a broken ClassCommented event with nil as values, which confuses Epicea.

MCPackageLoader>>tryToLoad: loads classes twice

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.

MCMethodDefinition>>#addMethodAdditionTo: sets the compiled method in MethodAddition

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.

CompiledMethod >>basicAsMCMethodDefinition is slow

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.

RBParser>>parseKeywordMessageWith:

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.

RPackage>>toTagName:

We override it to avoid creating a symbol.

Month class>>#indexOfMonth:

We override it to get a faster comparison.

Object>>#setPinnedInMemory:

This method was causing crashes. Most likely fixed in the vm for Pharo 11, maybe also Pharo 10.

ExternalData>>readStringUTF8

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>>classVariableNamed:ifAbsent:

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.

Inconsistency when comparing compiled methods

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.

ShMetaclassChanged>>propagateToSubclasses: and ShMetaclassChangeDetector>>newChanges

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.

FFIUnixLibraryFinder>>basePaths

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

Library names for LGitLibrary and CairoLibrary

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

Other Patches

EpBehaviorCategoryChange misses old package name

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.

EpMethodModification has incorrect previous 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.

Change Formatting of Generated Accessors

Accessors generated by the base image have an extra newline in them.

Patched by fixing the template strings. Changes are linked to the original issue: Keep code formatting consistent when one creates accessors for slots in an object/class

Remove Transcript Spam from FFI

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 instead of SubscriptionRegistry

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.

Class installer that triggers class changes once

There are current several operations related to classes that lead to methods being recompiled in subclasses without that being announced.

EvaluateCommandLineHandler doesn't allow other options

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.

PositionableStream>>#on: precondition

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.

Extensions

These are extensions to the basic image, rather than overrides

ZnEasy class>>gtGetPng:

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: