[llvm] [GlobalISel][TableGen] Adds GIReplaceRegWithUndef MIR builtin pattern. (PR #219410)
Vikash Gupta via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 28 00:55:35 PDT 2026
https://github.com/vg0204 created https://github.com/llvm/llvm-project/pull/219410
Following up on the original MIR pattern builtins (`GIReplaceReg`, `GIEraseRoot`) from 844c0da / D158975, this extends the declarative apply-side vocabulary so more combines can drop their hand-written C++.
`GIReplaceRegWithUndef` replaces a register defined by the match root with a fresh `G_IMPLICIT_DEF` of the same type — the declarative form of `Helper.replaceInstWithUndef`:
The following existing tests are used fro demonstration:
- `unary_undef_to_undef`: C++ apply replaced with `(GIReplaceRegWithUndef $dst)`.
- `erase_undef_store`: converted to a precise `G_STORE` root + the existing `(GIEraseRoot)`, dropping a `wip_match_opcode` and its C++ apply.
>From c2bc2cf5a1e9287b9522de74cbe727d2927a4925 Mon Sep 17 00:00:00 2001
From: vg0204 <Vikash.Gupta at amd.com>
Date: Fri, 28 Aug 2026 13:21:12 +0530
Subject: [PATCH] [GlobalISel][TableGen] Adds GIReplaceRegWithUndef MIR builtin
pattern.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Following up on the original MIR pattern builtins (`GIReplaceReg`,
`GIEraseRoot`) from 844c0da / D158975, this extends the declarative
apply-side vocabulary so more combines can drop their hand-written C++.
`GIReplaceRegWithUndef` replaces a register defined by the match root
with a fresh `G_IMPLICIT_DEF` of the same type — the declarative form of
`Helper.replaceInstWithUndef`:
The following existing tests are used fro demonstration:
- `unary_undef_to_undef`: C++ apply replaced with
`(GIReplaceRegWithUndef $dst)`.
- `erase_undef_store`: converted to a precise `G_STORE` root + the
existing
`(GIEraseRoot)`, dropping a `wip_match_opcode` and its C++ apply.
---
llvm/docs/GlobalISel/MIRPatterns.md | 30 +++++++++++++
.../include/llvm/Target/GlobalISel/Combine.td | 20 +++++++--
.../builtins/builtin-pattern-errors.td | 24 ++++++++++-
.../builtins/builtin-pattern-parrsing.td | 24 ++++++++++-
.../builtins/match-table-replacereg-undef.td | 39 +++++++++++++++++
.../TableGen/Common/GlobalISel/Patterns.h | 4 +-
.../TableGen/GlobalISelCombinerEmitter.cpp | 42 +++++++++++++++++++
7 files changed, 177 insertions(+), 6 deletions(-)
create mode 100644 llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/match-table-replacereg-undef.td
diff --git a/llvm/docs/GlobalISel/MIRPatterns.md b/llvm/docs/GlobalISel/MIRPatterns.md
index 8253edf463562..56f7b9a2f475c 100644
--- a/llvm/docs/GlobalISel/MIRPatterns.md
+++ b/llvm/docs/GlobalISel/MIRPatterns.md
@@ -211,6 +211,25 @@ Semantics:
- If both old/new are operands of matched instructions,
`canReplaceReg` is checked before applying the rule.
+#### GIReplaceRegWithUndef
+
+```{code-block} text
+:caption: Usage
+
+(apply (GIReplaceRegWithUndef $reg))
+```
+
+Operands:
+
+- `$reg` (out) register defined by the match root
+
+Semantics:
+
+- Can only appear in an 'apply' pattern.
+- `$reg` must be a register defined by the match root.
+- Replaces all uses of `$reg` with a fresh `G_IMPLICIT_DEF` of the same type,
+ then erases the root.
+
#### GIEraseRoot
```{code-block} text
@@ -423,6 +442,17 @@ def ReplaceTemp : GICombineRule<
(GIReplaceReg $b, $new))>
```
+To instead replace a register defined by the match root with an undefined value,
+use the `GIReplaceRegWithUndef` builtin. It creates a `G_IMPLICIT_DEF` of the same
+type and replaces the register with it, without any C++.
+
+```text
+def Foo : GICombineRule<
+ (defs root:$dst),
+ (match (G_FNEG $dst, $src)),
+ (apply (GIReplaceRegWithUndef $dst))>;
+```
+
#### Common Pattern #2: Erasing a Def-less Root
If we simply want to erase a def-less match root, we can use the
diff --git a/llvm/include/llvm/Target/GlobalISel/Combine.td b/llvm/include/llvm/Target/GlobalISel/Combine.td
index e0f665e85c2cc..975f3746d9a0e 100644
--- a/llvm/include/llvm/Target/GlobalISel/Combine.td
+++ b/llvm/include/llvm/Target/GlobalISel/Combine.td
@@ -162,6 +162,20 @@ class GIBuiltinInst;
// "canReplaceReg" is checked before applying the rule.
def GIReplaceReg : GIBuiltinInst;
+// Replace a register defined by the match root with a fresh G_IMPLICIT_DEF of
+// the same type.
+//
+// Usage:
+// (apply (GIReplaceRegWithUndef $reg))
+//
+// Operands:
+// - $reg (out) register defined by the match root
+//
+// Semantics:
+// - Can only appear in an 'apply' pattern.
+// - $reg must be a register defined by the match root.
+def GIReplaceRegWithUndef : GIBuiltinInst;
+
// Apply action that erases the match root.
//
// Usage:
@@ -516,7 +530,7 @@ def unary_undef_to_undef_frags : GICombinePatFrag<
def unary_undef_to_undef : GICombineRule<
(defs root:$dst),
(match (unary_undef_to_undef_frags $dst)),
- (apply [{ Helper.replaceInstWithUndef(*${dst}.getParent()); }])>;
+ (apply (GIReplaceRegWithUndef $dst))>;
// Instructions where if any source operand is undef, the instruction can be
// replaced with undef.
@@ -783,9 +797,9 @@ def binop_right_to_zero: GICombineRule<
// Erase stores of undef values.
def erase_undef_store : GICombineRule<
(defs root:$root),
- (match (wip_match_opcode G_STORE):$root,
+ (match (G_STORE $val, $ptr):$root,
[{ return Helper.matchUndefStore(*${root}); }]),
- (apply [{ Helper.eraseInst(*${root}); }])
+ (apply (GIEraseRoot))
>;
def simplify_add_to_sub_matchinfo: GIDefMatchData<"std::tuple<Register, Register>">;
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/builtin-pattern-errors.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/builtin-pattern-errors.td
index 8e3e27daaa5bf..96d36eb120d08 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/builtin-pattern-errors.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/builtin-pattern-errors.td
@@ -78,6 +78,25 @@ def replacereg_nonroot : GICombineRule<
(match (COPY $dst, $tmp), (COPY $tmp, $src)),
(apply (GIReplaceReg $dst, $src), (GIReplaceReg $tmp, $src))>;
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: 'GIReplaceRegWithUndef' cannot be used in a 'match' pattern
+def replaceregundef_in_match : GICombineRule<
+ (defs root:$dst),
+ (match (GIReplaceRegWithUndef $dst)),
+ (apply (COPY $dst, $src))>;
+
+// CHECK: :[[@LINE+2]]:{{[0-9]+}}: error: 'GIReplaceRegWithUndef' expected 1 operands, got 2
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: Failed to parse pattern: '(GIReplaceRegWithUndef ?:$dst, ?:$src)'
+def replaceregundef_ops : GICombineRule<
+ (defs root:$dst),
+ (match (COPY $dst, $src)),
+ (apply (GIReplaceRegWithUndef $dst, $src))>;
+
+// CHECK: :[[@LINE+1]]:{{[0-9]+}}: error: GIReplaceRegWithUndef cannot replace 'tmp': this builtin can only replace a register defined by the match root
+def replaceregundef_nonroot : GICombineRule<
+ (defs root:$dst),
+ (match (COPY $dst, $tmp), (COPY $tmp, $src)),
+ (apply (GIReplaceReg $dst, $src), (GIReplaceRegWithUndef $tmp))>;
+
// CHECK: error: Failed to parse one or more rules
def MyCombiner: GICombiner<"GenMyCombiner", [
@@ -90,5 +109,8 @@ def MyCombiner: GICombiner<"GenMyCombiner", [
eraseroot_notinstmatch,
replacereg_in_match,
replacereg_ops,
- replacereg_nonroot
+ replacereg_nonroot,
+ replaceregundef_in_match,
+ replaceregundef_ops,
+ replaceregundef_nonroot
]>;
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/builtin-pattern-parrsing.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/builtin-pattern-parrsing.td
index 9c839ee48d011..f42933bd8d6be 100644
--- a/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/builtin-pattern-parrsing.td
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/builtin-pattern-parrsing.td
@@ -49,7 +49,29 @@ def BuiltinTest1 : GICombineRule<
(apply (GIEraseRoot))
>;
+// CHECK: (CombineRule name:BuiltinTest2 id:2 root:a
+// CHECK-NEXT: (MatchPats
+// CHECK-NEXT: <match_root>__BuiltinTest2_match_0:(CodeGenInstructionPattern G_TRUNC operands:[<def>$a, $b])
+// CHECK-NEXT: )
+// CHECK-NEXT: (ApplyPats
+// CHECK-NEXT: <apply_root>__BuiltinTest2_apply_0:(BuiltinPattern GIReplaceRegWithUndef operands:[<def>$a])
+// CHECK-NEXT: )
+// CHECK-NEXT: (OperandTable MatchPats
+// CHECK-NEXT: a -> __BuiltinTest2_match_0
+// CHECK-NEXT: b -> <live-in>
+// CHECK-NEXT: )
+// CHECK-NEXT: (OperandTable ApplyPats
+// CHECK-NEXT: a -> __BuiltinTest2_apply_0
+// CHECK-NEXT: )
+// CHECK-NEXT: )
+def BuiltinTest2 : GICombineRule<
+ (defs root:$a),
+ (match (G_TRUNC $a, $b)),
+ (apply (GIReplaceRegWithUndef $a))
+>;
+
def MyCombiner: GICombiner<"GenMyCombiner", [
BuiltinTest0,
- BuiltinTest1
+ BuiltinTest1,
+ BuiltinTest2
]>;
diff --git a/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/match-table-replacereg-undef.td b/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/match-table-replacereg-undef.td
new file mode 100644
index 0000000000000..131486b330bd2
--- /dev/null
+++ b/llvm/test/TableGen/GlobalISelCombinerEmitter/builtins/match-table-replacereg-undef.td
@@ -0,0 +1,39 @@
+// RUN: llvm-tblgen -I %p/../../../../include -gen-global-isel-combiner \
+// RUN: -combiners=MyCombiner %s | \
+// RUN: FileCheck %s
+
+include "llvm/Target/Target.td"
+include "llvm/Target/GlobalISel/Combine.td"
+
+def MyTargetISA : InstrInfo;
+def MyTarget : Target { let InstructionSet = MyTargetISA; }
+
+def ReplaceWithUndef : GICombineRule<
+ (defs root:$dst),
+ (match (G_FNEG $dst, $src)),
+ (apply (GIReplaceRegWithUndef $dst))>;
+
+def MyCombiner: GICombiner<"GenMyCombiner", [
+ ReplaceWithUndef
+]>;
+
+// CHECK: const uint8_t *GenMyCombiner::getMatchTable() const {
+// CHECK-NEXT: constexpr static uint8_t MatchTable0[] = {
+// CHECK-NEXT: GIM_Try, /*On fail goto*//*Label 0*/ GIMT_Encode4([[L32:[0-9]+]]), // Rule ID 0 //
+// CHECK-NEXT: GIM_CheckSimplePredicate, GIMT_Encode2(GICXXPred_Simple_IsRule0Enabled),
+// CHECK-NEXT: GIM_CheckOpcode, /*MI*/0, GIMT_Encode2(TargetOpcode::G_FNEG),
+// CHECK-NEXT: // MIs[0] dst
+// CHECK-NEXT: GIM_RecordRegType, /*MI*/0, /*Op*/0, /*TempTypeIdx*/255,
+// CHECK-NEXT: // MIs[0] src
+// CHECK-NEXT: // No operand predicates
+// CHECK-NEXT: GIR_MakeTempReg, /*TempRegID*/0, /*TypeID*/255,
+// CHECK-NEXT: // Combiner Rule #0: ReplaceWithUndef
+// CHECK-NEXT: GIR_BuildRootMI, /*Opcode*/GIMT_Encode2(TargetOpcode::G_IMPLICIT_DEF),
+// CHECK-NEXT: GIR_AddTempRegister, /*InsnID*/0, /*TempRegID*/0, /*TempRegFlags*/GIMT_Encode2(static_cast<uint16_t>(RegState::Define)),
+// CHECK-NEXT: GIR_ReplaceRegWithTempReg, /*OldInsnID*/0, /*OldOpIdx*/0, /*TempRegID*/0,
+// CHECK-NEXT: GIR_EraseRootFromParent_Done,
+// CHECK-NEXT: // Label 0: @[[L32]]
+// CHECK-NEXT: GIM_Reject,
+// CHECK-NEXT: }; // Size: {{[0-9]+}} bytes
+// CHECK-NEXT: return MatchTable0;
+// CHECK-NEXT: }
diff --git a/llvm/utils/TableGen/Common/GlobalISel/Patterns.h b/llvm/utils/TableGen/Common/GlobalISel/Patterns.h
index 2bf2a22ee9998..a60f77ce1d07b 100644
--- a/llvm/utils/TableGen/Common/GlobalISel/Patterns.h
+++ b/llvm/utils/TableGen/Common/GlobalISel/Patterns.h
@@ -691,6 +691,7 @@ class PatFragPattern : public InstructionPattern {
/// Represents builtin instructions such as "GIReplaceReg" and "GIEraseRoot".
enum BuiltinKind {
BI_ReplaceReg,
+ BI_ReplaceRegWithUndef,
BI_EraseRoot,
};
@@ -702,8 +703,9 @@ class BuiltinPattern : public InstructionPattern {
unsigned NumDefs;
};
- static constexpr std::array<BuiltinInfo, 2> KnownBuiltins = {{
+ static constexpr std::array<BuiltinInfo, 3> KnownBuiltins = {{
{"GIReplaceReg", BI_ReplaceReg, 2, 1},
+ {"GIReplaceRegWithUndef", BI_ReplaceRegWithUndef, 1, 1},
{"GIEraseRoot", BI_EraseRoot, 0, 0},
}};
diff --git a/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp b/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
index bb93d0248e55f..2ea810c646085 100644
--- a/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
+++ b/llvm/utils/TableGen/GlobalISelCombinerEmitter.cpp
@@ -664,6 +664,10 @@ class CombineRuleBuilder {
return CGT.getInstruction(RuleDef.getRecords().getDef("G_CONSTANT"));
}
+ const CodeGenInstruction &getGImplicitDef() const {
+ return CGT.getInstruction(RuleDef.getRecords().getDef("G_IMPLICIT_DEF"));
+ }
+
std::optional<LLTCodeGenOrTempType>
getLLTCodeGenOrTempType(const PatternType &PT, RuleMatcher &RM);
@@ -1371,6 +1375,23 @@ bool CombineRuleBuilder::checkSemantics() {
}
break;
}
+ case BI_ReplaceRegWithUndef: {
+ // (GIReplaceRegWithUndef can only be used on the root instruction)
+ StringRef RegName = BIP->getOperand(0).getOperandName();
+ auto *Def = MatchOpTable.getDef(RegName);
+ if (!Def) {
+ PrintError(Name + " cannot find a matched pattern that defines '" +
+ RegName + "'");
+ return false;
+ }
+ if (Def != MatchRoot) {
+ PrintError(Name + " cannot replace '" + RegName +
+ "': this builtin can only replace a register defined by the "
+ "match root");
+ return false;
+ }
+ break;
+ }
}
}
@@ -2206,6 +2227,27 @@ bool CombineRuleBuilder::emitBuiltinApplyPattern(
assert(MatchOpTable.getDef(Old) == MatchRoot);
return true;
}
+ case BI_ReplaceRegWithUndef: {
+ StringRef Reg = P.getOperand(0).getOperandName();
+
+ auto &RegOM = M.getOperandMatcher(Reg);
+
+ // Build a G_IMPLICIT_DEF into a fresh temp reg of the same type as the
+ // replaced register, then replace all uses of the register with it.
+ unsigned TempRegID = M.allocateTempRegID();
+ M.insertAction<MakeTempRegisterAction>(
+ M.actions_begin(), RegOM.getTempTypeIdx(M), TempRegID);
+
+ auto &UndefMI =
+ M.addAction<BuildMIAction>(M.allocateOutputInsnID(), M, &getGImplicitDef());
+ UndefMI.addRenderer<TempRegRenderer>(TempRegID, /*IsDef=*/true);
+
+ M.addAction<ReplaceRegAction>(RegOM.getInsnVarID(), RegOM.getOpIdx(),
+ TempRegID);
+ // checkSemantics should have ensured that we can only rewrite the root.
+ assert(MatchOpTable.getDef(Reg) == MatchRoot);
+ return true;
+ }
}
llvm_unreachable("Unknown BuiltinKind!");
More information about the llvm-commits
mailing list