Leon Matthes uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.threaded-LM.3349.mcz
==================== Summary ====================
Name: VMMaker.threaded-LM.3349 Author: LM Time: 6 December 2023, 11:29:35.587268 am UUID: 5d97125e-0dec-4acd-acd5-ae9fd8a5b14b Ancestors: VMMaker.threaded-LM.3348
Log VM owner switches and add primitive to query the log.
=============== Diff against VMMaker.threaded-LM.3348 ===============
Item was added: + ----- Method: AtomicValue>>+ (in category 'as yet unclassified') ----- + + aValue + + ^ self value + aValue.!
Item was changed: ----- Method: CoInterpreterMT class>>ancilliaryClasses (in category 'translation') ----- ancilliaryClasses "Answer any extra classes to be included in the translation." + ^super ancilliaryClasses, { CogThreadManager. CogVMThread . CogVMOwnerLog }! - ^super ancilliaryClasses, { CogThreadManager. CogVMThread }!
Item was changed: ----- Method: CoInterpreterMT class>>initializePrimitiveTable (in category 'initialization') ----- initializePrimitiveTable super initializePrimitiveTable. PrimNumberRelinquishProcessor := 230. COGMTVM ifTrue: + [(226 to: 229) do: - [(227 to: 229) do: [:pidx| self assert: (PrimitiveTable at: pidx + 1) = #primitiveFail]. PrimitiveTable + at: 226 + 1 put: #primitiveGetOwnerLog; at: 227 + 1 put: #primitiveVMCurrentThreadId; at: 228 + 1 put: #primitiveProcessBoundThreadId; at: 229 + 1 put: #primitiveProcessBindToThreadAffinity]!
Item was added: + ----- Method: CoInterpreterMT>>primitiveGetOwnerLog (in category 'logging primitives') ----- + primitiveGetOwnerLog + "Write the owner log to the given ByteArray. + All members of the owner log struct are always written as defined by the C struct. + Return the number of instances of CogVMOwnerLog written into the ByteArray." + | logBuffer bufferPointer bytesCopied | + <export: true> + <var: #bufferPointer type: #'char *'> + argumentCount ~= 1 + ifTrue: [^ self primitiveFailFor: PrimErrBadNumArgs]. + + logBuffer := self stackTop. + ((objectMemory isNonImmediate: logBuffer) + and: [(objectMemory isPureBitsNonImm: logBuffer) + and: [(objectMemory numBytesOf: logBuffer) >= (OwnerLogSize * (self sizeof: CogVMOwnerLog))]]) + ifFalse: [^ self primitiveFailFor: PrimErrBadArgument]. + + bufferPointer := (self objectMemory firstFixedField: logBuffer). + bytesCopied := cogThreadManager copyLogTo: bufferPointer. + + self pop: argumentCount + 1 thenPushInteger: bytesCopied / (self sizeof: CogVMOwnerLog). + !
Item was changed: ----- Method: CoInterpreterMT>>reduceWaitingPriorityFrom:to: (in category 'accessing') ----- reduceWaitingPriorityFrom: existingWaitingPriority to: newMaxPriority <var: #existing type: #int> | existing | self cCode: [existing := existingWaitingPriority] + inSmalltalk: [existing := AtomicValue newFrom: existingWaitingPriority.]. - inSmalltalk: [existing := AtomicValue new. - existing value: existingWaitingPriority.]. "This CPXCHG may fail, that's fine though, as there may have been another thread that increased the priority in the meantime. In that case that threads priority is the correct one to use." ^ self atomic: (self addressOf: maxWaitingPriority) _compare: (self addressOf: existing) _exchange_strong: newMaxPriority!
Item was changed: ----- Method: CoInterpreterMT>>waitingPriorityIsAtLeast: (in category 'accessing') ----- waitingPriorityIsAtLeast: minPriority "Set the maxWaitingPriority to at least minPriority on behalf of a thread wanting to acquire the VM. If maxWaitingPriority is increased, schedule a thread activation check asap." <var: #currentWaitingPriority type: #int> | currentWaitingPriority didIncrease | self cCode: [currentWaitingPriority := self getMaxWaitingPriority.] + inSmalltalk: [currentWaitingPriority := AtomicValue newFrom: self getMaxWaitingPriority]. - inSmalltalk: [currentWaitingPriority := AtomicValue new. - currentWaitingPriority value: self getMaxWaitingPriority].
didIncrease := false. [(self cCode: [currentWaitingPriority] inSmalltalk: [currentWaitingPriority value]) >= minPriority or: [didIncrease := self atomic: (self addressOf: maxWaitingPriority) _compare: (self addressOf: currentWaitingPriority) _exchange_strong: minPriority]] whileFalse. didIncrease ifTrue: [ self assert: (self cCode: [currentWaitingPriority] inSmalltalk: [currentWaitingPriority value]) < minPriority. checkThreadActivation := true. self forceInterruptCheck]!
Item was changed: CogClass subclass: #CogThreadManager (excessive size, no diff calculated)
Item was changed: ----- Method: CogThreadManager class>>declareCVarsIn: (in category 'translation') ----- declareCVarsIn: cCodeGen cCodeGen removeVariable: 'coInterpreter'; removeVariable: 'cogit'; removeVariable: 'threadLocalStorage'; removeVariable: 'processorOwner'; removeVariable: 'registerStates'. cCodeGen var: #threads type: #'CogVMThread **'; var: #vmOSThread type: #sqOSThread; var: #vmOwner type: #'volatile atomic_int'; + var: #maxWaitingPriority type: #'volatile atomic_int'; + var: #ownerLog type: #'CogVMOwnerLog *'; + var: #ownerLogIndex type: #'volatile atomic_int'. - var: #maxWaitingPriority type: #'volatile atomic_int'. cCodeGen addHeaderFile: '<stdatomic.h>'!
Item was changed: ----- Method: CogThreadManager class>>initialize (in category 'class initialization') ----- initialize "CogThreadManager initialize" CTMUninitialized := 0. CTMInitializing := 1. CTMUnavailable := 2. "off doing its own thing and not available to run the VM, e.g. calling-out." CTMAssignableOrInVM := 3. "either owning the VM or blocked on its osSemaphore available for VM work" CTMWantingOwnership := 4. "with something specific to do in the VM (e.g. return a result from a call-out, make a call-back)" CTMUnknownOwner := -1.
"Define the size of the stack of processes at time of disown/own." + AWOLProcessesIncrement := 4. + OwnerLogSize := 1024.! - AWOLProcessesIncrement := 4!
Item was added: + ----- Method: CogThreadManager>>copyLogTo: (in category 'logging') ----- + copyLogTo: aPointer + | bufferPointer bytesCopied index startIndex bytesToCopy | + <var: #aPointer type: #'char *'> + <var: #bufferPointer type: #'char *'> + bufferPointer := aPointer. + index := self atomic_load: (self addressOf: ownerLogIndex). + bytesCopied := 0. + + "NOTE: The ownerLogWrapped isn't synchronized atomically. + Therefore, if this primitive is called exactly when we start wrapping the very first time, we'll read the wrong value here. + However, as this is very unlikely, don't bother fixing this yet." + ownerLogWrapped ifTrue: [ + "If we've reached the limit of our Ringbuffer, deliberately don't copy the last 100 entries, + so that new log entries don't overwrite during the memcpy." + startIndex := index + 100 min: (OwnerLogSize - 1). + bytesToCopy := (OwnerLogSize - startIndex) * (self sizeof: CogVMOwnerLog). + self memcpy: bufferPointer + _: ownerLog + startIndex + _: bytesToCopy. + bytesCopied := bytesCopied + bytesToCopy]. + + bytesToCopy := index * (self sizeof: CogVMOwnerLog). + self memcpy: bufferPointer + bytesCopied + _: ownerLog + _: bytesToCopy. + bytesCopied := bytesCopied + bytesToCopy. + ^ bytesCopied + !
Item was changed: ----- Method: CogThreadManager>>doTryLockVMOwnerTo: (in category 'Cogit lock implementation') ----- doTryLockVMOwnerTo: threadIndex "In the simulation, this is being called by #simulateTryLockVMOwnerTo:, in C this method will just be called directly. Returns true if the vmOwner has been successfully set to the given thread index." <inline: #always> <var: #expected type: #int> + | expected success | - | expected | expected := self cCode: 0 inSmalltalk: [AtomicValue newFrom: 0]. + success := (self atomic: (self addressOf: vmOwner) - ^ (self atomic: (self addressOf: vmOwner) _compare: (self addressOf: expected) _exchange_strong: threadIndex) or: ["We may already be vmOwner. The current vmOwner will be stored in expected. However, if an unknown owner is present, we cannot assume that's us!!" + expected = threadIndex and: [threadIndex ~= CTMUnknownOwner]]. + + self logOwnerSwitchTo: threadIndex successful: success. + ^ success! - expected = threadIndex and: [threadIndex ~= CTMUnknownOwner]]!
Item was changed: ----- Method: CogThreadManager>>initialize (in category 'initialize-release') ----- initialize + "Initialize is only called in Smalltalk simulation, don't initialize anything here that's important for C. + For that use #startThreadSubsystem." numThreads := numThreadsIncrement := 0. + vmOwner := AtomicValue newFrom: 0. + - self cCode: [self atomic_store: (self addressOf: vmOwner) _: 0] - inSmalltalk: [vmOwner := AtomicValue newFrom: 0]. memoryIsScarce := false. "N.B. Do not initialize threadLocalStorage; leave this to ioInitThreadLocalThreadIndices". + registerStates := IdentityDictionary new.! - registerStates := IdentityDictionary new!
Item was added: + ----- Method: CogThreadManager>>initializeOwnerLog (in category 'public api') ----- + initializeOwnerLog + "The owner log isn't actually used in Simulation, we just directly print everything, so we can leave those variables empty during simulation." + self cCode: [ownerLog := self calloc: OwnerLogSize _: (self sizeof: CogVMOwnerLog). + self atomic_store: (self addressOf: ownerLogIndex) _: 0. + ownerLogWrapped := false.].!
Item was added: + ----- Method: CogThreadManager>>logOwnerSwitchTo:successful: (in category 'logging') ----- + logOwnerSwitchTo: newOwner successful: aBoolean + <inline: false> + self cCode: [self saveOwnerSwitchTo: newOwner successful: aBoolean] + inSmalltalk: [self printOwnerSwitchTo: newOwner successful: aBoolean].!
Item was added: + ----- Method: CogThreadManager>>printOwnerSwitchTo:successful: (in category 'logging') ----- + printOwnerSwitchTo: newOwner successful: aBoolean + <doNotGenerate> + coInterpreter transcript + ensureCr; + f: 'VM Owner: %d :: %d -> %d %s\n' + printf: { coInterpreter ioMSecs. + self getVMOwner. + newOwner. + aBoolean ifTrue: ['ok'] ifFalse: ['FAILED'] }.!
Item was added: + ----- Method: CogThreadManager>>saveOwnerSwitchTo:successful: (in category 'logging') ----- + saveOwnerSwitchTo: newOwner successful: aBoolean + <var: #logEntry type: 'CogVMOwnerLog *'> + <var: #currentIndex type: 'int'> + <var: #newIndex type: 'int'> + | currentIndex newIndex timestamp logEntry | + timestamp := coInterpreter ioMSecs. + currentIndex := self atomic_load: (self addressOf: ownerLogIndex). + self cCode: '' inSmalltalk: [currentIndex := AtomicValue newFrom: currentIndex]. + + [newIndex := currentIndex + 1 \ OwnerLogSize. + self atomic: (self addressOf: ownerLogIndex) + _compare: (self addressOf: currentIndex) + _exchange_strong: newIndex] whileFalse: []. + + newIndex < currentIndex ifTrue: [ownerLogWrapped := true]. + logEntry := (self addressOf: (ownerLog at: (self cCode: [currentIndex] inSmalltalk: [currentIndex value]))). + logEntry + timestamp: timestamp; + successfulSwitch: aBoolean; + vmOwner: newOwner.!
Item was changed: ----- Method: CogThreadManager>>setVMOwner: (in category 'public api') ----- setVMOwner: indexOrZero "An ugly accessor used in only three cases: 1. by ownVMFromUnidentifiedThread when the VM is first locked to the thread id of the unidentified thread, and then, once identified, to the thread's index. 2. by wakeVMThreadFor: used by the two-level scheduler to switch threads when a Smalltalk process switch occurs to a process affined to another thread. 3. to release the VM (set the owner to zero)" <inline: #always> + "This can only be used when we're the VM Owner. It shall not be used to gain ownership. + Make sure this is the case!!" + self assert: (self getVMOwner = -1 or: [self getVMOwner = self ioGetThreadLocalThreadIndex]). - self assert: (self getVMOwner = self ioGetThreadLocalThreadIndex or: [self getVMOwner = -1]). self assert: (self getVMOwner ~= indexOrZero). + self logOwnerSwitchTo: indexOrZero successful: true. - self cCode: '' inSmalltalk: - [coInterpreter transcript - ensureCr; - f: 'setVMOwner: %d -> %d (%s)\n' - printf: { self getVMOwner. indexOrZero. thisContext home sender selector }]. "TODO: We could make this a `release` ordering, which may perform better on ARM." self atomic_store: (self addressOf: vmOwner) _: indexOrZero!
Item was changed: ----- Method: CogThreadManager>>simulateTryLockVMOwnerTo: (in category 'simulation') ----- simulateTryLockVMOwnerTo: threadIndex "In the real VM this is a direct call of #tryLockVMOwnerTo:. In the simulation this is where register state is restored, simulating a thread switch. State is stored in saveRegisterStateForCurrentProcess (sent by disownVM:, ioWaitOnOSSemaphore: and ioTransferTimeslice). The code here and in saveRegisterStateForCurrentProcess allow us to avoid the expensive and complex MultiProcessor hack.
The idea here is to save the register state around the invocation of tryLockVMOwnerTo:, and set the register state to that for the owner, changing the state if ownership has changed, restoring it if ownership has not changed." <doNotGenerate> self deny: threadIndex = 0. ^cogit withProcessorHaltedDo: [| previousOwner currentOwner processor result | processor := cogit processor. "After switching, the 'current' owner will be the 'previous' owner. Though the value will be the same, let's still introduce a second variable that we can use after the switch to make it more clear what's going on." previousOwner := currentOwner := self getVMOwner.
"If we currently have a VM owner, the register state should be valid for that owner, otherwise it should be empty. It may be CTMUnknownOwner (-1), in that case it should also be empty." currentOwner > 0 ifTrue: [self assertValidStackPointersInState: processor registerState forIndex: currentOwner] ifFalse: [self assertEmptyRegisterStates: processor registerState].
result := self doTryLockVMOwnerTo: threadIndex. self assert: result = (threadIndex = self getVMOwner). result ifTrue: ["If we did actually change owners, assert that previously the processor was emtpy." previousOwner ~= self getVMOwner ifTrue: [self assertEmptyRegisterStates: processor registerState. self loadOrInitializeRegisterStateFor: threadIndex]]. + - - coInterpreter transcript - ensureCr; - f: 'tryLockVMOwner %d -> %d (%s) %s\n' - printf: { previousOwner. threadIndex. thisContext home sender selector. result ifTrue: ['ok'] ifFalse: ['FAILED'] }. self assertValidProcessorStackPointersForIndex: self getVMOwner. result]!
Item was changed: ----- Method: CogThreadManager>>startThreadSubsystem (in category 'public api') ----- startThreadSubsystem "Initialize the threading subsystem, aborting if there is an error." | vmThread | <inline: false> self assert: threads = nil. vmOSThread := self ioCurrentOSThread. numThreadsIncrement := (self ioNumProcessors max: 2) min: 16. (self growThreadInfosToAtLeast: numThreadsIncrement * 2) ifFalse: [self error: 'no memory to start thread system']. self atomic_store: (self addressOf: vmOwner) _: 1. vmThread := threads at: self getVMOwner. vmThread setVmThreadState: CTMInitializing. self registerVMThread: vmThread. + vmThread setVmThreadState: CTMAssignableOrInVM. + + self initializeOwnerLog.! - vmThread setVmThreadState: CTMAssignableOrInVM!
Item was added: + VMStructType subclass: #CogVMOwnerLog + instanceVariableNames: 'timestamp vmOwner successfulSwitch' + classVariableNames: '' + poolDictionaries: 'VMThreadingConstants' + category: 'VMMaker-Multithreading'!
Item was added: + ----- Method: CogVMOwnerLog class>>instVarNamesAndTypesForTranslationDo: (in category 'translation') ----- + instVarNamesAndTypesForTranslationDo: aBinaryBlock + "enumerate aBinaryBlock with the names and C type strings for the inst vars to include in a CogVMThread struct." + + self allInstVarNames do: + [:ivn| aBinaryBlock value: ivn value: #sqInt]!
Item was added: + ----- Method: CogVMOwnerLog>>successfulSwitch (in category 'accessing') ----- + successfulSwitch + + ^ successfulSwitch!
Item was added: + ----- Method: CogVMOwnerLog>>successfulSwitch: (in category 'accessing') ----- + successfulSwitch: anObject + + ^ successfulSwitch := anObject.!
Item was added: + ----- Method: CogVMOwnerLog>>timestamp (in category 'accessing') ----- + timestamp + + ^ timestamp!
Item was added: + ----- Method: CogVMOwnerLog>>timestamp: (in category 'accessing') ----- + timestamp: anObject + + ^ timestamp := anObject.!
Item was added: + ----- Method: CogVMOwnerLog>>vmOwner (in category 'accessing') ----- + vmOwner + + ^ vmOwner!
Item was added: + ----- Method: CogVMOwnerLog>>vmOwner: (in category 'accessing') ----- + vmOwner: anObject + + ^ vmOwner := anObject.!
Item was added: + ----- Method: CogVMSimulator>>ownVM:withFlags: (in category 'multi-threading simulation switch') ----- + ownVM: vmThreadHandle withFlags: additionalFlags + "This method includes or excludes CoInterpreterMT methods as required. + Auto-generated by CogVMSimulator>>ensureMultiThreadingOverridesAreUpToDate" + + ^self perform: #ownVM:withFlags: + withArguments: {vmThreadHandle. additionalFlags} + inSuperclass: (cogThreadManager ifNil: [CoInterpreterPrimitives] ifNotNil: [CoInterpreterMT])!
Item was changed: ----- Method: Interpreter>>primitiveVMProfileSamplesInto (in category 'process primitives') ----- primitiveVMProfileSamplesInto "Primitive. 0 args: Answer whether the VM Profiler is running or not. 1 arg: Copy the sample data into the supplied argument, which must be a Bitmap of suitable size. Answer the number of samples copied into the buffer." | sampleBuffer sampleBufferAddress running bufferSize numSamples | <var: #bufferSize type: #long> <var: #sampleBufferAddress type: #'unsigned long *'> self cCode: 'ioNewProfileStatus(&running,&bufferSize)' inSmalltalk: [running := false. bufferSize := 0]. argumentCount = 0 ifTrue: [^self pop: 1 thenPushBool: running]. self success: argumentCount = 1. successFlag ifTrue: [sampleBuffer := self stackObjectValue: 0. self assertClassOf: sampleBuffer is: (self splObj: ClassBitmap). self success: (self numSlotsOf: sampleBuffer) >= bufferSize]. successFlag ifFalse: [^nil]. sampleBufferAddress := self firstFixedField: sampleBuffer. + "We use thread 0 here, as the normal Interpreter doesn't support threading anyway." + numSamples := self cCode: 'ioNewProfileThreadSamplesInto(0,sampleBufferAddress)' - numSamples := self cCode: 'ioNewProfileSamplesInto(sampleBufferAddress)' inSmalltalk: [sampleBufferAddress := sampleBufferAddress]. self pop: argumentCount + 1 thenPushInteger: numSamples!
Item was changed: ----- Method: InterpreterPrimitives>>primitiveVMProfileSamplesInto (in category 'process primitives') ----- primitiveVMProfileSamplesInto "Primitive. 0 args: Answer whether the VM Profiler is running or not. + 2 arg: Copy the sample data for the thread with the given index into the second + supplied argument, which must be a Bitmap of suitable size. + Answer the number of samples copied into the buffer." + | sampleBuffer running bufferSize numSamples threadIndex | - 1 arg: Copy the sample data into the supplied argument, which must be a Bitmap - of suitable size. Answer the number of samples copied into the buffer." - | sampleBuffer running bufferSize numSamples | <var: #bufferSize type: #long> + "Initialize to shut up the warning about 'uninitialized variables' in Squeak" + running := 0. + bufferSize := 0. self ioNewProfile: (self addressOf: running put: [:v| running := v]) Status: (self addressOf: bufferSize put: [:v| bufferSize := v]). argumentCount = 0 ifTrue: [^self pop: 1 thenPushBool: running]. + argumentCount = 2 ifFalse: - argumentCount = 1 ifFalse: [^self primitiveFailFor: PrimErrBadNumArgs]. + + threadIndex := self stackIntegerValue: 1. + sampleBuffer := self stackValue: 0. ((objectMemory isNonImmediate: sampleBuffer) and: [(objectMemory isPureBitsNonImm: sampleBuffer) and: [(objectMemory numBytesOf: sampleBuffer) >= (bufferSize * objectMemory wordSize)]]) ifFalse: [^self primitiveFailFor: PrimErrBadArgument]. + + numSamples := self ioNewProfileThread: threadIndex SamplesInto: (objectMemory firstFixedField: sampleBuffer). - numSamples := self ioNewProfileSamplesInto: (objectMemory firstFixedField: sampleBuffer). self pop: argumentCount + 1 thenPushInteger: numSamples!
Item was changed: ----- Method: SqueakSSLPlugin>>primitiveConnect (in category 'primitives') ----- primitiveConnect "Primitive. Starts or continues a client handshake using the provided data. Will eventually produce output to be sent to the server. Requires the host name to be set for the session. Returns: > 0 - Number of bytes to be sent to the server 0 - Success. The connection is established. -1 - More input is required. < -1 - Other errors. " | start srcLen dstLen srcOop dstOop handle srcPtr dstPtr result wasSrcPinned wasDestPinned vmHandle | <var: #srcPtr type: #'char *'> <var: #dstPtr type: #'char *'> <export: true> interpreterProxy methodArgumentCount = 5 ifFalse:[^interpreterProxy primitiveFail]. dstOop := interpreterProxy stackValue: 0. srcLen := interpreterProxy stackIntegerValue: 1. start := interpreterProxy stackIntegerValue: 2. srcOop := interpreterProxy stackValue: 3. handle := interpreterProxy stackIntegerValue: 4. interpreterProxy failed ifTrue:[^nil]. ((start > 0 and:[srcLen >= 0]) and:[(interpreterProxy isBytes: srcOop) and:[(interpreterProxy isBytes: dstOop) and:[(interpreterProxy byteSizeOf: srcOop) >= (start + srcLen - 1)]]]) ifFalse:[^interpreterProxy primitiveFail]. "Careful!! The object may move when being pinned!!" (wasSrcPinned := interpreterProxy isPinned: srcOop) ifFalse: [srcOop := interpreterProxy pinObject: srcOop]. (wasDestPinned := interpreterProxy isPinned: dstOop) ifFalse: [dstOop := interpreterProxy pinObject: dstOop]. "Pinning may fail (only if we're out of memory)" (srcOop isNil or: [dstOop isNil]) ifTrue: [^ interpreterProxy primitiveFail]. srcPtr := interpreterProxy firstIndexableField: srcOop. dstPtr := interpreterProxy firstIndexableField: dstOop. srcPtr := srcPtr + start - 1. dstLen := interpreterProxy byteSizeOf: dstOop. + "vmHandle := interpreterProxy disownVM: DisownVMForThreading." - vmHandle := interpreterProxy disownVM: DisownVMForThreading. result := self cCode: 'sqConnectSSL(handle, srcPtr, srcLen, dstPtr, dstLen)' inSmalltalk:[handle. srcPtr. srcLen. dstPtr. dstLen. -2]. + "interpreterProxy ownVM: vmHandle." - interpreterProxy ownVM: vmHandle. wasSrcPinned ifFalse: [interpreterProxy unpinObject: srcOop]. wasDestPinned ifFalse: [interpreterProxy unpinObject: dstOop]. interpreterProxy failed ifTrue:[^nil]. interpreterProxy pop: interpreterProxy methodArgumentCount+1. interpreterProxy pushInteger: result.!
Item was changed: SharedPool subclass: #VMThreadingConstants instanceVariableNames: '' + classVariableNames: 'AWOLProcessesIncrement CTMAssignableOrInVM CTMInitializing CTMUnavailable CTMUninitialized CTMUnknownOwner CTMWantingOwnership OwnerLogSize ThreadIdIndex ThreadIdShift' - classVariableNames: 'AWOLProcessesIncrement CTMAssignableOrInVM CTMInitializing CTMUnavailable CTMUninitialized CTMUnknownOwner CTMWantingOwnership ThreadIdIndex ThreadIdShift' poolDictionaries: '' category: 'VMMaker-Multithreading'!
!VMThreadingConstants commentStamp: '<historical>' prior: 0! VMThreadingConstants ensureClassPool. CogThreadManager classPool keys do: [:k| VMThreadingConstants classPool declare: k from: CogThreadManager classPool]. CoInterpreterMT classPool keys do: [:k| VMThreadingConstants classPool declare: k from: CoInterpreterMT classPool].!
vm-dev@lists.squeakfoundation.org