Here is a draft.
-----------
How to make a new RC of the OSVM:
1. Tag specific commit with its UTC timestamp, e.g., 202205110711 for May 11, 2022 at 7:11
2. Run all "Build for*" workflows manually for that tag
3. Verify that a pre-release was created for that tag, containing all build artifacts
How to make a new release of the OSVM:
1. Do a new RC (see above)
2. Edit that GitHub pre-release to be an actual release
3. Change the description to have some release notes on the changes
How to make use of OSVM-RC in squeak-app bundles:
1. Maybe do a new RC (see above)
2. In any branch's bundle.yml, set VM_RC_TAG in prepare-bundles job to the RC tag
How to make use of a new OSVM release in squeak-app bundles:
1. Maybe make a new VM release (see above)
2. Update the files in http://files.squeak.org/base/ for the Squeak versions of your choice
3. :warning: Watch out for minor differences between OSVM packaging and what vm-*.zip expects. For example, Linux has an extra indirection that needs to be removed. And those macOS .dmg packages have to be converted to .zip for squeak-app.
--
Reply to this email directly or view it on GitHub:
https://github.com/OpenSmalltalk/opensmalltalk-vm/issues/637
You are receiving this because you are subscribed to this thread.
Message ID: <OpenSmalltalk/opensmalltalk-vm/issues/637(a)github.com>
I noticed that recent VMs leak a few hundred kilobytes of memory every 3-6 seconds. It happens even if you just start VM and do nothing with the image.
I tested it with various 64-bit Linux VMs. There seems to be no upper limit on the leaked memory. The leak happens even with the -memory option set. The size of the object memory is not affected by the leak, saved images remain small and the image does not see the increase in memory usage.
Affected VMs include: 5.0-202206021410, 5.0-202205110711, 5.0-202204190959
Unaffected VMs include: 5.0-202112022203 and 5.0-202112201228
The 5.0-202206021410 assert and debug VMs leak the memory as well.
--
Reply to this email directly or view it on GitHub:
https://github.com/OpenSmalltalk/opensmalltalk-vm/issues/642
You are receiving this because you are subscribed to this thread.
Message ID: <OpenSmalltalk/opensmalltalk-vm/issues/642(a)github.com>
Hi all!
We just released the next version of the OpenSmalltalk VM.
Please find the binaries here:
https://github.com/OpenSmalltalk/opensmalltalk-vm/releases/tag/202205110711 [https://github.com/OpenSmalltalk/opensmalltalk-vm/releases/tag/202205110711]
(see VMMaker.oscog-mt.3184 and update.oscog-mt.6.mcm)
That version will be used in the upcoming Squeak 6.0 and also updated
bundles for Squeak 5.3. And probably in upcoming Cuis releases. :-)
Here is an attempt of a change log (since 2020):
- Adds ARMv8/Aarch64/ARM64 JIT incl. support for Apple M1
- Adds "fast C primitives" via #FastCPrimitiveFlag
- Adds support for catching exceptions in FFI callouts
- Adds #primitiveScreenScaleFactor (for DPI-aware images)
- Adds primitives 568 and 578 complementing 88 (primitiveSuspend)
- Adds #primitiveMultipleBytecodeSetsActive to update image format for SistaV1
- Adds VectorEnginePlugin
- Fixes regressions in ARMv6 support
- Fixes performance regressions of -metal and -opengl backends on macOS
- Fixes -core-graphics backend on macOS
- Fixes Retina scaling on macOS, i.e., support "backing scale factor"
- Fixes primitive 126 to fail on graphics backends w/o composition buffer
- Fixes regressions in vm-display-fbdev on Linux
- Fixes time sync (e.g., for DST) on Windows
- Fixes UDP binding on Windows
I am sure that I forgot something especially in plugin code. Please expand on this.
BIG THANKS to everybody who has worked on this release! Personally, I would like
to thank Eliot, who is a great software architect who keeps on making the OSVM
faster with every commit. Thank you!
Best,
Marcel (on behalf of the OSVM core dev team)
This I what I get in the VS2022 debugger. Of course, at the moment I do not understand the code there, but maybe hopefully Eliot can see something interesting in it. I leave VS open and look tomorrow at the code, maybe I understand a bit of it :-)
Seems there is something wrong with the forward pointers. I assume the longAt(referent) fails? I guess it is a macro, but VS could not find the definition.
########## Code area - the --> line is failing with access violation ###########
/* begin literalCountOfMethodHeader: */
assert((((header) & 7) == 1));
numLiterals = ((header >> 3)) & AlternateHeaderNumLiteralsMask;
numSlots = numLiterals + LiteralStart;
l9: /* end numStrongSlotsOf:format:ephemeronInactiveIf: */;
for (i = 0; i < numSlots; i += 1) {
referent = longAt((referrer + BaseHeaderSize) + (((sqInt)((usqInt)(i) << (shiftForWord())))));
if ((!(referent & (tagMask())))) {
/* a forwarding pointer could be because of become: or scavenging. */
--> if ((!((longAt(referent)) & ((classIndexMask()) - (isForwardedObjectClassIndexPun()))))) {
/* begin followForwarded: */
assert(isUnambiguouslyForwarder(referent));
/* begin fetchPointer:ofMaybeForwardedObject: */
referent1 = longAt((referent + BaseHeaderSize) + (0U << (shiftForWord())));
while (((!(referent1 & (tagMask()))))
&& ((!((longAt(referent1)) & ((classIndexMask()) - (isForwardedObjectClassIndexPun())))))) {
/* begin fetchPointer:ofMaybeForwardedObject: */
referent1 = longAt((referent1 + BaseHeaderSize) + (0U << (shiftForWord())));
}
referent = referent1;
########## Call stack ############
Squeak.exe!scavengeReferentsOf(__int64 referrer) Zeile 42680
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (42680)
Squeak.exe!scavengeRememberedSetStartingAt(__int64 n) Zeile 42760
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (42760)
Squeak.exe!scavengeLoop() Zeile 42542
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (42542)
Squeak.exe!doScavenge(__int64 tenuringCriterion) Zeile 47599
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (47599)
Squeak.exe!scavengingGCTenuringIf(__int64 tenuringCriterion) Zeile 57959
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (57959)
Squeak.exe!sufficientSpaceAfterGC(__int64 numBytes) Zeile 58691
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (58691)
Squeak.exe!checkForEventsMayContextSwitch(__int64 mayContextSwitch) Zeile 62762
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (62762)
Squeak.exe!handleStackOverflowOrEventAllowContextSwitch(__int64 mayContextSwitch) Zeile 66037
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (66037)
Squeak.exe!ceStackOverflow(__int64 contextSwitchIfNotNil) Zeile 15593
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (15593)
[Externer Code]
Squeak.exe!ioInitHeartbeat() Zeile 420
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\platforms\win32\vm\sqWin32Heartbeat.c (420)
Squeak.exe!interpret() Zeile 2875
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\src\spur64.cog\cointerp.c (2875)
Squeak.exe!sqMain(int argc, char * * argv) Zeile 1761
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\platforms\win32\vm\sqWin32Main.c (1761)
Squeak.exe!WinMain(HINSTANCE__ * hInst, HINSTANCE__ * hPrevInstance, char * lpCmdLine, int nCmdShow) Zeile 1851
unter C:\Users\joerg\Persoenlich\Entwicklung\Squeak\vmssource\trunk\platforms\win32\vm\sqWin32Main.c (1851)
[Externer Code]
########## Locals ############
classFormat 662660168776 __int64
contextSize 72198296718755736 __int64
fmt 2 __int64
foundNewReferentOrIsWeakling 0 __int64
header 140698790658008 __int64
header1 140698758633160 __int64
i 37029628 __int64
newLocation 811789632 __int64
numLiterals 64 __int64
numSlots 167772160 __int64
numSlots1 255 unsigned __int64
numSlots2 167772160 unsigned __int64
objOop1 72057594139401638 __int64
referent 139599658561184 __int64
referent1 0 __int64
referrer 140700938141704 __int64
sp 72058702004626295 __int64
############## Second try ##################
I needed to restart it again, here are my new local values in debugger
classFormat 973468582328 __int64
contextSize 72198310804720808 __int64
fmt 2 __int64
foundNewReferentOrIsWeakling 0 __int64
header 140699864399832 __int64
header1 140698758633160 __int64
i 81749860 __int64
newLocation 2070121416 __int64
numLiterals 64 __int64
numSlots 335544320 __int64
numSlots1 255 unsigned __int64
numSlots2 335544320 unsigned __int64
objOop1 72057594296693111 __int64
referent 139620721982368 __int64
referent1 0 __int64
referrer 140711869546504 __int64
sp 72058702004626295 __int64
numSlots seems to me very wrong. If I do some calculations I get also not the same value that the debugger says me. Here is the code
numLiterals = ((header >> 3)) & AlternateHeaderNumLiteralsMask;
numSlots = numLiterals + LiteralStart;
AlternateHeaderNumLiteralsMask seems to be 0x7fff
LiteralStart seems to be 1
For me is:
numLiterals = ((140699864399832 >> 3)) & 0x7fff = 32763 —> does not match the debugger local, where is the 64 coming from
Seems to me somebody has overridden already the „header“ variable, which seems to be wrong. Could it be that some other thread is writing in the wrong memory area and override my values?
Jörg
########## System Information ##############
Gerätename timemachine
Prozessor AMD Ryzen 9 3900X 12-Core Processor 3.80 GHz
Installierter RAM 32,0 GB
Geräte-ID F6FA897B-DDB1-44D6-9BF3-8BD1110AA754
Produkt-ID 00326-10048-08575-AA867
Systemtyp 64-Bit-Betriebssystem, x64-basierter Prozessor
Stift- und Toucheingabe Für diese Anzeige ist keine Stift- oder Toucheingabe verfügbar.
Edition Windows 10 Home
Version 20H2
Installiert am 23.03.2021
Betriebssystembuild 19042.1526
Leistung Windows Feature Experience Pack 120.2212.4170.0
Visual Studio 2022
win64x64\squeak.cog.spur
VM version ??? I ask Eliot how i can see that on my current local github clone and post it later
############# Code for reproducation ##############
Simply execute the following Smalltalk code. Maybe you need to run it multiple times, for me the crash happens sometimes at the 3rd or 4th try.
| oc |
oc := OrderedCollection new.
400000000 timesRepeat: [oc add: Object new]
--
Reply to this email directly or view it on GitHub:
https://github.com/OpenSmalltalk/opensmalltalk-vm/issues/614
You are receiving this because you are subscribed to this thread.
Message ID: <OpenSmalltalk/opensmalltalk-vm/issues/614(a)github.com>
Tom Braun uploaded a new version of VMMaker to project VM Maker:
http://source.squeak.org/VMMaker/VMMaker.oscog.seperateMarking-WoC.3257.mcz
==================== Summary ====================
Name: VMMaker.oscog.seperateMarking-WoC.3257
Author: WoC
Time: 31 August 2022, 9:00:41.089802 pm
UUID: d53709dc-bdaf-487e-bb8b-e47510bf5cc6
Ancestors: VMMaker.oscog.seperateMarking-WoC.3256
- small refactoring in Compactor
- mark forwarders if neccessary so they get not garbage collected early
=============== Diff against VMMaker.oscog.seperateMarking-WoC.3256 ===============
Item was changed:
+ ----- Method: SpurGarbageCollector>>initialize (in category 'initialize-release') -----
- ----- Method: SpurGarbageCollector>>initialize (in category 'nil') -----
initialize
allocatorShouldAllocateBlack := false!
Item was added:
+ ----- Method: SpurGarbageCollector>>maybeModifyForwarder: (in category 'object creation barriers') -----
+ maybeModifyForwarder: objOop
+
+ <doNotGenerate>!
Item was changed:
+ ----- Method: SpurGarbageCollector>>maybeModifyGCFlagsOf: (in category 'object creation barriers') -----
- ----- Method: SpurGarbageCollector>>maybeModifyGCFlagsOf: (in category 'as yet unclassified') -----
maybeModifyGCFlagsOf: objOop
<doNotGenerate>!
Item was added:
+ ----- Method: SpurIncrementalCompactor>>cannotBeCompacted: (in category 'as yet unclassified') -----
+ cannotBeCompacted: segInfo
+
+ ^ (self isSegmentBeingCompacted: segInfo) or: [segInfo containsPinned or: [manager segmentManager isEmptySegment: segInfo]]!
Item was changed:
----- Method: SpurIncrementalCompactor>>compactSegment:freeStart:segIndex: (in category 'incremental compaction') -----
compactSegment: segInfo freeStart: initialFreeStart segIndex: segIndex
<var: 'segInfo' type: #'SpurSegmentInfo *'>
+ | currentEntity fillStart bytesToCopy bridge |
- | currentEntity fillStart bytesToCopy bridge copy |
fillStart := initialFreeStart.
bridge := manager segmentManager bridgeFor: segInfo.
currentEntity := manager objectStartingAt: segInfo segStart.
self deny: segIndex = 0. "Cannot compact seg 0"
[self oop: currentEntity isLessThan: bridge] whileTrue:
[(manager isFreeObject: currentEntity)
ifTrue:
[manager detachFreeObject: currentEntity.
"To avoid confusing too much Spur (especially the leak/free checks), we mark the free chunk as a word object."
manager set: currentEntity classIndexTo: manager wordSizeClassIndexPun formatTo: manager wordIndexableFormat]
ifFalse:
["During the mutator runs new forwarding references can be created. Ignore them as they get resolved with the other forwarders in this segment in the next marking pass"
(manager isForwarded: currentEntity) not
ifTrue: ["Copy the object in segmentToFill and replace it by a forwarder."
- self assert: (manager isPinned: currentEntity) not.
bytesToCopy := manager bytesInBody: currentEntity.
+ self migrate: currentEntity sized: bytesToCopy to: fillStart.
- manager memcpy: fillStart asVoidPointer _: (manager startOfObject: currentEntity) asVoidPointer _: bytesToCopy.
- copy := manager objectStartingAt: fillStart.
- (manager isRemembered: copy) ifTrue:
- ["copy has the remembered bit set, but is not in the remembered table."
- manager setIsRememberedOf: copy to: false.
- scavenger remember: copy].
-
- manager forward: currentEntity to: (manager objectStartingAt: fillStart).
-
fillStart := fillStart + bytesToCopy.
self assert: (self oop: fillStart isLessThan: (segmentToFill segLimit - manager bridgeSize))]].
currentEntity := manager objectAfter: currentEntity limit: manager endOfMemory].
self assert: currentEntity = bridge.
currentSegment := currentSegment + 1.
^ fillStart!
Item was changed:
----- Method: SpurIncrementalCompactor>>doIncrementalCompact (in category 'incremental compaction') -----
doIncrementalCompact
| segInfo |
currentSegment to: manager numSegments - 1 do:
[:i |
segInfo := self addressOf: (manager segmentManager segments at: i).
(self isSegmentBeingCompacted: segInfo)
ifTrue: [currentSegment := i.
+ coInterpreter cr;
+ print: 'Compact from: '; printNum: segInfo segStart;
+ print: ' to: '; printNum: segInfo segStart + segInfo segSize;
+ print: ' into: ' ; printNum: segmentToFill segStart; tab; flush.
- coInterpreter cr; print: 'Compact from: '; printNum: segInfo segStart; print: ' to: '; printNum: segInfo segStart + segInfo segSize; print: ' into: ' ; printNum: segmentToFill segStart; tab; flush.
currentHeapPointer := self compactSegment: segInfo freeStart: currentHeapPointer segIndex: i.
self assert: manager totalFreeOldSpace = manager totalFreeListBytes.
self flag: #Todo. "for now we compact on segment at a time"
^ currentSegment = (manager numSegments - 1)
ifTrue: [true]
ifFalse: [false]]].
^ true!
Item was changed:
----- Method: SpurIncrementalCompactor>>findNextSegmentToCompact (in category 'compaction planning') -----
findNextSegmentToCompact
"Answers the next segment to compact or nil if none.
The next segment to compact:
- cannot be segment 0 (Segment 0 has specific objects
(nil, true, etc.) and special size computed at start-up
that we don't want to deal with)
- cannot have a high occupation rate (> MaxOccupationForCompaction)"
| leastOccupied leastOccupiedSegment tempOccupied segInfo |
<var: 'segInfo' type: #'SpurSegmentInfo *'>
leastOccupied := 16rFFFF.
1 to: manager numSegments - 1 do:
[:i|
segInfo := self addressOf: (manager segmentManager segments at: i).
+ (self cannotBeCompacted: segInfo)
- ((self isSegmentBeingCompacted: segInfo) or: [segInfo containsPinned or: [manager segmentManager isEmptySegment: segInfo] ])
ifFalse:
[(tempOccupied := self occupationOf: segInfo) <= leastOccupied
ifTrue: [ leastOccupied := tempOccupied.
leastOccupiedSegment := segInfo ]]].
leastOccupied > MaxOccupationForCompaction ifTrue:
[^self cCoerceSimple: nil to: #'SpurSegmentInfo *'].
^leastOccupiedSegment!
Item was added:
+ ----- Method: SpurIncrementalCompactor>>migrate:sized:to: (in category 'as yet unclassified') -----
+ migrate: obj sized: bytesToCopy to: address
+
+ | copy |
+ self assert: (manager isPinned: obj) not.
+
+ manager memcpy: address asVoidPointer _: (manager startOfObject: obj) asVoidPointer _: bytesToCopy.
+
+ copy := manager objectStartingAt: address.
+ (manager isRemembered: copy) ifTrue:
+ ["copy has the remembered bit set, but is not in the remembered table."
+ manager setIsRememberedOf: copy to: false.
+ scavenger remember: copy].
+
+ manager forward: obj to: (manager objectStartingAt: address).
+
+ ^ copy!
Item was changed:
----- Method: SpurIncrementalGarbageCollector>>doIncrementalCollect (in category 'as yet unclassified') -----
doIncrementalCollect
phase = InMarkingPhase
ifTrue: [
coInterpreter cr; print: 'start marking '; tab; flush.
marker incrementalMarkObjects
ifTrue: [
manager allPastSpaceObjectsDo: [:obj | self assert: (manager isWhite: obj)].
+ manager allOldSpaceObjectsDo: [:ea | (manager isForwarded: ea) ifTrue: [self halt] ].
"when sweeping the mutator needs to allocate new objects black as we do not have any information about them.
We only know if they should get swept after the next marking -> keep them alive for this cycle"
self allocatorShouldAllocateBlack: true.
compactor setInitialSweepingEntity.
phase := InSweepingPhase.
"marking is done and thus all forwarding references are resolved -> we can use the now free segments that were
compacted during the last cycle"
compactor freePastSegmentsAndSetSegmentToFill.
coInterpreter cr; print: 'finish marking '; tab; flush.
manager
setCheckForLeaks: GCCheckFreeSpace + GCModeFull;
runLeakCheckerFor: GCModeFull;
checkFreeSpace: GCModeFull.
^ self]
ifFalse: [coInterpreter cr; print: 'finish marking pass'; tab; flush.manager runLeakCheckerFor: GCModeIncremental]].
phase = InSweepingPhase
ifTrue: [
coInterpreter cr; print: 'start sweeping '; tab; flush.
compactor incrementalSweep
ifTrue: [
self allocatorShouldAllocateBlack: false.
manager allOldSpaceObjectsDo: [:ea | self assert: (manager isWhite: ea) ].
"self assert: manager allObjectsUnmarked."
coInterpreter cr; print: 'finish sweeping '; tab; flush.
manager
setCheckForLeaks: GCCheckFreeSpace + GCModeFull;
runLeakCheckerFor: GCModeFull;
checkFreeSpace: GCModeFull.
phase := InCompactingPhase.
^ self]].
phase = InCompactingPhase
ifTrue: [
coInterpreter cr; print: 'start compacting '; tab; flush.
compactor incrementalCompact
ifTrue: [
coInterpreter cr; print: 'finish compacting '; tab; flush.
manager
setCheckForLeaks: GCCheckFreeSpace + GCModeFull;
runLeakCheckerFor: GCModeFull;
checkFreeSpace: GCModeFull.
phase := InMarkingPhase.
^ self]]!
Item was changed:
----- Method: SpurIncrementalGarbageCollector>>fullGC (in category 'global') -----
fullGC
"We need to be able to make a full GC, e.g. when we save the image. Use the made progress and finish the collection"
"incredible hacky solution. Will later on be replaced with the old collection, but for now use this to keep the state transitions consistent"
self assert: manager validObjStacks.
+ self halt.
-
coInterpreter cr; print: 'start fullGC '; tab; flush.
coInterpreter setGCMode: GCModeNewSpace.
self doScavengeWithoutIncrementalCollect: MarkOnTenure.
phase = InMarkingPhase
ifTrue: [
"end marking"
[phase = InMarkingPhase]
whileTrue: [self doIncrementalCollect]].
"end this collection cycle"
[phase ~= InMarkingPhase]
whileTrue: [self doIncrementalCollect].
"resolve forwarders in young space"
coInterpreter setGCMode: GCModeNewSpace.
self doScavengeWithoutIncrementalCollect: MarkOnTenure.
"mark completely"
[phase = InMarkingPhase]
whileTrue: [self doIncrementalCollect].
"do rest of collection"
[phase ~= InMarkingPhase]
whileTrue: [self doIncrementalCollect].
manager setHeapSizeAtPreviousGC.
coInterpreter cr; print: 'end fullGC '; tab; flush.
^(manager freeLists at: 0) ~= 0
ifTrue: [manager bytesInBody: manager findLargestFreeChunk]
ifFalse: [0]!
Item was added:
+ ----- Method: SpurIncrementalGarbageCollector>>maybeModifyForwarder: (in category 'object creation barriers') -----
+ maybeModifyForwarder: objOop
+
+ "mark forwarders so they do not get garbage collected before they can get resolved.
+ 1. Does only apply to marking because only in this phase we can overlook forwarding references to be resolved (e.g. when
+ the mutator runs after the first marking pass and an already black object gets a forwarding pointer -> it will not be followed in this
+ marking pass and during sweeping the forwardgin pointer will get removes).
+ 2. Does not apply to sweeping or compacting because the forwarder is set on the header of the original object, which already includes
+ the correcty set mark bit"
+ self assert: (manager isForwarded: objOop).
+ ((manager isOldObject: objOop) and: [phase = InMarkingPhase])
+ ifTrue: [manager setIsMarkedOf: objOop to: true]!
Item was changed:
+ ----- Method: SpurIncrementalGarbageCollector>>maybeModifyGCFlagsOf: (in category 'object creation barriers') -----
- ----- Method: SpurIncrementalGarbageCollector>>maybeModifyGCFlagsOf: (in category 'as yet unclassified') -----
maybeModifyGCFlagsOf: objOop
"when allocating a new object behind the current sweeping hight mark it should be allocated black so it does not get garbage
collected although we do not know if this is correct"
<inline: true>
((manager isOldObject: objOop) and: [allocatorShouldAllocateBlack and: [objOop >= compactor currentSweepingEntity]])
ifTrue: [manager setIsMarkedOf: objOop to: true]!
Item was changed:
----- Method: SpurMemoryManager>>forward:to: (in category 'become implementation') -----
forward: obj1 to: obj2
self set: obj1 classIndexTo: self isForwardedObjectClassIndexPun formatTo: self forwardedFormat.
self cppIf: IMMUTABILITY ifTrue: [ self setIsImmutableOf: obj1 to: false ].
self storePointer: 0 ofForwarder: obj1 withValue: obj2.
+ gc maybeModifyForwarder: obj1.
"For safety make sure the forwarder has a slot count that includes its contents."
(self rawNumSlotsOf: obj1) = 0 ifTrue:
[self rawNumSlotsOf: obj1 put: 1]!