Eliot Miranda uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.oscog-LM.3337.mcz
==================== Summary ====================
Name: VMMaker.oscog-LM.3337 Author: LM Time: 14 September 2023, 1:56:57.987202 pm UUID: 337b7bcb-90f0-4329-820b-5a53d8a04249 Ancestors: VMMaker.oscog-mt.3336
First mostly stable version of the Threaded FFI.
Most important changes are: 1. Use C11 atomic instructions for the vmOwner. This simplifies the CPXCHG instruction and should make this a lot more resistant to breaking due to compiler optimizations.
2. Fix a critical bug when reentering the threadSchedulingLoop Previously this used the wrong jmp_buf, therefore a thread that tried to return to its threadSchedulingLoop might end up in the threadSchedulingLoop of ANOTHER thread!
With these two changes in place, the VM runs mostly stable whilst switching between two threads in the spurreader image. There are still some bugs to fix, especially in the scheduler, as well as synchronizing access to the threads variable. But this is good progress for now.
=============== Diff against VMMaker.oscog-mt.3336 ===============
Item was added: + Object subclass: #AtomicValue + instanceVariableNames: 'value' + classVariableNames: '' + poolDictionaries: '' + category: 'VMMaker-Multithreading'! + + !AtomicValue commentStamp: 'LM 8/26/2023 15:10' prior: 0! + An AtomicValue is a Smalltalk representation of C's atomic value types. + As atomics are always accessed through their address, we must be able to do the same in Smalltalk. + But as addressOf: doesn't create a real "pointer" in Smalltalk, we cannot just use plain values, but must store them in a wrapper object, which can change the internal representation. + + Instance Variables + value: <Object> + + value + - The actual value of the atomic + !
Item was added: + ----- Method: AtomicValue class>>newFrom: (in category 'as yet unclassified') ----- + newFrom: aValue + + ^ self new + value: aValue; + yourself!
Item was added: + ----- Method: AtomicValue>>= (in category 'as yet unclassified') ----- + = aValue + + ^ self value = aValue.!
Item was added: + ----- Method: AtomicValue>>compare:exchange: (in category 'atomic operations') ----- + compare: expected exchange: desired + self assert: (expected class = AtomicValue). + ^ self value = expected value + ifTrue: [self value: desired. true] + ifFalse: [expected value: self value. false]!
Item was added: + ----- Method: AtomicValue>>value (in category 'accessing') ----- + value + + ^ value!
Item was added: + ----- Method: AtomicValue>>value: (in category 'accessing') ----- + value: anObject + + value := anObject.!
Item was changed: ----- Method: CoInterpreter>>setImageHeaderFlagsFrom: (in category 'image save/restore') ----- setImageHeaderFlagsFrom: headerFlags "Set the flags that are contained in the 7th long of the image header. This is sent at image load time." imageHeaderFlags := headerFlags. "so as to preserve unrecognised flags." fullScreenFlag := headerFlags bitAnd: 1. imageFloatsBigEndian := (headerFlags noMask: 2) ifTrue: [1] ifFalse: [0]. + "processHasThreadAffinity := headerFlags anyMask: 4. specific to CoInterpreterMT" - "processHasThreadId := headerFlags anyMask: 4. specific to CoInterpreterMT" flagInterpretedMethods := headerFlags anyMask: 8. preemptionYields := headerFlags noMask: 16. "noThreadingOfGUIThread := headerFlags anyMask: 32. specific to CoInterpreterMT" newFinalization := headerFlags anyMask: 64. sendWheelEvents := headerFlags anyMask: 128. primitiveDoMixedArithmetic < 0 ifTrue: "i.e. has it not been set on the command line?" [primitiveDoMixedArithmetic := headerFlags noMask: 256]. "N.B. flag mask 512 is responded to by the FilePlugin & FileAttributesPlugin" upscaleDisplayIfHighDPI < 0 ifTrue: "i.e. has it not been set on the command line?" [upscaleDisplayIfHighDPI := headerFlags noMask: 1024]!
Item was changed: CoInterpreterPrimitives subclass: #CoInterpreterMT + instanceVariableNames: 'cogThreadManager checkThreadActivation maxWaitingPriority foreignCallbackPriority deferThreadSwitch disowningVMThread disownCount foreignCallbackProcessSlot activeProcessAffined relinquishing processHasThreadAffinity willNotThreadWarnCount' - instanceVariableNames: 'cogThreadManager checkThreadActivation maxWaitingPriority foreignCallbackPriority deferThreadSwitch disowningVMThread disownCount foreignCallbackProcessSlot activeProcessAffined relinquishing reenterThreadSchedulingLoop willNotThreadWarnCount processHasThreadAffinity' classVariableNames: 'DisownFlagsShift DisownVMForProcessorRelinquish OwnVMForeignThreadFlag PerThreadRumpCStackSize PrimNumberRelinquishProcessor ProcessUnaffinedOnDisown ReturnToThreadSchedulingLoop VMAlreadyOwnedHenceDoNotDisown' poolDictionaries: 'VMThreadingConstants' category: 'VMMaker-Multithreading'!
Item was changed: ----- Method: CoInterpreterMT class>>declareCVarsIn: (in category 'translation') ----- declareCVarsIn: aCCodeGenerator self class == thisContext methodClass ifFalse: [^self]. "Don't duplicate decls in subclasses" aCCodeGenerator addHeaderFile:'"sqAtomicOps.h"'. "For THRLOG" aCCodeGenerator vmClass declareInterpreterVersionIn: aCCodeGenerator defaultName: 'Cog MT'. aCCodeGenerator + var: #disowningVMThread type: #'CogVMThread *'.! - var: #disowningVMThread type: #'CogVMThread *'. - aCCodeGenerator var: #reenterThreadSchedulingLoop type: 'jmp_buf'.!
Item was changed: ----- Method: CoInterpreterMT>>_longjmp:_: (in category 'simulation') ----- _longjmp: aJumpBuf _: returnValue "Hack simulation of _setjmp/_longjmp, intended to invoke the most minimal setjmp/longjmp pair available on the platform; no saving/restoring signal masks, no stack unwinding, etc. Signal the exception that simulates a longjmp back to the interpreter." <doNotGenerate> + self assert: aJumpBuf class == ReenterThreadSchedulingLoop. - self assert: aJumpBuf == reenterThreadSchedulingLoop. aJumpBuf returnValue: returnValue; signal!
Item was changed: ----- 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 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> | 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 state: CTMUnavailable. ^0]. self assertCStackPointersBelongToCurrentThread. self assertValidNewMethodPropertyFlags. self cCode: '' inSmalltalk: [cogThreadManager saveRegisterStateForCurrentProcess. + cogThreadManager clearRegisterStates.]. - 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]. - disowningVMThread := vmThread. - "self cr; cr; print: 'disownVM Csp: '; printHex: vmThread cStackPointer; cr. - (0 to: 16 by: 4) do: - [:offset| - self print: ' *(esp+'; printNum: offset; print: ': '; printHex: (stackPages longAt: cogit processor sp + offset); cr]. - cogit processor printIntegerRegistersOn: Transcript."
"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 state: CTMUnavailable]. + result := ((vmThread index bitShift: DisownFlagsShift) bitOr: (activeProcessAffined ifTrue: [0] ifFalse: [ProcessUnaffinedOnDisown])) bitOr: flags. cogThreadManager releaseVM. ^result!
Item was changed: ----- Method: CoInterpreterMT>>initializeInterpreter: (in category 'initialization') ----- initializeInterpreter: bytesToShift super initializeInterpreter: bytesToShift. foreignCallbackProcessSlot := (objectMemory lengthOf: objectMemory specialObjectsOop) > ForeignCallbackProcess ifTrue: [ForeignCallbackProcess] ifFalse: [NilObject]. - self cCode: '' inSmalltalk: - [reenterThreadSchedulingLoop := ReenterThreadSchedulingLoop new]. !
Item was changed: ----- 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] - ifTrue: [(objectMemory integerValueOf: threadId) >> ThreadIdShift] ifFalse: [0]!
Item was changed: ----- Method: CoInterpreterMT>>returnToSchedulingLoopAndReleaseVMOrWakeThread:source: (in category 'process primitive support') ----- returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: source <var: #vmThread type: #'CogVMThread *'> <inline: false> + | activeThread | + activeThread := cogThreadManager currentVMThread. self recordThreadSwitchTo: (vmThread ifNotNil: [vmThread index] ifNil: [0]) source: source. vmThread ifNotNil: [cogThreadManager wakeVMThreadFor: vmThread index] ifNil: [cogit disownVM: DisownVMForThreading]. "I am not frightened of flying. Any value will do. I don't mind. Why should I be frightened of flying? There's no reason for it." + self _longjmp: activeThread reenterThreadSchedulingLoop _: 1 ! - self _longjmp: reenterThreadSchedulingLoop _: 1 !
Item was changed: ----- Method: CoInterpreterMT>>threadSchedulingLoop: (in category 'vm scheduling') ----- threadSchedulingLoop: vmThread "Enter a loop attempting to run the VM with the highest priority process and blocking on the thread's OS semaphore when unable to run that process. This version is for simulation only, simulating the longjmp back to the real threadSchedulingLoopImplementation: through exception handling."
<cmacro: '(vmThread) threadSchedulingLoopImplementation(vmThread)'> [[self threadSchedulingLoopImplementation: vmThread] on: ReenterThreadSchedulingLoop + do: [:reentry| + self assert: reentry returnValue = 1. + reentry reset. + reentry return: true]] whileTrue! - do: [:ex| - self assert: ex returnValue = 1. - reenterThreadSchedulingLoop reset. - ex return: true]] whileTrue!
Item was changed: ----- Method: CoInterpreterMT>>threadSchedulingLoopImplementation: (in category 'vm scheduling') ----- threadSchedulingLoopImplementation: vmThread "Enter a loop attempting to run the VM with the highest priority process and blocking on the thread's OS semaphore when unable to run that process. We will return to this via threadSwitchIfNecessary:from: which is called in the middle of transferTo:from: once the active process has been stored in the scheduler." <var: #vmThread type: #'CogVMThread *'> <inline: false> + self _setjmp: vmThread reenterThreadSchedulingLoop. - self _setjmp: reenterThreadSchedulingLoop. [self assert: vmThread state = CTMAssignableOrInVM. + (cogThreadManager tryLockVMOwnerTo: vmThread index) + ifTrue: + ["Yay, we're the VM owner!!" + "If relinquishing is true, then primitiveRelinquishProcessor has disowned the + VM and only a returning call or callback should take ownership in that case." + relinquishing ifFalse: [self tryToExecuteSmalltalk: vmThread]. + self disownVM: DisownVMForThreading.]. + cogThreadManager waitForWork: vmThread. + true] whileTrue! - (cogThreadManager tryLockVMOwnerTo: vmThread index) ifTrue: - ["If relinquishing is true, then primitiveRelinquishProcessor has disowned the - VM and only a returning call or callback should take ownership in that case." - relinquishing - ifTrue: [cogThreadManager disownVM: DisownVMForThreading] - ifFalse: [self tryToExecuteSmalltalk: vmThread]]. - (cogThreadManager vmOwnerIs: vmThread index) ifFalse: - [cogThreadManager waitForWork: vmThread]. - true] whileTrue!
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." | newProcOwnerIndex 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." newProcOwnerIndex := self ownerIndexOfProcess: newProc. ((activeProcessAffined := newProcOwnerIndex ~= 0) and: [(cogThreadManager vmOwnerIsCompatibleWith: newProcOwnerIndex) not]) ifFalse: [(self quickFetchInteger: PriorityIndex ofObject: newProc) < maxWaitingPriority ifTrue: [checkThreadActivation := true. self forceInterruptCheck]. ^self].
"The current process is affined to a thread, but not to the current owner. So switch to that owner." self cCode: '' inSmalltalk: [transcript ensureCr; f: 'threadSwitchIfNecessary: %08x from: %s(%d) owner %d -> %d\n' printf: { newProc. TraceSources at: sourceCode. sourceCode. cogThreadManager getVMOwner. newProcOwnerIndex }].
"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]].
newProcOwnerIndex < 0 ifTrue: [self assert: newProcOwnerIndex negated = cogThreadManager getVMOwner. vmThread := cogThreadManager ensureWillingThread. self deny: vmThread index = cogThreadManager getVMOwner. self assert: (cogThreadManager threadIndex: vmThread index isCompatibleWith: newProcOwnerIndex)] ifFalse: [vmThread := cogThreadManager vmThreadAt: newProcOwnerIndex. vmThread priority: (self quickFetchInteger: PriorityIndex ofObject: newProc). vmThread state = CTMUnavailable ifTrue: [vmThread state: 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." + cogThreadManager willingVMThread ifNotNil: - self willingVMThread ifNotNil: [:vmThread| vmThread state = CTMWantingOwnership ifTrue: [self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: sourceCode]]. self error: 'scheduler could not find a runnable process'].
"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 *'> | dvmt activeProc ownerIndex | <var: #dvmt type: #'CogVMThread *'> self assert: (cogThreadManager vmOwnerIs: vmThread index). self assert: cogThreadManager ioGetThreadLocalThreadIndex = vmThread index. dvmt := disowningVMThread. 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]. + + activeProc = objectMemory nilObject ifTrue:[^nil]. + - activeProc = objectMemory nilObject ifTrue: - [cogThreadManager disownVM: DisownVMForThreading. - ^nil]. ownerIndex := self ownerIndexOfProcess: activeProc. (ownerIndex = 0 or: [cogThreadManager vmOwnerIsCompatibleWith: ownerIndex]) ifTrue: [self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject. (objectMemory fetchPointer: SuspendedContextIndex ofObject: activeProc) ~= objectMemory nilObject ifTrue: [self externalSetStackPageAndPointersForSuspendedContextOfProcess: activeProc]. instructionPointer = cogit ceReturnToInterpreterPC ifTrue: [self deny: (self isMachineCodeFrame: framePointer). instructionPointer := self iframeSavedIP: framePointer]. self enterSmalltalkExecutive. "When we return here we should have already given up the VM and so we cannot touch any interpreter state." "NOTREACHED"]. cogThreadManager wakeVMThreadFor: ownerIndex!
Item was changed: CogAbstractInstruction subclass: #CogARMv8Compiler instanceVariableNames: '' + classVariableNames: 'AL ArithmeticAdd ArithmeticAddS ArithmeticSub ArithmeticSubS CASAL CArg0Reg CArg1Reg CArg2Reg CArg3Reg CArg4Reg CArg5Reg CArg6Reg CBNZ CBZ CC CCMPNE CLREX CS CSET D0 D1 D10 D11 D12 D13 D14 D15 D16 D17 D18 D19 D2 D20 D21 D22 D23 D24 D25 D26 D27 D28 D29 D3 D30 D31 D4 D5 D6 D7 D8 D9 DC DC_CISW DC_CIVAC DC_CSW DC_CVAC DC_CVAU DC_ISW DC_IVAC DC_ZVA DMB DSB DSB_ALL DSB_ALLSY DSB_ISH DSB_NSH DSB_OSH DSB_READS DSB_SY DSB_WRITES DivRRR EQ FP GE GT HI IC IC_IALLU IC_IALLUIS IC_IVAU ISB LDAXR LE LR LS LT LogicalAnd LogicalAndS LogicalOr LogicalXor MI MRS_CTR_EL0 MRS_ID_AA64ISAR0_EL1 MSubRRR MoveAwRR MoveRRAw MulOverflowRRR MulRRR NE NativePopRR NativePushRR PJWPNChange PJWPNClear PJWPNSet PJWPNState PL R0 R1 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R2 R20 R21 R22 R23 R24 R25 R26 R27 R28 R29 R3 R30 R31 R4 R5 R6 R7 R8 R9 SMULHRRR SP STLR STLXR SXTB SXTH SXTW SXTX UXTB UXTH UXTW UXTX VC VS XZR' - classVariableNames: 'AL ArithmeticAdd ArithmeticAddS ArithmeticSub ArithmeticSubS CASAL CArg0Reg CArg1Reg CArg2Reg CArg3Reg CArg4Reg CArg5Reg CArg6Reg CBNZ CBZ CC CCMPNE CLREX CS CSET D0 D1 D10 D11 D12 D13 D14 D15 D16 D17 D18 D19 D2 D20 D21 D22 D23 D24 D25 D26 D27 D28 D29 D3 D30 D31 D4 D5 D6 D7 D8 D9 DC DC_CISW DC_CIVAC DC_CSW DC_CVAC DC_CVAU DC_ISW DC_IVAC DC_ZVA DMB DSB DSB_ALL DSB_ALLSY DSB_ISH DSB_NSH DSB_OSH DSB_READS DSB_SY DSB_WRITES DataCacheFlushRequired DataCacheLineLength DivRRR EQ FP GE GT HI HasAtomicInstructions IC IC_IALLU IC_IALLUIS IC_IVAU ISB InstructionCacheFlushRequired InstructionCacheLineLength LDAXR LE LR LS LT LogicalAnd LogicalAndS LogicalOr LogicalXor MI MRS_CTR_EL0 MRS_ID_AA64ISAR0_EL1 MSubRRR MoveAwRR MoveRRAw MulOverflowRRR MulRRR NE NativePopRR NativePushRR PJWPNChange PJWPNClear PJWPNSet PJWPNState PL R0 R1 R10 R11 R12 R13 R14 R15 R16 R17 R18 R19 R2 R20 R21 R22 R23 R24 R25 R26 R27 R28 R29 R3 R30 R31 R4 R5 R6 R7 R8 R9 SMULHRRR SP STLR STLXR SXTB SXTH SXTW SXTX UXTB UXTH UXTW UXTX VC VS XZR' poolDictionaries: 'ARMv8A64Opcodes' category: 'VMMaker-JIT'!
!CogARMv8Compiler commentStamp: 'eem 1/7/2021 23:01' prior: 0! I generate ARMv8 machine code instructions from CogAbstractInstructions with CogRTLOpcodes. Here in "Arm ARM" refers to Arm® Architecture Reference Manual Armv8, for Armv8-A architecture profile https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-man...
Some things to know about ARMv8 instructions: Whether 31 in a register field implies the zero register or the SP register(s) depends on the specific instruction.
C3.2.1 Load/Store register If a Load instruction specifies writeback and the register being loaded is also the base register, then behavior is CONSTRAINED UNPREDICTABLE and one of the following behaviors must occur: - The instruction is treated as UNDEFINED. - The instruction is treated as a NOP. - The instruction performs the load using the specified addressing mode and the base register becomes UNKNOWN. In addition, if an exception occurs during the execution of such an instruction, the base address might be corrupted so that the instruction cannot be repeated. If a Store instruction performs a writeback and the register that is stored is also the base register, then behavior is CONSTRAINED UNPREDICTABLE and one of the following behaviors must occur: - The instruction is treated as UNDEFINED. - The instruction is treated as a NOP. - The instruction performs the store to the designated register using the specified addressing mode, but the value stored is UNKNOWN.!
Item was changed: ----- Method: CogSistaMethodSurrogate32>>counters: (in category 'accessing generated') ----- counters: aValue | index delta | index := address + 20 + baseHeaderSize. (delta := cogit getCodeToDataDelta) > 0 ifTrue: [self assert: (cogit addressIsInCodeZone: address - delta). objectMemory long32At: index - delta + put: aValue]. - put: ((aValue ifNotNil: [aValue asUnsignedInteger] ifNil: [0]))]. ^objectMemory long32At: index + put: aValue! - put: ((aValue ifNotNil: [aValue asUnsignedInteger] ifNil: [0]))!
Item was changed: ----- Method: CogSistaMethodSurrogate64>>counters: (in category 'accessing generated') ----- counters: aValue | index delta | index := address + 32 + baseHeaderSize. (delta := cogit getCodeToDataDelta) > 0 ifTrue: [self assert: (cogit addressIsInCodeZone: address - delta). objectMemory long64At: index - delta + put: aValue]. - put: ((aValue ifNotNil: [aValue asUnsignedInteger] ifNil: [0]))]. ^objectMemory long64At: index + put: aValue! - put: ((aValue ifNotNil: [aValue asUnsignedInteger] ifNil: [0]))!
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'. + cCodeGen addHeaderFile: '<stdatomic.h>'! - var: #vmOSThread type: #sqOSThread!
Item was changed: ----- Method: CogThreadManager>>acquireVMFor: (in category 'public api') ----- 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: threadIndex = self ioGetThreadLocalThreadIndex. vmThread := self vmThreadAt: threadIndex. self assert: (vmThread state = CTMUnavailable or: [vmThread state = CTMWantingOwnership]). + (self tryLockVMOwnerTo: threadIndex) + ifTrue: [vmThread state: CTMAssignableOrInVM] + ifFalse: + [vmThread state: CTMWantingOwnership. + [(self vmOwnerIs: threadIndex) + or: [self tryLockVMOwnerTo: threadIndex]] whileFalse: + [vmThread priority ifNotNil: + [coInterpreter waitingPriorityIsAtLeast: vmThread priority]. + (self vmOwnerIs: threadIndex) ifFalse: + [self ioWaitOnOSSemaphore: (self addressOf: vmThread osSemaphore)]]]. - (self tryLockVMOwnerTo: threadIndex) ifFalse: - [vmThread state: CTMWantingOwnership. - [(self vmOwnerIs: threadIndex) - or: [self tryLockVMOwnerTo: threadIndex]] whileFalse: - [vmThread priority ifNotNil: - [coInterpreter waitingPriorityIsAtLeast: vmThread priority]. - (self vmOwnerIs: threadIndex) ifFalse: - [self ioWaitOnOSSemaphore: (self addressOf: vmThread osSemaphore)]]]. coInterpreter assertProcessorStackPointersBelongToCurrentThread. vmOSThread := vmThread osThread. vmThread state: CTMAssignableOrInVM. ^vmThread!
Item was added: + ----- 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" expected = threadIndex]!
Item was changed: ----- Method: CogThreadManager>>ensureRunningVMThread: (in category 'public api') ----- ensureRunningVMThread: vmIsRelinquishing "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| "If the VM is relinquishing the processor then only schedule a thread if it has work to do." (vmIsRelinquishing and: [vmThread state ~= CTMWantingOwnership]) ifTrue: [^self]. + self assert: ((vmThread state = CTMAssignableOrInVM + or: [vmThread state = CTMInitializing]) + or: [vmThread state = CTMWantingOwnership]). (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]. self unusedThreadInfo ifNotNil: [:vmThread| (self tryLockVMOwnerTo: vmThread index) ifTrue: [(self startThreadForThreadInfo: vmThread) ifFalse: [self releaseVM]]]!
Item was changed: ----- Method: CogThreadManager>>getVMOwner (in category 'public api') ----- getVMOwner + <inline: true> + ^self atomic_load: (self addressOf: vmOwner)! - self sqLowLevelMFence. - ^vmOwner!
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 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. - [self free: newThreads. memoryIsScarce := true. ^false]. threads := newThreads. numThreads := newNumThreads. ^true!
Item was changed: ----- Method: CogThreadManager>>initialize (in category 'initialize-release') ----- initialize + numThreads := numThreadsIncrement := 0. + self cCode: [self atomic_store: (self addressOf: vmOwner) _: 0] + inSmalltalk: [vmOwner := AtomicValue newFrom: 0]. - vmOwner := numThreads := numThreadsIncrement := 0. memoryIsScarce := false. "N.B. Do not initialize threadLocalStorage; leave this to ioInitThreadLocalThreadIndices". registerStates := IdentityDictionary new!
Item was changed: ----- Method: CogThreadManager>>loadOrInitializeRegisterStateFor: (in category 'simulation') ----- loadOrInitializeRegisterStateFor: threadIndex <doNotGenerate> + |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| processor := cogit processor. processor setRegisterState: (registerStates at: threadIndex ifAbsentPut: + [self initializeProcessor: processor forThreadIndex: fakeThreadIndex. - [self initializeProcessor: processor forThreadIndex: threadIndex. processor registerState]).!
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). (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]]. - vmThread index: i]. ^true!
Item was changed: ----- Method: CogThreadManager>>releaseVM (in category 'public api') ----- releaseVM <inline: #always> self setVMOwner: 0!
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> + self assert: (self getVMOwner ~= 0 and: [self getVMOwner ~= indexOrZero]). - self assert: (vmOwner ~= 0 and: [vmOwner ~= indexOrZero]). self cCode: '' inSmalltalk: [coInterpreter transcript ensureCr; f: 'setVMOwner: %d -> %d (%s)\n' + printf: { self getVMOwner. indexOrZero. thisContext home sender selector }]. - printf: { vmOwner. 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! - "With the assignment followed by a LowLevelMFence this is equivalent to a - Fetch-Assign with an C++ Memory ordering of Squentially-Consistent. - TODO: We could make this a `release` ordering, which may perform better on ARM." - vmOwner := indexOrZero. - self sqLowLevelMFence!
Item was added: + ----- 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." + 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: (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 changed: ----- Method: CogThreadManager>>startThreadForThreadInfo: (in category 'scheduling') ----- startThreadForThreadInfo: vmThread <var: #vmThread type: #'CogVMThread *'> <inline: false> self assert: vmThread state isNil. vmThread state: 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 ioNewOS: #startVMThread: 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 := threads at: (vmOwner := 1). vmThread state: CTMInitializing. self registerVMThread: vmThread. vmThread state: CTMAssignableOrInVM!
Item was changed: ----- Method: CogThreadManager>>tryLockVMOwnerTo: (in category 'simulation') ----- tryLockVMOwnerTo: threadIndex + <api> - "In the real VM this is a direct call of Cogit>>#tryLockVMOwnerTo:/ceTryLockVMOwner. - 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. + + ^ self cCode: [self doTryLockVMOwnerTo: threadIndex] + inSmalltalk: [self simulateTryLockVMOwnerTo: threadIndex]! - ^cogit withProcessorHaltedDo: - [| previousOwner currentOwner processor result priorState | - 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 := vmOwner. - - priorState := processor registerState. - "If we currently have a VM owner, the register state should be - valid for that owner, otherwise it should be empty." - vmOwner ~= 0 - ifTrue: [self assertValidStackPointersInState: priorState forIndex: vmOwner] - ifFalse: [self assertEmptyRegisterStates: priorState]. - - vmOwner ~= threadIndex ifTrue: - ["There can be two cases here: - 1. There's currently a VM owner, and the processor has a non-empty state. - 2. There is currently no VM owner and the processor has an empty state. - In both cases, we need to restore our own register state into the processor, - as cogit>>#tryLockVMOwnerTo: call will use the processor, so the processor - cannot be empty and we also don't want the processor modify the current - owners state." - self loadOrInitializeRegisterStateFor: threadIndex]. - - result := cogit tryLockVMOwnerTo: threadIndex. - self assert: result = (threadIndex = vmOwner). - - result - ifTrue: ["If we did actually change owners, assert that previously the processor was emtpy." - previousOwner ~= vmOwner ifTrue: [self assertEmptyRegisterStates: priorState]] - ifFalse: [processor setRegisterState: priorState.]. - - coInterpreter transcript - ensureCr; - 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: vmOwner. - result]!
Item was changed: ----- Method: CogThreadManager>>vmIsOwned (in category 'public api-testing') ----- vmIsOwned "Answer if the vm is owned" <inline: true> + ^ self getVMOwner ~= 0! - self sqLowLevelMFence. - ^vmOwner > 0!
Item was changed: ----- Method: CogThreadManager>>vmOwnerAddress (in category 'Cogit lock implementation') ----- vmOwnerAddress <api> "NB. For the JIT only, so it can generate the lock & unlock functions." <returnTypeC: #usqInt> + self error: 'Deprecated!! Replaced by proper atomic functions'. ^self cCode: [(self addressOf: vmOwner) asUnsignedInteger] inSmalltalk: [cogit simulatedReadWriteVariableAddress: #vmOwnerFromMachineCode in: self]!
Item was changed: ----- Method: CogThreadManager>>vmOwnerFromMachineCode (in category 'Cogit lock implementation') ----- vmOwnerFromMachineCode <doNotGenerate> + self error: 'Deprecated!! Replaced by proper atomic C functions'. ^vmOwner!
Item was changed: ----- Method: CogThreadManager>>vmOwnerFromMachineCode: (in category 'Cogit lock implementation') ----- vmOwnerFromMachineCode: aValue <doNotGenerate> + self error: 'Deprecated!! Replaced by proper atomic C functions'. self assert: (aValue between: 0 and: numThreads). vmOwner := aValue!
Item was changed: ----- Method: CogThreadManager>>vmOwnerIs: (in category 'public api-testing') ----- vmOwnerIs: index "Test if the vmOwner is index." <inline: true> + ^self getVMOwner = index! - self sqLowLevelMFence. - ^vmOwner = index!
Item was changed: ----- Method: CogThreadManager>>vmOwnerIsCompatibleWith: (in category 'public api-testing') ----- vmOwnerIsCompatibleWith: processThreadId "Test if the vmOwner is index." <inline: true> + ^self threadIndex: self getVMOwner isCompatibleWith: processThreadId! - self sqLowLevelMFence. - ^self threadIndex: vmOwner isCompatibleWith: processThreadId!
Item was changed: ----- Method: CogThreadManager>>waitForWork: (in category 'public api') ----- waitForWork: vmThread "Wait for work." <var: #vmThread type: #'CogVMThread *'> <returnTypeC: #void> + vmThread state: CTMAssignableOrInVM. - self assert: vmThread state = CTMAssignableOrInVM. self deny: (self vmOwnerIs: vmThread index). self ioWaitOnOSSemaphore: (self addressOf: vmThread osSemaphore)!
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 | self assert: (self vmIsOwned and: [(self vmOwnerIs: index) not]). self assert: (index between: 1 and: numThreads). 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 cCode: [] inSmalltalk: - [self saveRegisterStateForCurrentProcess. self loadOrInitializeRegisterStateFor: index]. self setVMOwner: index.
vmThread state ifNil: [self startThreadForThreadInfo: vmThread] ifNotNil: + [self assert: ((vmThread state = CTMWantingOwnership + or: [vmThread state = CTMAssignableOrInVM]) + or: [vmThread state = CTMInitializing]). - [self assert: (vmThread state = CTMWantingOwnership - or: [vmThread state = CTMAssignableOrInVM]). self ioSignalOSSemaphore: (self addressOf: vmThread osSemaphore)]. self ioTransferTimeslice!
Item was changed: VMStructType subclass: #CogVMThread + instanceVariableNames: 'index state priority osSemaphore osThread newMethodOrNull argumentCount inMachineCode cStackPointer cFramePointer awolProcIndex awolProcLength awolProcesses reenterThreadSchedulingLoop' - instanceVariableNames: 'index state priority osSemaphore osThread newMethodOrNull argumentCount inMachineCode cStackPointer cFramePointer awolProcIndex awolProcLength awolProcesses' 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. '[', CogThreadManager awolProcessesIncrement printString, ']'}]. ['cStackPointer'] -> [#usqIntptr_t]. ['cFramePointer'] -> [#usqIntptr_t]. ['osSemaphore'] -> ['sqOSSemaphore']. + ['osThread'] -> ['sqOSThread']. + ['reenterThreadSchedulingLoop'] -> ['jmp_buf'] } - ['osThread'] -> ['sqOSThread'] } otherwise: [#sqInt])]!
Item was added: + ----- Method: CogVMThread>>reenterThreadSchedulingLoop (in category 'accessing') ----- + reenterThreadSchedulingLoop + <inline: #always> + ^ reenterThreadSchedulingLoop!
Item was added: + ----- Method: CogVMThread>>reenterThreadSchedulingLoop: (in category 'accessing') ----- + reenterThreadSchedulingLoop: aReenterThreadSchedulingLoop + <doNotGenerate> + reenterThreadSchedulingLoop := aReenterThreadSchedulingLoop!
Item was changed: ----- Method: Cogit>>initializeCodeZoneFrom:upTo: (in category 'initialization') ----- initializeCodeZoneFrom: startAddress upTo: endAddress <api> self initializeBackend. self sqMakeMemoryExecutableFrom: startAddress To: endAddress CodeToDataDelta: (self cppIf: #DUAL_MAPPED_CODE_ZONE ifTrue: [self addressOf: codeToDataDelta put: [:v| codeToDataDelta := v]] ifFalse: [nil]). codeBase := methodZoneBase := startAddress. backEnd stopsFrom: startAddress to: endAddress - 1. self cCode: '' inSmalltalk: [self initializeProcessor. backEnd stopsFrom: objectMemory memoryOffset "first word may not exist in the simulator's memory" to: guardPageSize - 1. backEnd has64BitPerformanceCounter ifTrue: [self initializeSimulationIOHighResClockForProfiling]]. methodZone manageFrom: methodZoneBase to: endAddress. self assertValidDualZone. backEnd detectFeatures. self maybeGenerateCacheFlush. + "self generateVMOwnerLockFunctions." - self generateVMOwnerLockFunctions. self genGetLeafCallStackPointers. self generateStackPointerCapture. self generateTrampolines. self computeEntryOffsets. self computeFullBlockEntryOffsets. self generateClosedPICPrototype. self alignMethodZoneBase.
"None of the above is executed beyond ceCheckFeatures, so a bulk flush now is the leanest thing to do." backEnd flushICacheFrom: startAddress to: methodZoneBase asUnsignedInteger. self maybeFlushWritableZoneFrom: startAddress to: methodZoneBase asUnsignedInteger. "Repeat so that now the methodZone ignores the generated run-time." methodZone manageFrom: methodZoneBase to: endAddress. "N.B. this is assumed to be the last thing done in initialization; see Cogit>>initialized. This is done only to compute openPICSize; the generated code is discarded." self generateOpenPICPrototype!
Item was removed: - ----- Method: Cogit>>tryLockVMOwnerTo: (in category 'multi-threading') ----- - tryLockVMOwnerTo: value - <api> - "ceTryLockVMOwner does an atomic compare-and-swap of the vmOwner - variable with zero and the argument, setting vmOwner to value if it was - zero. It answers if the lock was zero and hence was acquired. - - See CogThreadManager>>#tryLockVMOwnerTo: for the simulation of - processor thread switching which surrounds this method." - <cmacro: '(value) ceTryLockVMOwner(value)'> - | breakPCWasTrue | - "(thisContext findContextSuchThat: [:ctxt| ctxt selector == #primitiveRelinquishProcessor]) ifNil: - [self halt]." - (breakPCWasTrue := breakPC == true) ifTrue: - [breakPC := nil]. - processor abiMarshalArg0: value in: objectMemory memory. - ^[ | result instructions | - lastNInstructions removeAll. - result := self simulateLeafCallOf: ceTryLockVMOwner. - instructions := lastNInstructions copy. - self assert: (result ~= 0) = (coInterpreter threadManager getVMOwner = value). - result ~= 0] ensure: - [processor abiUnmarshal: 1. - breakPCWasTrue ifTrue: - [breakPC := true]]!
Item was changed: ----- Method: StackInterpreter>>setImageHeaderFlagsFrom: (in category 'image save/restore') ----- setImageHeaderFlagsFrom: headerFlags "Set the flags that are contained in the 7th long of the image header." imageHeaderFlags := headerFlags. "so as to preserve unrecognised flags." fullScreenFlag := headerFlags bitAnd: 1. imageFloatsBigEndian := (headerFlags noMask: 2) ifTrue: [1] ifFalse: [0]. + "processHasThreadAffinity := headerFlags anyMask: 4. specific to CoInterpreterMT" - "processHasThreadId := headerFlags anyMask: 4. specific to CoInterpreterMT" "flagInterpretedMethods := headerFlags anyMask: 8. specific to CoInterpreter" preemptionYields := headerFlags noMask: 16. "noThreadingOfGUIThread := headerFlags anyMask: 32. specific to CoInterpreterMT" newFinalization := headerFlags anyMask: 64. sendWheelEvents := headerFlags anyMask: 128. primitiveDoMixedArithmetic < 0 ifTrue: "i.e. has it not been set on the command line?" [primitiveDoMixedArithmetic := headerFlags noMask: 256]. "N.B. flag mask 512 is responded to by the FilePlugin & FileAttributesPlugin" upscaleDisplayIfHighDPI < 0 ifTrue: "i.e. has it not been set on the command line?" [upscaleDisplayIfHighDPI := headerFlags noMask: 1024]!
Item was added: + ----- Method: VMClass>>atomic:_compare:_exchange_strong: (in category 'C atomics simulation') ----- + atomic: anAtomicValue _compare: expected _exchange_strong: desired + "Simulator implementation of the C11 implementation of atomic instructions. + See: https://en.cppreference.com/w/c/atomic/atomic_compare_exchange" + <doNotGenerate> + self assert: (anAtomicValue class = AtomicValue). + ^ anAtomicValue compare: expected exchange: desired.!
Item was added: + ----- Method: VMClass>>atomic_load: (in category 'C atomics simulation') ----- + atomic_load: anAtomicValue + "Simulator implementation of the C11 implementation of atomic instructions. + See: https://en.cppreference.com/w/c/atomic/atomic_load" + <doNotGenerate> + self assert: (anAtomicValue class = AtomicValue). + ^ anAtomicValue value!
Item was added: + ----- Method: VMClass>>atomic_store:_: (in category 'C atomics simulation') ----- + atomic_store: anAtomicValue _: aValue + "Simulator implementation of the C11 atomic instructions. + See: https://en.cppreference.com/w/c/atomic/atomic_store" + <doNotGenerate> + self assert: (anAtomicValue class = AtomicValue). + anAtomicValue value: aValue!
vm-dev@lists.squeakfoundation.org