Skip to content

Commit 61fdeb8

Browse files
committed
[UR][L0v2] Implement IPC event sharing in the Level Zero v2 adapter
Adds the producer/consumer side of UR's IPC event extension on top of Level Zero's counter-based event IPC APIs (zeEventCounterBased{Create, Get/Open/CloseIpcHandle}). * urEventCreateExp handles UR_EXP_EVENT_FLAG_IPC_EXP only; the non-IPC reusable-event path is left to PR #22349. IPC + profiling is rejected. * IPC events are not pooled. The primary use case is IPC + reusable events, where the user reuses one event many times, so pool reuse buys little; and sharing pooled events across processes is unsafe because a recycled event hands one process state another process is still relying on. The producer wraps a freshly-created ze_event_handle_t; the consumer uses a dedicated raii variant whose destructor calls zeEventCounterBasedCloseIpcHandle, so urEventRelease tears the imported event down correctly with no separate close entry point. * UR_DEVICE_INFO_IPC_EVENT_SUPPORT_EXP is gated on v2/Linux, BMG+, and latest driver - this might need to be revised eventually. Conformance tests signal the producer event via Level Zero directly so they do not depend on the reusable-event extension. Assisted-By: Claude
1 parent 069456d commit 61fdeb8

14 files changed

Lines changed: 592 additions & 28 deletions

File tree

unified-runtime/source/adapters/level_zero/device.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,19 @@ ur_result_t urDeviceGetInfo(
13891389
#else
13901390
return ReturnValue(false);
13911391
#endif
1392+
case UR_DEVICE_INFO_IPC_EVENT_SUPPORT_EXP: {
1393+
#if defined(UR_ADAPTER_LEVEL_ZERO_V2) && defined(__linux__)
1394+
constexpr uint32_t MinDriverBuild = 38646;
1395+
ZeStruct<ze_driver_properties_t> ZeDriverProperties;
1396+
ZE2UR_CALL(zeDriverGetProperties,
1397+
(Device->Platform->ZeDriver, &ZeDriverProperties));
1398+
const uint32_t DriverBuild = ZeDriverProperties.driverVersion & 0xFFFF;
1399+
return ReturnValue(static_cast<ur_bool_t>(Device->isBMGOrNewer() &&
1400+
DriverBuild >= MinDriverBuild));
1401+
#else
1402+
return ReturnValue(false);
1403+
#endif
1404+
}
13921405
case UR_DEVICE_INFO_ASYNC_BARRIER:
13931406
return ReturnValue(false);
13941407
case UR_DEVICE_INFO_HOST_PIPE_READ_WRITE_SUPPORT:

unified-runtime/source/adapters/level_zero/device.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ struct ur_device_handle_t_ : ur_object {
209209
ZeDeviceIpVersionExt->ipVersion >= 0x030f0000);
210210
}
211211

212+
bool isBMGOrNewer() {
213+
return (ZeDeviceProperties->vendorId == 0x8086 &&
214+
ZeDeviceIpVersionExt->ipVersion >= 0x05004000);
215+
}
216+
212217
bool isIntegrated() {
213218
return (ZeDeviceProperties->flags & ZE_DEVICE_PROPERTY_FLAG_INTEGRATED);
214219
}

unified-runtime/source/adapters/level_zero/v2/common.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,15 @@ HANDLE_WRAPPER_TYPE(ze_context_handle_t, zeContextDestroy)
107107
HANDLE_WRAPPER_TYPE(ze_command_list_handle_t, zeCommandListDestroy)
108108
HANDLE_WRAPPER_TYPE(ze_image_handle_t, zeImageDestroy)
109109

110+
// Counter-based event IPC handle opened via zeEventCounterBasedOpenIpcHandle.
111+
// Same underlying handle type as ze_event_handle_t but a different teardown
112+
// function, hence a distinct alias.
113+
inline constexpr char ipc_event_destroy_name[] =
114+
"zeEventCounterBasedCloseIpcHandle";
115+
using ipc_event_handle_t =
116+
ze_handle_wrapper<::ze_event_handle_t, zeEventCounterBasedCloseIpcHandle,
117+
ipc_event_destroy_name>;
118+
110119
template <typename RawHandle, ur_result_t (*retain)(RawHandle),
111120
ur_result_t (*release)(RawHandle)>
112121
struct ur_handle {

unified-runtime/source/adapters/level_zero/v2/event.cpp

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
#include "event.hpp"
1515
#include "event_pool.hpp"
1616
#include "event_provider.hpp"
17+
#include "event_provider_counter.hpp"
1718
#include "queue_api.hpp"
1819
#include "queue_handle.hpp"
1920

21+
#include "../device.hpp"
2022
#include "../ur_interface_loader.hpp"
2123

2224
static uint64_t adjustEndEventTimestamp(uint64_t adjustedStartTimestamp,
@@ -164,11 +166,7 @@ void ur_event_handle_t_::reset() {
164166
}
165167

166168
ze_event_handle_t ur_event_handle_t_::getZeEvent() const {
167-
if (event_pool) {
168-
return std::get<v2::raii::cache_borrowed_event>(hZeEvent).get();
169-
} else {
170-
return std::get<v2::raii::ze_event_handle_t>(hZeEvent).get();
171-
}
169+
return std::visit([](const auto &h) { return h.get(); }, hZeEvent);
172170
}
173171

174172
ur_result_t ur_event_handle_t_::retain() {
@@ -182,10 +180,14 @@ ur_result_t ur_event_handle_t_::release() {
182180

183181
if (event_pool) {
184182
event_pool->free(this);
185-
} else {
186-
std::get<v2::raii::ze_event_handle_t>(hZeEvent).release();
187-
delete this;
183+
return UR_RESULT_SUCCESS;
188184
}
185+
186+
// The variant destructor runs the correct teardown:
187+
// - ze_event_handle_t honors ownZeHandle (zeEventDestroy if owned, no-op
188+
// for externally-imported events).
189+
// - ipc_event_handle_t runs zeEventCounterBasedCloseIpcHandle.
190+
delete this;
189191
return UR_RESULT_SUCCESS;
190192
}
191193

@@ -197,6 +199,14 @@ bool ur_event_handle_t_::isProfilingEnabled() const {
197199
return flags & v2::EVENT_FLAGS_PROFILING_ENABLED;
198200
}
199201

202+
bool ur_event_handle_t_::isIpcCapable() const {
203+
return flags & v2::EVENT_FLAGS_IPC;
204+
}
205+
206+
bool ur_event_handle_t_::isIpcImported() const {
207+
return flags & v2::EVENT_FLAGS_IPC_IMPORTED;
208+
}
209+
200210
std::pair<uint64_t *, ze_event_handle_t>
201211
ur_event_handle_t_::getEventEndTimestampAndHandle() {
202212
return {profilingData.eventEndTimestampAddr(), getZeEvent()};
@@ -234,6 +244,11 @@ ur_event_handle_t_::ur_event_handle_t_(
234244
,
235245
nullptr) {}
236246

247+
ur_event_handle_t_::ur_event_handle_t_(ur_context_handle_t hContext,
248+
event_variant hZeEvent,
249+
v2::event_flags_t flags)
250+
: ur_event_handle_t_(hContext, std::move(hZeEvent), flags, nullptr) {}
251+
237252
namespace ur::level_zero {
238253
ur_result_t urEventRetain(ur_event_handle_t hEvent) try {
239254
return hEvent->retain();
@@ -391,6 +406,11 @@ ur_result_t urEventGetProfilingInfo(
391406

392407
ur_result_t urEventGetNativeHandle(ur_event_handle_t hEvent,
393408
ur_native_handle_t *phNativeEvent) try {
409+
// Imported event handle must be torn down via
410+
// zeEventCounterBasedCloseIpcHandle, not zeEventDestroy; don't hand it out.
411+
if (hEvent->isIpcImported()) {
412+
return UR_RESULT_ERROR_INVALID_EVENT;
413+
}
394414
*phNativeEvent = reinterpret_cast<ur_native_handle_t>(hEvent->getZeEvent());
395415
return UR_RESULT_SUCCESS;
396416
} catch (...) {
@@ -416,12 +436,35 @@ urEventCreateWithNativeHandle(ur_native_handle_t hNativeEvent,
416436
return exceptionToResult(std::current_exception());
417437
}
418438

419-
ur_result_t urEventCreateExp(ur_context_handle_t /*hContext*/,
420-
ur_device_handle_t /*hDevice*/,
421-
const ur_exp_event_desc_t * /*pEventDesc*/,
422-
ur_event_handle_t * /*phEvent*/) {
423-
UR_LOG(ERR, "{} function not implemented!", __FUNCTION__);
439+
ur_result_t urEventCreateExp(ur_context_handle_t hContext,
440+
ur_device_handle_t hDevice,
441+
const ur_exp_event_desc_t *pEventDesc,
442+
ur_event_handle_t *phEvent) try {
443+
if (!hContext || !hDevice)
444+
return UR_RESULT_ERROR_INVALID_NULL_HANDLE;
445+
if (!pEventDesc || !phEvent)
446+
return UR_RESULT_ERROR_INVALID_NULL_POINTER;
447+
448+
if (pEventDesc->flags & UR_EXP_EVENT_FLAGS_MASK)
449+
return UR_RESULT_ERROR_INVALID_ENUMERATION;
450+
451+
if (pEventDesc->flags & UR_EXP_EVENT_FLAG_IPC_EXP) {
452+
if (pEventDesc->flags & UR_EXP_EVENT_FLAG_ENABLE_PROFILING)
453+
return UR_RESULT_ERROR_INVALID_VALUE;
454+
455+
ze_event_handle_t hZeEvent = nullptr;
456+
UR_CALL(v2::createIpcCounterBasedEvent(hContext, hDevice, &hZeEvent));
457+
458+
*phEvent = new ur_event_handle_t_(
459+
hContext, v2::raii::ze_event_handle_t{hZeEvent, /*ownZeHandle=*/true},
460+
v2::EVENT_FLAGS_COUNTER | v2::EVENT_FLAGS_IPC);
461+
return UR_RESULT_SUCCESS;
462+
}
463+
464+
UR_LOG(ERR, "{}: non-IPC reusable events not implemented!", __FUNCTION__);
424465
return UR_RESULT_ERROR_UNSUPPORTED_FEATURE;
466+
} catch (...) {
467+
return exceptionToResult(std::current_exception());
425468
}
426469

427470
} // namespace ur::level_zero

unified-runtime/source/adapters/level_zero/v2/event.hpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,16 @@ struct event_profiling_data_t {
5656

5757
struct ur_event_handle_t_ : ur_object {
5858
public:
59-
// cache_borrowed_event is used for pooled events, whilst ze_event_handle_t is
60-
// used for native events
59+
// The variant alternative encodes how the L0 event handle is torn down:
60+
// - cache_borrowed_event: returned to the pool.
61+
// - ze_event_handle_t: native event (urEventCreateWithNativeHandle if
62+
// external, urEventCreateExp with IPC flag if adapter-owned); the
63+
// wrapper's ownZeHandle flag controls whether zeEventDestroy runs.
64+
// - ipc_event_handle_t: opened from an IPC handle in another process;
65+
// torn down via zeEventCounterBasedCloseIpcHandle.
6166
using event_variant =
62-
std::variant<v2::raii::cache_borrowed_event, v2::raii::ze_event_handle_t>;
67+
std::variant<v2::raii::cache_borrowed_event, v2::raii::ze_event_handle_t,
68+
v2::raii::ipc_event_handle_t>;
6369

6470
ur_event_handle_t_(ur_context_handle_t hContext,
6571
v2::raii::cache_borrowed_event eventAllocation,
@@ -69,6 +75,11 @@ struct ur_event_handle_t_ : ur_object {
6975
ur_native_handle_t hNativeEvent,
7076
const ur_event_native_properties_t *pProperties);
7177

78+
// Wraps a caller-built event_variant with explicit flags. Used by the IPC
79+
// producer (urEventCreateExp) and consumer (urIPCOpenEventHandleExp) paths.
80+
ur_event_handle_t_(ur_context_handle_t hContext, event_variant hZeEvent,
81+
v2::event_flags_t flags);
82+
7283
// Set the queue and command that this event is associated with
7384
void setQueue(ur_queue_t_ *hQueue);
7485
void setCommandType(ur_command_t commandType);
@@ -99,6 +110,12 @@ struct ur_event_handle_t_ : ur_object {
99110
// Tells if this event comes from a pool that has profiling enabled.
100111
bool isProfilingEnabled() const;
101112

113+
// True for IPC-shareable events (both producer and consumer side).
114+
bool isIpcCapable() const;
115+
116+
// True for events opened via urIPCOpenEventHandleExp.
117+
bool isIpcImported() const;
118+
102119
// Queue associated with this event. Can be nullptr (for native events)
103120
ur_queue_t_ *getQueue() const;
104121

unified-runtime/source/adapters/level_zero/v2/event_provider.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ using event_flags_t = uint32_t;
2424
enum event_flag_t {
2525
EVENT_FLAGS_COUNTER = UR_BIT(0),
2626
EVENT_FLAGS_PROFILING_ENABLED = UR_BIT(1),
27+
// IPC-shareable producer event.
28+
EVENT_FLAGS_IPC = UR_BIT(2),
29+
// Event opened from an IPC handle.
30+
EVENT_FLAGS_IPC_IMPORTED = UR_BIT(3),
2731
};
32+
// Bits used to index pooled events in event_pool_cache. IPC events are never
33+
// pool-allocated, so EVENT_FLAGS_IPC* are excluded from this count.
2834
static constexpr size_t EVENT_FLAGS_USED_BITS = 2;
2935

3036
enum queue_type {

unified-runtime/source/adapters/level_zero/v2/event_provider_counter.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,20 @@ std::unique_ptr<event_provider> createProvider(ur_platform_handle_t platform,
113113
return std::make_unique<provider_normal>(context, queueType, flags);
114114
}
115115

116+
ur_result_t createIpcCounterBasedEvent(ur_context_handle_t context,
117+
ur_device_handle_t device,
118+
ze_event_handle_t *phEvent) {
119+
ze_event_counter_based_desc_t desc = {};
120+
desc.stype = ZE_STRUCTURE_TYPE_EVENT_COUNTER_BASED_DESC;
121+
desc.flags = ZE_EVENT_COUNTER_BASED_FLAG_HOST_VISIBLE |
122+
ZE_EVENT_COUNTER_BASED_FLAG_IMMEDIATE |
123+
ZE_EVENT_COUNTER_BASED_FLAG_NON_IMMEDIATE |
124+
ZE_EVENT_COUNTER_BASED_FLAG_IPC;
125+
desc.signal = ZE_EVENT_SCOPE_FLAG_HOST;
126+
127+
ZE2UR_CALL(zeEventCounterBasedCreate,
128+
(context->getZeHandle(), device->ZeDevice, &desc, phEvent));
129+
return UR_RESULT_SUCCESS;
130+
}
131+
116132
} // namespace v2

unified-runtime/source/adapters/level_zero/v2/event_provider_counter.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,10 @@ std::unique_ptr<event_provider> createProvider(ur_platform_handle_t platform,
6161
ur_device_handle_t device,
6262
event_flags_t flags);
6363

64+
// Creates an IPC-shareable counter-based ze_event_handle_t for the producer
65+
// side of urEventCreateExp.
66+
ur_result_t createIpcCounterBasedEvent(ur_context_handle_t context,
67+
ur_device_handle_t device,
68+
ze_event_handle_t *phEvent);
69+
6470
} // namespace v2

unified-runtime/source/adapters/level_zero/v2/ipc_event.cpp

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,97 @@
77
//
88
//===----------------------------------------------------------------------===//
99

10+
#include <memory>
11+
1012
#include <unified-runtime/ur_api.h>
1113

14+
#include "../platform.hpp"
1215
#include "../ur_interface_loader.hpp"
16+
#include "context.hpp"
17+
#include "event.hpp"
1318

1419
namespace ur::level_zero {
1520

16-
ur_result_t urIPCGetEventHandleExp(ur_event_handle_t /*hEvent*/,
17-
void ** /*ppIPCEventHandleData*/,
18-
size_t * /*pIPCEventHandleDataSizeRet*/) {
19-
return UR_RESULT_ERROR_UNSUPPORTED_FEATURE;
21+
namespace {
22+
23+
// Fixed L0 IPC handle size; reported by Get and validated by Open.
24+
constexpr size_t kIpcEventHandleDataSize =
25+
sizeof(ze_ipc_event_counter_based_handle_t);
26+
27+
} // namespace
28+
29+
ur_result_t urIPCGetEventHandleExp(ur_event_handle_t hEvent,
30+
void **ppIPCEventHandleData,
31+
size_t *pIPCEventHandleDataSizeRet) try {
32+
if (!hEvent) {
33+
return UR_RESULT_ERROR_INVALID_NULL_HANDLE;
34+
}
35+
if (!ppIPCEventHandleData || !pIPCEventHandleDataSizeRet) {
36+
return UR_RESULT_ERROR_INVALID_NULL_POINTER;
37+
}
38+
39+
std::shared_lock<ur_shared_mutex> lock(hEvent->Mutex);
40+
41+
if (!hEvent->isIpcCapable() || hEvent->isIpcImported()) {
42+
return UR_RESULT_ERROR_INVALID_EVENT;
43+
}
44+
if (hEvent->isProfilingEnabled() || hEvent->isTimestamped()) {
45+
return UR_RESULT_ERROR_UNSUPPORTED_FEATURE;
46+
}
47+
48+
auto handle = std::make_unique<ze_ipc_event_counter_based_handle_t>();
49+
ZE2UR_CALL(zeEventCounterBasedGetIpcHandle,
50+
(hEvent->getZeEvent(), handle.get()));
51+
52+
// Caller releases the buffer via urIPCPutEventHandleExp.
53+
*ppIPCEventHandleData = handle.release();
54+
*pIPCEventHandleDataSizeRet = kIpcEventHandleDataSize;
55+
return UR_RESULT_SUCCESS;
56+
} catch (...) {
57+
return exceptionToResult(std::current_exception());
2058
}
2159

2260
ur_result_t urIPCPutEventHandleExp(ur_context_handle_t /*hContext*/,
23-
void * /*pIPCEventHandleData*/) {
24-
return UR_RESULT_ERROR_UNSUPPORTED_FEATURE;
61+
void *pIPCEventHandleData) try {
62+
if (!pIPCEventHandleData) {
63+
return UR_RESULT_ERROR_INVALID_NULL_POINTER;
64+
}
65+
// Free the buffer allocated by urIPCGetEventHandleExp via RAII.
66+
std::unique_ptr<ze_ipc_event_counter_based_handle_t> owner(
67+
static_cast<ze_ipc_event_counter_based_handle_t *>(pIPCEventHandleData));
68+
return UR_RESULT_SUCCESS;
69+
} catch (...) {
70+
return exceptionToResult(std::current_exception());
2571
}
2672

27-
ur_result_t urIPCOpenEventHandleExp(ur_context_handle_t /*hContext*/,
28-
const void * /*pIPCEventHandleData*/,
29-
size_t /*ipcEventHandleDataSize*/,
30-
ur_event_handle_t * /*phEvent*/) {
31-
return UR_RESULT_ERROR_UNSUPPORTED_FEATURE;
73+
ur_result_t urIPCOpenEventHandleExp(ur_context_handle_t hContext,
74+
const void *pIPCEventHandleData,
75+
size_t ipcEventHandleDataSize,
76+
ur_event_handle_t *phEvent) try {
77+
if (!pIPCEventHandleData || !phEvent) {
78+
return UR_RESULT_ERROR_INVALID_NULL_POINTER;
79+
}
80+
if (ipcEventHandleDataSize != kIpcEventHandleDataSize) {
81+
return UR_RESULT_ERROR_INVALID_VALUE;
82+
}
83+
84+
// The driver consumes the handle by value.
85+
ze_ipc_event_counter_based_handle_t handle =
86+
*static_cast<const ze_ipc_event_counter_based_handle_t *>(
87+
pIPCEventHandleData);
88+
ze_event_handle_t hZeEvent = nullptr;
89+
ZE2UR_CALL(zeEventCounterBasedOpenIpcHandle,
90+
(hContext->getZeHandle(), handle, &hZeEvent));
91+
92+
// The ipc_event_handle_t variant runs zeEventCounterBasedCloseIpcHandle on
93+
// urEventRelease.
94+
*phEvent =
95+
new ur_event_handle_t_(hContext, v2::raii::ipc_event_handle_t{hZeEvent},
96+
v2::EVENT_FLAGS_COUNTER | v2::EVENT_FLAGS_IPC |
97+
v2::EVENT_FLAGS_IPC_IMPORTED);
98+
return UR_RESULT_SUCCESS;
99+
} catch (...) {
100+
return exceptionToResult(std::current_exception());
32101
}
33102

34103
} // namespace ur::level_zero

unified-runtime/test/conformance/device_code/linker_error.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
//
44
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
55

6+
#include <iostream>
67
#include <sycl/sycl.hpp>
7-
88
SYCL_EXTERNAL void this_function_does_not_exist();
99

1010
int main() {

0 commit comments

Comments
 (0)