Leon Matthes uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.threaded-LM.3357.mcz
==================== Summary ====================
Name: VMMaker.threaded-LM.3357 Author: LM Time: 28 March 2024, 5:22:40.509386 pm UUID: d0e43d64-5f78-4973-9e58-fce4a39677d5 Ancestors: VMMaker.threaded-LM.3356
Very WIP version of the multithreaded VM that fixes several performance issues.
Will explain when I get around to cleaning this up.
=============== Diff against VMMaker.threaded-LM.3356 ===============
Item was changed: ----- Method: CoInterpreterMT class>>initializePrimitiveTable (in category 'initialization') ----- initializePrimitiveTable super initializePrimitiveTable. PrimNumberRelinquishProcessor := 230. COGMTVM ifTrue: [(226 to: 229) do: [:pidx| self assert: (PrimitiveTable at: pidx + 1) = #primitiveFail]. PrimitiveTable at: 226 + 1 put: #primitiveGetOwnerLog; at: 227 + 1 put: #primitiveVMCurrentThreadId; at: 228 + 1 put: #primitiveProcessBoundThreadId; + at: 229 + 1 put: #primitiveProcessBindToThreadAffinity; + at: 190 put: #primitiveLogOwnerSwitch. "UGLY HACK: This is one of the old sound primitives"]! - at: 229 + 1 put: #primitiveProcessBindToThreadAffinity]!
Item was changed: ----- Method: CoInterpreterMT>>cedeToHigherPriorityThreads (in category 'process primitive support') ----- cedeToHigherPriorityThreads "Invoked from checkForEventsMayContextSwitch: to switch threads if a thread wanting to acquire the VM has higher priority than the active process." | activeProc processAffinity activeContext activePriority activeThread vmThread waitingPriority | <var: #activeThread type: #'CogVMThread *'> <var: #vmThread type: #'CogVMThread *'> <inline: false> activeProc := self activeProcess. activePriority := self quickFetchInteger: PriorityIndex ofObject: activeProc. processAffinity := self threadAffinityOfProcess: activeProc. activeThread := cogThreadManager currentVMThread. - self assert: (cogThreadManager threadIndex: activeThread index isCompatibleWith: processAffinity).
waitingPriority := self getMaxWaitingPriority. activeThread priority: activePriority. vmThread := cogThreadManager highestPriorityThreadIfHigherThan: activePriority expectedMax: waitingPriority.
(vmThread isNil "no waiting thread of sufficiently high priority. Do not switch." or: [vmThread = activeThread]) "The activeProcess needs to run on a different thread. Leave this to threadSwitchIfNecessary:from: in checkForEventsMayContextSwitch:" ifTrue: [waitingPriority > activePriority ifTrue: ["We found no thread of sufficiently high priority, even though waitingPriority indicated there should be one. So reduce the waiting priority back to the priority of the currently active process." self reduceWaitingPriorityFrom: waitingPriority to: activePriority]. ^self].
self assert: vmThread priority > activePriority. self assert: vmThread ~= cogThreadManager currentVMThread. self assertValidExecutionPointe: instructionPointer r: framePointer s: stackPointer.
waitingPriority > vmThread priority ifTrue: [self reduceWaitingPriorityFrom: waitingPriority to: vmThread priority]. statProcessSwitch := statProcessSwitch + 1. activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer. objectMemory storePointer: SuspendedContextIndex ofObject: activeProc withValue: activeContext. self ensurePushedInstructionPointer. self externalWriteBackHeadFramePointers. self putToSleep: activeProc yieldingIf: preemptionYields. "Transcript cr; print: #cedeToHighestPriorityThreadIfHigherThan:; cr. self printExternalHeadFrame. self print: 'ip: '; printHex: self instructionPointer. Transcript cr; flush." + cogThreadManager logOwnerSwitchTo: -5 successful: false. self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: CSCheckEvents!
Item was changed: ----- Method: CoInterpreterMT>>checkForEventsMayContextSwitch: (in category 'process primitive support') ----- checkForEventsMayContextSwitch: mayContextSwitch "Check for possible interrupts and handle one if necessary. Answer if a context switch has occurred." | switched sema now | <inline: false> <var: #now type: #usqLong> self assertSaneThreadAndProcess. cogit assertCStackWellAligned. statCheckForEvents := statCheckForEvents + 1.
"restore the stackLimit if it has been smashed." self restoreStackLimit. self externalWriteBackHeadFramePointers. self assert: stackPage = stackPages mostRecentlyUsedPage.
"Allow the platform to do anything it needs to do synchronously." self ioSynchronousCheckForEvents.
self checkCogCompiledCodeCompactionCalledFor.
objectMemory needGCFlag ifTrue: ["sufficientSpaceAfterGC: runs the incremental GC and then, if not enough space is available, the fullGC." (objectMemory sufficientSpaceAfterGC: 0) ifFalse: [self setSignalLowSpaceFlagAndSaveProcess]].
mayContextSwitch ifFalse: [^false]. + + cogThreadManager logOwnerSwitchTo: -3 successful: false.
switched := false. - self assert: deferThreadSwitch not. - deferThreadSwitch := true.
(profileProcess ~= objectMemory nilObject or: [nextProfileTick > 0 and:[self ioHighResClock >= nextProfileTick]]) ifTrue: [self zeroNextProfileTick. "Take a sample (if not already done so) for the profiler if it is active. This must be done before any of the synchronousSignals below or else we will attribute a pause in ioRelinquishProcessor to the newly activated process." profileProcess = objectMemory nilObject ifTrue: [profileProcess := self activeProcess. profileMethod := objectMemory nilObject]. "and signal the profiler semaphore if it is present" (profileSemaphore ~= objectMemory nilObject and: [self synchronousSignal: profileSemaphore]) ifTrue: [switched := true]].
self cppIf: #LRPCheck ifTrue: [self checkDeliveryOfLongRunningPrimitiveSignal ifTrue: [switched := true]].
objectMemory signalLowSpace ifTrue: [objectMemory signalLowSpace: false. "reset flag" sema := objectMemory splObj: TheLowSpaceSemaphore. (sema ~= objectMemory nilObject and: [self synchronousSignal: sema]) ifTrue: [switched := true]].
"inIOProcessEvents prevents reentrancy into ioProcessEvents and allows disabling ioProcessEvents e.g. for native GUIs. We would like to manage that here but can't since the platform code may choose to call ioProcessEvents itself in various places." false ifTrue: [((now := self ioUTCMicroseconds) >= nextPollUsecs and: [inIOProcessEvents = 0]) ifTrue: [statIOProcessEvents := statIOProcessEvents + 1. inIOProcessEvents := inIOProcessEvents + 1. self ioProcessEvents. "sets interruptPending if interrupt key pressed; may callback" inIOProcessEvents > 0 ifTrue: [inIOProcessEvents := inIOProcessEvents - 1]. nextPollUsecs := now + 20000 "msecs to wait before next call to ioProcessEvents. Note that strictly speaking we might need to update 'now' at this point since ioProcessEvents could take a very long time on some platforms"]] ifFalse: [(now := self ioUTCMicroseconds) >= nextPollUsecs ifTrue: [statIOProcessEvents := statIOProcessEvents + 1. self ioProcessEvents. "sets interruptPending if interrupt key pressed; may callback" nextPollUsecs := now + 20000 "msecs to wait before next call to ioProcessEvents. Note that strictly speaking we might need to update 'now' at this point since ioProcessEvents could take a very long time on some platforms"]].
interruptPending ifTrue: [interruptPending := false. "reset interrupt flag" sema := objectMemory splObj: TheInterruptSemaphore. (sema ~= objectMemory nilObject and: [self synchronousSignal: sema]) ifTrue: [switched := true]].
nextWakeupUsecs ~= 0 ifTrue: [now >= nextWakeupUsecs ifTrue: [nextWakeupUsecs := 0. "set timer interrupt to 0 for 'no timer'" + cogThreadManager logOwnerSwitchTo: -4 successful: false. sema := objectMemory splObj: TheTimerSemaphore. (sema ~= objectMemory nilObject and: [self synchronousSignal: sema]) ifTrue: [switched := true]]].
"signal any pending finalizations" pendingFinalizationSignals > 0 ifTrue: [pendingFinalizationSignals := 0. sema := objectMemory splObj: TheFinalizationSemaphore. (sema ~= objectMemory nilObject and: [self synchronousSignal: sema]) ifTrue: [switched := true]].
"signal all semaphores in semaphoresToSignal" self signalExternalSemaphores ifTrue: [switched := true].
- deferThreadSwitch := false. checkThreadActivation ifTrue: [checkThreadActivation := false. self cedeToHigherPriorityThreads]. "N.B. This may not return if we do switch."
+ cogThreadManager logOwnerSwitchTo: -2 successful: false. + "self threadSwitchIfNecessary: self activeProcess from: CSCheckEvents." - self threadSwitchIfNecessary: self activeProcess from: CSCheckEvents. ^switched!
Item was added: + ----- Method: CoInterpreterMT>>primitiveLogOwnerSwitch (in category 'logging primitives') ----- + primitiveLogOwnerSwitch + + | vmOwnerValue | + vmOwnerValue := self stackIntegerValue: 0. + self successful ifFalse: [^ self]. + + cogThreadManager logOwnerSwitchTo: vmOwnerValue successful: false. + self methodReturnReceiver.!
Item was changed: ----- Method: CoInterpreterMT>>restoreVMStateFor:andFlags: (in category 'vm scheduling') ----- restoreVMStateFor: vmThread andFlags: flags "We've been preempted; we must restore state and update the threadId in our process, and may have to put the active process to sleep." | sched activeProc myProc | sched := self schedulerPointer. activeProc := objectMemory fetchPointer: ActiveProcessIndex ofObject: sched. (flags anyMask: OwnVMForeignThreadFlag) ifTrue: [self assert: foreignCallbackProcessSlot == ForeignCallbackProcess. myProc := objectMemory splObj: foreignCallbackProcessSlot. self assert: myProc ~= objectMemory nilObject. objectMemory splObj: foreignCallbackProcessSlot put: objectMemory nilObject] ifFalse: [myProc := self popProcessWithTemporaryAffinity: vmThread index fromList: (objectMemory splObj: ProcessInExternalCodeTag)]. self assert: (myProc ~= objectMemory nilObject and: [activeProc ~= myProc]). (activeProc ~= objectMemory nilObject and: [(objectMemory fetchPointer: MyListIndex ofObject: activeProc) = objectMemory nilObject]) ifTrue: + [|myPriority activePriority| + "If the activeProcess doesn't have a context yet, it needs one from which we can resume later. - ["If the activeProcess doesn't have a context yet, it needs one from which we can resume later. This mostly only happens when a threadSwitchIfNecessary:from: ends up switching to a thread that's CTMUnavailable (this thread). See the comment in threadSwitchIfNecessary:from:" self ensureProcessHasContext: activeProc. + self putToSleep: activeProc yieldingIf: preemptionYields. + "TODO: The active process we just preempted may have a higher priority than we have!!!!!! + So we need to force an interrupt check and force that we actually check that the highest thread is running!! + For now, use an ugly hack!!" + activePriority := self quickFetchInteger: PriorityIndex ofObject: activeProc. + myPriority:= self quickFetchInteger: PriorityIndex ofObject: myProc. + activePriority > myPriority ifTrue: + [nextWakeupUsecs := self ioUTCMicroseconds. + self forceInterruptCheck.]]. - self putToSleep: activeProc yieldingIf: preemptionYields]. objectMemory storePointerUnchecked: MyListIndex ofObject: myProc withValue: objectMemory nilObject; storePointer: ActiveProcessIndex ofObject: sched withValue: myProc.
self setTemporaryThreadAffinityOfProcess: myProc to: 0. self initPrimCall. self cCode: [self externalSetStackPageAndPointersForSuspendedContextOfProcess: myProc] inSmalltalk: ["Bypass the no-offset stack depth check in the simulator's externalSetStackPageAndPointersForSuspendedContextOfProcess:" super externalSetStackPageAndPointersForSuspendedContextOfProcess: myProc. "We're in ownVM:, hence in a primitive, hence need to include the argument count" (self isMachineCodeFrame: framePointer) ifTrue: [self maybeCheckStackDepth: vmThread argumentCount sp: stackPointer pc: instructionPointer]]. "If this primitive is called from machine code maintain the invariant that the return pc of an interpreter callee calling a machine code caller is ceReturnToInterpreterPC." (vmThread inMachineCode and: [instructionPointer >= objectMemory startOfMemory]) ifTrue: [self iframeSavedIP: framePointer put: instructionPointer. instructionPointer := cogit ceReturnToInterpreterPC]. newMethod := vmThread newMethodOrNull. argumentCount := vmThread argumentCount. vmThread newMethodOrNull: nil. self cCode: '' inSmalltalk: [| range | range := self cStackRangeForThreadIndex: vmThread index. self assert: ((range includes: vmThread cStackPointer) and: [range includes: vmThread cFramePointer])]. self setCFramePointer: vmThread cFramePointer setCStackPointer: vmThread cStackPointer. + self assert: newMethod notNil. + + self forceInterruptCheck. - self assert: newMethod notNil !
Item was added: + ----- Method: CoInterpreterMT>>returnToExecutive:postContextSwitch: (in category 'as yet unclassified') ----- + returnToExecutive: inInterpreter postContextSwitch: switchedContext + + <inline: false> + switchedContext ifTrue: [self threadSwitchIfNecessary: self activeProcess from: CSSwitchIfNeccessary]. + + ^ super returnToExecutive: inInterpreter postContextSwitch: switchedContext!
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 assert: vmThread vmThreadState = 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." + self tryToExecuteSmalltalk: vmThread. - relinquishing ifFalse: [self tryToExecuteSmalltalk: vmThread]. "tryToExecuteSmalltalk: may return if there's no runnable process. Usually it doesn't return, but jumps straight back to the _setjmp at the top of this function, so this is only reached in case there's no runnable process." "TODO: Do we need to saveRegisterStateForCurrentProcess here?" self releaseVM]. 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." | newProcThreadAffinity vmThread threadSwitchNecessary | self assert: (cogThreadManager vmOwnerIs: cogThreadManager ioGetThreadLocalThreadIndex). - deferThreadSwitch ifTrue: [^self].
cogThreadManager assertValidProcessorStackPointersForIndex: cogThreadManager getVMOwner.
"If the current process is unaffined or it is affined to the current thread we're ok to run, but we should yield asap if a higher-priority thread wants the VM." newProcThreadAffinity := self threadAffinityOfProcess: newProc. threadSwitchNecessary := (activeProcessAffined := newProcThreadAffinity ~= 0) and: [(cogThreadManager vmOwnerIsCompatibleWith: newProcThreadAffinity) not]. threadSwitchNecessary ifFalse: [(self quickFetchInteger: PriorityIndex ofObject: newProc) < self getMaxWaitingPriority ifTrue: [checkThreadActivation := true. self forceInterruptCheck]. "We're done, no thread switch necessary" ^self].
"The current process is affined to a thread, but not to the current owner. So switch to that owner." self cCode: [] inSmalltalk: [transcript ensureCr; f: 'threadSwitchIfNecessary: %08x from: %s(%d) owner %d -> %d\n' printf: { newProc. TraceSources at: sourceCode. sourceCode. cogThreadManager getVMOwner. newProcThreadAffinity }].
"In most cases, we can just switch the thread here, without externalizing the stack pages. If the Processes context is nil, it's state is on the stack. As we're already done context switching, the new thread can just use the interpreter state as-is, without restoring the state from the context. tryToExecuteSmalltalk: already includes a check whether the SuspendedContext is nil. If it is, it leaves the interpreter state alone and just assumes it's correct. This is nice and fast. Otherwise it calls externalSetStackPageAndPointersForSuspendedContextOfProcess: to restore the interpreter state. There is however a special case. When we switch to a thread that is currently CTMUnavailable, that thread will need to restore its process when it tries to own the VM again. The check to restore the context has been moved there (in restoreVMStateFor:andFlags:), so that it only happens in that one case and not every time. In case there are other such special-cases later, adding a call to ensureProcessHasContext: here should fix it."
newProcThreadAffinity < 0 ifTrue: [self assert: newProcThreadAffinity negated = cogThreadManager getVMOwner. + vmThread := cogThreadManager ensureUnoccupiedThread. - vmThread := cogThreadManager ensureWillingThread. self deny: vmThread index = cogThreadManager getVMOwner. + self assert: (cogThreadManager threadIndex: vmThread index isCompatibleWith: newProcThreadAffinity). + self assert: (vmThread vmThreadState = CTMAssignableOrInVM or: [vmThread vmThreadState = CTMInitializing])] - self assert: (cogThreadManager threadIndex: vmThread index isCompatibleWith: newProcThreadAffinity)] ifFalse: [vmThread := cogThreadManager vmThreadAt: newProcThreadAffinity. vmThread priority: (self quickFetchInteger: PriorityIndex ofObject: newProc). + self flag: 'TODO - This causes issues, the new thread doesn''t know that it needs to check whether there''s a higher-priority process to return to!!'. vmThread vmThreadState = CTMUnavailable ifTrue: [vmThread setVmThreadState: CTMWantingOwnership]]. self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: CSSwitchIfNeccessary!
Item was changed: ----- Method: CoInterpreterMT>>transferTo:from: (in category 'process primitive support') ----- transferTo: newProc from: sourceCode "Record a process to be awoken on the next interpreter cycle. Override to potentially switch threads either if the new process is bound to another thread, or if there is no runnable process but there is a waiting thread. Note that the abort on no runnable process has beeen moved here from wakeHighestPriority." | sched oldProc activeContext | <inline: false> statProcessSwitch := statProcessSwitch + 1. self push: instructionPointer. self externalWriteBackHeadFramePointers. self assertValidExecutionPointe: instructionPointer r: framePointer s: stackPointer. "ensureMethodIsCogged: in makeBaseFrameFor: in externalSetStackPageAndPointersForSuspendedContextOfProcess: below may do a code compaction. Nil instructionPointer to avoid it getting pushed twice." instructionPointer := 0. sched := self schedulerPointer. oldProc := objectMemory fetchPointer: ActiveProcessIndex ofObject: sched. self recordContextSwitchFrom: oldProc in: sourceCode. activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer + objectMemory wordSize. objectMemory storePointer: SuspendedContextIndex ofObject: oldProc withValue: activeContext.
newProc ifNil: ["Two possibilities. One, there is at least one thread waiting to own the VM in which case it should be activated. Two, there are no processes to run and so abort. This is new in the MT VM, and only happens when the primitiveRelinquishProcessor has been preempted. In that case the idle Process is not runnable and there is no Process to return to. By setting the activeProcess to nilObject, any threads woken by the heartbeat don't actually start running Smalltalk. This is then fixed when an AWOL thread comes back and restores its previous state." objectMemory storePointer: ActiveProcessIndex ofObject: sched withValue: objectMemory nilObject. cogThreadManager willingVMThread ifNotNil: [:vmThread| vmThread vmThreadState = CTMWantingOwnership ifTrue: [self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: sourceCode]]. "self error: 'scheduler could not find a runnable process'" "relinquishing := true". self returnToSchedulingLoopAndReleaseVMOrWakeThread: nil source: sourceCode].
"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"! - self threadSwitchIfNecessary: newProc from: sourceCode!
Item was added: + ----- Method: CogThreadManager>>ensureUnoccupiedThread (in category 'scheduling') ----- + ensureUnoccupiedThread + | unoccupied newIndex | + unoccupied := self unoccupiedVMThread. + unoccupied ifNotNil: + [^unoccupied]. + 1 to: numThreads do: + [:index| + (self vmThreadAt: index) vmThreadState = CTMUninitialized ifTrue: + [self startThreadForThreadIndex: index. + ^self vmThreadAt: index]]. + self startThreadForThreadIndex: (newIndex := numThreads + 1). + ^self vmThreadAt: newIndex!
Item was changed: ----- Method: CogThreadManager>>returnToSchedulingLoopAndWakeThreadFor:source: (in category 'public api') ----- returnToSchedulingLoopAndWakeThreadFor: threadAffinity source: sourceIndex "Transfer the VM to a thread that is compatible with the given affinity. Called from a thread that finds the highest priority runnable process is bound to the given affinity." <returnTypeC: #void> "Make sure we do actually need to wake a thread" self assert: (self vmOwnerIsCompatibleWith: threadAffinity) not. self assert: threadAffinity ~= 0.
^ threadAffinity > 0 ifTrue: [self assert: (threadAffinity between: 1 and: numThreads). self returnToSchedulingLoopAndReleaseVMOrWakeThread: (threads at: threadAffinity) source: sourceIndex] ifFalse: [|willingThread| self assert: (self getVMOwner = threadAffinity negated). "We know the thread affinity is 'any thread other then this one!!'." + willingThread := self ensureUnoccupiedThread. - willingThread := self ensureWillingThread. willingThread ifNotNil: [self returnToSchedulingLoopAndReleaseVMOrWakeThread: willingThread source: sourceIndex]]!
Item was added: + ----- Method: CogThreadManager>>unoccupiedVMThread (in category 'thread set') ----- + unoccupiedVMThread + + "Answer a pointer to a live CogVMThread in an unoccupied state, + (other than the current owner if the VM is owned), or nil if none. + Specifically, do not answer any threads that actively want ownership. + Those have other work to do, so can't take over whatever it is we want to do right now." + <returnTypeC: #'CogVMThread *'> + <inline: false> + 1 to: numThreads do: + [:i| + (self vmOwnerIs: i) ifFalse: + [| thread | + thread := threads at: i. + thread vmThreadState = CTMAssignableOrInVM ifTrue: + [^ thread]]]. + ^ nil!
Item was changed: ----- Method: CogThreadManager>>willingVMThread (in category 'thread set') ----- willingVMThread "Answer a pointer to a live CogVMThread in any of the ``will do VM work'' states (other than the current owner if the VM is owned), or nil if none. Preferentially answer threads wanting ownership." <returnTypeC: #'CogVMThread *'> | thread threadWantingVM threadWilling | <inline: false> threadWantingVM := threadWilling := nil. 1 to: numThreads do: [:i| (self vmOwnerIs: i) ifFalse: [thread := threads at: i. thread vmThreadState = CTMWantingOwnership ifTrue: [(threadWantingVM isNil or: [threadWantingVM priority < thread priority]) ifTrue: [threadWantingVM := thread]]. thread vmThreadState = CTMAssignableOrInVM ifTrue: + "Threads that want ownership always have precedence over threads + that are simply assignable. The priority is only valid if the thread is + in the WantingOwnership state." + [threadWilling isNil ifTrue: - [(threadWilling isNil - or: [threadWilling priority < thread priority]) ifTrue: [threadWilling := thread]]]]. ^threadWantingVM ifNil: [threadWilling]!
Item was added: + ----- Method: SpurMemoryManager>>printNewSpaceOops (in category 'debug printing') ----- + printNewSpaceOops + "useful for VM debugging" + <public> + <inline: false> + self allNewSpaceObjectsDo: [:objOop | coInterpreter shortPrintOop: objOop]!
Item was changed: ----- Method: SqueakSSLPlugin>>primitiveCreate (in category 'primitives') ----- primitiveCreate "Primitive. Creates a new SSL session and returns its handle." + | handle vmHandle | - | handle | <export: true> interpreterProxy methodArgumentCount = 0 ifFalse:[^interpreterProxy primitiveFail]. + + vmHandle := interpreterProxy disownVM: DisownVMForThreading. handle := self cCode: 'sqCreateSSL()' inSmalltalk:[0]. + interpreterProxy ownVM: vmHandle. + handle = 0 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy pop: interpreterProxy methodArgumentCount+1. interpreterProxy pushInteger: handle. !
Item was changed: ----- Method: SqueakSSLPlugin>>primitiveDestroy (in category 'primitives') ----- primitiveDestroy "Primitive. Destroys an SSL session."
+ | handle result vmHandle | - | handle result | <export: true> interpreterProxy methodArgumentCount = 1 ifFalse:[^interpreterProxy primitiveFail]. handle := interpreterProxy stackIntegerValue: 0. interpreterProxy failed ifTrue:[^nil]. + + vmHandle := interpreterProxy disownVM: DisownVMForThreading. result := self cCode: 'sqDestroySSL(handle)' inSmalltalk:[handle. 0]. + interpreterProxy ownVM: vmHandle. + result = 0 ifTrue:[^interpreterProxy primitiveFail]. interpreterProxy pop: interpreterProxy methodArgumentCount.
!
vm-dev@lists.squeakfoundation.org