[llvm] [GlobalISel] Migrate undef-operand combines to MIR patterns (PR #219449)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Aug 28 05:06:15 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-aarch64
Author: Vikash Gupta (vg0204)
<details>
<summary>Changes</summary>
Part of the ongoing effort to retire `wip_match_opcode` from the generic combiner. A whole class of combines only use it to ask "is operand N of the root undef?", delegating to `matchAnyExplicitUseIsUndef` / `matchOperandIsUndef`. That predicate is purely structural, so it belongs in the MIR pattern itself rather than in C++.
It adds four `GICombinePatFrag` classes parameterized by an opcode list (`unary_undef_frag`, `binop_left_undef_frag`, `binop_right_undef_frag`, `binop_any_undef_frag`). These become the shared vocabulary for undef-operand folds and let each rule express only its match shape. All the undef-to-{zero,-1,undef} / propagate-undef rules and the two extractelement-of-undef rules are re-expressed on top of them, the latter collapsing into a single rule.
It also included the fneg/fneg folds for `fmul/fdiv/fmad/fma` which are partially migrated as MIR-pattern for sub-cases.
Behavioural caveats worth calling out for review:
- Relocating these rules from the opcode-only bucket to the structured bucket can possibly shifts combine application order on the opcodes but it won't effect the semantics equivalence on execution level.
---
Full diff: https://github.com/llvm/llvm-project/pull/219449.diff
5 Files Affected:
- (modified) llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h (-7)
- (modified) llvm/include/llvm/Target/GlobalISel/Combine.td (+94-48)
- (modified) llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp (+1-33)
- (modified) llvm/test/CodeGen/AArch64/GlobalISel/prelegalizer-combiner-divrem-insertpt-crash.mir (+3-2)
- (modified) llvm/test/CodeGen/AMDGPU/GlobalISel/merge-values-s16-true16.ll (+7-4)
``````````diff
diff --git a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
index 28495529ec160..00c7d4808536b 100644
--- a/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
+++ b/llvm/include/llvm/CodeGen/GlobalISel/CombinerHelper.h
@@ -496,10 +496,6 @@ class CombinerHelper {
applyCombineTruncOfShift(MachineInstr &MI,
std::pair<MachineInstr *, LLT> &MatchInfo) const;
- /// Return true if any explicit use operand on \p MI is defined by a
- /// G_IMPLICIT_DEF.
- LLVM_ABI bool matchAnyExplicitUseIsUndef(MachineInstr &MI) const;
-
/// Return true if all register explicit use operands on \p MI are defined by
/// a G_IMPLICIT_DEF.
LLVM_ABI bool matchAllExplicitUsesAreUndef(MachineInstr &MI) const;
@@ -572,9 +568,6 @@ class CombinerHelper {
/// Optimize (x op x) -> x
LLVM_ABI bool matchBinOpSameVal(MachineInstr &MI) const;
- /// Check if operand \p OpIdx is undef.
- LLVM_ABI bool matchOperandIsUndef(MachineInstr &MI, unsigned OpIdx) const;
-
/// Check if operand \p MO is known to be a power of 2. When \p OrNegative
/// is true, also match operands whose negation is a power of 2 (i.e. whose
/// absolute value is a power of 2).
diff --git a/llvm/include/llvm/Target/GlobalISel/Combine.td b/llvm/include/llvm/Target/GlobalISel/Combine.td
index e0f665e85c2cc..4b0a438cd6455 100644
--- a/llvm/include/llvm/Target/GlobalISel/Combine.td
+++ b/llvm/include/llvm/Target/GlobalISel/Combine.td
@@ -470,49 +470,74 @@ def narrow_binop_feeding_and : GICombineRule<
[{ return Helper.matchNarrowBinopFeedingAnd(*${root}, ${matchinfo}); }]),
(apply [{ Helper.applyBuildFnNoErase(*${root}, ${matchinfo}); }])>;
+// Reusable match fragments that fire when an operand of the root is undef
+// (i.e. defined by G_IMPLICIT_DEF). Each is a class parameterized by the list
+// of opcodes to match, so any set of opcodes sharing the same "undef operand"
+// shape is expressed once here and combined at the match side; each rule then
+// supplies its own 'apply' (fold to 0, -1, undef, ...). $dst is the root def.
+
+// Unary op ($dst = op $x) with an undef source.
+class unary_undef_frag<list<Instruction> ops> : GICombinePatFrag<
+ (outs root:$dst), (ins),
+ !foreach(op, ops, (pattern (op $dst, $x), (G_IMPLICIT_DEF $x)))>;
+
+// Binary op ($dst = op $x, $y) with an undef left operand ($x).
+class binop_left_undef_frag<list<Instruction> ops> : GICombinePatFrag<
+ (outs root:$dst), (ins),
+ !foreach(op, ops, (pattern (op $dst, $x, $y), (G_IMPLICIT_DEF $x)))>;
+
+// Binary op ($dst = op $x, $y) with an undef right operand ($y).
+class binop_right_undef_frag<list<Instruction> ops> : GICombinePatFrag<
+ (outs root:$dst), (ins),
+ !foreach(op, ops, (pattern (op $dst, $x, $y), (G_IMPLICIT_DEF $y)))>;
+
+// Binary op ($dst = op $x, $y) with either operand undef. Both operand
+// positions are enumerated because match patterns are not auto-commuted.
+class binop_any_undef_frag<list<Instruction> ops> : GICombinePatFrag<
+ (outs root:$dst), (ins),
+ !listconcat(
+ !foreach(op, ops, (pattern (op $dst, $x, $y), (G_IMPLICIT_DEF $x))),
+ !foreach(op, ops, (pattern (op $dst, $x, $y), (G_IMPLICIT_DEF $y))))>;
+
// [us]itofp(undef) = 0, because the result value is bounded.
+def undef_to_fp_zero_frags : unary_undef_frag<[G_UITOFP, G_SITOFP]>;
def undef_to_fp_zero : GICombineRule<
- (defs root:$root),
- (match (wip_match_opcode G_UITOFP, G_SITOFP):$root,
- [{ return Helper.matchAnyExplicitUseIsUndef(*${root}); }]),
- (apply [{ Helper.replaceInstWithFConstant(*${root}, 0.0); }])>;
+ (defs root:$dst),
+ (match (undef_to_fp_zero_frags $dst)),
+ (apply [{ Helper.replaceInstWithFConstant(*${dst}.getParent(), 0.0); }])>;
+def undef_to_int_zero_frags : binop_any_undef_frag<[G_AND, G_MUL]>;
def undef_to_int_zero: GICombineRule<
- (defs root:$root),
- (match (wip_match_opcode G_AND, G_MUL):$root,
- [{ return Helper.matchAnyExplicitUseIsUndef(*${root}); }]),
- (apply [{ Helper.replaceInstWithConstant(*${root}, 0); }])>;
+ (defs root:$dst),
+ (match (undef_to_int_zero_frags $dst)),
+ (apply [{ Helper.replaceInstWithConstant(*${dst}.getParent(), 0); }])>;
+def undef_to_negative_one_frags : binop_any_undef_frag<[G_OR]>;
def undef_to_negative_one: GICombineRule<
- (defs root:$root),
- (match (wip_match_opcode G_OR):$root,
- [{ return Helper.matchAnyExplicitUseIsUndef(*${root}); }]),
- (apply [{ Helper.replaceInstWithConstant(*${root}, -1); }])>;
+ (defs root:$dst),
+ (match (undef_to_negative_one_frags $dst)),
+ (apply [{ Helper.replaceInstWithConstant(*${dst}.getParent(), -1); }])>;
+def binop_left_undef_to_zero_frags : binop_left_undef_frag<[G_SHL, G_UDIV, G_UREM]>;
def binop_left_undef_to_zero: GICombineRule<
- (defs root:$root),
- (match (wip_match_opcode G_SHL, G_UDIV, G_UREM):$root,
- [{ return Helper.matchOperandIsUndef(*${root}, 1); }]),
- (apply [{ Helper.replaceInstWithConstant(*${root}, 0); }])>;
+ (defs root:$dst),
+ (match (binop_left_undef_to_zero_frags $dst)),
+ (apply [{ Helper.replaceInstWithConstant(*${dst}.getParent(), 0); }])>;
+def binop_right_undef_to_undef_frags : binop_right_undef_frag<[G_SHL, G_ASHR, G_LSHR]>;
def binop_right_undef_to_undef: GICombineRule<
- (defs root:$root),
- (match (wip_match_opcode G_SHL, G_ASHR, G_LSHR):$root,
- [{ return Helper.matchOperandIsUndef(*${root}, 2); }]),
- (apply [{ Helper.replaceInstWithUndef(*${root}); }])>;
+ (defs root:$dst),
+ (match (binop_right_undef_to_undef_frags $dst)),
+ (apply [{ Helper.replaceInstWithUndef(*${dst}.getParent()); }])>;
+def unary_undef_to_zero_frags : unary_undef_frag<[G_ABS]>;
def unary_undef_to_zero: GICombineRule<
- (defs root:$root),
- (match (wip_match_opcode G_ABS):$root,
- [{ return Helper.matchOperandIsUndef(*${root}, 1); }]),
- (apply [{ Helper.replaceInstWithConstant(*${root}, 0); }])>;
+ (defs root:$dst),
+ (match (unary_undef_to_zero_frags $dst)),
+ (apply [{ Helper.replaceInstWithConstant(*${dst}.getParent(), 0); }])>;
-def unary_undef_to_undef_frags : GICombinePatFrag<
- (outs root:$dst), (ins),
- !foreach(op,
- [G_TRUNC, G_BITCAST, G_ANYEXT, G_PTRTOINT, G_INTTOPTR, G_FPTOSI,
- G_FPTOUI],
- (pattern (op $dst, $x), (G_IMPLICIT_DEF $x)))>;
+def unary_undef_to_undef_frags : unary_undef_frag<
+ [G_TRUNC, G_BITCAST, G_ANYEXT, G_PTRTOINT, G_INTTOPTR, G_FPTOSI, G_FPTOUI]>;
def unary_undef_to_undef : GICombineRule<
(defs root:$dst),
(match (unary_undef_to_undef_frags $dst)),
@@ -520,11 +545,11 @@ def unary_undef_to_undef : GICombineRule<
// Instructions where if any source operand is undef, the instruction can be
// replaced with undef.
+def propagate_undef_any_op_frags : binop_any_undef_frag<[G_ADD, G_SUB, G_XOR]>;
def propagate_undef_any_op: GICombineRule<
- (defs root:$root),
- (match (wip_match_opcode G_ADD, G_SUB, G_XOR):$root,
- [{ return Helper.matchAnyExplicitUseIsUndef(*${root}); }]),
- (apply [{ Helper.replaceInstWithUndef(*${root}); }])>;
+ (defs root:$dst),
+ (match (propagate_undef_any_op_frags $dst)),
+ (apply [{ Helper.replaceInstWithUndef(*${dst}.getParent()); }])>;
// Instructions where if all source operands are undef, the instruction can be
// replaced with undef.
@@ -1648,12 +1673,39 @@ def truncusatu_to_fptouisat : GICombineRule<
def truncsat_combines : GICombineGroup<[trunc_ssats, trunc_ssatu, trunc_usatu, truncusatu_to_fptouisat]>;
-def redundant_neg_operands: GICombineRule<
+// fold (fadd x, fneg(y)) -> (fsub x, y)
+// fold (fsub x, fneg(y)) -> (fadd x, y)
+// These need a legality check on the swapped opcode, so stay in C++.
+def redundant_neg_operands_fadd_fsub: GICombineRule<
(defs root:$root, build_fn_matchinfo:$matchinfo),
- (match (wip_match_opcode G_FADD, G_FSUB, G_FMUL, G_FDIV, G_FMAD, G_FMA):$root,
+ (match (wip_match_opcode G_FADD, G_FSUB):$root,
[{ return Helper.matchRedundantNegOperands(*${root}, ${matchinfo}); }]),
(apply [{ Helper.applyBuildFnNoErase(*${root}, ${matchinfo}); }])>;
+// fold (fmul (fneg x), (fneg y)) -> (fmul x, y)
+// fold (fdiv (fneg x), (fneg y)) -> (fdiv x, y)
+foreach binop = [G_FMUL, G_FDIV] in {
+ def binop#"_fneg_fneg" : GICombineRule<
+ (defs root:$dst),
+ (match (G_FNEG $nx, $x), (G_FNEG $ny, $y),
+ (binop $dst, $nx, $ny):$root),
+ (apply (binop $dst, $x, $y, (MIFlags $root)))>;
+}
+
+// fold (fmad (fneg x), (fneg y), z) -> (fmad x, y, z)
+// fold (fma (fneg x), (fneg y), z) -> (fma x, y, z)
+foreach fmaop = [G_FMAD, G_FMA] in {
+ def fmaop#"_fneg_fneg" : GICombineRule<
+ (defs root:$dst),
+ (match (G_FNEG $nx, $x), (G_FNEG $ny, $y),
+ (fmaop $dst, $nx, $ny, $z):$root),
+ (apply (fmaop $dst, $x, $y, $z, (MIFlags $root)))>;
+}
+
+def redundant_neg_operands : GICombineGroup<
+ [redundant_neg_operands_fadd_fsub, G_FMUL_fneg_fneg, G_FDIV_fneg_fneg,
+ G_FMAD_fneg_fneg, G_FMA_fneg_fneg]>;
+
// Transform (fsub +-0.0, X) -> (fneg X)
def fsub_to_fneg: GICombineRule<
(defs root:$root, register_matchinfo:$matchinfo),
@@ -1863,17 +1915,12 @@ def match_subo_no_overflow : GICombineRule<
[{ return Helper.matchSuboCarryOut(*${root}, ${matchinfo}); }]),
(apply [{ Helper.applyBuildFn(*${root}, ${matchinfo}); }])>;
-def match_extract_of_element_undef_vector: GICombineRule <
- (defs root:$root),
- (match (G_IMPLICIT_DEF $vector),
- (G_EXTRACT_VECTOR_ELT $root, $vector, $idx)),
- (apply (G_IMPLICIT_DEF $root))
->;
-
-def match_extract_of_element_undef_index: GICombineRule <
+// extractelement(undef_vector, idx) and extractelement(vector, undef_idx) are
+// both undef. Either undef operand is matched by the shared frag.
+def extract_vector_element_undef_frags : binop_any_undef_frag<[G_EXTRACT_VECTOR_ELT]>;
+def extract_vector_element_undef : GICombineRule <
(defs root:$root),
- (match (G_IMPLICIT_DEF $idx),
- (G_EXTRACT_VECTOR_ELT $root, $vector, $idx)),
+ (match (extract_vector_element_undef_frags $root)),
(apply (G_IMPLICIT_DEF $root))
>;
@@ -2109,8 +2156,7 @@ def combine_shuffle_disjoint_mask : GICombineRule<
// match_extract_of_element and insert_vector_elt_oob must be the first!
def vector_ops_combines: GICombineGroup<[
-match_extract_of_element_undef_vector,
-match_extract_of_element_undef_index,
+extract_vector_element_undef,
insert_vector_element_idx_undef,
insert_vector_element_elt_undef,
match_extract_of_element,
diff --git a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
index 3f5d9ecfa26fb..102f9e50c34b1 100644
--- a/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/CombinerHelper.cpp
@@ -2889,13 +2889,6 @@ void CombinerHelper::applyCombineTruncOfShift(
eraseInst(MI);
}
-bool CombinerHelper::matchAnyExplicitUseIsUndef(MachineInstr &MI) const {
- return any_of(MI.explicit_uses(), [this](const MachineOperand &MO) {
- return MO.isReg() &&
- getOpcodeDef(TargetOpcode::G_IMPLICIT_DEF, MO.getReg(), MRI);
- });
-}
-
bool CombinerHelper::matchAllExplicitUsesAreUndef(MachineInstr &MI) const {
return all_of(MI.explicit_uses(), [this](const MachineOperand &MO) {
return !MO.isReg() ||
@@ -3132,19 +3125,6 @@ bool CombinerHelper::matchSelectSameVal(MachineInstr &MI) const {
MRI);
}
-bool CombinerHelper::matchBinOpSameVal(MachineInstr &MI) const {
- return matchEqualDefs(MI.getOperand(1), MI.getOperand(2)) &&
- canReplaceReg(MI.getOperand(0).getReg(), MI.getOperand(1).getReg(),
- MRI);
-}
-
-bool CombinerHelper::matchOperandIsUndef(MachineInstr &MI,
- unsigned OpIdx) const {
- MachineOperand &MO = MI.getOperand(OpIdx);
- return MO.isReg() &&
- getOpcodeDef(TargetOpcode::G_IMPLICIT_DEF, MO.getReg(), MRI);
-}
-
bool CombinerHelper::matchOperandIsKnownToBeAPowerOfTwo(
const MachineOperand &MO, bool OrNegative) const {
return isKnownToBeAPowerOfTwo(MO.getReg(), MRI, VT, OrNegative);
@@ -6381,9 +6361,7 @@ bool CombinerHelper::matchTruncUSatUToFPTOUISat(MachineInstr &MI,
bool CombinerHelper::matchRedundantNegOperands(MachineInstr &MI,
BuildFnTy &MatchInfo) const {
unsigned Opc = MI.getOpcode();
- assert(Opc == TargetOpcode::G_FADD || Opc == TargetOpcode::G_FSUB ||
- Opc == TargetOpcode::G_FMUL || Opc == TargetOpcode::G_FDIV ||
- Opc == TargetOpcode::G_FMAD || Opc == TargetOpcode::G_FMA);
+ assert(Opc == TargetOpcode::G_FADD || Opc == TargetOpcode::G_FSUB);
Register Dst = MI.getOperand(0).getReg();
Register X = MI.getOperand(1).getReg();
@@ -6401,16 +6379,6 @@ bool CombinerHelper::matchRedundantNegOperands(MachineInstr &MI,
else if (mi_match(Dst, MRI, m_GFSub(m_Reg(X), m_GFNeg(m_Reg(Y)))) &&
isLegalOrBeforeLegalizer({TargetOpcode::G_FADD, {Type}})) {
Opc = TargetOpcode::G_FADD;
- }
- // fold (fmul fneg(x), fneg(y)) -> (fmul x, y)
- // fold (fdiv fneg(x), fneg(y)) -> (fdiv x, y)
- // fold (fmad fneg(x), fneg(y), z) -> (fmad x, y, z)
- // fold (fma fneg(x), fneg(y), z) -> (fma x, y, z)
- else if ((Opc == TargetOpcode::G_FMUL || Opc == TargetOpcode::G_FDIV ||
- Opc == TargetOpcode::G_FMAD || Opc == TargetOpcode::G_FMA) &&
- mi_match(X, MRI, m_GFNeg(m_Reg(X))) &&
- mi_match(Y, MRI, m_GFNeg(m_Reg(Y)))) {
- // no opcode change
} else
return false;
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/prelegalizer-combiner-divrem-insertpt-crash.mir b/llvm/test/CodeGen/AArch64/GlobalISel/prelegalizer-combiner-divrem-insertpt-crash.mir
index c9e8adfeaa94a..b66ed3b50feb7 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/prelegalizer-combiner-divrem-insertpt-crash.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/prelegalizer-combiner-divrem-insertpt-crash.mir
@@ -16,6 +16,7 @@ body: |
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: [[COPY:%[0-9]+]]:_(p0) = COPY $x0
; CHECK-NEXT: [[DEF:%[0-9]+]]:_(i1) = G_IMPLICIT_DEF
+ ; CHECK-NEXT: [[DEF1:%[0-9]+]]:_(i64) = G_IMPLICIT_DEF
; CHECK-NEXT: [[C:%[0-9]+]]:_(i64) = G_CONSTANT i64 0
; CHECK-NEXT: G_BRCOND [[DEF]](i1), %bb.2
; CHECK-NEXT: G_BR %bb.1
@@ -23,8 +24,8 @@ body: |
; CHECK-NEXT: bb.1:
; CHECK-NEXT: successors: %bb.2(0x80000000)
; CHECK-NEXT: {{ $}}
- ; CHECK-NEXT: [[C1:%[0-9]+]]:_(i64) = G_CONSTANT i64 -1
- ; CHECK-NEXT: [[UDIV:%[0-9]+]]:_(i64) = G_UDIV [[C1]], [[C]]
+ ; CHECK-NEXT: [[FREEZE:%[0-9]+]]:_(i64) = G_FREEZE [[DEF1]]
+ ; CHECK-NEXT: [[UDIV:%[0-9]+]]:_(i64) = G_UDIV [[FREEZE]], [[C]]
; CHECK-NEXT: G_STORE [[UDIV]](i64), [[COPY]](p0) :: (store (i64))
; CHECK-NEXT: {{ $}}
; CHECK-NEXT: bb.2:
diff --git a/llvm/test/CodeGen/AMDGPU/GlobalISel/merge-values-s16-true16.ll b/llvm/test/CodeGen/AMDGPU/GlobalISel/merge-values-s16-true16.ll
index fc1be777b09a3..dfa76d8d0acfa 100644
--- a/llvm/test/CodeGen/AMDGPU/GlobalISel/merge-values-s16-true16.ll
+++ b/llvm/test/CodeGen/AMDGPU/GlobalISel/merge-values-s16-true16.ll
@@ -28,17 +28,20 @@ define amdgpu_kernel void @store_i136_divergent(ptr %p) {
; GFX1150-LABEL: store_i136_divergent:
; GFX1150: ; %bb.0:
; GFX1150-NEXT: s_load_b64 s[0:1], s[4:5], 0x0
-; GFX1150-NEXT: v_dual_mov_b32 v3, 0 :: v_dual_and_b32 v0, 0x3ff, v0
-; GFX1150-NEXT: v_mov_b32_e32 v2, 0
+; GFX1150-NEXT: s_pack_ll_b32_b16 s2, 0, 0
; GFX1150-NEXT: v_mov_b32_e32 v6, 0
-; GFX1150-NEXT: s_delay_alu instid0(VALU_DEP_3) | instskip(SKIP_2) | instid1(VALU_DEP_3)
+; GFX1150-NEXT: s_mov_b32 s3, s2
+; GFX1150-NEXT: s_delay_alu instid0(SALU_CYCLE_1) | instskip(SKIP_1) | instid1(VALU_DEP_2)
+; GFX1150-NEXT: v_dual_mov_b32 v3, s3 :: v_dual_and_b32 v0, 0x3ff, v0
+; GFX1150-NEXT: v_mov_b32_e32 v2, s2
; GFX1150-NEXT: v_lshrrev_b32_e32 v1, 8, v0
; GFX1150-NEXT: v_mov_b16_e32 v0.h, 0
; GFX1150-NEXT: v_and_b16 v0.l, 0xff, v0.l
+; GFX1150-NEXT: s_delay_alu instid0(VALU_DEP_3) | instskip(NEXT) | instid1(VALU_DEP_3)
; GFX1150-NEXT: v_lshlrev_b16 v4.l, 8, v1.l
-; GFX1150-NEXT: s_delay_alu instid0(VALU_DEP_3) | instskip(SKIP_1) | instid1(VALU_DEP_3)
; GFX1150-NEXT: v_mov_b16_e32 v1.l, v0.h
; GFX1150-NEXT: v_mov_b16_e32 v1.h, v0.h
+; GFX1150-NEXT: s_delay_alu instid0(VALU_DEP_3)
; GFX1150-NEXT: v_or_b16 v0.l, v0.l, v4.l
; GFX1150-NEXT: s_waitcnt lgkmcnt(0)
; GFX1150-NEXT: v_dual_mov_b32 v5, s1 :: v_dual_mov_b32 v4, s0
``````````
</details>
https://github.com/llvm/llvm-project/pull/219449
More information about the llvm-commits
mailing list