Eliot Miranda uploaded a new version of CogPools-ISAs to project VM Maker:
http://source.squeak.org/VMMaker/CogPools-ISAs-eem.13.mcz
==================== Summary ====================
Name: CogPools-ISAs-eem.13
Author: eem
Time: 28 December 2022, 6:53:41.688471 pm
UUID: 3da180c9-0a31-43d1-8261-73454dc53c87
Ancestors: CogPools-ISAs-eem.12
Add a useful instruction field decomposer for aarch64
=============== Diff against CogPools-ISAs-eem.12 ===============
Item was added:
+ ----- Method: ARMv8A64Opcodes class>>decomposeLoadStore: (in category 'utilities') -----
+ decomposeLoadStore: word
+ ^{ 'op0'. word >> 28 bitAnd: 15.
+ 'op1'. word >> 26 bitAnd: 1.
+ 'op2'. word >> 23 bitAnd: 3.
+ 'op3'. word >> 16 bitAnd: 63.
+ 'op4'. word >> 10 bitAnd: 3 }!
Hello,
My name is Pierre Misse-Chanabier.
I'm finishing my PhD in the RMOD team on testing Virtual Machines soon.
I wanted to share with you a project I did with Theo Rogliano on the
Pharo VM, which should be straightforward to use on the OpenSmalltalk-VM
as well.
Except for the UI, of course !
In a nutshell, Polyphemus [1] reifies OOPs in the memory to propose a
representation that is closer to the language level.
Therefore, it provides a layer that is easier to work with.
For example, if an OOP is pointing to a class, I can ask for its name,
inspect it as I would at the language level, or send message special to
a class that shouldn't work on a string OOP for example.
We have develop a few visualizations and basic tools for the Pharo VM.
Particularly, we have been able to fix error containing meta-errors and
memory corruptions.
You may have encounter a video of this at the Technological Innovation
award at ESUG 2022 [2].
We have also wrote a paper that will be published soon at VMIL 2022 [3].
I haven't checked in quite a long time, but I think that most (or all)
of the simulator features we use in Polyphemus are available in the
OpenSmalltal-VM as well.
I hope it'll be useful to someone else, or at least interesting to some
of you :)
Pierre
[1] Github Repository: https://github.com/hogoww/Polyphemus/
[2] Video Presentation for Esug Innovation award (I was unable to
present it myself): https://www.youtube.com/watch?v=zf3cCtNW830
[3] VMIL'22: Ease VM Level Tooling with Language Level Ordinary Object
Pointers https://hal.inria.fr/hal-03827632 (preprint)
Stephen,
I cross-posted this to vm-dev(a)lists.squeakfoundation.org
The vm-dev folks have a deeper view of the FFI mechanisms.
-KenD
On 2022-12-27 16:59, Stephen Travis Pope via Cuis-dev wrote:
> Hello colleagues,
>
> The good news: the socket plug-in in Cuis on an M1-based Mac works
> great, so I can use the OSC (OpenSoundControl) output from
> Siren-on-Cuis to play! I've also ported some of the further support
> code and updated the workbook.
> The less-good news: after creating a Mac dylib bundle with the PortMIDI
> primitives that work on Squeak, and porting the Squeak MIDIPort class
> (Squeak uses a different primitive-calling format) to Cuis, I can get
> as far as primitives failing with the message, 'Callout mechanism not
> available' -- I'm stumped here...
>
> I put all of Siren (Cuis and Squeak versions) on GitHub at
> https://github.com/stpope/Siren9C and will keep it up to date as I
> progress.
>
> stp
>
> --------
>
> Stephen Travis Pope Ojai, California, USA
>
> http://HeavenEverywhere.com
> http://FASTLabInc.com
> https://vimeo.com/user19434036/videos
> http://heaveneverywhere.com/Reflections
Eliot Miranda uploaded a new version of VMMaker to project VM Maker:
http://source.squeak.org/VMMaker/VMMaker.oscog-eem.3284.mcz
==================== Summary ====================
Name: VMMaker.oscog-eem.3284
Author: eem
Time: 25 December 2022, 12:56:31.883528 pm
UUID: 99050939-5e02-4371-aa33-f716c24648f6
Ancestors: VMMaker.oscog-eem.3283
Slang: refactor macro generation to make it ewasier to check for validity in the slang test workspace.
Cog ARMv8 Simulator: make the DUAL_MAPPED_CODE_ZONE regime simulate again, fixing an assert fail due to overlap of the guard page and the end of the code zone when mapped. We need an additional guard poage's delta to make sure that the simulated hi-rez timer code is generated outside of the code zone. And the asserts for executability in simulateCogCodeAt:/simulateLeafCallOf: only apply to the non-dual-mapped regime.
Fix some excess var:type: in old code.
=============== Diff against VMMaker.oscog-eem.3283 ===============
Item was added:
+ ----- Method: CCodeGenerator>>emitAsCMacro:on: (in category 'C code generator') -----
+ emitAsCMacro: tMethod on: aStream
+ "Store the global variable declarations on the given stream. Answer any constants used in the macros."
+ aStream
+ nextPutAll: '#define ';
+ nextPutAll:(self cFunctionNameFor: tMethod selector);
+ nextPutAll: (macros at: tMethod selector); cr!
Item was changed:
----- Method: CCodeGenerator>>emitCMacros:on: (in category 'C code generator') -----
+ emitCMacros: methodList on: aStream
- emitCMacros: methodList on: aStream
"Store the global variable declarations on the given stream. Answer any constants used in the macros."
| usedConstants |
macros isEmpty ifTrue: [^#()].
aStream cr; nextPutAll: '/*** Macros ***/'; cr.
usedConstants := Set new.
(methodList reject: [:m| m isRealMethod]) do:
[:m |
m definedAsMacro ifTrue:
+ [self emitAsCMacro: m on: aStream.
- [aStream
- nextPutAll: '#define ';
- nextPutAll:(self cFunctionNameFor: m selector);
- nextPutAll: (macros at: m selector); cr.
m compiledMethod literalsDo:
[:lit|
(lit isVariableBinding and: [(macros at: m selector) includesSubstring: lit key]) ifTrue:
[usedConstants add: lit key]]]].
aStream cr.
^usedConstants!
Item was changed:
----- Method: CogRegisterAllocatingSimStackEntry>>isMergedWithTargetEntry: (in category 'comparing') -----
isMergedWithTargetEntry: targetEntry
"The receiver is a simStackEntry at a jump to the corresponding simStackEntry at the jump's target.
Answer if no merge is required for the jump."
+ <var: 'targetEntry' type: #'CogSimStackEntry *'>
- <var: 'ssEntry' type: #'CogSimStackEntry *'>
spilled ~= targetEntry spilled ifTrue: "push or pop required"
[^false].
(liveRegister = NoReg and: [targetEntry liveRegister ~= NoReg]) ifTrue: "register load required"
[^false].
(self isSameEntryAs: targetEntry) ifTrue:
[^liveRegister = targetEntry liveRegister].
(type = SSConstant and: [targetEntry type = SSRegister and: [liveRegister = targetEntry register]]) ifTrue:
[^true].
"self: const =1 (16r1) (live: Extra4Reg) {172} vs reg ReceiverResultReg {127}"
"self: reg ReceiverResultReg {95} vs reg Extra5Reg {85}"
"self: (bo ReceiverResultReg+296 (live: Extra5Reg) {88} vs reg ReceiverResultReg {84}"
"self: const =1 (16r1) (spilled) {167} vs spill @ FPReg-48 {122}"
((type = SSConstant and: [targetEntry type = SSRegister and: [liveRegister ~= targetEntry registerOrNone]])
or: [(type = SSRegister and: [targetEntry type = SSRegister and: [register ~= targetEntry registerOrNone]])
or: [(type = SSBaseOffset and: [register = ReceiverResultReg and: [targetEntry type = SSRegister]])
or: [(type = SSConstant and: [targetEntry type = SSSpill])]]]) ifFalse:
[self halt: 'comment the incompatible pair please'].
^false!
Item was changed:
----- Method: CogVMSimulator>>openOn:extraMemory: (in category 'initialize-release') -----
openOn: fileName extraMemory: extraBytes
"CogVMSimulator new openOn: 'clone.im' extraMemory: 100000"
| f version headerSize dataSize count oldBaseAddr bytesToShift swapBytes
headerFlags firstSegSize heapSize
hdrNumStackPages hdrEdenBytes hdrMaxExtSemTabSize hdrCogCodeSize
stackZoneSize methodCacheSize primTraceLogSize allocationReserve |
"open image file and read the header"
(f := self openImageFileNamed: fileName) ifNil: [^self].
"Set the image name and the first argument; there are
no arguments during simulation unless set explicitly."
systemAttributes at: 1 put: fileName.
["begin ensure block..."
imageName := f fullName.
f binary.
version := self getWord32FromFile: f swap: false. "current version: 16r1968 (=6504) vive la revolucion!!"
(self readableFormat: version)
ifTrue: [swapBytes := false]
ifFalse: [(version := version byteSwap32) = self imageFormatVersion
ifTrue: [swapBytes := true]
ifFalse: [self error: 'incomaptible image format']].
headerSize := self getWord32FromFile: f swap: swapBytes.
dataSize := self getLongFromFile: f swap: swapBytes. "length of heap in file"
oldBaseAddr := self getLongFromFile: f swap: swapBytes. "object memory base address of image"
objectMemory specialObjectsOop: (self getLongFromFile: f swap: swapBytes).
objectMemory lastHash: (self getLongFromFile: f swap: swapBytes). "Should be loaded from, and saved to the image header"
savedWindowSize := self getLongFromFile: f swap: swapBytes.
headerFlags := self getLongFromFile: f swap: swapBytes.
self setImageHeaderFlagsFrom: headerFlags.
extraVMMemory := self getWord32FromFile: f swap: swapBytes.
hdrNumStackPages := self getShortFromFile: f swap: swapBytes.
"4 stack pages is small. Should be able to run with as few as
three. 4 should be comfortable but slow. 8 is a reasonable
default. Can be changed via vmParameterAt: 43 put: n"
numStackPages := desiredNumStackPages ~= 0
ifTrue: [desiredNumStackPages]
ifFalse: [hdrNumStackPages = 0
ifTrue: [self defaultNumStackPages]
ifFalse: [hdrNumStackPages]].
desiredNumStackPages := hdrNumStackPages.
stackZoneSize := self computeStackZoneSize.
"This slot holds the size of the native method zone in 1k units. (pad to word boundary)."
hdrCogCodeSize := (self getShortFromFile: f swap: swapBytes) * 1024.
cogCodeSize := desiredCogCodeSize ~= 0
ifTrue: [desiredCogCodeSize]
ifFalse:
[hdrCogCodeSize = 0
ifTrue: [cogit defaultCogCodeSize]
ifFalse: [hdrCogCodeSize]].
desiredCogCodeSize := hdrCogCodeSize.
self assert: f position = (objectMemory wordSize = 4 ifTrue: [40] ifFalse: [64]).
hdrEdenBytes := self getWord32FromFile: f swap: swapBytes.
objectMemory edenBytes: (desiredEdenBytes ~= 0
ifTrue: [desiredEdenBytes]
ifFalse:
[hdrEdenBytes = 0
ifTrue: [objectMemory defaultEdenBytes]
ifFalse: [hdrEdenBytes]]).
desiredEdenBytes := hdrEdenBytes.
hdrMaxExtSemTabSize := self getShortFromFile: f swap: swapBytes.
hdrMaxExtSemTabSize ~= 0 ifTrue:
[self setMaxExtSemSizeTo: hdrMaxExtSemTabSize].
"pad to word boundary. This slot can be used for anything else that will fit in 16 bits.
Preserve it to be polite to other VMs."
the2ndUnknownShort := self getShortFromFile: f swap: swapBytes.
self assert: f position = (objectMemory wordSize = 4 ifTrue: [48] ifFalse: [72]).
firstSegSize := self getLongFromFile: f swap: swapBytes.
objectMemory firstSegmentSize: firstSegSize.
"For Open PICs to be able to probe the method cache during
simulation the methodCache must be relocated to memory."
methodCacheSize := methodCache size * objectMemory wordSize.
primTraceLogSize := primTraceLog size * objectMemory wordSize.
"To cope with modern OSs that disallow executing code in writable memory we dual-map
the code zone, one mapping with read/write permissions and the other with read/execute
permissions. In simulation all we can do is use memory, so if we're simulating dual mapping
we use double the memory and simulate the memory sharing in the Cogit's backEnd."
effectiveCogCodeSize := (InitializationOptions at: #DUAL_MAPPED_CODE_ZONE ifAbsent: [false])
+ ifTrue: [cogCodeSize * 2 + Cogit guardPageSize]
- ifTrue: [cogCodeSize * 2]
ifFalse: [cogCodeSize].
"allocate interpreter memory. This list is in address order, low to high.
In the actual VM the stack zone exists on the C stack."
heapBase := (Cogit guardPageSize
+ effectiveCogCodeSize
+ stackZoneSize
+ methodCacheSize
+ primTraceLogSize
+ self rumpCStackSize) roundUpTo: objectMemory allocationUnit.
"compare memory requirements with availability"
allocationReserve := self interpreterAllocationReserveBytes.
objectMemory hasSpurMemoryManagerAPI
ifTrue:
[| freeOldSpaceInImage headroom |
freeOldSpaceInImage := self getLongFromFile: f swap: swapBytes.
headroom := objectMemory
initialHeadroom: extraVMMemory
givenFreeOldSpaceInImage: freeOldSpaceInImage.
heapSize := objectMemory roundUpHeapSize:
dataSize
+ headroom
+ objectMemory newSpaceBytes
+ (headroom > allocationReserve
ifTrue: [0]
ifFalse: [allocationReserve])]
ifFalse:
[heapSize := dataSize
+ extraBytes
+ objectMemory newSpaceBytes
+ (extraBytes > allocationReserve
ifTrue: [0]
ifFalse: [allocationReserve])].
"allocate interpreter memory"
heapBase := objectMemory
setHeapBase: heapBase
memoryLimit: heapBase + heapSize
endOfMemory: heapBase + dataSize. "bogus for Spur"
self assert: cogCodeSize \\ 4 = 0.
self assert: objectMemory memoryLimit \\ 4 = 0.
self assert: self rumpCStackSize \\ 4 = 0.
objectMemory allocateMemoryOfSize: objectMemory memoryLimit.
"read in the image in bulk, then swap the bytes if necessary"
f position: headerSize.
count := objectMemory readHeapFromImageFile: f dataBytes: dataSize.
count ~= dataSize ifTrue: [self halt]]
ensure: [f close].
self moveMethodCacheToMemoryAt: objectMemory cogCodeBase + effectiveCogCodeSize + stackZoneSize.
self movePrimTraceLogToMemoryAt: objectMemory cogCodeBase + effectiveCogCodeSize + stackZoneSize + methodCacheSize.
self ensureImageFormatIsUpToDate: swapBytes.
bytesToShift := objectMemory memoryBaseForImageRead - oldBaseAddr. "adjust pointers for zero base address"
UIManager default
informUser: 'Relocating object pointers...'
during: [self initializeInterpreter: bytesToShift].
cogit
initializeCodeZoneFrom: Cogit guardPageSize
upTo: Cogit guardPageSize + cogCodeSize!
Item was changed:
----- Method: Cogit>>assertValidDualZone (in category 'debugging') -----
assertValidDualZone
"{self firstInvalidDualZoneAddress. self firstInvalidDualZoneAddress + codeToDataDelta }"
"{self firstInvalidDualZoneAddress hex. (self firstInvalidDualZoneAddress + codeToDataDelta) hex }"
+ "[:fidza| {fidza. objectMemory longAt: fidza. objectMemory longAt: fidza + codeToDataDelta } collect: #hex] value: self firstInvalidDualZoneAddress"
- "{(objectMemory longAt: self firstInvalidDualZoneAddress) hex. (objectMemory longAt: self firstInvalidDualZoneAddress + codeToDataDelta) hex }"
"self armDisassembleDualZoneAnomalies"
"self armPrintDualZoneAnomalies"
+ <cmacro: '() true'>
+ self assert: self firstInvalidDualZoneAddress isNil!
- self cCode: ''
- inSmalltalk: [self assert: self firstInvalidDualZoneAddress isNil]!
Item was changed:
----- Method: Cogit>>ensureExecutableCodeZone (in category 'memory access') -----
ensureExecutableCodeZone
"On some platforms run-time calls may be required to enable execution and disable
write-protect of the code zone. This is sequenced by ensuring that the code zone is
executable most of the time. Note that any code space modification requires an
icache flush (on processors with such an icache). Hence the least invasive time to
ensure code is executable is post icache flush. Making sure code is writable can be
done either before any bulk edit (e.g. code zone reclamation) or as part of any fine-
grained code modification (e.g. setting an anonymous method's selector)."
<inline: #always>
self cppIf: #DUAL_MAPPED_CODE_ZONE
ifFalse:
[backEnd needsCodeZoneExecuteWriteSwitch ifTrue:
[self cCode: nil inSmalltalk: [| currentAPISelector |
"What's all this crap? We're trying to catch cases where ensureExecutableCodeZone
is called without first calling ensureWritableCodeZone, and vice verse. But there are
lots of exceptions where code is not modified but executability is turned on unnecessarily.
The list of exceptions follows."
currentAPISelector := self debugAPISelector.
self assert: (codeZoneIsExecutableNotWritable not
or: [currentAPISelector == #mapObjectReferencesInMachineCode:
+ or: [currentAPISelector == #freeUnmarkedMachineCode
+ or: [currentAPISelector == #cogitPostGCAction:
+ or: [currentAPISelector == #ceSICMiss:
+ or: [currentAPISelector == #ceCPICMiss:receiver:
+ or: [currentAPISelector == #unlinkSendsOf:isMNUSelector:
+ or: [currentAPISelector == #unlinkSendsTo:andFreeIf:
+ or: [currentAPISelector == #unlinkSendsToMethodsSuchThat:AndFreeIf:
+ or: [currentAPISelector == #followMovableLiteralsAndUpdateYoungReferrers]]]]]]]]])].
- or: [currentAPISelector == #cogitPostGCAction:
- or: [currentAPISelector == #ceSICMiss:
- or: [currentAPISelector == #ceCPICMiss:receiver:
- or: [currentAPISelector == #unlinkSendsOf:isMNUSelector:
- or: [currentAPISelector == #unlinkSendsTo:andFreeIf:
- or: [currentAPISelector == #unlinkSendsToMethodsSuchThat:AndFreeIf:
- or: [currentAPISelector == #followMovableLiteralsAndUpdateYoungReferrers]]]]]]]])].
backEnd makeCodeZoneExecutable.
self cCode: nil inSmalltalk: [codeZoneIsExecutableNotWritable := true. debugAPISelector := self debugAPISelector]]]!
Item was changed:
----- Method: Cogit>>simulateCogCodeAt: (in category 'simulation only') -----
simulateCogCodeAt: address "<Integer>"
<doNotGenerate>
| stackZoneBase |
+ (InitializationOptions at: #DUAL_MAPPED_CODE_ZONE ifAbsent: [false]) ifFalse:
+ [backEnd needsCodeZoneExecuteWriteSwitch ifTrue:
+ [self assert: codeZoneIsExecutableNotWritable]].
- backEnd needsCodeZoneExecuteWriteSwitch ifTrue:
- [self assert: codeZoneIsExecutableNotWritable].
stackZoneBase := coInterpreter stackZoneBase.
processor pc: address.
[[[singleStep
ifTrue:
[[processor sp < stackZoneBase ifTrue: [self halt].
self recordProcessing.
self maybeBreakAt: processor pc] value. "So that the Debugger's Over steps over all this"
processorLock critical:
[processor
singleStepIn: coInterpreter memory
minimumAddress: guardPageSize
readOnlyBelow: methodZone zoneEnd]]
ifFalse:
[processorLock critical:
[processor
runInMemory: coInterpreter memory
minimumAddress: guardPageSize
readOnlyBelow: methodZone zoneEnd]].
"((printRegisters or: [printInstructions]) and: [clickConfirm]) ifTrue:
[(self confirm: 'continue?') ifFalse:
[clickConfirm := false. self halt]]."
true] whileTrue]
on: ProcessorSimulationTrap
do: [:ex| ex applyTo: self].
true] whileTrue!
Item was changed:
----- Method: Cogit>>simulateLeafCallOf: (in category 'simulation only') -----
simulateLeafCallOf: someFunction
"Simulate execution of machine code that leaf-calls someFunction,
answering the result returned by someFunction."
"CogProcessorAlienInspector openFor: coInterpreter"
<doNotGenerate>
| priorSP priorPC priorLR spOnEntry bogusRetPC |
+ self cppIf: #DUAL_MAPPED_CODE_ZONE ifFalse:
+ [backEnd needsCodeZoneExecuteWriteSwitch ifTrue:
+ [self assert: codeZoneIsExecutableNotWritable]].
- backEnd needsCodeZoneExecuteWriteSwitch ifTrue:
- [self assert: codeZoneIsExecutableNotWritable].
self recordRegisters.
priorSP := processor sp.
priorPC := processor pc.
priorLR := backEnd hasLinkRegister ifTrue: [processor lr].
processor
simulateLeafCallOf: someFunction
nextpc: (bogusRetPC := 16rBADF00D5 roundTo: backEnd codeGranularity)
memory: coInterpreter memory.
spOnEntry := processor sp.
self recordInstruction: {'(simulated call of '. someFunction. ')'}.
^[[[processor pc between: self class guardPageSize and: methodZone zoneEnd] whileTrue:
[singleStep
ifTrue: [self recordProcessing.
self maybeBreakAt: processor pc.
processorLock critical:
[processor
singleStepIn: coInterpreter memory
minimumAddress: guardPageSize
readOnlyBelow: methodZone zoneEnd]]
ifFalse: [processorLock critical:
[processor
runInMemory: coInterpreter memory
minimumAddress: guardPageSize
readOnlyBelow: methodZone zoneEnd]]]]
on: ProcessorSimulationTrap, Error
do: [:ex|
"Again this is a hack for the processor simulators not properly simulating returns to bogus addresses.
In this case BochsX64Alien doesn't do the right thing."
processor pc = bogusRetPC ifTrue:
[self recordInstruction: {'(simulated (real) return to '. processor pc. ')'}.
^processor cResultRegister].
ex isProcessorSimulationTrap ifFalse:
[ex pass].
ex applyTo: self.
ex type == #return ifTrue:
[^processor cResultRegister]].
processor pc = bogusRetPC ifTrue:
[self recordInstruction: {'(simulated (real) return to '. processor pc. ')'}].
processor cResultRegister]
ensure:
[processor sp: priorSP.
processor pc: priorPC.
priorLR ifNotNil: [:lr| processor lr: lr]]!
Item was changed:
----- Method: Cogit>>sqMakeMemoryExecutableFrom:To:CodeToDataDelta: (in category 'initialization') -----
sqMakeMemoryExecutableFrom: startAddress To: endAddress CodeToDataDelta: codeToDataDeltaPtr
<doNotGenerate>
"Simulate setting executable permissions on the code zone. In production this will apply execute permission
to startAddress throguh endAddress - 1. If starting up in the DUAL_MAPPED_CODE_ZONE regime then it
will also create a writable mapping for the code zone and assign the distance from executable zone to the
+ writable zone through codeToDataDeltaPtr. If in this regime when simulating, the CogVMSimulator will
- writable zone throguh codeToDataDeltaPtr. If in this regime when simulating, the CogVMSimulator will
have allocated twice as much code memory as asked for (see CogVMSimulator openOn:extraMemory:) and
so simply set the delta to the code size."
(InitializationOptions at: #DUAL_MAPPED_CODE_ZONE ifAbsent: [false]) ifTrue:
+ [codeToDataDeltaPtr at: 0 put: coInterpreter cogCodeSize + Cogit guardPageSize]!
- [codeToDataDeltaPtr at: 0 put: coInterpreter cogCodeSize]!
Item was changed:
----- Method: RegisterAllocatingCogit>>genForwardersInlinedIdenticalOrNotIf: (in category 'bytecode generators') -----
genForwardersInlinedIdenticalOrNotIf: orNot
| nextPC branchDescriptor unforwardRcvr argReg targetPC
unforwardArg rcvrReg postBranchPC retry fixup
comparison rcvrConstant argConstant
needMergeToTarget needMergeToContinue |
<var: #branchDescriptor type: #'BytecodeDescriptor *'>
- <var: #toContinueLabel type: #'AbstractInstruction *'>
- <var: #toTargetLabel type: #'AbstractInstruction *'>
<var: #comparison type: #'AbstractInstruction *'>
<var: #retry type: #'AbstractInstruction *'>
self extractMaybeBranchDescriptorInto: [ :descr :next :postBranch :target |
branchDescriptor := descr. nextPC := next. postBranchPC := postBranch. targetPC := target ].
"If an operand is an annotable constant, it may be forwarded, so we need to store it into a
register so the forwarder check can jump back to the comparison after unforwarding the constant.
However, if one of the operand is an unnanotable constant, does not allocate a register for it
(machine code will use operations on constants) and does not generate forwarder checks."
unforwardRcvr := (self ssValue: 1) mayBeAForwarder.
unforwardArg := self ssTop mayBeAForwarder.
(unforwardRcvr not and: [unforwardArg not])
ifTrue: [^self genVanillaInlinedIdenticalOrNotIf: orNot].
self assert: (unforwardArg or: [unforwardRcvr]).
"We use reg for non annotable constants to avoid duplicating objRef."
rcvrConstant := objectRepresentation isUnannotatableConstant: (self ssValue: 1).
argConstant := objectRepresentation isUnannotatableConstant: self ssTop.
self
allocateEqualsEqualsRegistersArgNeedsReg: argConstant not
rcvrNeedsReg: rcvrConstant not
into: [ :rcvr :arg | rcvrReg:= rcvr. argReg := arg ].
"If not followed by a branch, resolve to true or false."
(branchDescriptor isBranchTrue or: [branchDescriptor isBranchFalse]) ifFalse:
[^self
genIdenticalNoBranchArgIsConstant: argConstant
rcvrIsConstant: rcvrConstant
argReg: argReg
rcvrReg: rcvrReg
orNotIf: orNot].
self assert: (unforwardArg or: [unforwardRcvr]).
self ssPop: 2. "If we had moveAllButTop: 2 volatileSimStackEntriesToRegistersPreserving: we could avoid the extra ssPop:s"
self moveVolatileSimStackEntriesToRegistersPreserving:
(self allocatedRegisters bitOr: (argReg = NoReg
ifTrue: [self registerMaskFor: rcvrReg]
ifFalse:
[rcvrReg = NoReg
ifTrue: [self registerMaskFor: argReg]
ifFalse: [self registerMaskFor: rcvrReg and: argReg]])).
retry := self Label.
self ssPop: -2.
self genCmpArgIsConstant: argConstant rcvrIsConstant: rcvrConstant argReg: argReg rcvrReg: rcvrReg.
self ssPop: 2.
(self fixupAt: nextPC) notAFixup "The next instruction is dead. we can skip it."
ifTrue: [deadCode := true]
ifFalse: [self deny: deadCode]. "push dummy value below"
"self printSimStack; printSimStack: (self fixupAt: postBranchPC) mergeSimStack"
"If there are merges to be performed on the forward branches we have to execute
the merge code only along the path requiring that merge, and exactly once."
needMergeToTarget := self mergeRequiredForJumpTo: targetPC.
needMergeToContinue := self mergeRequiredForJumpTo: postBranchPC.
orNot == branchDescriptor isBranchTrue
ifFalse: "a == b ifTrue: ... or a ~~ b ifFalse: ... jump on equal to target pc"
[fixup := needMergeToContinue
ifTrue: [0] "jumps will fall-through to to-continue merge code"
ifFalse: [self ensureFixupAt: postBranchPC].
comparison := self JumpZero: (needMergeToTarget
ifTrue: [0] "comparison will be fixed up to to-target merge code"
ifFalse: [self ensureFixupAt: targetPC])]
ifTrue: "a == b ifFalse: ... or a ~~ b ifTrue: ... jump on equal to post-branch pc"
[fixup := needMergeToTarget
ifTrue: [0] "jumps will fall-through to to-target merge code"
ifFalse: [self ensureFixupAt: targetPC].
comparison := self JumpZero: (needMergeToContinue
ifTrue: [0] "comparison will be fixed up to to-continue merge code"
ifFalse: [self ensureFixupAt: postBranchPC])].
"The forwarders check(s) need(s) to jump back to the comparison (retry) if a forwarder is found,
else jump forward either to the next forwarder check or to the postBranch or branch target (fixup).
But if there is merge code along a path, the jump must be to the merge code."
(unforwardArg and: [unforwardRcvr]) ifTrue:
[objectRepresentation genEnsureOopInRegNotForwarded: argReg scratchReg: TempReg jumpBackTo: retry].
objectRepresentation
genEnsureOopInRegNotForwarded: (unforwardRcvr ifTrue: [rcvrReg] ifFalse: [argReg])
scratchReg: TempReg
ifForwarder: retry
ifNotForwarder: fixup.
"If fixup is zero then the ifNotForwarder path falls through to a Label which is interpreted
as either to-continue or to-target, depending on orNot == branchDescriptor isBranchTrue."
orNot == branchDescriptor isBranchTrue
ifFalse: "a == b ifTrue: ... or a ~~ b ifFalse: ... jump on equal to target pc"
[needMergeToContinue ifTrue: "fall-through to to-continue merge code"
[self Jump: (self ensureFixupAt: postBranchPC)].
needMergeToTarget ifTrue: "fixup comparison to to-target merge code"
[comparison jmpTarget: self Label.
self Jump: (self ensureFixupAt: targetPC)]]
ifTrue: "a == b ifFalse: ... or a ~~ b ifTrue: ... jump on equal to post-branch pc"
[needMergeToTarget ifTrue: "fall-through to to-target merge code"
[self Jump: (self ensureFixupAt: targetPC)].
needMergeToContinue ifTrue: "fixup comparison to to-continue merge code"
[comparison jmpTarget: self Label.
self Jump: (self ensureFixupAt: postBranchPC)]].
deadCode ifFalse: "duplicate the merge fixup's top of stack so as to avoid a false confict."
[self ssPushDesc: ((self fixupAt: nextPC) mergeSimStack at: simStackPtr + 1)].
^0!
Item was changed:
----- Method: SistaCogitClone>>genByteEqualsInlinePrimitiveResult:returnReg: (in category 'inline primitive generators') -----
genByteEqualsInlinePrimitiveResult: jmp returnReg: reg
"Byte equal is falling through if the result is true, or jumping using jmp if the result is false.
The method is required to set the jump target of jmp.
We look ahead for a branch and pipeline the jumps if possible..
ReturnReg is used only if not followed immediately by a branch."
| branchDescriptor nextPC postBranchPC targetBytecodePC localJump canElide |
+ <var: #jmp type: #'AbstractInstruction *'>
<var: #localJump type: #'AbstractInstruction *'>
<var: #branchDescriptor type: #'BytecodeDescriptor *'>
self extractMaybeBranchDescriptorInto: [ :descr :next :postBranch :target |
branchDescriptor := descr. nextPC := next. postBranchPC := postBranch. targetBytecodePC := target ].
"Case 1 - not followed by a branch"
(branchDescriptor isBranchTrue or: [branchDescriptor isBranchFalse])
ifFalse:
[self genMoveTrueR: reg.
localJump := self Jump: 0.
jmp jmpTarget: (self genMoveFalseR: reg).
localJump jmpTarget: self Label.
self ssPushRegister: reg.
^ 0].
"Case 2 - followed by a branch"
(self fixupAt: nextPC) notAFixup
ifTrue: "The next instruction is dead. we can skip it."
[deadCode := true.
self ensureFixupAt: targetBytecodePC.
self ensureFixupAt: postBranchPC ]
ifFalse:
[self ssPushConstant: objectMemory trueObject]. "dummy value"
"We can only elide the jump if the pc after nextPC is the same as postBranchPC.
Branch following means it may not be."
self nextDescriptorExtensionsAndNextPCInto:
[:iguana1 :iguana2 :iguana3 :followingPC| nextPC := followingPC].
canElide := deadCode and: [nextPC = postBranchPC].
branchDescriptor isBranchTrue
ifTrue:
[ self Jump: (self ensureNonMergeFixupAt: targetBytecodePC).
canElide
ifFalse: [ jmp jmpTarget: (self ensureNonMergeFixupAt: postBranchPC) ]
ifTrue: [ jmp jmpTarget: self Label ] ]
ifFalse: [ canElide ifFalse: [ self Jump: (self ensureNonMergeFixupAt: postBranchPC).
jmp jmpTarget: (self ensureNonMergeFixupAt: targetBytecodePC) ] ].
^0!
Branch: refs/heads/Cog
Home: https://github.com/OpenSmalltalk/opensmalltalk-vm
Commit: 8adddc61a1ff2d9f8f2f368ca92749de4d8a4795
https://github.com/OpenSmalltalk/opensmalltalk-vm/commit/8adddc61a1ff2d9f8f…
Author: Marcel Taeumel <marcel.taeumel(a)hpi.de>
Date: 2022-12-22 (Thu, 22 Dec 2022)
Changed paths:
M .github/workflows/win.yml
M building/win32x86/common/Makefile.tools
M building/win64x64/common/Makefile.tools
M scripts/ci/actions_prepare_msys.sh
Log Message:
-----------
Stop this experiment for now. Revert the changes. There seems to be some 32bit-vs-64bit mixup in the toolchain.
Branch: refs/heads/Cog
Home: https://github.com/OpenSmalltalk/opensmalltalk-vm
Commit: a420f76d96c635d2ca3c4a9f3b90299c1671cd53
https://github.com/OpenSmalltalk/opensmalltalk-vm/commit/a420f76d96c635d2ca…
Author: Marcel Taeumel <marcel.taeumel(a)hpi.de>
Date: 2022-12-22 (Thu, 22 Dec 2022)
Changed paths:
M .github/workflows/win.yml
M building/win32x86/common/Makefile.tools
M building/win64x64/common/Makefile.tools
M scripts/ci/actions_prepare_msys.sh
Log Message:
-----------
Prepare CI scripts for windows-2022 runner
Simplify usage of MSYS2 instead of Cygwin for local builds
Hi Folks,
There seems to be a problem in the validation of the number of arguments
passed to #perform: (primitive 83, presumably also 84 and 100). I can
reproduce the problem consistently in Cuis using OpenSmalltalk Mac x64
Cog Spur VMs. Tried releases from 2022-05 and 2022-06. Also tried build
from 2022-11-21. I observe the same behavior on a fresh Squeak6.0-22104
Mac bundle from squeak.org.
To reproduce the problem, print the result of this snippet:
results := Bag new.
n := 100.
c := 0.
n timesRepeat: [
"Smalltalk garbageCollect."
b := [ 7 perform: #isDivisibleBy: ] on: Error do: [ #errorAsExpected ].
results add: b.
b = #errorAsExpected ifFalse: [ c := c + 1 ]].
{c. 'failures out of'. n}
I get 98 or 99 failures out of 100. The results bag shows that the
answer of #isDivisibleBy: without any argument is false (when the Error
is not raised). Adding this line:
true ifTrue: [ ^ {self. aNumber} ].
to the the start of #isDivisibleBy: shows that the argument passed to
the method is the #isDivisibleBy: symbol itself, i.e. the argument to
#perform:.
As a last experiment, if I activate the call to garbage collection, it
works as expected, the Error is always raised, and there are zero
failures. So, whatever got broken, GC fixes it.
I don't know the related VM internals in enough detail to work this out.
Can you please take a look?
Thanks!
--
Juan Vuletich
cuis.st
github.com/jvuletichresearchgate.net/profile/Juan-Vuletichindependent.academia.edu/JuanVuletichpatents.justia.com/inventor/juan-manuel-vuletichlinkedin.com/in/juan-vuletich-75611b3twitter.com/JuanVuletich