Skip to content
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

Merged
merged 29 commits into from Jun 1, 2023

Conversation

adutilleul
Copy link
Collaborator

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.

@codecov
Copy link

codecov bot commented May 24, 2023

Codecov Report

Patch coverage: 97.22% and project coverage change: +0.10 🎉

Comparison is base (4530299) 86.98% compared to head (b3fff0a) 87.09%.

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     
Impacted Files Coverage Δ
xdsl/transforms/riscv_register_allocation.py 94.44% <91.17%> (-5.56%) ⬇️
docs/Toy/toy/tests/test_regalloc.py 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@tobiasgrosser
Copy link
Contributor

Very cool. Would it make sense to have some filecheck test cases?

@compor
Copy link
Collaborator

compor commented May 24, 2023

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.
So, not really sure.

@adutilleul adutilleul added the dialects Changes on the dialects label May 24, 2023
@tobiasgrosser
Copy link
Contributor

tobiasgrosser commented May 24, 2023

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. So, not really sure.

That's the point. If it is brittle we want to see this exposed. In particular, the registers assigned should at least be deterministic.

Copy link
Collaborator

@AntonLydike AntonLydike left a 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!

xdsl/transforms/riscv_register_allocation.py Outdated Show resolved Hide resolved
"""

name = "riscv-allocate-registers"

allocation_type: Union[str, None] = None
Copy link
Collaborator

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.

Comment on lines 98 to 99
GlobalJRegs = 0
BlockNaive = 1
Copy link
Collaborator

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...

Copy link
Collaborator

@compor compor May 24, 2023

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.

xdsl/riscv_asm_writer.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@nazavode nazavode left a 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).

@superlopuh superlopuh changed the base branch from main to christos/riscv/labelop May 25, 2023 12:54
@@ -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
Copy link
Member

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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class RegisterAllocatorStrategy:
class AbstractRegisterAllocator(ABC):

def apply(self, ctx: MLContext, op: ModuleOp) -> None:
allocator = RegisterAllocator()
allocator_strategies = {
Copy link
Member

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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
allocation_type: str = "GlobalJRegs"
allocation_strategy: str = "GlobalJRegs"

Copy link
Member

@superlopuh superlopuh left a 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

@@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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)
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My b

Copy link
Member

@superlopuh superlopuh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you!

Base automatically changed from christos/riscv/labelop to main May 29, 2023 12:11
Copy link
Collaborator

@AntonLydike AntonLydike left a 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.

Comment on lines 93 to 128
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"


Copy link
Collaborator

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?

Copy link
Collaborator

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.

@adutilleul adutilleul merged commit e1e8e2d into main Jun 1, 2023
12 checks passed
@adutilleul adutilleul deleted the adutilleul/riscv/regalloc branch June 1, 2023 08:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dialects Changes on the dialects
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants