Skip to content

Conversation

rj-jesus
Copy link
Contributor

@rj-jesus rj-jesus commented Jul 16, 2025

When clearing register kills, ensure we do so inside a bundle too. This came up in AArch64LdStOpt when we attempt to clear kill flags that affect the first store's register. If the kill happens in a bundle, we were only clearing it in the bundle operands, but not in the constituent instructions. This was leading to a verification error, for example: https://godbolt.org/z/WjhEWGcPW.

Fixes #149092.

@llvmbot
Copy link
Member

llvmbot commented Jul 16, 2025

@llvm/pr-subscribers-backend-aarch64

Author: Ricardo Jesus (rj-jesus)

Changes

When clearing register kills, ensure we do so inside a bundle too. This came up in AArch64LdStOpt where we attempt to clear kill flags that affect the first store's register. If the kill happens in a bundle, we were only clearing it in the bundle operands, but not in the constituent instructions. This was leading to a verification error, for example: https://godbolt.org/z/WjhEWGcPW.

Fixes #149092.


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

2 Files Affected:

  • (modified) llvm/lib/CodeGen/MachineInstr.cpp (+4-4)
  • (modified) llvm/test/CodeGen/AArch64/sve-vls-ldst-opt.mir (+20)
diff --git a/llvm/lib/CodeGen/MachineInstr.cpp b/llvm/lib/CodeGen/MachineInstr.cpp
index da3665b3b6a0b..a966add78a97e 100644
--- a/llvm/lib/CodeGen/MachineInstr.cpp
+++ b/llvm/lib/CodeGen/MachineInstr.cpp
@@ -2177,12 +2177,12 @@ void MachineInstr::clearRegisterKills(Register Reg,
                                       const TargetRegisterInfo *RegInfo) {
   if (!Reg.isPhysical())
     RegInfo = nullptr;
-  for (MachineOperand &MO : operands()) {
-    if (!MO.isReg() || !MO.isUse() || !MO.isKill())
+  for (MIBundleOperands O(*this); O.isValid(); ++O) {
+    if (!O->isReg() || !O->isUse() || !O->isKill())
       continue;
-    Register OpReg = MO.getReg();
+    Register OpReg = O->getReg();
     if ((RegInfo && RegInfo->regsOverlap(Reg, OpReg)) || Reg == OpReg)
-      MO.setIsKill(false);
+      O->setIsKill(false);
   }
 }
 
diff --git a/llvm/test/CodeGen/AArch64/sve-vls-ldst-opt.mir b/llvm/test/CodeGen/AArch64/sve-vls-ldst-opt.mir
index 49453bc178914..28c5220642ec5 100644
--- a/llvm/test/CodeGen/AArch64/sve-vls-ldst-opt.mir
+++ b/llvm/test/CodeGen/AArch64/sve-vls-ldst-opt.mir
@@ -72,3 +72,23 @@ body: |
 # CHECK: STURQi killed renamable $q1, renamable $x1, 16 :: (store (s128))
 # CHECK: STURQi killed renamable $q2, renamable $x1, 48 :: (store (s128))
 # CHECK: STR_ZXI killed renamable $z3, renamable $x1, 4 :: (store (<vscale x 1 x s128>))
+---
+name: clear-kill-in-bundle
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $x0, $z0
+
+    STR_ZXI $z0, $x0, 0 :: (store (<vscale x 1 x s128>))
+    BUNDLE implicit-def $z1, implicit-def $q1, implicit killed $z0 {
+      $z1 = ADD_ZZZ_D $z0, killed $z0
+    }
+    STR_ZXI renamable $z1, $x0, 1 :: (store (<vscale x 1 x s128>))
+
+    RET_ReallyLR
+...
+# CHECK-LABEL: name: clear-kill-in-bundle
+# CHECK: BUNDLE implicit-def $z1, implicit-def $q1, implicit $z0 {
+# CHECK:   $z1 = ADD_ZZZ_D $z0, $z0
+# CHECK: }
+# CHECK: STPQi $q0, $q1, $x0, 0 :: (store (<vscale x 1 x s128>))

Copy link

⚠️ We detected that you are using a GitHub private e-mail address to contribute to the repo.
Please turn off Keep my email addresses private setting in your account.
See LLVM Developer Policy and LLVM Discourse for more information.

@AZero13
Copy link
Contributor

AZero13 commented Jul 16, 2025

Wonder if the other functions should also operate on bundles or if just this one needs to

@rj-jesus rj-jesus requested a review from sjoerdmeijer July 17, 2025 07:24
@davemgreen
Copy link
Collaborator

Ah I see this was only tangentially related to #134068 - it just made it mode likely to trigger.

I fixed #146415 recently for a similar issue, but there it used a different iterator type to make sure the instruction in the bundle were also visited, not skipped over.

Perhaps @arsenm knows if this sounds OK for the generic MachineInstr::clearRegisterKills? It seems sensible to me but we could just try and make sure the the AArch64LoadStore optimizer visited the instruction in the bundle too.

@rj-jesus
Copy link
Contributor Author

Yeah, I think so, due to the MOVPRFX bundles.

Would you rather I move this over to AArch64LoadStoreOpt (perhaps with a different iterator for the instructions)? It looks like it might be helpful in AArch64LoadStoreOpt::promoteLoadFromStore too, although we might need a contrived and probably unrealistic example to trigger it (presumably a store's register could be killed in a bundle before a promotable load, but I don't think we'd generate such code?).

@rj-jesus
Copy link
Contributor Author

Wonder if the other functions should also operate on bundles or if just this one needs to

I'm not sure, bundle iterators are already used elsewhere (for example in LiveRegUnits::accumulateUsedDefed), but it might be that there are a few places where they are missed or where different strategies are used (for example as @davemgreen suggested).

Copy link
Contributor

@arsenm arsenm left a comment

Choose a reason for hiding this comment

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

For the various MachineInstr property checks, there is the QueryType for how to handle the bundle, which all default to AnyInBundle. The more sophisticated checks seem to have never been updated to do anything with bundle. I guess doing this in all of these type functions is the most internally consistent way to do it

@rj-jesus
Copy link
Contributor Author

rj-jesus commented Jul 18, 2025

Hi @arsenm, when you write:

I guess doing this in all of these type functions (...)

By this, are you referring to updating "the more sophisticated checks (that) seem to have never been updated to do anything with bundle", or are you referring to adding something like AnyInBundle to clearRegisterKills, and potentially other such functions, to control how they handle a bundle? (I'm sorry if this is very obvious, but I just got confused interpreting the path you're suggesting.)

@arsenm
Copy link
Contributor

arsenm commented Jul 18, 2025

By this, are you referring to updating "the more sophisticated checks (that) seem to have never been updated to do anything with bundle", or are you referring to adding something like AnyInBundle to clearRegisterKills, and potentially other such functions, to control how they handle a bundle? (I'm sorry if this is very obvious, but I just got confused interpreting the path you're suggesting.)

Yes, something like that. All of these register based functions, e.g. substituteRegister, clearRegisterKills, addRegisterDead and so on. They possibly should have a bundle query, like the predicate functions.

@rj-jesus
Copy link
Contributor Author

Yes, something like that. All of these register based functions, e.g. substituteRegister, clearRegisterKills, addRegisterDead and so on. They possibly should have a bundle query, like the predicate functions.

Thanks very much, that sounds good to me. In that case, if you think this is a reasonable approach, I'll update this PR as you're suggesting for clearRegisterKills so that we can progress the linked issue, and create a separate PR to extend this to the remaining functions. Does this sound okay to you?

@arsenm
Copy link
Contributor

arsenm commented Jul 18, 2025

Yes

IgnoreBundle, // Ignore bundles
AnyInBundle, // Return true if any instruction in bundle has property
AllInBundle // Return true if all instructions in bundle have property
IgnoreBundle, // Ignore bundles
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These formatting changes were made to avoid code formatter errors.

@@ -1700,7 +1700,8 @@ class MachineInstr
/// Clear all kill flags affecting Reg. If RegInfo is provided, this includes
/// all aliasing registers.
LLVM_ABI void clearRegisterKills(Register Reg,
const TargetRegisterInfo *RegInfo);
const TargetRegisterInfo *RegInfo,
QueryType Type = AllInBundle);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@arsenm For now I've reused the pre-existing QueryType. If you think it would be better to define a separate type for updates, please let me know.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should the default be IgnoreBundle to match the previous behaviour? (And maybe that would help for compile-time, I'm not sure how much it makes a difference)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If we go that route, would we then override the default in AArch64LoadStoreOptimizer?

I'm happy with that as it solves the direct issue in #149092, but I would assume that not clearing kills in bundles could be an issue elsewhere.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If the code calling clearRegisterKills already iterates over all instructions they might be fine already. It's hard to tell from the code which is which, but I think the Arm uses are probably OK (there are many more bundles used there, they are rarer on AArch64 before movprfx).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good, thanks for the suggestion. I'll open a separate PR to iterate through all instructions in AArch64LoadStoreOptimizer instead.

@rj-jesus
Copy link
Contributor Author

Ping

@@ -1700,7 +1700,8 @@ class MachineInstr
/// Clear all kill flags affecting Reg. If RegInfo is provided, this includes
/// all aliasing registers.
LLVM_ABI void clearRegisterKills(Register Reg,
const TargetRegisterInfo *RegInfo);
const TargetRegisterInfo *RegInfo,
QueryType Type = AllInBundle);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should the default be IgnoreBundle to match the previous behaviour? (And maybe that would help for compile-time, I'm not sure how much it makes a difference)

rj-jesus added a commit that referenced this pull request Aug 12, 2025
As an alternative to #149177, iterate through all instructions in
`AArch64LoadStoreOptimizer`.
@rj-jesus
Copy link
Contributor Author

I'm closing this PR since the original motivating issue was instead fixed via #152994.

@rj-jesus rj-jesus closed this Aug 13, 2025
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.

[AArch64] STPQi: Bad machine code: Using an undefined physical register
5 participants