Hi all,
It seems LLVM allows defining a non-overloaded intrinsic where the name has a prefix that matches the name of an overloaded intrinsic. In some cases, that could lead to incorrect intrinsic name lookup and causing the intrinsic to be considered an unknown intrinsic when ideally it shouldn’t.
The patch below demonstrates the issue:
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 0a74a217a5f0..4de6bdaa45bd 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -2753,6 +2753,9 @@ def int_experimental_convergence_anchor
def int_experimental_convergence_loop
: DefaultAttrsIntrinsic<[llvm_token_ty], [], [IntrNoMem, IntrConvergent]>;
+def int_foo : Intrinsic<[llvm_anyint_ty], [llvm_anyint_ty]>;
+def int_foo_i32 : Intrinsic<[llvm_i32_ty], []>;
+
//===----------------------------------------------------------------------===//
// Target-specific intrinsics
//===----------------------------------------------------------------------===//
diff --git a/llvm/unittests/IR/IntrinsicsTest.cpp b/llvm/unittests/IR/IntrinsicsTest.cpp
index 5916a194f76d..10a0f5adec8f 100644
--- a/llvm/unittests/IR/IntrinsicsTest.cpp
+++ b/llvm/unittests/IR/IntrinsicsTest.cpp
@@ -25,6 +25,8 @@
#include "llvm/IR/IntrinsicsS390.h"
#include "llvm/IR/IntrinsicsX86.h"
#include "llvm/IR/Module.h"
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/Support/SourceMgr.h"
#include "gtest/gtest.h"
using namespace llvm;
@@ -165,4 +167,16 @@ TEST_F(IntrinsicsTest, InstrProfInheritance) {
}
}
+TEST(IntrinsicOverload, Test) {
+ const char *IR = R"(
+ declare i32 @llvm.foo.i16.i16()
+ declare i32 @llvm.foo.i32()
+ declare i32 @llvm.foo.i32.i32()
+ )";
+ LLVMContext Ctx;
+ SMDiagnostic Error;
+ auto Mod = parseAssemblyString(IR, Error, Ctx);
+ for (const Function &F : Mod->getFunctionList())
+ errs() << F.getName() << ": " << F.getIntrinsicID() << '\n';
+}
} // end namespace
Running this test gives the following output:
[----------] 1 test from IntrinsicOverload
[ RUN ] IntrinsicOverload.Test
llvm.foo.i16.i16: 169
llvm.foo.i32: 170
llvm.foo.i32.i32: 0
[ OK ] IntrinsicOverload.Test (0 ms)
[----------] 1 test from IntrinsicOverload (0 ms total)
Here int_foo_i32
share has same prefix name as int_foo
, so when LLVM looks up the name “llvm.foo.i32.i32” it first matches against “llvm.foo.i32” in Function::lookupIntrinsicID
, and since llvm.foo.i32 is not overloaded and it’s not an exact match, return Intrinsic::not_intrinsic
.
There may be a way to change the lookupIntrinsicID
function to instead try to find an overloaded intrinsic that can match the name and return it instead (may be by segregating the lookup table into one for non-overloaded ones and one for overloaded ones). However, it seems that even if that is fixed, having one intrinsic (overloaded or not) share a prefix with another overloaded one can be confusing as its sort of ambiguous which intrinsic it is.
So, I am proposing that we disallow this case, by adding additional checks in the intrinsic emitter backend to detect this. I am not sure if any upstream backends already have this problem in their intrinsics (hopefully not). Note that this will disallow some cases like the same patch but with the overloaded llvm.foo with just one overload type and hence just one type in the mangling suffix. But may be that’s ok as it leads to better clarify (i.e., any llvm.foo.* should be an overload of the llvm.foo intrinsic and won’t match something else inadvertently).
For clarify, the exact check will be that if there is an overloaded intrinsic defined, any other intrinsic with the overload name as a prefix will not be allowed and will result in an error from the intrinsic emitter backend.
Thanks,
Rahul