From 14262484b4a6b672b1bf1756eaad5041d25a14e4 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 20 Oct 2023 16:57:58 +0100 Subject: [PATCH 01/28] RISC-V: avoid storing / reloading link register for leaf functions --- .../TRRV64GPSABILinkage.class.st | 19 +++++++++++++------ src/Tinyrossa/TRCodeGenerator.class.st | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st index c63bd30..c08850f 100644 --- a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st @@ -98,6 +98,9 @@ TRRV64GPSABILinkage >> generateCall: node [ ]. ]. + "Note that link register has been overwritten" + codegen linkRegisterKilled: true. + node symbol type == Void ifTrue:[ retVreg := nil. ] ifFalse:[ @@ -116,11 +119,13 @@ TRRV64GPSABILinkage >> generateCallIndirect: node [ TRRV64GPSABILinkage >> generateEpilogue: valReg [ | preserved offset | - "Move value to ABI return register... - ...and reload link register." - generate - addi: a0, valReg, 0; - ld: ra, (sp + 0). + "Move value to ABI return register..." + generate addi: a0, valReg, 0. + + "...and reload link register if needed." + codegen linkRegisterKilled ifTrue:[ + generate ld: ra, (sp + 0). + ]. "Restore preserved registers" offset := framePreservedOffset. @@ -145,7 +150,9 @@ TRRV64GPSABILinkage >> generatePrologue [ generate addi: sp, sp, frameSize negated. "Save link register" - generate sd: ra, (sp + 0). + codegen linkRegisterKilled ifTrue:[ + generate sd: ra, (sp + 0). + ]. "Save parameters" parameters := codegen compilation symbolManager lookupSymbolsByType: TRParameterSymbol. diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index ef553a3..6e58557 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -6,7 +6,8 @@ Class { 'virtualRegisters', 'linkage', 'evaluator', - 'generate' + 'generate', + 'linkRegisterKilled' ], #pools : [ 'TRRegisterKinds' @@ -219,6 +220,7 @@ TRCodeGenerator >> initializeWithCompilation: aTRCompilation [ virtualRegisters := Dictionary new. generate := self createAssembler. evaluator := self createEvaluator. + linkRegisterKilled := false ] { #category : #accessing } @@ -226,6 +228,20 @@ TRCodeGenerator >> instructions [ ^ generate memory instructions ] +{ #category : #accessing } +TRCodeGenerator >> linkRegisterKilled [ + "Return true, if code contains a call (and therefore + overwrites link register if any)." + ^linkRegisterKilled +] + +{ #category : #accessing } +TRCodeGenerator >> linkRegisterKilled: aBoolean [ + "Note whether code contains a call (and therefore + overwrites a link register if any)." + linkRegisterKilled := aBoolean +] + { #category : #accessing } TRCodeGenerator >> linkage [ linkage isNil ifTrue: [ From 83116276596f64ca3bfebb31c47e8de66e6c3cf9 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 20 Oct 2023 17:30:04 +0100 Subject: [PATCH 02/28] RISC-V: prefer volatile registers for leaf methods ...over preserved registers. This saves us the need to save / reload (preserved) registers in prologue / epilogue. --- .../TRRV64GPSABILinkage.class.st | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st index c08850f..1523b26 100644 --- a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st @@ -21,8 +21,19 @@ Class { { #category : #accessing } TRRV64GPSABILinkage >> allocatableRegisters [ - "^ { t0 . t1 . t2 . t3 . t4 . t5 . t6 }" - ^ self preservedRegisters + codegen linkRegisterKilled ifFalse:[ + "For leaf methods, we prefer volatile registers over preserved + registers. This saves us a need to save / reload (preserved) + registers in prologue / epilogue." + + ^ self volatileRegisters , self preservedRegisters + ] ifTrue:[ + "FIXME: For non-leaf methods, we only allow to use preserved registers + because as of now, there's no support for spilling / reloading + registers in the code (only in prologue / epilogue)." + + ^ self preservedRegisters + ] ] { #category : #accessing } @@ -267,3 +278,8 @@ TRRV64GPSABILinkage >> parameterRegisters [ TRRV64GPSABILinkage >> preservedRegisters [ ^ { s0 . s1 . s2 . s3 . s4 . s5 . s6 . s7 . s8 . s9 . s10 . s11 } ] + +{ #category : #accessing } +TRRV64GPSABILinkage >> volatileRegisters [ + ^ { t0 . t1 . t2 . t3 . t4 . t5 . t6 } +] From 73c808186aee5b2ac5ab17868801e52799faba66 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 27 Oct 2023 23:59:11 +0100 Subject: [PATCH 03/28] Consider `TRLeave`s return value register as "read" virtual register This commit fixes a bug where return value (virtual) register of an `TRLeave` pseudo-instruction was not considered as "read" by that instruction, which in turn could have caused RA to allocate the same physical register to different virtual register and thus cause invalid value to be returned. This commit fixes this problem (which, interestingly, did not manifest) --- .../TRPPC64CodeGenerator.class.st | 22 +++------ .../TRRV64GCodeGenerator.class.st | 22 +++------ src/Tinyrossa/TRCodeGenerator.class.st | 47 ++++++++++++++++--- 3 files changed, 53 insertions(+), 38 deletions(-) diff --git a/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st b/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st index 08b56ed..e0f7111 100644 --- a/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st +++ b/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st @@ -50,14 +50,9 @@ TRPPC64CodeGenerator >> loadConstant32: value into: reg [ ]. ] -{ #category : #registers } -TRPPC64CodeGenerator >> virtualRegistersModifiedBy: instruction do: block [ - "Evaluate block for each virtual register modified by - given instruction." - - instruction isPseudoInstruction "such as label" ifTrue: [ - ^ self - ]. +{ #category : #'registers-private' } +TRPPC64CodeGenerator >> virtualRegistersModifiedByProcessorInstruction: instruction do: block [ + self assert: instruction isProcessorInstruction. instruction externalBindings keysAndValuesDo: [ :name :value | name = 'rt' ifTrue: [ @@ -71,14 +66,9 @@ TRPPC64CodeGenerator >> virtualRegistersModifiedBy: instruction do: block [ ]. ] -{ #category : #registers } -TRPPC64CodeGenerator >> virtualRegistersReadBy: instruction do: block [ - "Evaluate block for each virtual register read by - given instruction." - - instruction isPseudoInstruction "such as label" ifTrue: [ - ^ self - ]. +{ #category : #'registers-private' } +TRPPC64CodeGenerator >> virtualRegistersReadByProcessorInstruction: instruction do: block [ + self assert: instruction isProcessorInstruction. instruction externalBindings keysAndValuesDo: [ :name :value | (#('ra' 'rb' 'rc' 'rs') includes: name) ifTrue: [ diff --git a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st index 5d14081..37a24db 100644 --- a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st @@ -114,14 +114,9 @@ TRRV64GCodeGenerator >> loadConstant64: value into: reg [ ]. ] -{ #category : #registers } -TRRV64GCodeGenerator >> virtualRegistersModifiedBy: instruction do: block [ - "Evaluate block for each virtual register modified by - given instruction." - - instruction isPseudoInstruction "such as label" ifTrue: [ - ^ self - ]. +{ #category : #'registers-private' } +TRRV64GCodeGenerator >> virtualRegistersModifiedByProcessorInstruction: instruction do: block [ + self assert: instruction isProcessorInstruction. instruction externalBindings keysAndValuesDo: [ :name :value | name = 'rd' ifTrue: [ @@ -135,14 +130,9 @@ TRRV64GCodeGenerator >> virtualRegistersModifiedBy: instruction do: block [ ]. ] -{ #category : #registers } -TRRV64GCodeGenerator >> virtualRegistersReadBy: instruction do: block [ - "Evaluate block for each virtual register read by - given instruction." - - instruction isPseudoInstruction "such as label" ifTrue: [ - ^ self - ]. +{ #category : #'registers-private' } +TRRV64GCodeGenerator >> virtualRegistersReadByProcessorInstruction: instruction do: block [ + self assert: instruction isProcessorInstruction. instruction externalBindings keysAndValuesDo: [ :name :value | (#('rs1' 'rs2' 'rs3') includes: name) ifTrue: [ diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index 6e58557..da77671 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -286,13 +286,51 @@ TRCodeGenerator >> virtualRegisters [ TRCodeGenerator >> virtualRegistersModifiedBy: instruction do: block [ "Evaluate block for each virtual register modified by given instruction." + + instruction isProcessorInstruction ifTrue: [ + self virtualRegistersModifiedByProcessorInstruction: instruction do: block. + ^ self. + ]. +] + +{ #category : #'registers-private' } +TRCodeGenerator >> virtualRegistersModifiedByProcessorInstruction: instruction do: block [ + "Evaluate block for each virtual register modified by + given processor instruction. + + DO NOT USE this method directly, use virtualRegistersModifiedBy:do: + " ^ self subclassResponsibility ] { #category : #registers } TRCodeGenerator >> virtualRegistersReadBy: instruction do: block [ - "Evaluate block for each virtual register read by - given instruction." + "Evaluate block for each virtual register read by + given instruction." + instruction isProcessorInstruction ifTrue: [ + self virtualRegistersReadByProcessorInstruction: instruction do: block. + ^ self. + ]. + + instruction isLeaveInstruction ifTrue: [ + "In some cases, leave instruction refer to value in + physical (real) register rather then virtual register. + + One example is when function returns zero and machine + has zero register (as it is common on RISCs)." + instruction value isTRVirtualRegister ifTrue: [ + block value: instruction value + ]. + ]. +] + +{ #category : #'registers-private' } +TRCodeGenerator >> virtualRegistersReadByProcessorInstruction: instruction do: block [ + "Evaluate block for each virtual register read by + given processor instruction. + + DO NOT USE this method directly, use virtualRegistersModifiedBy:do: + " ^ self subclassResponsibility ] @@ -300,11 +338,8 @@ TRCodeGenerator >> virtualRegistersReadBy: instruction do: block [ TRCodeGenerator >> virtualRegistersUsedBy: instruction [ | used | - instruction isPseudoInstruction "such as label" ifTrue: [ - ^ #() - ]. - used := Set new. self virtualRegistersReadBy: instruction do: [ :vReg | used add: vReg ]. self virtualRegistersModifiedBy: instruction do: [ :vReg | used add: vReg ]. + ^ used ] From e0e102eebdd06a33432faa543dca8695ccfa045e Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 30 Oct 2023 13:43:19 +0000 Subject: [PATCH 04/28] Rename `#virtualRegistersModifiedBy...` to `#virtualRegistersAssignedBy...` as the word 'assigned' is more commonly used in literature. --- .../TRPPC64CodeGenerator.class.st | 18 +++++++-------- .../TRRV64GCodeGenerator.class.st | 22 +++++++++---------- src/Tinyrossa/TRCodeGenerator.class.st | 22 +++++++++---------- .../TRLinearScanRegisterAllocator.class.st | 18 +++++++-------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st b/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st index e0f7111..c180f1e 100644 --- a/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st +++ b/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st @@ -51,19 +51,19 @@ TRPPC64CodeGenerator >> loadConstant32: value into: reg [ ] { #category : #'registers-private' } -TRPPC64CodeGenerator >> virtualRegistersModifiedByProcessorInstruction: instruction do: block [ +TRPPC64CodeGenerator >> virtualRegistersAssignedByProcessorInstruction: instruction do: block [ self assert: instruction isProcessorInstruction. + instruction externalBindings + keysAndValuesDo: [:name :value | + name = 'rt' ifTrue: [ + (value isBitVector and: [ value isSymbolic and: [ value isConstant ] ]) ifTrue: [ + | vReg | - instruction externalBindings keysAndValuesDo: [ :name :value | - name = 'rt' ifTrue: [ - (value isBitVector and: [ value isSymbolic and: [ value isConstant ] ]) ifTrue: [ - | vReg | - - vReg := virtualRegisters at: value sym ifAbsent: nil. - block value: vReg. + vReg := virtualRegisters at: value sym ifAbsent: nil. + block value: vReg. + ]. ]. ]. - ]. ] { #category : #'registers-private' } diff --git a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st index 37a24db..e61d787 100644 --- a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st @@ -115,19 +115,19 @@ TRRV64GCodeGenerator >> loadConstant64: value into: reg [ ] { #category : #'registers-private' } -TRRV64GCodeGenerator >> virtualRegistersModifiedByProcessorInstruction: instruction do: block [ - self assert: instruction isProcessorInstruction. - - instruction externalBindings keysAndValuesDo: [ :name :value | - name = 'rd' ifTrue: [ - (value isBitVector and: [ value isSymbolic and: [ value isConstant ] ]) ifTrue: [ - | vReg | - - vReg := virtualRegisters at: value sym ifAbsent: nil. - block value: vReg. +TRRV64GCodeGenerator >> virtualRegistersAssignedByProcessorInstruction: instruction do: block [ + self assert: instruction isProcessorInstruction. + instruction externalBindings + keysAndValuesDo: [:name :value | + name = 'rd' ifTrue: [ + (value isBitVector and: [ value isSymbolic and: [ value isConstant ] ]) ifTrue: [ + | vReg | + + vReg := virtualRegisters at: value sym ifAbsent: nil. + block value: vReg. + ]. ]. ]. - ]. ] { #category : #'registers-private' } diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index da77671..6893b7d 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -283,23 +283,23 @@ TRCodeGenerator >> virtualRegisters [ ] { #category : #registers } -TRCodeGenerator >> virtualRegistersModifiedBy: instruction do: block [ - "Evaluate block for each virtual register modified by - given instruction." - - instruction isProcessorInstruction ifTrue: [ - self virtualRegistersModifiedByProcessorInstruction: instruction do: block. +TRCodeGenerator >> virtualRegistersAssignedBy: instruction do: block [ + "Evaluate block for each virtual register modified by + given instruction." + + instruction isProcessorInstruction ifTrue: [ + self virtualRegistersAssignedByProcessorInstruction: instruction do: block. ^ self. ]. ] { #category : #'registers-private' } -TRCodeGenerator >> virtualRegistersModifiedByProcessorInstruction: instruction do: block [ +TRCodeGenerator >> virtualRegistersAssignedByProcessorInstruction: instruction do: block [ "Evaluate block for each virtual register modified by - given processor instruction. + given processor instruction. - DO NOT USE this method directly, use virtualRegistersModifiedBy:do: - " + DO NOT USE this method directly, use virtualRegistersModifiedBy:do:" + ^ self subclassResponsibility ] @@ -340,6 +340,6 @@ TRCodeGenerator >> virtualRegistersUsedBy: instruction [ used := Set new. self virtualRegistersReadBy: instruction do: [ :vReg | used add: vReg ]. - self virtualRegistersModifiedBy: instruction do: [ :vReg | used add: vReg ]. + self virtualRegistersAssignedBy: instruction do: [:vReg | used add: vReg ]. ^ used ] diff --git a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st index 95d5293..5f71d64 100644 --- a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st @@ -91,21 +91,21 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ "See class comment why we use `i * 2`." liveRange used: i * 2. ]. - codegen virtualRegistersModifiedBy: insn do: [:vreg | - | liveRange | - - liveRange := liveRanges at: vreg. - "See class comment why we use `(i * 2) + 1`." - liveRange used: (i * 2) + 1. - ]. + codegen virtualRegistersAssignedBy: insn + do: [:vreg | + | liveRange | + + liveRange := liveRanges at: vreg. + "See class comment why we use `(i * 2) + 1`." + liveRange used: (i * 2) + 1. + ]. ]. liveRanges := liveRanges associations sort: [ :a :b | a value start < b value start ]. liveRanges do: [:vregAndRange | - | vReg range | + | vReg | vReg := vregAndRange key. - range := vregAndRange value. self expireOldRanges: vregAndRange. availableRegisters isEmpty ifTrue: [ From f4e62dfe4e53275eb19527a5d21554303d921e5b Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 30 Oct 2023 15:25:04 +0000 Subject: [PATCH 05/28] Introduce new default register allocator - reverse linear scan allocator This commit introduces a new (default) register allocator class: `TRReverseLinearScanRegisterAllocator`. It is similar to linear scan allocator used previously except it progresses "backwards", that is from last instruction to first, from last register use towards its assignment. The advantage of this is that one can insert spills / reloads into an instruction stream without changing indexes of instructions not yet "processed". However, support for spills / reloads is not yet implemented. --- .../TRLinearScanRegisterAllocator.class.st | 3 +- src/Tinyrossa/TRRegisterAllocator.class.st | 2 +- ...everseLinearScanRegisterAllocator.class.st | 134 ++++++++++++++++++ 3 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st diff --git a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st index 5f71d64..28839b8 100644 --- a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st @@ -1,5 +1,6 @@ " -`TRLinearScanRegisterAllocator` is the default allocator used in Tinyrossa. +`TRLinearScanRegisterAllocator` is the old register allocator superseded by +`TRReverseLinearScanRegisterAllocator`. It's straightforward reimplementation from original 1999 paper [1] with one small change: when computing live intervals, use either 2 * i (where i is instruction diff --git a/src/Tinyrossa/TRRegisterAllocator.class.st b/src/Tinyrossa/TRRegisterAllocator.class.st index 204b95d..fd2704a 100644 --- a/src/Tinyrossa/TRRegisterAllocator.class.st +++ b/src/Tinyrossa/TRRegisterAllocator.class.st @@ -9,7 +9,7 @@ Class { { #category : #defaults } TRRegisterAllocator class >> defaultClass [ - ^ TRLinearScanRegisterAllocator + ^ TRReverseLinearScanRegisterAllocator ] { #category : #'instance creation' } diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st new file mode 100644 index 0000000..efaad5f --- /dev/null +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -0,0 +1,134 @@ +" +`TRReverseLinearScanRegisterAllocator` is the default allocator used in Tinyrossa. + +It's straightforward reimplementation from original 1999 paper [1] with two +small changes: + +1. When computing live intervals, use either 2 * i (where i is instruction + index) if the register is read or ( 2 * i ) + 1 if the register is modified. + For example, consider following code: + + i=0: reg_B = reg A - 1 + i=1: reg_C = reg_B * reg_A + i=2: reg_D = reg_C + + then live intervals are: + reg_A = <0, 2> + reg_B = <1, 2> + reg_C = <3, 4> + reg_D = <5, ...> + + This helps on 3-address architectures (RISCs) to (for example) allocate + reg_C (used in instruction i=1) into the same physical register as + reg_B or reg_A - at the start of live interval for reg_C (3) reg_B and reg_A + are already free (since their live interval ends at 2). + +2. The allocation progresses in reverse order. That is, from last instruction + towards first one. + +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 +to transfer value from one (extended) block to another is via `?store` and +`?load` IL operations. + +[1]: MASSIMILIANO POLETTO and VIVEK SARKAR: Linear Scan Register Allocation + http://web.cs.ucla.edu/~palsberg/course/cs132/linearscan.pdf + +" +Class { + #name : #TRReverseLinearScanRegisterAllocator, + #superclass : #TRRegisterAllocator, + #instVars : [ + 'liveRanges', + 'activeRanges', + 'availableRegisters' + ], + #category : #'Tinyrossa-Codegen-Register Allocation' +} + +{ #category : #allocation } +TRReverseLinearScanRegisterAllocator >> allocateRegisterFor: aTRVirtualRegister [ + self assert: aTRVirtualRegister allocation isNil. + aTRVirtualRegister hasConstraints ifTrue: [ + "Check, that constraints can be met, that is no other + active v-reg is allocated to requested register." + self assert: aTRVirtualRegister constraints size == 1. + aTRVirtualRegister constraints do: [:rReg | + activeRanges do: [:vRegAndRange | + vRegAndRange key allocation == rReg ifTrue: [ + self error: 'Conflicing constraints (not yet supported)' + ]. + ]. + aTRVirtualRegister allocation: rReg. + ]. + ] ifFalse: [ + codegen linkage allocatableRegisters do: [:rReg | + (availableRegisters includes: rReg) ifTrue: [ + aTRVirtualRegister allocation: rReg. + availableRegisters remove: rReg. + ^ self. + ]. + ]. + self error: 'Should not happen!'. + ]. +] + +{ #category : #allocation } +TRReverseLinearScanRegisterAllocator >> allocateRegisters [ + | insns | + + insns := codegen instructions. + availableRegisters := codegen linkage allocatableRegisters asSet. + activeRanges := SortedCollection sortBlock: [ :a :b | a value start < b value start ]. + + "Step 1 - compute live intervals." + liveRanges := Dictionary new. + codegen virtualRegisters do: [:vReg | + liveRanges at: vReg put: TRRegisterLiveRange new. + ]. + "Here we compute live intervals in reverse order although + we need not to." + insns size downTo: 1 do: [:i | + | insn | + insn := insns at: i. + codegen virtualRegistersReadBy: insn do: [:vreg | + | liveRange | + + liveRange := liveRanges at: vreg. + "See class comment why we use `i * 2`." + liveRange used: i * 2. + ]. + codegen virtualRegistersAssignedBy: insn + do: [:vreg | + | liveRange | + + liveRange := liveRanges at: vreg. + "See class comment why we use `(i * 2) + 1`." + liveRange used: (i * 2) + 1. + ]. + ]. + liveRanges := liveRanges associations sort: [ :a :b | a value stop < b value stop ]. + + liveRanges reverseDo: [:vregAndRange | + | vReg | + + vReg := vregAndRange key. + + self expireOldIntervals: vregAndRange. + availableRegisters isEmpty ifTrue: [ + self error: 'Spilling not supported yet!' + ] ifFalse: [ + self allocateRegisterFor: vReg. + activeRanges add: vregAndRange + ]. + ]. +] + +{ #category : #allocation } +TRReverseLinearScanRegisterAllocator >> expireOldIntervals: newRange [ + activeRanges copy reverseDo: [:activeRange | + activeRange value start <= newRange value stop ifTrue: [ ^ self ]. + activeRanges remove: activeRange. + availableRegisters add: activeRange key allocation. + ]. +] From 9b7c2483f4a80a7e737dbff5d140ca4b52388bd6 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 30 Oct 2023 15:56:02 +0000 Subject: [PATCH 06/28] Remove support for (unused) per-virtual-register constraints This commit removes original support for constraints on virtual register allocation. Not only they were not used but also they could not represent fact that some instructions kill registers instruction is not using - for example a call kills (trashes) all volatile registers. Subsequent commit will introduce per-instruction specified register dependencies just like in grown-up Testarossa. --- .../TRLinearScanRegisterAllocator.class.st | 27 ++++---------- ...onstraintSolvingRegisterAllocator.class.st | 9 ----- ...everseLinearScanRegisterAllocator.class.st | 27 ++++---------- src/Tinyrossa/TRVirtualRegister.class.st | 35 ------------------- 4 files changed, 14 insertions(+), 84 deletions(-) diff --git a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st index 28839b8..161c4b8 100644 --- a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st @@ -45,28 +45,15 @@ Class { { #category : #allocation } TRLinearScanRegisterAllocator >> allocateRegisterFor: aTRVirtualRegister [ self assert: aTRVirtualRegister allocation isNil. - aTRVirtualRegister hasConstraints ifTrue: [ - "Check, that constraints can be met, that is no other - active v-reg is allocated to requested register." - self assert: aTRVirtualRegister constraints size == 1. - aTRVirtualRegister constraints do: [:rReg | - activeRanges do: [:vRegAndRange | - vRegAndRange key allocation == rReg ifTrue: [ - self error: 'Conflicing constraints (not yet supported)' - ]. - ]. - aTRVirtualRegister allocation: rReg. - ]. - ] ifFalse: [ - codegen linkage allocatableRegisters do: [:rReg | - (availableRegisters includes: rReg) ifTrue: [ - aTRVirtualRegister allocation: rReg. - availableRegisters remove: rReg. - ^ self. - ]. + + codegen linkage allocatableRegisters do: [:rReg | + (availableRegisters includes: rReg) ifTrue: [ + aTRVirtualRegister allocation: rReg. + availableRegisters remove: rReg. + ^ self. ]. - self error: 'Should not happen!'. ]. + self error: 'Should not happen!'. ] { #category : #allocation } diff --git a/src/Tinyrossa/TRNaiveConstraintSolvingRegisterAllocator.class.st b/src/Tinyrossa/TRNaiveConstraintSolvingRegisterAllocator.class.st index 5f3c6e3..4fefa49 100644 --- a/src/Tinyrossa/TRNaiveConstraintSolvingRegisterAllocator.class.st +++ b/src/Tinyrossa/TRNaiveConstraintSolvingRegisterAllocator.class.st @@ -29,15 +29,6 @@ TRNaiveConstraintSolvingRegisterAllocator >> allocateRegisters [ solver assert: (Bool or: (realRegisters collect: [ :rReg | vReg toInt eq: rReg toInt ])). ]. - " - Second, add constraints - " - codegen virtualRegisters do: [:vReg | - vReg constraints do: [:rReg | - solver assert: (vReg toInt eq: rReg toInt) - ]. - ]. - " Third, make sure that mapping has no conflicts. Following code is absolutely bogus as it does not diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index efaad5f..cd08792 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -49,28 +49,15 @@ Class { { #category : #allocation } TRReverseLinearScanRegisterAllocator >> allocateRegisterFor: aTRVirtualRegister [ self assert: aTRVirtualRegister allocation isNil. - aTRVirtualRegister hasConstraints ifTrue: [ - "Check, that constraints can be met, that is no other - active v-reg is allocated to requested register." - self assert: aTRVirtualRegister constraints size == 1. - aTRVirtualRegister constraints do: [:rReg | - activeRanges do: [:vRegAndRange | - vRegAndRange key allocation == rReg ifTrue: [ - self error: 'Conflicing constraints (not yet supported)' - ]. - ]. - aTRVirtualRegister allocation: rReg. - ]. - ] ifFalse: [ - codegen linkage allocatableRegisters do: [:rReg | - (availableRegisters includes: rReg) ifTrue: [ - aTRVirtualRegister allocation: rReg. - availableRegisters remove: rReg. - ^ self. - ]. + + codegen linkage allocatableRegisters do: [:rReg | + (availableRegisters includes: rReg) ifTrue: [ + aTRVirtualRegister allocation: rReg. + availableRegisters remove: rReg. + ^ self. ]. - self error: 'Should not happen!'. ]. + self error: 'Should not happen!'. ] { #category : #allocation } diff --git a/src/Tinyrossa/TRVirtualRegister.class.st b/src/Tinyrossa/TRVirtualRegister.class.st index ae6eea0..8ae1d79 100644 --- a/src/Tinyrossa/TRVirtualRegister.class.st +++ b/src/Tinyrossa/TRVirtualRegister.class.st @@ -3,7 +3,6 @@ Class { #superclass : #AcDSLSymbol, #instVars : [ 'kind', - 'constraints', 'assigned', 'allocation' ], @@ -44,40 +43,6 @@ TRVirtualRegister >> allocation: realReg [ allocation := realReg ] -{ #category : #constraints } -TRVirtualRegister >> constrainTo: reg [ - " - Ensure this virtual register maps to `reg`. - " - self assert: (reg isKindOf: AcDSLRegister). - - constraints isNil ifTrue: [ constraints := Dictionary new ]. - constraints at: nil put: reg. -] - -{ #category : #constraints } -TRVirtualRegister >> constrainTo: reg atInstruction: insn [ - " - Ensure this virtual register maps to `reg` for instruction `insn`. - " - self assert: (reg isKindOf: AcDSLRegister). - self assert: (insn isKindOf: ProcessorInstruction). - - constraints isNil ifTrue: [ constraints := Dictionary new ]. - constraints at: insn put: reg. -] - -{ #category : #constraints } -TRVirtualRegister >> constraints [ - constraints isNil ifTrue: [ ^ Dictionary new ]. - ^ constraints -] - -{ #category : #queries } -TRVirtualRegister >> hasConstraints [ - ^ constraints notNil and: [ constraints notEmpty ] -] - { #category : #initialization } TRVirtualRegister >> initializeWithName: aString kind: aTRRegisterKind [ value := aString. From ca3a88bd591b5a6fcc7d0165bd8b8e5bcd8841cf Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Wed, 1 Nov 2023 11:29:51 +0000 Subject: [PATCH 07/28] Rename `TRLinearScanRegisterAllocator >> #expireOldRanges:` to `#expireOldIntervals:` ...to use the names from original paper [1]. [1]: MASSIMILIANO POLETTO, VIVEK SARKAR: Linear Scan Register Allocation --- src/Tinyrossa/TRLinearScanRegisterAllocator.class.st | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st index 161c4b8..3ecee4e 100644 --- a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st @@ -95,7 +95,7 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ vReg := vregAndRange key. - self expireOldRanges: vregAndRange. + self expireOldIntervals: vregAndRange. availableRegisters isEmpty ifTrue: [ self error: 'Spilling not supported yet!' ] ifFalse: [ @@ -106,7 +106,7 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ ] { #category : #allocation } -TRLinearScanRegisterAllocator >> expireOldRanges: newRange [ +TRLinearScanRegisterAllocator >> expireOldIntervals: newRange [ activeRanges copy do: [:activeRange | activeRange value stop >= newRange value start ifTrue: [ ^ self ]. activeRanges remove: activeRange. From 60e547dd3deaf7cb85889eb23539a27ba5bbd13f Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Wed, 1 Nov 2023 14:01:51 +0000 Subject: [PATCH 08/28] Introduce `TRRegisterDependencies` to express register constraints... ...by instructions that require certain values to be in specific registers. Most common example are calls where (some) parameters are passed in specific registers an return value is passed on specific register(s). Moreover, register dependencies may be used to express situation where an instruction trashes some registers - for example call to a function trashes all volatile registers. Also, some architectures have other constraints, for example on x86, `mul` places result to `eax` / `rax`. Again, this can be (should be) expressed register dependencies as well. This commit only adds the necessary classes and infrastructure but does not use it. This is left for later commits. --- src/Tinyrossa/AcInstruction.extension.st | 10 ++ src/Tinyrossa/TRCodeGenerator.class.st | 6 +- src/Tinyrossa/TRRealRegister.class.st | 5 + src/Tinyrossa/TRRegisterDependencies.class.st | 103 ++++++++++++++++++ src/Tinyrossa/TRRegisterDependency.class.st | 68 ++++++++++++ .../TRRegisterDependencyGroup.class.st | 36 ++++++ 6 files changed, 226 insertions(+), 2 deletions(-) create mode 100644 src/Tinyrossa/TRRegisterDependencies.class.st create mode 100644 src/Tinyrossa/TRRegisterDependency.class.st create mode 100644 src/Tinyrossa/TRRegisterDependencyGroup.class.st diff --git a/src/Tinyrossa/AcInstruction.extension.st b/src/Tinyrossa/AcInstruction.extension.st index 3257150..c58e04c 100644 --- a/src/Tinyrossa/AcInstruction.extension.st +++ b/src/Tinyrossa/AcInstruction.extension.st @@ -1,5 +1,15 @@ Extension { #name : #AcInstruction } +{ #category : #'*Tinyrossa' } +AcInstruction >> dependencies [ + ^ self annotationAt: TRRegisterDependencies +] + +{ #category : #'*Tinyrossa' } +AcInstruction >> dependencies: aTRRegisterDependencies [ + self annotationAddOrReplace: aTRRegisterDependencies +] + { #category : #'*Tinyrossa' } AcInstruction >> isLeaveInstruction [ ^ false diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index 6893b7d..023f60a 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -306,10 +306,12 @@ TRCodeGenerator >> virtualRegistersAssignedByProcessorInstruction: instruction d { #category : #registers } TRCodeGenerator >> virtualRegistersReadBy: instruction do: block [ "Evaluate block for each virtual register read by - given instruction." + given instruction." + + | deps | + instruction isProcessorInstruction ifTrue: [ self virtualRegistersReadByProcessorInstruction: instruction do: block. - ^ self. ]. instruction isLeaveInstruction ifTrue: [ diff --git a/src/Tinyrossa/TRRealRegister.class.st b/src/Tinyrossa/TRRealRegister.class.st index d114583..71b1b56 100644 --- a/src/Tinyrossa/TRRealRegister.class.st +++ b/src/Tinyrossa/TRRealRegister.class.st @@ -28,6 +28,11 @@ TRRealRegister >> initializeWithValue: anAcAsmMapEntry kind: aTRRegisterKind [ kind := aTRRegisterKind ] +{ #category : #testing } +TRRealRegister >> isTRRealRegister [ + ^ true +] + { #category : #testing } TRRealRegister >> isTRRegister [ ^ true diff --git a/src/Tinyrossa/TRRegisterDependencies.class.st b/src/Tinyrossa/TRRegisterDependencies.class.st new file mode 100644 index 0000000..24c51eb --- /dev/null +++ b/src/Tinyrossa/TRRegisterDependencies.class.st @@ -0,0 +1,103 @@ +" +`TRRegisterDependencies` are used to describe dependencies (constraints) +of (virtual and real) registers at given point in instruction steam. This +information is used by register allocator when assiging real registers +to virtual registers. + +Examples of such dependencies (constraints) include: + + * When making a subroutine call, often (some) parameters are + passed in specific real registers and return value is + returned through specific real registers. Calling convention + usually dictates that. + + In this case, we say that (virtual) register containing + parameter value ""is dependent"" on real register at given + instruction. Similarly for return values. + + * Also when making call, some registers are volatile (or caller-saved) + so when used by the caller, they must be spilled before / reloaded after + the call. + + In this case, we say that real registers is trashed by given + instruction. + + * On some architectures certain instruction requires operands to be + placed in certain registers or put result into certain register. + Intel X86 is famous for this - for example `mul` instruction uses + `eax` (`rax`) as one operand and places result to `edx`,`eax` + (`rdx`:`rax`) pairs. + + In this case there's a dependency of first operand on `eax` (`rax`), + dependency of return (virtual) register on `eax` (`rax`) and + either dependency of return (virtual) register on `edx` (`rdx`) + in case we want high bits of multiplication result or `edx` (`rdx`) + is trashed (in case we do not need high bits). + +`TRRegisterDependencies` contain two set of dependencies: + + (i) 'pre-dependencies' that express dependencies (constraints) on + registers *BEFORE* the instruction is executed. + + (ii) 'pre-dependencies' that express dependencies (constraints) on + registers *AFTER* the instruction is executed. + +" +Class { + #name : #TRRegisterDependencies, + #superclass : #Object, + #instVars : [ + 'pre', + 'post' + ], + #category : #'Tinyrossa-Codegen' +} + +{ #category : #'instance creation' } +TRRegisterDependencies class >> new [ + "return an initialized instance" + + ^ self basicNew initialize. +] + +{ #category : #'adding & removing' } +TRRegisterDependencies >> addPostDependencyOf: vreg on: rreg [ + "Add register dependency ensuring that *after execution* + of an instruction, value of *vreg* is kept in *rreg*." + + post add: (TRRegisterDependency virtual: vreg real: rreg) +] + +{ #category : #'adding & removing' } +TRRegisterDependencies >> addPreDependencyOf: vreg on: rreg [ + "Add register dependency ensuring that *prior execution* + of an instruction, value of *vreg* is stored in *rreg*." + + pre add: (TRRegisterDependency virtual: vreg real: rreg) +] + +{ #category : #initialization } +TRRegisterDependencies >> initialize [ + pre := TRRegisterDependencyGroup new. + post := TRRegisterDependencyGroup new. +] + +{ #category : #testing } +TRRegisterDependencies >> isEmptyOrNil [ + ^ pre isEmptyOrNil and: [ post isEmptyOrNil ] +] + +{ #category : #testing } +TRRegisterDependencies >> notEmptyOrNil [ + ^ pre notEmptyOrNil or: [ post notEmptyOrNil ] +] + +{ #category : #accessing } +TRRegisterDependencies >> post [ + ^ post +] + +{ #category : #accessing } +TRRegisterDependencies >> pre [ + ^ pre +] diff --git a/src/Tinyrossa/TRRegisterDependency.class.st b/src/Tinyrossa/TRRegisterDependency.class.st new file mode 100644 index 0000000..6b37039 --- /dev/null +++ b/src/Tinyrossa/TRRegisterDependency.class.st @@ -0,0 +1,68 @@ +" +See class TRRegisterDependencies + +" +Class { + #name : #TRRegisterDependency, + #superclass : #Object, + #instVars : [ + 'vreg', + 'rreg' + ], + #category : #'Tinyrossa-Codegen' +} + +{ #category : #'instance creation' } +TRRegisterDependency class >> new [ + ^ self shouldNotImplement. "Use virtual:real: instead" +] + +{ #category : #'instance creation' } +TRRegisterDependency class >> virtual: vreg real: rreg [ + ^ self basicNew initializeWithVirtual: vreg real: rreg +] + +{ #category : #initialization } +TRRegisterDependency >> initializeWithVirtual: aTRVirtualRegister real: aTRRealRegister [ + self assert:(aTRVirtualRegister isNil or:[aTRVirtualRegister isTRRegister]). + self assert: aTRRealRegister isTRRealRegister. + + vreg := aTRVirtualRegister. + rreg := aTRRealRegister. +] + +{ #category : #testing } +TRRegisterDependency >> isDependency [ + ^ vreg notNil +] + +{ #category : #testing } +TRRegisterDependency >> isTrash [ + ^ vreg isNil +] + +{ #category : #'printing & storing' } +TRRegisterDependency >> printOn:aStream [ + "append a printed representation of the receiver to the argument, aStream" + + super printOn:aStream. + aStream nextPut:$(. + vreg notNil ifTrue: [ + aStream nextPutAll: vreg name. + aStream nextPutAll:' -> '. + ] ifFalse: [ + aStream nextPutAll: 'thrashing '. + ]. + aStream nextPutAll: rreg name. + aStream nextPut:$). +] + +{ #category : #accessing } +TRRegisterDependency >> rreg [ + ^ rreg +] + +{ #category : #accessing } +TRRegisterDependency >> vreg [ + ^ vreg +] diff --git a/src/Tinyrossa/TRRegisterDependencyGroup.class.st b/src/Tinyrossa/TRRegisterDependencyGroup.class.st new file mode 100644 index 0000000..24eccd8 --- /dev/null +++ b/src/Tinyrossa/TRRegisterDependencyGroup.class.st @@ -0,0 +1,36 @@ +" +See class TRRegisterDependencies + +" +Class { + #name : #TRRegisterDependencyGroup, + #superclass : #OrderedCollection, + #category : #'Tinyrossa-Codegen' +} + +{ #category : #'adding & removing' } +TRRegisterDependencyGroup >> addDependency: vReg on: rReg [ + "Add dependency of given (usually virtual) register `vReg` + on given real register `rReg`. + + See class documentation of `TRRegisterDependencies`. + " + + self assert: vReg isTRRegister. + self assert: rReg isTRRealRegister. + + self add: (TRRegisterDependency virtual: vReg real: rReg). +] + +{ #category : #'adding & removing' } +TRRegisterDependencyGroup >> addTrashed: rReg [ + "Mark real register `rReg` as 'trashed', that is + overwritten by given instruction. + + See class documentation of `TRRegisterDependencies`. + " + + self assert: rReg isTRRealRegister. + + self add: (TRRegisterDependency virtual: nil real: rReg). +] From f5c8f2ae9ab050403e72414474007ab2b906009e Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Thu, 2 Nov 2023 16:20:02 +0000 Subject: [PATCH 09/28] Add (virtual) register to `TRRegisterLiveRange` This commit adds new instvar - `register` - to `TRRegisterLiveRange` making use of associations in linear scan allocators unnecessary. This feels as a cleaner solution. --- .../TRLinearScanRegisterAllocator.class.st | 39 +++++++-------- src/Tinyrossa/TRRegisterLiveRange.class.st | 29 +++++++++-- ...everseLinearScanRegisterAllocator.class.st | 49 +++++++++---------- 3 files changed, 68 insertions(+), 49 deletions(-) diff --git a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st index 3ecee4e..cd9ebca 100644 --- a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st @@ -62,12 +62,12 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ insns := codegen instructions. availableRegisters := codegen linkage allocatableRegisters asSet. - activeRanges := SortedCollection sortBlock: [ :a :b | a value stop < b value stop ]. + activeRanges := SortedCollection sortBlock: [ :a :b | a stop < b stop ]. "Step 1 - compute live ranges." liveRanges := Dictionary new. codegen virtualRegisters do: [:vReg | - liveRanges at: vReg put: TRRegisterLiveRange new. + liveRanges at: vReg put: (TRRegisterLiveRange forRegister: vReg). ]. 1 to: insns size do: [:i | | insn | @@ -79,28 +79,27 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ "See class comment why we use `i * 2`." liveRange used: i * 2. ]. - codegen virtualRegistersAssignedBy: insn - do: [:vreg | - | liveRange | - - liveRange := liveRanges at: vreg. - "See class comment why we use `(i * 2) + 1`." - liveRange used: (i * 2) + 1. - ]. - ]. - liveRanges := liveRanges associations sort: [ :a :b | a value start < b value start ]. + codegen virtualRegistersAssignedBy: insn do: [:vreg | + | liveRange | - liveRanges do: [:vregAndRange | - | vReg | + liveRange := liveRanges at: vreg. + "See class comment why we use `(i * 2) + 1`." + liveRange used: (i * 2) + 1. + ]. + ]. + liveRanges := liveRanges associations sort: [ :a :b | a start < b start ]. - vReg := vregAndRange key. + liveRanges do: [:liveRange | + self assert: liveRange start odd. + self assert: liveRange stop even. + self assert: liveRange start < liveRange stop. - self expireOldIntervals: vregAndRange. + self expireOldIntervals: liveRange. availableRegisters isEmpty ifTrue: [ self error: 'Spilling not supported yet!' ] ifFalse: [ - self allocateRegisterFor: vReg. - activeRanges add: vregAndRange + self allocateRegisterFor: liveRange. + activeRanges add: liveRange ]. ]. ] @@ -108,8 +107,8 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ { #category : #allocation } TRLinearScanRegisterAllocator >> expireOldIntervals: newRange [ activeRanges copy do: [:activeRange | - activeRange value stop >= newRange value start ifTrue: [ ^ self ]. + activeRange stop >= newRange start ifTrue: [ ^ self ]. activeRanges remove: activeRange. - availableRegisters add: activeRange key allocation. + availableRegisters add: activeRange allocation. ]. ] diff --git a/src/Tinyrossa/TRRegisterLiveRange.class.st b/src/Tinyrossa/TRRegisterLiveRange.class.st index 55535d7..9fbb222 100644 --- a/src/Tinyrossa/TRRegisterLiveRange.class.st +++ b/src/Tinyrossa/TRRegisterLiveRange.class.st @@ -2,19 +2,38 @@ Class { #name : #TRRegisterLiveRange, #superclass : #Object, #instVars : [ + 'register', 'start', 'stop' ], #category : #'Tinyrossa-Codegen-Register Allocation' } +{ #category : #'instance creation' } +TRRegisterLiveRange class >> forRegister: aTRVirtualRegister [ + ^ self basicNew initializeWithRegister: aTRVirtualRegister +] + { #category : #'instance creation' } TRRegisterLiveRange class >> new [ - ^ self basicNew initialize + ^ self shouldNotImplement. "Use #forRegister: instead" +] + +{ #category : #accessing } +TRRegisterLiveRange >> allocation [ + ^ register allocation +] + +{ #category : #accessing } +TRRegisterLiveRange >> allocation: aTRRealRegister [ + register allocation: aTRRealRegister ] { #category : #initialization } -TRRegisterLiveRange >> initialize [ +TRRegisterLiveRange >> initializeWithRegister: aTRVirtualRegister [ + self assert: aTRVirtualRegister isTRVirtualRegister. + + register := aTRVirtualRegister. start := SmallInteger maxVal. stop := 0. ] @@ -24,11 +43,13 @@ TRRegisterLiveRange >> printOn:aStream [ "append a printed representation of the receiver to the argument, aStream" super printOn:aStream. - aStream nextPutAll:'< '. + aStream nextPut: $(. + aStream nextPutAll: register name. + aStream nextPutAll:', <'. start printOn:aStream. aStream nextPutAll:', '. stop printOn:aStream. - aStream nextPutAll:' >'. + aStream nextPutAll:'>)'. ] { #category : #accessing } diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index cd08792..4f2bd2a 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -47,12 +47,12 @@ Class { } { #category : #allocation } -TRReverseLinearScanRegisterAllocator >> allocateRegisterFor: aTRVirtualRegister [ - self assert: aTRVirtualRegister allocation isNil. +TRReverseLinearScanRegisterAllocator >> allocateRegisterFor: newRange [ + self assert: newRange allocation isNil. codegen linkage allocatableRegisters do: [:rReg | (availableRegisters includes: rReg) ifTrue: [ - aTRVirtualRegister allocation: rReg. + newRange allocation: rReg. availableRegisters remove: rReg. ^ self. ]. @@ -66,12 +66,12 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ insns := codegen instructions. availableRegisters := codegen linkage allocatableRegisters asSet. - activeRanges := SortedCollection sortBlock: [ :a :b | a value start < b value start ]. + activeRanges := SortedCollection sortBlock: [ :a :b | a start < b start ]. "Step 1 - compute live intervals." liveRanges := Dictionary new. codegen virtualRegisters do: [:vReg | - liveRanges at: vReg put: TRRegisterLiveRange new. + liveRanges at: vReg put: (TRRegisterLiveRange forRegister: vReg). ]. "Here we compute live intervals in reverse order although we need not to." @@ -85,28 +85,27 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ "See class comment why we use `i * 2`." liveRange used: i * 2. ]. - codegen virtualRegistersAssignedBy: insn - do: [:vreg | - | liveRange | - - liveRange := liveRanges at: vreg. - "See class comment why we use `(i * 2) + 1`." - liveRange used: (i * 2) + 1. - ]. + codegen virtualRegistersAssignedBy: insn do: [:vreg | + | liveRange | + + liveRange := liveRanges at: vreg. + "See class comment why we use `(i * 2) + 1`." + liveRange used: (i * 2) + 1. + ]. ]. - liveRanges := liveRanges associations sort: [ :a :b | a value stop < b value stop ]. - - liveRanges reverseDo: [:vregAndRange | - | vReg | - - vReg := vregAndRange key. - - self expireOldIntervals: vregAndRange. + liveRanges := liveRanges values sort: [ :a :b | a stop < b stop ]. + + liveRanges reverseDo: [:liveRange | + self assert: liveRange start odd. + self assert: liveRange stop even. + self assert: liveRange start < liveRange stop. + + self expireOldIntervals: liveRange. availableRegisters isEmpty ifTrue: [ self error: 'Spilling not supported yet!' ] ifFalse: [ - self allocateRegisterFor: vReg. - activeRanges add: vregAndRange + self allocateRegisterFor: liveRange. + activeRanges add: liveRange ]. ]. ] @@ -114,8 +113,8 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ { #category : #allocation } TRReverseLinearScanRegisterAllocator >> expireOldIntervals: newRange [ activeRanges copy reverseDo: [:activeRange | - activeRange value start <= newRange value stop ifTrue: [ ^ self ]. + activeRange start <= newRange stop ifTrue: [ ^ self ]. activeRanges remove: activeRange. - availableRegisters add: activeRange key allocation. + availableRegisters add: activeRange allocation. ]. ] From 8061a465dcf4d8e1afee01f6a4ffff3aa4f83538 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 6 Nov 2023 14:47:55 +0000 Subject: [PATCH 10/28] Delegate `?return` evaluation to linkage ...just like `?call` is delegated. As before, (new) `TRLinkage >> #generateReturn:` is supposed to generate a leave instruction (see `TRLeave`), *NOT* complete epilogue. This is in preparation for using register dependencies to coerce virtual registers to specific physical registers. --- src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st | 12 ++++++++++++ src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st | 12 ++++++++++++ src/Tinyrossa/TRCodeEvaluator.class.st | 8 +------- src/Tinyrossa/TRLinkage.class.st | 10 ++++++++++ 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st b/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st index b19af28..3774450 100644 --- a/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st +++ b/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st @@ -120,6 +120,18 @@ TRPPC64PSABILinkage >> generatePrologue [ ]. ] +{ #category : #'code generation' } +TRPPC64PSABILinkage >> generateReturn: node [ + | srcReg | + + self assert: codegen compilation functionType == node child1 type. + + srcReg := codegen evaluator evaluate: node child1. + generate leave: srcReg. + + ^ nil +] + { #category : #mapping } TRPPC64PSABILinkage >> mapParameters: parameterTypes [ "Map parameters to argument registers. diff --git a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st index 1523b26..3c0a2f1 100644 --- a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st @@ -182,6 +182,18 @@ TRRV64GPSABILinkage >> generatePrologue [ ]. ] +{ #category : #'code generation' } +TRRV64GPSABILinkage >> generateReturn: node [ + | srcReg | + + self assert: codegen compilation functionType == node child1 type. + + srcReg := codegen evaluator evaluate: node child1. + generate leave: srcReg. + + ^ nil +] + { #category : #initialization } TRRV64GPSABILinkage >> initializeWithCodeGenerator: aTRCodeGenerator [ super initializeWithCodeGenerator: aTRCodeGenerator. diff --git a/src/Tinyrossa/TRCodeEvaluator.class.st b/src/Tinyrossa/TRCodeEvaluator.class.st index 2bffd17..48fa48f 100644 --- a/src/Tinyrossa/TRCodeEvaluator.class.st +++ b/src/Tinyrossa/TRCodeEvaluator.class.st @@ -156,13 +156,7 @@ TRCodeEvaluator >> evaluate_lreturn: node [ { #category : #evaluation } TRCodeEvaluator >> evaluate_return: node [ - | retReg | - - self assert: codegen compilation functionType == node child1 type. - - retReg := self evaluate: node child1. - generate leave: retReg. - ^ nil + ^ codegen linkage generateReturn: node. ] { #category : #evaluation } diff --git a/src/Tinyrossa/TRLinkage.class.st b/src/Tinyrossa/TRLinkage.class.st index 238eef9..45e2e3d 100644 --- a/src/Tinyrossa/TRLinkage.class.st +++ b/src/Tinyrossa/TRLinkage.class.st @@ -49,6 +49,16 @@ TRLinkage >> generatePrologue [ self subclassResponsibility ] +{ #category : #'code generation' } +TRLinkage >> generateReturn: node [ + "Generate a TRLeave for given return node (see AcDSLAssembler >> #leave:). + + If function's linkage return value through register(s), leave pseudo instruction + has to have dependencies set appropriately." + + ^ self subclassResponsibility +] + { #category : #initialization } TRLinkage >> initializeWithCodeGenerator: aTRCodeGenerator [ codegen := aTRCodeGenerator. From 27961027c68c3bc4dbefe7752838dd74a9e4afe5 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 6 Nov 2023 17:10:41 +0000 Subject: [PATCH 11/28] Add `TRCodeGenerator >> #cursor:` (and `#cursor`) ...for setting (and reading) current instruction generation cursor. Useful for injecting register moves / spills and reloads after RA. See `AcDSLAssembler >> #cursor:` (and `#cursor`) --- src/Tinyrossa/TRCodeGenerator.class.st | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index 023f60a..afae608 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -82,6 +82,16 @@ TRCodeGenerator >> createLinkage: linkageClass [ ^ linkageClass forCodeGenerator: self ] +{ #category : #accessing } +TRCodeGenerator >> cursor [ + ^ generate cursor +] + +{ #category : #accessing } +TRCodeGenerator >> cursor: anInteger [ + generate cursor: anInteger +] + { #category : #accessing } TRCodeGenerator >> evaluator [ ^ evaluator From 2e3a54246c340ce34a68e25260b64421dfea8296 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 6 Nov 2023 17:14:49 +0000 Subject: [PATCH 12/28] Add `TRCodeGenerator >> #registerCopyFrom:to:` ...to move register contents from one register to another. Useful (for example) for injecting register moves after RA to satisfy register dependencies. --- src/Tinyrossa/TRCodeGenerator.class.st | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index afae608..01bc72a 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -287,6 +287,13 @@ TRCodeGenerator >> registerAllocatorClass [ ^ compilation config registerAllocatorClass ] +{ #category : #utilities } +TRCodeGenerator >> registerCopyFrom: srcReg to: dstReg [ + "Copy value of source register into destination register" + + self subclassResponsibility +] + { #category : #accessing } TRCodeGenerator >> virtualRegisters [ ^ virtualRegisters From 81f89e4e5405b35be502b654dc88210d59bf5b8e Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 6 Nov 2023 17:17:28 +0000 Subject: [PATCH 13/28] RISC-V: Implement `TRCodeGenerator >> #registerCopyFrom:to:` --- src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st index e61d787..182052b 100644 --- a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st @@ -114,6 +114,13 @@ TRRV64GCodeGenerator >> loadConstant64: value into: reg [ ]. ] +{ #category : #utilities } +TRRV64GCodeGenerator >> registerCopyFrom: srcReg to: dstReg [ + "Copy value of source register into destination register" + + generate addi: dstReg, srcReg, 0 +] + { #category : #'registers-private' } TRRV64GCodeGenerator >> virtualRegistersAssignedByProcessorInstruction: instruction do: block [ self assert: instruction isProcessorInstruction. From ae54ae136883b7fabe8e6e72c15f412ae8aeb727 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 6 Nov 2023 17:35:30 +0000 Subject: [PATCH 14/28] RISC-V: use register dependencies to express register constraints This commit uses `TRRegisterDependencies` to express constraints on registers upon function call and return such that arguments are passed in specific registers as well as return value is returned through a register. --- .../TRRV64GPSABILinkage.class.st | 56 +++++++++++++++---- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st index 3c0a2f1..4e4c322 100644 --- a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st @@ -46,14 +46,18 @@ TRRV64GPSABILinkage >> allocatedPreservedRegisters [ { #category : #'code generation' } TRRV64GPSABILinkage >> generateCall: node [ - "Also handles indirect calls" - | indirect firstParameterIndex parameterVregs parameterTypes parameterRregs retVreg| + "Also handles indirect calls." + | indirect deps firstParameterIndex parameterVregs parameterTypes parameterRregs call retVreg | indirect := node opcode isIndirect. + deps := TRRegisterDependencies new. + + "Step 1: collect parameters and map parameters to parameter registers + (and stack slots, thought that's not supported yet)" + "If this is an indirect call, the first child is the address of the callee." firstParameterIndex := indirect ifTrue: [ 2 ] ifFalse: [ 1 ]. - parameterVregs := Array new: node children size - firstParameterIndex + 1. parameterTypes := Array new: node children size - firstParameterIndex + 1. @@ -66,22 +70,40 @@ TRRV64GPSABILinkage >> generateCall: node [ parameterTypes at: i - firstParameterIndex + 1 put: parameter type. ]. - parameterRregs := self mapParameters: parameterTypes. - parameterVregs with: parameterRregs do: [:valReg :paramReg | - generate addi: paramReg , valReg, 0 + + "Step 2: map parameters into parameter registers and + thrash all other unused parameter registers and volatile + registers." + parameterVregs with: parameterRregs do: [:vReg :rReg | + generate addi: rReg, vReg, 0. + deps pre addDependency: vReg on: rReg. + rReg ~~ a0 ifTrue: [ + deps post addDependency: vReg on: rReg. + ] + ]. + self parameterRegisters do: [:rReg | + (parameterRregs includes: rReg) ifFalse: [ + deps pre addTrashed: rReg. + deps post addTrashed: rReg. + ]. + ]. + self volatileRegisters do: [:rReg | + deps pre addTrashed: rReg. + deps post addTrashed: rReg. ]. + "Step 3: generate all instruction" indirect ifTrue: [ | addrReg | addrReg := codegen evaluator evaluate: node child1. - generate jalr: ra, addrReg, 0. + call := generate jalr: ra, addrReg, 0. ] ifFalse: [ "If the call a recursive call..." node symbol == codegen compilation functionSymbol ifTrue: [ "...then generate 'jal ra, '..." - generate jal: ra, node symbol. + call := generate jal: ra, node symbol. ] ifFalse: [ "...otherwise..." codegen compilation isAOT ifTrue: [ @@ -89,7 +111,7 @@ TRRV64GPSABILinkage >> generateCall: node [ In AOT mode we generate call and let the (runtime) linker to properly relocate it. " - generate call: node symbol + call := generate call: node symbol ] ifFalse: [ " In JIT mode we load address directly into 'ra' @@ -104,20 +126,25 @@ TRRV64GPSABILinkage >> generateCall: node [ self assert: node symbol address notNil description: 'No address set for function symbol'. codegen loadConstant64: node symbol address into: ra. - generate jalr: ra, ra, 0. + call := generate jalr: ra, ra, 0. ]. ]. ]. + call dependencies: deps. + "Note that link register has been overwritten" codegen linkRegisterKilled: true. + "Step 4: map return value into return register (if any) and finish" node symbol type == Void ifTrue:[ retVreg := nil. ] ifFalse:[ retVreg := codegen allocateRegister. generate addi: retVreg , a0, 0. + deps post addDependency: retVreg on: a0. ]. + ^ retVreg ] @@ -184,12 +211,17 @@ TRRV64GPSABILinkage >> generatePrologue [ { #category : #'code generation' } TRRV64GPSABILinkage >> generateReturn: node [ - | srcReg | + | srcReg leave deps | self assert: codegen compilation functionType == node child1 type. srcReg := codegen evaluator evaluate: node child1. - generate leave: srcReg. + + deps := TRRegisterDependencies new. + deps pre addDependency: srcReg on: a0. + + leave := generate leave: srcReg. + leave dependencies: deps. ^ nil ] From 5de102ee2c01e8a855c23c846e00a1b082377726 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 6 Nov 2023 17:15:36 +0000 Subject: [PATCH 15/28] POWER: Implement `TRCodeGenerator >> #registerCopyFrom:to:` --- src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st b/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st index c180f1e..4a46007 100644 --- a/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st +++ b/src/Tinyrossa-POWER/TRPPC64CodeGenerator.class.st @@ -50,6 +50,11 @@ TRPPC64CodeGenerator >> loadConstant32: value into: reg [ ]. ] +{ #category : #utilities } +TRPPC64CodeGenerator >> registerCopyFrom: srcReg to: dstReg [ + generate mr: dstReg, srcReg +] + { #category : #'registers-private' } TRPPC64CodeGenerator >> virtualRegistersAssignedByProcessorInstruction: instruction do: block [ self assert: instruction isProcessorInstruction. From 7c918d05ddcb0f1772e6b7521223de3233fb4aee Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 6 Nov 2023 17:36:00 +0000 Subject: [PATCH 16/28] POWER: use register dependencies to express register constraints This commit uses `TRRegisterDependencies` to express constraints on registers upon function call and return such that arguments are passed in specific registers as well as return value is returned through a register. --- src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st b/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st index 3774450..8ce71fd 100644 --- a/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st +++ b/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st @@ -122,12 +122,17 @@ TRPPC64PSABILinkage >> generatePrologue [ { #category : #'code generation' } TRPPC64PSABILinkage >> generateReturn: node [ - | srcReg | + | srcReg leave deps | self assert: codegen compilation functionType == node child1 type. srcReg := codegen evaluator evaluate: node child1. - generate leave: srcReg. + + deps := TRRegisterDependencies new. + deps pre addDependency: srcReg on: gr4. + + leave := generate leave: srcReg. + leave dependencies: deps. ^ nil ] From d43edf97c783545139d8a16a0282a1449edc0be8 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Tue, 7 Nov 2023 11:35:52 +0000 Subject: [PATCH 17/28] Treat pre-dependent virtual registers of an instruction as "read" This commit treats any dependent virtual register in instructions' register pre-dependencies as "read" by that instruction. This makes sure the register is live at that point. --- src/Tinyrossa/TRCodeGenerator.class.st | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index 01bc72a..cde978d 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -341,6 +341,18 @@ TRCodeGenerator >> virtualRegistersReadBy: instruction do: block [ block value: instruction value ]. ]. + + "Pre-dependencies represent physical registers that contain + values of (presumably) used virtual registers. Therefore + such virtual registers are considered as 'read'." + deps := instruction dependencies. + deps notNil ifTrue: [ + deps pre do: [:info | + info vreg isTRVirtualRegister ifTrue: [ + block value: info vreg. + ]. + ]. + ]. ] { #category : #'registers-private' } From d5d185d1135b4a69f9db88f4604044d82db9e8b1 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Tue, 7 Nov 2023 11:38:16 +0000 Subject: [PATCH 18/28] Treat post-dependent virtual registers of an instruction as "assigned" Similar to previous commit, this commit treats any dependent virtual register in instructions' register post-dependencies as "assigned" by that instruction. This makes sure the register is live at that point. --- src/Tinyrossa/TRCodeGenerator.class.st | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index cde978d..24e1a60 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -303,10 +303,23 @@ TRCodeGenerator >> virtualRegisters [ TRCodeGenerator >> virtualRegistersAssignedBy: instruction do: block [ "Evaluate block for each virtual register modified by given instruction." - + + | deps | + instruction isProcessorInstruction ifTrue: [ self virtualRegistersAssignedByProcessorInstruction: instruction do: block. - ^ self. + ]. + + "Post-dependencies represent physical registers that contain + values of (presumably) later used virtual registers. Therefore + such virtual registers are considered as 'assigned'." + deps := instruction dependencies. + deps notNil ifTrue: [ + deps post do: [:info | + info vreg isTRVirtualRegister ifTrue: [ + block value: info vreg. + ]. + ]. ]. ] From 1de66be10461b56c5cc9dc2a2976e4aa887e345b Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Tue, 7 Nov 2023 12:03:28 +0000 Subject: [PATCH 19/28] Let the register allocator to handle (virtual) register dependencies Previously, satisfying (virtual) register dependencies was hard-coded in linkage, i.e., linkage generated code to move values to appropriate parameter registers before the call and move return value from return register to desired (virtual) register. This solution has two problems: * It works only for parameter / return values of a call, not for any instruction. This is especially problem for x86 which is famous for having peculiar restrictions on what registers can be used with what instructions. * Moreover, it effectively prohibits register allocator to allocate values directly into parameter registers or read value directly from return register and thus, increases register pressure. This commit addreses these two problem by moving the responsibility for satisfying register dependencies to to register allocator. As of now, it does not do anything smart w.r.t. allocation but simply inserts register moves. This will be improved later. --- .../TRPPC64PSABILinkage.class.st | 4 -- .../TRRV64GPSABILinkage.class.st | 9 +---- .../TRLinearScanRegisterAllocator.class.st | 40 +++++++++++++++++-- src/Tinyrossa/TRRegisterAllocator.class.st | 9 +++++ ...everseLinearScanRegisterAllocator.class.st | 38 ++++++++++++++++-- 5 files changed, 82 insertions(+), 18 deletions(-) diff --git a/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st b/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st index 8ce71fd..923a471 100644 --- a/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st +++ b/src/Tinyrossa-POWER/TRPPC64PSABILinkage.class.st @@ -55,10 +55,6 @@ TRPPC64PSABILinkage >> generateCall: node [ { #category : #'code generation' } TRPPC64PSABILinkage >> generateEpilogue: valReg [ - "Move value to ABI return register..." - generate - mr: gr4, valReg. - "Restore preserved registers" self preservedRegisters do: [:reg | (self allocatedRegisters includes: reg) ifTrue: [ diff --git a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st index 4e4c322..af59ff6 100644 --- a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st @@ -76,7 +76,6 @@ TRRV64GPSABILinkage >> generateCall: node [ thrash all other unused parameter registers and volatile registers." parameterVregs with: parameterRregs do: [:vReg :rReg | - generate addi: rReg, vReg, 0. deps pre addDependency: vReg on: rReg. rReg ~~ a0 ifTrue: [ deps post addDependency: vReg on: rReg. @@ -141,7 +140,6 @@ TRRV64GPSABILinkage >> generateCall: node [ retVreg := nil. ] ifFalse:[ retVreg := codegen allocateRegister. - generate addi: retVreg , a0, 0. deps post addDependency: retVreg on: a0. ]. @@ -157,12 +155,9 @@ TRRV64GPSABILinkage >> generateCallIndirect: node [ TRRV64GPSABILinkage >> generateEpilogue: valReg [ | preserved offset | - "Move value to ABI return register..." - generate addi: a0, valReg, 0. - - "...and reload link register if needed." + "Reload link register if needed" codegen linkRegisterKilled ifTrue:[ - generate ld: ra, (sp + 0). + generate ld: ra, (sp + 0). ]. "Restore preserved registers" diff --git a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st index cd9ebca..2d7b825 100644 --- a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st @@ -37,6 +37,7 @@ Class { #instVars : [ 'liveRanges', 'activeRanges', + 'allocatableRegisters', 'availableRegisters' ], #category : #'Tinyrossa-Codegen-Register Allocation' @@ -46,7 +47,7 @@ Class { TRLinearScanRegisterAllocator >> allocateRegisterFor: aTRVirtualRegister [ self assert: aTRVirtualRegister allocation isNil. - codegen linkage allocatableRegisters do: [:rReg | + allocatableRegisters do: [:rReg | (availableRegisters includes: rReg) ifTrue: [ aTRVirtualRegister allocation: rReg. availableRegisters remove: rReg. @@ -61,10 +62,11 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ | insns | insns := codegen instructions. - availableRegisters := codegen linkage allocatableRegisters asSet. + allocatableRegisters := codegen linkage allocatableRegisters. + availableRegisters := allocatableRegisters asSet. activeRanges := SortedCollection sortBlock: [ :a :b | a stop < b stop ]. - "Step 1 - compute live ranges." + "Step 1 - compute live intervals." liveRanges := Dictionary new. codegen virtualRegisters do: [:vReg | liveRanges at: vReg put: (TRRegisterLiveRange forRegister: vReg). @@ -87,8 +89,11 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ liveRange used: (i * 2) + 1. ]. ]. - liveRanges := liveRanges associations sort: [ :a :b | a start < b start ]. + " + Step 2. Allocate registers using collected intervals. + " + liveRanges := liveRanges associations sort: [ :a :b | a start < b start ]. liveRanges do: [:liveRange | self assert: liveRange start odd. self assert: liveRange stop even. @@ -102,6 +107,33 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ activeRanges add: liveRange ]. ]. + + " + Step 3. Insert register moves to satisfy register dependencies + " + insns size downTo: 1 do: [:i | + | insn deps | + + insn := insns at: i. + deps := insn dependencies. + + deps notEmptyOrNil ifTrue: [ + codegen cursor: i. + deps post do: [:dep | + dep isDependency ifTrue:[ + self insertMoveFrom: dep rreg to: dep vreg. + ]. + ]. + + codegen cursor: i - 1. + deps pre do: [:dep | + dep isDependency ifTrue:[ + self insertMoveFrom: dep vreg to: dep rreg. + ]. + + ]. + ]. + ]. ] { #category : #allocation } diff --git a/src/Tinyrossa/TRRegisterAllocator.class.st b/src/Tinyrossa/TRRegisterAllocator.class.st index fd2704a..60558fb 100644 --- a/src/Tinyrossa/TRRegisterAllocator.class.st +++ b/src/Tinyrossa/TRRegisterAllocator.class.st @@ -31,3 +31,12 @@ TRRegisterAllocator >> allocateRegisters [ TRRegisterAllocator >> initializeWithCodeGenerator: aTRCodeGenerator [ codegen := aTRCodeGenerator ] + +{ #category : #utilities } +TRRegisterAllocator >> insertMoveFrom: srcReg to: dstReg [ + (srcReg allocation isNil + or: [ dstReg allocation isNil + or: [ srcReg allocation ~~ dstReg allocation ]]) ifTrue: [ + codegen registerCopyFrom: srcReg to: dstReg. + ]. +] diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index 4f2bd2a..28a204a 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -41,6 +41,7 @@ Class { #instVars : [ 'liveRanges', 'activeRanges', + 'allocatableRegisters', 'availableRegisters' ], #category : #'Tinyrossa-Codegen-Register Allocation' @@ -50,7 +51,7 @@ Class { TRReverseLinearScanRegisterAllocator >> allocateRegisterFor: newRange [ self assert: newRange allocation isNil. - codegen linkage allocatableRegisters do: [:rReg | + allocatableRegisters do: [:rReg | (availableRegisters includes: rReg) ifTrue: [ newRange allocation: rReg. availableRegisters remove: rReg. @@ -65,7 +66,8 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ | insns | insns := codegen instructions. - availableRegisters := codegen linkage allocatableRegisters asSet. + allocatableRegisters := codegen linkage allocatableRegisters. + availableRegisters := allocatableRegisters asSet. activeRanges := SortedCollection sortBlock: [ :a :b | a start < b start ]. "Step 1 - compute live intervals." @@ -77,6 +79,7 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ we need not to." insns size downTo: 1 do: [:i | | insn | + insn := insns at: i. codegen virtualRegistersReadBy: insn do: [:vreg | | liveRange | @@ -93,8 +96,11 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ liveRange used: (i * 2) + 1. ]. ]. - liveRanges := liveRanges values sort: [ :a :b | a stop < b stop ]. + " + Step 2. Allocate registers using collected intervals. + " + liveRanges := liveRanges values sort: [ :a :b | a stop < b stop ]. liveRanges reverseDo: [:liveRange | self assert: liveRange start odd. self assert: liveRange stop even. @@ -108,6 +114,32 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ activeRanges add: liveRange ]. ]. + + " + Step 3. Insert register moves to satisfy register dependencies + " + insns size downTo: 1 do: [:i | + | insn deps | + + insn := insns at: i. + deps := insn dependencies. + + deps notEmptyOrNil ifTrue: [ + codegen cursor: i. + deps post do: [:dep | + dep isDependency ifTrue:[ + self insertMoveFrom: dep rreg to: dep vreg. + ]. + ]. + + codegen cursor: i - 1. + deps pre do: [:dep | + dep isDependency ifTrue:[ + self insertMoveFrom: dep vreg to: dep rreg. + ]. + ]. + ]. + ]. ] { #category : #allocation } From 1e782384cdfbaeacbdf9356b08686381552102f6 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Tue, 7 Nov 2023 15:52:25 +0000 Subject: [PATCH 20/28] Simplify linear scan allocators This commit simplifies implementation of both (forward) linear scan allocator and reverse linear scan allocator so they no longer use odd interval start points and and even end points (see that `i * 2` and `(i * 2) + 1` when collecting live intervals. Obviously the same effect can be achieved by carefully implementing interval expiration (which is what this commit does). --- src/Tinyrossa/TRCodeGenerator.class.st | 6 ++ .../TRLinearScanRegisterAllocator.class.st | 50 +++++------------ ...everseLinearScanRegisterAllocator.class.st | 55 +++++-------------- 3 files changed, 36 insertions(+), 75 deletions(-) diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index 24e1a60..10509aa 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -387,3 +387,9 @@ TRCodeGenerator >> virtualRegistersUsedBy: instruction [ self virtualRegistersAssignedBy: instruction do: [:vReg | used add: vReg ]. ^ used ] + +{ #category : #registers } +TRCodeGenerator >> virtualRegistersUsedBy: instruction do: block [ + self virtualRegistersReadBy: instruction do: block. + self virtualRegistersAssignedBy: instruction do: block. +] diff --git a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st index 2d7b825..1aeb7e4 100644 --- a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st @@ -2,27 +2,8 @@ `TRLinearScanRegisterAllocator` is the old register allocator superseded by `TRReverseLinearScanRegisterAllocator`. -It's straightforward reimplementation from original 1999 paper [1] with one -small change: when computing live intervals, use either 2 * i (where i is instruction -index) if the register is read or ( 2 * i ) + 1 if the register is modified. -For example, consider following code: - - i=0: reg_B = reg A - 1 - i=1: reg_C = reg_B * reg_A - i=2: reg_D = reg_C - -then live intervals are: - reg_A = <0, 2> - reg_B = <1, 2> - reg_C = <3, 4> - reg_D = <5, ...> - -This helps on 3-address architectures (RISCs) to (for example) allocate -reg_C (used in instruction i=1) into the same physical register as -reg_B or reg_A - at the start of live interval for reg_C (3) reg_B and reg_A -are already free (since their live interval ends at 2). - -Note that there's no need to deal with virtual registers being used across +It's straightforward reimplementation from original 1999 paper [1] 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 to transfer value from one (extended) block to another is via `?store` and `?load` IL operations. @@ -73,20 +54,13 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ ]. 1 to: insns size do: [:i | | insn | - insn := insns at: i. - codegen virtualRegistersReadBy: insn do: [:vreg | - | liveRange | - liveRange := liveRanges at: vreg. - "See class comment why we use `i * 2`." - liveRange used: i * 2. - ]. - codegen virtualRegistersAssignedBy: insn do: [:vreg | + insn := insns at: i. + codegen virtualRegistersUsedBy: insn do: [:vreg | | liveRange | liveRange := liveRanges at: vreg. - "See class comment why we use `(i * 2) + 1`." - liveRange used: (i * 2) + 1. + liveRange used: i. ]. ]. @@ -138,9 +112,15 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ { #category : #allocation } TRLinearScanRegisterAllocator >> expireOldIntervals: newRange [ - activeRanges copy do: [:activeRange | - activeRange stop >= newRange start ifTrue: [ ^ self ]. - activeRanges remove: activeRange. - availableRegisters add: activeRange allocation. + self expireOldIntervalsAt: newRange start +] + +{ #category : #allocation } +TRLinearScanRegisterAllocator >> expireOldIntervalsAt: i [ + [ activeRanges notEmpty and:[ activeRanges first stop <= i ] ] whileTrue: [ + | expiredRange | + + expiredRange := activeRanges removeFirst. + availableRegisters add: expiredRange allocation ]. ] diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index 28a204a..678ba70 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -1,30 +1,9 @@ " `TRReverseLinearScanRegisterAllocator` is the default allocator used in Tinyrossa. -It's straightforward reimplementation from original 1999 paper [1] with two -small changes: - -1. When computing live intervals, use either 2 * i (where i is instruction - index) if the register is read or ( 2 * i ) + 1 if the register is modified. - For example, consider following code: - - i=0: reg_B = reg A - 1 - i=1: reg_C = reg_B * reg_A - i=2: reg_D = reg_C - - then live intervals are: - reg_A = <0, 2> - reg_B = <1, 2> - reg_C = <3, 4> - reg_D = <5, ...> - - This helps on 3-address architectures (RISCs) to (for example) allocate - reg_C (used in instruction i=1) into the same physical register as - reg_B or reg_A - at the start of live interval for reg_C (3) reg_B and reg_A - are already free (since their live interval ends at 2). - -2. The allocation progresses in reverse order. That is, from last instruction - towards first one. +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. 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 @@ -81,19 +60,11 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ | insn | insn := insns at: i. - codegen virtualRegistersReadBy: insn do: [:vreg | + codegen virtualRegistersUsedBy: insn do: [:vreg | | liveRange | liveRange := liveRanges at: vreg. - "See class comment why we use `i * 2`." - liveRange used: i * 2. - ]. - codegen virtualRegistersAssignedBy: insn do: [:vreg | - | liveRange | - - liveRange := liveRanges at: vreg. - "See class comment why we use `(i * 2) + 1`." - liveRange used: (i * 2) + 1. + liveRange used: i. ]. ]. @@ -102,8 +73,6 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ " liveRanges := liveRanges values sort: [ :a :b | a stop < b stop ]. liveRanges reverseDo: [:liveRange | - self assert: liveRange start odd. - self assert: liveRange stop even. self assert: liveRange start < liveRange stop. self expireOldIntervals: liveRange. @@ -144,9 +113,15 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ { #category : #allocation } TRReverseLinearScanRegisterAllocator >> expireOldIntervals: newRange [ - activeRanges copy reverseDo: [:activeRange | - activeRange start <= newRange stop ifTrue: [ ^ self ]. - activeRanges remove: activeRange. - availableRegisters add: activeRange allocation. + ^ self expireOldIntervalsAt: newRange stop +] + +{ #category : #allocation } +TRReverseLinearScanRegisterAllocator >> expireOldIntervalsAt: i [ + [ activeRanges notEmpty and:[ activeRanges last start >= i ] ] whileTrue: [ + | expiredRange | + + expiredRange := activeRanges removeLast. + availableRegisters add: expiredRange allocation ]. ] From 35c00a4a6be24207a80fde49fb4738260b217ca7 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 10 Nov 2023 14:53:27 +0000 Subject: [PATCH 21/28] Refactor reverse linear scan allocator (part i) This commit refactors reverse linear scan allocator so that it processes one instruction at time, allocating registers and ensuring dependencies are met. This is a preparation for more clever allocation that handles spills and reloads as well. --- ...everseLinearScanRegisterAllocator.class.st | 92 +++++++++++-------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index 678ba70..b934fee 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -18,6 +18,7 @@ Class { #name : #TRReverseLinearScanRegisterAllocator, #superclass : #TRRegisterAllocator, #instVars : [ + 'instructions', 'liveRanges', 'activeRanges', 'allocatableRegisters', @@ -29,11 +30,13 @@ Class { { #category : #allocation } TRReverseLinearScanRegisterAllocator >> allocateRegisterFor: newRange [ self assert: newRange allocation isNil. + self assert: availableRegisters notEmpty. allocatableRegisters do: [:rReg | (availableRegisters includes: rReg) ifTrue: [ newRange allocation: rReg. availableRegisters remove: rReg. + activeRanges add: newRange. ^ self. ]. ]. @@ -42,9 +45,7 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisterFor: newRange [ { #category : #allocation } TRReverseLinearScanRegisterAllocator >> allocateRegisters [ - | insns | - - insns := codegen instructions. + instructions := codegen instructions. allocatableRegisters := codegen linkage allocatableRegisters. availableRegisters := allocatableRegisters asSet. activeRanges := SortedCollection sortBlock: [ :a :b | a start < b start ]. @@ -56,10 +57,10 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ ]. "Here we compute live intervals in reverse order although we need not to." - insns size downTo: 1 do: [:i | + instructions size downTo: 1 do: [:i | | insn | - insn := insns at: i. + insn := instructions at: i. codegen virtualRegistersUsedBy: insn do: [:vreg | | liveRange | @@ -67,45 +68,64 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ liveRange used: i. ]. ]. + liveRanges do: [:liveRange | + self assert: liveRange start < liveRange stop. + ]. " - Step 2. Allocate registers using collected intervals. + Step 2. Walk instructions in reverse order and allocate + registers. Insert moves / reloads and spills as needed. " - liveRanges := liveRanges values sort: [ :a :b | a stop < b stop ]. - liveRanges reverseDo: [:liveRange | - self assert: liveRange start < liveRange stop. - - self expireOldIntervals: liveRange. - availableRegisters isEmpty ifTrue: [ - self error: 'Spilling not supported yet!' - ] ifFalse: [ - self allocateRegisterFor: liveRange. - activeRanges add: liveRange - ]. + liveRanges := liveRanges values asOrderedCollection sort: [ :a :b | a stop < b stop ]. + + instructions size downTo: 1 do: [:i | + self allocateRegistersAt: i ]. +] +{ #category : #allocation } +TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ + "Helper to allocate registers for instruction at given index. + Also makes ensures both pre and post register dependencies are + met at this point. + + When reading this code, keep in mind that we progress in reverse order, + from last to first instruction! " - Step 3. Insert register moves to satisfy register dependencies - " - insns size downTo: 1 do: [:i | - | insn deps | - - insn := insns at: i. - deps := insn dependencies. - - deps notEmptyOrNil ifTrue: [ - codegen cursor: i. - deps post do: [:dep | - dep isDependency ifTrue:[ - self insertMoveFrom: dep rreg to: dep vreg. - ]. + | insn deps | + + insn := instructions at: insnIndex. + deps := insn dependencies. + + "Satisfy post-dependencies, i.e., move values from fixed (real) + registers to desired (virtual) registers" + deps notEmptyOrNil ifTrue: [ + codegen cursor: insnIndex. + deps post do: [:dep | + dep isDependency ifTrue:[ + self insertMoveFrom: dep rreg to: dep vreg. ]. + ]. + ]. + + "Free registers no longer 'live'" + self expireOldIntervalsAt: insnIndex. + + "Allocate registers going to be live at this point. + Here we remove a register for list of live intervals + (liveRanges is actually a worklist)." + [ liveRanges notEmpty and: [ liveRanges last stop == insnIndex ] ] whileTrue: [ + self allocateRegisterFor: liveRanges removeLast. + ]. - codegen cursor: i - 1. - deps pre do: [:dep | - dep isDependency ifTrue:[ - self insertMoveFrom: dep vreg to: dep rreg. - ]. + "Satisfy pre-dependencies, i.e., move values from (virtual) registers + to desired (real) registers. They're placed *before* the instruction + being processed, hence the `insnIndex - 1`" + deps notEmptyOrNil ifTrue: [ + codegen cursor: insnIndex - 1. + deps pre do: [:dep | + dep isDependency ifTrue:[ + self insertMoveFrom: dep vreg to: dep rreg. ]. ]. ]. From 62335c228c1cbb69fdcaa2df8dcb58cf19f02eca Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Thu, 9 Nov 2023 17:00:00 +0000 Subject: [PATCH 22/28] Refactor reverse linear scan allocator (part ii) This commit further refactors reverse linear scan allocator to facilitate further improvements. --- .../TRLinearScanRegisterAllocator.class.st | 2 +- ...ass.st => TRRegisterLiveInterval.class.st} | 38 +++++------ ...everseLinearScanRegisterAllocator.class.st | 64 +++++++++---------- 3 files changed, 52 insertions(+), 52 deletions(-) rename src/Tinyrossa/{TRRegisterLiveRange.class.st => TRRegisterLiveInterval.class.st} (64%) diff --git a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st index 1aeb7e4..4a4d8a4 100644 --- a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st @@ -50,7 +50,7 @@ TRLinearScanRegisterAllocator >> allocateRegisters [ "Step 1 - compute live intervals." liveRanges := Dictionary new. codegen virtualRegisters do: [:vReg | - liveRanges at: vReg put: (TRRegisterLiveRange forRegister: vReg). + liveRanges at: vReg put: (TRRegisterLiveInterval forRegister: vReg). ]. 1 to: insns size do: [:i | | insn | diff --git a/src/Tinyrossa/TRRegisterLiveRange.class.st b/src/Tinyrossa/TRRegisterLiveInterval.class.st similarity index 64% rename from src/Tinyrossa/TRRegisterLiveRange.class.st rename to src/Tinyrossa/TRRegisterLiveInterval.class.st index 9fbb222..1f06643 100644 --- a/src/Tinyrossa/TRRegisterLiveRange.class.st +++ b/src/Tinyrossa/TRRegisterLiveInterval.class.st @@ -1,5 +1,12 @@ +" +`TRRegisterLiveInterval` is a helper structure used by +(reverse) linear scan allocators. It keeps information +required by the allocator as it progresses and allocates +registers. + +" Class { - #name : #TRRegisterLiveRange, + #name : #TRRegisterLiveInterval, #superclass : #Object, #instVars : [ 'register', @@ -10,27 +17,17 @@ Class { } { #category : #'instance creation' } -TRRegisterLiveRange class >> forRegister: aTRVirtualRegister [ +TRRegisterLiveInterval class >> forRegister: aTRVirtualRegister [ ^ self basicNew initializeWithRegister: aTRVirtualRegister ] { #category : #'instance creation' } -TRRegisterLiveRange class >> new [ +TRRegisterLiveInterval class >> new [ ^ self shouldNotImplement. "Use #forRegister: instead" ] -{ #category : #accessing } -TRRegisterLiveRange >> allocation [ - ^ register allocation -] - -{ #category : #accessing } -TRRegisterLiveRange >> allocation: aTRRealRegister [ - register allocation: aTRRealRegister -] - { #category : #initialization } -TRRegisterLiveRange >> initializeWithRegister: aTRVirtualRegister [ +TRRegisterLiveInterval >> initializeWithRegister: aTRVirtualRegister [ self assert: aTRVirtualRegister isTRVirtualRegister. register := aTRVirtualRegister. @@ -39,7 +36,7 @@ TRRegisterLiveRange >> initializeWithRegister: aTRVirtualRegister [ ] { #category : #'printing & storing' } -TRRegisterLiveRange >> printOn:aStream [ +TRRegisterLiveInterval >> printOn:aStream [ "append a printed representation of the receiver to the argument, aStream" super printOn:aStream. @@ -53,17 +50,22 @@ TRRegisterLiveRange >> printOn:aStream [ ] { #category : #accessing } -TRRegisterLiveRange >> start [ +TRRegisterLiveInterval >> register [ + ^ register +] + +{ #category : #accessing } +TRRegisterLiveInterval >> start [ ^ start ] { #category : #accessing } -TRRegisterLiveRange >> stop [ +TRRegisterLiveInterval >> stop [ ^ stop ] { #category : #utilities } -TRRegisterLiveRange >> used: anInteger [ +TRRegisterLiveInterval >> used: anInteger [ anInteger < start ifTrue:[ start := anInteger. ]. diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index b934fee..48e194f 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -19,8 +19,8 @@ Class { #superclass : #TRRegisterAllocator, #instVars : [ 'instructions', - 'liveRanges', - 'activeRanges', + 'intervals', + 'live', 'allocatableRegisters', 'availableRegisters' ], @@ -28,15 +28,17 @@ Class { } { #category : #allocation } -TRReverseLinearScanRegisterAllocator >> allocateRegisterFor: newRange [ - self assert: newRange allocation isNil. +TRReverseLinearScanRegisterAllocator >> allocateRegister: interval [ + "Allocate register for given `interval`." + + self assert: interval register allocation isNil. self assert: availableRegisters notEmpty. allocatableRegisters do: [:rReg | (availableRegisters includes: rReg) ifTrue: [ - newRange allocation: rReg. + interval register allocation: rReg. availableRegisters remove: rReg. - activeRanges add: newRange. + live add: interval. ^ self. ]. ]. @@ -48,12 +50,12 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ instructions := codegen instructions. allocatableRegisters := codegen linkage allocatableRegisters. availableRegisters := allocatableRegisters asSet. - activeRanges := SortedCollection sortBlock: [ :a :b | a start < b start ]. + live := SortedCollection sortBlock: [ :a :b | a start < b start ]. "Step 1 - compute live intervals." - liveRanges := Dictionary new. + intervals := Dictionary new. codegen virtualRegisters do: [:vReg | - liveRanges at: vReg put: (TRRegisterLiveRange forRegister: vReg). + intervals at: vReg put: (TRRegisterLiveInterval forRegister: vReg). ]. "Here we compute live intervals in reverse order although we need not to." @@ -62,22 +64,21 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ insn := instructions at: i. codegen virtualRegistersUsedBy: insn do: [:vreg | - | liveRange | + | interval | - liveRange := liveRanges at: vreg. - liveRange used: i. + interval := intervals at: vreg. + interval used: i. ]. ]. - liveRanges do: [:liveRange | - self assert: liveRange start < liveRange stop. + intervals do: [:interval | + self assert: interval start < interval stop. ]. + intervals := intervals values asOrderedCollection sort: [ :a :b | a stop < b stop ]. " Step 2. Walk instructions in reverse order and allocate registers. Insert moves / reloads and spills as needed. " - liveRanges := liveRanges values asOrderedCollection sort: [ :a :b | a stop < b stop ]. - instructions size downTo: 1 do: [:i | self allocateRegistersAt: i ]. @@ -109,13 +110,14 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ ]. "Free registers no longer 'live'" - self expireOldIntervalsAt: insnIndex. - - "Allocate registers going to be live at this point. - Here we remove a register for list of live intervals - (liveRanges is actually a worklist)." - [ liveRanges notEmpty and: [ liveRanges last stop == insnIndex ] ] whileTrue: [ - self allocateRegisterFor: liveRanges removeLast. + self expireRegistersAt: insnIndex. + + "Allocate registers going to be live at this point. Here we remove + 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. ]. "Satisfy pre-dependencies, i.e., move values from (virtual) registers @@ -132,16 +134,12 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ ] { #category : #allocation } -TRReverseLinearScanRegisterAllocator >> expireOldIntervals: newRange [ - ^ self expireOldIntervalsAt: newRange stop -] - -{ #category : #allocation } -TRReverseLinearScanRegisterAllocator >> expireOldIntervalsAt: i [ - [ activeRanges notEmpty and:[ activeRanges last start >= i ] ] whileTrue: [ - | expiredRange | +TRReverseLinearScanRegisterAllocator >> expireRegistersAt: insnIndex [ + "Expire all registers no longer live at given instruction (index)." + [ live notEmpty and:[ live last start >= insnIndex ] ] whileTrue: [ + | expired | - expiredRange := activeRanges removeLast. - availableRegisters add: expiredRange allocation + expired := live removeLast. + availableRegisters add: expired register allocation ]. ] From 73e3c0b7708c0bf08ea590187cbef4474bbc61ae Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Thu, 9 Nov 2023 11:34:04 +0000 Subject: [PATCH 23/28] Keep reference to code generator in `TRVirtualRegister` --- src/Tinyrossa/TRCodeGenerator.class.st | 2 +- src/Tinyrossa/TRVirtualRegister.class.st | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index 10509aa..5396cce 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -34,7 +34,7 @@ TRCodeGenerator >> allocateRegister [ TRCodeGenerator >> allocateRegister: kind [ | reg | - reg := TRVirtualRegister named: (kind name , '_' , (virtualRegisters size + 1) printString) kind: kind. + reg := TRVirtualRegister named: (kind name , '_' , (virtualRegisters size + 1) printString) kind: kind codeGenerator: self. virtualRegisters at: reg name put: reg. ^ reg ] diff --git a/src/Tinyrossa/TRVirtualRegister.class.st b/src/Tinyrossa/TRVirtualRegister.class.st index 8ae1d79..46712b8 100644 --- a/src/Tinyrossa/TRVirtualRegister.class.st +++ b/src/Tinyrossa/TRVirtualRegister.class.st @@ -2,21 +2,19 @@ Class { #name : #TRVirtualRegister, #superclass : #AcDSLSymbol, #instVars : [ + 'codegen', 'kind', 'assigned', - 'allocation' + 'allocation', + 'spill', + 'spilled' ], #category : #'Tinyrossa-Codegen' } { #category : #'instance creation' } -TRVirtualRegister class >> named: aString [ - ^ self basicNew initializeWithName: aString -] - -{ #category : #'instance creation' } -TRVirtualRegister class >> named: aString kind: aTRRegisterKind [ - ^ self basicNew initializeWithName: aString kind: aTRRegisterKind +TRVirtualRegister class >> named: aString kind: aTRRegisterKind codeGenerator: aTRCodeGenerator [ + ^ self basicNew initializeWithName: aString kind: aTRRegisterKind codeGenerator: aTRCodeGenerator ] { #category : #arithmetic } @@ -44,7 +42,8 @@ TRVirtualRegister >> allocation: realReg [ ] { #category : #initialization } -TRVirtualRegister >> initializeWithName: aString kind: aTRRegisterKind [ +TRVirtualRegister >> initializeWithName: aString kind: aTRRegisterKind codeGenerator: aTRCodeGenerator [ + codegen := aTRCodeGenerator. value := aString. kind := aTRRegisterKind. assigned := false. From 4481da31520e4dc94fb00bd0eb29c90f97a6440a Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 10 Nov 2023 12:51:06 +0000 Subject: [PATCH 24/28] Add new compilation option: `stressRA` This commit adds new boolean option - `stressRA`. When set to `true` it reduces the set of registers available for allocation drastically to stress-test register allocator. --- src/Tinyrossa/TRCompilationConfig.class.st | 10 ++++++++++ src/Tinyrossa/TRCompilationConfigOptions.class.st | 2 ++ .../TRReverseLinearScanRegisterAllocator.class.st | 3 +++ 3 files changed, 15 insertions(+) diff --git a/src/Tinyrossa/TRCompilationConfig.class.st b/src/Tinyrossa/TRCompilationConfig.class.st index 20e7706..fcde197 100644 --- a/src/Tinyrossa/TRCompilationConfig.class.st +++ b/src/Tinyrossa/TRCompilationConfig.class.st @@ -100,6 +100,16 @@ TRCompilationConfig >> registerAllocatorClass: aClass [ ^ options at: OptionRegisterAllocator put: aClass ] +{ #category : #options } +TRCompilationConfig >> stressRA [ + ^ options at: OptionStressRA ifAbsent: [ false ] +] + +{ #category : #options } +TRCompilationConfig >> stressRA: aBoolean [ + ^ options at: OptionStressRA put: aBoolean +] + { #category : #options } TRCompilationConfig >> target [ ^ options at: OptionTarget ifAbsent: [ self error:'Oops, no target specified!' ] diff --git a/src/Tinyrossa/TRCompilationConfigOptions.class.st b/src/Tinyrossa/TRCompilationConfigOptions.class.st index cae06fd..c46ef51 100644 --- a/src/Tinyrossa/TRCompilationConfigOptions.class.st +++ b/src/Tinyrossa/TRCompilationConfigOptions.class.st @@ -7,6 +7,7 @@ Class { 'OptionObjectFile', 'OptionOptimizationPasses', 'OptionRegisterAllocator', + 'OptionStressRA', 'OptionTarget', 'OptionVerifyAfterEachOptimizationPass' ], @@ -23,4 +24,5 @@ TRCompilationConfigOptions class >> initialize [ OptionObjectFile := #OptionObjectFile. OptionVerifyAfterEachOptimizationPass := #OptionVerifyAfterEachOptimizationPass. OptionOptimizationPasses := #OptionOptimizationPasses. + OptionStressRA := #OptionStressRA ] diff --git a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st index 48e194f..5d32c16 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -49,6 +49,9 @@ TRReverseLinearScanRegisterAllocator >> allocateRegister: interval [ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ instructions := codegen instructions. allocatableRegisters := codegen linkage allocatableRegisters. + codegen compilation config stressRA ifTrue: [ + allocatableRegisters := allocatableRegisters copyFrom: 1 to: 2. + ]. availableRegisters := allocatableRegisters asSet. live := SortedCollection sortBlock: [ :a :b | a start < b start ]. From 1d75e605fcde97ce45f8b0061368121c5f1c1e60 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 10 Nov 2023 12:52:23 +0000 Subject: [PATCH 25/28] RISC-V: always prefer volatile registers when stress-testing the register allocator ...as it forces allocator to spill / reload live volatile register on each call. --- .../TRRV64GPSABILinkage.class.st | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st index af59ff6..43980cb 100644 --- a/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GPSABILinkage.class.st @@ -21,18 +21,18 @@ Class { { #category : #accessing } TRRV64GPSABILinkage >> allocatableRegisters [ - codegen linkRegisterKilled ifFalse:[ - "For leaf methods, we prefer volatile registers over preserved - registers. This saves us a need to save / reload (preserved) - registers in prologue / epilogue." + (codegen linkRegisterKilled not or:[codegen compilation config stressRA]) ifTrue:[ + "For leaf methods (or when we want to stress RA), we prefer volatile registers + over preserved registers. This might save us a need to spill / reload (preserved) + registers in prologue / epilogue for small methods." ^ self volatileRegisters , self preservedRegisters - ] ifTrue:[ - "FIXME: For non-leaf methods, we only allow to use preserved registers - because as of now, there's no support for spilling / reloading - registers in the code (only in prologue / epilogue)." + ] 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 preservedRegisters , self volatileRegisters ] ] From a8ebd65c9a2e59725a93b5f70fb51c89a32cc0e9 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Fri, 17 Nov 2023 15:38:49 +0000 Subject: [PATCH 26/28] When fixing up registers, build fixup map manually This commit creates register fixup map manually rather than depending on fact that virtual register converts to concrete bitvector when real register is allocated. This is not only easier to understand but also makes it easier debug RA because inserted spills / reloads / moves refer to virtual registers so it is clear which (virtual) register is spilled. --- src/Tinyrossa/TRCodeGenerator.class.st | 13 ++++++++++++- src/Tinyrossa/TRVirtualRegister.class.st | 2 -- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index 5396cce..13b5b85 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -138,7 +138,18 @@ TRCodeGenerator >> fixupOffsets [ { #category : #'code gen-phases' } TRCodeGenerator >> fixupRegisters [ - self fixupUsing: virtualRegisters + | v2rMap | + + v2rMap := Dictionary new: virtualRegisters size. + virtualRegisters keysAndValuesDo: [ :name :reg | + "It might be that some virtual register was allocated but not + actually used. In this case, it has not been assigned a + real register." + reg allocation notNil ifTrue: [ + v2rMap at: name put: reg allocation. + ]. + ]. + self fixupUsing: v2rMap ] { #category : #private } diff --git a/src/Tinyrossa/TRVirtualRegister.class.st b/src/Tinyrossa/TRVirtualRegister.class.st index 46712b8..c11722e 100644 --- a/src/Tinyrossa/TRVirtualRegister.class.st +++ b/src/Tinyrossa/TRVirtualRegister.class.st @@ -97,12 +97,10 @@ TRVirtualRegister >> setAssigned [ { #category : #conversion } TRVirtualRegister >> toBitVector: length [ - allocation notNil ifTrue: [ ^ allocation toBitVector: length ]. ^ value toBitVector: length ] { #category : #conversion } TRVirtualRegister >> toInt [ - allocation notNil ifTrue: [ ^ allocation toInt ]. ^ value toInt ] From 192d92d1b7353e09a96a75fdd7a52ee74e057e7d Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 20 Nov 2023 14:56:23 +0000 Subject: [PATCH 27/28] RLSRA: spill / reload trashed live registers This commit add support for spilling / reloading trashed registers that contain live values. Mostly, this is the case of volatile (and argument) registers being (potentially) thrashed by calls. This is only supported by reverse linear scan allocator. Implementation note: for now, spill slot is allocated as unnamed automatic on a stack for each (spilled) interval. For actually spilling, it used `TRCodeGenerator >> registerStore:to:` and `#registerLoad:from:`. This may not work in all cases with all linkages - some linkages may prescribe where to spill registers (PPC64 ELF v2 ABI?, Microsoft x64?). --- .../TRRV64GCodeGenerator.class.st | 25 +++ src/Tinyrossa/TRCodeGenerator.class.st | 20 +++ src/Tinyrossa/TRRegisterLiveInterval.class.st | 34 +++- ...everseLinearScanRegisterAllocator.class.st | 164 ++++++++++++++++-- 4 files changed, 223 insertions(+), 20 deletions(-) diff --git a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st index 182052b..fffa629 100644 --- a/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st +++ b/src/Tinyrossa-RISCV/TRRV64GCodeGenerator.class.st @@ -2,6 +2,7 @@ Class { #name : #TRRV64GCodeGenerator, #superclass : #TRCodeGenerator, #pools : [ + 'TRDataTypes', 'TRIntLimits', 'TRRV64GISALimits', 'TRRV64GRegisters' @@ -121,6 +122,30 @@ TRRV64GCodeGenerator >> registerCopyFrom: srcReg to: dstReg [ generate addi: dstReg, srcReg, 0 ] +{ #category : #utilities } +TRRV64GCodeGenerator >> registerLoad: reg from: sym [ + | offset | + + self assert: reg isTRRegister. + self assert: sym isTRAutomaticSymbol. + self assert: sym type == Address. + + offset := AcDSLSymbol value: sym name. + generate ld: reg, (sp + offset). +] + +{ #category : #utilities } +TRRV64GCodeGenerator >> registerStore: reg to: sym [ + | offset | + + self assert: reg isTRRegister. + self assert: sym isTRAutomaticSymbol. + self assert: sym type == Address. + + offset := AcDSLSymbol value: sym name. + generate sd: reg, (sp + offset). +] + { #category : #'registers-private' } TRRV64GCodeGenerator >> virtualRegistersAssignedByProcessorInstruction: instruction do: block [ self assert: instruction isProcessorInstruction. diff --git a/src/Tinyrossa/TRCodeGenerator.class.st b/src/Tinyrossa/TRCodeGenerator.class.st index 13b5b85..133666e 100644 --- a/src/Tinyrossa/TRCodeGenerator.class.st +++ b/src/Tinyrossa/TRCodeGenerator.class.st @@ -305,6 +305,26 @@ TRCodeGenerator >> registerCopyFrom: srcReg to: dstReg [ self subclassResponsibility ] +{ #category : #utilities } +TRCodeGenerator >> registerLoad: reg from: sym [ + "Load value of given symbol `sym` into a register `reg`. + + Currently, symbol must be an automatic and of type Address. + This method is used to implement register reloads. + " + self subclassResponsibility. +] + +{ #category : #utilities } +TRCodeGenerator >> registerStore: reg to: sym [ + "Store value of given register `reg` into a symbol `sym`. + + Currently, symbol must be an automatic and of type Address. + This method is used to implement register spills. + " + ^ self subclassResponsibility +] + { #category : #accessing } TRCodeGenerator >> virtualRegisters [ ^ virtualRegisters diff --git a/src/Tinyrossa/TRRegisterLiveInterval.class.st b/src/Tinyrossa/TRRegisterLiveInterval.class.st index 1f06643..96675aa 100644 --- a/src/Tinyrossa/TRRegisterLiveInterval.class.st +++ b/src/Tinyrossa/TRRegisterLiveInterval.class.st @@ -11,7 +11,9 @@ Class { #instVars : [ 'register', 'start', - 'stop' + 'stop', + 'spilled', + 'spillSlot' ], #category : #'Tinyrossa-Codegen-Register Allocation' } @@ -33,6 +35,7 @@ TRRegisterLiveInterval >> initializeWithRegister: aTRVirtualRegister [ register := aTRVirtualRegister. start := SmallInteger maxVal. stop := 0. + spilled := false. ] { #category : #'printing & storing' } @@ -54,6 +57,35 @@ TRRegisterLiveInterval >> register [ ^ register ] +{ #category : #accessing } +TRRegisterLiveInterval >> spillSlot [ + ^ spillSlot +] + +{ #category : #accessing } +TRRegisterLiveInterval >> spillSlot: aTRAutomaticSymbol [ + self assert: aTRAutomaticSymbol isTRAutomaticSymbol. + self assert: spillSlot isNil. + + 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 5d32c16..d92c213 100644 --- a/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st +++ b/src/Tinyrossa/TRReverseLinearScanRegisterAllocator.class.st @@ -24,6 +24,10 @@ Class { 'allocatableRegisters', 'availableRegisters' ], + #pools : [ + 'TRDataTypes', + 'TRRegisterKinds' + ], #category : #'Tinyrossa-Codegen-Register Allocation' } @@ -31,18 +35,14 @@ Class { TRReverseLinearScanRegisterAllocator >> allocateRegister: interval [ "Allocate register for given `interval`." + | assigned | + self assert: interval register allocation isNil. - self assert: availableRegisters notEmpty. - allocatableRegisters do: [:rReg | - (availableRegisters includes: rReg) ifTrue: [ - interval register allocation: rReg. - availableRegisters remove: rReg. - live add: interval. - ^ self. - ]. - ]. - self error: 'Should not happen!'. + assigned := self pickRegister: interval. + assigned isNil ifTrue: [ self error: 'No available register!' ]. + + live add: interval. ] { #category : #allocation } @@ -50,7 +50,7 @@ TRReverseLinearScanRegisterAllocator >> allocateRegisters [ 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 ]. @@ -96,19 +96,40 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ When reading this code, keep in mind that we progress in reverse order, from last to first instruction! " - | insn deps | + | insn deps liveAcross thrashed | insn := instructions at: insnIndex. deps := insn dependencies. - "Satisfy post-dependencies, i.e., move values from fixed (real) - registers to desired (virtual) registers" + "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 + " 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] ]. + + thrashed := OrderedCollection new: deps post size. codegen cursor: insnIndex. deps post do: [:dep | dep isDependency ifTrue:[ self insertMoveFrom: dep rreg to: dep vreg. ]. + dep isTrash ifTrue: [ + 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)" + self insertReload: i. + thrashed add: i. + ]. + ] + ]. ]. ]. @@ -123,15 +144,29 @@ TRReverseLinearScanRegisterAllocator >> allocateRegistersAt: insnIndex [ self allocateRegister: intervals removeLast. ]. - "Satisfy pre-dependencies, i.e., move values from (virtual) registers - to desired (real) registers. They're placed *before* the instruction - being processed, hence the `insnIndex - 1`" + "Satisfy pre-dependencies, i.e., + (i) move values from (virtual) registers to desired + (real) registers and... + (ii) spill all thrashed live registers. + + Moves and spills must be placed placed *before* + the instruction being processed, hence the `insnIndex - 1`" deps notEmptyOrNil ifTrue: [ codegen cursor: insnIndex - 1. - deps pre do: [:dep | + deps pre reverseDo: [:dep | 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. + ]. + ] + ]. ]. ]. ] @@ -143,6 +178,97 @@ TRReverseLinearScanRegisterAllocator >> expireRegistersAt: insnIndex [ | expired | expired := live removeLast. - availableRegisters add: expired register allocation + self freeRegister: expired. ]. ] + +{ #category : #utilities } +TRReverseLinearScanRegisterAllocator >> freeRegister: interval [ + "Free register assigned to given interval, i.e., + put it back to list of available registers." + + self assert: interval register allocation notNil. + + availableRegisters add: interval register allocation +] + +{ #category : #utilities } +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. + + slot := interval spillSlot. + slot isNil ifTrue: [ + slot := codegen compilation symbolManager defineAutomatic: nil type: Address. + interval spillSlot: slot. + ]. + 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. + codegen registerStore: interval register to: slot. + interval spilled: false. +] + +{ #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. + + 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 + ]. + ]. + self assert: false description: 'Should never be reached'. +] + +{ #category : #utilities } +TRReverseLinearScanRegisterAllocator >> pickSpill: interval [ + "Pick (choose) and return the best spill FIXME: TBW" + + | insn candidates | + + insn := instructions at: interval stop. + + candidates := live reject: [:each | each spilled ]. + codegen virtualRegistersReadBy: insn do: [:vReg | + live do: [:e | e register == vReg ifTrue:[ candidates remove: e ifAbsent:nil] ] + ]. + candidates isEmpty ifTrue: [ + ^ nil. + ]. + ^ candidates first. +] + +{ #category : #utilities } +TRReverseLinearScanRegisterAllocator >> takeRegister: interval [ + "Mark register assigned to given interval as used." + + self assert: interval register allocation notNil. + + availableRegisters remove: interval register allocation +] From 9825aadfe8301afa54a80ef74d4e6fd2af3cfe92 Mon Sep 17 00:00:00 2001 From: Jan Vrany Date: Mon, 27 Nov 2023 11:56:44 +0000 Subject: [PATCH 28/28] Remove (old) linear scan and (even older) naive constraint solving register allocators This commit removes `TRLinearScanRegisterAllocator` and `TRNaiveConstraintSolvingRegisterAllocator` register allocators. The main reason is that commit "Let the register allocator to handle (virtual) register dependencies" moves the responsibility to move values to argument registers and from return register(s) to register allocator based on register dependencies. Neither `TRLinearScanRegisterAllocator` and `TRNaiveConstraintSolvingRegisterAllocator` can be easily updated to work with this change, so this commit removes them. --- .../TRLinearScanRegisterAllocator.class.st | 126 ------------------ ...onstraintSolvingRegisterAllocator.class.st | 59 -------- 2 files changed, 185 deletions(-) delete mode 100644 src/Tinyrossa/TRLinearScanRegisterAllocator.class.st delete mode 100644 src/Tinyrossa/TRNaiveConstraintSolvingRegisterAllocator.class.st diff --git a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st b/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st deleted file mode 100644 index 4a4d8a4..0000000 --- a/src/Tinyrossa/TRLinearScanRegisterAllocator.class.st +++ /dev/null @@ -1,126 +0,0 @@ -" -`TRLinearScanRegisterAllocator` is the old register allocator superseded by -`TRReverseLinearScanRegisterAllocator`. - -It's straightforward reimplementation from original 1999 paper [1] 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 -to transfer value from one (extended) block to another is via `?store` and -`?load` IL operations. - -[1]: MASSIMILIANO POLETTO and VIVEK SARKAR: Linear Scan Register Allocation - http://web.cs.ucla.edu/~palsberg/course/cs132/linearscan.pdf - -" -Class { - #name : #TRLinearScanRegisterAllocator, - #superclass : #TRRegisterAllocator, - #instVars : [ - 'liveRanges', - 'activeRanges', - 'allocatableRegisters', - 'availableRegisters' - ], - #category : #'Tinyrossa-Codegen-Register Allocation' -} - -{ #category : #allocation } -TRLinearScanRegisterAllocator >> allocateRegisterFor: aTRVirtualRegister [ - self assert: aTRVirtualRegister allocation isNil. - - allocatableRegisters do: [:rReg | - (availableRegisters includes: rReg) ifTrue: [ - aTRVirtualRegister allocation: rReg. - availableRegisters remove: rReg. - ^ self. - ]. - ]. - self error: 'Should not happen!'. -] - -{ #category : #allocation } -TRLinearScanRegisterAllocator >> allocateRegisters [ - | insns | - - insns := codegen instructions. - allocatableRegisters := codegen linkage allocatableRegisters. - availableRegisters := allocatableRegisters asSet. - activeRanges := SortedCollection sortBlock: [ :a :b | a stop < b stop ]. - - "Step 1 - compute live intervals." - liveRanges := Dictionary new. - codegen virtualRegisters do: [:vReg | - liveRanges at: vReg put: (TRRegisterLiveInterval forRegister: vReg). - ]. - 1 to: insns size do: [:i | - | insn | - - insn := insns at: i. - codegen virtualRegistersUsedBy: insn do: [:vreg | - | liveRange | - - liveRange := liveRanges at: vreg. - liveRange used: i. - ]. - ]. - - " - Step 2. Allocate registers using collected intervals. - " - liveRanges := liveRanges associations sort: [ :a :b | a start < b start ]. - liveRanges do: [:liveRange | - self assert: liveRange start odd. - self assert: liveRange stop even. - self assert: liveRange start < liveRange stop. - - self expireOldIntervals: liveRange. - availableRegisters isEmpty ifTrue: [ - self error: 'Spilling not supported yet!' - ] ifFalse: [ - self allocateRegisterFor: liveRange. - activeRanges add: liveRange - ]. - ]. - - " - Step 3. Insert register moves to satisfy register dependencies - " - insns size downTo: 1 do: [:i | - | insn deps | - - insn := insns at: i. - deps := insn dependencies. - - deps notEmptyOrNil ifTrue: [ - codegen cursor: i. - deps post do: [:dep | - dep isDependency ifTrue:[ - self insertMoveFrom: dep rreg to: dep vreg. - ]. - ]. - - codegen cursor: i - 1. - deps pre do: [:dep | - dep isDependency ifTrue:[ - self insertMoveFrom: dep vreg to: dep rreg. - ]. - - ]. - ]. - ]. -] - -{ #category : #allocation } -TRLinearScanRegisterAllocator >> expireOldIntervals: newRange [ - self expireOldIntervalsAt: newRange start -] - -{ #category : #allocation } -TRLinearScanRegisterAllocator >> expireOldIntervalsAt: i [ - [ activeRanges notEmpty and:[ activeRanges first stop <= i ] ] whileTrue: [ - | expiredRange | - - expiredRange := activeRanges removeFirst. - availableRegisters add: expiredRange allocation - ]. -] diff --git a/src/Tinyrossa/TRNaiveConstraintSolvingRegisterAllocator.class.st b/src/Tinyrossa/TRNaiveConstraintSolvingRegisterAllocator.class.st deleted file mode 100644 index 4fefa49..0000000 --- a/src/Tinyrossa/TRNaiveConstraintSolvingRegisterAllocator.class.st +++ /dev/null @@ -1,59 +0,0 @@ -Class { - #name : #TRNaiveConstraintSolvingRegisterAllocator, - #superclass : #TRRegisterAllocator, - #category : #'Tinyrossa-Codegen-Register Allocation' -} - -{ #category : #allocation } -TRNaiveConstraintSolvingRegisterAllocator >> allocateRegisters [ - " - Following code is bogus, it does not really work. - Cannot handle running out of registers. Cannot spill. - Does not handle register liveness. Essentially it does - work only for very simple cases. - But hey, it does simulate register allocation and - it took mi literraly 5 mins to write it :-) - " - - | realRegisters insns solver | - - insns := codegen instructions. - realRegisters := codegen linkage allocatableRegisters. - solver := Z3Solver new. - - " - First, make sure each virtual register gets mapped to - some real, allocatable register. - " - codegen virtualRegisters do: [:vReg | - solver assert: (Bool or: (realRegisters collect: [ :rReg | vReg toInt eq: rReg toInt ])). - ]. - - " - Third, make sure that mapping has no conflicts. - Following code is absolutely bogus as it does not - take liveness into an account, but will do for now. - " - insns do: [:insn | - | used | - - used := codegen virtualRegistersUsedBy: insn. - used do: [:usedReg1 | used do: [:usedReg2 | - usedReg1 ~~ usedReg2 ifTrue: [ - solver assert: (usedReg1 toInt eq: usedReg2 toInt) not - ]. - ]]. - ]. - - solver check ifFalse: [ - self error: 'I give up, you''d better ask mr Chaitin help you here!' - ]. - - solver getModel constants keysAndValuesDo: [:vRegName :rRegValue | - | vReg rReg | - - vReg := codegen virtualRegisters at: vRegName. - rReg := realRegisters detect: [:each | each value = rRegValue value ]. - vReg allocation: rReg. - ]. -]