Leon Matthes uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.threaded-LM.3341.mcz
==================== Summary ====================
Name: VMMaker.threaded-LM.3341 Author: LM Time: 3 October 2023, 7:03:09.087577 pm UUID: ec2245e8-492b-4c66-9f42-a836ba795a7d Ancestors: VMMaker.threaded-LM.3339
Allow -1 to be set as vmOwner. This is currently used by the heartbeat thread. In future this will be used by the ownVMFromUnidentifiedThread function.
This combines well with the additional change that makes disownVM return a CogVMThread* disguised as void*.
We can then ensure that the threads variable is only ever accessed by the vm owner.
=============== Diff against VMMaker.threaded-LM.3339 ===============
Item was added: + ----- Method: AtomicValue>>printOn: (in category 'as yet unclassified') ----- + printOn: aStream + + aStream nextPutAll: 'Atomic: '. + self value printOn: aStream.!
Item was changed: ----- Method: CCodeGenerator>>addStructClass: (in category 'public') ----- addStructClass: aClass "Add the non-accessor methods of the given struct class to the code base."
aClass prepareToBeAddedToCodeGenerator: self. self addClassVarsFor: aClass. self addPoolVarsFor: aClass. self retainMethods: (aClass requiredMethodNames: self options). 'Adding Class ' , aClass name , '...' displayProgressAt: Sensor cursorPoint from: 0 to: aClass selectors size during: [:bar | aClass selectors withIndexDo: [:sel :i | | source | bar value: i. self addStructMethodFor: aClass selector: sel]]. aClass declareCVarsIn: self!
Item was changed: ----- Method: CCodeGenerator>>addStructMethodFor:selector: (in category 'utilities') ----- addStructMethodFor: aClass selector: selector "Add the given struct method to the code base and answer its translation or nil if it shouldn't be translated." (self methodNamed: selector) ifNotNil: [:tmethod| "If we're repeating an attempt to add the same thing, or if the existing method overrides this one,don't complain." (tmethod definingClass includesBehavior: aClass) ifTrue: [^self]. "If the methods are both simple accessors, don't complain." ((tmethod definingClass isAccessor: selector) and: [aClass isAccessor: selector]) ifTrue: [^self]. "If the method is overriding a method in a superclass, don't complain" (aClass inheritsFrom: tmethod definingClass) ifTrue: [methods removeKey: selector] ifFalse: [self error: 'conflicting implementations for ', selector storeString]]. ^(self addMethodFor: aClass selector: selector) ifNotNil: [:tmethod| tmethod transformToStructClassMethodFor: self. tmethod]!
Item was changed: ----- Method: CCodeGenerator>>doInlining: (in category 'inlining') ----- doInlining: inlineFlagOrSymbol "Inline the bodies of all methods that are suitable for inlining." "Modified slightly for the core VM translator, since the first level of inlining for the interpret loop must be performed in order that the instruction implementations can easily discover their addresses. Remember to inline the bytecode routines as well"
| removed | beganInlining := true. inlineFlagOrSymbol isSymbol ifTrue: [self inlineDispatchesInMethodNamed: #interpret localizingVars: #(). self doBasicInlining: inlineFlagOrSymbol. self pruneUnreachableMethods. ^self].
inlineFlagOrSymbol ifFalse: [self inlineDispatchesInMethodNamed: #interpret localizingVars: #(). self pruneUnreachableMethods. ^self].
self doBasicInlining: inlineFlagOrSymbol.
vmClass ifNil: [^self].
'Inlining bytecodes' displayProgressAt: Sensor cursorPoint from: 1 to: 2 during: [:bar | self inlineDispatchesInMethodNamed: #interpret localizingVars: vmClass namesOfVariablesToLocalize. bar value: 1. removed := self removeMethodsReferingToGlobals: vmClass namesOfVariablesToLocalize except: #interpret. bar value: 2].
"only prune when generating the interpreter itself" self pruneUnreachableMethods.
self reportShouldNotBeRemoved: removed varList: vmClass namesOfVariablesToLocalize!
Item was changed: ----- Method: CoInterpreterMT>>checkVMOwnershipFromHeartbeat (in category 'process primitive support') ----- checkVMOwnershipFromHeartbeat "Check whether the VM is unowned and needs to set a thread running to try and own it. Do not attempt this if the image doesn't have a threadAffinity inst var in Process; the VM can't thread these images." <inline: false> self sqLowLevelMFence. (processHasThreadAffinity + and: [cogThreadManager doTryLockVMOwnerTo: CTMUnknownOwner]) ifTrue: - and: [cogThreadManager vmIsUnowned]) ifTrue: [cogThreadManager ensureRunningVMThread: relinquishing]!
Item was changed: ----- Method: CoInterpreterMT>>disownVM: (in category 'vm scheduling') ----- disownVM: flags "Release the VM to other threads and answer the current thread's index. Currently valid flags: DisownVMForFFICall - informs the VM that it is entering an FFI call DisownVMForThreading - informs the VM that it is entering code during which threading should be permitted OwnVMForeignThreadFlag - indicates lowest-level entry from a foreign thread - not to be used explicitly by clients - only set by ownVMFromUnidentifiedThread VMAlreadyOwnedHenceDoNotDisown - indicates an ownVM from a callback was made when the vm was still owned. - not to be used explicitly by clients - only set by ownVMFromUnidentifiedThread
This is the entry-point for plugins and primitives that wish to release the VM while performing some operation that may potentially block, and for callbacks returning back to some blocking operation. If this thread does not reclaim the VM before- hand then when the next heartbeat occurs the thread manager will schedule a thread to acquire the VM which may start running the VM in place of this thread.
N.B. Most of the state needed to resume after preemption is set in preemptDisowningThread." <public> <inline: false> + <returnTypeC: #'void *'> + | vmThread | - | vmThread result | self assert: (flags >= 0 and: [flags < (1 bitShift: DisownFlagsShift)]). self assert: self successful. cogit recordEventTrace ifTrue: [self recordTrace: TraceDisownVM thing: (objectMemory integerObjectOf: flags) source: 0]. processHasThreadAffinity ifFalse: [willNotThreadWarnCount < 10 ifTrue: [self print: 'warning: VM parameter 48 indicates Process doesn''t have threadId; VM will not thread'; cr. willNotThreadWarnCount := willNotThreadWarnCount + 1]]. vmThread := cogThreadManager currentVMThread. (flags anyMask: VMAlreadyOwnedHenceDoNotDisown) ifTrue: [disowningVMThread := vmThread. vmThread setVmThreadState: CTMUnavailable. + ^nil]. - ^0]. self assertCStackPointersBelongToCurrentThread. self assertValidNewMethodPropertyFlags. self cCode: '' inSmalltalk: [cogThreadManager saveRegisterStateForCurrentProcess. cogThreadManager clearRegisterStates.]. (flags anyMask: DisownVMForProcessorRelinquish) ifTrue: [| proc | (proc := objectMemory splObj: foreignCallbackProcessSlot) ~= objectMemory nilObject ifTrue: [foreignCallbackPriority := self quickFetchInteger: PriorityIndex ofObject: proc]. relinquishing := true. self sqLowLevelMFence]. disownCount := disownCount + 1. "If we're disowning the VM because there's no active process to run, there's nothing to preempt later, so don't indicate that there's a disowningVMThread that needs to be restored later." self activeProcess ~= objectMemory nilObject ifTrue: [disowningVMThread := vmThread].
"OwnVMForeignThreadFlag indicates lowest-level of entry by a foreign thread. If that's where we are then release the vmThread. Otherwise indicate the vmThread is off doing something outside of the VM." (flags anyMask: OwnVMForeignThreadFlag) ifTrue: ["I don't think this is quite right. Josh's use case is creating some foreign thread and then registering it with the VM. That's not the same as binding a process to a foreign thread given that the foreign callback process is about to terminate anyway (it is returning from a callback here). So do we need an additional concept, that of a vmThread being either of the set known to the VM or floating?" self flag: 'issue with registering foreign threads with the VM'. (self isBoundProcess: self activeProcess) ifFalse: [cogThreadManager unregisterVMThread: vmThread]] ifFalse: [vmThread setVmThreadState: CTMUnavailable].
+ vmThread disownFlags: (flags bitOr: (activeProcessAffined ifTrue: [0] ifFalse: [ProcessUnaffinedOnDisown])). - result := ((vmThread index bitShift: DisownFlagsShift) - bitOr: (activeProcessAffined ifTrue: [0] ifFalse: [ProcessUnaffinedOnDisown])) - bitOr: flags. cogThreadManager releaseVM. + ^vmThread! - ^result!
Item was changed: ----- Method: CoInterpreterMT>>ownVM: (in category 'vm scheduling') ----- + ownVM: vmThreadHandle - ownVM: threadIndexAndFlags <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: - | threadIndex flags vmThread | - threadIndexAndFlags = 0 ifTrue: [^self ownVMFromUnidentifiedThread].
+ flags := vmThread disownFlags. - threadIndex := threadIndexAndFlags bitShift: DisownFlagsShift negated. - flags := threadIndexAndFlags bitAnd: (1 bitShift: DisownFlagsShift) - 1.
(flags anyMask: DisownVMForProcessorRelinquish) ifTrue: ["Presumably we have nothing to do; this primitive is typically called from the background process. So we should /not/ try and activate any threads in the pool; they will waste cycles finding there is no runnable process, and will cause a VM abort if no runnable process is found. But we /do/ want to allow FFI calls that have completed, or callbacks a chance to get into the VM; they do have something to do. DisownVMForProcessorRelinquish indicates this." relinquishing := false. self sqLowLevelMFence].
+ vmThread := cogThreadManager acquireVMFor: vmThread. - vmThread := cogThreadManager acquireVMFor: threadIndex. 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. - self restoreVMStateFor: vmThread threadIndexAndFlags: threadIndexAndFlags.
cogit recordEventTrace ifTrue: [self recordTrace: TraceOwnVM thing: ConstTwo source: 0]. + ^flags bitAnd: OwnVMForeignThreadFlag! - ^threadIndexAndFlags bitAnd: OwnVMForeignThreadFlag!
Item was changed: ----- Method: CoInterpreterMT>>ownVMFromUnidentifiedThread (in category 'vm scheduling') ----- ownVMFromUnidentifiedThread "Attempt to take ownership from a thread that as yet doesn't know its index. This supports callbacks where the callback could originate from any thread. Answer 0 if the owning thread is known to the VM. Answer 1 if the owning thread is unknown to the VM and now owns the VM. Answer -1 if the owning thread is unknown to the VM and fails to own the VM. Answer -2 if the owning thread is unknown to the VM and there is no foreign callback process installed." | count threadIndex vmThread | <var: #vmThread type: #'CogVMThread *'> <inline: false> self cCode: [] inSmalltalk: [self halt: 'TODO: Implement processor register switching']. (threadIndex := cogThreadManager ioGetThreadLocalThreadIndex) ~= 0 ifTrue: [ "this is a callback from a known thread" (cogThreadManager vmOwnerIs: threadIndex) ifTrue: "the VM has not been disowned" [self assert: (disowningVMThread isNil or: [disowningVMThread = self currentVMThread]). disowningVMThread := nil. self currentVMThread setVmThreadState: CTMAssignableOrInVM. ^VMAlreadyOwnedHenceDoNotDisown]. + ^self cCode: [nil] inSmalltalk: [self error: 'TODO: Needs to take a vmThread, not the index'."self ownVM: threadIndex"]]. - ^self ownVM: threadIndex]. foreignCallbackPriority = 0 ifTrue: [^-2]. count := 0. "If the current thread doesn't have an index it's new to the vm and we need to allocate a new threadInfo, failing if we can't. We also need a process in the foreignCallbackProcessSlot upon which to run the thread's eventual callback." [[cogThreadManager tryLockVMOwnerTo: cogThreadManager ioCurrentOSThread asUnsignedInteger] whileFalse: [self waitingPriorityIsAtLeast: foreignCallbackPriority. cogThreadManager ioTransferTimeslice]. (objectMemory splObj: foreignCallbackProcessSlot) ~= objectMemory nilObject] whileFalse: [cogThreadManager releaseVM. (count := count + 1) > 1000 ifTrue: [^-2]. cogThreadManager ioMilliSleep: 1].
vmThread := cogThreadManager unusedThreadInfo. "N.B. Keep the VM locked anonymously so that we reserve the non-nil ForeignCallbackProcess for this thread, avoiding the race between competing foreign callbacks. The acquireVMFor: in ownVM: will set the vmOwner to the actual index. So only unlock on failure." vmThread ifNil: [cogThreadManager releaseVM. ^-1]. cogThreadManager setVMOwner: vmThread index. vmThread setVmThreadState: CTMWantingOwnership; priority: foreignCallbackPriority. cogThreadManager registerVMThread: vmThread. ^self ownVM: vmThread index + OwnVMForeignThreadFlag!
Item was changed: ----- Method: CoInterpreterMT>>primitiveRelinquishProcessor (in category 'I/O primitives') ----- primitiveRelinquishProcessor "Relinquish the processor for up to the given number of microseconds. The exact behavior of this primitive is platform dependent. Override to check for waiting threads."
+ | microSecs vmHandle currentCStackPointer currentCFramePointer | - | microSecs threadIndexAndFlags currentCStackPointer currentCFramePointer | <var: #currentCStackPointer type: #'volatile usqIntptr_t'> <var: #currentCFramePointer type: #'volatile usqIntptr_t'> microSecs := self stackTop. (objectMemory isIntegerObject: microSecs) ifFalse: [^self primitiveFail]. self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject. self assert: relinquishing not. "DO NOT allow relinquishing the processor while we are profiling since this may skew the time base for our measures (it may reduce processor speed etc). Instead we go full speed, therefore measuring the precise time we spend in the inner idle loop as a busy loop." nextProfileTick = 0 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." [currentCStackPointer := CStackPointer. currentCFramePointer := CFramePointer. + vmHandle := self disownVM: DisownVMForProcessorRelinquish. - threadIndexAndFlags := self disownVM: DisownVMForProcessorRelinquish. self assert: relinquishing. self ioRelinquishProcessorForMicroseconds: (objectMemory integerValueOf: microSecs). self assert: relinquishing. + self ownVM: vmHandle. - self ownVM: threadIndexAndFlags. self assert: relinquishing not. self assert: cogThreadManager currentVMThread vmThreadState = CTMAssignableOrInVM. self assert: currentCStackPointer = CStackPointer. self assert: currentCFramePointer = CFramePointer. "In simulation we allow ioRelinquishProcessorForMicroseconds: to fail so that we can arrange that the simulator responds to input events promptly. This *DOES NOT HAPPEN* in the real vm." self cCode: [] inSmalltalk: [primFailCode ~= 0 ifTrue: [^self]]]. self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject. self pop: 1 "microSecs; leave rcvr on stack"!
Item was added: + ----- 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 := cogThreadManager popAWOLProcess: vmThread]. + self assert: activeProc ~= myProc. + (activeProc ~= objectMemory nilObject + and: [(objectMemory fetchPointer: MyListIndex ofObject: activeProc) = objectMemory nilObject]) ifTrue: + [self putToSleep: activeProc yieldingIf: preemptionYields]. + self assert: (objectMemory fetchPointer: MyListIndex ofObject: myProc) = (objectMemory splObj: ProcessInExternalCodeTag). + objectMemory + 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 setOwnerIndexOfProcess: 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 removed: - ----- Method: CoInterpreterMT>>restoreVMStateFor:threadIndexAndFlags: (in category 'vm scheduling') ----- - restoreVMStateFor: vmThread threadIndexAndFlags: threadIndexAndFlags - "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. - (threadIndexAndFlags anyMask: OwnVMForeignThreadFlag) - ifTrue: - [self assert: foreignCallbackProcessSlot == ForeignCallbackProcess. - myProc := objectMemory splObj: foreignCallbackProcessSlot. - self assert: myProc ~= objectMemory nilObject. - objectMemory splObj: foreignCallbackProcessSlot put: objectMemory nilObject] - ifFalse: [myProc := cogThreadManager popAWOLProcess: vmThread]. - self assert: activeProc ~= myProc. - (activeProc ~= objectMemory nilObject - and: [(objectMemory fetchPointer: MyListIndex ofObject: activeProc) = objectMemory nilObject]) ifTrue: - [self putToSleep: activeProc yieldingIf: preemptionYields]. - self assert: (objectMemory fetchPointer: MyListIndex ofObject: myProc) = (objectMemory splObj: ProcessInExternalCodeTag). - objectMemory - 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." - ((threadIndexAndFlags anyMask: ProcessUnaffinedOnDisown) - and: [(self isBoundProcess: myProc) not]) ifTrue: - [self setOwnerIndexOfProcess: 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 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!
Item was changed: ----- Method: CogThreadManager>>acquireVMFor: (in category 'public api') ----- + acquireVMFor: vmThread - acquireVMFor: threadIndex "Attempt to acquire the VM, eventually blocking until it becomes available. Spin until the maxWaitingPriority has been updated if it is lower than this thread's priority." <returnTypeC: #'CogVMThread *'> - | vmThread | <var: #vmThread type: #'CogVMThread *'> + self assert: vmThread index = self ioGetThreadLocalThreadIndex. - self assert: threadIndex = self ioGetThreadLocalThreadIndex. - vmThread := self vmThreadAt: threadIndex. self assert: (vmThread vmThreadState = CTMUnavailable or: [vmThread vmThreadState = CTMWantingOwnership]). + (self tryLockVMOwnerTo: vmThread index) - (self tryLockVMOwnerTo: threadIndex) ifTrue: [vmThread setVmThreadState: CTMAssignableOrInVM] ifFalse: [vmThread setVmThreadState: CTMWantingOwnership. + [(self vmOwnerIs: vmThread index) + or: [self tryLockVMOwnerTo: vmThread index]] whileFalse: - [(self vmOwnerIs: threadIndex) - or: [self tryLockVMOwnerTo: threadIndex]] whileFalse: [vmThread priority ifNotNil: [coInterpreter waitingPriorityIsAtLeast: vmThread priority]. + (self vmOwnerIs: vmThread index) ifFalse: - (self vmOwnerIs: threadIndex) ifFalse: [self ioWaitOnOSSemaphore: (self addressOf: vmThread osSemaphore)]]]. coInterpreter assertProcessorStackPointersBelongToCurrentThread. vmOSThread := vmThread osThread. vmThread setVmThreadState: CTMAssignableOrInVM. ^vmThread!
Item was changed: ----- Method: CogThreadManager>>assertValidProcessorStackPointersForIndex: (in category 'simulation') ----- assertValidProcessorStackPointersForIndex: threadIndex <cmacro: '(ignored) 0'> "simulation only" | time range | + threadIndex = CTMUnknownOwner + ifTrue: [^ self assertEmptyRegisterStates: cogit processor registerState]. time := Time utcMicrosecondClock. range := coInterpreter cStackRangeForThreadIndex: threadIndex. self assert: ((range includes: cogit processor fp) and: [range includes: cogit processor sp])
"(0 to: numThreads + numThreadsIncrement) detect: [:i| | range | range := coInterpreter cStackRangeForThreadIndex: threadIndex. ((range includes: cogit processor fp) and: [range includes: cogit processor sp])] ifNone: []"
"{ coInterpreter whereIs: cogit processor fp. coInterpreter whereIs: cogit processor sp }"!
Item was changed: ----- Method: CogThreadManager>>assertValidStackPointersInState:forIndex: (in category 'simulation') ----- assertValidStackPointersInState: registerState forIndex: threadIndex | time range | time := Time utcMicrosecondClock. range := coInterpreter cStackRangeForThreadIndex: threadIndex. self assert: ((range includes: (registerState at: cogit processor registerStateFPIndex)) and: [range includes: (registerState at: cogit processor registerStateSPIndex)])
"(1 to: self maxNumThreads) collect: [:i| | range | range := coInterpreter cStackRangeForThreadIndex: i. range storeStringBase: 16]"
"(1 to: self maxNumThreads) detect: [:i| | range | range := coInterpreter cStackRangeForThreadIndex: i. ((range includes: (registerState at: cogit processor registerStateFPIndex)) and: [range includes: (registerState at: cogit processor registerStateSPIndex)])] ifNone: []"
"{ (registerState at: cogit processor registerStateFPIndex) hex. coInterpreter whereIs: (registerState at: cogit processor registerStateFPIndex). (registerState at: cogit processor registerStateSPIndex) hex. coInterpreter whereIs: (registerState at: cogit processor registerStateSPIndex) }"
"(1 to: self maxNumThreads) detect: [:i| | range | range := coInterpreter cStackRangeForThreadIndex: i. ((range includes: cogit processor fp) and: [range includes: cogit processor sp])] ifNone: []"
"{ cogit processor fp hex. coInterpreter whereIs: cogit processor fp. cogit processor sp hex. coInterpreter whereIs: cogit processor sp }"!
Item was changed: ----- Method: CogThreadManager>>doTryLockVMOwnerTo: (in category 'simulation') ----- 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 | expected := self cCode: 0 inSmalltalk: [AtomicValue newFrom: 0]. ^ (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]]! - or: ["We may already be vmOwner. The current vmOwner will be stored in expected" expected = threadIndex]!
Item was changed: ----- Method: CogThreadManager>>ensureRunningVMThread: (in category 'public api') ----- ensureRunningVMThread: vmIsRelinquishing + "Called from checkVMOwnershipFromHeartbeat if the heartbeat thread manages to lock the vmOwner to CTMUnknownOwner. + Hence we are the vmOwner and are in the heartbeat thread. + We must therefore ensure that when we leave this method either: + 1. Some other thread is running and vmOwner + 2. We've released ownership of the VM" - "Called from checkVMOwnershipFromHeartbeat if the VM is unowned. - Hence we are in the heartbeat thread. The race is against that thread - owning the VM and against foreign callbacks." <returnTypeC: #void> <var: #vmThread type: #'CogVMThread *'> self willingVMThread ifNotNil: [:vmThread| | threadState | threadState := vmThread vmThreadState. "If the VM is relinquishing the processor then only schedule a thread if it has work to do." (vmIsRelinquishing and: [threadState ~= CTMWantingOwnership]) ifTrue: + [^self releaseVM]. - [^self]. self assert: ((threadState = CTMAssignableOrInVM or: [threadState = CTMInitializing]) or: [threadState = CTMWantingOwnership]). + "Ownership will be transferred to vmThread, no need to release the VM." + ^ self wakeVMThread: vmThread]. - (self tryLockVMOwnerTo: vmThread index) ifFalse: "someone beat us to it..." - [^self]. - vmOSThread := vmThread osThread. - "release the thread from its blocking loop" - self ioSignalOSSemaphore: (self addressOf: vmThread osSemaphore). - self ioTransferTimeslice. - "self cCode: [coInterpreter print: 'ERVT signalled '; printNum: vmThread index; cr]." - ^self].
"If the VM is relinquishing the processor then only schedule a thread if it has work to do (willingVMThread not nil above). If we have failed to allocate thread storage before there is no point continuing to try to do so. By this time we should have quite a few threads in the pool." (vmIsRelinquishing or: [memoryIsScarce]) ifTrue: + [^self releaseVM]. + self unusedThreadInfo ifNotNil: [:vmThread| ^ self wakeVMThread: vmThread]. + ^ self releaseVM! - [^self]. - self unusedThreadInfo ifNotNil: - [:vmThread| - (self tryLockVMOwnerTo: vmThread index) ifTrue: - [(self startThreadForThreadInfo: vmThread) ifFalse: - [self releaseVM]]]!
Item was changed: ----- Method: CogThreadManager>>loadOrInitializeRegisterStateFor: (in category 'simulation') ----- loadOrInitializeRegisterStateFor: threadIndex <doNotGenerate> + |processor| + threadIndex = CTMUnknownOwner ifTrue: [^ self halt]. + - |fakeThreadIndex processor| - "The heartbeat thread will lock the VM to -1, so generate a fake processor data for this." - fakeThreadIndex := threadIndex == -1 ifTrue: [self maxNumThreads] ifFalse: [threadIndex]. processor := cogit processor. processor setRegisterState: (registerStates at: threadIndex ifAbsentPut: + [self initializeProcessor: processor forThreadIndex: threadIndex. - [self initializeProcessor: processor forThreadIndex: fakeThreadIndex. processor registerState]).!
Item was changed: ----- Method: CogThreadManager>>saveRegisterStateForCurrentProcess (in category 'simulation') ----- saveRegisterStateForCurrentProcess "On switching osProcess we have to both - SAVE old process's register state - RESTORE new process's register state So what are the transitions? The transitions out (where processor state must be saved) are the opposites of tryLockVMOwnerTo:. Hence - waitOnOSSemaphore: - disownVM: - ioTransferTimeslice i.e. the continuations from here, disownVM:, and ioTransferTimeslice, will use tryLockVMOwnerTo: to regain control of the VM. So the register state to be restored at that point (if tryLockVMOwnerTo: succeeds) is the register state saved in one of the three places. But the processor is initialized in tryLockVMOwnerTo:, so only save the state if state is already present." <doNotGenerate> + self getVMOwner = CTMUnknownOwner ifTrue: [^ self]. cogit withProcessorHaltedDo: [| currentVMThread state | currentVMThread := self vmThreadForCurrentProcess. state := cogit processor registerState. self assertValidStackPointersInState: state forIndex: currentVMThread index. registerStates at: currentVMThread index put: state]!
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 - valid for that owner, otherwise it should 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'] }. - f: (result ifTrue: ['tryLockVMOwner %d -> %d (%s) ok\n'] ifFalse: ['tryLockVMOwner %d -> %d (%s) FAILED\n']) - printf: { previousOwner. threadIndex. thisContext home sender selector }. self assertValidProcessorStackPointersForIndex: self getVMOwner. result]!
Item was added: + ----- Method: CogThreadManager>>wakeVMThread: (in category 'scheduling') ----- + wakeVMThread: vmThread + <var: #vmThread type: #'CogVMThread *'> + <returnTypeC: #void> + | threadState | + self assert: (self vmIsOwned and: [(self vmOwnerIs: vmThread index) not]). + + "Instead of going through a #disownVM: call, directly set the new VM owner. + This has the advantage of avoiding a race for the different threads to become the new + VM owner. + In Simulation, this means we need to simulate a thread-switch." + self cCode: [] inSmalltalk: [ + self saveRegisterStateForCurrentProcess. + self loadOrInitializeRegisterStateFor: vmThread index]. + self setVMOwner: vmThread index. + + threadState := vmThread vmThreadState. + threadState = CTMUninitialized + ifTrue: [(self startThreadForThreadInfo: vmThread) ifFalse: [self releaseVM. "TODO: IS THIS SANE?"]] + ifFalse: + [self assert: ((threadState = CTMWantingOwnership + or: [threadState = CTMAssignableOrInVM]) + or: [threadState = CTMInitializing]). + self ioSignalOSSemaphore: (self addressOf: vmThread osSemaphore)]. + self ioTransferTimeslice!
Item was changed: ----- Method: CogThreadManager>>wakeVMThreadFor: (in category 'public api') ----- wakeVMThreadFor: index "Transfer the VM to the thread with index. Called from a thread that finds the highest priority runnable process is bound to the thread with index index." <returnTypeC: #void> - | vmThread threadState | - self assert: (self vmIsOwned and: [(self vmOwnerIs: index) not]). self assert: (index between: 1 and: numThreads). + ^ self wakeVMThread: (threads at: index). + ! - vmThread := threads at: index. - - "Instead of going through a #disownVM: call, directly set the new VM owner. - This has the advantage of avoiding a race for the different threads to become the new - VM owner. - In Simulation, this means we need to simulate a thread-switch." - self cCode: [] inSmalltalk: [ - self saveRegisterStateForCurrentProcess. - self loadOrInitializeRegisterStateFor: index]. - self setVMOwner: index. - - threadState := vmThread vmThreadState. - threadState = CTMUninitialized - ifTrue: [self startThreadForThreadInfo: vmThread] - ifFalse: - [self assert: ((threadState = CTMWantingOwnership - or: [threadState = CTMAssignableOrInVM]) - or: [threadState = CTMInitializing]). - self ioSignalOSSemaphore: (self addressOf: vmThread osSemaphore)]. - self ioTransferTimeslice!
Item was changed: VMStructType subclass: #CogVMThread + instanceVariableNames: 'index state priority osSemaphore osThread disownFlags newMethodOrNull argumentCount inMachineCode cStackPointer cFramePointer awolProcIndex awolProcLength awolProcesses reenterThreadSchedulingLoop' - instanceVariableNames: 'index state priority osSemaphore osThread 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 added: + ----- Method: CogVMThread>>awolProcesses: (in category 'accessing') ----- + awolProcesses: anObject + + ^ awolProcesses := anObject.!
Item was added: + ----- Method: CogVMThread>>coerceTo:sim: (in category 'as yet unclassified') ----- + coerceTo: aSymbol sim: aSimulator + + self assert: aSymbol = #'CogVMThread *'. + ^ self!
Item was added: + ----- Method: CogVMThread>>disownFlags (in category 'accessing') ----- + disownFlags + + ^ disownFlags!
Item was added: + ----- Method: CogVMThread>>disownFlags: (in category 'accessing') ----- + disownFlags: anObject + + ^ disownFlags := anObject.!
Item was changed: ----- Method: CogVMThread>>printOn: (in category 'printing') ----- printOn: aStream super printOn: aStream. aStream nextPutAll: ' index: '; print: index; nextPutAll: ' state: '; nextPutAll: (VMThreadingConstants keys + detect: [:k| k first == $C and: [(VMThreadingConstants classPool at: k) = state value]] + ifNone: [state value printString])! - detect: [:k| k first == $C and: [(VMThreadingConstants classPool at: k) = state]] - ifNone: [state printString])!
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." + | count startIndex array file slotSize elementSize bytesRead vmHandle wasPinned | - | count startIndex array file slotSize elementSize bytesRead threadIndexAndFlags 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).
(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. - threadIndexAndFlags := interpreterProxy disownVM: DisownVMForThreading. "Note: adjust startIndex for zero-origin byte indexing" elementSize := slotSize = 0 ifTrue: [1] ifFalse: [(interpreterProxy byteSizeOf: array) // slotSize]. bytesRead := self sqFile: file Read: count * elementSize Into: (interpreterProxy cCoerce: (interpreterProxy firstIndexableField: array) to: #'char *') At: startIndex - 1 * elementSize. + interpreterProxy ownVM: vmHandle. - interpreterProxy ownVM: threadIndexAndFlags. wasPinned ifFalse: [interpreterProxy unpinObject: array]. interpreterProxy failed ifFalse: [interpreterProxy methodReturnInteger: bytesRead // elementSize] "answer # of elements read"!
Item was changed: ----- Method: TMethod>>inlineSend:directReturn:exitVar:in: (in category 'inlining') ----- inlineSend: aSendNode directReturn: directReturn exitVar: exitVar in: aCodeGen "Answer a collection of statements to replace the given send. directReturn indicates that the send is the expression in a return statement, so returns can be left in the body of the inlined method. If exitVar is nil, the value returned by the send is not used; thus, returns need not assign to the output variable.
Types are propagated to as-yet-untyped variables when inlining a send that is assigned, otherwise the assignee variable type must match the return type of the inlinee. Return types are not propagated."
| sel meth methArgs exitLabel inlineStmts label exitType elidedArgs | sel := aSendNode selector. meth := aCodeGen methodNamed: sel. methArgs := meth args. "convenient for debugging..." aCodeGen maybeBreakForInlineOf: aSendNode in: self. elidedArgs := #(). (methArgs notEmpty and: [methArgs first beginsWith: 'self_in_']) ifTrue: "If the first arg is not used we can and should elide it." [| varNode | varNode := TVariableNode new setName: methArgs first. (meth parseTree noneSatisfy: [:node| varNode isSameAs: node]) ifTrue: [elidedArgs := {methArgs first}]. methArgs := methArgs allButFirst]. methArgs size = aSendNode args size ifFalse: [^nil]. meth := meth copy.
(meth statements size > 1 and: [meth statements first isSend and: [meth statements first selector == #flag:]]) ifTrue: [meth statements removeFirst].
"Propagate the return type of an inlined method" (directReturn or: [exitVar notNil]) ifTrue: [exitType := directReturn ifTrue: [returnType] ifFalse: [(self typeFor: exitVar in: aCodeGen) ifNil: [#sqInt]]. (exitType = #void or: [exitType = meth returnType]) ifFalse: [meth propagateReturnIn: aCodeGen]].
"Propagate any unusual argument types to untyped argument variables" methArgs with: aSendNode args do: [:formal :actual| (meth declarationAt: formal ifAbsent: nil) ifNil: [(self typeFor: actual in: aCodeGen) ifNotNil: [:type| type ~= #sqInt ifTrue: [meth declarationAt: formal put: (type last = $* ifTrue: [type, formal] ifFalse: [type, ' ', formal])]]]].
meth renameVarsForInliningInto: self except: elidedArgs in: aCodeGen. meth renameLabelsForInliningInto: self. self addVarsDeclarationsAndLabelsOf: meth except: elidedArgs. meth hasReturn ifTrue: [directReturn ifFalse: [exitLabel := self unusedLabelForInliningInto: self. (meth exitVar: exitVar label: exitLabel) "is label used?" ifTrue: [ labels add: exitLabel ] ifFalse: [ exitLabel := nil ]]]. (inlineStmts := OrderedCollection new: meth statements size + meth args size + 2) add: (label := TLabeledCommentNode new setComment: 'begin ', sel); addAll: (self argAssignmentsFor: meth send: aSendNode except: elidedArgs in: aCodeGen); addAll: meth statements. "method body" directReturn ifTrue: [meth endsWithReturn ifTrue: [exitVar ifNotNil: "don't remove the returns if being invoked in the context of a return" [inlineStmts at: inlineStmts size put: inlineStmts last copyWithoutReturn]] ifFalse: [inlineStmts add: (TReturnNode new setExpression: (TVariableNode new setName: 'nil'))]]. exitLabel ifNotNil: [inlineStmts add: (TLabeledCommentNode new setLabel: exitLabel comment: 'end ', meth selector)]. inlineStmts size = 1 ifTrue: "Nuke empty methods; e.g. override of flushAtCache" [self assert: inlineStmts first isComment. inlineStmts removeFirst]. ^inlineStmts!
Item was changed: ----- Method: TMethod>>transformToStructClassMethodFor: (in category 'transformations') ----- transformToStructClassMethodFor: aCCodeGenerator "Transform this method so that it can be used on an instance of a struct class (VMStructType subclass). Convert inst var refs into field dereferences of self. Add selfSelector as the first argument with the right struct type. As a complete hack to avoid breaking the inlinert don't use 'self' as the name for self as this causes serious type redefinitions ``somewhere'' in the inliner." | replacements selfNode typeForSelf | self isStructAccessor ifTrue: [^self returnType: (definingClass returnTypeForAccessor: selector)]. replacements := IdentityDictionary new. selfNode := TVariableNode new setName: 'self_in_', (aCCodeGenerator cFunctionNameFor: selector). args do: [:var| (definingClass isAccessor: var) ifTrue: [self error: 'In ', definingClass name, '>>', selector, ' ', var, ' arg shadows struct field and will break during translation!!']]. parseTree nodesDo: [:node| node isVariable ifTrue: [node name = 'self' ifTrue: [replacements at: node put: selfNode copy]. (definingClass isAccessor: node name) ifTrue: [replacements at: node put: (TSendNode new setSelector: node name asSymbol receiver: selfNode arguments: #())]]]. replacements notEmpty ifTrue: [self replaceNodesIn: replacements]. typeForSelf := self typeForSelf. self assert: (typeForSelf notNil and: [typeForSelf ~~ #implicit]). self declarationAt: (args addFirst: selfNode name) put: (declarations removeKey: 'self'), '_in_', (aCCodeGenerator cFunctionNameFor: selector)!
Item was changed: ----- Method: TSendNode>>emitCCodeAsArgumentOn:level:generator: (in category 'C code generation') ----- emitCCodeAsArgumentOn: aStream level: level generator: aCodeGen "Emit the receiver in a form that can be passed as an argument."
"If the selector is a built-in construct, translate it and return" (aCodeGen emitBuiltinConstructAsArgumentFor: self on: aStream level: level) ifFalse: ["If it is a pointer dereference generate it" (self emitCCodeAsFieldReferenceOn: aStream level: level generator: aCodeGen) ifFalse: ["Otherwise generate the vanilla C function call." self emitCCodeAsFunctionCallOn: aStream level: level generator: aCodeGen]]!
Item was changed: ----- Method: TSendNode>>emitCCodeAsFieldReferenceOn:level:generator: (in category 'C code generation') ----- emitCCodeAsFieldReferenceOn: aStream level: level generator: aCodeGen "If appropriate, translate this message send as a pointer dereference"
| parenCount | (aCodeGen isStructSend: self) ifFalse: [^false]. (aCodeGen shouldGenerateAsInterpreterProxySend: self) ifTrue: [^false].
+ "(selector = #state + and: [(receiver typeOrNilFrom: aCodeGen in: aCodeGen currentMethod) = 'CogVMThread *']) + ifTrue: [(CogVMThread>>#state asTranslationMethodOfClass: TMethod) + emitInlineOn: aStream level: level generator: aCodeGen. + ^ true + ^ false.]." parenCount := receiver isSend ifTrue: [2] ifFalse: [1]. aStream next: parenCount put: $(. receiver emitCCodeAsExpressionOn: aStream level: 0 generator: aCodeGen. parenCount > 1 ifTrue: [aStream nextPut: $)]. (receiver structTargetKindIn: aCodeGen) caseOf: { [#pointer] -> [aStream nextPut: $-; nextPut: $>]. [#struct] -> [aStream nextPut: $.] }. aStream nextPutAll: (aCodeGen cFunctionNameFor: selector). arguments isEmpty ifFalse: [self assert: arguments size = 1. aStream nextPutAll: ' = '. arguments first emitCCodeAsExpressionOn: aStream level: level generator: aCodeGen]. aStream nextPut: $). ^true!
Item was changed: ----- Method: TSendNode>>emitCCodeAsFunctionCallOn:level:generator: (in category 'C code generation') ----- emitCCodeAsFunctionCallOn: aStream level: level generator: aCodeGen "Translate this message send into a C function call"
selector == #break ifTrue: [aStream nextPutAll: '/* send of break elided */'. ^self].
"Special case for pluggable modules. Replace messages to interpreterProxy by interpreterProxy->message(..) if the message is not builtin" (aCodeGen shouldGenerateAsInterpreterProxySend: self) ifTrue: [(aCodeGen noteUsedPluginFunction: selector) ifTrue: [aStream nextPutAll: 'interpreterProxy->']].
aStream nextPutAll: (aCodeGen cFunctionNameFor: selector); nextPut: $(.
"Only include the receiver as the first argument in certain cases. The receiver is always included if it is an expression. If it is a variable: If the vmClass says it is an implicit variable, don't include it. If the variable is 'self' and the method being called is not in the method set (i.e. it is some external code), don't include it." (self shouldExcludeReceiverAsFirstArgument: aCodeGen) ifFalse: [(receiver structTargetKindIn: aCodeGen) == #struct ifTrue: [aStream nextPut: $&]. "Hack fix warnings for isCMOpenPIC, isCMClosedPIC, et al" (self shouldCastReceiverIfStructIn: aCodeGen ifTrue: [:formalParameterType| aCodeGen nodeToCast: receiver to: formalParameterType] ifFalse: [receiver]) emitCCodeOn: aStream level: level generator: aCodeGen. arguments isEmpty ifFalse: [aStream nextPutAll: ', ']]. arguments do: [ :arg| arg emitCCodeAsArgumentOn: aStream level: level generator: aCodeGen] separatedBy: (self argumentSeparationBlockFor: aStream numArgs: arguments size level: level). aStream nextPut: $)!
Item was changed: ----- Method: TSendNode>>emitCCodeOn:level:generator: (in category 'C code generation') ----- emitCCodeOn: aStream level: level generator: aCodeGen "Emit the receiver as a statement."
"If the selector is a built-in construct, translate it and return" (aCodeGen emitBuiltinConstructFor: self on: aStream level: level) ifFalse: ["If it is a pointer dereference generate it" (self emitCCodeAsFieldReferenceOn: aStream level: level generator: aCodeGen) ifFalse: ["Otherwise generate the vanilla C function call." self emitCCodeAsFunctionCallOn: aStream level: level generator: aCodeGen]]!
Item was changed: SharedPool subclass: #VMThreadingConstants instanceVariableNames: '' + classVariableNames: 'AWOLProcessesIncrement CTMAssignableOrInVM CTMInitializing CTMUnavailable CTMUninitialized CTMUnknownOwner CTMWantingOwnership ThreadIdIndex ThreadIdShift' - classVariableNames: 'AWOLProcessesIncrement CTMAssignableOrInVM CTMInitializing CTMUnavailable CTMUninitialized 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