Leon Matthes uploaded a new version of VMMaker to project VM Maker:
http://source.squeak.org/VMMaker/VMMaker.threaded-LM.3355.mcz
==================== Summary ====================
Name: VMMaker.threaded-LM.3355
Author: LM
Time: 20 March 2024, 3:45:46.213376 pm
UUID: 537e53f3-b38a-4f82-a7d8-b3eeb232e2ce
Ancestors: VMMaker.threaded-LM.3354
Fix a critical bug in threadSwitchIfNecessary:from: .
If the activeProcess context was nil, it wasn't restored correctly.
This has been fixed now.
This has a problem though, as we need to marry the activeProcess context every time we do a thread switch, this can cause a lot of GCs, as we create a lot of context objects.
Maybe we can fix this later?
=============== Diff against VMMaker.threaded-LM.3354 ===============
Item was changed:
----- Method: CoInterpreterMT>>ownVM:withFlags: (in category 'vm scheduling') -----
ownVM: vmThreadHandle withFlags: additionalFlags
<public>
<inline: false>
<var: #vmThreadHandle type: #'void *'>
<var: #vmThread type: #'CogVMThread *'>
"This is the entry-point for plugins and primitives that wish to reacquire the VM after having
released it via disownVM or callbacks that want to acquire it without knowing their ownership
status. This call will block until the VM is owned by the current thread or an error occurs.
The argument should be the value answered by disownVM, or 0 for callbacks that don't know
if they have disowned or not. This is both an optimization to avoid having to query thread-
local storage for the current thread's index (since it can easily keep it in some local variable),
and a record of when an unbound process becomes affined to a thread for the dynamic
extent of some operation.
Answer 0 if the current thread is known to the VM (and on return owns the VM).
Answer 1 if the current thread is unknown to the VM and takes ownership.
Answer -1 if the current thread is unknown to the VM and fails to take ownership."
| flags vmThread |
vmThread := self cCoerce: vmThreadHandle to: #'CogVMThread *'.
vmThread ifNil:
[^self ownVMFromUnidentifiedThread].
self assert: vmThread = (cogThreadManager vmThreadAt: vmThread index).
flags := vmThread disownFlags bitOr: additionalFlags.
+ vmThread := cogThreadManager acquireVMFor: vmThread.
+ disownCount := disownCount - 1.
+
(flags anyMask: DisownVMForProcessorRelinquish) ifTrue:
["Presumably we have nothing to do; this primitive is typically called from the
background process. So we should /not/ try and activate any threads in the
pool; they will waste cycles finding there is no runnable process, and will
cause a VM abort if no runnable process is found. But we /do/ want to allow
FFI calls that have completed, or callbacks a chance to get into the VM; they
do have something to do. DisownVMForProcessorRelinquish indicates this."
relinquishing := false.
self sqLowLevelMFence].
- vmThread := cogThreadManager acquireVMFor: vmThread.
- disownCount := disownCount - 1.
-
disowningVMThread ifNotNil:
[vmThread = disowningVMThread ifTrue:
[self assert: (vmThread cFramePointer isNil
or: [CFramePointer = vmThread cFramePointer and: [CStackPointer = vmThread cStackPointer]]).
self assert: self successful.
self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject.
disowningVMThread := nil.
cogit recordEventTrace ifTrue:
[self recordTrace: TraceOwnVM thing: ConstOne source: 0].
^0]. "if not preempted we're done."
self preemptDisowningThread].
"We've been preempted; we must restore state and update the threadId
in our process, and may have to put the active process to sleep."
self restoreVMStateFor: vmThread andFlags: flags.
cogit recordEventTrace ifTrue:
[self recordTrace: TraceOwnVM thing: ConstTwo source: 0].
^flags bitAnd: OwnVMForeignThreadFlag!
Item was changed:
----- Method: CoInterpreterMT>>primitiveProcessBindToThreadAffinity (in category 'process primitives') -----
primitiveProcessBindToThreadAffinity
"Attempt to bind the receiver to the thread affinity of the argument or nil, where the receiver is a Process.
The thread affinity may be an integer where:
0 - means no thread affinity, the process is free to run on any thread.
> 0 - positive values mean the process has to run on the thread with this specific index.
< 0 - negative values mean the process may run on on any thread **APART** from the thread
with the absolute value of the index.
Usually values of 1, -1 and 0 are used.
Thread number 1 is the thread the VM started with. On some OSes this thread has special priviliges.
I.e. on macOS only thread 1 can make draw calls.
Therefore it is mostly important whether a thread must run on thread 1, must **not** run on thread 1
or whether it doesn't care.
If successful the VM will ensure that there is at least one compatible thread active."
| aProcess affinity waitingPriority activePriority |
<export: true>
self cCode: [] inSmalltalk: [cogThreadManager isNil ifTrue: [^self primitiveFail]].
processHasThreadAffinity ifFalse:
[^self primitiveFailFor: PrimErrUnsupported].
affinity := self stackTop.
aProcess := self stackValue: 1.
((affinity = objectMemory nilObject or: [(objectMemory isIntegerObject: affinity)
and: [affinity ~= (objectMemory integerObjectOf: 0)]])
and: [(objectMemory isPointers: aProcess)
and: [(objectMemory slotSizeOf: aProcess) >= (ThreadIdIndex + 1)]]) ifFalse:
[^self primitiveFailFor: PrimErrBadArgument].
affinity := affinity = objectMemory nilObject ifTrue: [0] ifFalse: [objectMemory integerValueOf: affinity].
affinity abs >= cogThreadManager maxNumThreads ifTrue:
[^self primitiveFailFor: PrimErrLimitExceeded].
+
(self bindProcess: aProcess toAffinity: affinity) ifNotNil:
[:ec| ^self primitiveFailFor: ec].
self methodReturnReceiver.
waitingPriority := self getMaxWaitingPriority.
activePriority := self quickFetchInteger: PriorityIndex ofObject: aProcess.
affinity := self threadAffinityOfProcess: aProcess.
(aProcess = self activeProcess
and: [(activeProcessAffined := affinity ~= 0)
and: [(cogThreadManager vmOwnerIsCompatibleWith: affinity) not]]) ifTrue:
[activePriority < waitingPriority ifTrue:
[self reduceWaitingPriorityFrom: waitingPriority to: activePriority "TODO: Check if this is correct?"].
self threadSwitchIfNecessary: aProcess from: CSThreadBind]!
Item was changed:
----- Method: CoInterpreterMT>>temporaryAffinityOfProcess: (in category 'process primitive support') -----
temporaryAffinityOfProcess: aProcess
+ <inline: false> "useful for debugging so don't inline"
"Answer the threadId of the thread threadIdField is temporarily bound to, or 0 if none."
^ self temporaryAffinedThreadId: (self threadAffinityFieldOf: aProcess)!
Item was changed:
----- Method: CoInterpreterMT>>threadAffinityOfProcess: (in category 'process primitive support') -----
threadAffinityOfProcess: aProcess
+ <inline: false>
+ "useful for debugging, so don't inline"
^self threadAffinityOfThreadID: (self threadAffinityFieldOf: aProcess)!
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 activeContext threadSwitchNecessary |
- | newProcThreadAffinity vmThread activeContext |
self assert: (cogThreadManager vmOwnerIs: cogThreadManager ioGetThreadLocalThreadIndex).
deferThreadSwitch ifTrue: [^self].
cogThreadManager assertValidProcessorStackPointersForIndex: cogThreadManager getVMOwner.
"If the current process is unaffined or it is affined to the current thread we're
ok to run, but we should yield asap if a higher-priority thread wants the VM."
newProcThreadAffinity := self threadAffinityOfProcess: newProc.
+ threadSwitchNecessary := (activeProcessAffined := newProcThreadAffinity ~= 0)
+ and: [(cogThreadManager vmOwnerIsCompatibleWith: newProcThreadAffinity) not].
+ threadSwitchNecessary ifFalse:
- ((activeProcessAffined := newProcThreadAffinity ~= 0)
- and: [(cogThreadManager vmOwnerIsCompatibleWith: newProcThreadAffinity) not]) ifFalse:
[(self quickFetchInteger: PriorityIndex ofObject: newProc) < self getMaxWaitingPriority ifTrue:
[checkThreadActivation := true.
self forceInterruptCheck].
+ "We're done, no thread switch necessary"
^self].
"The current process is affined to a thread, but not to the current owner. So switch to that owner."
+ self cCode: [] inSmalltalk:
- self cCode: '' inSmalltalk:
[transcript
ensureCr;
f: 'threadSwitchIfNecessary: %08x from: %s(%d) owner %d -> %d\n'
printf: { newProc. TraceSources at: sourceCode. sourceCode. cogThreadManager getVMOwner. newProcThreadAffinity }].
+ "If the activeProcess doesn't have a context yet, it needs one from which the new thread can resume execution."
- "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.
+ "The instructionPointer is popped from the stack in 'externalSetStackPageAndPointersForSuspendedContextOfProcess:' "
self push: instructionPointer.
+ "We at least need to externalize the stack pointers to enable a thread switch..."
self externalWriteBackHeadFramePointers.
+ activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer.
+ objectMemory storePointer: SuspendedContextIndex ofObject: newProc withValue: activeContext].
- false ifTrue:
- "If the activeProcess doesn't have a context yet, it needs one from which the new thread can resume execution."
- [activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer.
- objectMemory storePointer: SuspendedContextIndex ofObject: newProc withValue: activeContext]].
newProcThreadAffinity < 0
ifTrue:
[self assert: newProcThreadAffinity negated = cogThreadManager getVMOwner.
vmThread := cogThreadManager ensureWillingThread.
self deny: vmThread index = cogThreadManager getVMOwner.
self assert: (cogThreadManager threadIndex: vmThread index isCompatibleWith: newProcThreadAffinity)]
ifFalse:
[vmThread := cogThreadManager vmThreadAt: newProcThreadAffinity.
vmThread priority: (self quickFetchInteger: PriorityIndex ofObject: newProc).
vmThread vmThreadState = CTMUnavailable ifTrue:
[vmThread setVmThreadState: CTMWantingOwnership]].
self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: CSSwitchIfNeccessary!
Item was changed:
----- Method: CoInterpreterMT>>transferTo:from: (in category 'process primitive support') -----
transferTo: newProc from: sourceCode
"Record a process to be awoken on the next interpreter cycle. Override to
potentially switch threads either if the new process is bound to another thread,
or if there is no runnable process but there is a waiting thread. Note that the
abort on no runnable process has beeen moved here from wakeHighestPriority."
| sched oldProc activeContext |
<inline: false>
statProcessSwitch := statProcessSwitch + 1.
self push: instructionPointer.
self externalWriteBackHeadFramePointers.
self assertValidExecutionPointe: instructionPointer r: framePointer s: stackPointer.
"ensureMethodIsCogged: in makeBaseFrameFor: in
externalSetStackPageAndPointersForSuspendedContextOfProcess:
below may do a code compaction. Nil instructionPointer to avoid it getting pushed twice."
instructionPointer := 0.
sched := self schedulerPointer.
oldProc := objectMemory fetchPointer: ActiveProcessIndex ofObject: sched.
self recordContextSwitchFrom: oldProc in: sourceCode.
activeContext := self ensureFrameIsMarried: framePointer SP: stackPointer + objectMemory wordSize.
objectMemory storePointer: SuspendedContextIndex ofObject: oldProc withValue: activeContext.
newProc ifNil:
["Two possibilities. One, there is at least one thread waiting to own the VM in which
case it should be activated. Two, there are no processes to run and so abort. This
is new in the MT VM, and only happens when the primitiveRelinquishProcessor has been
preempted. In that case the idle Process is not runnable and there is no Process to return to.
By setting the activeProcess to nilObject, any threads woken by the heartbeat don't actually
start running Smalltalk. This is then fixed when an AWOL thread comes back and restores its
previous state."
objectMemory
storePointer: ActiveProcessIndex ofObject: sched withValue: objectMemory nilObject.
cogThreadManager willingVMThread ifNotNil:
[:vmThread|
vmThread vmThreadState = CTMWantingOwnership ifTrue:
[self returnToSchedulingLoopAndReleaseVMOrWakeThread: vmThread source: sourceCode]].
"self error: 'scheduler could not find a runnable process'"
+ "relinquishing := true".
+ self returnToSchedulingLoopAndReleaseVMOrWakeThread: nil source: sourceCode].
- self returnToSchedulingLoopAndReleaseVMOrWakeThread: nil source: sourceCode].
"Switch to the new process"
objectMemory
storePointer: ActiveProcessIndex ofObject: sched withValue: newProc;
storePointerUnchecked: MyListIndex ofObject: newProc withValue: objectMemory nilObject.
self externalSetStackPageAndPointersForSuspendedContextOfProcess: newProc.
"Finally thread switch if required"
self threadSwitchIfNecessary: newProc from: sourceCode!
Item was changed:
----- Method: CoInterpreterMT>>tryToExecuteSmalltalk: (in category 'vm scheduling') -----
tryToExecuteSmalltalk: vmThread
"Attempt to run the current process, if it exists, on the given vmThread."
<var: #vmThread type: #'CogVMThread *'>
| activeProc threadAffinity |
self assert: (cogThreadManager vmOwnerIs: vmThread index).
self assert: cogThreadManager ioGetThreadLocalThreadIndex = vmThread index.
disowningVMThread
ifNil: [activeProc := self activeProcess]
ifNotNil:
[self preemptDisowningThread.
activeProc := self wakeHighestPriority.
activeProc
ifNil: [activeProc := objectMemory nilObject]
ifNotNil: [objectMemory
storePointerUnchecked: MyListIndex
ofObject: activeProc
withValue: objectMemory nilObject].
objectMemory
storePointer: ActiveProcessIndex
ofObject: self schedulerPointer
withValue: activeProc].
+ "There is a special case here.
+ When the VM has relinquished, but then another thread finishes external code execution, there may no longer be a process to run.
+ However, the relinquishing flag may already have been reset by another thread that has owned the VM again."
+ activeProc = objectMemory nilObject
+ ifTrue: ["self warning: 'tryToExecuteSmalltalk: no active process!!'."
+ "relinquishing := true".
+ ^nil].
- activeProc = objectMemory nilObject ifTrue:[^nil].
threadAffinity := self threadAffinityOfProcess: activeProc.
(cogThreadManager vmOwnerIsCompatibleWith: threadAffinity) ifTrue:
[self assert: (objectMemory fetchPointer: MyListIndex ofObject: self activeProcess) = objectMemory nilObject.
(objectMemory fetchPointer: SuspendedContextIndex ofObject: activeProc) ~= objectMemory nilObject ifTrue:
[self externalSetStackPageAndPointersForSuspendedContextOfProcess: activeProc].
instructionPointer = cogit ceReturnToInterpreterPC ifTrue:
[self deny: (self isMachineCodeFrame: framePointer).
instructionPointer := self iframeSavedIP: framePointer].
self enterSmalltalkExecutive.
"When we return here we should have already given up
the VM and so we cannot touch any interpreter state."
self error: 'NOTREACHED'.].
cogThreadManager returnToSchedulingLoopAndWakeThreadFor: threadAffinity source: CSTryToExecuteSmalltalk.
"This is only reached if the above call has failed, then ownership has not been transferred and we still need to release the VM."!
Hi,
I have sent in the past email to Eliot Miranda about an issue which I discovered in August 2021 (two years ago), and I thought to raise an issue here.
It is a Linux/UNIX patch issue with the behavior of the mmap() system call or library call as used by the OpenSmalltalk COG vm.
When way back in 2021 some changes were made for Linux ARM8 to the file saUnixSpurMemory.c I reported that this was breaking the Solaris 11.3 build. Curiously the Solaris 11.4 build was still working fine.
I think (I don't know) that perhaps the behavior of mmap() on Solaris 11.4 is slightly diferent than on 11.3, to match (perhaps) the Linux kernel behavior of mmap().
An idea that I have is to ask some UNIX guru's whether there exist a autoconf-archive (configure script) test or script to test the behavior of mmap() for portability.
However here I will just report what the issue is : since august 2021 I am building succesfully on Solaris with the following patch
```
--- opensmalltalk-vm-sun-v5.0.34/platforms/unix/vm/sqUnixSpurMemory.c Fri Aug 13 19:54:42 2021
+++ p0/opensmalltalk-vm-sun-v5.0.34/platforms/unix/vm/sqUnixSpurMemory.c Sun Aug 15 14:11:58 2021
@@ -277,16 +277,21 @@
{
void *hint = sbrk(0); // a hint of the lowest possible address for mmap
void *result;
+ char *address;
+ unsigned long alignment;
pageSize = getpagesize();
pageMask = ~(pageSize - 1);
+ alignment = max(pageSize,1024*1024);
+ address = (char *)(((usqInt)hint + alignment - 1) & ~(alignment - 1));
+
#if !defined(MAP_JIT)
# define MAP_JIT 0
#endif
*desiredSize = roundUpToPage(*desiredSize);
- result = mmap(hint, *desiredSize,
+ result = mmap(address, *desiredSize,
#if DUAL_MAPPED_CODE_ZONE
PROT_READ | PROT_EXEC,
#else
```
I really need Eliot's help on this.
Eliot : what I did was try to understand your code and you seem to make some alignment calculations on the desired memory location that you request through mmap().
I believe that the traditional UNIX mmap() or the Solaris 10 and Solaris 11.3 mmap() is reacting slightly differently to the requested memory "hint".
By some experimentatoin and debugging I found the above patch, which I wrote based on looking at your code and try to mirror the calculations that you are doing, which are not fully symmetric.
By copying some code, I discovered that with the above patch the build for both Solaris 11.3 and Solaris 11.4 are working fine again.
I think this is a general UNIX issue that could be of interest to all UNIX software.
If an autoconfigure autoconf-archive test would exist to test the behavior of mmap() then that could be dealt in all software that uses mmap() with anonymous memory mapping.
As far as I know the history of the mmap() call is that it originated in the world of Sun and Solaris and is since a very long time present in Linux as well, and I think some improvements in Linux where then backported to Solaris 11.4.
That would explain why the current OpenSmalltalk-VM code works fine on Solaris 11.4.
For reasons of portability to all flavors of UNIX and Linux, I think a configure build test for the code could benefit portability of your Cog VM.
Let us know please what you think. I could simply submit my patch as a patch to mainstream upstream OpenSmalltalk but I don't know your opinion about it.
And the way I have been using it as a platform specific patch (that I apply prior to the build on Solaris) is fine for me as well.
Regards,
David Stes
--
Reply to this email directly or view it on GitHub:
https://github.com/OpenSmalltalk/opensmalltalk-vm/issues/665
You are receiving this because you are subscribed to this thread.
Message ID: <OpenSmalltalk/opensmalltalk-vm/issues/665(a)github.com>