[llvm] [Offload][L0] Include context allocations in kernel indirect access flags (PR #229384)

Jan Trusiłło via llvm-commits llvm-commits at lists.llvm.org
Tue Oct 6 04:40:01 PDT 2026


https://github.com/311Volt created https://github.com/llvm/llvm-project/pull/229384

#224930 moved L0-backed liboffload allocations into the per-context allocators of `LevelZeroPluginContextTy`. However, `L0KernelTy::setIndirectFlags` was never updated to count flags from these new allocators, and as a result `olMemAlloc` allocations no longer count towards kernel indirect access flags. This is significant for libsycl - for instance, a kernel lambda is passed there as a single byval struct, and therefore all captured USM pointers are necessarily indirect access.

This bug was revealed by a buildbot failure when https://github.com/llvm/llvm-project/pull/227589 was merged and later reverted. The libsycl tests on the buildbot (Arc B580) only passed because the counters in `L0ContextTy::HostMemAllocator` are uninitialized and happened to contain leftover heap data, which set SHARED by accident. #227589 changed the size of L0ContextTy, the counters came out as zero, and 15 libsycl tests failed. That PR was reverted in #228485. Its change was correct and can reland once this one is in.

Zero-initialize the implicit arg counters, make `getIndirectFlags` aware of context-local allocators, and add a unit test where a kernel writes managed memory through a pointer held inside a by-value struct, the same pattern as a SYCL lambda.

>From 91ec309a1a30ae2de603f9892826aa6d88d31854 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Trusi=C5=82=C5=82o?= <jan.trusillo at intel.com>
Date: Tue, 6 Oct 2026 10:23:17 +0000
Subject: [PATCH] include context allocs in kernel indirect access flags

---
 .../level_zero/include/L0Kernel.h             |  5 +++-
 .../level_zero/include/L0Memory.h             |  2 +-
 .../level_zero/include/L0Plugin.h             | 10 +++++++
 .../level_zero/src/L0Kernel.cpp               |  7 ++++-
 .../OffloadAPI/device_code/CMakeLists.txt     |  2 ++
 .../OffloadAPI/device_code/byval_ptr.cpp      | 12 +++++++++
 .../OffloadAPI/kernel/olLaunchKernel.cpp      | 27 +++++++++++++++++++
 7 files changed, 62 insertions(+), 3 deletions(-)
 create mode 100644 offload/unittests/OffloadAPI/device_code/byval_ptr.cpp

diff --git a/offload/plugins-nextgen/level_zero/include/L0Kernel.h b/offload/plugins-nextgen/level_zero/include/L0Kernel.h
index d9bc1bf5d727f2a..1760a5846fa0709 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Kernel.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Kernel.h
@@ -23,6 +23,7 @@ namespace llvm::omp::target::plugin {
 class L0DeviceTy;
 class L0ProgramTy;
 class L0QueueTy;
+class LevelZeroPluginContextTy;
 struct L0LaunchEnvTy;
 
 /// Kernel properties.
@@ -64,7 +65,9 @@ class L0KernelTy : public GenericKernelTy {
   ze_group_size_t createKernelGroups(L0DeviceTy &L0Device, L0LaunchEnvTy &KEnv,
                                      uint32_t NumThreads[3],
                                      uint32_t NumBlocks[3]) const;
-  Error setIndirectFlags(L0DeviceTy &L0Device, L0LaunchEnvTy &KEnv) const;
+  Error setIndirectFlags(L0DeviceTy &L0Device,
+                         LevelZeroPluginContextTy &UserCtx,
+                         L0LaunchEnvTy &KEnv) const;
 
 public:
   /// Create a L0 kernel with a name and an execution mode.
diff --git a/offload/plugins-nextgen/level_zero/include/L0Memory.h b/offload/plugins-nextgen/level_zero/include/L0Memory.h
index 32bb0a5dc288c19..f7efc9cdd05b033 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Memory.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Memory.h
@@ -240,7 +240,7 @@ class MemAllocatorTy {
     /// Map from allocated pointer to allocation information.
     std::map<void *, MemAllocInfoTy> Map;
     /// Map from target alloc kind to number of implicit arguments.
-    std::array<uint32_t, MaxMemKind> NumImplicitArgs;
+    std::array<uint32_t, MaxMemKind> NumImplicitArgs{};
 
   public:
     /// Add allocation information to the map.
diff --git a/offload/plugins-nextgen/level_zero/include/L0Plugin.h b/offload/plugins-nextgen/level_zero/include/L0Plugin.h
index ac95287de23b047..fd5bc733ca8a85e 100644
--- a/offload/plugins-nextgen/level_zero/include/L0Plugin.h
+++ b/offload/plugins-nextgen/level_zero/include/L0Plugin.h
@@ -49,6 +49,16 @@ class LevelZeroPluginContextTy final : public PluginContextTy {
                          TargetAllocTy Kind) override;
   Expected<PluginAllocInfoTy> getAllocInfo(const void *Ptr) override;
 
+  /// Get kernel indirect access flags from all allocators in this context.
+  ze_kernel_indirect_access_flags_t getIndirectFlags() {
+    ze_kernel_indirect_access_flags_t Flags = 0;
+    for (auto &[Device, Allocator] : DeviceAllocators)
+      Flags |= Allocator->getIndirectFlags();
+    if (HostAllocator)
+      Flags |= HostAllocator->getIndirectFlags();
+    return Flags;
+  }
+
   /// Initialize per-plugin-context memory allocators. Runs the pool
   /// probe L0 calls up-front so the first user allocation is not delayed.
   Error initAllocators();
diff --git a/offload/plugins-nextgen/level_zero/src/L0Kernel.cpp b/offload/plugins-nextgen/level_zero/src/L0Kernel.cpp
index 52b1d267d05e455..23af19f6df61e58 100644
--- a/offload/plugins-nextgen/level_zero/src/L0Kernel.cpp
+++ b/offload/plugins-nextgen/level_zero/src/L0Kernel.cpp
@@ -12,6 +12,7 @@
 
 #include "L0Kernel.h"
 #include "L0Device.h"
+#include "L0Plugin.h"
 #include "L0Program.h"
 
 namespace llvm::omp::target::plugin {
@@ -102,11 +103,14 @@ ze_group_size_t L0KernelTy::createKernelGroups(L0DeviceTy &L0Device,
 }
 
 Error L0KernelTy::setIndirectFlags(L0DeviceTy &L0Device,
+                                   LevelZeroPluginContextTy &UserCtx,
                                    L0LaunchEnvTy &KEnv) const {
   // Set Kernel Indirect flags.
   ze_kernel_indirect_access_flags_t Flags = 0;
   Flags |= L0Device.getMemAllocator(TARGET_ALLOC_HOST).getIndirectFlags();
   Flags |= L0Device.getMemAllocator(TARGET_ALLOC_DEVICE).getIndirectFlags();
+  // Allocations made through a liboffload context live in its own allocators.
+  Flags |= UserCtx.getIndirectFlags();
 
   if (KEnv.KernelPR.IndirectAccessFlags != Flags) {
     // Combine with common access flags.
@@ -193,7 +197,8 @@ Error L0KernelTy::launchImpl(GenericDeviceTy &GenericDevice,
         "expected by the kernel (%u)",
         LaunchArgs.NumArgs, KernelPR.NumKernelArgs);
 
-  if (auto Err = setIndirectFlags(L0Device, KEnv))
+  assert(Queue->getUserCtx() && "Queue must belong to a plugin context");
+  if (auto Err = setIndirectFlags(L0Device, *Queue->getUserCtx(), KEnv))
     return Err;
 
   // The next call should unlock the KernelLock internally.
diff --git a/offload/unittests/OffloadAPI/device_code/CMakeLists.txt b/offload/unittests/OffloadAPI/device_code/CMakeLists.txt
index 92112484b3df791..2f9dd252120b5ba 100644
--- a/offload/unittests/OffloadAPI/device_code/CMakeLists.txt
+++ b/offload/unittests/OffloadAPI/device_code/CMakeLists.txt
@@ -15,6 +15,7 @@ add_offload_test_device_code(global_ctor.cpp global_ctor)
 add_offload_test_device_code(global_dtor.cpp global_dtor)
 add_offload_test_device_code(sequence.cpp sequence)
 add_offload_test_device_code(gridsize.cpp gridsize)
+add_offload_test_device_code(byval_ptr.cpp byval_ptr -O3)
 
 add_custom_target(offload_device_binaries DEPENDS
     foo.bin
@@ -33,5 +34,6 @@ add_custom_target(offload_device_binaries DEPENDS
     global_dtor.bin
     sequence.bin
     gridsize.bin
+    byval_ptr.bin
 )
 set(OFFLOAD_TEST_DEVICE_CODE_PATH ${CMAKE_CURRENT_BINARY_DIR} PARENT_SCOPE)
diff --git a/offload/unittests/OffloadAPI/device_code/byval_ptr.cpp b/offload/unittests/OffloadAPI/device_code/byval_ptr.cpp
new file mode 100644
index 000000000000000..e4731267383f446
--- /dev/null
+++ b/offload/unittests/OffloadAPI/device_code/byval_ptr.cpp
@@ -0,0 +1,12 @@
+#include <gpuintrin.h>
+#include <stdint.h>
+
+// The output pointer is only reachable through a by-value struct, like a
+// captured SYCL kernel lambda, so the device accesses it indirectly.
+struct Wrapper {
+  uint32_t *Out;
+};
+
+extern "C" __gpu_kernel void byval_ptr(Wrapper W) {
+  W.Out[__gpu_thread_id(0)] = __gpu_thread_id(0);
+}
diff --git a/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp b/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp
index f9cc29d1e9d0c24..f8883e0659c1749 100644
--- a/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp
+++ b/offload/unittests/OffloadAPI/kernel/olLaunchKernel.cpp
@@ -28,6 +28,7 @@ KERNEL_TEST(SingleCounterSyncEvent, single_counter)
 KERNEL_TEST(GlobalCtor, global_ctor)
 KERNEL_TEST(GlobalDtor, global_dtor)
 KERNEL_TEST(GridSize, gridsize)
+KERNEL_TEST(ByValPtr, byval_ptr)
 
 struct LaunchMultipleKernelTestBase : LaunchKernelTestBase {
   void SetUpKernels(const char *program, std::vector<const char *> kernels) {
@@ -167,6 +168,32 @@ TEST_P(olLaunchKernelCompositeTest, Success) {
   ASSERT_SUCCESS(olMemFree(Context, Mem));
 }
 
+struct Wrapper {
+  uint32_t *Out;
+};
+
+TEST_P(olLaunchKernelByValPtrTest, Success) {
+  void *Mem;
+  ASSERT_SUCCESS(olMemAlloc(Context, Device, OL_ALLOC_TYPE_MANAGED,
+                            LaunchArgs.GroupSize.x * sizeof(uint32_t), &Mem));
+
+  Wrapper W{(uint32_t *)Mem};
+
+  void *ArgPtrs[] = {&W};
+  size_t ArgSizes[] = {sizeof(W)};
+
+  ASSERT_SUCCESS(olLaunchKernel(Queue, Device, Kernel, &LaunchArgs, nullptr,
+                                std::size(ArgPtrs), ArgPtrs, ArgSizes));
+
+  ASSERT_SUCCESS(olSyncQueue(Queue));
+
+  uint32_t *Data = (uint32_t *)Mem;
+  for (uint32_t i = 0; i < LaunchArgs.GroupSize.x; i++)
+    ASSERT_EQ(Data[i], i);
+
+  ASSERT_SUCCESS(olMemFree(Context, Mem));
+}
+
 TEST_P(olLaunchKernelFooTest, SuccessSynchronous) {
   void *Mem;
   ASSERT_SUCCESS(olMemAlloc(Context, Device, OL_ALLOC_TYPE_MANAGED,



More information about the llvm-commits mailing list