Skip to content

Commit 46345fb

Browse files
committed
[LLVM][TableGen] Check overloaded intrinsic mangling suffix conflicts
1 parent df4d7d3 commit 46345fb

File tree

5 files changed

+237
-4
lines changed

5 files changed

+237
-4
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: llvm-tblgen -gen-intrinsic-enums -I %p/../../include %s -DTEST_INTRINSICS_SUPPRESS_DEFS | FileCheck %s
2+
// RUN: not llvm-tblgen -gen-intrinsic-enums -I %p/../../include %s -DTEST_INTRINSICS_SUPPRESS_DEFS -DCONFLICT 2>&1 | FileCheck %s -DFILE=%s --check-prefix=CHECK-CONFLICT
3+
4+
5+
include "llvm/IR/Intrinsics.td"
6+
// CHECK: foo = 1,
7+
def int_foo : Intrinsic<[llvm_any_ty]>;
8+
9+
// No conflicts, since .bar is not a vaid mangled type.
10+
// CHECK: foo_bar,
11+
def int_foo_bar : Intrinsic<[llvm_i32_ty]>;
12+
13+
// CHECK: foo_bar_f32,
14+
def int_foo_bar_f32 : Intrinsic<[llvm_i32_ty]>;
15+
16+
#ifdef CONFLICT
17+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.a3` cannot share prefix `llvm.foo.a3` with another overloaded intrinsic `llvm.foo`
18+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.bf16` cannot share prefix `llvm.foo.bf16` with another overloaded intrinsic `llvm.foo`
19+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.f32` cannot share prefix `llvm.foo.f32` with another overloaded intrinsic `llvm.foo`
20+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.f64` cannot share prefix `llvm.foo.f64` with another overloaded intrinsic `llvm.foo`
21+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.f_3` cannot share prefix `llvm.foo.f_3` with another overloaded intrinsic `llvm.foo`
22+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.i65` cannot share prefix `llvm.foo.i65` with another overloaded intrinsic `llvm.foo`
23+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.i65.bar` cannot share prefix `llvm.foo.i65` with another overloaded intrinsic `llvm.foo`
24+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.p3` cannot share prefix `llvm.foo.p3` with another overloaded intrinsic `llvm.foo`
25+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.s_3` cannot share prefix `llvm.foo.s_3` with another overloaded intrinsic `llvm.foo`
26+
// CHECK-CONFLICT: error: intrinsic `llvm.foo.v4` cannot share prefix `llvm.foo.v4` with another overloaded intrinsic `llvm.foo`
27+
// CHECK-CONFLICT: 10 errors
28+
// CHECK-CONFLICT-NOT: error
29+
30+
// Confcts due to suffix being a posssible mangled type
31+
def int_foo_f32 : Intrinsic<[llvm_i32_ty]>;
32+
def int_foo_f64 : Intrinsic<[llvm_i32_ty]>;
33+
def int_foo_bf16: Intrinsic<[llvm_i32_ty]>;
34+
def int_foo_p3 : Intrinsic<[llvm_i32_ty]>;
35+
def int_foo_a3 : Intrinsic<[llvm_i32_ty]>;
36+
def int_foo_v4 : Intrinsic<[llvm_i32_ty]>;
37+
def int_foo_func : Intrinsic<[llvm_i32_ty], [], [], "llvm.foo.f_3">;
38+
def int_foo_struct : Intrinsic<[llvm_i32_ty], [], [], "llvm.foo.s_3">;
39+
def int_foo_t3 : Intrinsic<[llvm_i32_ty]>;
40+
def int_foo_i65 : Intrinsic<[llvm_i32_ty]>;
41+
def int_foo_i65_bar : Intrinsic<[llvm_i32_ty]>;
42+
43+
#endif

llvm/unittests/IR/IntrinsicsTest.cpp

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class IntrinsicsTest : public ::testing::Test {
6363
};
6464

6565
TEST(IntrinsicNameLookup, Basic) {
66-
static constexpr const char *const NameTable1[] = {
66+
static constexpr const char *const NameTable[] = {
6767
"llvm.foo", "llvm.foo.a", "llvm.foo.b", "llvm.foo.b.a", "llvm.foo.c",
6868
};
6969

@@ -74,15 +74,74 @@ TEST(IntrinsicNameLookup, Basic) {
7474
};
7575

7676
for (const auto &[Name, ExpectedIdx] : Tests) {
77-
int Idx = Intrinsic::lookupLLVMIntrinsicByName(NameTable1, Name);
77+
int Idx = Intrinsic::lookupLLVMIntrinsicByName(NameTable, Name);
7878
EXPECT_EQ(ExpectedIdx, Idx);
7979
if (!StringRef(Name).starts_with("llvm.foo"))
8080
continue;
81-
Idx = Intrinsic::lookupLLVMIntrinsicByName(NameTable1, Name, "foo");
81+
Idx = Intrinsic::lookupLLVMIntrinsicByName(NameTable, Name, "foo");
8282
EXPECT_EQ(ExpectedIdx, Idx);
8383
}
8484
}
8585

86+
// Test case to demonstrate potential conflicts between overloaded and non-
87+
// overloaded intrinsics. The name match works by essentially dividing then
88+
// name into . separated components and doing successive search for each
89+
// component. When a search fails, the lowest component of the matching
90+
// range for the previous component is returned.
91+
TEST(IntrinsicNameLookup, OverloadConflict) {
92+
// Assume possible mangled type strings are just .f32 and .i32.
93+
static constexpr const char *const NameTable[] = {
94+
"llvm.foo",
95+
"llvm.foo.f32",
96+
"llvm.foo.i32",
97+
};
98+
99+
// Here, first we match llvm.foo and our search window is [0,2]. Then we try
100+
// to match llvm.foo.f64 and there is no match, so it returns the low of the
101+
// last match. So this lookup works as expected.
102+
int Idx = Intrinsic::lookupLLVMIntrinsicByName(NameTable, "llvm.foo.f64");
103+
EXPECT_EQ(Idx, 0);
104+
105+
// Now imagine if llvm.foo has 2 mangling suffixes, .f32 and .f64. The search
106+
// will first match llvm.foo to [0, 2] and then llvm.foo.f32 to [1,1] and then
107+
// not find any match for llvm.foo.f32.f64. So it will return the low of the
108+
// last match, which is llvm.foo.f32. However, the intent was to match
109+
// llvm.foo. So the presence of llvm.foo.f32 eliminated the possibility of
110+
// matching with llvm.foo. So it seems if we have an intrinsic llvm.foo,
111+
// another one with the same suffix and a single .suffix is not going to
112+
// cause problems. If there exists another one with 2 or more suffixes,
113+
// .suffix0 and .suffix1, its possible that the mangling suffix for llvm.foo
114+
// might match with .suffix0 and then the match will fail to match llvm.foo.
115+
// .suffix1 won't be a problem because its the last one so the matcher will
116+
// try an exact match (in which case exact name in the table was searched for,
117+
// so its expected to match that entry).
118+
//
119+
// This example leads the the following rule: if we have an overloaded
120+
// intrinsic with name `llvm.foo` and another one with same prefix and one or
121+
// more suffixes, `llvm.foo[.<suffixN>]+`, then the name search will try to
122+
// first match against suffix0, then suffix1 etc. If suffix0 can match a
123+
// mangled type, then the search for an `llvm.foo` with a mangling suffix can
124+
// match against suffix0, preventing a match with `llvm.foo`. If suffix0
125+
// cannot match a mangled type, then that cannot happen, so we do not need to
126+
// check for later suffixes. Generalizing, the `llvm.foo[.suffixN]+` will
127+
// cause a conflict if the first suffix (.suffix0) can match a mangled type
128+
// (and then we do not need to check later suffixes) and will not cause a
129+
// conflict if it cannot (and then again, we do not need to check for later
130+
// suffixes.)
131+
Idx = Intrinsic::lookupLLVMIntrinsicByName(NameTable, "llvm.foo.f32.f64");
132+
EXPECT_EQ(Idx, 1);
133+
134+
// Here .bar and .f33 do not conflict with the manging suffixes, so the search
135+
// should match against llvm.foo.
136+
static constexpr const char *const NameTable1[] = {
137+
"llvm.foo",
138+
"llvm.foo.bar",
139+
"llvm.foo.f33",
140+
};
141+
Idx = Intrinsic::lookupLLVMIntrinsicByName(NameTable1, "llvm.foo.f32.f64");
142+
EXPECT_EQ(Idx, 0);
143+
}
144+
86145
// Tests to verify getIntrinsicForClangBuiltin.
87146
TEST(IntrinsicNameLookup, ClangBuiltinLookup) {
88147
using namespace Intrinsic;

llvm/utils/TableGen/Basic/CodeGenIntrinsics.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ CodeGenIntrinsicTable::CodeGenIntrinsicTable(const RecordKeeper &RC) {
7676

7777
CheckDuplicateIntrinsics();
7878
CheckTargetIndependentIntrinsics();
79+
CheckOverloadSuffixConflicts();
7980
}
8081

8182
// Check for duplicate intrinsic names.
@@ -124,6 +125,132 @@ void CodeGenIntrinsicTable::CheckTargetIndependentIntrinsics() const {
124125
}
125126
}
126127

128+
// Return true if the given Suffix looks like mangled type. Note that this
129+
// check is conservative, but allows all existing LLVM intrinsic suffixes to be
130+
// consider as not looking like a mangling suffix.
131+
static bool DoesSuffixLookLikeMangledType(StringRef Suffix) {
132+
// Try to match against possible mangling suffixes for various types.
133+
// See getMangledTypeStr() for the mangling suffixes possible. It includes
134+
// pointer : p[0-9]+
135+
// array : a[0-9]+[.+]
136+
// struct: : s_/sl_[.+]
137+
// function : f_[.+]
138+
// vector : v/nxv[0-9]+[.+]
139+
// target type : t[.*]
140+
// integer : i[0-9]+
141+
// named types : See `NamedTypes` below.
142+
143+
// Match anything with an _, so match function and struct types.
144+
if (Suffix.contains('_'))
145+
return true;
146+
147+
// [a|v][0-9|$][.*] // $ is end of string.
148+
if (is_contained("av", Suffix[0]) &&
149+
(Suffix.size() == 1 || isDigit(Suffix[1])))
150+
return true;
151+
152+
// nxv[0-9|$][.*]
153+
if (Suffix.starts_with("nxv") && (Suffix.size() == 3 || isDigit(Suffix[3])))
154+
return true;
155+
// t[.*]
156+
if (Suffix.starts_with('t'))
157+
return false;
158+
159+
// [p|i][0-9]+
160+
if ((Suffix[0] == 'i' || Suffix[0] == 'p') &&
161+
all_of(Suffix.drop_front(), isDigit))
162+
return true;
163+
164+
// Match one of the named types.
165+
StringLiteral NamedTypes[] = {"isVoid", "Metadata", "f16", "f32",
166+
"f64", "f80", "f128", "bf16",
167+
"ppcf128", "x86amx"};
168+
return is_contained(NamedTypes, Suffix);
169+
}
170+
171+
// Check for conflicts with overloaded intrinsics. If there exists an overloaded
172+
// intrinsic with base name `llvm.target.foo`, LLVM will add a mangling suffix
173+
// to it to encode the overload types. This mangling suffix is 1 or more .
174+
// prefixed mangled type string as defined in `getMangledTypeStr`. If there
175+
// exists another intrinsic `llvm.target.foo[.<suffixN>]+`, which has the same
176+
// prefix as the overloaded intrinsic, its possible that there may be a name
177+
// conflict with the overloaded intrinsic and either one may interfere with name
178+
// lookup for the other, leading to wrong intrinsic ID being assigned.
179+
//
180+
// The actual name lookup in the intrinsic name table is done by a search
181+
// on each successive . separted component of the intrinsic name (see
182+
// `lookupLLVMIntrinsicByName`). Consider first the case where there exists a
183+
// non-overloaded intrinsic `llvm.target.foo[.suffix]+`. For the non-overloaded
184+
// intrinsics, the name lookup is an exact match, so the presence of the
185+
// overloaded intrinsic with the same prefix will not interfere with the
186+
// search. However, a lookup intended to match the overloaded intrinsic might be
187+
// affected by the presence of another entry in the name table with the same
188+
// prefix. See the `OverloadConflict` sub-test in IntrinsicsTest.cpp to
189+
// demonstrate the cases where there is a conflict and for the exact check
190+
// (replicated below) for when the conflict can or cannot happen.
191+
//
192+
// Since LLVM's name lookup first selects the target specific (or target
193+
// independent) slice of the name table to look into, intrinsics in 2 different
194+
// slices cannot conflict with each other. Within a specific slice,
195+
// if we have an overloaded intrinsic with name `llvm.target.foo` and another
196+
// one with same prefix and one or more suffixes `llvm.target.foo[.<suffixN>]+`,
197+
// then the name search will try to first match against suffix0, then suffix1
198+
// etc. If suffix0 can match a mangled type, then the search for an
199+
// `llvm.target.foo` with a mangling suffix can match against suffix0,
200+
// preventing a match with `llvm.target.foo`. If suffix0 cannot match a mangled
201+
// type, then that cannot happen, so we do not need to check for later suffixes.
202+
//
203+
// Generalizing, the `llvm.target.foo[.suffixN]+` will cause a conflict if the
204+
// first suffix (.suffix0) can match a mangled type (and then we do not need to
205+
// check later suffixes) and will not cause a conflict if it cannot (and then
206+
// again, we do not need to check for later suffixes.)
207+
void CodeGenIntrinsicTable::CheckOverloadSuffixConflicts() const {
208+
for (const TargetSet &Set : Targets) {
209+
const CodeGenIntrinsic *Overloaded = nullptr;
210+
for (const CodeGenIntrinsic &Int : (*this)[Set]) {
211+
// If we do not have an overloaded intrinsic to check against, nothing
212+
// to do except potentially identifying this as a candidate for checking
213+
// against in future iteration.
214+
if (!Overloaded) {
215+
if (Int.isOverloaded)
216+
Overloaded = &Int;
217+
continue;
218+
}
219+
220+
StringRef Name = Int.Name;
221+
StringRef OverloadName = Overloaded->Name;
222+
// If we have an overloaded intrinsic to check again, check if its name is
223+
// a proper prefix of this intrinsic.
224+
if (Name.starts_with(OverloadName) && Name[OverloadName.size()] == '.') {
225+
// If yes, verify suffixes and flag an error.
226+
StringRef Suffixes = Name.drop_front(OverloadName.size() + 1);
227+
228+
// Only need to look at the first suffix.
229+
StringRef Suffix0 = Suffixes.split('.').first;
230+
231+
if (!DoesSuffixLookLikeMangledType(Suffix0))
232+
continue;
233+
234+
unsigned SuffixSize = OverloadName.size() + 1 + Suffix0.size();
235+
// If suffix looks like mangling suffix, flag it as an error.
236+
PrintError(Int.TheDef->getLoc(),
237+
"intrinsic `" + Name + "` cannot share prefix `" +
238+
Name.take_front(SuffixSize) +
239+
"` with another overloaded intrinsic `" + OverloadName +
240+
"`");
241+
PrintNote(Overloaded->TheDef->getLoc(),
242+
"Overloaded intrinsic `" + OverloadName + "` defined here");
243+
continue;
244+
}
245+
246+
// If we find an intrinsic that is not a proper prefix, any later
247+
// intrinsic is also not going to be a proper prefix, so invalidate the
248+
// overloaded to check against.
249+
Overloaded = nullptr;
250+
}
251+
}
252+
}
253+
127254
CodeGenIntrinsic &CodeGenIntrinsicMap::operator[](const Record *Record) {
128255
if (!Record->isSubClassOf("Intrinsic"))
129256
PrintFatalError("Intrinsic defs should be subclass of 'Intrinsic' class");

llvm/utils/TableGen/Basic/CodeGenIntrinsics.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,17 @@ class CodeGenIntrinsicTable {
186186
auto begin() const { return Intrinsics.begin(); }
187187
auto end() const { return Intrinsics.end(); }
188188
CodeGenIntrinsic &operator[](size_t Pos) { return Intrinsics[Pos]; }
189+
ArrayRef<CodeGenIntrinsic> operator[](const TargetSet &Set) const {
190+
return ArrayRef(&Intrinsics[Set.Offset], Set.Count);
191+
}
189192
const CodeGenIntrinsic &operator[](size_t Pos) const {
190193
return Intrinsics[Pos];
191194
}
192195

193196
private:
194197
void CheckDuplicateIntrinsics() const;
195198
void CheckTargetIndependentIntrinsics() const;
199+
void CheckOverloadSuffixConflicts() const;
196200
};
197201

198202
// This class builds `CodeGenIntrinsic` on demand for a given Def.

llvm/utils/TableGen/IntrinsicEmitter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ void IntrinsicEmitter::EmitEnumInfo(const CodeGenIntrinsicTable &Ints,
154154

155155
OS << "// Enum values for intrinsics.\n";
156156
bool First = true;
157-
for (const auto &Int : ArrayRef(&Ints[Set->Offset], Set->Count)) {
157+
for (const auto &Int : Ints[*Set]) {
158158
OS << " " << Int.EnumName;
159159

160160
// Assign a value to the first intrinsic in this target set so that all

0 commit comments

Comments
 (0)