[Mlir-commits] [llvm] [mlir] [OpenMP] Set a debug location for __kmpc_target_init/deinit calls (PR #217407)

Jason Van Beusekom llvmlistbot at llvm.org
Tue Aug 25 08:08:34 PDT 2026


https://github.com/Jason-Van-Beusekom updated https://github.com/llvm/llvm-project/pull/217407

>From 8553b39693bfb7d5bccb21edeb3479aad11294d9 Mon Sep 17 00:00:00 2001
From: Jason Van Beusekom <jason.van-beusekom at hpe.com>
Date: Wed, 19 Aug 2026 12:27:56 -0500
Subject: [PATCH 1/5] [OpenMP] Set a debug location for
 __kmpc_target_init/deinit calls

---
 llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp     | 20 +++++
 .../Frontend/OpenMPIRBuilderTest.cpp          | 79 +++++++++++++++++++
 2 files changed, 99 insertions(+)

diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 24d8169ffd663..2f7b67c9c54e3 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -8681,6 +8681,26 @@ void OpenMPIRBuilder::createTargetDeinit(const LocationDescription &Loc,
   if (!updateToLocation(Loc))
     return;
 
+  // Ensure a debug location so the inlinable __kmpc_target_deinit call verifies
+  // inside a kernel that has debug info.
+  if (!Builder.getCurrentDebugLocation())
+    if (DISubprogram *SP =
+            Builder.GetInsertBlock()->getParent()->getSubprogram())
+      Builder.SetCurrentDebugLocation(
+          DILocation::get(M.getContext(), SP->getLine(), /*Column=*/0, SP));
+
+  // The matching __kmpc_target_init call is emitted by createTargetInit before
+  // the kernel's subprogram is attached, so it can be left without a debug
+  // location; fix it up here, where the subprogram is available.
+  if (DebugLoc DbgLoc = Builder.getCurrentDebugLocation()) {
+    Function *Kernel = Builder.GetInsertBlock()->getParent();
+    Function *InitFn = getOrCreateRuntimeFunctionPtr(
+        omp::RuntimeFunction::OMPRTL___kmpc_target_init);
+    for (Instruction &I : Kernel->getEntryBlock())
+      if (auto *CI = dyn_cast<CallInst>(&I))
+        if (CI->getCalledFunction() == InitFn && !CI->getDebugLoc())
+          CI->setDebugLoc(DbgLoc);
+  }
   Function *Fn = getOrCreateRuntimeFunctionPtr(
       omp::RuntimeFunction::OMPRTL___kmpc_target_deinit);
 
diff --git a/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp b/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
index 084dcb0a5847f..8bc8ccdecd80f 100644
--- a/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
+++ b/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
@@ -6796,6 +6796,85 @@ TEST_F(OpenMPIRBuilderTest, TargetRegionDevice) {
             cast<ConstantInt>(ExecModeValue)->getZExtValue());
 }
 
+// When the enclosing kernel has debug info (-g offload), the inlinable
+// __kmpc_target_init/__kmpc_target_deinit calls must carry a !dbg location or
+// the verifier rejects the module during the device link.
+TEST_F(OpenMPIRBuilderTest, TargetInitDeinitDebugLoc) {
+  OpenMPIRBuilder OMPBuilder(*M);
+  OMPBuilder.setConfig(OpenMPIRBuilderConfig(
+      /*IsTargetDevice=*/true, false, false, false, false, false, false));
+  OMPBuilder.initialize();
+
+  FunctionType *KernelFTy =
+      FunctionType::get(Type::getVoidTy(Ctx), {PointerType::get(Ctx, 0)},
+                        /*isVarArg=*/false);
+  Function *Kernel = Function::Create(KernelFTy, Function::WeakODRLinkage,
+                                      "__omp_offloading_test_kernel", M.get());
+  BasicBlock *KernelBB = BasicBlock::Create(Ctx, "entry", Kernel);
+
+  IRBuilder<> Builder(KernelBB);
+  // No debug location and, initially, no subprogram: the state in which flang
+  // emits the init call.
+  Builder.SetCurrentDebugLocation(DebugLoc());
+  OpenMPIRBuilder::LocationDescription Loc({Builder.saveIP(), DebugLoc()});
+
+  OpenMPIRBuilder::TargetKernelDefaultAttrs DefaultAttrs = {
+      /*ExecFlags=*/omp::OMPTgtExecModeFlags::OMP_TGT_EXEC_MODE_GENERIC,
+      /*MaxTeams=*/{-1}, /*MinTeams=*/0, /*MaxThreads=*/{0}, /*MinThreads=*/0};
+
+  OpenMPIRBuilder::InsertPointTy AfterIP =
+      OMPBuilder.createTargetInit(Loc, DefaultAttrs);
+
+  // The init call exists but has no !dbg yet, since the kernel had no
+  // subprogram when it was emitted.
+  CallInst *InitCall = nullptr;
+  for (Instruction &I : Kernel->getEntryBlock())
+    if (auto *CI = dyn_cast<CallInst>(&I))
+      if (CI->getCalledFunction()->getName() == "__kmpc_target_init")
+        InitCall = CI;
+  ASSERT_NE(InitCall, nullptr);
+  EXPECT_FALSE(InitCall->getDebugLoc());
+
+  // Attach a subprogram now, as the flang debug pass does after outlining.
+  DIBuilder DIB(*M);
+  DIFile *File = DIB.createFile("kernel.f90", "/");
+  DICompileUnit *CU = DIB.createCompileUnit(
+      DISourceLanguageName(dwarf::DW_LANG_C), File, "flang", true, "", 0);
+  DISubroutineType *SPTy =
+      DIB.createSubroutineType(DIB.getOrCreateTypeArray({}));
+  DISubprogram *SP = DIB.createFunction(
+      CU, "test_kernel", "", File, 10, SPTy, 10, DINode::FlagZero,
+      DISubprogram::SPFlagDefinition | DISubprogram::SPFlagOptimized);
+  Kernel->setSubprogram(SP);
+  DIB.finalize();
+
+  // Emit the matching deinit, again with no current debug location.
+  Builder.restoreIP(AfterIP);
+  Builder.SetCurrentDebugLocation(DebugLoc());
+  OpenMPIRBuilder::LocationDescription DeinitLoc(
+      {Builder.saveIP(), DebugLoc()});
+  OMPBuilder.createTargetDeinit(DeinitLoc);
+  Builder.CreateRetVoid();
+
+  CallInst *DeinitCall = nullptr;
+  for (BasicBlock &FnBB : *Kernel)
+    for (Instruction &I : FnBB)
+      if (auto *CI = dyn_cast<CallInst>(&I))
+        if (CI->getCalledFunction()->getName() == "__kmpc_target_deinit")
+          DeinitCall = CI;
+  ASSERT_NE(DeinitCall, nullptr);
+
+  // Both runtime calls now carry a !dbg location scoped to the kernel's
+  // subprogram: the init call was patched retroactively, the deinit call got
+  // the fallback location directly.
+  ASSERT_TRUE(InitCall->getDebugLoc());
+  EXPECT_EQ(InitCall->getDebugLoc()->getScope()->getSubprogram(), SP);
+  ASSERT_TRUE(DeinitCall->getDebugLoc());
+  EXPECT_EQ(DeinitCall->getDebugLoc()->getScope()->getSubprogram(), SP);
+
+  EXPECT_FALSE(verifyFunction(*Kernel, &errs()));
+}
+
 TEST_F(OpenMPIRBuilderTest, TargetRegionSPMD) {
   using InsertPointTy = OpenMPIRBuilder::InsertPointTy;
   OpenMPIRBuilder OMPBuilder(*M);

>From 75410d1f5ad389fa1dc7b5d34ce605bdc2f759be Mon Sep 17 00:00:00 2001
From: Abid Qadeer <haqadeer at amd.com>
Date: Mon, 24 Aug 2026 17:32:35 +0100
Subject: [PATCH 2/5] [MLIR][OpenMP] Give the target init/deinit calls a debug
 location.

The __kmpc_target_init and __kmpc_target_deinit calls are emitted by
createOutlinedFunction, which resets the current debug location to empty
first because the location it inherits is scoped to the parent function
and would be in the wrong scope inside the newly created kernel. The
subprogram for the outlined function is only attached later, by the body
callback, so neither call ends up with a location.

That matters once a device runtime built with debug info is linked in:
the two runtime functions then have definitions with their own
DISubprogram, which makes these inlinable calls inside a function that
has debug info, and the verifier requires those to carry a !dbg. The
device link of a target region compiled with -g fails with

  inlinable function call in a function with debug info must have a !dbg
  location

convertOmpTarget already has the location these calls need. It saves
outlinedFnLoc, the location of the target construct scoped to the
subprogram that will be attached to the outlined function, before
re-scoping the builder to the parent function for the code it emits
there. Pass that down through createTarget so createOutlinedFunction can
install it instead of clearing the location, which gives everything
emitted into the outlined function a correct scope from the start.

The location cannot instead be carried in the LocationDescription that
createTarget already takes. That one is scoped to the parent function
and is what emitTargetCall uses for the host-side offloading code it
emits there, so re-scoping it to the kernel makes those instructions
fail the verifier with "!dbg attachment points at wrong subprogram for
function". Both locations are needed at once, so one of them has to be
passed separately.

The new parameter defaults to an empty location, so callers with no
subprogram to offer keep the current behaviour. MLIR's target lowering
is the only in-tree caller of createTarget; clang outlines target
regions through emitTargetRegionFunction with its own generate callback
and never reaches createOutlinedFunction, so the default only covers the
OMPIRBuilder unit tests.

Co-authored-by: Cursor <cursoragent at cursor.com>
(cherry picked from commit 8c7e2cfd92b7ccf33397f85c435e2565583e3428)
---
 .../llvm/Frontend/OpenMP/OMPIRBuilder.h       |  8 +++-
 llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp     | 20 ++++++---
 .../OpenMP/OpenMPToLLVMIRTranslation.cpp      | 12 +++++-
 .../omptarget-debug-runtime-call-loc.mlir     | 43 +++++++++++++++++++
 4 files changed, 74 insertions(+), 9 deletions(-)
 create mode 100644 mlir/test/Target/LLVMIR/omptarget-debug-runtime-call-loc.mlir

diff --git a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
index 3560cfef096fe..92410d4d9a7ff 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
@@ -3816,6 +3816,11 @@ class OpenMPIRBuilder {
   /// cgroup.
   /// \param DynCGroupMem The fallback mechanism to execute if the requested
   /// cgroup memory cannot be provided.
+  /// \param OutlinedFnLoc Location scoped to the DISubprogram that the caller
+  ///        will attach to the outlined function. \p Loc is scoped to the
+  ///        parent function, so it cannot be used for code emitted inside the
+  ///        outlined function. If this is empty, such code is emitted without a
+  ///        debug location.
   LLVM_ABI InsertPointOrErrorTy createTarget(
       const LocationDescription &Loc, bool IsOffloadEntry,
       OpenMPIRBuilder::InsertPointTy AllocaIP,
@@ -3831,7 +3836,8 @@ class OpenMPIRBuilder {
       const DependenciesInfo &Dependencies = {}, bool HasNowait = false,
       Value *DynCGroupMem = nullptr,
       omp::OMPDynGroupprivateFallbackType DynCGroupMemFallback =
-          omp::OMPDynGroupprivateFallbackType::Abort);
+          omp::OMPDynGroupprivateFallbackType::Abort,
+      DebugLoc OutlinedFnLoc = {});
 
   /// Returns __kmpc_for_static_init_* runtime function for the specified
   /// size \a IVSize and sign \a IVSigned. Will create a distribute call
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index 2f7b67c9c54e3..e2e1f247173e2 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -9233,7 +9233,8 @@ static Expected<Function *> createOutlinedFunction(
     const OpenMPIRBuilder::TargetKernelDefaultAttrs &DefaultAttrs,
     StringRef FuncName, SmallVectorImpl<Value *> &Inputs,
     OpenMPIRBuilder::TargetBodyGenCallbackTy &CBFunc,
-    OpenMPIRBuilder::TargetGenArgAccessorsCallbackTy &ArgAccessorFuncCB) {
+    OpenMPIRBuilder::TargetGenArgAccessorsCallbackTy &ArgAccessorFuncCB,
+    DebugLoc OutlinedFnLoc) {
   SmallVector<Type *> ParameterTypes;
   if (OMPBuilder.Config.isTargetDevice()) {
     // All parameters to target devices are passed as pointers
@@ -9280,8 +9281,11 @@ static Expected<Function *> createOutlinedFunction(
   // Save insert point.
   IRBuilder<>::InsertPointGuard IPG(Builder);
   // We will generate the entries in the outlined function but the debug
-  // location may still be pointing to the parent function. Reset it now.
-  Builder.SetCurrentDebugLocation(llvm::DebugLoc());
+  // location is still pointing to the parent function, which is the wrong
+  // scope. OutlinedFnLoc, when the caller provides one, is the same source
+  // position scoped to the subprogram that will be attached to the outlined
+  // function, so it is what everything emitted below needs.
+  Builder.SetCurrentDebugLocation(OutlinedFnLoc);
 
   // Generate the region into the function.
   BasicBlock *EntryBB = BasicBlock::Create(Builder.getContext(), "entry", Func);
@@ -9609,13 +9613,14 @@ static Error emitTargetOutlinedFunction(
     Function *&OutlinedFn, Constant *&OutlinedFnID,
     SmallVectorImpl<Value *> &Inputs,
     OpenMPIRBuilder::TargetBodyGenCallbackTy &CBFunc,
-    OpenMPIRBuilder::TargetGenArgAccessorsCallbackTy &ArgAccessorFuncCB) {
+    OpenMPIRBuilder::TargetGenArgAccessorsCallbackTy &ArgAccessorFuncCB,
+    DebugLoc OutlinedFnLoc) {
 
   OpenMPIRBuilder::FunctionGenCallback &&GenerateOutlinedFunction =
       [&](StringRef EntryFnName) {
         return createOutlinedFunction(OMPBuilder, Builder, DefaultAttrs,
                                       EntryFnName, Inputs, CBFunc,
-                                      ArgAccessorFuncCB);
+                                      ArgAccessorFuncCB, OutlinedFnLoc);
       };
 
   return OMPBuilder.emitTargetRegionFunction(
@@ -10236,7 +10241,8 @@ OpenMPIRBuilder::InsertPointOrErrorTy OpenMPIRBuilder::createTarget(
     OpenMPIRBuilder::TargetGenArgAccessorsCallbackTy ArgAccessorFuncCB,
     CustomMapperCallbackTy CustomMapperCB, const DependenciesInfo &Dependencies,
     bool HasNowait, Value *DynCGroupMem,
-    OMPDynGroupprivateFallbackType DynCGroupMemFallback) {
+    OMPDynGroupprivateFallbackType DynCGroupMemFallback,
+    DebugLoc OutlinedFnLoc) {
 
   if (!updateToLocation(Loc))
     return InsertPointTy();
@@ -10250,7 +10256,7 @@ OpenMPIRBuilder::InsertPointOrErrorTy OpenMPIRBuilder::createTarget(
   // and ArgAccessorFuncCB
   if (Error Err = emitTargetOutlinedFunction(
           *this, Builder, IsOffloadEntry, EntryInfo, DefaultAttrs, OutlinedFn,
-          OutlinedFnID, Inputs, CBFunc, ArgAccessorFuncCB))
+          OutlinedFnID, Inputs, CBFunc, ArgAccessorFuncCB, OutlinedFnLoc))
     return Err;
 
   // If we are not on the target device, then we need to generate code
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index 1a861767b5512..ea097ed6da45a 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -9296,6 +9296,16 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
         parentLLVMFn->getContext(), outlinedFnLoc.getLine(),
         outlinedFnLoc.getCol(), SP, outlinedFnLoc.getInlinedAt()));
 
+  // OMPIRBuilder emits runtime calls into the outlined function before bodyCB
+  // below gets a chance to attach the subprogram to it, so it needs the
+  // outlined function's location handed to it separately. Only pass it under
+  // the same condition that decides whether the subprogram is attached at all:
+  // a location may not be attached to an instruction in a function that has no
+  // subprogram.
+  llvm::DebugLoc outlinedFnDbgLoc;
+  if (outlinedFnLoc && parentLLVMFn->getSubprogram())
+    outlinedFnDbgLoc = outlinedFnLoc;
+
   llvm::OpenMPIRBuilder *ompBuilder = moduleTranslation.getOpenMPBuilder();
   bool isTargetDevice = ompBuilder->Config.isTargetDevice();
   bool isGPU = ompBuilder->Config.isGPU();
@@ -9695,7 +9705,7 @@ convertOmpTarget(Operation &opInst, llvm::IRBuilderBase &builder,
           ompLoc, isOffloadEntry, allocaIP, builder.saveIP(), deallocBlocks,
           info, entryInfo, defaultAttrs, runtimeAttrs, ifCond, kernelInput,
           genMapInfoCB, bodyCB, argAccessorCB, customMapperCB, dds,
-          targetOp.getNowait(), dynSizeVal, fallbackType);
+          targetOp.getNowait(), dynSizeVal, fallbackType, outlinedFnDbgLoc);
 
   if (failed(handleError(afterIP, opInst)))
     return failure();
diff --git a/mlir/test/Target/LLVMIR/omptarget-debug-runtime-call-loc.mlir b/mlir/test/Target/LLVMIR/omptarget-debug-runtime-call-loc.mlir
new file mode 100644
index 0000000000000..3baed4c9b36fb
--- /dev/null
+++ b/mlir/test/Target/LLVMIR/omptarget-debug-runtime-call-loc.mlir
@@ -0,0 +1,43 @@
+// RUN: mlir-translate -mlir-to-llvmir %s | FileCheck %s
+
+// The __kmpc_target_init and __kmpc_target_deinit calls are emitted into the
+// outlined kernel by OpenMPIRBuilder, which has no location of its own for
+// them: the translation's current location is scoped to the parent function at
+// that point. If they are left without a !dbg, the verifier rejects the module
+// once a device runtime that carries debug info is linked in, because they
+// become inlinable calls inside a function that has debug info. Check that both
+// get a location scoped to the outlined function's subprogram.
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<"dlti.alloca_memory_space", 5 : ui32>>, llvm.target_triple = "amdgcn-amd-amdhsa", omp.is_target_device = true} {
+  llvm.func @_QQmain() {
+    %0 = llvm.mlir.constant(1 : i32) : i32
+    %1 = llvm.alloca %0 x i32 : (i32) -> !llvm.ptr<5>
+    %ascast = llvm.addrspacecast %1 : !llvm.ptr<5> to !llvm.ptr
+    %9 = omp.map.info var_ptr(%ascast : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) name("") -> !llvm.ptr
+    omp.target kernel_type(generic) map_entries(%9 -> %arg0 : !llvm.ptr) {
+      %13 = llvm.mlir.constant(1 : i32) : i32
+      llvm.store %13, %arg0 : i32, !llvm.ptr loc(#loc2)
+      omp.terminator
+    } loc(#loc4)
+    llvm.return
+  } loc(#loc3)
+}
+#file = #llvm.di_file<"target.f90" in "">
+#cu = #llvm.di_compile_unit<id = distinct[0]<>,
+ sourceLanguage = DW_LANG_Fortran95, file = #file, isOptimized = false,
+ emissionKind = LineTablesOnly>
+#sp_ty = #llvm.di_subroutine_type<callingConvention = DW_CC_normal>
+#sp = #llvm.di_subprogram<id = distinct[1]<>, compileUnit = #cu, scope = #file,
+ name = "_QQmain", file = #file, subprogramFlags = "Definition", type = #sp_ty>
+#sp1 = #llvm.di_subprogram<id = distinct[2]<>, compileUnit = #cu, scope = #file,
+ name = "__omp_offloading_target", file = #file, subprogramFlags = "Definition",
+ type = #sp_ty>
+#loc1 = loc("target.f90":12:5)
+#loc2 = loc("target.f90":46:3)
+#loc3 = loc(fused<#sp>[#loc1])
+#loc4 = loc(fused<#sp1>[#loc1])
+
+// CHECK: call i32 @__kmpc_target_init({{.*}}), !dbg ![[LOC:[0-9]+]]
+// CHECK: call void @__kmpc_target_deinit(), !dbg ![[LOC]]
+// CHECK-DAG: ![[SP:[0-9]+]] = distinct !DISubprogram(name: "__omp_offloading_target"
+// CHECK-DAG: ![[LOC]] = !DILocation(line: 12, column: 5, scope: ![[SP]])

>From e6c32feaac2a666c28b371c86d0ec627cf89df0d Mon Sep 17 00:00:00 2001
From: Jason Van Beusekom <jason.van-beusekom at hpe.com>
Date: Mon, 24 Aug 2026 16:48:58 -0500
Subject: [PATCH 3/5] Update test , remove orig implmentation

(cherry picked from commit 5bf905ae82442c33bbc3e0a3e00ffb05871f60e1)
---
 llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp     | 20 ------
 .../Frontend/OpenMPIRBuilderTest.cpp          | 63 +++++++++----------
 2 files changed, 30 insertions(+), 53 deletions(-)

diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index e2e1f247173e2..f1d1029b78f43 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -8681,26 +8681,6 @@ void OpenMPIRBuilder::createTargetDeinit(const LocationDescription &Loc,
   if (!updateToLocation(Loc))
     return;
 
-  // Ensure a debug location so the inlinable __kmpc_target_deinit call verifies
-  // inside a kernel that has debug info.
-  if (!Builder.getCurrentDebugLocation())
-    if (DISubprogram *SP =
-            Builder.GetInsertBlock()->getParent()->getSubprogram())
-      Builder.SetCurrentDebugLocation(
-          DILocation::get(M.getContext(), SP->getLine(), /*Column=*/0, SP));
-
-  // The matching __kmpc_target_init call is emitted by createTargetInit before
-  // the kernel's subprogram is attached, so it can be left without a debug
-  // location; fix it up here, where the subprogram is available.
-  if (DebugLoc DbgLoc = Builder.getCurrentDebugLocation()) {
-    Function *Kernel = Builder.GetInsertBlock()->getParent();
-    Function *InitFn = getOrCreateRuntimeFunctionPtr(
-        omp::RuntimeFunction::OMPRTL___kmpc_target_init);
-    for (Instruction &I : Kernel->getEntryBlock())
-      if (auto *CI = dyn_cast<CallInst>(&I))
-        if (CI->getCalledFunction() == InitFn && !CI->getDebugLoc())
-          CI->setDebugLoc(DbgLoc);
-  }
   Function *Fn = getOrCreateRuntimeFunctionPtr(
       omp::RuntimeFunction::OMPRTL___kmpc_target_deinit);
 
diff --git a/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp b/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
index 8bc8ccdecd80f..5dfa7c0e0530d 100644
--- a/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
+++ b/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
@@ -6798,7 +6798,9 @@ TEST_F(OpenMPIRBuilderTest, TargetRegionDevice) {
 
 // When the enclosing kernel has debug info (-g offload), the inlinable
 // __kmpc_target_init/__kmpc_target_deinit calls must carry a !dbg location or
-// the verifier rejects the module during the device link.
+// the verifier rejects the module during the device link. createOutlinedFunction
+// installs the outlined function's location as the builder's current debug
+// location before emitting these calls, so both must inherit it.
 TEST_F(OpenMPIRBuilderTest, TargetInitDeinitDebugLoc) {
   OpenMPIRBuilder OMPBuilder(*M);
   OMPBuilder.setConfig(OpenMPIRBuilderConfig(
@@ -6812,30 +6814,7 @@ TEST_F(OpenMPIRBuilderTest, TargetInitDeinitDebugLoc) {
                                       "__omp_offloading_test_kernel", M.get());
   BasicBlock *KernelBB = BasicBlock::Create(Ctx, "entry", Kernel);
 
-  IRBuilder<> Builder(KernelBB);
-  // No debug location and, initially, no subprogram: the state in which flang
-  // emits the init call.
-  Builder.SetCurrentDebugLocation(DebugLoc());
-  OpenMPIRBuilder::LocationDescription Loc({Builder.saveIP(), DebugLoc()});
-
-  OpenMPIRBuilder::TargetKernelDefaultAttrs DefaultAttrs = {
-      /*ExecFlags=*/omp::OMPTgtExecModeFlags::OMP_TGT_EXEC_MODE_GENERIC,
-      /*MaxTeams=*/{-1}, /*MinTeams=*/0, /*MaxThreads=*/{0}, /*MinThreads=*/0};
-
-  OpenMPIRBuilder::InsertPointTy AfterIP =
-      OMPBuilder.createTargetInit(Loc, DefaultAttrs);
-
-  // The init call exists but has no !dbg yet, since the kernel had no
-  // subprogram when it was emitted.
-  CallInst *InitCall = nullptr;
-  for (Instruction &I : Kernel->getEntryBlock())
-    if (auto *CI = dyn_cast<CallInst>(&I))
-      if (CI->getCalledFunction()->getName() == "__kmpc_target_init")
-        InitCall = CI;
-  ASSERT_NE(InitCall, nullptr);
-  EXPECT_FALSE(InitCall->getDebugLoc());
-
-  // Attach a subprogram now, as the flang debug pass does after outlining.
+  // Attach a subprogram to the kernel, as the outlining does.
   DIBuilder DIB(*M);
   DIFile *File = DIB.createFile("kernel.f90", "/");
   DICompileUnit *CU = DIB.createCompileUnit(
@@ -6848,14 +6827,33 @@ TEST_F(OpenMPIRBuilderTest, TargetInitDeinitDebugLoc) {
   Kernel->setSubprogram(SP);
   DIB.finalize();
 
-  // Emit the matching deinit, again with no current debug location.
+  IRBuilder<> Builder(KernelBB);
+  // The kernel-scoped location createOutlinedFunction installs from
+  // OutlinedFnLoc before emitting the runtime calls.
+  DebugLoc DL = DILocation::get(Ctx, 10, /*Column=*/0, SP);
+  Builder.SetCurrentDebugLocation(DL);
+
+  OpenMPIRBuilder::TargetKernelDefaultAttrs DefaultAttrs = {
+      /*ExecFlags=*/omp::OMPTgtExecModeFlags::OMP_TGT_EXEC_MODE_GENERIC,
+      /*MaxTeams=*/{-1}, /*MinTeams=*/{0}, /*MaxThreads=*/{0},
+      /*MinThreads=*/{0}};
+
+  // createTargetInit/createTargetDeinit capture the builder's current debug
+  // location, mirroring how createOutlinedFunction emits them.
+  OpenMPIRBuilder::InsertPointTy AfterIP =
+      OMPBuilder.createTargetInit(Builder, DefaultAttrs);
   Builder.restoreIP(AfterIP);
-  Builder.SetCurrentDebugLocation(DebugLoc());
-  OpenMPIRBuilder::LocationDescription DeinitLoc(
-      {Builder.saveIP(), DebugLoc()});
-  OMPBuilder.createTargetDeinit(DeinitLoc);
+  Builder.SetCurrentDebugLocation(DL);
+  OMPBuilder.createTargetDeinit(Builder);
   Builder.CreateRetVoid();
 
+  CallInst *InitCall = nullptr;
+  for (Instruction &I : Kernel->getEntryBlock())
+    if (auto *CI = dyn_cast<CallInst>(&I))
+      if (CI->getCalledFunction()->getName() == "__kmpc_target_init")
+        InitCall = CI;
+  ASSERT_NE(InitCall, nullptr);
+
   CallInst *DeinitCall = nullptr;
   for (BasicBlock &FnBB : *Kernel)
     for (Instruction &I : FnBB)
@@ -6864,9 +6862,8 @@ TEST_F(OpenMPIRBuilderTest, TargetInitDeinitDebugLoc) {
           DeinitCall = CI;
   ASSERT_NE(DeinitCall, nullptr);
 
-  // Both runtime calls now carry a !dbg location scoped to the kernel's
-  // subprogram: the init call was patched retroactively, the deinit call got
-  // the fallback location directly.
+  // Both runtime calls carry a !dbg location scoped to the kernel's subprogram,
+  // so the kernel verifies.
   ASSERT_TRUE(InitCall->getDebugLoc());
   EXPECT_EQ(InitCall->getDebugLoc()->getScope()->getSubprogram(), SP);
   ASSERT_TRUE(DeinitCall->getDebugLoc());

>From 65d69bf52f89ce8bdc775798af6c517187b91779 Mon Sep 17 00:00:00 2001
From: Jason Van Beusekom <jason.van-beusekom at hpe.com>
Date: Mon, 24 Aug 2026 16:49:39 -0500
Subject: [PATCH 4/5] format

(cherry picked from commit 6601c06941bc9d6a387b3cf4e8ff14c3bdb9b4bf)
---
 llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp b/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
index 5dfa7c0e0530d..612116523d2dd 100644
--- a/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
+++ b/llvm/unittests/Frontend/OpenMPIRBuilderTest.cpp
@@ -6798,9 +6798,10 @@ TEST_F(OpenMPIRBuilderTest, TargetRegionDevice) {
 
 // When the enclosing kernel has debug info (-g offload), the inlinable
 // __kmpc_target_init/__kmpc_target_deinit calls must carry a !dbg location or
-// the verifier rejects the module during the device link. createOutlinedFunction
-// installs the outlined function's location as the builder's current debug
-// location before emitting these calls, so both must inherit it.
+// the verifier rejects the module during the device link.
+// createOutlinedFunction installs the outlined function's location as the
+// builder's current debug location before emitting these calls, so both must
+// inherit it.
 TEST_F(OpenMPIRBuilderTest, TargetInitDeinitDebugLoc) {
   OpenMPIRBuilder OMPBuilder(*M);
   OMPBuilder.setConfig(OpenMPIRBuilderConfig(

>From 08bcf00bbcd685cb2e117e278d47c8bd4a6096d7 Mon Sep 17 00:00:00 2001
From: Jason Van Beusekom <jason.van-beusekom at hpe.com>
Date: Tue, 25 Aug 2026 10:08:18 -0500
Subject: [PATCH 5/5] test update

---
 mlir/test/Target/LLVMIR/omptarget-debug-runtime-call-loc.mlir | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mlir/test/Target/LLVMIR/omptarget-debug-runtime-call-loc.mlir b/mlir/test/Target/LLVMIR/omptarget-debug-runtime-call-loc.mlir
index 3baed4c9b36fb..f558644d33aa8 100644
--- a/mlir/test/Target/LLVMIR/omptarget-debug-runtime-call-loc.mlir
+++ b/mlir/test/Target/LLVMIR/omptarget-debug-runtime-call-loc.mlir
@@ -13,7 +13,7 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<"dlti.alloca_memo
     %0 = llvm.mlir.constant(1 : i32) : i32
     %1 = llvm.alloca %0 x i32 : (i32) -> !llvm.ptr<5>
     %ascast = llvm.addrspacecast %1 : !llvm.ptr<5> to !llvm.ptr
-    %9 = omp.map.info var_ptr(%ascast : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) name("") -> !llvm.ptr
+    %9 = omp.map.info var_ptr(%ascast : !llvm.ptr, i32) map_clauses(tofrom) capture(ByRef) -> !llvm.ptr {name = ""}
     omp.target kernel_type(generic) map_entries(%9 -> %arg0 : !llvm.ptr) {
       %13 = llvm.mlir.constant(1 : i32) : i32
       llvm.store %13, %arg0 : i32, !llvm.ptr loc(#loc2)



More information about the Mlir-commits mailing list