[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