Eliot Miranda uploaded a new version of Collections to project The Trunk:
http://source.squeak.org/trunk/Collections-eem.571.mcz
==================== Summary ====================
Name: Collections-eem.571
Author: eem
Time: 27 May 2014, 11:49:16.946 am
UUID: 5610c761-96af-422f-bb1d-63bc87a1cc7a
Ancestors: Collections-nice.570
Speed up testing different sized strings for equality by
adding a size text to String>>#=. Tis speeds up e.g.
comparing the first 10,000 ByteString instances to each
other by -67%.
=============== Diff against Collections-nice.570 ===============
Item was changed:
----- Method: String>>= (in category 'comparing') -----
= aString
"Answer whether the receiver sorts equally as aString.
The collation order is simple ascii (with case differences)."
+ (aString isString
+ and: [self size = aString size]) ifFalse: [^ false].
- aString isString ifFalse: [ ^ false ].
^ (self compare: self with: aString collated: AsciiOrder) = 2!
Eliot Miranda uploaded a new version of Files to project The Trunk:
http://source.squeak.org/trunk/Files-eem.136.mcz
==================== Summary ====================
Name: Files-eem.136
Author: eem
Time: 27 May 2014, 11:47:15.952 am
UUID: 83fc1540-4b6b-4ff7-b18d-e864e9e7fb16
Ancestors: Files-eem.135
Fix the regression introduced in Files-eem.133 by
implementing peekLast more directly. Add a lastWritten
inst var to StandardFileStream. Change the definition of
isBinary so that e.g. WideString could be used as a buffer1
and the file is still considered not binary.
=============== Diff against Files-eem.135 ===============
Item was changed:
FileStream subclass: #StandardFileStream
+ instanceVariableNames: 'name fileID buffer1 lastWritten'
- instanceVariableNames: 'name fileID buffer1'
classVariableNames: 'Registry'
poolDictionaries: ''
category: 'Files-Kernel'!
!StandardFileStream commentStamp: 'ul 12/6/2009 05:13' prior: 0!
Provides a simple, platform-independent, interface to a file system. The instance variable rwmode, inherited from class PositionableStream, here is used to hold a Boolean -- true means opened for read-write, false means opened for read-only. 2/12/96 sw
I implement a simple read buffering scheme with the variables defined in PositionableStream (which are unused in me otherwise) in the following way:
collection <ByteString> or <ByteArray> This is the buffer.
position <Integer> The relative position in the buffer. Greater or equal to zero.
readLimit <Integer> The number of bytes buffered. Greater or equal to zero.
Read buffering is enabled with #enableReadBuffering, disabled with #disableReadBuffering and it is enabled by default. The buffer is filled when a read attempt of an unbuffered absolute position is requested, or when a negative repositioning is made (with #position: with an argument < than the current absolute position) to an absolute position which is not buffered. In the first case, the buffer is positioned to the given absolute position. In the latter case the repositioning is made to the requested absolute position minus fourth of the buffer size. This means that further small negative repositionings won't result in buffer flushing. This is really useful when filing in code.
The read buffer is flushed (#flushReadBuffer) whenever a write attempt is made.
The buffer state is valid if and only if collection is not nil and position < readLimit.!
Item was changed:
----- Method: StandardFileStream>>ascii (in category 'properties-setting') -----
ascii
"opposite of binary"
+ buffer1 := ByteString new: 1.
+ collection ifNotNil: [collection := collection asString].
+ lastWritten ifNotNil: [lastWritten isInteger ifTrue: [lastWritten := lastWritten asCharacter]]!
- buffer1 := String new: 1.
- collection ifNotNil: [ collection := collection asString ]!
Item was changed:
----- Method: StandardFileStream>>binary (in category 'properties-setting') -----
binary
buffer1 := ByteArray new: 1.
+ collection ifNotNil: [collection := collection asByteArray].
+ lastWritten ifNotNil: [lastWritten isCharacter ifTrue: [lastWritten := lastWritten asInteger]]!
- collection ifNotNil: [ collection := collection asByteArray ]!
Item was changed:
----- Method: StandardFileStream>>isBinary (in category 'properties-setting') -----
isBinary
+ ^buffer1 isString not!
- ^ buffer1 class == ByteArray!
Item was changed:
----- Method: StandardFileStream>>nextPut: (in category 'read, write, position') -----
+ nextPut: element
+ "Write the given element (character or integer) to this file."
- nextPut: char
- "Write the given character to this file."
rwmode ifFalse: [^ self error: 'Cannot write a read-only file'].
+ collection ifNotNil:
+ [position < readLimit ifTrue: [ self flushReadBuffer ] ].
+ buffer1 at: 1 put: element.
- collection ifNotNil: [
- position < readLimit ifTrue: [ self flushReadBuffer ] ].
- buffer1 at: 1 put: char.
self primWrite: fileID from: buffer1 startingAt: 1 count: 1.
+ lastWritten := element.
+ ^ element
- ^ char
!
Item was changed:
----- Method: StandardFileStream>>nextPutAll: (in category 'read, write, position') -----
+ nextPutAll: aCollection
+ "Write all the elements of the given collection (a String or IntegerArray of some kind) to this file."
- nextPutAll: aString
- "Write all the characters of the given string to this file."
| size |
rwmode ifFalse: [^ self error: 'Cannot write a read-only file'].
+ collection ifNotNil:
+ [position < readLimit ifTrue: [ self flushReadBuffer]].
+ (size := aCollection basicSize) > 0 ifTrue:
+ [self primWrite: fileID from: aCollection startingAt: 1 count: size.
+ lastWritten := aCollection at: size].
+ ^ aCollection!
- collection ifNotNil: [
- position < readLimit ifTrue: [ self flushReadBuffer ] ].
- (size := aString size) > 0 ifTrue:
- [buffer1
- at: 1
- put: ((buffer1 at: 1) isCharacter
- ifTrue: [(aString at: size) asCharacter]
- ifFalse: [(aString at: size) asInteger]).
- self primWrite: fileID from: aString startingAt: 1 count: aString basicSize].
- ^ aString
- !
Item was changed:
----- Method: StandardFileStream>>peekLast (in category 'read, write, position') -----
peekLast
+ "Answer the item just put at the end of the stream, if any."
- "Return that item just put at the end of the stream"
+ ^lastWritten!
- ^ buffer1 size > 0
- ifTrue: [buffer1 last]
- ifFalse: [nil]
- !
Eliot Miranda uploaded a new version of Files to project The Trunk:
http://source.squeak.org/trunk/Files-eem.136.mcz
==================== Summary ====================
Name: Files-eem.136
Author: eem
Time: 27 May 2014, 11:47:15.952 am
UUID: 83fc1540-4b6b-4ff7-b18d-e864e9e7fb16
Ancestors: Files-eem.135
Fix the regression introduced in Files-eem.133 by
implementing peekLast more directly. Add a lastWritten
inst var to StandardFileStream. Change the definition of
isBinary so that e.g. WideString could be used as a buffer1
and the file is still considered not binary.
=============== Diff against Files-eem.135 ===============
Item was changed:
FileStream subclass: #StandardFileStream
+ instanceVariableNames: 'name fileID buffer1 lastWritten'
- instanceVariableNames: 'name fileID buffer1'
classVariableNames: 'Registry'
poolDictionaries: ''
category: 'Files-Kernel'!
!StandardFileStream commentStamp: 'ul 12/6/2009 05:13' prior: 0!
Provides a simple, platform-independent, interface to a file system. The instance variable rwmode, inherited from class PositionableStream, here is used to hold a Boolean -- true means opened for read-write, false means opened for read-only. 2/12/96 sw
I implement a simple read buffering scheme with the variables defined in PositionableStream (which are unused in me otherwise) in the following way:
collection <ByteString> or <ByteArray> This is the buffer.
position <Integer> The relative position in the buffer. Greater or equal to zero.
readLimit <Integer> The number of bytes buffered. Greater or equal to zero.
Read buffering is enabled with #enableReadBuffering, disabled with #disableReadBuffering and it is enabled by default. The buffer is filled when a read attempt of an unbuffered absolute position is requested, or when a negative repositioning is made (with #position: with an argument < than the current absolute position) to an absolute position which is not buffered. In the first case, the buffer is positioned to the given absolute position. In the latter case the repositioning is made to the requested absolute position minus fourth of the buffer size. This means that further small negative repositionings won't result in buffer flushing. This is really useful when filing in code.
The read buffer is flushed (#flushReadBuffer) whenever a write attempt is made.
The buffer state is valid if and only if collection is not nil and position < readLimit.!
Item was changed:
----- Method: StandardFileStream>>ascii (in category 'properties-setting') -----
ascii
"opposite of binary"
+ buffer1 := ByteString new: 1.
+ collection ifNotNil: [collection := collection asString].
+ lastWritten ifNotNil: [lastWritten isInteger ifTrue: [lastWritten := lastWritten asCharacter]]!
- buffer1 := String new: 1.
- collection ifNotNil: [ collection := collection asString ]!
Item was changed:
----- Method: StandardFileStream>>binary (in category 'properties-setting') -----
binary
buffer1 := ByteArray new: 1.
+ collection ifNotNil: [collection := collection asByteArray].
+ lastWritten ifNotNil: [lastWritten isCharacter ifTrue: [lastWritten := lastWritten asInteger]]!
- collection ifNotNil: [ collection := collection asByteArray ]!
Item was changed:
----- Method: StandardFileStream>>isBinary (in category 'properties-setting') -----
isBinary
+ ^buffer1 isString not!
- ^ buffer1 class == ByteArray!
Item was changed:
----- Method: StandardFileStream>>nextPut: (in category 'read, write, position') -----
+ nextPut: element
+ "Write the given element (character or integer) to this file."
- nextPut: char
- "Write the given character to this file."
rwmode ifFalse: [^ self error: 'Cannot write a read-only file'].
+ collection ifNotNil:
+ [position < readLimit ifTrue: [ self flushReadBuffer ] ].
+ buffer1 at: 1 put: element.
- collection ifNotNil: [
- position < readLimit ifTrue: [ self flushReadBuffer ] ].
- buffer1 at: 1 put: char.
self primWrite: fileID from: buffer1 startingAt: 1 count: 1.
+ lastWritten := element.
+ ^ element
- ^ char
!
Item was changed:
----- Method: StandardFileStream>>nextPutAll: (in category 'read, write, position') -----
+ nextPutAll: aCollection
+ "Write all the elements of the given collection (a String or IntegerArray of some kind) to this file."
- nextPutAll: aString
- "Write all the characters of the given string to this file."
| size |
rwmode ifFalse: [^ self error: 'Cannot write a read-only file'].
+ collection ifNotNil:
+ [position < readLimit ifTrue: [ self flushReadBuffer]].
+ (size := aCollection basicSize) > 0 ifTrue:
+ [self primWrite: fileID from: aCollection startingAt: 1 count: size.
+ lastWritten := aCollection at: size].
+ ^ aCollection!
- collection ifNotNil: [
- position < readLimit ifTrue: [ self flushReadBuffer ] ].
- (size := aString size) > 0 ifTrue:
- [buffer1
- at: 1
- put: ((buffer1 at: 1) isCharacter
- ifTrue: [(aString at: size) asCharacter]
- ifFalse: [(aString at: size) asInteger]).
- self primWrite: fileID from: aString startingAt: 1 count: aString basicSize].
- ^ aString
- !
Item was changed:
----- Method: StandardFileStream>>peekLast (in category 'read, write, position') -----
peekLast
+ "Answer the item just put at the end of the stream, if any."
- "Return that item just put at the end of the stream"
+ ^lastWritten!
- ^ buffer1 size > 0
- ifTrue: [buffer1 last]
- ifFalse: [nil]
- !
Eliot Miranda uploaded a new version of Files to project The Trunk:
http://source.squeak.org/trunk/Files-eem.136.mcz
==================== Summary ====================
Name: Files-eem.136
Author: eem
Time: 27 May 2014, 11:47:15.952 am
UUID: 83fc1540-4b6b-4ff7-b18d-e864e9e7fb16
Ancestors: Files-eem.135
Fix the regression introduced in Files-eem.133 by
implementing peekLast more directly. Add a lastWritten
inst var to StandardFileStream. Change the definition of
isBinary so that e.g. WideString could be used as a buffer1
and the file is still considered not binary.
=============== Diff against Files-eem.135 ===============
Item was changed:
FileStream subclass: #StandardFileStream
+ instanceVariableNames: 'name fileID buffer1 lastWritten'
- instanceVariableNames: 'name fileID buffer1'
classVariableNames: 'Registry'
poolDictionaries: ''
category: 'Files-Kernel'!
!StandardFileStream commentStamp: 'ul 12/6/2009 05:13' prior: 0!
Provides a simple, platform-independent, interface to a file system. The instance variable rwmode, inherited from class PositionableStream, here is used to hold a Boolean -- true means opened for read-write, false means opened for read-only. 2/12/96 sw
I implement a simple read buffering scheme with the variables defined in PositionableStream (which are unused in me otherwise) in the following way:
collection <ByteString> or <ByteArray> This is the buffer.
position <Integer> The relative position in the buffer. Greater or equal to zero.
readLimit <Integer> The number of bytes buffered. Greater or equal to zero.
Read buffering is enabled with #enableReadBuffering, disabled with #disableReadBuffering and it is enabled by default. The buffer is filled when a read attempt of an unbuffered absolute position is requested, or when a negative repositioning is made (with #position: with an argument < than the current absolute position) to an absolute position which is not buffered. In the first case, the buffer is positioned to the given absolute position. In the latter case the repositioning is made to the requested absolute position minus fourth of the buffer size. This means that further small negative repositionings won't result in buffer flushing. This is really useful when filing in code.
The read buffer is flushed (#flushReadBuffer) whenever a write attempt is made.
The buffer state is valid if and only if collection is not nil and position < readLimit.!
Item was changed:
----- Method: StandardFileStream>>ascii (in category 'properties-setting') -----
ascii
"opposite of binary"
+ buffer1 := ByteString new: 1.
+ collection ifNotNil: [collection := collection asString].
+ lastWritten ifNotNil: [lastWritten isInteger ifTrue: [lastWritten := lastWritten asCharacter]]!
- buffer1 := String new: 1.
- collection ifNotNil: [ collection := collection asString ]!
Item was changed:
----- Method: StandardFileStream>>binary (in category 'properties-setting') -----
binary
buffer1 := ByteArray new: 1.
+ collection ifNotNil: [collection := collection asByteArray].
+ lastWritten ifNotNil: [lastWritten isCharacter ifTrue: [lastWritten := lastWritten asInteger]]!
- collection ifNotNil: [ collection := collection asByteArray ]!
Item was changed:
----- Method: StandardFileStream>>isBinary (in category 'properties-setting') -----
isBinary
+ ^buffer1 isString not!
- ^ buffer1 class == ByteArray!
Item was changed:
----- Method: StandardFileStream>>nextPut: (in category 'read, write, position') -----
+ nextPut: element
+ "Write the given element (character or integer) to this file."
- nextPut: char
- "Write the given character to this file."
rwmode ifFalse: [^ self error: 'Cannot write a read-only file'].
+ collection ifNotNil:
+ [position < readLimit ifTrue: [ self flushReadBuffer ] ].
+ buffer1 at: 1 put: element.
- collection ifNotNil: [
- position < readLimit ifTrue: [ self flushReadBuffer ] ].
- buffer1 at: 1 put: char.
self primWrite: fileID from: buffer1 startingAt: 1 count: 1.
+ lastWritten := element.
+ ^ element
- ^ char
!
Item was changed:
----- Method: StandardFileStream>>nextPutAll: (in category 'read, write, position') -----
+ nextPutAll: aCollection
+ "Write all the elements of the given collection (a String or IntegerArray of some kind) to this file."
- nextPutAll: aString
- "Write all the characters of the given string to this file."
| size |
rwmode ifFalse: [^ self error: 'Cannot write a read-only file'].
+ collection ifNotNil:
+ [position < readLimit ifTrue: [ self flushReadBuffer]].
+ (size := aCollection basicSize) > 0 ifTrue:
+ [self primWrite: fileID from: aCollection startingAt: 1 count: size.
+ lastWritten := aCollection at: size].
+ ^ aCollection!
- collection ifNotNil: [
- position < readLimit ifTrue: [ self flushReadBuffer ] ].
- (size := aString size) > 0 ifTrue:
- [buffer1
- at: 1
- put: ((buffer1 at: 1) isCharacter
- ifTrue: [(aString at: size) asCharacter]
- ifFalse: [(aString at: size) asInteger]).
- self primWrite: fileID from: aString startingAt: 1 count: aString basicSize].
- ^ aString
- !
Item was changed:
----- Method: StandardFileStream>>peekLast (in category 'read, write, position') -----
peekLast
+ "Answer the item just put at the end of the stream, if any."
- "Return that item just put at the end of the stream"
+ ^lastWritten!
- ^ buffer1 size > 0
- ifTrue: [buffer1 last]
- ifFalse: [nil]
- !
Eliot Miranda uploaded a new version of Kernel to project The Trunk:
http://source.squeak.org/trunk/Kernel-eem.854.mcz
==================== Summary ====================
Name: Kernel-eem.854
Author: eem
Time: 27 May 2014, 11:17:11.701 am
UUID: 354e7db1-1a1c-426c-ae87-018e33f29c69
Ancestors: Kernel-nice.853
Fix the regression caused by Kernel-eem.847. Instead of
installing teh correct binding in compileAllFrom: add
behavior>>updateMethodBindingsTo: and invoke it from
ClassBuilder>>update:to:. This restores the failing
PureBehaviorTests>>testReshapeClass.
=============== Diff against Kernel-nice.853 ===============
Item was changed:
----- Method: Behavior>>compileAllFrom: (in category 'compiling') -----
compileAllFrom: oldClass
"Compile all the methods in the receiver's method dictionary.
+ This validates sourceCode and variable references and forces
+ all methods to use the current bytecode set"
+
- This validates sourceCode and variable references and forces
- all methods to use the current bytecode set"
- | envBinding binding |
"ar 7/10/1999: Use oldClass selectors not self selectors"
+ oldClass selectorsDo: [:sel | self recompile: sel from: oldClass]!
- oldClass selectorsDo: [:sel | self recompile: sel from: oldClass].
-
- "Ensure that we share a common binding after recompilation.
- This is so that ClassBuilder reshapes avoid creating new bindings
- for every method when recompiling a large class hierarchy.
- eem 5/2/2014 17:43: Further, if we're not yet in the environment
- (because we're about to be mutated into oldClass), use oldClass's
- binding, so as not to end up with the wrong binding post recompile."
- binding := self binding.
- (self name = oldClass name
- and: [(envBinding := self environment bindingOf: self name) ~= binding
- and: [envBinding = oldClass binding]]) ifTrue:
- [binding := envBinding].
- self methodsDo:[:m|
- m methodClassAssociation == binding
- ifFalse:[m methodClassAssociation: binding]].
- !
Item was changed:
----- Method: ClassBuilder>>update:to: (in category 'class mutation') -----
update: oldClass to: newClass
"Convert oldClass, all its instances and possibly its meta class into newClass, instances of newClass and possibly its meta class. The process is surprisingly simple in its implementation and surprisingly complex in its nuances and potentially bad side effects.
We can rely on two assumptions (which are critical):
#1: The method #updateInstancesFrom: will not create any lasting pointers to 'old' instances ('old' is quote on quote since #updateInstancesFrom: will do a become of the old vs. the new instances and therefore it will not create pointers to *new* instances before the #become: which are *old* afterwards)
#2: The non-preemptive execution of the critical piece of code guarantees that nobody can get a hold by 'other means' (such as process interruption and reflection) on the old instances.
Given the above two, we know that after #updateInstancesFrom: there are no pointer to any old instances. After the forwarding become there will be no pointers to the old class or meta class either. Meaning that if we throw in a nice fat GC at the end of the critical block, everything will be gone (but see the comment right there). There's no need to worry.
"
| meta |
meta := oldClass isMeta.
"Note: Everything from here on will run without the ability to get interrupted
to prevent any other process to create new instances of the old class."
[
"Note: The following removal may look somewhat obscure and needs an explanation. When we mutate the class hierarchy we create new classes for any existing subclass. So it may look as if we don't have to remove the old class from its superclass. However, at the top of the hierarchy (the first class we reshape) that superclass itself is not newly created so therefore it will hold both the oldClass and newClass in its (obsolete or not) subclasses. Since the #become: below will transparently replace the pointers to oldClass with newClass the superclass would have newClass in its subclasses TWICE. With rather unclear effects if we consider that we may convert the meta-class hierarchy itself (which is derived from the non-meta class hierarchy).
Due to this problem ALL classes are removed from their superclass just prior to converting them. Here, breaking the superclass/subclass invariant really doesn't matter since we will effectively remove the oldClass (become+GC) just a few lines below."
oldClass superclass removeSubclass: oldClass.
oldClass superclass removeObsoleteSubclass: oldClass.
"make sure that the VM cache is clean"
oldClass methodDict do: [:cm | cm flushCache].
"Convert the instances of oldClass into instances of newClass"
newClass updateInstancesFrom: oldClass.
meta
+ ifTrue:
+ [oldClass becomeForward: newClass.
+ oldClass updateMethodBindingsTo: oldClass binding]
+ ifFalse:
+ [{oldClass. oldClass class} elementsForwardIdentityTo: {newClass. newClass class}.
+ oldClass updateMethodBindingsTo: oldClass binding.
+ oldClass class updateMethodBindingsTo: oldClass class binding].
- ifTrue:[oldClass becomeForward: newClass]
- ifFalse:[(Array with: oldClass with: oldClass class)
- elementsForwardIdentityTo:
- (Array with: newClass with: newClass class)].
Smalltalk garbageCollect.
"Warning: Read this before you even think about removing the GC. Yes, it slows us down. Quite heavily if you have a large image. However, there's no good and simple alternative here, since unfortunately, #become: does change class pointers. What happens is that after the above become all of the instances of the old class will have a class pointer identifying them as instances of newClass. If we get our hands on any of these instances we will break immediately since their expected instance layout (that of its class, e.g., newClass) will not match their actual instance layout (that of oldClass). And getting your hands on any of those instances is really simple - just reshaping one class two times in rapid succession will do it. Reflection techniques, interrupts, etc. will only add to this problem. In the case of Metaclass things get even worse since when we recompile the entire class hierarchy we will recompile both, Metaclass and its instances (and some of its instances will have the old and some the new layout).
The only easy solution to this problem would be to 'fix up' the class pointers of the old instances to point to the old class (using primitiveChangeClassTo:). But this won't work either - as we do a one-way become we would have to search the entire object memory for the oldClass and couldn't even clearly identify it unless we give it some 'special token' which sounds quite error-prone. If you really need to get rid of the GC here are some alternatives:
On the image level, one could create a copy of the oldClass before becoming it into the new class and, after becoming it, 'fix up' the old instances. That would certainly work but it sounds quite complex, as we need to make sure we're not breaking any of the superclass/subclass meta/non-meta class variants.
Alternatively, fix up #becomeForward on the VM-level to 'dump the source objects' of #become. This would be quite doable (just 'convert' them into a well known special class such as bitmap) yet it has problems if (accidentally or not) one of the objects in #become: appears on 'both sides of the fence' (right now, this will work ... in a way ... even though the consequences are unclear).
Another alternative is to provide a dedicated primitive for this (instead of using it implicitly in become) which would allow us to dump all the existing instances right here. This is equivalent to a more general primitiveChangeClassTo: and might be worthwhile but it would likely have to keep in mind the differences between bits and pointer thingies etc.
Since all of the alternatives seem rather complex and magical compared to a straight-forward GC it seems best to stick with the GC solution for now. If someone has a real need to fix this problem, that person will likely be motivated enough to check out the alternatives. Personally I'd probably go for #1 (copy the old class and remap the instances to it) since it's a solution that could be easily reverted from within the image if there's any problem with it."
] valueUnpreemptively.
!
Item was added:
+ ----- Method: ClassDescription>>updateMethodBindingsTo: (in category 'initialize-release') -----
+ updateMethodBindingsTo: aBinding
+ "ClassBuilder support for maintaining valid method bindings."
+ methodDict do: [:method| method methodClassAssociation: aBinding]!
Eliot Miranda uploaded a new version of Kernel to project The Trunk:
http://source.squeak.org/trunk/Kernel-eem.854.mcz
==================== Summary ====================
Name: Kernel-eem.854
Author: eem
Time: 27 May 2014, 11:17:11.701 am
UUID: 354e7db1-1a1c-426c-ae87-018e33f29c69
Ancestors: Kernel-nice.853
Fix the regression caused by Kernel-eem.847. Instead of
installing teh correct binding in compileAllFrom: add
behavior>>updateMethodBindingsTo: and invoke it from
ClassBuilder>>update:to:. This restores the failing
PureBehaviorTests>>testReshapeClass.
=============== Diff against Kernel-nice.853 ===============
Item was changed:
----- Method: Behavior>>compileAllFrom: (in category 'compiling') -----
compileAllFrom: oldClass
"Compile all the methods in the receiver's method dictionary.
+ This validates sourceCode and variable references and forces
+ all methods to use the current bytecode set"
+
- This validates sourceCode and variable references and forces
- all methods to use the current bytecode set"
- | envBinding binding |
"ar 7/10/1999: Use oldClass selectors not self selectors"
+ oldClass selectorsDo: [:sel | self recompile: sel from: oldClass]!
- oldClass selectorsDo: [:sel | self recompile: sel from: oldClass].
-
- "Ensure that we share a common binding after recompilation.
- This is so that ClassBuilder reshapes avoid creating new bindings
- for every method when recompiling a large class hierarchy.
- eem 5/2/2014 17:43: Further, if we're not yet in the environment
- (because we're about to be mutated into oldClass), use oldClass's
- binding, so as not to end up with the wrong binding post recompile."
- binding := self binding.
- (self name = oldClass name
- and: [(envBinding := self environment bindingOf: self name) ~= binding
- and: [envBinding = oldClass binding]]) ifTrue:
- [binding := envBinding].
- self methodsDo:[:m|
- m methodClassAssociation == binding
- ifFalse:[m methodClassAssociation: binding]].
- !
Item was changed:
----- Method: ClassBuilder>>update:to: (in category 'class mutation') -----
update: oldClass to: newClass
"Convert oldClass, all its instances and possibly its meta class into newClass, instances of newClass and possibly its meta class. The process is surprisingly simple in its implementation and surprisingly complex in its nuances and potentially bad side effects.
We can rely on two assumptions (which are critical):
#1: The method #updateInstancesFrom: will not create any lasting pointers to 'old' instances ('old' is quote on quote since #updateInstancesFrom: will do a become of the old vs. the new instances and therefore it will not create pointers to *new* instances before the #become: which are *old* afterwards)
#2: The non-preemptive execution of the critical piece of code guarantees that nobody can get a hold by 'other means' (such as process interruption and reflection) on the old instances.
Given the above two, we know that after #updateInstancesFrom: there are no pointer to any old instances. After the forwarding become there will be no pointers to the old class or meta class either. Meaning that if we throw in a nice fat GC at the end of the critical block, everything will be gone (but see the comment right there). There's no need to worry.
"
| meta |
meta := oldClass isMeta.
"Note: Everything from here on will run without the ability to get interrupted
to prevent any other process to create new instances of the old class."
[
"Note: The following removal may look somewhat obscure and needs an explanation. When we mutate the class hierarchy we create new classes for any existing subclass. So it may look as if we don't have to remove the old class from its superclass. However, at the top of the hierarchy (the first class we reshape) that superclass itself is not newly created so therefore it will hold both the oldClass and newClass in its (obsolete or not) subclasses. Since the #become: below will transparently replace the pointers to oldClass with newClass the superclass would have newClass in its subclasses TWICE. With rather unclear effects if we consider that we may convert the meta-class hierarchy itself (which is derived from the non-meta class hierarchy).
Due to this problem ALL classes are removed from their superclass just prior to converting them. Here, breaking the superclass/subclass invariant really doesn't matter since we will effectively remove the oldClass (become+GC) just a few lines below."
oldClass superclass removeSubclass: oldClass.
oldClass superclass removeObsoleteSubclass: oldClass.
"make sure that the VM cache is clean"
oldClass methodDict do: [:cm | cm flushCache].
"Convert the instances of oldClass into instances of newClass"
newClass updateInstancesFrom: oldClass.
meta
+ ifTrue:
+ [oldClass becomeForward: newClass.
+ oldClass updateMethodBindingsTo: oldClass binding]
+ ifFalse:
+ [{oldClass. oldClass class} elementsForwardIdentityTo: {newClass. newClass class}.
+ oldClass updateMethodBindingsTo: oldClass binding.
+ oldClass class updateMethodBindingsTo: oldClass class binding].
- ifTrue:[oldClass becomeForward: newClass]
- ifFalse:[(Array with: oldClass with: oldClass class)
- elementsForwardIdentityTo:
- (Array with: newClass with: newClass class)].
Smalltalk garbageCollect.
"Warning: Read this before you even think about removing the GC. Yes, it slows us down. Quite heavily if you have a large image. However, there's no good and simple alternative here, since unfortunately, #become: does change class pointers. What happens is that after the above become all of the instances of the old class will have a class pointer identifying them as instances of newClass. If we get our hands on any of these instances we will break immediately since their expected instance layout (that of its class, e.g., newClass) will not match their actual instance layout (that of oldClass). And getting your hands on any of those instances is really simple - just reshaping one class two times in rapid succession will do it. Reflection techniques, interrupts, etc. will only add to this problem. In the case of Metaclass things get even worse since when we recompile the entire class hierarchy we will recompile both, Metaclass and its instances (and some of its instances will have the old and some the new layout).
The only easy solution to this problem would be to 'fix up' the class pointers of the old instances to point to the old class (using primitiveChangeClassTo:). But this won't work either - as we do a one-way become we would have to search the entire object memory for the oldClass and couldn't even clearly identify it unless we give it some 'special token' which sounds quite error-prone. If you really need to get rid of the GC here are some alternatives:
On the image level, one could create a copy of the oldClass before becoming it into the new class and, after becoming it, 'fix up' the old instances. That would certainly work but it sounds quite complex, as we need to make sure we're not breaking any of the superclass/subclass meta/non-meta class variants.
Alternatively, fix up #becomeForward on the VM-level to 'dump the source objects' of #become. This would be quite doable (just 'convert' them into a well known special class such as bitmap) yet it has problems if (accidentally or not) one of the objects in #become: appears on 'both sides of the fence' (right now, this will work ... in a way ... even though the consequences are unclear).
Another alternative is to provide a dedicated primitive for this (instead of using it implicitly in become) which would allow us to dump all the existing instances right here. This is equivalent to a more general primitiveChangeClassTo: and might be worthwhile but it would likely have to keep in mind the differences between bits and pointer thingies etc.
Since all of the alternatives seem rather complex and magical compared to a straight-forward GC it seems best to stick with the GC solution for now. If someone has a real need to fix this problem, that person will likely be motivated enough to check out the alternatives. Personally I'd probably go for #1 (copy the old class and remap the instances to it) since it's a solution that could be easily reverted from within the image if there's any problem with it."
] valueUnpreemptively.
!
Item was added:
+ ----- Method: ClassDescription>>updateMethodBindingsTo: (in category 'initialize-release') -----
+ updateMethodBindingsTo: aBinding
+ "ClassBuilder support for maintaining valid method bindings."
+ methodDict do: [:method| method methodClassAssociation: aBinding]!
Eliot Miranda uploaded a new version of Kernel to project The Trunk:
http://source.squeak.org/trunk/Kernel-eem.854.mcz
==================== Summary ====================
Name: Kernel-eem.854
Author: eem
Time: 27 May 2014, 11:17:11.701 am
UUID: 354e7db1-1a1c-426c-ae87-018e33f29c69
Ancestors: Kernel-nice.853
Fix the regression caused by Kernel-eem.847. Instead of
installing teh correct binding in compileAllFrom: add
behavior>>updateMethodBindingsTo: and invoke it from
ClassBuilder>>update:to:. This restores the failing
PureBehaviorTests>>testReshapeClass.
=============== Diff against Kernel-nice.853 ===============
Item was changed:
----- Method: Behavior>>compileAllFrom: (in category 'compiling') -----
compileAllFrom: oldClass
"Compile all the methods in the receiver's method dictionary.
+ This validates sourceCode and variable references and forces
+ all methods to use the current bytecode set"
+
- This validates sourceCode and variable references and forces
- all methods to use the current bytecode set"
- | envBinding binding |
"ar 7/10/1999: Use oldClass selectors not self selectors"
+ oldClass selectorsDo: [:sel | self recompile: sel from: oldClass]!
- oldClass selectorsDo: [:sel | self recompile: sel from: oldClass].
-
- "Ensure that we share a common binding after recompilation.
- This is so that ClassBuilder reshapes avoid creating new bindings
- for every method when recompiling a large class hierarchy.
- eem 5/2/2014 17:43: Further, if we're not yet in the environment
- (because we're about to be mutated into oldClass), use oldClass's
- binding, so as not to end up with the wrong binding post recompile."
- binding := self binding.
- (self name = oldClass name
- and: [(envBinding := self environment bindingOf: self name) ~= binding
- and: [envBinding = oldClass binding]]) ifTrue:
- [binding := envBinding].
- self methodsDo:[:m|
- m methodClassAssociation == binding
- ifFalse:[m methodClassAssociation: binding]].
- !
Item was changed:
----- Method: ClassBuilder>>update:to: (in category 'class mutation') -----
update: oldClass to: newClass
"Convert oldClass, all its instances and possibly its meta class into newClass, instances of newClass and possibly its meta class. The process is surprisingly simple in its implementation and surprisingly complex in its nuances and potentially bad side effects.
We can rely on two assumptions (which are critical):
#1: The method #updateInstancesFrom: will not create any lasting pointers to 'old' instances ('old' is quote on quote since #updateInstancesFrom: will do a become of the old vs. the new instances and therefore it will not create pointers to *new* instances before the #become: which are *old* afterwards)
#2: The non-preemptive execution of the critical piece of code guarantees that nobody can get a hold by 'other means' (such as process interruption and reflection) on the old instances.
Given the above two, we know that after #updateInstancesFrom: there are no pointer to any old instances. After the forwarding become there will be no pointers to the old class or meta class either. Meaning that if we throw in a nice fat GC at the end of the critical block, everything will be gone (but see the comment right there). There's no need to worry.
"
| meta |
meta := oldClass isMeta.
"Note: Everything from here on will run without the ability to get interrupted
to prevent any other process to create new instances of the old class."
[
"Note: The following removal may look somewhat obscure and needs an explanation. When we mutate the class hierarchy we create new classes for any existing subclass. So it may look as if we don't have to remove the old class from its superclass. However, at the top of the hierarchy (the first class we reshape) that superclass itself is not newly created so therefore it will hold both the oldClass and newClass in its (obsolete or not) subclasses. Since the #become: below will transparently replace the pointers to oldClass with newClass the superclass would have newClass in its subclasses TWICE. With rather unclear effects if we consider that we may convert the meta-class hierarchy itself (which is derived from the non-meta class hierarchy).
Due to this problem ALL classes are removed from their superclass just prior to converting them. Here, breaking the superclass/subclass invariant really doesn't matter since we will effectively remove the oldClass (become+GC) just a few lines below."
oldClass superclass removeSubclass: oldClass.
oldClass superclass removeObsoleteSubclass: oldClass.
"make sure that the VM cache is clean"
oldClass methodDict do: [:cm | cm flushCache].
"Convert the instances of oldClass into instances of newClass"
newClass updateInstancesFrom: oldClass.
meta
+ ifTrue:
+ [oldClass becomeForward: newClass.
+ oldClass updateMethodBindingsTo: oldClass binding]
+ ifFalse:
+ [{oldClass. oldClass class} elementsForwardIdentityTo: {newClass. newClass class}.
+ oldClass updateMethodBindingsTo: oldClass binding.
+ oldClass class updateMethodBindingsTo: oldClass class binding].
- ifTrue:[oldClass becomeForward: newClass]
- ifFalse:[(Array with: oldClass with: oldClass class)
- elementsForwardIdentityTo:
- (Array with: newClass with: newClass class)].
Smalltalk garbageCollect.
"Warning: Read this before you even think about removing the GC. Yes, it slows us down. Quite heavily if you have a large image. However, there's no good and simple alternative here, since unfortunately, #become: does change class pointers. What happens is that after the above become all of the instances of the old class will have a class pointer identifying them as instances of newClass. If we get our hands on any of these instances we will break immediately since their expected instance layout (that of its class, e.g., newClass) will not match their actual instance layout (that of oldClass). And getting your hands on any of those instances is really simple - just reshaping one class two times in rapid succession will do it. Reflection techniques, interrupts, etc. will only add to this problem. In the case of Metaclass things get even worse since when we recompile the entire class hierarchy we will recompile both, Metaclass and its instances (and some of its instances will have the old and some the new layout).
The only easy solution to this problem would be to 'fix up' the class pointers of the old instances to point to the old class (using primitiveChangeClassTo:). But this won't work either - as we do a one-way become we would have to search the entire object memory for the oldClass and couldn't even clearly identify it unless we give it some 'special token' which sounds quite error-prone. If you really need to get rid of the GC here are some alternatives:
On the image level, one could create a copy of the oldClass before becoming it into the new class and, after becoming it, 'fix up' the old instances. That would certainly work but it sounds quite complex, as we need to make sure we're not breaking any of the superclass/subclass meta/non-meta class variants.
Alternatively, fix up #becomeForward on the VM-level to 'dump the source objects' of #become. This would be quite doable (just 'convert' them into a well known special class such as bitmap) yet it has problems if (accidentally or not) one of the objects in #become: appears on 'both sides of the fence' (right now, this will work ... in a way ... even though the consequences are unclear).
Another alternative is to provide a dedicated primitive for this (instead of using it implicitly in become) which would allow us to dump all the existing instances right here. This is equivalent to a more general primitiveChangeClassTo: and might be worthwhile but it would likely have to keep in mind the differences between bits and pointer thingies etc.
Since all of the alternatives seem rather complex and magical compared to a straight-forward GC it seems best to stick with the GC solution for now. If someone has a real need to fix this problem, that person will likely be motivated enough to check out the alternatives. Personally I'd probably go for #1 (copy the old class and remap the instances to it) since it's a solution that could be easily reverted from within the image if there's any problem with it."
] valueUnpreemptively.
!
Item was added:
+ ----- Method: ClassDescription>>updateMethodBindingsTo: (in category 'initialize-release') -----
+ updateMethodBindingsTo: aBinding
+ "ClassBuilder support for maintaining valid method bindings."
+ methodDict do: [:method| method methodClassAssociation: aBinding]!