[llvm] [TableGen][AMDGPU][AsmParser] Catch and fix ambiguous instructions (PR #211093)
Ivan Kosarev via llvm-commits
llvm-commits at lists.llvm.org
Mon Aug 3 03:42:15 PDT 2026
https://github.com/kosarev updated https://github.com/llvm/llvm-project/pull/211093
>From 67527776b6094b70424b8ed206d2b2291b310e60 Mon Sep 17 00:00:00 2001
From: Ivan Kosarev <ivan.kosarev at amd.com>
Date: Thu, 16 Jul 2026 14:39:16 +0100
Subject: [PATCH 1/2] [TableGen][AMDGPU][AsmParser] Catch and fix ambiguous
instructions
Declares mutually exclusive assembler predicates and teaches
TableGen to use that knowledge to report matchables that are
indistinguishable to the asm parser.
The instruction changes are effectively NFCI, mostly removing
genuine duplicates and fixing predicates.
Prepared using Claude.
https://github.com/llvm/llvm-project/issues/69256
---
llvm/include/llvm/Target/Target.td | 18 +++
llvm/lib/Target/AMDGPU/AMDGPU.td | 98 +++++++++++++++
llvm/lib/Target/AMDGPU/DSInstructions.td | 26 +++-
llvm/lib/Target/AMDGPU/FLATInstructions.td | 15 ++-
llvm/lib/Target/AMDGPU/SIInstructions.td | 6 +-
llvm/lib/Target/AMDGPU/VOP2Instructions.td | 26 ++--
llvm/lib/Target/AMDGPU/VOP3PInstructions.td | 5 +-
.../asm-matcher-ambiguous-matchables.td | 56 +++++++++
.../asm-matcher-exclusive-pred-self.td | 24 ++++
llvm/utils/TableGen/AsmMatcherEmitter.cpp | 119 +++++++++++++-----
10 files changed, 340 insertions(+), 53 deletions(-)
create mode 100644 llvm/test/TableGen/asm-matcher-ambiguous-matchables.td
create mode 100644 llvm/test/TableGen/asm-matcher-exclusive-pred-self.td
diff --git a/llvm/include/llvm/Target/Target.td b/llvm/include/llvm/Target/Target.td
index cb97ab5d1bae4..23939bacc5cb4 100644
--- a/llvm/include/llvm/Target/Target.td
+++ b/llvm/include/llvm/Target/Target.td
@@ -1897,6 +1897,13 @@ class AsmParser {
// (Optional) Instruction id (see AsmMatcherEmitter.cpp for details),
// Number of required features (least first)
bit PreferSmallerInstructions = false;
+
+ // ErrorOnAmbiguousMatchables - Set to true to make the assembler matcher
+ // emitter produce a fatal error for any pair of matchables that are
+ // indistinguishable to the asm parser. The check relies on mutually
+ // exclusive assembler predicates declared using
+ // MutuallyExclusiveAssemblerPredicates.
+ bit ErrorOnAmbiguousMatchables = false;
}
def DefaultAsmParser : AsmParser;
@@ -1951,6 +1958,17 @@ class AssemblerPredicate<dag cond, string name = ""> {
string PredicateName = name;
}
+/// MutuallyExclusiveAssemblerPredicates - Declares that predicate A is
+/// mutually exclusive with every predicate in list B, meaning no target
+/// configuration can satisfy A and any predicate from B simultaneously.
+/// The assembler matcher emitter uses this information to disambiguate
+/// differently predicated but otherwise identical instructions.
+class MutuallyExclusiveAssemblerPredicates<AssemblerPredicate A,
+ list<AssemblerPredicate> B> {
+ AssemblerPredicate PredicateA = A;
+ list<AssemblerPredicate> PredicatesB = B;
+}
+
/// TokenAlias - This class allows targets to define assembler token
/// operand aliases. That is, a token literal operand which is equivalent
/// to another, canonical, token literal. For example, ARM allows:
diff --git a/llvm/lib/Target/AMDGPU/AMDGPU.td b/llvm/lib/Target/AMDGPU/AMDGPU.td
index ed454806d7193..8d1ef8cc61a87 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPU.td
+++ b/llvm/lib/Target/AMDGPU/AMDGPU.td
@@ -2399,6 +2399,8 @@ def AMDGPUAsmParser : AsmParser {
// Call the custom operand parser for all operands.
let OperandParserMethod = "parseCustomOperand";
let CallCustomParserForAllOperands = true;
+
+ let ErrorOnAmbiguousMatchables = true;
}
def AMDGPUAsmWriter : AsmWriter {
@@ -2911,6 +2913,102 @@ def isWave32Strict : Predicate<"Subtarget->isWave32()">,
def isWave64Strict : Predicate<"Subtarget->isWave64()">,
AssemblerPredicate <(all_of FeatureWavefrontSize64)>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX8GFX9NotGFX90A,
+ [isGFX6GFX7]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX8GFX9,
+ [isGFX6GFX7, isGFX7Only]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX8Only,
+ [isGFX6GFX7, isGFX7Only]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX8GFX9NotGFX940,
+ [isGFX6GFX7, isGFX7Only]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX9Only,
+ [isGFX6GFX7, isGFX7Only, isGFX8Only]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX90AOnly,
+ [isGFX6GFX7, isGFX8GFX9NotGFX90A]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX940Plus,
+ [isGFX6GFX7, isGFX8GFX9NotGFX90A,
+ isGFX8GFX9NotGFX940, isGFX90AOnly]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX90APlus,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX8GFX9NotGFX90A, isGCN3ExcludingGFX90A]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX10Only,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX11Only,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus,
+ isGFX10Only]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX11Plus,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus,
+ isGFX10Only]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX11Not11_70,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus,
+ isGFX10Only, isGFX1170Only]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX1170Only,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus,
+ isGFX10Only, isGFX11Not11_70]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX12Only,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus,
+ isGFX10Only, isGFX11Only,
+ isGFX11Not11_70, isGFX1170Only]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX12Not12_50,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus,
+ isGFX10Only, isGFX11Only,
+ isGFX11Not11_70, isGFX1170Only, isGFX125xOnly]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX125xOnly,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus,
+ isGFX10Only, isGFX11Only,
+ isGFX11Not11_70, isGFX1170Only, isGFX12Not12_50]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX13Only,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus,
+ isGFX10Only, isGFX11Only,
+ isGFX11Not11_70, isGFX1170Only, isGFX12Only,
+ isGFX12Not12_50, isGFX125xOnly]>;
+def : MutuallyExclusiveAssemblerPredicates<isGFX13Plus,
+ [isGFX6GFX7, isGFX6GFX7GFX8GFX9NotGFX90A,
+ isGFX7Only, isGFX8Only, isGFX9Only,
+ isGFX8GFX9, isGFX8GFX9NotGFX90A, isGFX8GFX9NotGFX940,
+ isGFX90AOnly, isGFX940Plus, isGFX90APlus,
+ isGFX10Only, isGFX11Only,
+ isGFX11Not11_70, isGFX1170Only, isGFX12Only,
+ isGFX12Not12_50, isGFX125xOnly]>;
+
+def : MutuallyExclusiveAssemblerPredicates<HasSDWA9,
+ [isGFX10Only, isGFX11Only,
+ isGFX11Not11_70, isGFX1170Only, isGFX12Only,
+ isGFX12Not12_50, isGFX125xOnly, isGFX13Only]>;
+def : MutuallyExclusiveAssemblerPredicates<HasPackedD16VMem, [HasUnpackedD16VMem]>;
+def : MutuallyExclusiveAssemblerPredicates<HasDPP,
+ [isGFX10Only, isGFX11Only, isGFX11Plus,
+ isGFX11Not11_70, isGFX1170Only, isGFX12Only,
+ isGFX12Not12_50, isGFX125xOnly, isGFX13Only,
+ isGFX13Plus]>;
+
//===----------------------------------------------------------------------===//
// HwModes
//===----------------------------------------------------------------------===//
diff --git a/llvm/lib/Target/AMDGPU/DSInstructions.td b/llvm/lib/Target/AMDGPU/DSInstructions.td
index 204cd89d4aefb..af78966a33a41 100644
--- a/llvm/lib/Target/AMDGPU/DSInstructions.td
+++ b/llvm/lib/Target/AMDGPU/DSInstructions.td
@@ -1458,6 +1458,15 @@ multiclass DS_Real_gfx12_with_name<bits<8> op, string name> {
multiclass DS_Real_gfx12_gfx13<bits<8> op, DS_Pseudo ps = !cast<DS_Pseudo>(NAME), string name = !tolower(NAME)> :
DS_Real_gfx12<op, ps, name>, DS_Real_gfx13<op, ps, name>;
+multiclass DS_Real_gfx125x_gfx13<bits<8> op, DS_Pseudo ps = !cast<DS_Pseudo>(NAME)> {
+ let AssemblerPredicate = isGFX125xOnly, DecoderNamespace = "GFX1250" in
+ def _gfx12 : Base_DS_Real_gfx6_gfx7_gfx10_gfx11_gfx12_gfx13<op, ps,
+ SIEncodingFamily.GFX12, !tolower(NAME), /*hasGDS=*/false>;
+ let AssemblerPredicate = isGFX13Only, DecoderNamespace = "GFX13" in
+ def _gfx13 : Base_DS_Real_gfx6_gfx7_gfx10_gfx11_gfx12_gfx13<op, ps,
+ SIEncodingFamily.GFX13, !tolower(NAME), /*hasGDS=*/false>;
+}
+
multiclass DS_Real_gfx12_gfx13_with_name<bits<8> op, string name> {
defm "" : DS_Real_gfx12_gfx13<op, !cast<DS_Pseudo>(NAME#"_gfx9"), name>;
}
@@ -1493,10 +1502,8 @@ defm DS_BVH_STACK_PUSH8_POP2_RTN_B64 : DS_Real_gfx12<0x0e2>;
defm DS_ADD_F64 : DS_Real_gfx12_gfx13<0x054>;
defm DS_ADD_RTN_F64 : DS_Real_gfx12_gfx13<0x074>;
-let AssemblerPredicate = HasLdsBarrierArriveAtomic in {
-defm DS_ATOMIC_ASYNC_BARRIER_ARRIVE_B64 : DS_Real_gfx12_gfx13<0x056>;
-defm DS_ATOMIC_BARRIER_ARRIVE_RTN_B64 : DS_Real_gfx12_gfx13<0x075>;
-}
+defm DS_ATOMIC_ASYNC_BARRIER_ARRIVE_B64 : DS_Real_gfx125x_gfx13<0x056>;
+defm DS_ATOMIC_BARRIER_ARRIVE_RTN_B64 : DS_Real_gfx125x_gfx13<0x075>;
// New aliases added in GFX12 without renaming the instructions.
let AssemblerPredicate = isGFX12Plus in {
@@ -1880,10 +1887,17 @@ class DS_Real_Base_vi <bits<8> op, DS_Pseudo ps> :
multiclass DS_Real_vi <bits<8> op, DS_Pseudo base_pseudo, bit need_gfx9_suffix = true> {
- def "" : DS_Real_Base_vi<op, base_pseudo>;
-
+ // When a GFX9 variant exists, restrict the base variant to GFX8 only to
+ // avoid assembler ambiguity.
+ if !not(need_gfx9_suffix) then {
+ def "" : DS_Real_Base_vi<op, base_pseudo>;
+ }
if need_gfx9_suffix then {
+ def "" : DS_Real_Base_vi<op, base_pseudo> {
+ let AssemblerPredicate = isGFX8Only;
+ }
def _gfx9 : DS_Real_Base_vi<op, !cast<DS_Pseudo>(!cast<string>(base_pseudo)#"_gfx9")> {
+ let AssemblerPredicate = isGFX9Only;
let DecoderNamespace = "GFX9";
}
}
diff --git a/llvm/lib/Target/AMDGPU/FLATInstructions.td b/llvm/lib/Target/AMDGPU/FLATInstructions.td
index 0f30ab24521cb..a11f930c5995d 100644
--- a/llvm/lib/Target/AMDGPU/FLATInstructions.td
+++ b/llvm/lib/Target/AMDGPU/FLATInstructions.td
@@ -2927,12 +2927,15 @@ let AssemblerPredicate = isGFX8GFX9NotGFX940 in {
defm GLOBAL_ATOMIC_PK_ADD_F16 : FLAT_Global_Real_Atomics_vi <0x04e, 0>;
}
-defm FLAT_ATOMIC_ADD_F64 : FLAT_Real_Atomics_vi<0x4f, 0>;
-defm FLAT_ATOMIC_MIN_F64 : FLAT_Real_Atomics_vi<0x50, 0>;
-defm FLAT_ATOMIC_MAX_F64 : FLAT_Real_Atomics_vi<0x51, 0>;
-defm GLOBAL_ATOMIC_ADD_F64 : FLAT_Global_Real_Atomics_vi<0x4f, 0>;
-defm GLOBAL_ATOMIC_MIN_F64 : FLAT_Global_Real_Atomics_vi<0x50, 0>;
-defm GLOBAL_ATOMIC_MAX_F64 : FLAT_Global_Real_Atomics_vi<0x51, 0>;
+// These instructions are encoded differently on gfx90* and gfx94*.
+let AssemblerPredicate = isGFX8GFX9NotGFX940 in {
+ defm FLAT_ATOMIC_ADD_F64 : FLAT_Real_Atomics_vi<0x4f, 0>;
+ defm FLAT_ATOMIC_MIN_F64 : FLAT_Real_Atomics_vi<0x50, 0>;
+ defm FLAT_ATOMIC_MAX_F64 : FLAT_Real_Atomics_vi<0x51, 0>;
+ defm GLOBAL_ATOMIC_ADD_F64 : FLAT_Global_Real_Atomics_vi<0x4f, 0>;
+ defm GLOBAL_ATOMIC_MIN_F64 : FLAT_Global_Real_Atomics_vi<0x50, 0>;
+ defm GLOBAL_ATOMIC_MAX_F64 : FLAT_Global_Real_Atomics_vi<0x51, 0>;
+}
multiclass FLAT_Real_AllAddr_gfx940<bits<7> op> {
def _gfx940 : FLAT_Real_gfx940<op, !cast<FLAT_Pseudo>(NAME)>;
diff --git a/llvm/lib/Target/AMDGPU/SIInstructions.td b/llvm/lib/Target/AMDGPU/SIInstructions.td
index d978735a92db8..9d45474f7cb8c 100644
--- a/llvm/lib/Target/AMDGPU/SIInstructions.td
+++ b/llvm/lib/Target/AMDGPU/SIInstructions.td
@@ -58,12 +58,12 @@ defm V_INTERP_P1_F32 : V_INTERP_P1_F32_m;
} // End OtherPredicates = [has32BankLDS, isNotGFX90APlus]
let OtherPredicates = [has16BankLDS, isNotGFX90APlus],
- Constraints = "@earlyclobber $vdst", isAsmParserOnly=1 in {
+ Constraints = "@earlyclobber $vdst", isCodeGenOnly=1 in {
defm V_INTERP_P1_F32_16bank : V_INTERP_P1_F32_m;
-} // End OtherPredicates = [has32BankLDS, isNotGFX90APlus],
- // Constraints = "@earlyclobber $vdst", isAsmParserOnly=1
+} // End OtherPredicates = [has16BankLDS, isNotGFX90APlus],
+ // Constraints = "@earlyclobber $vdst", isCodeGenOnly=1
let OtherPredicates = [isNotGFX90APlus] in {
let Constraints = "$src0 = $vdst" in {
diff --git a/llvm/lib/Target/AMDGPU/VOP2Instructions.td b/llvm/lib/Target/AMDGPU/VOP2Instructions.td
index bcf2ca6e8729e..3c4610a3a9efe 100644
--- a/llvm/lib/Target/AMDGPU/VOP2Instructions.td
+++ b/llvm/lib/Target/AMDGPU/VOP2Instructions.td
@@ -309,6 +309,15 @@ multiclass VOP2bInst <string opName,
}
}
+class VOP2bInstNoVCCSubstAlias <VOP2_Pseudo ps, Instruction inst, string OpName> :
+ InstAlias <OpName#" "#ps.Pfl.Asm32,
+ (inst ps.Pfl.DstRC:$vdst, ps.Pfl.Src0RC32:$src0,
+ ps.Pfl.Src1RC32:$src1),
+ 1, inst.AsmVariantName>,
+ PredicateControl {
+}
+
+// Alias class that substitutes vcc with opnd in Asm32 (e.g. vcc_lo on Wave32).
class VOP2bInstAlias <VOP2_Pseudo ps, Instruction inst,
string OpName, string opnd> :
InstAlias <OpName#" "#!subst("vcc", opnd, ps.Pfl.Asm32),
@@ -2834,12 +2843,11 @@ defm : VOP2eInstAliases<V_CNDMASK_B32_e32, V_CNDMASK_B32_e32_vi>;
let SubtargetPredicate = isGFX9Only in {
-defm : VOP2bInstAliases<V_ADD_U32_e32, V_ADD_CO_U32_e32_gfx9, "v_add_co_u32">;
-defm : VOP2bInstAliases<V_ADDC_U32_e32, V_ADDC_CO_U32_e32_gfx9, "v_addc_co_u32">;
-defm : VOP2bInstAliases<V_SUB_U32_e32, V_SUB_CO_U32_e32_gfx9, "v_sub_co_u32">;
-defm : VOP2bInstAliases<V_SUBB_U32_e32, V_SUBB_CO_U32_e32_gfx9, "v_subb_co_u32">;
-defm : VOP2bInstAliases<V_SUBREV_U32_e32, V_SUBREV_CO_U32_e32_gfx9, "v_subrev_co_u32">;
-defm : VOP2bInstAliases<V_SUBBREV_U32_e32, V_SUBBREV_CO_U32_e32_gfx9, "v_subbrev_co_u32">;
+// These pseudos have no vcc in Asm32, so VOP2bInstAliases would produce two
+// identical aliases.
+def : VOP2bInstNoVCCSubstAlias<V_ADD_U32_e32, V_ADD_CO_U32_e32_gfx9, "v_add_co_u32">;
+def : VOP2bInstNoVCCSubstAlias<V_SUB_U32_e32, V_SUB_CO_U32_e32_gfx9, "v_sub_co_u32">;
+def : VOP2bInstNoVCCSubstAlias<V_SUBREV_U32_e32, V_SUBREV_CO_U32_e32_gfx9, "v_subrev_co_u32">;
} // End SubtargetPredicate = isGFX9Only
@@ -2883,7 +2891,7 @@ let SubtargetPredicate = HasFmacF64Inst, OtherPredicates = [isGFX9Only] in {
defm V_FMAC_F64 : VOP2_Real_e32e64_gfx90a <0x4>;
} // End SubtargetPredicate = HasFmacF64Inst
-let IsSingle = 1 in {
+let AssemblerPredicate = isGFX90APlus, DecoderNamespace = "GFX90A", IsSingle = 1 in {
defm V_MUL_LEGACY_F32 : VOP2_Real_e64_gfx90a <0x2a1>;
}
@@ -2927,9 +2935,9 @@ let OtherPredicates = [HasDot3Insts] in {
defm V_DOT8C_I32_I4 : VOP2_Real_DOT_ACC_gfx9<0x3a>;
}
-let SubtargetPredicate = HasPkFmacF16Inst in {
+let SubtargetPredicate = HasPkFmacF16Inst, AssemblerPredicate = isGFX8Only in {
defm V_PK_FMAC_F16 : VOP2_Real_e32_vi<0x3c>;
-} // End SubtargetPredicate = HasPkFmacF16Inst
+} // End SubtargetPredicate = HasPkFmacF16Inst, AssemblerPredicate = isGFX8Only
let SubtargetPredicate = HasDot3Insts in {
// NB: Opcode conflicts with V_DOT2C_F32_F16
diff --git a/llvm/lib/Target/AMDGPU/VOP3PInstructions.td b/llvm/lib/Target/AMDGPU/VOP3PInstructions.td
index f99367d198f26..33a89d36c3363 100644
--- a/llvm/lib/Target/AMDGPU/VOP3PInstructions.td
+++ b/llvm/lib/Target/AMDGPU/VOP3PInstructions.td
@@ -2845,7 +2845,10 @@ defm V_WMMA_I32_16X16X16_IU4 : VOP3P_Real_WMMA <0x045>;
multiclass VOP3P_Real_vi<bits<7> op> {
def _vi : VOP3P_Real<!cast<VOP3_Pseudo>(NAME), SIEncodingFamily.VI>,
VOP3Pe_vi <op, !cast<VOP3_Pseudo>(NAME).Pfl> {
- let AssemblerPredicate = HasVOP3PInsts;
+ // VOP3P instructions use the VI encoding, which is GFX9-only; GFX8 does
+ // not support VOP3P. Use isGFX9Only so the assembler predicate also
+ // distinguishes this variant from the GFX10+ real instructions.
+ let AssemblerPredicate = isGFX9Only;
let DecoderNamespace = "GFX8";
let VOP3P = 1;
}
diff --git a/llvm/test/TableGen/asm-matcher-ambiguous-matchables.td b/llvm/test/TableGen/asm-matcher-ambiguous-matchables.td
new file mode 100644
index 0000000000000..98d035e2fe995
--- /dev/null
+++ b/llvm/test/TableGen/asm-matcher-ambiguous-matchables.td
@@ -0,0 +1,56 @@
+// RUN: not llvm-tblgen -gen-asm-matcher -I %p/../../include %s 2>&1 | \
+// RUN: FileCheck --check-prefix=ERR %s
+// RUN: llvm-tblgen -gen-asm-matcher -I %p/../../include \
+// RUN: -DEXCLUSIVE %s 2>/dev/null | FileCheck %s
+
+// Check that ErrorOnAmbiguousMatchables catches genuinely ambiguous matchables,
+// and that MutuallyExclusiveAssemblerPredicates suppresses false positives.
+
+include "llvm/Target/Target.td"
+
+def archInstrInfo : InstrInfo {}
+
+def ArchAsmParser : AsmParser {
+ let ErrorOnAmbiguousMatchables = 1;
+}
+
+def arch : Target {
+ let InstructionSet = archInstrInfo;
+ let AssemblyParsers = [ArchAsmParser];
+}
+
+def FeatureA : SubtargetFeature<"featureA", "featureA", "true", "">;
+def FeatureB : SubtargetFeature<"featureB", "featureB", "true", "">;
+
+def PredA : Predicate<"PredA">, AssemblerPredicate<(all_of FeatureA)>;
+def PredB : Predicate<"PredB">, AssemblerPredicate<(all_of FeatureB)>;
+
+let Namespace = "arch" in
+def R0 : Register<"r0">;
+def Regs : RegisterClass<"Regs", [i32], 32, (add R0)>;
+
+class TestInsn<int Opc, string Asm, list<Predicate> Preds> : Instruction {
+ let Size = 2;
+ let OutOperandList = (outs);
+ let InOperandList = (ins);
+ field bits<16> Inst = Opc;
+ field bits<16> SoftFail = 0;
+ let AsmString = Asm;
+ let Predicates = Preds;
+}
+
+def InsnA : TestInsn<0, "insn", [PredA]>;
+def InsnB : TestInsn<1, "insn", [PredB]>;
+
+#ifdef EXCLUSIVE
+def : MutuallyExclusiveAssemblerPredicates<PredA, [PredB]>;
+#endif
+
+// Without EXCLUSIVE: InsnA and InsnB have the same asm string with no declared
+// mutual exclusion, so the clash check fires.
+// ERR: error: matchable is indistinguishable to the parser
+// ERR: error: 1 ambiguous matchables found
+
+// With EXCLUSIVE: PredA and PredB are declared mutually exclusive, so no clash
+// is reported and the output is generated normally.
+// CHECK: MatchTable
diff --git a/llvm/test/TableGen/asm-matcher-exclusive-pred-self.td b/llvm/test/TableGen/asm-matcher-exclusive-pred-self.td
new file mode 100644
index 0000000000000..0f0820669612f
--- /dev/null
+++ b/llvm/test/TableGen/asm-matcher-exclusive-pred-self.td
@@ -0,0 +1,24 @@
+// RUN: not llvm-tblgen -gen-asm-matcher -I %p/../../include %s 2>&1 | \
+// RUN: FileCheck %s
+
+// Check that declaring a predicate mutually exclusive with itself is an error,
+// regardless of whether ErrorOnAmbiguousMatchables is set.
+
+include "llvm/Target/Target.td"
+
+def archInstrInfo : InstrInfo {}
+
+def arch : Target {
+ let InstructionSet = archInstrInfo;
+}
+
+def FeatureA : SubtargetFeature<"featureA", "featureA", "true", "">;
+def PredA : Predicate<"PredA">, AssemblerPredicate<(all_of FeatureA)>;
+
+let Namespace = "arch" in
+def R0 : Register<"r0">;
+def Regs : RegisterClass<"Regs", [i32], 32, (add R0)>;
+
+def : MutuallyExclusiveAssemblerPredicates<PredA, [PredA]>;
+
+// CHECK: error: predicate 'PredA' declared mutually exclusive with itself
diff --git a/llvm/utils/TableGen/AsmMatcherEmitter.cpp b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
index 4af5c8510bffb..40c5fea4ef02d 100644
--- a/llvm/utils/TableGen/AsmMatcherEmitter.cpp
+++ b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
@@ -691,8 +691,10 @@ struct MatchableInfo {
/// couldMatchAmbiguouslyWith - Check whether this matchable could
/// ambiguously match the same set of operands as \p RHS (without being a
/// strictly superior match).
- bool couldMatchAmbiguouslyWith(const MatchableInfo &RHS,
- bool PreferSmallerInstructions) const {
+ bool couldMatchAmbiguouslyWith(
+ const MatchableInfo &RHS, bool PreferSmallerInstructions,
+ const DenseSet<std::pair<const Record *, const Record *>> &ExclusivePreds)
+ const {
// The primary comparator is the instruction mnemonic.
if (Mnemonic != RHS.Mnemonic)
return false;
@@ -702,11 +704,32 @@ struct MatchableInfo {
return false;
// The size of instruction is unambiguous.
+ // TODO: We should consult shouldBeMatchedBefore() here instead of
+ // checking PreferSmallerInstructions directly.
if (PreferSmallerInstructions && ResInstSize != RHS.ResInstSize)
return false;
- // The number of operands is unambiguous.
- if (AsmOperands.size() != RHS.AsmOperands.size())
+ for (const SubtargetFeatureInfo *FA : RequiredFeatures) {
+ for (const SubtargetFeatureInfo *FB : RHS.RequiredFeatures) {
+ if (ExclusivePreds.count(std::minmax(FA->TheDef, FB->TheDef)))
+ return false;
+ }
+ }
+
+ // Ignore optional operands as they can't be used to disasmbiguate
+ // otherwise identical matchables.
+ SmallVector<const AsmOperand *, 8> LHSOps, RHSOps;
+ for (const AsmOperand &Op : AsmOperands) {
+ if (!Op.Class->IsOptional)
+ LHSOps.push_back(&Op);
+ }
+ for (const AsmOperand &Op : RHS.AsmOperands) {
+ if (!Op.Class->IsOptional)
+ RHSOps.push_back(&Op);
+ }
+
+ // The number of required operands is unambiguous.
+ if (LHSOps.size() != RHSOps.size())
return false;
// Otherwise, make sure the ordering of the two instructions is unambiguous
@@ -715,27 +738,29 @@ struct MatchableInfo {
// Tokens and operand kinds are unambiguous (assuming a correct target
// specific parser).
- for (const auto &[LHSOp, RHSOp] : zip_equal(AsmOperands, RHS.AsmOperands)) {
- if (LHSOp.Class->Kind != RHSOp.Class->Kind ||
- LHSOp.Class->Kind == ClassInfo::Token)
- if (*LHSOp.Class < *RHSOp.Class || *RHSOp.Class < *LHSOp.Class)
+ for (const auto &[LHSOp, RHSOp] : zip_equal(LHSOps, RHSOps)) {
+ if (LHSOp->Class->Kind != RHSOp->Class->Kind ||
+ LHSOp->Class->Kind == ClassInfo::Token) {
+ if (*LHSOp->Class < *RHSOp->Class || *RHSOp->Class < *LHSOp->Class)
return false;
+ }
}
// Otherwise, this operand could commute if all operands are equivalent, or
// there is a pair of operands that compare less than and a pair that
// compare greater than.
bool HasLT = false, HasGT = false;
- for (const auto &[LHSOp, RHSOp] : zip_equal(AsmOperands, RHS.AsmOperands)) {
- if (*LHSOp.Class < *RHSOp.Class)
+ for (const auto &[LHSOp, RHSOp] : zip_equal(LHSOps, RHSOps)) {
+ if (*LHSOp->Class < *RHSOp->Class)
HasLT = true;
- if (*RHSOp.Class < *LHSOp.Class)
+ if (*RHSOp->Class < *LHSOp->Class)
HasGT = true;
}
return HasLT == HasGT;
}
+ void print(raw_ostream &OS) const;
void dump() const;
private:
@@ -849,18 +874,22 @@ class AsmMatcherInfo {
} // end anonymous namespace
-#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
-LLVM_DUMP_METHOD void MatchableInfo::dump() const {
- errs() << TheDef->getName() << " -- "
- << "flattened:\"" << AsmString << "\"\n";
+void MatchableInfo::print(raw_ostream &OS) const {
+ OS << TheDef->getName() << " -- " << "flattened:\"" << AsmString << "\"\n";
+
+ OS << " variant: " << AsmVariantID << "\n";
- errs() << " variant: " << AsmVariantID << "\n";
+ for (const SubtargetFeatureInfo *F : RequiredFeatures)
+ OS << " feature: " << F->TheDef->getName() << "\n";
for (const auto &[Idx, Op] : enumerate(AsmOperands)) {
- errs() << " op[" << Idx << "] = " << Op.Class->ClassName << " - ";
- errs() << '\"' << Op.Token << "\"\n";
+ OS << " op[" << Idx << "] = " << Op.Class->ClassName << " - ";
+ OS << '\"' << Op.Token << "\"\n";
}
}
+
+#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
+LLVM_DUMP_METHOD void MatchableInfo::dump() const { print(errs()); }
#endif
static std::pair<StringRef, StringRef>
@@ -3386,7 +3415,32 @@ void AsmMatcherEmitter::run(raw_ostream &OS) {
});
// Check for ambiguous matchables.
- DEBUG_WITH_TYPE("ambiguous_instrs", {
+ bool ErrorOnAmbiguousMatchables =
+ AsmParser->getValueAsBit("ErrorOnAmbiguousMatchables");
+ bool CheckAmbiguousMatchables = ErrorOnAmbiguousMatchables;
+ DEBUG_WITH_TYPE("ambiguous_instrs", CheckAmbiguousMatchables = true);
+
+ ArrayRef<const Record *> ExclusivePredDecls =
+ Records.getAllDerivedDefinitions("MutuallyExclusiveAssemblerPredicates");
+ for (const Record *R : ExclusivePredDecls) {
+ const Record *PA = R->getValueAsDef("PredicateA");
+ for (const Record *PB : R->getValueAsListOfDefs("PredicatesB"))
+ if (PA == PB)
+ PrintFatalError(R->getLoc(),
+ "predicate '" + PA->getName() +
+ "' declared mutually exclusive with itself");
+ }
+
+ if (CheckAmbiguousMatchables) {
+ // Build a flat set of mutually exclusive assembler predicate pairs from
+ // MutuallyExclusiveAssemblerPredicates declarations. Pairs are normalized
+ // (smaller pointer first) for cheap symmetric lookup.
+ DenseSet<std::pair<const Record *, const Record *>> ExclusivePreds;
+ for (const Record *R : ExclusivePredDecls) {
+ const Record *PA = R->getValueAsDef("PredicateA");
+ for (const Record *PB : R->getValueAsListOfDefs("PredicatesB"))
+ ExclusivePreds.insert(std::minmax(PA, PB));
+ }
unsigned NumAmbiguous = 0;
for (auto I = Info.Matchables.begin(), E = Info.Matchables.end(); I != E;
++I) {
@@ -3394,19 +3448,28 @@ void AsmMatcherEmitter::run(raw_ostream &OS) {
const MatchableInfo &A = **I;
const MatchableInfo &B = **J;
- if (A.couldMatchAmbiguouslyWith(B, PreferSmallerInstructions)) {
- errs() << "warning: ambiguous matchables:\n";
- A.dump();
- errs() << "\nis incomparable with:\n";
- B.dump();
- errs() << "\n\n";
+ if (A.couldMatchAmbiguouslyWith(B, PreferSmallerInstructions,
+ ExclusivePreds)) {
+ StringLiteral Msg = "matchable is indistinguishable to the parser";
+ if (ErrorOnAmbiguousMatchables)
+ PrintError(A.TheDef->getLoc(), Msg);
+ else
+ PrintWarning(A.TheDef->getLoc(), Msg);
+ A.print(errs());
+ PrintNote(B.TheDef->getLoc(), "from this matchable");
+ B.print(errs());
++NumAmbiguous;
}
}
}
- if (NumAmbiguous)
- errs() << "warning: " << NumAmbiguous << " ambiguous matchables!\n";
- });
+ if (NumAmbiguous) {
+ Twine Msg = Twine(NumAmbiguous) + " ambiguous matchables found";
+ if (ErrorOnAmbiguousMatchables)
+ PrintFatalError(Msg);
+ else
+ PrintWarning(Msg);
+ }
+ }
// Compute the information on the custom operand parsing.
Info.buildOperandMatchInfo();
>From 91af5fb72ccb171a2c6a1dc0de8cffff5928208d Mon Sep 17 00:00:00 2001
From: Ivan Kosarev <ivan.kosarev at amd.com>
Date: Tue, 28 Jul 2026 13:20:56 +0100
Subject: [PATCH 2/2] Change a comment
---
llvm/include/llvm/Target/Target.td | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/llvm/include/llvm/Target/Target.td b/llvm/include/llvm/Target/Target.td
index 23939bacc5cb4..232a9452eafa2 100644
--- a/llvm/include/llvm/Target/Target.td
+++ b/llvm/include/llvm/Target/Target.td
@@ -1898,10 +1898,9 @@ class AsmParser {
// Number of required features (least first)
bit PreferSmallerInstructions = false;
- // ErrorOnAmbiguousMatchables - Set to true to make the assembler matcher
- // emitter produce a fatal error for any pair of matchables that are
- // indistinguishable to the asm parser. The check relies on mutually
- // exclusive assembler predicates declared using
+ // Set to true to make the assembler matcher emitter produce a fatal error
+ // for any pair of matchables that are indistinguishable to the asm parser.
+ // The check relies on mutually exclusive assembler predicates declared using
// MutuallyExclusiveAssemblerPredicates.
bit ErrorOnAmbiguousMatchables = false;
}
More information about the llvm-commits
mailing list