Eliot Miranda uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.oscog-eem.3357.mcz
==================== Summary ====================
Name: VMMaker.oscog-eem.3357 Author: eem Time: 26 March 2024, 12:49:10.503904 pm UUID: 576f7ca7-be48-4c44-a578-9507029c4f49 Ancestors: VMMaker.oscog-eem.3356
Integrate VMMaker.threaded-LM.3344 through 3356.
VMMaker.threaded-LM.3356 Author: LM (Leon Matthes) Time: 21 March 2024, 10:41:55 am
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.
VMMaker.threaded-LM.3355 Time: 20 March 2024, 3:45:46 pm
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?
VMMaker.threaded-LM.3354 Time: 19 March 2024, 2:33:25 pm
Increase Owner Log to 1Mi entries.
Some small additional adjustments
VMMaker.threaded-LM.3353 Time: 21 December 2023, 4:24:17 pm
Do not make primitives pin themselves, rather check for pinned arguments as a condition for disowning.
Also: Increase size of VM Owner Log to 100Ki entries. TODO: Make VMOwnerLog optional for release builds.
VMMaker.threaded-LM.3352 Time: 11 December 2023, 6:49:53 pm
Fix a critical issue that caused duplicate threads to be spawned.
Binding two processes (or the same process twice) to the same thread caused it to spawn two threads for the same CogVMThread, which of course caused critical errors as both threads might think they were in fact the vm owner, which caused all kinds of issues.
VMMaker.threaded-LM.3351 Time: 11 December 2023, 5:15:46 pm
Use Micro instead of milli-seconds for logging VM owner switches
VMMaker.threaded-LM.3350 Time: 11 December 2023, 5:12:31 pm
Make sure the InterpreterProxy and friends follow the new ownVM/disownVM API.
VMMaker.threaded-LM.3349 Time: 6 December 2023, 11:29:35 am
Log VM owner switches and add primitive to query the log.
VMMaker.threaded-LM.3348 Time: 23 November 2023, 5:07:58 pm
Fix VMProfileLinuxSupportPlugin>>#primitiveExectuableModules
64-bit linux handles the linux-gate.so.1 differently which lead to memory-corruption due to the primitive expecting to filter out a single item.
VMMaker.threaded-LM.3347 Time: 21 November 2023, 7:55:00 pm
Add argument to ownVM to provide custom flags.
Required for FFI callout flag when an exception occurs.
VMMaker.threaded-LM.3346 Time: 14 November 2023, 4:11:25 pm
Refactor pop operation to immediately remove MyList.
VMMaker.threaded-LM.3345 Time: 14 November 2023, 1:59:11 pm
No longer use the list of awol processes within CogVMThread. awol processes are now stored in the ProcessInExternalCodeTag.
Also do some refactorings to get threadAffinity and temporaryThreadAffinity in line.
VMMaker.threaded-LM.3344 Time: 9 November 2023, 5:33:02 pm
Fix multiple issues with process switching 1. Processes became re-activated when suspended 2. AWOL processes were garbage-collected 3. AWOL processes were stored in the CogVMThread itself, causing realloc to move the CogVMThread struct. This invalidated the previously created pointers, including the handles returned from disownVM!
=============== Diff against VMMaker.oscog-eem.3356 ===============
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]! - at: 229 + 1 put: #primitiveProcessBindToThreadId]!
Item was removed: - ----- Method: CoInterpreterMT>>affinedThreadId: (in category 'process primitive support') ----- - affinedThreadId: threadIdField - "Answer the threadId of the thread threadIdField is temporarily bound to, or 0 if none." - ^(objectMemory isIntegerObject: threadIdField) - ifTrue: [(objectMemory integerValueOf: threadIdField) anyMask: 1 << ThreadIdShift - 1] - ifFalse: [0]!
Item was added: + ----- Method: CoInterpreterMT>>bindProcess:toAffinity: (in category 'process primitive support') ----- + bindProcess: aProcess toAffinity: newAffinity + "Change a Process's thread binding and answer 0, otherwise answer a suitable error code. + Cases: + process is unbound & unaffined + affinity 0 nothing to do + affinity non-zero ensure thread and bind + process is affined (temporarily bound to a thread for the duration of a surrender of ownership) + affinity = affined index nothing to do + affinity = 0 nothing to do + affinity ~= 0 && affinity ~= affined index fail + process is bound (permanently bound to a thread) + affinity = bound index nothing to do + affinity ~= bound index set bound index" + | threadIdField currentAffinity temporaryAffinity | + processHasThreadAffinity ifFalse: + [^PrimErrUnsupported]. + + threadIdField := self threadAffinityFieldOf: aProcess. + currentAffinity := self threadAffinityOfThreadID: threadIdField. + temporaryAffinity := self temporaryAffinedThreadId: threadIdField. + + "If aProcess is affined (temporarily bound to) a thread then the operation can only + succeed if the newId is the same as that aProcess is affined to, or is zero (is unbinding)." + (self isTemporaryAffinedThreadId: threadIdField) ifTrue: + [(newAffinity = 0 or: [newAffinity = temporaryAffinity]) ifFalse: + [^PrimErrInappropriate]]. + + currentAffinity > 0 ifTrue: + [(self startThreadForThreadIndex: currentAffinity) ifFalse: + [^PrimErrLimitExceeded]]. + + self setThreadIdFieldOfProcess: aProcess toAffinity: newAffinity andTemporaryAffinity: temporaryAffinity. + ^nil!
Item was removed: - ----- Method: CoInterpreterMT>>bindProcess:toId: (in category 'process primitive support') ----- - bindProcess: aProcess toId: newId - "Change a Process's thread binding and answer 0, otherwise answer a suitable error code. - Cases: - process is unbound & unaffined - affinity 0 nothing to do - affinity non-zero ensure thread and bind - process is affined (temporarily bound to a thread for the duration of a surrender of ownership) - affinity = affined index nothing to do - affinity = 0 nothing to do - affinity ~= 0 && affinity ~= affined index fail - process is bound (permanently bound to a thread) - affinity = bound index nothing to do - affinity ~= bound index set bound index" - | threadIdField ownerIndex affinedId | - processHasThreadAffinity ifFalse: - [^PrimErrUnsupported]. - - threadIdField := self threadAffinityFieldOf: aProcess. - ownerIndex := self ownerIndexOfThreadId: threadIdField. - - "If aProcess is affined (temporarily bound to) a thread then the operation can only - succeed if the newId is the same as that aProcess is affined to, or is zero (is unbinding)." - (self isAffinedThreadId: threadIdField) ifTrue: - [affinedId := self affinedThreadId: threadIdField. - (newId = 0 - or: [newId = affinedId]) ifTrue: - [self setThreadIdFieldOfProcess: aProcess to: newId << ThreadIdShift + affinedId. - ^nil]. - ^PrimErrInappropriate]. - - ownerIndex > 0 ifTrue: - [(self startThreadForThreadIndex: ownerIndex) ifFalse: - [^PrimErrLimitExceeded]]. - - self setThreadIdFieldOfProcess: aProcess to: newId << ThreadIdShift. - ^nil!
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 removed: - ----- Method: CoInterpreterMT>>isAffinedProcess: (in category 'process primitive support') ----- - isAffinedProcess: aProcess - ^self isAffinedThreadId: (self threadAffinityFieldValueOf: aProcess)!
Item was removed: - ----- Method: CoInterpreterMT>>isAffinedThreadId: (in category 'process primitive support') ----- - isAffinedThreadId: threadIdField - "Answer if the threadId has the bits set indicating the thread it is temporarily bound to." - ^(self affinedThreadId: threadIdField) ~= 0!
Item was added: + ----- Method: CoInterpreterMT>>isTemporaryAffinedProcess: (in category 'process primitive support') ----- + isTemporaryAffinedProcess: aProcess + ^self isTemporaryAffinedThreadId: (self threadAffinityFieldValueOf: aProcess)!
Item was added: + ----- Method: CoInterpreterMT>>isTemporaryAffinedThreadId: (in category 'process primitive support') ----- + isTemporaryAffinedThreadId: threadIdField + "Answer if the threadId has the bits set indicating the thread it is temporarily bound to." + ^(self temporaryAffinedThreadId: threadIdField) ~= 0!
Item was changed: ----- Method: CoInterpreterMT>>loadInitialContext (in category 'initialization') ----- loadInitialContext + | activeProc threadAffinity | - | activeProc | super loadInitialContext. activeProc := self activeProcess. + threadAffinity := self threadAffinityOfProcess: activeProc. + + self assert: (threadAffinity = 0 or: [threadAffinity = 1]). + self cCode: [] inSmalltalk: [self flag: #todoMT "Ensure we cannot save an image, where the 'activeProc' is affined to another thread!!"]. + + activeProcessAffined := threadAffinity ~= 0! - self assert: (self threadAffinityOfProcess: activeProc) = 0. - activeProcessAffined := (self threadAffinityOfProcess: activeProc) ~= 0!
Item was changed: ----- Method: CoInterpreterMT>>ownVM: (in category 'vm scheduling') ----- ownVM: vmThreadHandle <public> <inline: false> <var: #vmThreadHandle type: #'void *'> + ^ self ownVM: vmThreadHandle withFlags: 0! - <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]. - - flags := vmThread disownFlags. - - (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 added: + ----- 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]. + + 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 removed: - ----- Method: CoInterpreterMT>>ownerIndexOfThreadId: (in category 'process primitive support') ----- - ownerIndexOfThreadId: threadId - ^(objectMemory isIntegerObject: threadId) - ifTrue: ["We need a signed shift here (>>>), as otherwise we lose the sign of the threadId." - (objectMemory integerValueOf: threadId) >>> ThreadIdShift] - ifFalse: [0]!
Item was added: + ----- Method: CoInterpreterMT>>popProcessWithTemporaryAffinity:fromList: (in category 'process primitive support') ----- + popProcessWithTemporaryAffinity: anAffinity fromList: aList + "Find the first process from the list that is temporarily affined to the given affinity. + Remove this process from the list and return it. + This is used by the preempt/restore flow to find the process that is to be restored." + | firstLink lastLink nextLink tempLink theProcess | + self assert: (anAffinity ~= 0). + self deny: (objectMemory isForwarded: aList). + "any process on the list could have been becomed, so use a read barrier..." + firstLink := objectMemory followField: FirstLinkIndex ofObject: aList. + lastLink := objectMemory followField: LastLinkIndex ofObject: aList. + "fail if any link doesn't look like a process..." + ((objectMemory isPointers: firstLink) + and: [(objectMemory numSlotsOf: firstLink) > MyListIndex]) ifFalse: + [^ objectMemory nilObject]. + + (firstLink ~= objectMemory nilObject and: [(self temporaryAffinityOfProcess: firstLink) = anAffinity]) + ifTrue: + [theProcess := firstLink. + nextLink := objectMemory followField: NextLinkIndex ofObject: firstLink. + objectMemory storePointer: FirstLinkIndex ofObject: aList withValue: nextLink. + firstLink = lastLink ifTrue: + [objectMemory storePointerUnchecked: LastLinkIndex ofObject: aList withValue: objectMemory nilObject]] + ifFalse: + [tempLink := firstLink. + [ + nextLink := objectMemory followField: NextLinkIndex ofObject: tempLink. + "fail if any link doesn't look like a process..." + ((objectMemory isPointers: nextLink) + and: [(objectMemory numSlotsOf: nextLink) > MyListIndex]) ifFalse: + [^ objectMemory nilObject]. + (self temporaryAffinityOfProcess: nextLink) = anAffinity] + whileFalse: [tempLink := nextLink]. + + theProcess := nextLink. + nextLink := objectMemory fetchPointer: NextLinkIndex ofObject: nextLink. + objectMemory storePointer: NextLinkIndex ofObject: tempLink withValue: nextLink. + theProcess = lastLink ifTrue: + [objectMemory storePointer: LastLinkIndex ofObject: aList withValue: tempLink]]. + objectMemory + storePointerUnchecked: NextLinkIndex ofObject: theProcess withValue: objectMemory nilObject; + storePointerUnchecked: MyListIndex ofObject: theProcess withValue: objectMemory nilObject. + ^ theProcess!
Item was changed: ----- Method: CoInterpreterMT>>preemptDisowningThread (in category 'vm scheduling') ----- preemptDisowningThread "Set the relevant state for disowningVMThread so that it can resume after being preempted and set disowningVMThread to nil to indicate preemption.
N.B. This should only be sent from checkPreemptionOfDisowningThread.
There are essentially four things to do. a) save the VM's notion of the current C stack pointers; these are pointers into a thread's stack and must be saved and restored in thread switch. b) save the VM's notion of the current Smalltalk execution point. This is simply the suspend half of a process switch that saves the current context in the current process. c) add the process to the thread's set of AWOL processes so that the scheduler won't try to run the process while the thread has disowned the VM. d) save the in-primitive VM state, newMethod and argumentCount
ownVM: will restore the VM context as of disownVM: from the above when it finds it has been preempted."
| activeProc activeContext preemptedThread | <var: #preemptedThread type: #'CogVMThread *'> <inline: false> self assert: disowningVMThread notNil. self assert: (disowningVMThread vmThreadState = CTMUnavailable or: [disowningVMThread vmThreadState = CTMWantingOwnership]). self assertCStackPointersBelongToDisowningThread. cogit recordEventTrace ifTrue: [self recordTrace: TracePreemptDisowningThread thing: (objectMemory integerObjectOf: disowningVMThread index) source: 0]. disowningVMThread cStackPointer: CStackPointer. disowningVMThread cFramePointer: CFramePointer. activeProc := self activeProcess. + - self assert: (objectMemory fetchPointer: MyListIndex ofObject: activeProc) = objectMemory nilObject. - objectMemory - storePointer: MyListIndex - ofObject: activeProc - withValue: (objectMemory splObj: ProcessInExternalCodeTag). activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer. objectMemory storePointer: SuspendedContextIndex ofObject: activeProc withValue: activeContext. "The instructionPointer must be pushed because the convention for inactive stack pages is that the instructionPointer is top of stack. We need to know if this primitive is called from machine code because the invariant that the return pc of an interpreter callee calling a machine code caller is ceReturnToInterpreterPC must be maintained." self push: instructionPointer. self externalWriteBackHeadFramePointers. "Since pushing the awol process may realloc disowningVMThread we need to reassign. But since we're going to nil disowningVMThread anyway we can assign to a local." + preemptedThread := disowningVMThread. - preemptedThread := cogThreadManager pushAWOLProcess: activeProc on: disowningVMThread. disowningVMThread := nil. + + "Store the process in the ProcessInExternalCodeTag special object (a LinkedList). + This ensures: + - The process isn't garbage collected + - If the process is moved due to GC, we can still find it + - The process is available from inside the image + On restore we can use the temporaryThreadAffinity to find the last process that was disowned from the preempted thread. + This therefore creates a LIFO stack for each thread which are all interleaved in this one list." + self assert: (self isTemporaryAffinedProcess: activeProc) not. + self setTemporaryThreadAffinityOfProcess: activeProc to: preemptedThread index. + self addFirstLink: activeProc toList: (objectMemory splObj: ProcessInExternalCodeTag). + - (self threadAffinityOfProcess: activeProc) = 0 ifTrue: - [self setTemporaryThreadAffinityOfProcess: activeProc to: preemptedThread index bind: false]. preemptedThread newMethodOrNull: newMethod; argumentCount: argumentCount; inMachineCode: instructionPointer <= objectMemory startOfMemory!
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 added: + ----- 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 removed: - ----- Method: CoInterpreterMT>>primitiveProcessBindToThreadId (in category 'process primitives') ----- - primitiveProcessBindToThreadId - "Attempt to bind the receiver to the thread with the id of the argument or nil, where the receiver is a Process. - If successful the VM will ensure that there are at least id many threads active." - | aProcess id waitingPriority activePriority | - <export: true> - self cCode: [] inSmalltalk: [cogThreadManager isNil ifTrue: [^self primitiveFail]]. - processHasThreadAffinity ifFalse: - [^self primitiveFailFor: PrimErrUnsupported]. - id := self stackTop. - aProcess := self stackValue: 1. - ((id = objectMemory nilObject or: [(objectMemory isIntegerObject: id) - and: [id ~= (objectMemory integerObjectOf: 0)]]) - and: [(objectMemory isPointers: aProcess) - and: [(objectMemory slotSizeOf: aProcess) >= (ThreadIdIndex + 1)]]) ifFalse: - [^self primitiveFailFor: PrimErrBadArgument]. - id := id = objectMemory nilObject ifTrue: [0] ifFalse: [objectMemory integerValueOf: id]. - id abs >= cogThreadManager maxNumThreads ifTrue: - [^self primitiveFailFor: PrimErrLimitExceeded]. - (self bindProcess: aProcess toId: id) ifNotNil: - [:ec| ^self primitiveFailFor: ec]. - self methodReturnReceiver. - - waitingPriority := self getMaxWaitingPriority. - activePriority := self quickFetchInteger: PriorityIndex ofObject: aProcess. - id := self threadAffinityOfProcess: aProcess. - (aProcess = self activeProcess - and: [(activeProcessAffined := id ~= 0) - and: [(cogThreadManager vmOwnerIsCompatibleWith: id) 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>>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 added: + ----- Method: CoInterpreterMT>>removeFirstProcessWithTemporaryAffinity:fromList: (in category 'process primitive support') ----- + removeFirstProcessWithTemporaryAffinity: anAffinity fromList: aList + + "Find the first process from the list that is temporarily affined to the given affinity. + Remove this process from the list and return it. + This is used by the preempt/restore flow to find the process that is to be restored." + | firstLink lastLink nextLink tempLink theProcess | + self assert: (anAffinity ~= 0). + self deny: (objectMemory isForwarded: aList). + "any process on the list could have been becomed, so use a read barrier..." + firstLink := objectMemory followField: FirstLinkIndex ofObject: aList. + lastLink := objectMemory followField: LastLinkIndex ofObject: aList. + (firstLink ~= objectMemory nilObject and: [(self temporaryAffinityOfProcess: firstLink) = anAffinity]) + ifTrue: + [theProcess := firstLink. + nextLink := objectMemory followField: NextLinkIndex ofObject: firstLink. + objectMemory storePointer: FirstLinkIndex ofObject: aList withValue: nextLink. + firstLink = lastLink ifTrue: + [objectMemory storePointerUnchecked: LastLinkIndex ofObject: aList withValue: objectMemory nilObject]] + ifFalse: + [tempLink := firstLink. + ["fail if any link doesn't look like a process..." + ((objectMemory isPointers: tempLink) + and: [(objectMemory numSlotsOf: tempLink) > MyListIndex]) ifFalse: + [^false]. + nextLink := objectMemory followField: NextLinkIndex ofObject: tempLink. + nextLink ~= objectMemory nilObject and: [(self temporaryAffinityOfProcess: nextLink) = anAffinity]] + whileFalse: [tempLink := nextLink]. + + nextLink = objectMemory nilObject ifTrue: [^ false]. + + theProcess := nextLink. + nextLink := objectMemory fetchPointer: NextLinkIndex ofObject: nextLink. + objectMemory storePointer: NextLinkIndex ofObject: tempLink withValue: nextLink. + theProcess = lastLink ifTrue: + [objectMemory storePointer: LastLinkIndex ofObject: aList withValue: tempLink]]. + objectMemory storePointerUnchecked: NextLinkIndex ofObject: theProcess withValue: objectMemory nilObject. + ^true!
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]). - ifFalse: [myProc := cogThreadManager popAWOLProcess: vmThread]. - self assert: 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]. - self assert: (objectMemory fetchPointer: MyListIndex ofObject: myProc) = (objectMemory splObj: ProcessInExternalCodeTag). objectMemory + storePointerUnchecked: MyListIndex ofObject: myProc withValue: objectMemory nilObject; + storePointer: ActiveProcessIndex ofObject: sched withValue: myProc. + + self setTemporaryThreadAffinityOfProcess: myProc to: 0. - storePointer: ActiveProcessIndex ofObject: sched withValue: myProc; - storePointerUnchecked: MyListIndex ofObject: myProc withValue: objectMemory nilObject. - "Only unaffine if the process was affined at this level and did not become bound in the interim." - ((flags anyMask: ProcessUnaffinedOnDisown) - and: [(self isBoundProcess: myProc) not]) ifTrue: - [self setTemporaryThreadAffinityOfProcess: myProc to: 0 bind: false]. 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 added: + ----- Method: CoInterpreterMT>>setTemporaryThreadAffinityOfProcess:to: (in category 'process primitive support') ----- + setTemporaryThreadAffinityOfProcess: aProcess to: anIndex + "When a thread is disowned for threading, it will temporarily affine the process to itself. + Therefore we can make sure no one else accidentally tries to re-bind the process whilst it is AWOL." + | threadAffinity | + threadAffinity := self threadAffinityOfProcess: aProcess. + self setThreadIdFieldOfProcess: aProcess toAffinity: threadAffinity andTemporaryAffinity: anIndex.!
Item was removed: - ----- Method: CoInterpreterMT>>setTemporaryThreadAffinityOfProcess:to:bind: (in category 'process primitive support') ----- - setTemporaryThreadAffinityOfProcess: aProcess to: anIndex bind: bind - | threadId | - threadId := anIndex = 0 - ifTrue: [objectMemory nilObject] - ifFalse: [objectMemory integerObjectOf: (anIndex << 1) + (bind ifTrue: [1] ifFalse: [0])]. - objectMemory storePointerUnchecked: ThreadIdIndex ofObject: aProcess withValue: threadId!
Item was removed: - ----- Method: CoInterpreterMT>>setThreadAffinityOfProcess:to:bind: (in category 'process primitive support') ----- - setThreadAffinityOfProcess: aProcess to: anIndex bind: bind - | threadId | - threadId := anIndex = 0 - ifTrue: [objectMemory nilObject] - ifFalse: [objectMemory integerObjectOf: (anIndex << 1) + (bind ifTrue: [1] ifFalse: [0])]. - objectMemory storePointerUnchecked: ThreadIdIndex ofObject: aProcess withValue: threadId!
Item was removed: - ----- Method: CoInterpreterMT>>setThreadIdFieldOfProcess:to: (in category 'process primitive support') ----- - setThreadIdFieldOfProcess: aProcess to: threadIdField - | threadIdSlot | - threadIdSlot := threadIdField = 0 - ifTrue: [objectMemory nilObject] - ifFalse: [objectMemory integerObjectOf: threadIdField]. - objectMemory storePointerUnchecked: ThreadIdIndex ofObject: aProcess withValue: threadIdSlot!
Item was added: + ----- Method: CoInterpreterMT>>setThreadIdFieldOfProcess:toAffinity:andTemporaryAffinity: (in category 'process primitive support') ----- + setThreadIdFieldOfProcess: aProcess toAffinity: threadAffinity andTemporaryAffinity: anIndex + | threadIdSlot bits | + self assert: anIndex >= 0. + self assert: anIndex <= cogThreadManager maxNumThreads. + self assert: threadAffinity >= cogThreadManager maxNumThreads negated. + self assert: threadAffinity <= cogThreadManager maxNumThreads. + + anIndex > 0 ifTrue: [self assert: (cogThreadManager threadIndex: anIndex isCompatibleWith: threadAffinity)]. + + bits := threadAffinity << ThreadIdShift + anIndex. + + threadIdSlot := bits = 0 + ifTrue: [objectMemory nilObject] + ifFalse: [objectMemory integerObjectOf: bits]. + objectMemory storePointerUnchecked: ThreadIdIndex ofObject: aProcess withValue: threadIdSlot!
Item was added: + ----- Method: CoInterpreterMT>>temporaryAffinedThreadId: (in category 'process primitive support') ----- + temporaryAffinedThreadId: threadIdField + "Answer the threadId of the thread threadIdField is temporarily bound to, or 0 if none." + ^(objectMemory isIntegerObject: threadIdField) + ifTrue: [(objectMemory integerValueOf: threadIdField) bitAnd: 1 << ThreadIdShift - 1] + ifFalse: [0]!
Item was added: + ----- 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>>threadAffinityFieldValueOf: (in category 'process primitive support') ----- threadAffinityFieldValueOf: aProcess + <inline: false> "Should not be inlined, as it's useful for debugging." ^processHasThreadAffinity ifTrue: [| field | field := objectMemory fetchPointer: ThreadIdIndex ofObject: aProcess. field = objectMemory nilObject ifTrue: [0] ifFalse: [objectMemory integerValueOf: field]] ifFalse: [0]!
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)! - ^self ownerIndexOfThreadId: (self threadAffinityFieldOf: aProcess)!
Item was added: + ----- Method: CoInterpreterMT>>threadAffinityOfThreadID: (in category 'process primitive support') ----- + threadAffinityOfThreadID: threadId + ^(objectMemory isIntegerObject: threadId) + ifTrue: ["We need a signed shift here (>>>), as otherwise we lose the sign of the threadId." + (objectMemory integerValueOf: threadId) >>> ThreadIdShift] + ifFalse: [0]!
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 | 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 }].
+ "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." - "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. - self push: instructionPointer. - self externalWriteBackHeadFramePointers. - 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. + - case it should be activated. Two, there are no processes to run and so abort." 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. + "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."!
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: ----- Method: CoInterpreterPrimitives>>primitiveSuspend (in category 'process primitives') ----- primitiveSuspend "Primitive #88. Suspend the receiver, aProcess, such that it can be executed again by sending #resume. If the given process is not the active process, take it off its corresponding list. The primitive returns the list the receiver was previously on. c.f. primitiveSuspendBackingUpV1,#568 & primitiveSuspendBackingUpV2,#578" | process myList myContext ok | process := self stackTop. process = self activeProcess ifTrue: [| inInterpreter | "We're going to switch process, either to an interpreted frame or a machine code frame. To know whether to return or enter machine code we have to know from whence we came. We could have come from the interpreter, either directly or via a machine code primitive. We could have come from machine code. The instructionPointer tells us where from:" self stackTopPut: objectMemory nilObject. inInterpreter := instructionPointer >= objectMemory startOfMemory. self transferTo: self wakeHighestPriority from: CSSuspend. ^self forProcessPrimitiveReturnToExecutivePostContextSwitch: inInterpreter]. myList := objectMemory fetchPointer: MyListIndex ofObject: process. myContext := objectMemory fetchPointer: SuspendedContextIndex ofObject: process. ((objectMemory isPointers: myList) and: [(objectMemory numSlotsOf: myList) > LastLinkIndex and: [(objectMemory isContext: myContext) and: [self isResumableContext: myContext]]]) ifFalse: [^self primitiveFailFor: PrimErrBadReceiver]. ok := self removeProcess: process fromList: myList. ok ifFalse: [^self primitiveFailFor: PrimErrOperationFailed]. - objectMemory storePointerUnchecked: MyListIndex ofObject: process withValue: objectMemory nilObject. self stackTopPut: myList!
Item was changed: ----- Method: CoInterpreterPrimitives>>primitiveSuspendBackingUpV1 (in category 'process primitives') ----- primitiveSuspendBackingUpV1 "Primitive #568. Suspend the receiver, aProcess, such that it can be executed again by sending #resume. If the given process is not the active process, take it off its corresponding list. If the list was not its run queue assume it was on some condition variable (Semaphore, Mutex) and back up its pc to the send that invoked the wait state the process entered. Hence when the process resumes it will reenter the wait state. Answer the list the receiver was previously on, unless it was the activeProcess, in which case answer nil. c.f. primitiveSuspend,#88 & primitiveSuspendBackingUpV2,#578" | process myList myContext ok | process := self stackTop. process = self activeProcess ifTrue: [| inInterpreter | "We're going to switch process, either to an interpreted frame or a machine code frame. To know whether to return or enter machine code we have to know from whence we came. We could have come from the interpreter, either directly or via a machine code primitive. We could have come from machine code. The instructionPointer tells us where from:" self stackTopPut: objectMemory nilObject. inInterpreter := instructionPointer >= objectMemory startOfMemory. self transferTo: self wakeHighestPriority from: CSSuspend. ^self forProcessPrimitiveReturnToExecutivePostContextSwitch: inInterpreter]. myList := objectMemory fetchPointer: MyListIndex ofObject: process. myContext := objectMemory fetchPointer: SuspendedContextIndex ofObject: process. ((objectMemory isPointers: myList) and: [(objectMemory numSlotsOf: myList) > LastLinkIndex and: [(objectMemory isContext: myContext) and: [self isResumableContext: myContext]]]) ifFalse: [^self primitiveFailFor: PrimErrBadReceiver]. ok := self removeProcess: process fromList: myList. ok ifFalse: [^self primitiveFailFor: PrimErrOperationFailed]. - objectMemory storePointerUnchecked: MyListIndex ofObject: process withValue: objectMemory nilObject. (objectMemory fetchClassTagOfNonImm: myList) ~= classLinkedListClassTag ifTrue: [self backupContext: myContext toBlockingSendTo: myList]. self stackTopPut: myList!
Item was changed: ----- Method: CoInterpreterPrimitives>>primitiveSuspendBackingUpV2 (in category 'process primitives') ----- primitiveSuspendBackingUpV2 "Primitive #578. Suspend the receiver, aProcess, such that it can be executed again by sending #resume. If the given process is not the active process, take it off its corresponding list. If the list was not its run queue assume it was on some condition variable (Semaphore, Mutex) and back up its pc to the send that invoked the wait state the process entered. Hence when the process resumes it will reenter the wait state. Answer the list the receiver was previously on iff it was not active and not blocked, otherwise answer nil. c.f. primitiveSuspend,#88 & primitiveSuspendBackingUpV1,#568, which always answer the list the process was on, even if blocked." <export: true> | process myList myContext ok | process := self stackTop. process = self activeProcess ifTrue: [| inInterpreter | "We're going to switch process, either to an interpreted frame or a machine code frame. To know whether to return or enter machine code we have to know from whence we came. We could have come from the interpreter, either directly or via a machine code primitive. We could have come from machine code. The instructionPointer tells us where from:" self stackTopPut: objectMemory nilObject. inInterpreter := instructionPointer >= objectMemory startOfMemory. self transferTo: self wakeHighestPriority from: CSSuspend. ^self forProcessPrimitiveReturnToExecutivePostContextSwitch: inInterpreter]. myList := objectMemory fetchPointer: MyListIndex ofObject: process. myContext := objectMemory fetchPointer: SuspendedContextIndex ofObject: process. ((objectMemory isPointers: myList) and: [(objectMemory numSlotsOf: myList) > LastLinkIndex and: [(objectMemory isContext: myContext) and: [self isResumableContext: myContext]]]) ifFalse: [^self primitiveFailFor: PrimErrBadReceiver]. ok := self removeProcess: process fromList: myList. ok ifFalse: [^self primitiveFailFor: PrimErrOperationFailed]. - objectMemory storePointerUnchecked: MyListIndex ofObject: process withValue: objectMemory nilObject. (objectMemory fetchClassTagOfNonImm: myList) ~= classLinkedListClassTag ifTrue: [self backupContext: myContext toBlockingSendTo: myList. self stackTopPut: objectMemory nilObject] ifFalse: [self stackTopPut: myList]!
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 * 1024 "1M entries for logging owner switches".! - 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: [ |entriesToSpare| + "NOTE: The owner log is still written to whilst this primitive is running. + Therefore copy only 90% of the log, such that the remaining 10% can be spared + to still be written." + entriesToSpare := OwnerLogSize / 10. + startIndex := index + entriesToSpare 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>>growThreadInfosToAtLeast: (in category 'thread set') ----- growThreadInfosToAtLeast: index "Grow the thread infos to at least index in numThreadsIncrement quanta." | newThreads newNumThreads | <var: #newThreads type: #'CogVMThread **'> <inline: false> memoryIsScarce ifTrue: [^false]. newNumThreads := index + numThreadsIncrement - 1 // numThreadsIncrement * numThreadsIncrement. newNumThreads >= self maxNumThreads ifTrue: [^false]. "Since 0 is not a valid index we allocate one extra CogVMThread and use 1-relative indices." newThreads := self cCode: 'realloc(GIV(threads), (newNumThreads + 1) * sizeof(CogVMThread *))' inSmalltalk: [(Array new: newNumThreads) replaceFrom: 1 to: numThreads with: threads startingAt: 1]. + + newThreads ifNil: [memoryIsScarce := true. ^ false]. - (newThreads notNil - and: [self populate: newThreads from: numThreads + 1 to: newNumThreads]) ifFalse: - [ - "TODO: This cannot free 'newThreads', as that's going to mean 'threads' is freed as well." - self abort. - self free: newThreads. - memoryIsScarce := true. - ^false]. threads := newThreads. + + (self populate: newThreads from: numThreads + 1 to: newNumThreads) + ifTrue: [numThreads := newNumThreads. + ^true] + ifFalse: ["Allocation of new threads may fail, even after the array has been moved. + If this is the case, simply do not increase the number of useable threads. + The old ones will still point to the right addresses, they'll just be in a new list + which technically has too much space, but that doesn't hurt anything." + memoryIsScarce := true. + ^false]. + ! - numThreads := newNumThreads. - ^true!
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 changed: ----- Method: CogThreadManager>>populate:from:to: (in category 'thread set') ----- populate: vmThreadPointers from: start to: finish "Populate vmThreadPointers with vmThreads over the given range." <var: #vmThreadPointers type: #'CogVMThread **'> | nThreads vmThreads | <var: #vmThreads type: #'CogVMThread *'> <var: #vmThread type: #'CogVMThread *'> <inline: true> nThreads := finish - start + 1. vmThreads := self cCode: [self calloc: nThreads _: (self sizeof: CogVMThread)] inSmalltalk: [CArrayAccessor on: ((1 to: nThreads) collect: [:ign| CogVMThread new])]. vmThreads ifNil: [^false]. "Since 0 is not a valid index, in C we allocate one extra CogVMThread and use 1-relative indices." self cCode: [start = 1 ifTrue: [vmThreadPointers at: 0 put: nil]] inSmalltalk: []. start to: finish do: [:i| | vmThread | vmThread := self addressOf: (vmThreads at: i - start). vmThread initializeThreadState. (self ioNewOSSemaphore: (self addressOf: vmThread osSemaphore put: [:sem| vmThread osSemaphore: sem])) ~= 0 ifTrue: [start to: i - 1 do: [:j| vmThread := self addressOf: (vmThreads at: j - start). self ioDestroyOSSemaphore: (self addressOf: vmThread osSemaphore)]. self free: vmThreads. ^false]. vmThreadPointers at: i put: vmThread. - vmThread awolProcLength: AWOLProcessesIncrement. vmThread index: i. self cCode: [] inSmalltalk: [vmThread reenterThreadSchedulingLoop: ReenterThreadSchedulingLoop new]]. ^true!
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 removed: - ----- Method: CogThreadManager>>pushAWOLProcess:on: (in category 'public api') ----- - pushAWOLProcess: awolProcess on: vmThread - <var: #vmThread type: #'CogVMThread *'> - <returnTypeC: #'CogVMThread *'> - | cvt | - <var: #cvt type: #'CogVMThread *'> - cvt := vmThread. - self assert: (cvt awolProcIndex between: 0 and: cvt awolProcLength). - cvt awolProcIndex >= cvt awolProcLength ifTrue: - ["The realloc doesn't look like it grows but it does so by AWOLProcessesIncrement - entries because sizeof(CogVMThread) includes room for that many entries." - cvt := self cCode: 'realloc(cvt,sizeof(CogVMThread) + (sizeof(sqInt) * cvt->awolProcLength))' - inSmalltalk: [cvt growAWOLProcesses]. - threads at: vmThread index put: cvt. - cvt awolProcLength: cvt awolProcLength + AWOLProcessesIncrement]. - cvt awolProcesses at: cvt awolProcIndex put: awolProcess. - cvt awolProcIndex: cvt awolProcIndex + 1. - ^cvt!
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 ioUTCMicrosecondsNow. + 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>>startThreadForThreadIndex: (in category 'scheduling') ----- startThreadForThreadIndex: index + self assert: index > 0. index > numThreads ifTrue: [(self growThreadInfosToAtLeast: index) ifFalse: [^false]]. ^self startThreadForThreadInfo: (self vmThreadAt: index)!
Item was changed: ----- Method: CogThreadManager>>startThreadForThreadInfo: (in category 'scheduling') ----- startThreadForThreadInfo: vmThread <var: #vmThread type: #'CogVMThread *'> <inline: false> + vmThread vmThreadState ~= CTMUninitialized + ifTrue: [^true "Already started"]. + self assert: vmThread vmThreadState = CTMUninitialized. vmThread setVmThreadState: CTMInitializing. "self cCode: '' inSmalltalk: [coInterpreter transcript cr; nextPutAll: 'starting VM thread '; print: vmThread index; flush. (thisContext home stackOfSize: 10) do: [:ctxt| coInterpreter transcript cr; print: ctxt; flush]]." (self ioNewOS: (self cCoerce: #startVMThread: to: 'void (*)(void*)') Thread: vmThread) = 0 ifTrue: [self ioTransferTimeslice. ^true]. memoryIsScarce := true. "self cCode: [coInterpreter print: 'ERVT failed to spawn so memory is scarce'; cr]" ^false!
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 removed: - ----- Method: CogVMSimulator>>bindProcess:toId: (in category 'multi-threading simulation switch') ----- - bindProcess: aProcess toId: newId - "This method includes or excludes CoInterpreterMT methods as required. - Auto-generated by CogVMSimulator>>ensureMultiThreadingOverridesAreUpToDate" - - ^self perform: #bindProcess:toId: - withArguments: {aProcess. newId} - inSuperclass: (cogThreadManager ifNil: [CoInterpreterPrimitives] ifNotNil: [CoInterpreterMT])!
Item was removed: - ----- Method: CogVMSimulator>>isAffinedProcess: (in category 'multi-threading simulation switch') ----- - isAffinedProcess: aProcess - "This method includes or excludes CoInterpreterMT methods as required. - Auto-generated by CogVMSimulator>>ensureMultiThreadingOverridesAreUpToDate" - - ^self perform: #isAffinedProcess: - withArguments: {aProcess} - inSuperclass: (cogThreadManager ifNil: [CoInterpreterPrimitives] ifNotNil: [CoInterpreterMT])!
Item was removed: - ----- Method: CogVMSimulator>>isAffinedThreadId: (in category 'multi-threading simulation switch') ----- - isAffinedThreadId: threadId - "This method includes or excludes CoInterpreterMT methods as required. - Auto-generated by CogVMSimulator>>ensureMultiThreadingOverridesAreUpToDate" - - ^self perform: #isAffinedThreadId: - withArguments: {threadId} - inSuperclass: (cogThreadManager ifNil: [CoInterpreterPrimitives] ifNotNil: [CoInterpreterMT])!
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 removed: - ----- Method: CogVMSimulator>>ownerIndexOfThreadId: (in category 'multi-threading simulation switch') ----- - ownerIndexOfThreadId: threadId - "This method includes or excludes CoInterpreterMT methods as required. - Auto-generated by CogVMSimulator>>ensureMultiThreadingOverridesAreUpToDate" - - ^self perform: #ownerIndexOfThreadId: - withArguments: {threadId} - inSuperclass: (cogThreadManager ifNil: [CoInterpreterPrimitives] ifNotNil: [CoInterpreterMT])!
Item was added: + ----- Method: CogVMSimulator>>primitiveProcessBindToThreadAffinity (in category 'multi-threading simulation switch') ----- + primitiveProcessBindToThreadAffinity + "This method includes or excludes CoInterpreterMT methods as required. + Auto-generated by CogVMSimulator>>ensureMultiThreadingOverridesAreUpToDate" + + ^self perform: #primitiveProcessBindToThreadAffinity + withArguments: {} + inSuperclass: (cogThreadManager ifNil: [CoInterpreterPrimitives] ifNotNil: [CoInterpreterMT])!
Item was removed: - ----- Method: CogVMSimulator>>primitiveProcessBindToThreadId (in category 'multi-threading simulation switch') ----- - primitiveProcessBindToThreadId - "This method includes or excludes CoInterpreterMT methods as required. - Auto-generated by CogVMSimulator>>ensureMultiThreadingOverridesAreUpToDate" - - ^self perform: #primitiveProcessBindToThreadId - withArguments: {} - inSuperclass: (cogThreadManager ifNil: [CoInterpreterPrimitives] ifNotNil: [CoInterpreterMT])!
Item was changed: VMStructType subclass: #CogVMThread + instanceVariableNames: 'index state priority osSemaphore osThread disownFlags newMethodOrNull argumentCount inMachineCode cStackPointer cFramePointer reenterThreadSchedulingLoop awolProcIndex awolProcLength awolProcesses' - instanceVariableNames: 'index state priority osSemaphore osThread disownFlags newMethodOrNull argumentCount inMachineCode cStackPointer cFramePointer awolProcIndex awolProcLength awolProcesses reenterThreadSchedulingLoop' classVariableNames: '' poolDictionaries: 'VMThreadingConstants' category: 'VMMaker-Multithreading'!
!CogVMThread commentStamp: '<historical>' prior: 0! Instances of this class represent control blocks for native threads that cooperatively schedule the VM. See the class comment of CogThreadManager for full documentation.
N.B. awolProcesses must be the last inst var.!
Item was changed: ----- Method: CogVMThread 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: (ivn caseOf: { + ['awolProcesses'] -> [#'sqInt *']. - ['awolProcesses'] -> [{#sqInt. '[', CogThreadManager awolProcessesIncrement printString, ']'}]. ['cStackPointer'] -> [#usqIntptr_t]. ['cFramePointer'] -> [#usqIntptr_t]. ['osSemaphore'] -> ['sqOSSemaphore']. ['osThread'] -> ['sqOSThread']. ['reenterThreadSchedulingLoop'] -> ['jmp_buf']. ['state'] -> ['volatile atomic_int'] } otherwise: [#sqInt])]!
Item was removed: - ----- Method: CogVMThread>>growAWOLProcesses (in category 'simulation only') ----- - growAWOLProcesses - <doNotGenerate> - awolProcesses setObject: awolProcesses object, (Array new: CogThreadManager awolProcessesIncrement)!
Item was changed: ----- Method: CogVMThread>>initializeThreadState (in category 'initialize-release') ----- initializeThreadState "Unfortunately this cannot be inlined as Slang otherwise screws up the generation of the `atomic_store` call." <inline: false> "In comparision to #initialize, this is also called in C code to initialize the VMThread, not just in the Smalltalk simulation." self cCode: [] inSmalltalk: [state := AtomicValue new]. + self atomic_store: (self addressOf: self state) _: CTMUninitialized. + + self + cCode: [awolProcesses := self malloc: AWOLProcessesIncrement * (self sizeof: #sqInt)] + inSmalltalk: [awolProcesses := CArrayAccessor on: (Array new: AWOLProcessesIncrement)]. + awolProcIndex := 0. + awolProcLength := AWOLProcessesIncrement.! - self atomic_store: (self addressOf: self state) _: CTMUninitialized.!
Item was changed: ----- Method: FilePlugin>>primitiveFileReadPinningAndDisowning (in category 'file primitives') ----- primitiveFileReadPinningAndDisowning "This version of primitiveFileRead is for garbage collectors that support pinning + and the multi-threaded VM. It actually does the own/disown dance if the bytearray is pinned." + | count startIndex array file slotSize elementSize bytesRead vmHandle | - and the multi-threaded VM. It actually does the own/disown dance." - | count startIndex array file slotSize elementSize bytesRead vmHandle wasPinned | <inline: true> <var: 'file' type: #'SQFile *'> <var: 'count' type: #'size_t'> <var: 'startIndex' type: #'size_t'> <var: 'slotSize' type: #'size_t'> <var: 'elementSize' type: #'size_t'> count := interpreterProxy positiveMachineIntegerValueOf: (interpreterProxy stackValue: 0). startIndex := interpreterProxy positiveMachineIntegerValueOf: (interpreterProxy stackValue: 1). array := interpreterProxy stackValue: 2. file := self fileValueOf: (interpreterProxy stackValue: 3). + vmHandle := nil.
(interpreterProxy failed "buffer can be any indexable words or bytes object except CompiledMethod" or: [(interpreterProxy isWordsOrBytes: array) not]) ifTrue: [^interpreterProxy primitiveFailFor: PrimErrBadArgument].
slotSize := interpreterProxy slotSizeOf: array. (startIndex >= 1 and: [startIndex + count - 1 <= slotSize]) ifFalse: [^interpreterProxy primitiveFailFor: PrimErrBadIndex]. - (wasPinned := interpreterProxy isPinned: array) ifFalse: - [array := interpreterProxy pinObject: array]. - vmHandle := interpreterProxy disownVM: DisownVMForThreading. "Note: adjust startIndex for zero-origin byte indexing" elementSize := slotSize = 0 ifTrue: [1] ifFalse: [(interpreterProxy byteSizeOf: array) // slotSize]. + + (interpreterProxy isPinned: array) ifTrue: + [vmHandle := interpreterProxy disownVM: DisownVMForThreading]. bytesRead := self sqFile: file Read: count * elementSize Into: (interpreterProxy cCoerce: (interpreterProxy firstIndexableField: array) to: #'char *') At: startIndex - 1 * elementSize. + vmHandle ifNotNil: + [interpreterProxy ownVM: vmHandle]. + - interpreterProxy ownVM: vmHandle. - wasPinned ifFalse: - [interpreterProxy unpinObject: array]. interpreterProxy failed ifFalse: [interpreterProxy methodReturnInteger: bytesRead // elementSize] "answer # of elements read"!
Item was changed: ----- Method: InterpreterPrimitives>>primitiveFailForFFIException:at: (in category 'primitive support') ----- primitiveFailForFFIException: exceptionCode at: pc <var: 'exceptionCode' type: #usqLong> <var: 'pc' type: #usqInt> "Set PrimErrFFIException primitive failure and associated exceptionCode (a.k.a. secondaryErrorCode) and exceptionPC. Under control of the ffiExceptionResponse flag, if in a primitive with an error code and the inFFIFlags indicate we're in an FFI call, then fail the primitive. ffiExceptionResponse < 0 never fail ffiExceptionResponse = 0 fail if method has a primitive error code (default) ffiExceptionResponse > 0 always fail" <public> ((inFFIFlags noMask: DisownVMForFFICall) "i.e. not in an FFI call" or: [ffiExceptionResponse < 0]) ifTrue: "i.e. never fail" [^self]. secondaryErrorCode := self cCoerceSimple: exceptionCode to: #sqLong. exceptionPC := pc. primFailCode := PrimErrFFIException. (ffiExceptionResponse > 0 "always fail..." or: [(objectMemory isOopCompiledMethod: newMethod) and: [self methodUsesPrimitiveErrorCode: newMethod]]) ifTrue: + [self ownVM: nil withFlags: DisownVMForFFICall. "To take ownership but importantly to reset inFFIFlags" - [self ownVM: DisownVMForFFICall. "To take ownership but importantly to reset inFFIFlags" self activateFailingPrimitiveMethod]!
Item was added: + ----- Method: InterpreterPrimitives>>primitiveProcessBindToThreadAffinity (in category 'process primitives') ----- + primitiveProcessBindToThreadAffinity + "Simulation only. Fail." + <doNotGenerate> + self primitiveFail!
Item was removed: - ----- Method: InterpreterPrimitives>>primitiveProcessBindToThreadId (in category 'process primitives') ----- - primitiveProcessBindToThreadId - "Simulation only. Fail." - <doNotGenerate> - self primitiveFail!
Item was added: + ----- Method: InterpreterPrimitives>>primitiveVMProfileThreadedSamplesInto (in category 'process primitives') ----- + primitiveVMProfileThreadedSamplesInto + "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 | + <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: + [^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). + self methodReturnInteger: numSamples!
Item was changed: ----- Method: InterpreterProxy>>disownVM: (in category 'FFI support') ----- disownVM: flags + <returnTypeC: #'void *'> ^self notYetImplementedError!
Item was changed: ----- Method: InterpreterProxy>>ownVM: (in category 'FFI support') ----- + ownVM: handle + <var: #handle type: #'void*'> - ownVM: flags ^self notYetImplementedError!
Item was changed: ----- Method: NewObjectMemory>>disownVM: (in category 'simulation only') ----- disownVM: flags + <returnTypeC: #'void*'> "hack around the CoInterpreter/ObjectMemory split refactoring" <doNotGenerate> ^coInterpreter disownVM: flags!
Item was changed: ----- Method: NewObjectMemory>>ownVM: (in category 'simulation only') ----- + ownVM: handle - ownVM: flags "hack around the CoInterpreter/ObjectMemory split refactoring" <doNotGenerate> + <var: #handle type: #'void *'> + ^coInterpreter ownVM: handle! - ^coInterpreter ownVM: flags!
Item was changed: ----- Method: SocketPlugin>>primitiveSocketCloseConnection: (in category 'primitives') ----- primitiveSocketCloseConnection: socket
| s | + <var: #s type: #SocketPtr> + self primitive: 'primitiveSocketCloseConnection' parameters: #(Oop). - <var: #s type: 'SocketPtr '> - self primitive: 'primitiveSocketCloseConnection' - parameters: #(Oop). s := self socketValueOf: socket. + interpreterProxy failed ifFalse: + [self cppIf: COGMTVM + ifTrue: [self sqSocketCloseConnection: s isPinned: (interpreterProxy isPinned: socket)] + ifFalse: [self sqSocketCloseConnection: s]]! - interpreterProxy failed ifFalse: [ - self sqSocketCloseConnection: s]!
Item was added: + ----- Method: SocketPlugin>>sqSocketCloseConnection:isPinned: (in category 'simulation') ----- + sqSocketCloseConnection: socketHandleCArray isPinned: isPinned + <private> + <option: #COGMTVM> + <inline: #always> + | result handle | + isPinned ifTrue: [handle := interpreterProxy disownVM: DisownVMForThreading]. + result := self sqSocketCloseConnection: socketHandleCArray. + isPinned ifTrue: [interpreterProxy ownVM: handle]. + ^ result!
Item was changed: ----- Method: SpurMemoryManager>>disownVM: (in category 'simulation only') ----- disownVM: flags + <returnTypeC: #'void*'> "hack around the CoInterpreter/ObjectMemory split refactoring" <doNotGenerate> ^coInterpreter disownVM: flags!
Item was changed: ----- Method: SpurMemoryManager>>ownVM: (in category 'simulation only') ----- + ownVM: handle - ownVM: flags "hack around the CoInterpreter/ObjectMemory split refactoring" <doNotGenerate> + <var: #handle type: #'void *'> + ^coInterpreter ownVM: handle! - ^coInterpreter ownVM: flags!
Item was changed: ----- Method: SpurMemoryManager>>printOopsFrom:to: (in category 'debug printing') ----- printOopsFrom: startAddress to: endAddress <public> "useful for VM debugging" | oop limit firstNonEntity inEmptySpace lastNonEntity | oop := self objectBefore: startAddress. limit := endAddress asUnsignedIntegerPtr min: endOfMemory. oop := oop ifNil: [startAddress] ifNotNil: [(self objectAfter: oop) = startAddress ifTrue: [startAddress] ifFalse: [oop]]. + inEmptySpace := false. - inEmptySpace := false. [self oop: oop isLessThan: limit] whileTrue: [self printEntity: oop. [oop := self objectAfter: oop. (self long64At: oop) = 0] whileTrue: [inEmptySpace ifFalse: [inEmptySpace := true. firstNonEntity := oop]. lastNonEntity := oop]. inEmptySpace ifTrue: [inEmptySpace := false. coInterpreter print: 'skipped empty space from '; printHexnp: firstNonEntity; print:' to '; printHexnp: lastNonEntity; cr. oop := self objectStartingAt: oop]]!
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 vmHandle canDisown | - | 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. + canDisown := (interpreterProxy isPinned: srcOop) and: [interpreterProxy isPinned: dstOop]. + canDisown ifTrue: [vmHandle := interpreterProxy disownVM: DisownVMForThreading]. - vmHandle := interpreterProxy disownVM: DisownVMForThreading. + result := self sqConnectSSL: handle _: srcPtr _: srcLen _: dstPtr _: dstLen. + + canDisown ifTrue: [interpreterProxy ownVM: vmHandle]. - result := self cCode: 'sqConnectSSL(handle, srcPtr, srcLen, dstPtr, dstLen)' - inSmalltalk:[handle. srcPtr. srcLen. dstPtr. dstLen. -2]. + interpreterProxy failed ifFalse: + [interpreterProxy methodReturnInteger: result]! - 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: ----- Method: StackInterpreter>>ownVM: (in category 'vm scheduling') ----- ownVM: threadIndexAndFlags <public> <inline: false> + ^ self ownVM: threadIndexAndFlags withFlags: 0! - "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. While this exists for the threaded FFI VM we use it to reset newMethod and the - argumentCount after a callback. - - Answer -1 if the current thread is unknown to the VM and fails to take ownership." - <var: 'amInVMThread' declareC: 'extern sqInt amInVMThread(void)'> - self cppIf: COGMTVM - ifTrue: - [self amInVMThread ifFalse: - [^-1]]. - - self assert: ((objectMemory isOopCompiledMethod: newMethod) - and: [(self argumentCountOf: newMethod) = argumentCount]). - - threadIndexAndFlags = DisownVMForThreading ifTrue: - [^threadIndexAndFlags]. - - "Hack encodings of client state. We use non-immediates (bottom three bits clear) - for FFI/Plugin doing - save := self disownVM: FLAGS. ... callout ... self ownVM: save. - We use immediate integer (bottom bit 1) for callbacks doing - save := self ownVM: 0. ... callback ... self disownVM: save. return to C" - - "If DisownVMForFFICall this is from the FFI plugin and we're returning from a callout." - (threadIndexAndFlags anyMask: DisownVMForFFICall) ifTrue: - [inFFIFlags := 0. - ^threadIndexAndFlags]. - - "Otherwise this is a callback; stash newMethod on the stack and encode - argumentCount in the flags retrieved when the calback calls disownVM:." - self assert: primFailCode = 0. - self push: newMethod. - ^objectMemory integerObjectOf: argumentCount!
Item was added: + ----- Method: StackInterpreter>>ownVM:withFlags: (in category 'vm scheduling') ----- + ownVM: threadIndexAndFlags withFlags: additionalFlags + <public> + <inline: false> + "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. While this exists for the threaded FFI VM we use it to reset newMethod and the + argumentCount after a callback. + + Answer -1 if the current thread is unknown to the VM and fails to take ownership." + <var: 'amInVMThread' declareC: 'extern sqInt amInVMThread(void)'> + | flags | + flags := threadIndexAndFlags bitOr: additionalFlags. + self cppIf: COGMTVM + ifTrue: + [self amInVMThread ifFalse: + [^-1]]. + + self assert: ((objectMemory isOopCompiledMethod: newMethod) + and: [(self argumentCountOf: newMethod) = argumentCount]). + + "Hack encodings of client state. We use non-immediates (bottom three bits clear) + for FFI/Plugin doing + save := self disownVM: FLAGS. ... callout ... self ownVM: save. + We use immediate integer (bottom bit 1) for callbacks doing + save := self ownVM: 0. ... callback ... self disownVM: save. return to C" + + "If DisownVMForFFICall this is from the FFI plugin and we're returning from a callout." + (flags anyMask: DisownVMForFFICall) ifTrue: + [inFFIFlags := 0. + ^flags]. + + "Otherwise this is a callback; stash newMethod on the stack and encode + argumentCount in the flags retrieved when the calback calls disownVM:." + self assert: primFailCode = 0. + self push: newMethod. + ^objectMemory integerObjectOf: argumentCount!
Item was changed: ----- Method: StackInterpreter>>removeProcess:fromList: (in category 'process primitive support') ----- removeProcess: aProcess fromList: aList "Attempt to remove a process from a linked list. Answer if the attempt succeeded." | firstLink lastLink nextLink tempLink | self deny: (objectMemory isForwarded: aProcess). self deny: (objectMemory isForwarded: aList). "any process on the list could have been becomed, so use a read barrier..." firstLink := objectMemory followField: FirstLinkIndex ofObject: aList. lastLink := objectMemory followField: LastLinkIndex ofObject: aList. aProcess = firstLink ifTrue: [nextLink := objectMemory followField: NextLinkIndex ofObject: aProcess. objectMemory storePointer: FirstLinkIndex ofObject: aList withValue: nextLink. aProcess = lastLink ifTrue: [objectMemory storePointerUnchecked: LastLinkIndex ofObject: aList withValue: objectMemory nilObject]] ifFalse: [tempLink := firstLink. ["fail if any link doesn't look like a process..." ((objectMemory isPointers: tempLink) and: [(objectMemory numSlotsOf: tempLink) > MyListIndex]) ifFalse: [^false]. nextLink := objectMemory followField: NextLinkIndex ofObject: tempLink. nextLink = aProcess] whileFalse: [tempLink := nextLink]. nextLink := objectMemory fetchPointer: NextLinkIndex ofObject: aProcess. objectMemory storePointer: NextLinkIndex ofObject: tempLink withValue: nextLink. aProcess = lastLink ifTrue: [objectMemory storePointer: LastLinkIndex ofObject: aList withValue: tempLink]]. objectMemory storePointerUnchecked: NextLinkIndex ofObject: aProcess withValue: objectMemory nilObject. + objectMemory storePointerUnchecked: MyListIndex ofObject: aProcess withValue: objectMemory nilObject. ^true!
Item was changed: ----- Method: StackInterpreterPrimitives>>primitiveSuspend (in category 'process primitives') ----- primitiveSuspend "Primitive #88. Suspend the receiver, aProcess, such that it can be executed again by sending #resume. If the given process is not the active process, take it off its corresponding list. The primitive returns the list the receiver was previously on. c.f. primitiveSuspendBackingUpV1,#568 & primitiveSuspendBackingUpV2,#578" | process myList myContext ok | process := self stackTop. process = self activeProcess ifTrue: [self stackTopPut: objectMemory nilObject. ^self transferTo: self wakeHighestPriority]. myList := objectMemory fetchPointer: MyListIndex ofObject: process. myContext := objectMemory fetchPointer: SuspendedContextIndex ofObject: process. ((objectMemory isPointers: myList) and: [(objectMemory numSlotsOf: myList) > LastLinkIndex and: [(objectMemory isContext: myContext) and: [self isResumableContext: myContext]]]) ifFalse: [^self primitiveFailFor: PrimErrBadReceiver]. ok := self removeProcess: process fromList: myList. ok ifFalse: [^self primitiveFailFor: PrimErrOperationFailed]. - objectMemory storePointerUnchecked: MyListIndex ofObject: process withValue: objectMemory nilObject. self stackTopPut: myList!
Item was changed: ----- Method: StackInterpreterPrimitives>>primitiveSuspendBackingUpV1 (in category 'process primitives') ----- primitiveSuspendBackingUpV1 "Primitive #568. Suspend the receiver, aProcess, such that it can be executed again by sending #resume. If the given process is not the active process, take it off its corresponding list. If the list was not its run queue assume it was on some condition variable (Semaphore, Mutex) and back up its pc to the send that invoked the wait state the process entered. Hence when the process resumes it will reenter the wait state. Answer the list the receiver was previously on, unless it was the activeProcess, in which case answer nil. c.f. primitiveSuspend,#88 & primitiveSuspendBackingUpV2,#578" | process myList myContext ok | process := self stackTop. process = self activeProcess ifTrue: [self stackTopPut: objectMemory nilObject. ^self transferTo: self wakeHighestPriority]. myList := objectMemory fetchPointer: MyListIndex ofObject: process. myContext := objectMemory fetchPointer: SuspendedContextIndex ofObject: process. ((objectMemory isPointers: myList) and: [(objectMemory numSlotsOf: myList) > LastLinkIndex and: [(objectMemory isContext: myContext) and: [self isResumableContext: myContext]]]) ifFalse: [^self primitiveFailFor: PrimErrBadReceiver]. ok := self removeProcess: process fromList: myList. ok ifFalse: [^self primitiveFailFor: PrimErrOperationFailed]. - objectMemory storePointerUnchecked: MyListIndex ofObject: process withValue: objectMemory nilObject. (objectMemory fetchClassTagOfNonImm: myList) ~= classLinkedListClassTag ifTrue: [self backupContext: myContext toBlockingSendTo: myList]. self stackTopPut: myList!
Item was changed: ----- Method: StackInterpreterPrimitives>>primitiveSuspendBackingUpV2 (in category 'process primitives') ----- primitiveSuspendBackingUpV2 "Primitive #578. Suspend the receiver, aProcess, such that it can be executed again by sending #resume. If the given process is not the active process, take it off its corresponding list. If the list was not its run queue assume it was on some condition variable (Semaphore, Mutex) and back up its pc to the send that invoked the wait state the process entered. Hence when the process resumes it will reenter the wait state. Answer the list the receiver was previously on iff it was not active and not blocked, otherwise answer nil. c.f. primitiveSuspend,#88 & primitiveSuspendBackingUpV1,#568, which always answer the list the process was on, even if blocked." <export: true> | process myList myContext ok | process := self stackTop. process = self activeProcess ifTrue: [self stackTopPut: objectMemory nilObject. ^self transferTo: self wakeHighestPriority]. myList := objectMemory fetchPointer: MyListIndex ofObject: process. myContext := objectMemory fetchPointer: SuspendedContextIndex ofObject: process. ((objectMemory isPointers: myList) and: [(objectMemory numSlotsOf: myList) > LastLinkIndex and: [(objectMemory isContext: myContext) and: [self isResumableContext: myContext]]]) ifFalse: [^self primitiveFailFor: PrimErrBadReceiver]. ok := self removeProcess: process fromList: myList. ok ifFalse: [^self primitiveFailFor: PrimErrOperationFailed]. - objectMemory storePointerUnchecked: MyListIndex ofObject: process withValue: objectMemory nilObject. (objectMemory fetchClassTagOfNonImm: myList) ~= classLinkedListClassTag ifTrue: [self backupContext: myContext toBlockingSendTo: myList. self stackTopPut: objectMemory nilObject] ifFalse: [self stackTopPut: myList]!
Item was changed: ----- Method: VMProfileLinuxSupportPlugin>>primitiveExecutableModules (in category 'primitives') ----- primitiveExecutableModules "Answer an Array of pairs of strings for executable modules (the VM executable and loaded libraries). The first element in each pair is the filename of the module. The second element is either nil or the symlink's target, if the filename is a symlink." <export: true> | resultObj | numModules := 0. self dl_iterate_phdr: #countnummodules _: 0. resultObj := interpreterProxy instantiateClass: interpreterProxy classArray + indexableSize: numModules * 2. - indexableSize: numModules - 1 * 2. "skip the fake linux-gate.so.1" resultObj = 0 ifTrue: [^interpreterProxy primitiveFailFor: PrimErrNoMemory]. interpreterProxy pushRemappableOop: resultObj. primErr := numModules := 0. self dl_iterate_phdr: #reapmodulesymlinks _: 0. resultObj := interpreterProxy popRemappableOop. primErr ~= 0 ifTrue: [^interpreterProxy primitiveFailFor: primErr]. ^interpreterProxy methodReturnValue: resultObj!
Item was changed: ----- Method: VMProfileLinuxSupportPlugin>>reap:module:symlinks: (in category 'iteration callbacks') ----- reap: info module: size symlinks: ignored "like reap:module:names:, but follows symlinks" <var: 'info' type: #'struct dl_phdr_info *'> <var: 'size' type: #'size_t'> <var: 'ignored' type: #'void *'> <returnTypeC: #int> | elfModuleName len moduleNameObj symLinkBuf | <var: 'elfModuleName' type: #'const char *'> <var: 'symLinkBuf' declareC: 'char symLinkBuf[PATH_MAX]'> elfModuleName := numModules > 0 ifTrue: [info dlpi_name] ifFalse: [self getAttributeString: 0]. (elfModuleName isNil or: [(len := self strlen: elfModuleName) = 0]) ifTrue: + [^0]. "skip the fake linux-gate.so.1 --- NOTE: On 64-bit linux this is linux-vdso.so.1 + and doesn't seem to appear as a zero-length/nullptr string!! + It also doesn't seem to hurt anything to leave it in the list." - [^0]. "skip the fake linux-gate.so.1" moduleNameObj := interpreterProxy instantiateClass: interpreterProxy classString indexableSize: len. moduleNameObj = 0 ifTrue: [primErr := PrimErrNoMemory. ^1]. "stop iteration" self strncpy: (interpreterProxy arrayValueOf: moduleNameObj) _: elfModuleName _: len. "(char *)strncpy()" interpreterProxy storePointer: numModules ofObject: interpreterProxy topRemappableOop withValue: moduleNameObj. "now dereference the symlink, if it exists" self str: symLinkBuf cpy: elfModuleName. (len := self read: elfModuleName li: symLinkBuf nk: #'PATH_MAX') > 0 ifTrue: [moduleNameObj := interpreterProxy instantiateClass: interpreterProxy classString indexableSize: len. moduleNameObj = 0 ifTrue: [primErr := PrimErrNoMemory. ^1]. "stop iteration" self strncpy: (interpreterProxy arrayValueOf: moduleNameObj) _: symLinkBuf _: len. "(char *)strncpy()" interpreterProxy storePointer: numModules + 1 ofObject: interpreterProxy topRemappableOop withValue: moduleNameObj] ifFalse: [interpreterProxy storePointer: numModules + 1 ofObject: interpreterProxy topRemappableOop withValue: interpreterProxy nilObject]. numModules := numModules + 2. ^0!
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