New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
dialects: riscv: Add basic register allocator strategy #995
Conversation
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #995 +/- ##
==========================================
+ Coverage 86.98% 87.09% +0.10%
==========================================
Files 126 127 +1
Lines 19438 19544 +106
Branches 2951 2960 +9
==========================================
+ Hits 16909 17022 +113
+ Misses 2029 2021 -8
- Partials 500 501 +1
☔ View full report in Codecov by Sentry. |
Very cool. Would it make sense to have some filecheck test cases? |
Eventually yes, but IMHO it will be something very brittle at this point due to the way we "hand out" available registers. |
That's the point. If it is brittle we want to see this exposed. In particular, the registers assigned should at least be deterministic. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall a step in a very good direction! Just some minor things I'd like to see cleaned up. If you need help feel free to drop by my office!
""" | ||
|
||
name = "riscv-allocate-registers" | ||
|
||
allocation_type: Union[str, None] = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouls suggest something like:
allocation_type: str = 'global-jregs'
Or whatever the magic constant is.
GlobalJRegs = 0 | ||
BlockNaive = 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't these have other values? I'm a bit frustated with typing enums in python, maybe a dictionary is better suited here? In a dict you can at least map names to one specific type. Python doesn't let you specify a type of the enum values...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by other values? Is this a tooling deficiency in typing or a general thing?
I cannot find a lot of evidence supporting this opinion online.
I thought the point of an enum was to represent a custom type.
Would using IntEnum
be better?
Moreover, I see that it is used elsewhere in our codebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful, thanks. No additional comments since I would have added the same remarks @AntonLydike pointed out already (especially having #994 merged first).
requirements-optional.txt
Outdated
@@ -8,6 +8,7 @@ nbval<0.11 | |||
filecheck<0.0.24 | |||
lit<17.0.0 | |||
pre-commit==3.3.2 | |||
git+https://github.com/antonlydike/riscemu.git@25d059da090760862f9143478524ed6daf0ab449#egg=riscemu |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't actually include this yet, or at least would need to do this in a separate PR where we can discuss the extra dependency separately. I don't think the register allocation requires this change, even if it's useful for debugging, so I'd recommend taking out all riscemu related changes for now.
from xdsl.dialects.builtin import ModuleOp | ||
from xdsl.dialects.riscv import Register, RegisterType, RISCVOp | ||
from xdsl.ir import MLContext | ||
from xdsl.passes import ModulePass | ||
|
||
|
||
class RegisterAllocator: | ||
class RegisterAllocatorStrategy: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
class RegisterAllocatorStrategy: | |
class AbstractRegisterAllocator(ABC): |
def apply(self, ctx: MLContext, op: ModuleOp) -> None: | ||
allocator = RegisterAllocator() | ||
allocator_strategies = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
""" | ||
|
||
name = "riscv-allocate-registers" | ||
|
||
allocation_type: str = "GlobalJRegs" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
allocation_type: str = "GlobalJRegs" | |
allocation_strategy: str = "GlobalJRegs" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! Would be good to iterate on the changes a bit, I left a few comments
169e3c2
to
fa25a79
Compare
@@ -57,16 +62,16 @@ def allocate_registers(self, module: ModuleOp) -> None: | |||
assert isinstance(result.typ, RegisterType) | |||
if result.typ.data.name is None: | |||
# If we run out of real registers, allocate a j register | |||
if block_registers == list(): | |||
if len(block_registers) == 0: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if len(block_registers) == 0: | |
if not block_registers: |
let's just assume that we have all the registers available for our use except the one explicitly reserved by the default riscv ABI. | ||
""" | ||
|
||
self.available_registers = OrderedDict(Register.ABI_INDEX_BY_NAME) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at it more in detail, it seems like I gave a bad recommendation here. Only reserved_registers
should be a set, the others were better the way you first wrote them, as lists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My b
7cf4648
to
053876a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks a lot better already, I think you still have some label PR changes in here. I'd suggest either rebasing or merging main into this branch so it contains only your changes.
tests/dialects/test_riscv.py
Outdated
def test_label_op_without_comment(): | ||
label_str = "mylabel" | ||
label_op = riscv.LabelOp(label_str) | ||
|
||
assert label_op.label.data == label_str | ||
|
||
code = riscv_code(ModuleOp([label_op])) | ||
assert code == f"{label_str}:\n" | ||
|
||
|
||
def test_label_op_with_comment(): | ||
label_str = "mylabel" | ||
label_op = riscv.LabelOp(f"{label_str}", comment="my label") | ||
|
||
assert label_op.label.data == label_str | ||
|
||
code = riscv_code(ModuleOp([label_op])) | ||
assert code == f"{label_str}: # my label\n" | ||
|
||
|
||
def test_label_op_with_region(): | ||
@Builder.implicit_region | ||
def label_region(): | ||
a1_reg = TestSSAValue(riscv.RegisterType(riscv.Registers.A1)) | ||
a2_reg = TestSSAValue(riscv.RegisterType(riscv.Registers.A2)) | ||
riscv.AddOp(a1_reg, a2_reg, rd=riscv.Registers.A0) | ||
|
||
label_str = "mylabel" | ||
label_op = riscv.LabelOp(f"{label_str}", region=label_region) | ||
|
||
assert label_op.label.data == label_str | ||
|
||
code = riscv_code(ModuleOp([label_op])) | ||
assert code == f"{label_str}:\n add a0, a1, a2\n" | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are still changes from the label op in this PR, or am I mistaken?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I'd suggest rebasing on the main
branch since that PR is now merged.
The current register allocator for the RISC-V dialect exclusively utilizes j-registers for unallocated values. However, @compor and I propose an improvement over the existing allocator by introducing a naive strategy that leverages the available real RISC-V registers for each block. In case the real registers are exhausted, the allocator gracefully falls back to j-registers.
This enhancement brings forth several important questions that need to be addressed at this stage:
Determining the Set of Available Registers: The choice of available registers depends on the ABI and the adopted calling convention. So, we probably want to determine a target for now and see how it involves other topics (like function handling see dialects: riscv-func add initial function call lowering #929).
Efficiency Requirements for Future Work: We might need to establish the desired level of efficiency for the register allocator (linear scan, ...) and the relevant use cases.
Moreover, this improvement is contingent on the merging of the LabelOp into the main branch. For further details regarding the LabelOp merge, please refer to the following pull request #994.