Leon Matthes uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.threaded-LM.3355.mcz
==================== Summary ====================
Name: VMMaker.threaded-LM.3355 Author: LM Time: 20 March 2024, 3:45:46.213376 pm UUID: 537e53f3-b38a-4f82-a7d8-b3eeb232e2ce Ancestors: VMMaker.threaded-LM.3354
Fix a critical bug in threadSwitchIfNecessary:from: . If the activeProcess context was nil, it wasn't restored correctly. This has been fixed now.
This has a problem though, as we need to marry the activeProcess context every time we do a thread switch, this can cause a lot of GCs, as we create a lot of context objects. Maybe we can fix this later?
=============== Diff against VMMaker.threaded-LM.3354 ===============
Item was changed: ----- Method: CoInterpreterMT>>ownVM:withFlags: (in category 'vm scheduling') ----- ownVM: vmThreadHandle withFlags: additionalFlags <public> <inline: false> <var: #vmThreadHandle type: #'void *'> <var: #vmThread type: #'CogVMThread *'> "This is the entry-point for plugins and primitives that wish to reacquire the VM after having released it via disownVM or callbacks that want to acquire it without knowing their ownership status. This call will block until the VM is owned by the current thread or an error occurs. The argument should be the value answered by disownVM, or 0 for callbacks that don't know if they have disowned or not. This is both an optimization to avoid having to query thread- local storage for the current thread's index (since it can easily keep it in some local variable), and a record of when an unbound process becomes affined to a thread for the dynamic extent of some operation.
Answer 0 if the current thread is known to the VM (and on return owns the VM). Answer 1 if the current thread is unknown to the VM and takes ownership. Answer -1 if the current thread is unknown to the VM and fails to take ownership." | flags vmThread | vmThread := self cCoerce: vmThreadHandle to: #'CogVMThread *'. vmThread ifNil: [^self ownVMFromUnidentifiedThread]. self assert: vmThread = (cogThreadManager vmThreadAt: vmThread index).
flags := vmThread disownFlags bitOr: additionalFlags.
+ vmThread := cogThreadManager acquireVMFor: vmThread. + disownCount := disownCount - 1. + (flags anyMask: DisownVMForProcessorRelinquish) ifTrue: ["Presumably we have nothing to do; this primitive is typically called from the background process. So we should /not/ try and activate any threads in the pool; they will waste cycles finding there is no runnable process, and will cause a VM abort if no runnable process is found. But we /do/ want to allow FFI calls that have completed, or callbacks a chance to get into the VM; they do have something to do. DisownVMForProcessorRelinquish indicates this." relinquishing := false. self sqLowLevelMFence].
- vmThread := cogThreadManager acquireVMFor: vmThread. - disownCount := disownCount - 1. - disowningVMThread ifNotNil: [vmThread = disowningVMThread ifTrue: [self assert: (vmThread cFramePointer isNil or: [CFramePointer = vmThread cFramePointer and: [CStackPointer = vmThread cStackPointer]]). self assert: self successful. self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject. disowningVMThread := nil. cogit recordEventTrace ifTrue: [self recordTrace: TraceOwnVM thing: ConstOne source: 0]. ^0]. "if not preempted we're done." self preemptDisowningThread].
"We've been preempted; we must restore state and update the threadId in our process, and may have to put the active process to sleep." self restoreVMStateFor: vmThread andFlags: flags.
cogit recordEventTrace ifTrue: [self recordTrace: TraceOwnVM thing: ConstTwo source: 0]. ^flags bitAnd: OwnVMForeignThreadFlag!
Item was changed: ----- Method: CoInterpreterMT>>primitiveProcessBindToThreadAffinity (in category 'process primitives') ----- primitiveProcessBindToThreadAffinity "Attempt to bind the receiver to the thread affinity of the argument or nil, where the receiver is a Process. The thread affinity may be an integer where: 0 - means no thread affinity, the process is free to run on any thread. > 0 - positive values mean the process has to run on the thread with this specific index. < 0 - negative values mean the process may run on on any thread **APART** from the thread with the absolute value of the index. Usually values of 1, -1 and 0 are used. Thread number 1 is the thread the VM started with. On some OSes this thread has special priviliges. I.e. on macOS only thread 1 can make draw calls. Therefore it is mostly important whether a thread must run on thread 1, must **not** run on thread 1 or whether it doesn't care. If successful the VM will ensure that there is at least one compatible thread active." | aProcess affinity waitingPriority activePriority | <export: true> self cCode: [] inSmalltalk: [cogThreadManager isNil ifTrue: [^self primitiveFail]]. processHasThreadAffinity ifFalse: [^self primitiveFailFor: PrimErrUnsupported]. affinity := self stackTop. aProcess := self stackValue: 1. ((affinity = objectMemory nilObject or: [(objectMemory isIntegerObject: affinity) and: [affinity ~= (objectMemory integerObjectOf: 0)]]) and: [(objectMemory isPointers: aProcess) and: [(objectMemory slotSizeOf: aProcess) >= (ThreadIdIndex + 1)]]) ifFalse: [^self primitiveFailFor: PrimErrBadArgument]. affinity := affinity = objectMemory nilObject ifTrue: [0] ifFalse: [objectMemory integerValueOf: affinity]. affinity abs >= cogThreadManager maxNumThreads ifTrue: [^self primitiveFailFor: PrimErrLimitExceeded]. + (self bindProcess: aProcess toAffinity: affinity) ifNotNil: [:ec| ^self primitiveFailFor: ec]. self methodReturnReceiver.
waitingPriority := self getMaxWaitingPriority. activePriority := self quickFetchInteger: PriorityIndex ofObject: aProcess. affinity := self threadAffinityOfProcess: aProcess. (aProcess = self activeProcess and: [(activeProcessAffined := affinity ~= 0) and: [(cogThreadManager vmOwnerIsCompatibleWith: affinity) not]]) ifTrue: [activePriority < waitingPriority ifTrue: [self reduceWaitingPriorityFrom: waitingPriority to: activePriority "TODO: Check if this is correct?"]. self threadSwitchIfNecessary: aProcess from: CSThreadBind]!
Item was changed: ----- Method: CoInterpreterMT>>temporaryAffinityOfProcess: (in category 'process primitive support') ----- temporaryAffinityOfProcess: aProcess + <inline: false> "useful for debugging so don't inline" "Answer the threadId of the thread threadIdField is temporarily bound to, or 0 if none." ^ self temporaryAffinedThreadId: (self threadAffinityFieldOf: aProcess)!
Item was changed: ----- Method: CoInterpreterMT>>threadAffinityOfProcess: (in category 'process primitive support') ----- threadAffinityOfProcess: aProcess + <inline: false> + "useful for debugging, so don't inline" ^self threadAffinityOfThreadID: (self threadAffinityFieldOf: aProcess)!
Item was changed: ----- Method: CoInterpreterMT>>threadSwitchIfNecessary:from: (in category 'process primitive support') ----- threadSwitchIfNecessary: newProc from: sourceCode "Invoked from transferTo:from: or primitiveProcessBindToThreadId to switch threads if the new process is bound or affined to some other thread." + | newProcThreadAffinity vmThread activeContext threadSwitchNecessary | - | newProcThreadAffinity vmThread activeContext | self assert: (cogThreadManager vmOwnerIs: cogThreadManager ioGetThreadLocalThreadIndex). deferThreadSwitch ifTrue: [^self].
cogThreadManager assertValidProcessorStackPointersForIndex: cogThreadManager getVMOwner.
"If the current process is unaffined or it is affined to the current thread we're ok to run, but we should yield asap if a higher-priority thread wants the VM." newProcThreadAffinity := self threadAffinityOfProcess: newProc. + threadSwitchNecessary := (activeProcessAffined := newProcThreadAffinity ~= 0) + and: [(cogThreadManager vmOwnerIsCompatibleWith: newProcThreadAffinity) not]. + threadSwitchNecessary ifFalse: - ((activeProcessAffined := newProcThreadAffinity ~= 0) - and: [(cogThreadManager vmOwnerIsCompatibleWith: newProcThreadAffinity) not]) ifFalse: [(self quickFetchInteger: PriorityIndex ofObject: newProc) < self getMaxWaitingPriority ifTrue: [checkThreadActivation := true. self forceInterruptCheck]. + "We're done, no thread switch necessary" ^self].
"The current process is affined to a thread, but not to the current owner. So switch to that owner." + self cCode: [] inSmalltalk: - self cCode: '' inSmalltalk: [transcript ensureCr; f: 'threadSwitchIfNecessary: %08x from: %s(%d) owner %d -> %d\n' printf: { newProc. TraceSources at: sourceCode. sourceCode. cogThreadManager getVMOwner. newProcThreadAffinity }].
+ "If the activeProcess doesn't have a context yet, it needs one from which the new thread can resume execution." - "We at least need to externalize the stack pointers to enable a thread switch..." (objectMemory fetchPointer: SuspendedContextIndex ofObject: newProc) = objectMemory nilObject ifTrue: [self assert: newProc = self activeProcess. + "The instructionPointer is popped from the stack in 'externalSetStackPageAndPointersForSuspendedContextOfProcess:' " self push: instructionPointer. + "We at least need to externalize the stack pointers to enable a thread switch..." self externalWriteBackHeadFramePointers. + activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer. + objectMemory storePointer: SuspendedContextIndex ofObject: newProc withValue: activeContext]. - false ifTrue: - "If the activeProcess doesn't have a context yet, it needs one from which the new thread can resume execution." - [activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer. - objectMemory storePointer: SuspendedContextIndex ofObject: newProc withValue: activeContext]].
newProcThreadAffinity < 0 ifTrue: [self assert: newProcThreadAffinity negated = cogThreadManager getVMOwner. vmThread := cogThreadManager ensureWillingThread. self deny: vmThread index = cogThreadManager getVMOwner. self assert: (cogThreadManager threadIndex: vmThread index isCompatibleWith: newProcThreadAffinity)] ifFalse: [vmThread := cogThreadManager vmThreadAt: newProcThreadAffinity. vmThread priority: (self quickFetchInteger: PriorityIndex ofObject: newProc). vmThread vmThreadState = CTMUnavailable ifTrue: [vmThread setVmThreadState: CTMWantingOwnership]]. self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: CSSwitchIfNeccessary!
Item was changed: ----- Method: CoInterpreterMT>>transferTo:from: (in category 'process primitive support') ----- transferTo: newProc from: sourceCode "Record a process to be awoken on the next interpreter cycle. Override to potentially switch threads either if the new process is bound to another thread, or if there is no runnable process but there is a waiting thread. Note that the abort on no runnable process has beeen moved here from wakeHighestPriority." | sched oldProc activeContext | <inline: false> statProcessSwitch := statProcessSwitch + 1. self push: instructionPointer. self externalWriteBackHeadFramePointers. self assertValidExecutionPointe: instructionPointer r: framePointer s: stackPointer. "ensureMethodIsCogged: in makeBaseFrameFor: in externalSetStackPageAndPointersForSuspendedContextOfProcess: below may do a code compaction. Nil instructionPointer to avoid it getting pushed twice." instructionPointer := 0. sched := self schedulerPointer. oldProc := objectMemory fetchPointer: ActiveProcessIndex ofObject: sched. self recordContextSwitchFrom: oldProc in: sourceCode. activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer + objectMemory wordSize. objectMemory storePointer: SuspendedContextIndex ofObject: oldProc withValue: activeContext.
newProc ifNil: ["Two possibilities. One, there is at least one thread waiting to own the VM in which case it should be activated. Two, there are no processes to run and so abort. This is new in the MT VM, and only happens when the primitiveRelinquishProcessor has been preempted. In that case the idle Process is not runnable and there is no Process to return to. By setting the activeProcess to nilObject, any threads woken by the heartbeat don't actually start running Smalltalk. This is then fixed when an AWOL thread comes back and restores its previous state." objectMemory storePointer: ActiveProcessIndex ofObject: sched withValue: objectMemory nilObject. cogThreadManager willingVMThread ifNotNil: [:vmThread| vmThread vmThreadState = CTMWantingOwnership ifTrue: [self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: sourceCode]]. "self error: 'scheduler could not find a runnable process'" + "relinquishing := true". + self returnToSchedulingLoopAndReleaseVMOrWakeThread: nil source: sourceCode]. - self returnToSchedulingLoopAndReleaseVMOrWakeThread: nil source: sourceCode].
"Switch to the new process" objectMemory storePointer: ActiveProcessIndex ofObject: sched withValue: newProc; storePointerUnchecked: MyListIndex ofObject: newProc withValue: objectMemory nilObject. self externalSetStackPageAndPointersForSuspendedContextOfProcess: newProc. "Finally thread switch if required" self threadSwitchIfNecessary: newProc from: sourceCode!
Item was changed: ----- Method: CoInterpreterMT>>tryToExecuteSmalltalk: (in category 'vm scheduling') ----- tryToExecuteSmalltalk: vmThread "Attempt to run the current process, if it exists, on the given vmThread." <var: #vmThread type: #'CogVMThread *'> | activeProc threadAffinity | self assert: (cogThreadManager vmOwnerIs: vmThread index). self assert: cogThreadManager ioGetThreadLocalThreadIndex = vmThread index.
disowningVMThread ifNil: [activeProc := self activeProcess] ifNotNil: [self preemptDisowningThread. activeProc := self wakeHighestPriority. activeProc ifNil: [activeProc := objectMemory nilObject] ifNotNil: [objectMemory storePointerUnchecked: MyListIndex ofObject: activeProc withValue: objectMemory nilObject]. objectMemory storePointer: ActiveProcessIndex ofObject: self schedulerPointer withValue: activeProc].
+ "There is a special case here. + When the VM has relinquished, but then another thread finishes external code execution, there may no longer be a process to run. + However, the relinquishing flag may already have been reset by another thread that has owned the VM again." + activeProc = objectMemory nilObject + ifTrue: ["self warning: 'tryToExecuteSmalltalk: no active process!!'." + "relinquishing := true". + ^nil]. - activeProc = objectMemory nilObject ifTrue:[^nil].
threadAffinity := self threadAffinityOfProcess: activeProc. (cogThreadManager vmOwnerIsCompatibleWith: threadAffinity) ifTrue: [self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject. (objectMemory fetchPointer: SuspendedContextIndex ofObject: activeProc) ~= objectMemory nilObject ifTrue: [self externalSetStackPageAndPointersForSuspendedContextOfProcess: activeProc]. instructionPointer = cogit ceReturnToInterpreterPC ifTrue: [self deny: (self isMachineCodeFrame: framePointer). instructionPointer := self iframeSavedIP: framePointer]. self enterSmalltalkExecutive. "When we return here we should have already given up the VM and so we cannot touch any interpreter state." self error: 'NOTREACHED'.]. cogThreadManager returnToSchedulingLoopAndWakeThreadFor: threadAffinity source: CSTryToExecuteSmalltalk. "This is only reached if the above call has failed, then ownership has not been transferred and we still need to release the VM."!
Hi Leon,
On Mar 20, 2024, at 7:48 AM, commits@source.squeak.org wrote:
Leon Matthes uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.threaded-LM.3355.mcz
==================== Summary ====================
Name: VMMaker.threaded-LM.3355 Author: LM Time: 20 March 2024, 3:45:46.213376 pm UUID: 537e53f3-b38a-4f82-a7d8-b3eeb232e2ce Ancestors: VMMaker.threaded-LM.3354
Fix a critical bug in threadSwitchIfNecessary:from: . If the activeProcess context was nil, it wasn't restored correctly. This has been fixed now.
This has a problem though, as we need to marry the activeProcess context every time we do a thread switch, this can cause a lot of GCs, as we create a lot of context objects. Maybe we can fix this later?
It’s the cost of doing business :-) But it is nothing to worry about. In any real application there will be many more block activations, and substantially more block instantiations. And the first block instantiation in any method activation requires a context instantiation. Luckily - scavenging gc is great at collecting short lived objects very very cheaply (because it works only on live objects; the dead objects are collected “for free”) - allocating contexts in Spur is cheap (so simple and cheap we can do it in generated machine code)
Eliot _,,,^..^,,,_ (phone)
=============== Diff against VMMaker.threaded-LM.3354 ===============
Item was changed: ----- Method: CoInterpreterMT>>ownVM:withFlags: (in category 'vm scheduling') ----- ownVM: vmThreadHandle withFlags: additionalFlags <public> <inline: false> <var: #vmThreadHandle type: #'void *'> <var: #vmThread type: #'CogVMThread *'> "This is the entry-point for plugins and primitives that wish to reacquire the VM after having released it via disownVM or callbacks that want to acquire it without knowing their ownership status. This call will block until the VM is owned by the current thread or an error occurs. The argument should be the value answered by disownVM, or 0 for callbacks that don't know if they have disowned or not. This is both an optimization to avoid having to query thread- local storage for the current thread's index (since it can easily keep it in some local variable), and a record of when an unbound process becomes affined to a thread for the dynamic extent of some operation.
Answer 0 if the current thread is known to the VM (and on return owns the VM). Answer 1 if the current thread is unknown to the VM and takes ownership. Answer -1 if the current thread is unknown to the VM and fails to take ownership." | flags vmThread | vmThread := self cCoerce: vmThreadHandle to: #'CogVMThread *'. vmThread ifNil: [^self ownVMFromUnidentifiedThread]. self assert: vmThread = (cogThreadManager vmThreadAt: vmThread index). flags := vmThread disownFlags bitOr: additionalFlags.
- vmThread := cogThreadManager acquireVMFor: vmThread.
- disownCount := disownCount - 1.
- (flags anyMask: DisownVMForProcessorRelinquish) ifTrue: ["Presumably we have nothing to do; this primitive is typically called from the background process. So we should /not/ try and activate any threads in the pool; they will waste cycles finding there is no runnable process, and will cause a VM abort if no runnable process is found. But we /do/ want to allow FFI calls that have completed, or callbacks a chance to get into the VM; they do have something to do. DisownVMForProcessorRelinquish indicates this." relinquishing := false. self sqLowLevelMFence].
vmThread := cogThreadManager acquireVMFor: vmThread.
disownCount := disownCount - 1.
disowningVMThread ifNotNil: [vmThread = disowningVMThread ifTrue: [self assert: (vmThread cFramePointer isNil or: [CFramePointer = vmThread cFramePointer and: [CStackPointer = vmThread cStackPointer]]). self assert: self successful. self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject. disowningVMThread := nil. cogit recordEventTrace ifTrue: [self recordTrace: TraceOwnVM thing: ConstOne source: 0]. ^0]. "if not preempted we're done." self preemptDisowningThread].
"We've been preempted; we must restore state and update the threadId in our process, and may have to put the active process to sleep." self restoreVMStateFor: vmThread andFlags: flags.
cogit recordEventTrace ifTrue: [self recordTrace: TraceOwnVM thing: ConstTwo source: 0]. ^flags bitAnd: OwnVMForeignThreadFlag!
Item was changed: ----- Method: CoInterpreterMT>>primitiveProcessBindToThreadAffinity (in category 'process primitives') ----- primitiveProcessBindToThreadAffinity "Attempt to bind the receiver to the thread affinity of the argument or nil, where the receiver is a Process. The thread affinity may be an integer where: 0 - means no thread affinity, the process is free to run on any thread. > 0 - positive values mean the process has to run on the thread with this specific index. < 0 - negative values mean the process may run on on any thread **APART** from the thread with the absolute value of the index.
Usually values of 1, -1 and 0 are used. Thread number 1 is the thread the VM started with. On some OSes this thread has special priviliges. I.e. on macOS only thread 1 can make draw calls. Therefore it is mostly important whether a thread must run on thread 1, must **not** run on thread 1 or whether it doesn't care. If successful the VM will ensure that there is at least one compatible thread active." | aProcess affinity waitingPriority activePriority | <export: true> self cCode: [] inSmalltalk: [cogThreadManager isNil ifTrue: [^self primitiveFail]]. processHasThreadAffinity ifFalse: [^self primitiveFailFor: PrimErrUnsupported]. affinity := self stackTop. aProcess := self stackValue: 1. ((affinity = objectMemory nilObject or: [(objectMemory isIntegerObject: affinity) and: [affinity ~= (objectMemory integerObjectOf: 0)]]) and: [(objectMemory isPointers: aProcess) and: [(objectMemory slotSizeOf: aProcess) >= (ThreadIdIndex + 1)]]) ifFalse: [^self primitiveFailFor: PrimErrBadArgument]. affinity := affinity = objectMemory nilObject ifTrue: [0] ifFalse: [objectMemory integerValueOf: affinity]. affinity abs >= cogThreadManager maxNumThreads ifTrue: [^self primitiveFailFor: PrimErrLimitExceeded].
(self bindProcess: aProcess toAffinity: affinity) ifNotNil: [:ec| ^self primitiveFailFor: ec]. self methodReturnReceiver.
waitingPriority := self getMaxWaitingPriority. activePriority := self quickFetchInteger: PriorityIndex ofObject: aProcess. affinity := self threadAffinityOfProcess: aProcess. (aProcess = self activeProcess and: [(activeProcessAffined := affinity ~= 0) and: [(cogThreadManager vmOwnerIsCompatibleWith: affinity) not]]) ifTrue: [activePriority < waitingPriority ifTrue: [self reduceWaitingPriorityFrom: waitingPriority to: activePriority "TODO: Check if this is correct?"]. self threadSwitchIfNecessary: aProcess from: CSThreadBind]!
Item was changed: ----- Method: CoInterpreterMT>>temporaryAffinityOfProcess: (in category 'process primitive support') ----- temporaryAffinityOfProcess: aProcess
- <inline: false> "useful for debugging so don't inline" "Answer the threadId of the thread threadIdField is temporarily bound to, or 0 if none." ^ self temporaryAffinedThreadId: (self threadAffinityFieldOf: aProcess)!
Item was changed: ----- Method: CoInterpreterMT>>threadAffinityOfProcess: (in category 'process primitive support') ----- threadAffinityOfProcess: aProcess
- <inline: false>
- "useful for debugging, so don't inline" ^self threadAffinityOfThreadID: (self threadAffinityFieldOf: aProcess)!
Item was changed: ----- Method: CoInterpreterMT>>threadSwitchIfNecessary:from: (in category 'process primitive support') ----- threadSwitchIfNecessary: newProc from: sourceCode "Invoked from transferTo:from: or primitiveProcessBindToThreadId to switch threads if the new process is bound or affined to some other thread."
- | newProcThreadAffinity vmThread activeContext threadSwitchNecessary |
| newProcThreadAffinity vmThread activeContext | self assert: (cogThreadManager vmOwnerIs: cogThreadManager ioGetThreadLocalThreadIndex). deferThreadSwitch ifTrue: [^self].
cogThreadManager assertValidProcessorStackPointersForIndex: cogThreadManager getVMOwner.
"If the current process is unaffined or it is affined to the current thread we're ok to run, but we should yield asap if a higher-priority thread wants the VM." newProcThreadAffinity := self threadAffinityOfProcess: newProc.
- threadSwitchNecessary := (activeProcessAffined := newProcThreadAffinity ~= 0)
and: [(cogThreadManager vmOwnerIsCompatibleWith: newProcThreadAffinity) not].
- threadSwitchNecessary ifFalse:
- ((activeProcessAffined := newProcThreadAffinity ~= 0)
and: [(cogThreadManager vmOwnerIsCompatibleWith: newProcThreadAffinity) not]) ifFalse: [(self quickFetchInteger: PriorityIndex ofObject: newProc) < self getMaxWaitingPriority ifTrue: [checkThreadActivation := true. self forceInterruptCheck].
"We're done, no thread switch necessary" ^self].
"The current process is affined to a thread, but not to the current owner. So switch to that owner."
self cCode: [] inSmalltalk:
- self cCode: '' inSmalltalk: [transcript ensureCr; f: 'threadSwitchIfNecessary: %08x from: %s(%d) owner %d -> %d\n' printf: { newProc. TraceSources at: sourceCode. sourceCode. cogThreadManager getVMOwner. newProcThreadAffinity }].
- "If the activeProcess doesn't have a context yet, it needs one from which the new thread can resume execution."
- "We at least need to externalize the stack pointers to enable a thread switch..." (objectMemory fetchPointer: SuspendedContextIndex ofObject: newProc) = objectMemory nilObject ifTrue: [self assert: newProc = self activeProcess.
"The instructionPointer is popped from the stack in 'externalSetStackPageAndPointersForSuspendedContextOfProcess:' " self push: instructionPointer.
"We at least need to externalize the stack pointers to enable a thread switch..." self externalWriteBackHeadFramePointers.
activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer.
objectMemory storePointer: SuspendedContextIndex ofObject: newProc withValue: activeContext].
false ifTrue:
"If the activeProcess doesn't have a context yet, it needs one from which the new thread can resume execution."
[activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer.
objectMemory storePointer: SuspendedContextIndex ofObject: newProc withValue: activeContext]].
newProcThreadAffinity < 0 ifTrue: [self assert: newProcThreadAffinity negated = cogThreadManager getVMOwner. vmThread := cogThreadManager ensureWillingThread. self deny: vmThread index = cogThreadManager getVMOwner. self assert: (cogThreadManager threadIndex: vmThread index isCompatibleWith: newProcThreadAffinity)] ifFalse: [vmThread := cogThreadManager vmThreadAt: newProcThreadAffinity. vmThread priority: (self quickFetchInteger: PriorityIndex ofObject: newProc). vmThread vmThreadState = CTMUnavailable ifTrue: [vmThread setVmThreadState: CTMWantingOwnership]]. self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: CSSwitchIfNeccessary!
Item was changed: ----- Method: CoInterpreterMT>>transferTo:from: (in category 'process primitive support') ----- transferTo: newProc from: sourceCode "Record a process to be awoken on the next interpreter cycle. Override to potentially switch threads either if the new process is bound to another thread, or if there is no runnable process but there is a waiting thread. Note that the abort on no runnable process has beeen moved here from wakeHighestPriority." | sched oldProc activeContext | <inline: false> statProcessSwitch := statProcessSwitch + 1. self push: instructionPointer. self externalWriteBackHeadFramePointers. self assertValidExecutionPointe: instructionPointer r: framePointer s: stackPointer. "ensureMethodIsCogged: in makeBaseFrameFor: in externalSetStackPageAndPointersForSuspendedContextOfProcess: below may do a code compaction. Nil instructionPointer to avoid it getting pushed twice." instructionPointer := 0. sched := self schedulerPointer. oldProc := objectMemory fetchPointer: ActiveProcessIndex ofObject: sched. self recordContextSwitchFrom: oldProc in: sourceCode. activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer + objectMemory wordSize. objectMemory storePointer: SuspendedContextIndex ofObject: oldProc withValue: activeContext.
newProc ifNil: ["Two possibilities. One, there is at least one thread waiting to own the VM in which case it should be activated. Two, there are no processes to run and so abort. This is new in the MT VM, and only happens when the primitiveRelinquishProcessor has been preempted. In that case the idle Process is not runnable and there is no Process to return to. By setting the activeProcess to nilObject, any threads woken by the heartbeat don't actually start running Smalltalk. This is then fixed when an AWOL thread comes back and restores its previous state." objectMemory storePointer: ActiveProcessIndex ofObject: sched withValue: objectMemory nilObject. cogThreadManager willingVMThread ifNotNil: [:vmThread| vmThread vmThreadState = CTMWantingOwnership ifTrue: [self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: sourceCode]]. "self error: 'scheduler could not find a runnable process'"
"relinquishing := true".
self returnToSchedulingLoopAndReleaseVMOrWakeThread: nil source: sourceCode].
self returnToSchedulingLoopAndReleaseVMOrWakeThread: nil source: sourceCode].
"Switch to the new process" objectMemory storePointer: ActiveProcessIndex ofObject: sched withValue: newProc; storePointerUnchecked: MyListIndex ofObject: newProc withValue: objectMemory nilObject. self externalSetStackPageAndPointersForSuspendedContextOfProcess: newProc. "Finally thread switch if required" self threadSwitchIfNecessary: newProc from: sourceCode!
Item was changed: ----- Method: CoInterpreterMT>>tryToExecuteSmalltalk: (in category 'vm scheduling') ----- tryToExecuteSmalltalk: vmThread "Attempt to run the current process, if it exists, on the given vmThread." <var: #vmThread type: #'CogVMThread *'> | activeProc threadAffinity | self assert: (cogThreadManager vmOwnerIs: vmThread index). self assert: cogThreadManager ioGetThreadLocalThreadIndex = vmThread index.
disowningVMThread ifNil: [activeProc := self activeProcess] ifNotNil: [self preemptDisowningThread. activeProc := self wakeHighestPriority. activeProc ifNil: [activeProc := objectMemory nilObject] ifNotNil: [objectMemory storePointerUnchecked: MyListIndex ofObject: activeProc withValue: objectMemory nilObject]. objectMemory storePointer: ActiveProcessIndex ofObject: self schedulerPointer withValue: activeProc].
- "There is a special case here.
- When the VM has relinquished, but then another thread finishes external code execution, there may no longer be a process to run.
- However, the relinquishing flag may already have been reset by another thread that has owned the VM again."
- activeProc = objectMemory nilObject
ifTrue: ["self warning: 'tryToExecuteSmalltalk: no active process!!'."
"relinquishing := true".
^nil].
activeProc = objectMemory nilObject ifTrue:[^nil].
threadAffinity := self threadAffinityOfProcess: activeProc. (cogThreadManager vmOwnerIsCompatibleWith: threadAffinity) ifTrue: [self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject. (objectMemory fetchPointer: SuspendedContextIndex ofObject: activeProc) ~= objectMemory nilObject ifTrue: [self externalSetStackPageAndPointersForSuspendedContextOfProcess: activeProc]. instructionPointer = cogit ceReturnToInterpreterPC ifTrue: [self deny: (self isMachineCodeFrame: framePointer). instructionPointer := self iframeSavedIP: framePointer]. self enterSmalltalkExecutive. "When we return here we should have already given up the VM and so we cannot touch any interpreter state." self error: 'NOTREACHED'.]. cogThreadManager returnToSchedulingLoopAndWakeThreadFor: threadAffinity source: CSTryToExecuteSmalltalk. "This is only reached if the above call has failed, then ownership has not been transferred and we still need to release the VM."!
vm-dev@lists.squeakfoundation.org