Skip to content

IR/Verifier: Do not allow kernel to kernel calls. #144445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

jofrn
Copy link
Contributor

@jofrn jofrn commented Jun 16, 2025

Allowing a kernel to call another kernel is invalid. This extends the Verifier to recognize this fact. The calling convention is retrieved from the module because it may not be labeling the callsite.

Allowing a kernel to call another kernel is invalid. This extends
the Verifier to recognize this fact. The calling convention is
retrieved from the module because it may not be labeling the callsite.
@llvmbot
Copy link
Member

llvmbot commented Jun 16, 2025

@llvm/pr-subscribers-llvm-ir

@llvm/pr-subscribers-backend-amdgpu

Author: None (jofrn)

Changes

Allowing a kernel to call another kernel is invalid. This extends the Verifier to recognize this fact. The calling convention is retrieved from the module because it may not be labeling the callsite.


Full diff: https://github.com/llvm/llvm-project/pull/144445.diff

2 Files Affected:

  • (modified) llvm/lib/IR/Verifier.cpp (+12)
  • (added) llvm/test/Verifier/AMDGPU/kernel-recursivecall.ll (+9)
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 592bb6aa90613..179cb85ca04f4 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -3636,6 +3636,18 @@ void Verifier::visitCallBase(CallBase &Call) {
   Check(isCallableCC(Call.getCallingConv()),
         "calling convention does not permit calls", Call);
 
+  // Find the actual CC of the callee from the Module.
+  CallingConv::ID CalleeCC = Call.getParent()->getParent()->getParent()
+      ->getFunction(Call.getCalledFunction()->getName())->getCallingConv();
+  // Verify that a kernel does not call another kernel.
+  if (CalleeCC == CallingConv::AMDGPU_KERNEL ||
+      CalleeCC == CallingConv::SPIR_KERNEL) {
+    CallingConv::ID CallerCC = Call.getParent()->getParent()->getCallingConv();
+    Check(CallerCC != CallingConv::AMDGPU_KERNEL &&
+          CallerCC != CallingConv::SPIR_KERNEL,
+          "a kernel may not call a kernel", Call.getParent()->getParent());
+  }
+
   // Disallow passing/returning values with alignment higher than we can
   // represent.
   // FIXME: Consider making DataLayout cap the alignment, so this isn't
diff --git a/llvm/test/Verifier/AMDGPU/kernel-recursivecall.ll b/llvm/test/Verifier/AMDGPU/kernel-recursivecall.ll
new file mode 100755
index 0000000000000..83e8f6dadedfe
--- /dev/null
+++ b/llvm/test/Verifier/AMDGPU/kernel-recursivecall.ll
@@ -0,0 +1,9 @@
+; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
+
+define amdgpu_kernel void @kernel(ptr addrspace(1) %out, i32 %n) {
+entry:
+; CHECK: a kernel may not call a kernel
+; CHECK-NEXT: ptr @kernel
+  call void @kernel(ptr addrspace(1) %out, i32 %n)
+  ret void
+}

Copy link

github-actions bot commented Jun 16, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions cpp -- llvm/lib/IR/Verifier.cpp
View the diff from clang-format here.
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 69f05bf0b..2a1b39348 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -3622,11 +3622,15 @@ void Verifier::visitCallBase(CallBase &Call) {
           "Intrinsic called with incompatible signature", Call);
 
   // Find the actual CC of the callee from the Module.
-  CallingConv::ID CalleeCC = Call.getParent()->getParent()->getParent()
-      ->getFunction(Call.getCalledFunction()->getName())->getCallingConv();
+  CallingConv::ID CalleeCC =
+      Call.getParent()
+          ->getParent()
+          ->getParent()
+          ->getFunction(Call.getCalledFunction()->getName())
+          ->getCallingConv();
   // Verify if the calling convention of the callee is callable.
-  Check(isCallableCC(CalleeCC),
-        "calling convention does not permit calls", Call);
+  Check(isCallableCC(CalleeCC), "calling convention does not permit calls",
+        Call);
 
   // Disallow passing/returning values with alignment higher than we can
   // represent.

@shiltian
Copy link
Contributor

duplicate to #134910?

Copy link
Contributor

@arsenm arsenm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate?

@@ -0,0 +1,9 @@
; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-disable-output is canonical way to disable output, also this does not require AMDGPU

->getFunction(Call.getCalledFunction()->getName())->getCallingConv();
// Verify that a kernel does not call another kernel.
if (CalleeCC == CallingConv::AMDGPU_KERNEL ||
CalleeCC == CallingConv::SPIR_KERNEL) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spir_kernel case not tested, nor call sites

@jofrn jofrn force-pushed the verifier-kernel-call branch from e0208f1 to 1f46318 Compare June 17, 2025 14:02
Copy link
Contributor

@nikic nikic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wat?

@@ -3632,8 +3632,11 @@ void Verifier::visitCallBase(CallBase &Call) {
Check(Callee->getValueType() == FTy,
"Intrinsic called with incompatible signature", Call);

// Find the actual CC of the callee from the Module.
CallingConv::ID CalleeCC = Call.getParent()->getParent()->getParent()
->getFunction(Call.getCalledFunction()->getName())->getCallingConv();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very complicated way to spell Callee->getCallingConv() -- but note that Callee may be null here for indirect calls -- which is the cause of your four thousand or so test failures.

Copy link
Contributor Author

@jofrn jofrn Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it is, but we can have a call of the function without its CC specified. If its definition has a kernel CC, then the callsite may be treated as requiring the definition when it does not have it. If the Verifier checks from the module, then we will not error out in a location that requires the callsite to have the CC at its definition; we will error in the Verifier instead.

entry:
; CHECK: calling convention does not permit calls
; CHECK-NEXT: call void @kernel(ptr addrspace(1) %out, i32 %n)
call void @kernel(ptr addrspace(1) %out, i32 %n)
Copy link
Contributor

@shiltian shiltian Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This IR is malformed in the first place because it doesn't use the right CC for the callee.

Copy link
Contributor Author

@jofrn jofrn Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ends up getting the same error as your other one "Mark entry as invalid" if we do not have the CC match though, so it would be better to have this error in the Verifier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants