Skip to content

Conversation

@axelcool1234
Copy link
Contributor

@axelcool1234 axelcool1234 commented Apr 10, 2025

The funnel shift combiner rule from 4a3708c is currently missing from GlobalISel. The following is a port of that combiner to GlobalISel.

@axelcool1234
Copy link
Contributor Author

@mshockwave I finished that combiner port! Let me know what you think :)

@llvmbot
Copy link
Member

llvmbot commented Apr 10, 2025

@llvm/pr-subscribers-backend-risc-v
@llvm/pr-subscribers-backend-aarch64

@llvm/pr-subscribers-llvm-globalisel

Author: Axel Sorenson (axelcool1234)

Changes

The funnel shift combiner rule from 4a3708c is currently missing from GlobalISel. The following is a port of that combiner to GlobalISel.

If anyone has a better name for the tablegen functions (they're currently called funnel_shift_or_shift_to_funnel_shift_X), let me know so I can change it. I'm not very imaginative with my naming!


Full diff: https://github.com/llvm/llvm-project/pull/135132.diff

2 Files Affected:

  • (modified) llvm/include/llvm/Target/GlobalISel/Combine.td (+21-1)
  • (modified) llvm/test/CodeGen/RISCV/GlobalISel/shift.ll (+52)
diff --git a/llvm/include/llvm/Target/GlobalISel/Combine.td b/llvm/include/llvm/Target/GlobalISel/Combine.td
index 5309d5952f087..615617d4ca01b 100644
--- a/llvm/include/llvm/Target/GlobalISel/Combine.td
+++ b/llvm/include/llvm/Target/GlobalISel/Combine.td
@@ -1033,6 +1033,24 @@ def funnel_shift_overshift: GICombineRule<
   (apply [{ Helper.applyFunnelShiftConstantModulo(*${root}); }])
 >;
 
+// Transform: fshl x, ?, y | shl x, y -> fshl x, ?, y
+def funnel_shift_or_shift_to_funnel_shift_left: GICombineRule<
+  (defs root:$root), 
+  (match (G_FSHL $out1, $x, $_, $y),
+         (G_SHL $out2, $x, $y),
+         (G_OR $root, $out1, $out2)),
+  (apply (G_FSHL $root, $x, $_, $y))
+>;
+
+// Transform: fshr ?, x, y | srl x, y -> fshr ?, x, y
+def funnel_shift_or_shift_to_funnel_shift_right: GICombineRule<
+  (defs root:$root), 
+  (match (G_FSHR $out1, $_, $x, $y),
+         (G_LSHR $out2, $x, $y),
+         (G_OR $root, $out1, $out2)),
+  (apply (G_FSHR $root, $_, $x, $y))
+>;
+
 def rotate_out_of_range : GICombineRule<
   (defs root:$root),
   (match (wip_match_opcode G_ROTR, G_ROTL):$root,
@@ -1105,7 +1123,9 @@ def funnel_shift_combines : GICombineGroup<[funnel_shift_from_or_shift,
                                             funnel_shift_to_rotate,
                                             funnel_shift_right_zero,
                                             funnel_shift_left_zero,
-                                            funnel_shift_overshift]>;
+                                            funnel_shift_overshift,
+                                            funnel_shift_or_shift_to_funnel_shift_left,
+                                            funnel_shift_or_shift_to_funnel_shift_right]>;
 
 def bitfield_extract_from_sext_inreg : GICombineRule<
   (defs root:$root, build_fn_matchinfo:$info),
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/shift.ll b/llvm/test/CodeGen/RISCV/GlobalISel/shift.ll
index 75e318a58fd45..b52924cac0031 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/shift.ll
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/shift.ll
@@ -105,3 +105,55 @@ define i16 @test_shl_i48_2(i48 %x, i48 %y) {
   %trunc = trunc i48 %shl to i16
   ret i16 %trunc
 }
+
+define i16 @test_fshl_i32(i32 %x, i32 %_, i32 %y) {
+; RV32-LABEL: test_fshl_i32:
+; RV32:       # %bb.0:
+; RV32-NEXT:    not a3, a2
+; RV32-NEXT:    sll a0, a0, a2
+; RV32-NEXT:    srli a1, a1, 1
+; RV32-NEXT:    srl a1, a1, a3
+; RV32-NEXT:    or a0, a0, a1
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_fshl_i32:
+; RV64:       # %bb.0:
+; RV64-NEXT:    not a3, a2
+; RV64-NEXT:    sllw a0, a0, a2
+; RV64-NEXT:    srliw a1, a1, 1
+; RV64-NEXT:    srlw a1, a1, a3
+; RV64-NEXT:    or a0, a0, a1
+; RV64-NEXT:    ret
+
+    %fshl = call i32 @llvm.fshl.i32(i32 %x, i32 %_, i32 %y)
+    %shl = shl i32 %x, %y
+    %or = or i32 %fshl, %shl
+    %trunc = trunc i32 %or to i16
+    ret i16 %trunc
+}
+
+define i16 @test_fshr_i32(i32 %_, i32 %x, i32 %y) {
+; RV32-LABEL: test_fshr_i32:
+; RV32:       # %bb.0:
+; RV32-NEXT:    not a3, a2
+; RV32-NEXT:    slli a0, a0, 1
+; RV32-NEXT:    sll a0, a0, a3
+; RV32-NEXT:    srl a1, a1, a2
+; RV32-NEXT:    or a0, a0, a1
+; RV32-NEXT:    ret
+;
+; RV64-LABEL: test_fshr_i32:
+; RV64:       # %bb.0:
+; RV64-NEXT:    not a3, a2
+; RV64-NEXT:    slli a0, a0, 1
+; RV64-NEXT:    sllw a0, a0, a3
+; RV64-NEXT:    srlw a1, a1, a2
+; RV64-NEXT:    or a0, a0, a1
+; RV64-NEXT:    ret
+
+    %fshr = call i32 @llvm.fshr.i32(i32 %_, i32 %x, i32 %y)
+    %lshr = lshr i32 %x, %y
+    %or = or i32 %fshr, %lshr
+    %trunc = trunc i32 %or to i16
+    ret i16 %trunc
+}

@davemgreen
Copy link
Collaborator

davemgreen commented Apr 10, 2025

Can you regenerate llvm/test/CodeGen/AArch64/funnel-shift.ll too? It looks like it is failing in the precommit tests and needs an update.

@axelcool1234
Copy link
Contributor Author

axelcool1234 commented Apr 10, 2025

I updated the AArch64 test using update_llc_test_checks.py (it removed the redundant left shift and it being ORed with the left funnel shift).

Copy link
Collaborator

@davemgreen davemgreen left a comment

Choose a reason for hiding this comment

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

Thanks. The combine looks good - nice and clean - other than the points mentioned below.

We would not usually need to add combines that the midend can reliably perform unless they come up during lowering. I am not sure if that is expected in this case or not, from the legalization of higher sized types or from vectors.

@axelcool1234 axelcool1234 force-pushed the fsh-Combiner-Port branch 2 times, most recently from 17c74e0 to 733135e Compare April 12, 2025 07:19
Copy link
Member

@mshockwave mshockwave left a comment

Choose a reason for hiding this comment

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

Please refrain from force push unless it's necessary: https://www.llvm.org/docs/GitHub.html#rebasing-pull-requests-and-force-pushes.
Please append new commits instead, as they're all gonna be squash into one at the end.

@axelcool1234 axelcool1234 requested a review from mshockwave May 3, 2025 12:50
@cdevadas cdevadas changed the title [GISel] funnel shift combiner port from SelectionDAG ISel to GlobalISel [GISel] Funnel shift combiner port from SelectionDAG ISel to GlobalISel May 5, 2025
@jayfoad
Copy link
Contributor

jayfoad commented May 6, 2025

I don't see why the hasOneUse checks are needed, and the DAG version that you copied does not have them.

@axelcool1234
Copy link
Contributor Author

axelcool1234 commented May 6, 2025

I don't see why the hasOneUse checks are needed, and the DAG version that you copied does not have them.

I was told by @arsenm that it was a common combiner bug to lack the check. However, I do wonder:
@mshockwave You gave such a good explanation here #135132 (comment) about what's going on but I'm wondering if it's slightly incorrect now that we're applying a GIReplaceReg: In the combiner's current form, $root (which is the OR instruction) is replaced with the original FSHR instruction. If there are multiple uses of the LSHR instruction, would a hasOneUse check for that actually change anything? When LSHR has only one use, the replacement of the OR instruction leads to it having zero uses and not being emitted. If the LSHR has several uses, I suspect it'll be emitted as usual whether or not we have this hasOneUse check here since the combiner isn't removing it anymore (since it's simply replacing the OR instruction with the original FSHR instruction), right? Is my assessment of this correct?

I think my confusion stems from what happens when we do (apply (G_FSHR $root, $z, $x, $y)) vs (apply (GIReplaceReg $root, $out1)). This is how I believe it works:

  • The first apply I mentioned removes all of the instructions that are matched with the resulting instruction in the apply
  • The second apply I mentioned replaces only the specified instruction, not everything that's matched

If that's correct, then I think @jayfoad might be right: if the LSHR instruction lacks other users, it'll never be emitted anyways (and it will be emitted if it does have users). Thus, we don't need the hasOneUse check.

@jayfoad
Copy link
Contributor

jayfoad commented May 6, 2025

I think my confusion stems from what happens when we do (apply (G_FSHR $root, $z, $x, $y)) vs (apply (GIReplaceReg $root, $out1)). This is how I believe it works:

  • The first apply I mentioned removes all of the instructions that are matched with the resulting instruction in the apply
  • The second apply I mentioned replaces only the specified instruction, not everything that's matched

The combine doesn't really remove the original instructions, it just replaces all uses of the original root instruction. Then it's up to the normal dead code elimination to remove the original instructions if they are unused. The point of a hasOneUse check is to make sure that one of the sub-instructions in the pattern will be unused, because it doesn't have any other uses outside of the pattern we matched.

Typically for a cmobine that creates a new instruction, you would use hasOneUse checks, because you don't want to create a new instruction unless you can remove one or more of the existing instructions. But this combine does not create any new instructions, so I don't think there's any particular need for tha hasOneUse check.

@axelcool1234
Copy link
Contributor Author

Now that the hasOneUse checks were determined to not be required, the original lshr and shl tests, which were converted into negative multi-use tests for funnel shifts, are no longer negative multi-use tests (they now trigger despite multiple uses of the intermediate instructions). Because of this, perhaps it'd be best to change these tests back to what they were before? I still have two new tests I've introduced that test fshl and fshr.

Additionally, this file lacks vector versions of any of the shift tests. I'll look into introducing them.

@axelcool1234
Copy link
Contributor Author

axelcool1234 commented May 20, 2025

Additionally, this file lacks vector versions of any of the shift tests. I'll look into introducing them.

According to the docs, RISC-V supports vectory types: https://llvm.org/docs/RISCV/RISCVVectorExtension.html. However, every time I try to write a trivial function test with a vector I get an error stating that RISC-V is unable to "lower" the arguments. I think the reason I can't use them has to do with the test running a version of RISC-V without the vector extension enabled. What should I change the RUN commands to in order to test fshl and fshr with vectors?

@mshockwave
Copy link
Member

Additionally, this file lacks vector versions of any of the shift tests. I'll look into introducing them.

According to the docs, RISC-V supports vectory types: https://llvm.org/docs/RISCV/RISCVVectorExtension.html. However, every time I try to write a trivial function test with a vector I get an error stating that RISC-V is unable to "lower" the arguments. I think the reason I can't use them has to do with the test running a version of RISC-V without the vector extension enabled. What should I change the RUN commands to in order to test fshl and fshr with vectors?

To enable vectors, you can llc -mattr=+v ...

@axelcool1234
Copy link
Contributor Author

axelcool1234 commented May 24, 2025

@mshockwave So I've been exposed to scalable vectors for the first time - I've been exploring how they work in RISC-V (its very interesting). Please tell me if I'm mistaken, but it seems GlobalISel only supports scalable vectors (and not fixed vectors) for arguments to functions. Despite this, it seems most scalable vector instructions are not supported in GlobalISel. All of my fiddling around with LLVM IR is getting me LLVM ERROR: unable to translate instruction: call and LLVM ERROR: unable to lower arguments: errors. Investigation online leads me to believe that my observations are correct: LLVM lacks a lot of scalable vector instruction support. This talk from 2023 also says that GlobalISel lacks scalable vector support.

If this is all true, how should I construct these vector tests? Since I can't seem to pass fixed length vectors as arguments, I'm forced to write them in the function body (but this leads to the compiler constant folding it all down since it statically knows the content of said fixed vectors). Unless there's a better way, perhaps I should look into loading the vector from a pointer or something?

Admittedly, I feel I haven't done enough digging or deeper reading into this (beyond the language reference and this very useful article, combined with your blog post on this). If you have any resources I should really sink my teeth into, I will do so. Thanks again!

@axelcool1234
Copy link
Contributor Author

axelcool1234 commented Aug 14, 2025

@mshockwave it's been a while (I personally got busy), but I'm still interested in getting this merged. It's basically ready (beyond needing to rebase to the most recent commits of course), I just need some input on the vector tests another reviewer mentioned. The original SelectionDAG ISel version of this didn't have any, and the RISCV backend of GlobalISel is making it tricky to write vector tests. Do I even need them here? If so, can you read my previous comment and let me know how I should proceed? I want to thank you again for taking the time to help me get involved in the LLVM codebase.

@mshockwave
Copy link
Member

@mshockwave it's been a while (I personally got busy), but I'm still interested in getting this merged. It's basically ready (beyond needing to rebase to the most recent commits of course), I just need some input on the vector tests another reviewer mentioned. The original SelectionDAG ISel version of this didn't have any, and the RISCV backend of GlobalISel is making it tricky to write vector tests. Do I even need them here? If so, can you read my previous comment and let me know how I should proceed? I want to thank you again for taking the time to help me get involved in the LLVM codebase.

Oh nice! I'm glad you're still keen to work on this. Sorry I saw your previous comment but I was pretty occupied back then.
GISel's support on scalable vectors is indeed not as robust as its fixed vector counterpart. And RISCV is only able to translate a small set of instructions with scalable vectors. Switching to fixed vectors does help a lot either, because there appears to be some lacking legalizations on instructions with fixed vectors in RISCV's GISel backend.

So personally I think you could go ahead with only the scalar tests. Please rebase your PR and I'll give another round of review tomorrow morning.

Thanks again for pushing this forward!

@axelcool1234
Copy link
Contributor Author

@mshockwave it's been a while (I personally got busy), but I'm still interested in getting this merged. It's basically ready (beyond needing to rebase to the most recent commits of course), I just need some input on the vector tests another reviewer mentioned. The original SelectionDAG ISel version of this didn't have any, and the RISCV backend of GlobalISel is making it tricky to write vector tests. Do I even need them here? If so, can you read my previous comment and let me know how I should proceed? I want to thank you again for taking the time to help me get involved in the LLVM codebase.

Oh nice! I'm glad you're still keen to work on this. Sorry I saw your previous comment but I was pretty occupied back then.
GISel's support on scalable vectors is indeed not as robust as its fixed vector counterpart. And RISCV is only able to translate a small set of instructions with scalable vectors. Switching to fixed vectors does help a lot either, because there appears to be some lacking legalizations on instructions with fixed vectors in RISCV's GISel backend.

So personally I think you could go ahead with only the scalar tests. Please rebase your PR and I'll give another round of review tomorrow morning.

Thanks again for pushing this forward!

Awesome, I'll make sure to rebase tomorrow. I'd like to email you too about continuing my contributions to LLVM (i.e. what I should explore/do next) under your guidance - I know there's been a 3 month gap but I'd love to keep going if you're up for it.

@mshockwave
Copy link
Member

@mshockwave it's been a while (I personally got busy), but I'm still interested in getting this merged. It's basically ready (beyond needing to rebase to the most recent commits of course), I just need some input on the vector tests another reviewer mentioned. The original SelectionDAG ISel version of this didn't have any, and the RISCV backend of GlobalISel is making it tricky to write vector tests. Do I even need them here? If so, can you read my previous comment and let me know how I should proceed? I want to thank you again for taking the time to help me get involved in the LLVM codebase.

Oh nice! I'm glad you're still keen to work on this. Sorry I saw your previous comment but I was pretty occupied back then.
GISel's support on scalable vectors is indeed not as robust as its fixed vector counterpart. And RISCV is only able to translate a small set of instructions with scalable vectors. Switching to fixed vectors does help a lot either, because there appears to be some lacking legalizations on instructions with fixed vectors in RISCV's GISel backend.
So personally I think you could go ahead with only the scalar tests. Please rebase your PR and I'll give another round of review tomorrow morning.
Thanks again for pushing this forward!

Awesome, I'll make sure to rebase tomorrow. I'd like to email you too about continuing my contributions to LLVM (i.e. what I should explore/do next) under your guidance - I know there's been a 3 month gap but I'd love to keep going if you're up for it.

yeah absolutely

Copy link
Member

@mshockwave mshockwave left a comment

Choose a reason for hiding this comment

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

LGTM, cheers!

The funnel shift combiner rule from 4a3708c is currently missing from
GlobalISel. The following is a port of that combiner to GlobalISel.
@axelcool1234
Copy link
Contributor Author

@mshockwave I have made a pre-commit test and a post-commit with the PR's title and description. Let me know if this is correct! And if so, then this can be merged :)

@mshockwave mshockwave merged commit c249a9a into llvm:main Aug 28, 2025
9 checks passed
@mshockwave
Copy link
Member

@mshockwave I have made a pre-commit test and a post-commit with the PR's title and description. Let me know if this is correct! And if so, then this can be merged :)

It's merged now. For future reference: you don't have to squash commits manually because Github would do that for you upon merging ("Squash and Merge" is the only option for LLVM at this moment). The pre-commit test system is also just for reviewing purposes (reviewers could navigate between different commits to see the difference), you don't really need to split it out for merging as they'll all gonna squashed into one at the end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants