[PATCH] Rewrite object mutation

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[PATCH] Rewrite object mutation

Paolo Bonzini
This patch rewrites object mutation, simplifying it greatly (and fixing
bugs) by telling the mutation methods about what the new superclass will
be.  This is a really really old part of GNU Smalltalk, even predating
my maintainership; apparently it was mostly untested (it was part of the
Blox GUI in 1.1.5) and it is being exercised more because of the new
syntax and because of less common features being used---in this case,
class instance variables.

Anyway, when I modified it to fix mutate.st's XFAIL I broke it in new
interesting ways.  The changes to mutate.st are regression tests for
class mutation, so you can see what the bugs were about.

Paolo

2007-11-20  Paolo Bonzini  <[hidden email]>

        * kernel/Behavior.st: Partially undo change from
        #updateInstanceVars:shape: to
        #updateInstanceVars:numInherited:shape:.
        Add back #updateInstanceVars:shape: and add new
        #updateInstanceVars:superclass:shape:.  Return true from
        #inheritsFrom: if passed nil.  Use new keyword argument to
        include superclass variables in instVarMap.  Reverse direction of
        instVarMap.  Simplify creation of subclasses' instance variable
        array. Add here #mutate:via: (taken from kernel/Object.st)
        and use it instead of #mutate:startingAt:newClass:.
        * kernel/Object.st: Remove #mutate:startingAt:newClass:.
        * kernel/Metaclass.st: Rename "superclass" argument to
        "theSuperclass" or "newSuperclass".  Call
        #updateInstanceVars:superclass:shape:.

        * tests/mutate.st: Add minimal testcase for GTK+ loading failure.
        Test that class-instance variables are copied around correctly.
        Test that moving up the hierarchy preserves instance variables.


--- orig/kernel/Behavior.st
+++ mod/kernel/Behavior.st
@@ -54,10 +54,7 @@ method dictionary, and iterating over th
     ifTrue: [{symbol}]
     ifFalse: [instanceVariables copyWith: symbol].
  duplicated := self superclass allInstVarNames includes: symbol.
- self
-    updateInstanceVars: newInstanceVariables
-    numInherited: self superclass instSize
-    shape: self shape.
+ self updateInstanceVars: newInstanceVariables shape: self shape.
  duplicated ifTrue: [self compileAll].
  self compileAllSubclasses
     ]
@@ -77,10 +74,7 @@ method dictionary, and iterating over th
     to: index
     with: #().
  self
-    updateInstanceVars: newInstanceVariables
-    numInherited: self superclass instSize
-    shape: self shape.
- self
+    updateInstanceVars: newInstanceVariables shape: self shape;
     compileAll;
     compileAllSubclasses
     ]
@@ -98,10 +92,7 @@ method dictionary, and iterating over th
         "If instance variables change, update  instance variables and
          instance spec of the class and all its subclasses"
         variableArray = oldInstVarNames ifTrue: [^self].
-        self
-    updateInstanceVars: variableArray
-    numInherited: self superclass instSize
-    shape: self shape.
+        self updateInstanceVars: variableArray shape: self shape.
 
         "If no variable has been removed, no need to recompile"
         (oldInstVarNames allSatisfy: [:each | variableArray includes: each])
@@ -976,8 +967,9 @@ method dictionary, and iterating over th
 
  <category: 'testing the class hierarchy'>
  | sc |
+ aClass isNil ifTrue: [^true].
+
  sc := self.
-
  [sc := sc superclass.
  sc isNil] whileFalse: [sc == aClass ifTrue: [^true]].
  ^false
@@ -1015,10 +1007,7 @@ method dictionary, and iterating over th
  shape = #inherit ifTrue: [realShape := self superclass shape].
  self shape == realShape ifTrue: [^false].
  realShape isNil ifTrue: [
-            self
-        updateInstanceVars: self allInstVarNames
-        numInherited: self superclass instSize
-        shape: nil ].
+            self updateInstanceVars: self allInstVarNames shape: nil ].
  self isVariable
     ifTrue:
  [SystemExceptions.MutationError
@@ -1340,24 +1329,44 @@ method dictionary, and iterating over th
  ^true
     ]
 
-    updateInstanceVars: variableArray numInherited: numInherited shape: shape [
+    updateInstanceVars: variableArray shape: shape [
  "Update instance variables and instance spec of the class and all
- its subclasses"
+ its subclasses.  variableArray lists the new variables, including
+ inherited ones."
+ ^self
+    updateInstanceVars: variableArray
+    superclass: self superclass
+    shape: shape
+    ]
+
+    updateInstanceVars: variableArray superclass: newSuper shape: shape [
+ "Update instance variables and instance spec of the class and all
+ its subclasses.  variableArray lists the new variables, including
+ those inherited from newSuper."
 
  <category: 'private'>
- | instVarMap startOfInstanceVars endOfInstanceVars newInstanceVars oldInstVars oldClass instances |
- startOfInstanceVars := numInherited + 1.
- endOfInstanceVars := self instSize.
- newInstanceVars := variableArray copyFrom: startOfInstanceVars
-    to: variableArray size.
+ | instVarMap newInstVars oldInstVars oldClass instances oldSuper |
+
+ "Find a common superclass."
+ oldSuper := self superclass.
+ newSuper == oldSuper ifFalse: [
+    [ newSuper includesBehavior: oldSuper ] whileFalse: [
+        oldSuper := oldSuper superclass ] ].
+
+ "Make map for inherited instance variables."
  oldInstVars := self allInstVarNames.
- instVarMap := Array new: newInstanceVars size.
- startOfInstanceVars to: endOfInstanceVars
-    do:
- [:i |
- | map |
- map := newInstanceVars findLast: [:each | each = (oldInstVars at: i)].
- map > 0 ifTrue: [instVarMap at: map put: i]].
+ instVarMap := Array new: oldInstVars size.
+ 1 to: oldSuper instSize do: [ :i |
+    instVarMap at: i put: i ].
+
+ "Make map for this class's instance variables."
+ newInstVars := variableArray copyFrom: newSuper instSize + 1.
+ oldInstVars
+    from: oldSuper instSize + 1 to: oldInstVars size
+    keysAndValuesDo: [ :index :var |
+        | map |
+        map := newInstVars findLast: [:each | each = var].
+        map > 0 ifTrue: [instVarMap at: index put: map + newSuper instSize]].
 
  "Fix up all subclasses."
  self allSubclassesDo:
@@ -1367,21 +1376,16 @@ method dictionary, and iterating over th
  oldClass superclass: sc.
  instances := sc allInstances.
  instances do: [:each | each changeClassTo: oldClass].
- iv := sc allInstVarNames
-    copyReplaceFrom: startOfInstanceVars
-    to: endOfInstanceVars
-    with: newInstanceVars.
+ iv := variableArray, (sc allInstVarNames
+    copyFrom: oldInstVars size + 1
+    to: sc allInstVarNames size).
  sc setInstanceVariables: iv.
  sc setInstanceSpec: sc shape instVars: sc allInstVarNames size.
 
  "Mutate all instances of the class to conform to new memory model
  of the class."
- instances do:
- [:each |
- each
-    mutate: instVarMap
-    startAt: startOfInstanceVars
-    newClass: sc]].
+ instances do: [:each |
+    sc mutate: each via: instVarMap]].
 
  "Now update this class' instance vars"
  oldClass := Behavior new.
@@ -1390,12 +1394,36 @@ method dictionary, and iterating over th
  instances do: [:each | each changeClassTo: oldClass].
  self setInstanceVariables: variableArray.
  self setInstanceSpec: shape instVars: variableArray size.
- instances do:
- [:each |
- each
-    mutate: instVarMap
-    startAt: startOfInstanceVars
-    newClass: self]
+ instances do: [:each |
+    self mutate: each via: instVarMap]
+    ]
+
+    mutate: object via: instVarMap [
+        "Private - Mutate object to a new class representation. instVarMap
+         maps from old instVarAt: indices to new instVarAt:put: indices.
+         start is the first instance variable to change."
+
+        <category: 'private'>
+        | aCopy mappedValue end adjustment |
+        aCopy := object class isVariable
+                    ifTrue: [self basicNew: object basicSize]
+                    ifFalse: [self basicNew].
+
+        "Copy old instance variables to their new positions using instVarMap."
+        1 to: instVarMap size do: [:i |
+            mappedValue := instVarMap at: i.
+            mappedValue notNil
+                ifTrue: [aCopy instVarAt: mappedValue put: (object instVarAt: i)]].
+
+        "If mutating a subclass, instVarMap is smaller than `object class instSize';
+         in this case, everything after it must be copied."
+        adjustment := self instSize - object class instSize.
+        instVarMap size + 1 to: object class instSize
+            do: [:i | aCopy instVarAt: i + adjustment put: (object instVarAt: i)].
+
+        "Copy the indexed variables, if any."
+        1 to: object basicSize do: [:i | aCopy basicAt: i put: (object basicAt: i)].
+        ^object become: aCopy
     ]
 
     isBehavior [


--- orig/kernel/Metaclass.st
+++ mod/kernel/Metaclass.st
@@ -161,15 +161,15 @@ it should be...the Smalltalk metaclass s
  ^self instanceClass pragmaHandlerFor: aSymbol
     ]
 
-    name: className environment: aNamespace subclassOf: superclass [
+    name: className environment: aNamespace subclassOf: theSuperclass [
  "Private - create a full featured class and install it, or change the
  superclass or shape of an existing one; instance variable names,
  class variable names and pool dictionaries are left untouched."
 
  <category: 'basic'>
  | aClass variableArray |
- variableArray := superclass notNil
-    ifTrue: [superclass allInstVarNames]
+ variableArray := theSuperclass notNil
+    ifTrue: [theSuperclass allInstVarNames]
     ifFalse: [#()].
 
  "Look for an existing metaclass"
@@ -179,7 +179,7 @@ it should be...the Smalltalk metaclass s
  [^self
     newMeta: className
     environment: aNamespace
-    subclassOf: superclass
+    subclassOf: theSuperclass
     instanceVariableArray: variableArray
     shape: nil
     classPool: BindingDictionary new
@@ -189,7 +189,7 @@ it should be...the Smalltalk metaclass s
  ^self
     name: className
     environment: aNamespace
-    subclassOf: superclass
+    subclassOf: theSuperclass
     instanceVariableArray: variableArray
     shape: aClass shape
     classPool: aClass classPool
@@ -197,7 +197,7 @@ it should be...the Smalltalk metaclass s
     category: aClass category
     ]
 
-    name: newName environment: aNamespace subclassOf: superclass instanceVariableNames: stringOfInstVarNames shape: shape classVariableNames: stringOfClassVarNames poolDictionaries: stringOfPoolNames category: categoryName [
+    name: newName environment: aNamespace subclassOf: theSuperclass instanceVariableNames: stringOfInstVarNames shape: shape classVariableNames: stringOfClassVarNames poolDictionaries: stringOfPoolNames category: categoryName [
  "Private - parse the instance and class variables, and the pool
  dictionaries, then create the class."
 
@@ -206,8 +206,8 @@ it should be...the Smalltalk metaclass s
 
  | variableArray classVarDict sharedPoolNames |
  variableArray := self parseInstanceVariableString: stringOfInstVarNames.
- variableArray := superclass notNil
-    ifTrue: [superclass allInstVarNames , variableArray]
+ variableArray := theSuperclass notNil
+    ifTrue: [theSuperclass allInstVarNames , variableArray]
     ifFalse: [variableArray].
  classVarDict := self parse: stringOfClassVarNames
     toDictionary: BindingDictionary new.
@@ -215,7 +215,7 @@ it should be...the Smalltalk metaclass s
  ^self
     name: newName asSymbol
     environment: aNamespace
-    subclassOf: superclass
+    subclassOf: theSuperclass
     instanceVariableArray: variableArray
     shape: shape
     classPool: classVarDict
@@ -223,7 +223,7 @@ it should be...the Smalltalk metaclass s
     category: categoryName
     ]
 
-    name: className environment: aNamespace subclassOf: superclass instanceVariableArray: variableArray shape: shape classPool: classVarDict poolDictionaries: sharedPoolNames category: categoryName [
+    name: className environment: aNamespace subclassOf: newSuperclass instanceVariableArray: variableArray shape: shape classPool: classVarDict poolDictionaries: sharedPoolNames category: categoryName [
  "Private - create a full featured class and install it, or change an
  existing one"
 
@@ -240,7 +240,7 @@ it should be...the Smalltalk metaclass s
  [^self
     newMeta: className
     environment: aNamespace
-    subclassOf: superclass
+    subclassOf: newSuperclass
     instanceVariableArray: variableArray
     shape: realShape
     classPool: classVarDict
@@ -252,7 +252,7 @@ it should be...the Smalltalk metaclass s
     ifFalse:
  [SystemExceptions.MutationError
     signal: 'Cannot change shape of variable class']].
- superclass isUntrusted & self class isUntrusted not
+ newSuperclass isUntrusted & self class isUntrusted not
     ifTrue:
  [SystemExceptions.MutationError
     signal: 'Cannot move trusted class below untrusted superclass'].
@@ -278,7 +278,7 @@ it should be...the Smalltalk metaclass s
  [aClass instanceCount > 0 ifTrue: [ObjectMemory globalGarbageCollect].
  aClass
     updateInstanceVars: variableArray
-    numInherited: superclass instSize
+    superclass: newSuperclass
     shape: realShape].
 
  "Now add/remove pool dictionaries.  FIXME: They may affect name binding,
@@ -296,29 +296,29 @@ it should be...the Smalltalk metaclass s
     ifFalse:
  [aClass removeSharedPool: dict.
  needToRecompileMetaclasses := true]]].
- aClass superclass ~~ superclass
+ aClass superclass ~~ newSuperclass
     ifTrue:
  ["Mutate the class if the set of class-instance variables changes."
 
- self superclass allInstVarNames ~= superclass class allInstVarNames
+ self superclass allInstVarNames ~= newSuperclass class allInstVarNames
     ifTrue:
  [aClass class
     updateInstanceVars:
- superclass class allInstVarNames,
+ newSuperclass class allInstVarNames,
  aClass class instVarNames
-    numInherited: superclass class instSize
+    superclass: newSuperclass class
     shape: aClass class shape].
 
  "Fix references between classes..."
  aClass superclass removeSubclass: aClass.
- superclass addSubclass: aClass.
- aClass superclass: superclass.
+ newSuperclass addSubclass: aClass.
+ aClass superclass: newSuperclass.
  needToRecompileClasses := true.
 
  "...and between metaclasses..."
  self superclass removeSubclass: self.
- superclass class addSubclass: self.
- self superclass: superclass class.
+ newSuperclass class addSubclass: self.
+ self superclass: newSuperclass class.
  needToRecompileMetaclasses := true].
  aClass category: categoryName.
 
@@ -340,7 +340,7 @@ it should be...the Smalltalk metaclass s
  ^aClass
     ]
 
-    newMeta: className environment: aNamespace subclassOf: superclass instanceVariableArray: arrayOfInstVarNames shape: shape classPool: classVarDict poolDictionaries: sharedPoolNames category: categoryName [
+    newMeta: className environment: aNamespace subclassOf: theSuperclass instanceVariableArray: arrayOfInstVarNames shape: shape classPool: classVarDict poolDictionaries: sharedPoolNames category: categoryName [
  "Private - create a full featured class and install it"
 
  <category: 'basic'>
@@ -349,17 +349,17 @@ it should be...the Smalltalk metaclass s
  classVarDict environment: aClass.
  instanceClass := aClass.
  aNamespace at: className put: aClass.
- superclass isNil ifFalse: [superclass addSubclass: aClass].
+ theSuperclass isNil ifFalse: [theSuperclass addSubclass: aClass].
  Behavior flushCache.
  ^aClass
-    superclass: superclass;
+    superclass: theSuperclass;
     setName: className;
     setEnvironment: aNamespace;
     setInstanceVariables: arrayOfInstVarNames;
     setInstanceSpec: shape instVars: arrayOfInstVarNames size;
     setClassVariables: classVarDict;
     setSharedPools: sharedPoolNames;
-    makeUntrusted: superclass isUntrusted;
+    makeUntrusted: theSuperclass isUntrusted;
     category: categoryName;
     yourself
     ]
@@ -417,10 +417,10 @@ it should be...the Smalltalk metaclass s
  aStream nextPutAll: ' class'
     ]
 
-    initMetaclass: superclass [
+    initMetaclass: theSuperclass [
  <category: 'private'>
- instanceVariables := superclass allInstVarNames.
- instanceSpec := superclass instanceSpec
+ instanceVariables := theSuperclass allInstVarNames.
+ instanceSpec := theSuperclass instanceSpec
     ]
 
     parsePools: aString in: aNamespace [


--- orig/kernel/Object.st
+++ mod/kernel/Object.st
@@ -744,37 +744,6 @@ All classes in the system are subclasses
  ^name
     ]
 
-    mutate: instVarMap startAt: start newClass: class [
- "Private - Mutate object to a new class representation. instVarMap
- maps between old instVarAt: indices and new instVarAt:put: indices.
- start is the first instance variable to change."
-
- <category: 'private'>
- | aCopy mappedValue end adjustment |
- adjustment := self class instSize - class instSize.
- aCopy := self class isVariable
-    ifTrue: [class basicNew: self basicSize]
-    ifFalse: [class basicNew].
- end := instVarMap size + start - 1.
-
- "Copy the instance variables, if any"
- 1 to: start - 1 do: [:i | aCopy instVarAt: i put: (self instVarAt: i)].
-
- "Copy old instance variables to their new positions using instVarMap"
- start to: end
-    do:
- [:i |
- mappedValue := instVarMap at: i - start + 1.
- mappedValue notNil
-    ifTrue: [aCopy instVarAt: i put: (self instVarAt: mappedValue)]].
- end + 1 to: class instSize
-    do: [:i | aCopy instVarAt: i put: (self instVarAt: i + adjustment)].
-
- "Copy the indexed variables, if any."
- 1 to: self basicSize do: [:i | aCopy basicAt: i put: (self basicAt: i)].
- ^self become: aCopy
-    ]
-
     allOwners [
  "Return an Array of Objects that point to the receiver."
 


--- orig/tests/mutate.ok
+++ mod/tests/mutate.ok
@@ -99,6 +99,11 @@ returned value is true
 Recompiling classes...
 
 Execution begins...
+Smalltalk
+returned value is SystemDictionary new: 512 "<0>"
+Recompiling classes...
+
+Execution begins...
 (#a #b #c )
 returned value is Array new: 3 "<0>"
 
@@ -109,3 +114,7 @@ returned value is Array new: 4 "<0>"
 Execution begins...
 (#a #d )
 returned value is Array new: 2 "<0>"
+
+Execution begins...
+Recompiling classes...
+returned value is 'abc'


--- orig/tests/mutate.st
+++ mod/tests/mutate.st
@@ -124,6 +124,13 @@ Eval [ (C shape -> C classPool keys asAr
 Eval [ C class compile: 'foo [ ^MutationError ]' ]
 Eval [ C foo == SystemExceptions.MutationError ]
 
+"Test mutating the class when the new superclass has additional class-instance
+ variables"
+CObject subclass: CFoo [ ]
+CStruct subclass: CFoo [ ]
+Eval [ CFoo environment printNl ]
+
+"Test adding variables with multiple |...| blocks or with extend."
 Object subclass: Foo [ | a | ]
 Foo subclass: Bar [ | xyz | ]
 Foo subclass: Bar [ | b | | c | ]
@@ -131,3 +138,20 @@ Eval [ Bar allInstVarNames printNl ]
 Foo extend [ | d | ]
 Eval [ Bar allInstVarNames printNl ]
 Eval [ Foo allInstVarNames printNl ]
+
+"Test moving to an upper superclass, but preserving instance variables
+ because they are specified in the instanceVariableNames: keyword."
+Association subclass: Blah [ ]
+Eval [
+    | blah |
+    blah := Blah new.
+    blah value: 'abc'.
+    Object
+ subclass: #Blah
+ instanceVariableNames: 'key value'
+ classVariableNames: ''
+ poolDictionaries: ''
+ category: ''.
+
+    blah instVarAt: 2
+]





_______________________________________________
help-smalltalk mailing list
[hidden email]
http://lists.gnu.org/mailman/listinfo/help-smalltalk