Leon Matthes uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.threaded-LM.3356.mcz
==================== Summary ====================
Name: VMMaker.threaded-LM.3356 Author: LM Time: 21 March 2024, 10:41:55.233376 am UUID: bc5d6545-faa3-4e8e-8b64-8bd3c8ee48fe Ancestors: VMMaker.threaded-LM.3355
Optimize threadSwitchIfNecessary:from:
We only really need to marry the context when we pass the interpreter to a thread that schedules in its own process. Otherwise the thread can just take over the interpreter as-is.
=============== Diff against VMMaker.threaded-LM.3355 ===============
Item was added: + ----- Method: CoInterpreterMT>>ensureProcessHasContext: (in category 'vm scheduling') ----- + ensureProcessHasContext: aProcess + + | activeContext | + (objectMemory fetchPointer: SuspendedContextIndex ofObject: aProcess) = objectMemory nilObject ifTrue: + [self assert: aProcess = 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: aProcess withValue: activeContext].!
Item was changed: ----- Method: CoInterpreterMT>>restoreVMStateFor:andFlags: (in category 'vm scheduling') ----- restoreVMStateFor: vmThread andFlags: flags "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." | sched activeProc myProc | sched := self schedulerPointer. activeProc := objectMemory fetchPointer: ActiveProcessIndex ofObject: sched. (flags anyMask: OwnVMForeignThreadFlag) ifTrue: [self assert: foreignCallbackProcessSlot == ForeignCallbackProcess. myProc := objectMemory splObj: foreignCallbackProcessSlot. self assert: myProc ~= objectMemory nilObject. objectMemory splObj: foreignCallbackProcessSlot put: objectMemory nilObject] ifFalse: [myProc := self popProcessWithTemporaryAffinity: vmThread index fromList: (objectMemory splObj: ProcessInExternalCodeTag)]. self assert: (myProc ~= objectMemory nilObject and: [activeProc ~= myProc]). (activeProc ~= objectMemory nilObject and: [(objectMemory fetchPointer: MyListIndex ofObject: activeProc) = objectMemory nilObject]) ifTrue: + ["If the activeProcess doesn't have a context yet, it needs one from which we can resume later. + This mostly only happens when a threadSwitchIfNecessary:from: ends up switching to a thread that's CTMUnavailable (this thread). + See the comment in threadSwitchIfNecessary:from:" + self ensureProcessHasContext: activeProc. + self putToSleep: activeProc yieldingIf: preemptionYields]. - [self putToSleep: activeProc yieldingIf: preemptionYields]. objectMemory storePointerUnchecked: MyListIndex ofObject: myProc withValue: objectMemory nilObject; storePointer: ActiveProcessIndex ofObject: sched withValue: myProc.
self setTemporaryThreadAffinityOfProcess: myProc to: 0. self initPrimCall. self cCode: [self externalSetStackPageAndPointersForSuspendedContextOfProcess: myProc] inSmalltalk: ["Bypass the no-offset stack depth check in the simulator's externalSetStackPageAndPointersForSuspendedContextOfProcess:" super externalSetStackPageAndPointersForSuspendedContextOfProcess: myProc. "We're in ownVM:, hence in a primitive, hence need to include the argument count" (self isMachineCodeFrame: framePointer) ifTrue: [self maybeCheckStackDepth: vmThread argumentCount sp: stackPointer pc: instructionPointer]]. "If this primitive is called from machine code maintain the invariant that the return pc of an interpreter callee calling a machine code caller is ceReturnToInterpreterPC." (vmThread inMachineCode and: [instructionPointer >= objectMemory startOfMemory]) ifTrue: [self iframeSavedIP: framePointer put: instructionPointer. instructionPointer := cogit ceReturnToInterpreterPC]. newMethod := vmThread newMethodOrNull. argumentCount := vmThread argumentCount. vmThread newMethodOrNull: nil. self cCode: '' inSmalltalk: [| range | range := self cStackRangeForThreadIndex: vmThread index. self assert: ((range includes: vmThread cStackPointer) and: [range includes: vmThread cFramePointer])]. self setCFramePointer: vmThread cFramePointer setCStackPointer: vmThread cStackPointer. self assert: newMethod notNil !
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 threadSwitchNecessary | - | newProcThreadAffinity vmThread activeContext threadSwitchNecessary | 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: [(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: [transcript ensureCr; f: 'threadSwitchIfNecessary: %08x from: %s(%d) owner %d -> %d\n' printf: { newProc. TraceSources at: sourceCode. sourceCode. cogThreadManager getVMOwner. newProcThreadAffinity }].
+ "In most cases, we can just switch the thread here, without externalizing the stack pages. + If the Processes context is nil, it's state is on the stack. As we're already done context switching, + the new thread can just use the interpreter state as-is, without restoring the state from the context. + + tryToExecuteSmalltalk: already includes a check whether the SuspendedContext is nil. + If it is, it leaves the interpreter state alone and just assumes it's correct. + This is nice and fast. + Otherwise it calls externalSetStackPageAndPointersForSuspendedContextOfProcess: to restore the interpreter state. + + There is however a special case. When we switch to a thread that is currently CTMUnavailable, that thread will need + to restore its process when it tries to own the VM again. + The check to restore the context has been moved there (in restoreVMStateFor:andFlags:), so that it only happens in + that one case and not every time. + In case there are other such special-cases later, adding a call to ensureProcessHasContext: here should fix it." - "If the activeProcess doesn't have a context yet, it needs one from which the new thread can resume execution." - (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].
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>>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].
threadAffinity := self threadAffinityOfProcess: activeProc. (cogThreadManager vmOwnerIsCompatibleWith: threadAffinity) ifTrue: [self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject. + "If we switch threads in threadSwitchIfNecessary:from:, the interpreter state is likely + already in the correct state. + In that case, there is no suspended context and nothing to restore. We can just continue + execution. + If there is a suspended context, assume that we need to restore the state from that." (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