From 1087ef70ef8613907f3271ebebca51f3404497b1 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Thu, 9 Nov 2023 15:08:19 +0000 Subject: [PATCH 01/12] Introduce `TRMemoryReference` --- src/Tinyrossa/TRMemoryReference.class.st | 5 +++++ src/Tinyrossa/TRRealRegister.class.st | 10 ++++++++++ src/Tinyrossa/TRVirtualRegister.class.st | 4 ++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/Tinyrossa/TRMemoryReference.class.st diff --git a/src/Tinyrossa/TRMemoryReference.class.st b/src/Tinyrossa/TRMemoryReference.class.st new file mode 100644 index 0000000..93e3562 --- /dev/null +++ b/src/Tinyrossa/TRMemoryReference.class.st @@ -0,0 +1,5 @@ +Class { + #name : #TRMemoryReference, + #superclass : #AcDSLMemRef, + #category : #'Tinyrossa-Codegen' +} diff --git a/src/Tinyrossa/TRRealRegister.class.st b/src/Tinyrossa/TRRealRegister.class.st index 138221b..9dcc74a 100644 --- a/src/Tinyrossa/TRRealRegister.class.st +++ b/src/Tinyrossa/TRRealRegister.class.st @@ -17,6 +17,16 @@ TRRealRegister class >> value: value kind: kind [ ^ self basicNew initializeWithValue: value kind: kind ] +{ #category : #arithmetic } +TRRealRegister >> + offset [ + ^ TRMemoryReference base: self offset: offset asAcDSLOperand +] + +{ #category : #arithmetic } +TRRealRegister >> - offset [ + ^ TRMemoryReference base: self offset: offset negated asAcDSLOperand +] + { #category : #accessing } TRRealRegister >> allocation [ ^ self diff --git a/src/Tinyrossa/TRVirtualRegister.class.st b/src/Tinyrossa/TRVirtualRegister.class.st index c11722e..041353a 100644 --- a/src/Tinyrossa/TRVirtualRegister.class.st +++ b/src/Tinyrossa/TRVirtualRegister.class.st @@ -19,12 +19,12 @@ TRVirtualRegister class >> named: aString kind: aTRRegisterKind codeGenerator: a { #category : #arithmetic } TRVirtualRegister >> + offset [ - ^ AcDSLMemRef base: self offset: offset asAcDSLOperand + ^ TRMemoryReference base: self offset: offset asAcDSLOperand ] { #category : #arithmetic } TRVirtualRegister >> - offset [ - ^ AcDSLMemRef base: self offset: offset negated asAcDSLOperand + ^ TRMemoryReference base: self offset: offset negated asAcDSLOperand ] { #category : #accessing } From bf78f484ef4e9e0ea48dba661d9e431abb682858 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 6 May 2024 13:54:38 +0100 Subject: [PATCH 02/12] RLSRA: remove `spilled` variable from live interval This has been used to track whether or not the value is currently spilled or not but this information is not currently used anywhere. Might be reintroduced in future when needed. --- src/Tinyrossa/TRRegisterLiveInterval.class.st | 18 ------------------ ...ReverseLinearScanRegisterAllocator.class.st | 8 +++----- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/Tinyrossa/TRRegisterLiveInterval.class.st b/src/Tinyrossa/TRRegisterLiveInterval.class.st index 96675aa..b8a1300 100644 --- a/src/Tinyrossa/TRRegisterLiveInterval.class.st +++ b/src/Tinyrossa/TRRegisterLiveInterval.class.st @@ -12,7 +12,6 @@ Class { 'register', 'start', 'stop', - 'spilled', 'spillSlot' ], #category : #'Tinyrossa-Codegen-Register Allocation' @@ -35,7 +34,6 @@ TRRegisterLiveInterval >> initializeWithRegister: aTRVirtualRegister [ register := aTRVirtualRegister. start := SmallInteger maxVal. stop := 0. - spilled := false. ] { #category : #'printing & storing' } @@ -70,22 +68,6 @@ TRRegisterLiveInterval >> spillSlot: aTRAutomaticSymbol [ spillSlot := aTRAutomaticSymbol. ] -{ #category : #accessing } -TRRegisterLiveInterval >> spilled [ - ^ spilled -] - -{ #category : #accessing } -TRRegisterLiveInterval >> spilled: aBoolean [ - self assert: aBoolean = spilled not. - self assert: spillSlot isTRAutomaticSymbol. - - spilled := aBoolean. - spilled ifTrue: [ - spillSlot incUseCount. - ]. -] - { #category : #accessing } TRRegisterLiveInterval >> start [ ^ start diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index d92c213..856f3dc 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -196,7 +196,6 @@ TRReverseLinearScanRegisterAllocator >> freeRegister: interval [ TRReverseLinearScanRegisterAllocator >> insertReload: interval [ | slot | - self assert: interval spilled not. self assert: interval register kind == GPR description: 'FIXME: FPRs not yet supported'. self assert: interval register allocation notNil. @@ -205,21 +204,20 @@ TRReverseLinearScanRegisterAllocator >> insertReload: interval [ slot := codegen compilation symbolManager defineAutomatic: nil type: Address. interval spillSlot: slot. ]. + slot incUseCount. codegen registerLoad: interval register from: slot. - interval spilled: true. ] { #category : #utilities } TRReverseLinearScanRegisterAllocator >> insertSpill: interval [ | slot | - self assert: interval spilled. self assert: interval spillSlot isTRAutomaticSymbol. self assert: interval register kind == GPR description: 'FIXME: FPRs not yet supported'. slot := interval spillSlot. + slot incUseCount. codegen registerStore: interval register to: slot. - interval spilled: false. ] { #category : #utilities } @@ -254,7 +252,7 @@ TRReverseLinearScanRegisterAllocator >> pickSpill: interval [ insn := instructions at: interval stop. - candidates := live reject: [:each | each spilled ]. + candidates := live copy. codegen virtualRegistersReadBy: insn do: [:vReg | live do: [:e | e register == vReg ifTrue:[ candidates remove: e ifAbsent:nil] ] ]. From 66a8278e6bf4da3418bb9933d985ce40b1eb9c23 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Tue, 21 Nov 2023 16:45:15 +0000 Subject: [PATCH 03/12] RLSRA: keep track of all uses of a virtual register This commit changes live interval structure to keep track of all uses of a virtual registers rather than just its definition (start) and last use (end). It also keeps track whether at given position the register was defined (written to) or used (value read) or both. This is a preparation for spilling / reloading when running out of available registers as well as with deciding which register to spill (based on how many times and how "far" it is given virtual register defd/used). Internally, both def and use positions are are stored single ordered array: def position `p` is stored as `(p*2) + 1` and use position as `(p*2)`. --- src/Tinyrossa/TRRegisterLiveInterval.class.st | 191 ++++++++++++++++-- ...everseLinearScanRegisterAllocator.class.st | 10 +- 2 files changed, 181 insertions(+), 20 deletions(-) diff --git a/src/Tinyrossa/TRRegisterLiveInterval.class.st b/src/Tinyrossa/TRRegisterLiveInterval.class.st index b8a1300..3c41acb 100644 --- a/src/Tinyrossa/TRRegisterLiveInterval.class.st +++ b/src/Tinyrossa/TRRegisterLiveInterval.class.st @@ -1,8 +1,16 @@ " -`TRRegisterLiveInterval` is a helper structure used by -(reverse) linear scan allocators. It keeps information -required by the allocator as it progresses and allocates -registers. +`TRRegisterLiveInterval` represent (virtual) register live interval. + +For given (virtual) register it keeps track of all def positions +(i.e., positions where a register was written to) and use positions +(i.e., positions where register value was read). It also knows +spill slot, if any. + +Internally, def and use positions are kept in single ordered +`uses` array where + + * each def position `d` is encoded as `(d * 2) + 1` and + * each use position `u` is encoded as `(u * 2)` " Class { @@ -10,8 +18,7 @@ Class { #superclass : #Object, #instVars : [ 'register', - 'start', - 'stop', + 'uses', 'spillSlot' ], #category : #'Tinyrossa-Codegen-Register Allocation' @@ -27,13 +34,137 @@ TRRegisterLiveInterval class >> new [ ^ self shouldNotImplement. "Use #forRegister: instead" ] +{ #category : #private } +TRRegisterLiveInterval >> decodePosition: encodedPosition [ + ^ encodedPosition // 2 +] + +{ #category : #utilities } +TRRegisterLiveInterval >> defdAt: position [ + | positionEncoding | + + self assert: position isInteger. + self assert: self firstDef isNil description: 'Register defined more than once'. + + positionEncoding := self encodeDefPosition: position. + uses isEmpty ifTrue: [ + uses := Array with: positionEncoding. + ] ifFalse: [ + (uses includes: positionEncoding) ifFalse: [ + uses := (uses copyWith: positionEncoding) sort. + ]. + ]. +] + +{ #category : #enumerating } +TRRegisterLiveInterval >> defdDo: aBlock [ + "Evaluate `aBlock` for each instruction index where this virtual + registers is defined." + + uses do: [:i | + (self encodesDefPosition: i) ifTrue: [ + aBlock value: (self decodePosition: i) + ]. + ]. +] + +{ #category : #private } +TRRegisterLiveInterval >> encodeDefPosition: position [ + ^ (position * 2) + 1 +] + +{ #category : #private } +TRRegisterLiveInterval >> encodeUsePosition: position [ + ^ (position * 2) +] + +{ #category : #private } +TRRegisterLiveInterval >> encodesDefPosition: encodedPosition [ + ^ encodedPosition odd. +] + +{ #category : #private } +TRRegisterLiveInterval >> encodesUsePosition: encodedPosition [ + ^ encodedPosition even. +] + +{ #category : #accessing } +TRRegisterLiveInterval >> firstDef [ + "Return the first def position for this interval." + + uses do: [:encodedPosition | + (self encodesDefPosition: encodedPosition) ifTrue: [ + ^ self decodePosition: encodedPosition + ]. + ]. + ^ nil +] + { #category : #initialization } TRRegisterLiveInterval >> initializeWithRegister: aTRVirtualRegister [ self assert: aTRVirtualRegister isTRVirtualRegister. register := aTRVirtualRegister. - start := SmallInteger maxVal. - stop := 0. + uses := #(). +] + +{ #category : #testing } +TRRegisterLiveInterval >> isDefdAt: position [ + | positionEncoding | + + positionEncoding := self encodeDefPosition: position. + ^ uses includes: positionEncoding. +] + +{ #category : #testing } +TRRegisterLiveInterval >> isDefdOrUsedAt: position [ + ^ (self isDefdAt: position) or: [ self isUsedAt: position ] +] + +{ #category : #testing } +TRRegisterLiveInterval >> isUsedAt: position [ + | positionEncoding | + + positionEncoding := self encodeUsePosition: position. + ^ uses includes: positionEncoding. +] + +{ #category : #accessing } +TRRegisterLiveInterval >> lastUse [ + "Return the last use position for this interval." + + uses reverseDo: [:encodedPosition | + (self encodesUsePosition: encodedPosition) ifTrue: [ + ^ self decodePosition: encodedPosition + ]. + ]. + ^ nil +] + +{ #category : #accessing } +TRRegisterLiveInterval >> lastUseOrDefBefore: position [ + "Return the last use position for this interval smaller than `position`" + + | encodedPosition | + + encodedPosition := self encodeUsePosition: position. + uses reverseDo: [ :i | + i < encodedPosition ifTrue: [ + ^ self decodePosition: i. + ]. + ]. + ^ nil. +] + +{ #category : #accessing } +TRRegisterLiveInterval >> length [ + ^ self stop - self start + 1 +] + +{ #category : #queries } +TRRegisterLiveInterval >> needsToBeSpilled [ + "Return true, if this interval (register) has to be spilled after its definition" + ^ spillSlot notNil ] { #category : #'printing & storing' } @@ -43,10 +174,16 @@ TRRegisterLiveInterval >> printOn:aStream [ super printOn:aStream. aStream nextPut: $(. aStream nextPutAll: register name. + register allocation isNil ifTrue: [ + aStream nextPutAll:', *'. + ] ifFalse: [ + aStream nextPutAll:', '. + aStream nextPutAll: register allocation name. + ]. aStream nextPutAll:', <'. - start printOn:aStream. + self start printOn:aStream. aStream nextPutAll:', '. - stop printOn:aStream. + self stop printOn:aStream. aStream nextPutAll:'>)'. ] @@ -70,20 +207,38 @@ TRRegisterLiveInterval >> spillSlot: aTRAutomaticSymbol [ { #category : #accessing } TRRegisterLiveInterval >> start [ - ^ start + ^ self decodePosition: uses first ] { #category : #accessing } TRRegisterLiveInterval >> stop [ - ^ stop + ^ self decodePosition: uses last ] { #category : #utilities } -TRRegisterLiveInterval >> used: anInteger [ - anInteger < start ifTrue:[ - start := anInteger. - ]. - anInteger > stop ifTrue: [ - stop := anInteger. +TRRegisterLiveInterval >> usedAt: position [ + | positionEncoding | + + self assert: position isInteger. + + positionEncoding := self encodeUsePosition: position. + uses isEmpty ifTrue: [ + uses := Array with: positionEncoding. + ] ifFalse: [ + (uses includes: positionEncoding) ifFalse: [ + uses := (uses copyWith: positionEncoding) sort. + ]. ]. ] + +{ #category : #enumerating } +TRRegisterLiveInterval >> usedDo: aBlock [ + "Evaluate `aBlock` for each instruction index where this virtual + registers is used (read)." + + uses do: [:i | + (self encodesUsePosition: i) ifTrue: [ + aBlock value: (self decodePosition: i) + ]. + ]. +] diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index 856f3dc..8f1da75 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -66,11 +66,17 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ | insn | insn := instructions at: i. - codegen virtualRegistersUsedBy: insn do: [:vreg | + codegen virtualRegistersReadBy: insn do: [:vreg | | interval | interval := intervals at: vreg. - interval used: i. + interval usedAt: i. + ]. + codegen virtualRegistersAssignedBy: insn do: [:vreg | + | interval | + + interval := intervals at: vreg. + interval defdAt: i. ]. ]. intervals do: [:interval | From 114a6396c71b3299520f0129064261ab5369bb90 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 6 May 2024 14:33:50 +0100 Subject: [PATCH 04/12] RLSRA: spill registers right after they have been defined This commit moves spills up to where the value is actually defined (i.e., to the beginning of live interval). This will (hopefully) make interval splitting easier to implement. Also, this moves spills (memory writes) further away from reloads (memory reads) which may (or may not) help performance. This change makes manual handling of thrashed registers (defined by instruction's register dependencies) simpler - no need to call to `#insertSpill:` when handling pre-dependencies - thrashed registers are spilled automatically when expired. --- src/Tinyrossa/TRRegisterLiveInterval.class.st | 6 +++++ ...everseLinearScanRegisterAllocator.class.st | 27 +++++++++---------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Tinyrossa/TRRegisterLiveInterval.class.st b/src/Tinyrossa/TRRegisterLiveInterval.class.st index 3c41acb..7a0b596 100644 --- a/src/Tinyrossa/TRRegisterLiveInterval.class.st +++ b/src/Tinyrossa/TRRegisterLiveInterval.class.st @@ -167,6 +167,12 @@ TRRegisterLiveInterval >> needsToBeSpilled [ ^ spillSlot notNil ] +{ #category : #queries } +TRRegisterLiveInterval >> needsToBeSpilled [ + "Return true, if this interval (register) has to be spilled after its definition" + ^ spillSlot notNil +] + { #category : #'printing & storing' } TRRegisterLiveInterval >> printOn:aStream [ "append a printed representation of the receiver to the argument, aStream" diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index 8f1da75..b1b4272 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -102,7 +102,7 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ When reading this code, keep in mind that we progress in reverse order, from last to first instruction! " - | insn deps liveAcross thrashed | + | insn deps liveAcross | insn := instructions at: insnIndex. deps := insn dependencies. @@ -119,7 +119,6 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ liveAcross := Set new: live size. live do: [:i | (i start < insnIndex and: [ i stop > insnIndex ]) ifTrue:[liveAcross add:i] ]. - thrashed := OrderedCollection new: deps post size. codegen cursor: insnIndex. deps post do: [:dep | dep isDependency ifTrue:[ @@ -129,10 +128,13 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ liveAcross do:[:i | (i register allocation == dep rreg) ifTrue: [ "Live-across register is trashed, we have to spill and reload. - So reload here and note the it has to be spilled before this - instruction executes (see handling of pre-dependencies below)" + Since we go 'backwards', we reload here. + + Note, that calling #insertReload: has the sideffect of allocating + a spill slot for the (virtual) register. Also, the register is spilled + right after it is defined in `#expireRegistersAt:`. Therefore, there's + no need to call `#insertSpill:` when satisfying pre-dependencies." self insertReload: i. - thrashed add: i. ]. ] ]. @@ -163,16 +165,8 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ dep isDependency ifTrue:[ self insertMoveFrom: dep vreg to: dep rreg. ]. - dep isTrash ifTrue: [ - thrashed do:[:i | - (i register allocation == dep rreg) ifTrue: [ - "Live register is trashed and has to be spilled. - See handling of post-dependencies above where `spilled` set - is populated." - self insertSpill: i. - ]. - ] - ]. + "Note that there's no need to handle thrashed registers here + see comment in code handling post-dependencies above." ]. ]. ] @@ -184,6 +178,9 @@ TRReverseLinearScanRegisterAllocator >> expireRegistersAt: insnIndex [ | expired | expired := live removeLast. + expired spillSlot notNil ifTrue: [ + self insertSpill: expired. + ]. self freeRegister: expired. ]. ] From 0b3afc45dd0e2438443742ae5c6e454b420a4c95 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Tue, 7 May 2024 15:54:29 +0100 Subject: [PATCH 05/12] RLSRA: support register spilling via interval splitting This commit add support for general register spilling, handling the (previously unhandled) case allocator runs out of free registers. This is done by splitting (live) interval at given allocation position. A reload is inserted at allocation position + 1 and new interval representing the 'still-live' part of splitted interval is setup to spill and pushed back onto worklist. Also, thrashed registers are now handled the same way - when an instruction at given position thrashes a register, its interval is force-split, causing the value to be spilled before and reloaded after given position. This these changes, Tinyrossa can compile recursive factorial with overflow with just 2 registers available for allocation. --- .../TRRV64GCodeGenerator.class.st | 2 + src/Tinyrossa/TRRegisterLiveInterval.class.st | 22 +- ...everseLinearScanRegisterAllocator.class.st | 251 ++++++++++++++---- 3 files changed, 218 insertions(+), 57 deletions(-) diff --git a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st index fffa629..13543b6 100644 --- a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st @@ -128,6 +128,7 @@ TRRV64GCodeGenerator >> registerLoad: reg from: sym [ self assert: reg isTRRegister. self assert: sym isTRAutomaticSymbol. + self assert: sym useCount > 0. self assert: sym type == Address. offset := AcDSLSymbol value: sym name. @@ -140,6 +141,7 @@ TRRV64GCodeGenerator >> registerStore: reg to: sym [ self assert: reg isTRRegister. self assert: sym isTRAutomaticSymbol. + self assert: sym useCount > 0. self assert: sym type == Address. offset := AcDSLSymbol value: sym name. diff --git a/src/Tinyrossa/TRRegisterLiveInterval.class.st b/src/Tinyrossa/TRRegisterLiveInterval.class.st index 7a0b596..269eabe 100644 --- a/src/Tinyrossa/TRRegisterLiveInterval.class.st +++ b/src/Tinyrossa/TRRegisterLiveInterval.class.st @@ -44,7 +44,6 @@ TRRegisterLiveInterval >> defdAt: position [ | positionEncoding | self assert: position isInteger. - self assert: self firstDef isNil description: 'Register defined more than once'. positionEncoding := self encodeDefPosition: position. uses isEmpty ifTrue: [ @@ -129,6 +128,18 @@ TRRegisterLiveInterval >> isUsedAt: position [ ^ uses includes: positionEncoding. ] +{ #category : #accessing } +TRRegisterLiveInterval >> lastDef [ + "Return the last def position for this interval." + + uses reverseDo: [:encodedPosition | + (self encodesDefPosition: encodedPosition) ifTrue: [ + ^ self decodePosition: encodedPosition + ]. + ]. + ^ nil +] + { #category : #accessing } TRRegisterLiveInterval >> lastUse [ "Return the last use position for this interval." @@ -162,9 +173,8 @@ TRRegisterLiveInterval >> length [ ] { #category : #queries } -TRRegisterLiveInterval >> needsToBeSpilled [ - "Return true, if this interval (register) has to be spilled after its definition" - ^ spillSlot notNil +TRRegisterLiveInterval >> needsToBeSpilledAt: position [ + ^ spillSlot notNil and: [ position = self lastDef ] ] { #category : #queries } @@ -205,8 +215,8 @@ TRRegisterLiveInterval >> spillSlot [ { #category : #accessing } TRRegisterLiveInterval >> spillSlot: aTRAutomaticSymbol [ - self assert: aTRAutomaticSymbol isTRAutomaticSymbol. - self assert: spillSlot isNil. + self assert: (aTRAutomaticSymbol isTRAutomaticSymbol and: [spillSlot isNil]) + | (aTRAutomaticSymbol isNil and: [spillSlot isTRAutomaticSymbol]). spillSlot := aTRAutomaticSymbol. ] diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index b1b4272..75aa6b8 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -1,9 +1,29 @@ " `TRReverseLinearScanRegisterAllocator` is the default allocator used in Tinyrossa. -It's straightforward reimplementation from original 1999 paper [1] with one -small change: the allocation progresses in reverse order. That is, from last -instruction towards first one. +It is a more or less straightforward reimplementation from original 1999 paper +[1] with few changes. + +First, as name suggests, allocation progresses backwards, from the last +instruction in sequence towards the first. This way, we can insert more +instructions in already processed part without a need to update indexes. +Second, the way it implements spilling is more along the lines of [2]. + +# Spilling and reloading + +Each interval one more 'defined position' (or 'defd pos') and +zero or more `used positions` (no use position of an interval might be +a result of interval splitting, see below). + +When there's no free register (`#pickRegister` fails) then one live +interval is chosen (`#pickSplit:`) and split (`#splitRegister:at:`). +If interval is split between first 'def position' and following 'use position' +the new interval has no 'use positions' and spans only one instruction. + +Thrashing of a register by an instruction (for example, function call +'thrashes' all volatile registers) is solved by interval splitting +too. When a register is thrashed, its interval if forcefully splitted +(see `#allocateRegistersAt:`. Note that there's no need to deal with virtual registers being used across basic block boundary - in Tinyrossa (as well as in Testarossa), the only way @@ -13,13 +33,16 @@ to transfer value from one (extended) block to another is via `?store` and [1]: MASSIMILIANO POLETTO and VIVEK SARKAR: Linear Scan Register Allocation http://web.cs.ucla.edu/~palsberg/course/cs132/linearscan.pdf +[2]: Christian Wimmer, Hanspeter Mossenbock: Optimized Interval Splitting + in a Linear Scan Register Allocator + " Class { #name : #TRReverseLinearScanRegisterAllocator, #superclass : #TRRegisterAllocator, #instVars : [ 'instructions', - 'intervals', + 'todo', 'live', 'allocatableRegisters', 'availableRegisters' @@ -40,17 +63,34 @@ TRReverseLinearScanRegisterAllocator >> allocateRegister: interval [ self assert: interval register allocation isNil. assigned := self pickRegister: interval. - assigned isNil ifTrue: [ self error: 'No available register!' ]. + assigned isNil ifTrue: [ + "No free registers so pick and split some interval to free + some. Then try to allocate register again (this should succeed) + `interval` and then try to allocate it." + | split | + + split := self pickSplit: interval. + split isNil ifTrue: [ + self error: 'Cannot allocate register for ', interval vreg name, ': no free registers and no interval to split!' + ]. + self splitRegister: split at: interval stop. + + assigned := self pickRegister: interval. + ]. + + self assert: assigned notNil. live add: interval. ] { #category : #allocation } TRReverseLinearScanRegisterAllocator >> allocateRegisters [ + | intervals | + instructions := codegen instructions. allocatableRegisters := codegen linkage allocatableRegisters. codegen compilation config stressRA ifTrue: [ - "allocatableRegisters := allocatableRegisters copyFrom: 1 to: 2." + allocatableRegisters := allocatableRegisters copyFrom: 1 to: 2. ]. availableRegisters := allocatableRegisters asSet. live := SortedCollection sortBlock: [ :a :b | a start < b start ]. @@ -82,7 +122,14 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ intervals do: [:interval | self assert: interval start < interval stop. ]. - intervals := intervals values asOrderedCollection sort: [ :a :b | a stop < b stop ]. + + "Create todo (work) list. The list is sorted by interval's end position (#stop). + + Within the same end positions, intervals are sorted by start position. This is so + to make sure short live intervals are allocated first (see #allocateRegistersAt: + which allocates registers one by one, taking them off the end of `todo` list. + " + todo := intervals values asSortedCollection: [ :a :b | (a stop < b stop) or:[a stop == b stop and:[a start < b start]]]. " Step 2. Walk instructions in reverse order and allocate @@ -110,16 +157,16 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ "Satisfy post-dependencies, i.e., (i) move values from fixed (real) registers to desired (virtual) registers and... - (ii) ...reload all trashed registers live across - this instruction + (ii) ...force-split all thrashed registers live across + current position (`insnIndex`) " + codegen cursor: insnIndex. deps notEmptyOrNil ifTrue: [ "Compute 'live-across' intervals, that is intervals that are assigned before this instruction and used after this instruction." liveAcross := Set new: live size. live do: [:i | (i start < insnIndex and: [ i stop > insnIndex ]) ifTrue:[liveAcross add:i] ]. - codegen cursor: insnIndex. deps post do: [:dep | dep isDependency ifTrue:[ self insertMoveFrom: dep rreg to: dep vreg. @@ -128,13 +175,8 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ liveAcross do:[:i | (i register allocation == dep rreg) ifTrue: [ "Live-across register is trashed, we have to spill and reload. - Since we go 'backwards', we reload here. - - Note, that calling #insertReload: has the sideffect of allocating - a spill slot for the (virtual) register. Also, the register is spilled - right after it is defined in `#expireRegistersAt:`. Therefore, there's - no need to call `#insertSpill:` when satisfying pre-dependencies." - self insertReload: i. + We do it by forcefully splitting the interval." + self splitRegister: i at: insnIndex. ]. ] ]. @@ -148,10 +190,35 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ the interval from the list so we do not need to always search the list for intervals starting at this point. In other words, `intervals` collection serve as a worklist." - [ intervals notEmpty and: [ intervals last stop == insnIndex ] ] whileTrue: [ - self allocateRegister: intervals removeLast. + [ todo notEmpty and: [ todo last stop == insnIndex ] ] whileTrue: [ + | interval | + + interval := todo removeLast. + + self allocateRegister: interval. + interval length == 1 ifTrue: [ + "We have just allocated an register interval of length 1 - + such interval may be result of a split between its + definition and first use. In this case, this interval is + defined at this interval get immediatelly spilled so + we can expire it right now to free the register for + possibly other intervals that go live here." + self assert: (interval needsToBeSpilledAt: insnIndex). + + self insertSpill: interval. + self expireRegister: interval. + ] ]. + "Spill all live registers that have to spilled at this + point." + live do: [:interval | + (interval needsToBeSpilledAt: insnIndex) ifTrue: [ + self insertSpill: interval. + ]. + ]. + + "Satisfy pre-dependencies, i.e., (i) move values from (virtual) registers to desired (real) registers and... @@ -159,29 +226,35 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ Moves and spills must be placed placed *before* the instruction being processed, hence the `insnIndex - 1`" + codegen cursor: insnIndex - 1. deps notEmptyOrNil ifTrue: [ - codegen cursor: insnIndex - 1. deps pre reverseDo: [:dep | dep isDependency ifTrue:[ self insertMoveFrom: dep vreg to: dep rreg. ]. "Note that there's no need to handle thrashed registers here - see comment in code handling post-dependencies above." + as all thrashed intervals have been split, see above." ]. ]. ] +{ #category : #allocation } +TRReverseLinearScanRegisterAllocator >> expireRegister: interval [ + "Expire given interval" + + interval spillSlot notNil ifTrue: [ + codegen cursor: interval start. + self insertSpill: interval. + ]. + self freeRegister: interval. + live remove: interval. +] + { #category : #allocation } TRReverseLinearScanRegisterAllocator >> expireRegistersAt: insnIndex [ "Expire all registers no longer live at given instruction (index)." [ live notEmpty and:[ live last start >= insnIndex ] ] whileTrue: [ - | expired | - - expired := live removeLast. - expired spillSlot notNil ifTrue: [ - self insertSpill: expired. - ]. - self freeRegister: expired. + self expireRegister: live last. ]. ] @@ -196,31 +269,26 @@ TRReverseLinearScanRegisterAllocator >> freeRegister: interval [ ] { #category : #utilities } -TRReverseLinearScanRegisterAllocator >> insertReload: interval [ - | slot | +TRReverseLinearScanRegisterAllocator >> insertReload: interval from: spillSlot [ self assert: interval register kind == GPR description: 'FIXME: FPRs not yet supported'. self assert: interval register allocation notNil. + self assert: spillSlot isTRAutomaticSymbol. - slot := interval spillSlot. - slot isNil ifTrue: [ - slot := codegen compilation symbolManager defineAutomatic: nil type: Address. - interval spillSlot: slot. - ]. - slot incUseCount. - codegen registerLoad: interval register from: slot. + spillSlot incUseCount. + codegen registerLoad: interval register from: spillSlot. ] { #category : #utilities } TRReverseLinearScanRegisterAllocator >> insertSpill: interval [ - | slot | + | spillSlot | self assert: interval spillSlot isTRAutomaticSymbol. self assert: interval register kind == GPR description: 'FIXME: FPRs not yet supported'. - slot := interval spillSlot. - slot incUseCount. - codegen registerStore: interval register to: slot. + spillSlot := interval spillSlot. + interval spillSlot: nil. + codegen registerStore: interval register to: spillSlot. ] { #category : #utilities } @@ -248,21 +316,102 @@ TRReverseLinearScanRegisterAllocator >> pickRegister: interval [ ] { #category : #utilities } -TRReverseLinearScanRegisterAllocator >> pickSpill: interval [ - "Pick (choose) and return the best spill FIXME: TBW" +TRReverseLinearScanRegisterAllocator >> pickSpillSlot: interval [ + "Pick (choose) a spill slot to use when splitting given `interval`." + + self assert: interval register kind == GPR description: 'FIXME: FPRs not yet supported'. + + ^ codegen compilation symbolManager defineAutomatic: nil type: Address. +] + +{ #category : #utilities } +TRReverseLinearScanRegisterAllocator >> pickSplit: interval [ + "Pick (choose) and return 'good enough' live interval to split + (and therefore spill) in order to allocate given (about to go live) + `interval`. - | insn candidates | + Preferably, choose among intervals that are not not defined not + used at current position. If there's none such that, choose among + ones that are used but not defined. - insn := instructions at: interval stop. + Among multiple candidates to split, prefer the one whose previous + (since we go backwards) use/def is the furthest from current position. + This frees the register for longest possible time. - candidates := live copy. - codegen virtualRegistersReadBy: insn do: [:vReg | - live do: [:e | e register == vReg ifTrue:[ candidates remove: e ifAbsent:nil] ] + Above process may not be the best, but it is 'good enough'. + + [1]: Christian Wimmer, Hanspeter Mossenbock: Optimized Interval Splitting + in a Linear Scan Register Allocator + " + + | candidates candidate candidatePrevUse | + + "1. Select candidates" + candidates := live reject: [:each | each isDefdOrUsedAt: interval stop ]. + candidates isEmpty ifTrue: [ + candidates := live reject: [:each | each isDefdAt: interval stop ]. ]. - candidates isEmpty ifTrue: [ - ^ nil. + + "2. Among candidates, select the one one whose previous use/def + is the furthest." + candidatePrevUse := SmallInteger maxVal. + candidate := nil. + candidates reverseDo: [ :each | + | eachPrevUse | + + eachPrevUse := each lastUseOrDefBefore: interval stop. + (eachPrevUse notNil and: [ eachPrevUse < candidatePrevUse ]) ifTrue: [ + candidate := each. + candidatePrevUse := eachPrevUse. + ]. + ]. + + ^ candidate +] + +{ #category : #allocation } +TRReverseLinearScanRegisterAllocator >> splitRegister: interval at: insnIndex [ + "Split given live `interval` at given `position`. + After interval is split. given `interval` is no + longer live (but may become live at `position` - 1) + and the part of interval before `position` is added to + the worklist (`todo`). " + + | before regmap spillSlot | + + self assert: (live includes: interval). + + before := TRRegisterLiveInterval forRegister: (codegen allocateRegister: interval register kind). + + "Create new interval representing the first part of original interval + up to current position. While walking definitions and uses, + update instructions to use new virtual registers" + regmap := Dictionary new at: interval register name put: before register name; yourself. + interval defdDo: [ :i | + before defdAt:i. + instructions at: i put: ((instructions at: i) inEnvironment: regmap). ]. - ^ candidates first. + interval usedDo: [:i | i <= insnIndex ifTrue: [ + before usedAt:i. + instructions at: i put: ((instructions at: i) inEnvironment: regmap). + ]]. + + "Allocate spill slot for being-splitted `interval`. Insert reload + and arrange `before` interval so that value is spilled when + defined." + spillSlot := self pickSpillSlot: interval. + self insertReload: interval from: spillSlot. + before spillSlot: spillSlot. + + "Finally, expire `interval` and push `before` to + worklist." + self expireRegister: interval. + todo add: before. + + "Just a sanity check." + self assert: (live includes: interval) not. + self assert: (availableRegisters includes: interval register allocation). + self assert: (todo includes: before). ] { #category : #utilities } From 37f9efa2cc8ec741c53b85c85e8b27d5fd4c9a65 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 10 May 2024 21:41:42 +0100 Subject: [PATCH 06/12] RLSRA: make `#pickRegister:` side-effect free This commit changes `#pickRegister:` to NOT assign register to interval in order to behave like other 'pick' methods in RSLRA. --- .../TRReverseLinearScanRegisterAllocator.class.st | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index 75aa6b8..afcda6f 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -80,6 +80,9 @@ TRReverseLinearScanRegisterAllocator >> allocateRegister: interval [ ]. self assert: assigned notNil. + + interval register allocation: assigned. + self takeRegister: interval. live add: interval. ] @@ -293,22 +296,15 @@ TRReverseLinearScanRegisterAllocator >> insertSpill: interval [ { #category : #utilities } TRReverseLinearScanRegisterAllocator >> pickRegister: interval [ - "Pick (choose) and assign the best real register for given live interval. - Return the chosen register. - - Internal list of currently available registers is updated accordingly. + "Pick (choose) and return the best real register to assign to given (about to + going live) `interval`. Chosen register is NOT assigned. If there's no available register at this point, return `nil`. Caller is responsible for handling this case and schedule a spill / reload. " - self assert: interval register allocation isNil. - availableRegisters isEmpty ifTrue: [ ^ nil ]. - allocatableRegisters do: [:rReg | (availableRegisters includes: rReg) ifTrue: [ - interval register allocation: rReg. - self takeRegister: interval. ^ rReg ]. ]. From 4cf5afe8e8293feee80aa2a87a0d8d598683cff9 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Tue, 14 May 2024 14:41:40 +0100 Subject: [PATCH 07/12] Add `TRRegisterDependency >> #isUnsatisfiedDependency` This commit adds `#isUnsatisfiedDependency` which returns true if there's a dependency of virtual register on real register which is not satisfied (i.e., virtual register is allocated different register than the desired real register). Also remove `TRRegisterDependency >> #isDependency` as it is not used any more. --- src/Tinyrossa/TRRegisterDependency.class.st | 17 +++++++++++++---- ...RReverseLinearScanRegisterAllocator.class.st | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Tinyrossa/TRRegisterDependency.class.st b/src/Tinyrossa/TRRegisterDependency.class.st index 6b37039..bb9629a 100644 --- a/src/Tinyrossa/TRRegisterDependency.class.st +++ b/src/Tinyrossa/TRRegisterDependency.class.st @@ -32,13 +32,22 @@ TRRegisterDependency >> initializeWithVirtual: aTRVirtualRegister real: aTRRealR ] { #category : #testing } -TRRegisterDependency >> isDependency [ - ^ vreg notNil +TRRegisterDependency >> isTrash [ + ^ vreg isNil ] { #category : #testing } -TRRegisterDependency >> isTrash [ - ^ vreg isNil +TRRegisterDependency >> isUnsatisfiedDependency [ + "Return true, if + (1) this dependency express dependency on + real register (a value of virtual register has to be + in specified real register) + AND + (ii) this dependecy is not satisfied, that is + the virtual register is allocated a different register + than required real register" + + ^ vreg notNil and: [ vreg allocation ~~ rreg ] ] { #category : #'printing & storing' } diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index afcda6f..2d85e05 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -171,7 +171,7 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ live do: [:i | (i start < insnIndex and: [ i stop > insnIndex ]) ifTrue:[liveAcross add:i] ]. deps post do: [:dep | - dep isDependency ifTrue:[ + dep isUnsatisfiedDependency ifTrue:[ self insertMoveFrom: dep rreg to: dep vreg. ]. dep isTrash ifTrue: [ @@ -232,7 +232,7 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ codegen cursor: insnIndex - 1. deps notEmptyOrNil ifTrue: [ deps pre reverseDo: [:dep | - dep isDependency ifTrue:[ + dep isUnsatisfiedDependency ifTrue:[ self insertMoveFrom: dep vreg to: dep rreg. ]. "Note that there's no need to handle thrashed registers here From 45d8cdd4d909d175c7778c0480a71d1d17993d81 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Wed, 15 May 2024 12:03:22 +0100 Subject: [PATCH 08/12] RLSRA: handle pre and post dependencies via interval splitting So far, handling pre and post register dependencies was very limited: it could only handle dependencies on non-allocatable registers. This made things simple - all that was needed was to move value to / from required real register. Since dependency could be only on non-allocatable registers, there was no way to introduce conflict. The downside of this was that this limited a number of registers available - for example argument / return registers could not be allocated: what a waste of good registers! This commit improves handling of dependencies leveraging support for interval splitting. --- ...everseLinearScanRegisterAllocator.class.st | 97 ++++++++++++++++++- src/Tinyrossa/TRVirtualRegister.class.st | 3 +- 2 files changed, 93 insertions(+), 7 deletions(-) diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index 2d85e05..67664fe 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -20,10 +20,29 @@ interval is chosen (`#pickSplit:`) and split (`#splitRegister:at:`). If interval is split between first 'def position' and following 'use position' the new interval has no 'use positions' and spans only one instruction. -Thrashing of a register by an instruction (for example, function call -'thrashes' all volatile registers) is solved by interval splitting -too. When a register is thrashed, its interval if forcefully splitted -(see `#allocateRegistersAt:`. +When an interval is split into two intervals, they're both assigned +same spill slot. The value is spilled at closest prior def position +and reloaded at split position (see `#splitRegister:at:`) + +# Satisfying dependencies + +Dependencies are satisfied by interval splitting too: + + * Intervals allocated to thrashed registers are split at instruction + that thrashes them, this forces spill and reload. + + * Unsatisfied post-dependencies are solved by moving the value + to required real register. If that real register is already + allocated to some other interval, that interval is split which + makes it free (and therefore value can be freely moved there). + + * Unsatisfied pre-dependencies on currently allocated real registers + are trickier and solved by either re-allocating the conflicting + interval to a free register (if any) or swapping allocations + or splitting. For details, see comments in relevant part of + allocateRegistersAt: + +--- Note that there's no need to deal with virtual registers being used across basic block boundary - in Tinyrossa (as well as in Testarossa), the only way @@ -172,7 +191,16 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ deps post do: [:dep | dep isUnsatisfiedDependency ifTrue:[ + "Move value from real register to its virtual register." self insertMoveFrom: dep rreg to: dep vreg. + + live copy do: [:i | + (i register allocation == dep rreg) ifTrue: [ + "Live-across register is trashed, we have to spill and reload. + We do it by forcefully splitting the interval." + self splitRegister: i at: insnIndex. + ]. + ]. ]. dep isTrash ifTrue: [ liveAcross do:[:i | @@ -233,7 +261,66 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ deps notEmptyOrNil ifTrue: [ deps pre reverseDo: [:dep | dep isUnsatisfiedDependency ifTrue:[ - self insertMoveFrom: dep vreg to: dep rreg. + | conflicting | + + conflicting := live detect: [:each | each register allocation == dep rreg ] ifNone: nil. + conflicting notNil ifTrue: [ + "There's a dependency on a real register but (another) live interval (`conflicting`) + is allocated to that very real register (so the real register is not free)." + + "If conflicting interval have just gone live..." + conflicting stop == insnIndex ifTrue: [ + | free | + + free := self pickRegister: conflicting. + free notNil ifTrue:[ + "...and there's a free register, we can just reassign conflicting register + to this free one." + self freeRegister: conflicting register allocation. + conflicting register allocation: free. + self takeRegister: free. + + "Now the real register is free so we can simply move + value from virtual to (now free) real register." + self insertMoveFrom: dep vreg to: dep rreg. + ] ifFalse: [ + "If there's no free register, then we swap allocations for + conflicting and dependent registers. However, we can do this + safely only if dependent register has also gone live at this + position." + + | dependent | + + dependent := live detect: [:each | each register == dep vreg ]. + dependent stop == insnIndex ifFalse: [ + "So if it doesn't (has alreadt been live at this point), se + split it and allocate it right back to the same register. + This way, we can easily swap allocations without possibly + creating conflict in already allocated intervals." + + self splitRegister: dependent at: insnIndex. + self allocateRegister: (dependent := todo removeLast). + ]. + + self assert: dependent stop == insnIndex. + + "Now, swap allocations" + conflicting register allocation: dependent register allocation. + dependent register allocation: dep rreg. + + "Since dependent (virtual) register is allocated to required (real) register, + there's no need to move values." + ]. + ] ifFalse: [ + "Ouch, what to do here?" + self notYetImplemented. + ]. + ] ifFalse: [ + "There's no conflict, i.e., there's no live register currently allocated + to required real register. Therefore we can simply move value from + virtual register to real register." + self insertMoveFrom: dep vreg to: dep rreg. + ]. ]. "Note that there's no need to handle thrashed registers here as all thrashed intervals have been split, see above." diff --git a/src/Tinyrossa/TRVirtualRegister.class.st b/src/Tinyrossa/TRVirtualRegister.class.st index 041353a..b978343 100644 --- a/src/Tinyrossa/TRVirtualRegister.class.st +++ b/src/Tinyrossa/TRVirtualRegister.class.st @@ -34,8 +34,7 @@ TRVirtualRegister >> allocation [ { #category : #accessing } TRVirtualRegister >> allocation: realReg [ - self assert: allocation isNil. - self assert: (realReg isKindOf: TRRealRegister). + self assert: realReg isTRRealRegister. self assert: kind == realReg kind. allocation := realReg From ee81d2fc514b4626b129515a6371cc7973967343 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Thu, 23 May 2024 13:15:14 +0100 Subject: [PATCH 09/12] Tests: add test parameter to run RA in stress mode This commit adds one more parameter to run all tests with `stressRA` config option. --- src/Tinyrossa-Tests/TRCompilationTestCase.class.st | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Tinyrossa-Tests/TRCompilationTestCase.class.st b/src/Tinyrossa-Tests/TRCompilationTestCase.class.st index e6aaa3f..971dd2b 100644 --- a/src/Tinyrossa-Tests/TRCompilationTestCase.class.st +++ b/src/Tinyrossa-Tests/TRCompilationTestCase.class.st @@ -57,7 +57,8 @@ TRCompilationTestCase >> int64Values [ { #category : #accessing } TRCompilationTestCase >> parametersIterator [ ^ super parametersIterator , - (self parameter: #target values: { self target }) + (self parameter: #target values: { self target }), + (self parameter: #stressRA values: { true . false }) ] { #category : #running } @@ -67,6 +68,7 @@ TRCompilationTestCase >> setUp [ target := testParameters at:#target. compilation := TRCompilation forTarget: target. + compilation config stressRA: (testParameters at: #stressRA). shell := TRCompilationTestShell forCompilation: compilation. ] From d312e15f802d9d675d3f02e15efb12ae9ead9c02 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 10 May 2024 10:57:23 +0100 Subject: [PATCH 10/12] RISC-V: force `mul`/`mulw` to use `t0` when `stressRA` is enabled This commit forces first operand and result of `mul` and `mulw` instruction to register `t0` when user enabled `stressRA` config option. This is to generate more pressure on register allocator for testing and debugging purposes, normally this option is not used. --- .../TRRV64GCodeEvaluator.class.st | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/Tinyrossa-RISCV/TRRV64GCodeEvaluator.class.st b/src/Tinyrossa-RISCV/TRRV64GCodeEvaluator.class.st index b79276b..dbcfc10 100644 --- a/src/Tinyrossa-RISCV/TRRV64GCodeEvaluator.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GCodeEvaluator.class.st @@ -88,20 +88,45 @@ TRRV64GCodeEvaluator >> commonMul: node [ ] ifFalse:[ child2 constant == -1 ifTrue:[ dstReg := self codegen allocateRegister. - generate sub: dstReg, zero, src1Reg - ]]]. + generate sub: dstReg, zero, src1Reg + ]]]. ] ifFalse:[ - src2Reg := self evaluate: child2. + src2Reg := self evaluate: child2. dstReg := self codegen allocateRegister. - node type == Int64 ifTrue:[ - generate mul: dstReg, src1Reg , src2Reg - ] ifFalse:[ - generate mulw: dstReg, src1Reg , src2Reg - ]. + + codegen compilation config stressRA ifTrue: [ + "User requested to put more stress on RA (presumably for + RA debugging purposes). + + So here we force argument and return value to be in + certain real register." + + | real insn deps | + + real := t0. + deps := TRRegisterDependencies new. + deps pre addDependency: src1Reg on: real. + deps post addDependency: dstReg on: real. + + node type == Int64 ifTrue:[ + insn := generate mul: real, real , src2Reg + ] ifFalse:[ + insn := generate mulw: real, real , src2Reg + ]. + insn dependencies: deps. + ] ifFalse: [ + node type == Int64 ifTrue:[ + generate mul: dstReg, src1Reg , src2Reg + ] ifFalse:[ + generate mulw: dstReg, src1Reg , src2Reg + ]. + ]. + + + ]. ^dstReg - ] { #category : #'evaluation-helpers' } From 3f5d5e765ea2248c563eefaefba46dffd65f5eeb Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 24 May 2024 15:56:04 +0100 Subject: [PATCH 11/12] RISC-V: make parameter registers allocatable --- src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st index 43980cb..bc3aecd 100644 --- a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st @@ -26,13 +26,13 @@ TRRV64GPSABILinkage >> allocatableRegisters [ over preserved registers. This might save us a need to spill / reload (preserved) registers in prologue / epilogue for small methods." - ^ self volatileRegisters , self preservedRegisters + ^ self volatileRegisters , (self parameterRegisters reversed) , self preservedRegisters ] ifFalse:[ "For non-leaf methods we prefer preserved registers over volatile registers. This might save us a need to spill / reload (volatile) registers at call instructions for small functions." - ^ self preservedRegisters , self volatileRegisters + ^ self preservedRegisters , self volatileRegisters , (self parameterRegisters reversed) ] ] From ba6bfc2fff98b6da32a438b5ff26bb91587a55da Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 24 May 2024 15:56:18 +0100 Subject: [PATCH 12/12] POWER: make parameter registers allocatable --- src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st b/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st index 923a471..75d9b8a 100644 --- a/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st +++ b/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st @@ -43,7 +43,7 @@ TRPPC64PSABILinkage class >> initialize [ { #category : #accessing } TRPPC64PSABILinkage >> allocatableRegisters [ - ^ self preservedRegisters reversed + ^ self preservedRegisters reversed , self parameterRegisters reversed ] { #category : #'code generation' }