[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