diff --git a/CHANGELOG.md b/CHANGELOG.md
index 446be5c..1cd03df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,3 +27,6 @@ can be found at:
- Runtime kernel bundle compilation and one-, two-, and three-dimensional
kernel launch support.
- Derive support for typed kernel argument lists.
+- `RawKernelArg`, which marks USM pointer kernel arguments apart from plain
+ bytes, so kernels also launch on backends that take pointers through a
+ separate entry point, such as the OpenCL CPU device.
diff --git a/sycl/sycl-rs-derive/src/lib.rs b/sycl/sycl-rs-derive/src/lib.rs
index bc37a49..150c5cd 100644
--- a/sycl/sycl-rs-derive/src/lib.rs
+++ b/sycl/sycl-rs-derive/src/lib.rs
@@ -43,7 +43,7 @@ pub fn derive_kernel_argument_list(input: TokenStream) -> TokenStream {
let expanded = quote! {
unsafe impl #impl_generics #oneapi::kernel::KernelArgumentList<#argc>
for #ident #ty_generics #where_clause {
- unsafe fn as_raw_arg_list(&self) -> [&[u8]; #argc] {
+ unsafe fn as_raw_arg_list(&self) -> [#oneapi::kernel::RawKernelArg<'_>; #argc] {
[ #(unsafe { self.#members.as_raw_arg() }),* ]
}
}
@@ -69,7 +69,7 @@ fn get_single_tuple_impl(argc: usize) -> proc_macro2::TokenStream {
quote! {
unsafe impl<#(#types),*> crate::kernel::KernelArgumentList<#argc> for (#(#types),*)
where #(#types: crate::kernel::KernelArgument),* {
- unsafe fn as_raw_arg_list(&self) -> [&[u8]; #argc] {
+ unsafe fn as_raw_arg_list(&self) -> [crate::kernel::RawKernelArg<'_>; #argc] {
[ #(unsafe { self.#iter.as_raw_arg() }),* ]
}
}
diff --git a/sycl/sycl-rs-sys/include/queue.hpp b/sycl/sycl-rs-sys/include/queue.hpp
index 0fe5bbe..9f0c08e 100644
--- a/sycl/sycl-rs-sys/include/queue.hpp
+++ b/sycl/sycl-rs-sys/include/queue.hpp
@@ -18,6 +18,7 @@ struct EventPtr;
struct Range1;
struct Range2;
struct Range3;
+struct KernelArg;
} // namespace sycl_shims
namespace sycl_shims::queue {
@@ -37,20 +38,17 @@ std::unique_ptr memset(std::unique_ptr &, std::uint8_t *ptr,
std::unique_ptr barrier(std::unique_ptr &, rust::Vec);
void wait(std::unique_ptr &);
-std::unique_ptr
-launch_1d(std::unique_ptr &, Range1 global_size, Range1 local_size,
- Kernel const &,
- rust::Slice const> args);
+std::unique_ptr launch_1d(std::unique_ptr &, Range1 global_size,
+ Range1 local_size, Kernel const &,
+ rust::Slice args);
-std::unique_ptr
-launch_2d(std::unique_ptr &, Range2 global_size, Range2 local_size,
- Kernel const &,
- rust::Slice const> args);
+std::unique_ptr launch_2d(std::unique_ptr &, Range2 global_size,
+ Range2 local_size, Kernel const &,
+ rust::Slice args);
-std::unique_ptr
-launch_3d(std::unique_ptr &, Range3 global_size, Range3 local_size,
- Kernel const &,
- rust::Slice const> args);
+std::unique_ptr launch_3d(std::unique_ptr &, Range3 global_size,
+ Range3 local_size, Kernel const &,
+ rust::Slice args);
std::unique_ptr memcpy(std::unique_ptr &, std::uint8_t *dest,
std::uint8_t const *src, std::size_t num_bytes,
diff --git a/sycl/sycl-rs-sys/src/queue-sys.rs b/sycl/sycl-rs-sys/src/queue-sys.rs
index 2310585..3d34107 100644
--- a/sycl/sycl-rs-sys/src/queue-sys.rs
+++ b/sycl/sycl-rs-sys/src/queue-sys.rs
@@ -17,6 +17,7 @@ pub mod ffi {
type Range1 = crate::types::ffi::Range1;
type Range2 = crate::types::ffi::Range2;
type Range3 = crate::types::ffi::Range3;
+ type KernelArg<'a> = crate::types::ffi::KernelArg<'a>;
}
unsafe extern "C++" {
@@ -81,37 +82,37 @@ pub mod ffi {
/// # Safety
///
/// Each entry in `args` must match the corresponding kernel parameter's size, layout,
- /// and alignment.
- unsafe fn launch_1d(
+ /// and alignment, and must be marked as a pointer exactly when it holds a USM pointer.
+ unsafe fn launch_1d<'a>(
queue: &mut UniquePtr,
global_size: Range1,
local_size: Range1,
kernel: &Kernel,
- args: &[&[u8]],
+ args: &[KernelArg<'a>],
) -> Result>;
/// # Safety
///
/// Each entry in `args` must match the corresponding kernel parameter's size, layout,
- /// and alignment.
- unsafe fn launch_2d(
+ /// and alignment, and must be marked as a pointer exactly when it holds a USM pointer.
+ unsafe fn launch_2d<'a>(
queue: &mut UniquePtr,
global_size: Range2,
local_size: Range2,
kernel: &Kernel,
- args: &[&[u8]],
+ args: &[KernelArg<'a>],
) -> Result>;
/// # Safety
///
/// Each entry in `args` must match the corresponding kernel parameter's size, layout,
- /// and alignment.
- unsafe fn launch_3d(
+ /// and alignment, and must be marked as a pointer exactly when it holds a USM pointer.
+ unsafe fn launch_3d<'a>(
queue: &mut UniquePtr,
global_size: Range3,
local_size: Range3,
kernel: &Kernel,
- args: &[&[u8]],
+ args: &[KernelArg<'a>],
) -> Result>;
}
}
diff --git a/sycl/sycl-rs-sys/src/queue.cpp b/sycl/sycl-rs-sys/src/queue.cpp
index 5698ffc..9e097a4 100644
--- a/sycl/sycl-rs-sys/src/queue.cpp
+++ b/sycl/sycl-rs-sys/src/queue.cpp
@@ -10,6 +10,9 @@
#include "sycl-rs-sys/include/utils.hpp"
#include "sycl-rs-sys/src/queue-sys.rs.h"
+#include
+#include
+
using sycl::ext::intel::property::queue::immediate_command_list;
using sycl::property::queue::in_order;
using sycl_shims::utils::vec_to_vector;
@@ -62,42 +65,65 @@ std::unique_ptr barrier(std::unique_ptr &queue,
void wait(std::unique_ptr &queue) { queue->wait(); }
+// Pointers go through the typed set_arg overload rather than raw_kernel_arg: a
+// backend may take them through a different entry point than plain bytes (the
+// OpenCL CPU device rejects USM pointers passed as bytes), and raw_kernel_arg
+// only takes non-pointer values.
+static void set_kernel_arg(sycl::handler &cgh, std::size_t index,
+ KernelArg const &arg) {
+ switch (arg.kind) {
+ case KernelArgKind::Bytes:
+ cgh.set_arg(index,
+ syclexp::raw_kernel_arg(arg.bytes.data(), arg.bytes.size()));
+ return;
+ case KernelArgKind::Pointer: {
+ void *pointer;
+ if (arg.bytes.size() != sizeof(pointer))
+ throw std::invalid_argument(
+ "pointer kernel argument is not pointer-sized");
+ std::memcpy(&pointer, arg.bytes.data(), sizeof(pointer));
+ cgh.set_arg(index, pointer);
+ return;
+ }
+ }
+ throw std::invalid_argument("unknown kernel argument kind");
+}
+
template
std::unique_ptr
launch(std::unique_ptr &queue, sycl::nd_range nd_range,
- Kernel const &kernel,
- rust::Slice const> args) {
+ Kernel const &kernel, rust::Slice args) {
return std::make_unique(queue->submit([&](sycl::handler &cgh) {
for (std::size_t i = 0; i < args.size(); ++i)
- cgh.set_arg(i, syclexp::raw_kernel_arg(args[i].data(), args[i].size()));
+ set_kernel_arg(cgh, i, args[i]);
cgh.parallel_for(nd_range, kernel);
}));
}
-std::unique_ptr
-launch_1d(std::unique_ptr &queue, Range1 global_size, Range1 local_size,
- Kernel const &kernel,
- rust::Slice const> args) {
+std::unique_ptr launch_1d(std::unique_ptr &queue,
+ Range1 global_size, Range1 local_size,
+ Kernel const &kernel,
+ rust::Slice args) {
return launch(queue,
sycl::nd_range<1>{{global_size.data[0]}, {local_size.data[0]}},
kernel, args);
}
-std::unique_ptr
-launch_2d(std::unique_ptr &queue, Range2 global_size, Range2 local_size,
- Kernel const &kernel,
- rust::Slice const> args) {
+std::unique_ptr launch_2d(std::unique_ptr &queue,
+ Range2 global_size, Range2 local_size,
+ Kernel const &kernel,
+ rust::Slice args) {
return launch(queue,
sycl::nd_range<2>{{global_size.data[0], global_size.data[1]},
{local_size.data[0], local_size.data[1]}},
kernel, args);
}
-std::unique_ptr
-launch_3d(std::unique_ptr &queue, Range3 global_size, Range3 local_size,
- Kernel const &kernel,
- rust::Slice const> args) {
+std::unique_ptr launch_3d(std::unique_ptr &queue,
+ Range3 global_size, Range3 local_size,
+ Kernel const &kernel,
+ rust::Slice args) {
return launch(
queue,
sycl::nd_range<3>{
diff --git a/sycl/sycl-rs-sys/src/types-sys.rs b/sycl/sycl-rs-sys/src/types-sys.rs
index 3136ca6..2dfd99c 100644
--- a/sycl/sycl-rs-sys/src/types-sys.rs
+++ b/sycl/sycl-rs-sys/src/types-sys.rs
@@ -106,6 +106,22 @@ pub mod ffi {
data: [u64; 3],
}
+ /// How the bytes of a kernel argument are handed to the backend. A backend may take
+ /// pointers through a different entry point than plain bytes, and cannot tell the two
+ /// apart from the bytes alone.
+ #[derive(Debug, Hash)]
+ enum KernelArgKind {
+ /// The bytes of a trivially copyable value that is not a pointer.
+ Bytes,
+ /// The bytes of a pointer to USM memory.
+ Pointer,
+ }
+
+ struct KernelArg<'a> {
+ kind: KernelArgKind,
+ bytes: &'a [u8],
+ }
+
impl UniquePtr {}
impl UniquePtr {}
impl UniquePtr {}
diff --git a/sycl/sycl-rs/src/kernel.rs b/sycl/sycl-rs/src/kernel.rs
index d1ce3a8..dce18d0 100644
--- a/sycl/sycl-rs/src/kernel.rs
+++ b/sycl/sycl-rs/src/kernel.rs
@@ -49,25 +49,50 @@ impl From> for Kernel {
}
}
+/// The raw representation of a SYCL kernel argument.
+///
+/// A backend may take pointers through a different entry point than plain bytes, and cannot
+/// tell the two apart from the bytes alone, so pointers are marked as such.
+#[derive(Clone, Copy, Debug)]
+#[non_exhaustive]
+pub enum RawKernelArg<'a> {
+ /// The bytes of a trivially copyable value that is not a pointer.
+ Bytes(&'a [u8]),
+ /// The bytes of a pointer to USM memory.
+ Pointer(&'a [u8]),
+}
+
+impl<'a> RawKernelArg<'a> {
+ pub(crate) fn into_ffi(self) -> types::ffi::KernelArg<'a> {
+ let (kind, bytes) = match self {
+ Self::Bytes(bytes) => (types::ffi::KernelArgKind::Bytes, bytes),
+ Self::Pointer(bytes) => (types::ffi::KernelArgKind::Pointer, bytes),
+ };
+ types::ffi::KernelArg { kind, bytes }
+ }
+}
+
/// Types which can be passed as SYCL kernel arguments.
///
/// # Safety
///
/// A type implementing this trait must mirror the representation and alignment of the
-/// corresponding SYCL kernel argument structure.
+/// corresponding SYCL kernel argument structure, and must return [`RawKernelArg::Pointer`]
+/// exactly when it represents a pointer to USM memory.
pub unsafe trait KernelArgument {
- /// Converts self to a raw byte representation.
+ /// Converts self to its raw representation.
///
/// # Safety
///
- /// This function returns a reference to raw bytes. These bytes will be passed to FFI
- /// functions. The caller must make sure these functions respect Rust's aliasing rules.
- unsafe fn as_raw_arg(&self) -> &[u8];
+ /// This function returns a reference to raw bytes, marked as a pointer or plain bytes.
+ /// These bytes will be passed to FFI functions. The caller must make sure these functions
+ /// respect Rust's aliasing rules.
+ unsafe fn as_raw_arg(&self) -> RawKernelArg<'_>;
}
unsafe impl KernelArgument for T {
- unsafe fn as_raw_arg(&self) -> &[u8] {
- bytemuck::bytes_of(self)
+ unsafe fn as_raw_arg(&self) -> RawKernelArg<'_> {
+ RawKernelArg::Bytes(bytemuck::bytes_of(self))
}
}
@@ -76,25 +101,27 @@ unsafe impl KernelArgument for T {
/// # Safety
///
/// A type implementing this trait must mirror the representation and alignment of each
-/// corresponding SYCL kernel argument inside the returned array.
+/// corresponding SYCL kernel argument inside the returned array, and each entry must be
+/// [`RawKernelArg::Pointer`] exactly when it represents a pointer to USM memory.
pub unsafe trait KernelArgumentList {
- /// Converts each struct member to a raw byte representation.
+ /// Converts each struct member to its raw representation.
///
/// # Safety
///
- /// This function returns references to raw bytes. These bytes will be passed to FFI
- /// functions. The caller must make sure these functions respect Rust's aliasing rules.
- unsafe fn as_raw_arg_list(&self) -> [&[u8]; ARGC];
+ /// This function returns references to raw bytes, each marked as a pointer or plain bytes.
+ /// These bytes will be passed to FFI functions. The caller must make sure these functions
+ /// respect Rust's aliasing rules.
+ unsafe fn as_raw_arg_list(&self) -> [RawKernelArg<'_>; ARGC];
}
unsafe impl KernelArgumentList<0> for () {
- unsafe fn as_raw_arg_list(&self) -> [&[u8]; 0] {
+ unsafe fn as_raw_arg_list(&self) -> [RawKernelArg<'_>; 0] {
[]
}
}
unsafe impl KernelArgumentList<1> for T {
- unsafe fn as_raw_arg_list(&self) -> [&[u8]; 1] {
+ unsafe fn as_raw_arg_list(&self) -> [RawKernelArg<'_>; 1] {
[unsafe { self.as_raw_arg() }]
}
}
diff --git a/sycl/sycl-rs/src/prelude.rs b/sycl/sycl-rs/src/prelude.rs
index 2ab3549..2bee2aa 100644
--- a/sycl/sycl-rs/src/prelude.rs
+++ b/sycl/sycl-rs/src/prelude.rs
@@ -10,7 +10,7 @@ pub use crate::{
context::Context,
device::{Aspect, Device},
info::{self, InfoTarget},
- kernel::{Kernel, KernelArgument, KernelArgumentList},
+ kernel::{Kernel, KernelArgument, KernelArgumentList, RawKernelArg},
platform::Platform,
queue::Queue,
range::{NdRange, Range},
diff --git a/sycl/sycl-rs/src/range.rs b/sycl/sycl-rs/src/range.rs
index c8b4059..b82dfbe 100644
--- a/sycl/sycl-rs/src/range.rs
+++ b/sycl/sycl-rs/src/range.rs
@@ -11,7 +11,7 @@ use sycl_rs_sys::types;
use crate::{
event::Event,
- kernel::{Kernel, KernelArgumentList},
+ kernel::{Kernel, KernelArgumentList, RawKernelArg},
private::Sealed,
queue::Queue,
};
@@ -72,7 +72,7 @@ impl ValidDimension for NdRange<1> {
data: self.local_size,
},
&kernel.0,
- &args.as_raw_arg_list(),
+ &args.as_raw_arg_list().map(RawKernelArg::into_ffi),
)
}
.map(Into::into)
@@ -97,7 +97,7 @@ impl ValidDimension for NdRange<2> {
data: self.local_size,
},
&kernel.0,
- &args.as_raw_arg_list(),
+ &args.as_raw_arg_list().map(RawKernelArg::into_ffi),
)
}
.map(Into::into)
@@ -122,7 +122,7 @@ impl ValidDimension for NdRange<3> {
data: self.local_size,
},
&kernel.0,
- &args.as_raw_arg_list(),
+ &args.as_raw_arg_list().map(RawKernelArg::into_ffi),
)
}
.map(Into::into)
diff --git a/sycl/sycl-rs/src/usmbox.rs b/sycl/sycl-rs/src/usmbox.rs
index b9ba1ab..da21d31 100644
--- a/sycl/sycl-rs/src/usmbox.rs
+++ b/sycl/sycl-rs/src/usmbox.rs
@@ -21,7 +21,7 @@ use pin_project::pin_project;
use crate::{
Result,
event::{Event, EventFuture},
- kernel::KernelArgument,
+ kernel::{KernelArgument, RawKernelArg},
usm::{
DeviceAllocator, HostAccessible, HostAllocator, SharedAllocator, UsmAlloc, UsmAllocator,
},
@@ -76,10 +76,10 @@ impl UsmBox {
self.len
}
+ /// Returns the bytes of the USM pointer itself, which is what a kernel receives.
unsafe fn as_raw_arg_impl(&self) -> &[u8] {
- let data_ptr: *const NonNull<_> = &self.data;
- let cast_ptr = data_ptr as *const u8;
- unsafe { slice::from_raw_parts(cast_ptr, std::mem::size_of_val(&cast_ptr)) }
+ let data: *const NonNull = &self.data;
+ unsafe { slice::from_raw_parts(data.cast::(), std::mem::size_of::>()) }
}
}
@@ -169,19 +169,19 @@ pub type SharedUsmBoxFuture = UsmBoxFuture>;
pub type DeviceUsmBoxFuture = UsmBoxFuture>;
unsafe impl KernelArgument for UsmBox {
- unsafe fn as_raw_arg(&self) -> &[u8] {
- unsafe { self.as_raw_arg_impl() }
+ unsafe fn as_raw_arg(&self) -> RawKernelArg<'_> {
+ RawKernelArg::Pointer(unsafe { self.as_raw_arg_impl() })
}
}
unsafe impl KernelArgument for &UsmBox {
- unsafe fn as_raw_arg(&self) -> &[u8] {
- unsafe { self.as_raw_arg_impl() }
+ unsafe fn as_raw_arg(&self) -> RawKernelArg<'_> {
+ RawKernelArg::Pointer(unsafe { self.as_raw_arg_impl() })
}
}
unsafe impl KernelArgument for &mut UsmBox {
- unsafe fn as_raw_arg(&self) -> &[u8] {
- unsafe { self.as_raw_arg_impl() }
+ unsafe fn as_raw_arg(&self) -> RawKernelArg<'_> {
+ RawKernelArg::Pointer(unsafe { self.as_raw_arg_impl() })
}
}
diff --git a/sycl/sycl-rs/tests/launch.rs b/sycl/sycl-rs/tests/launch.rs
new file mode 100644
index 0000000..e27b681
--- /dev/null
+++ b/sycl/sycl-rs/tests/launch.rs
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2026 Intel Corporation
+//
+// Under the MIT License or the Apache License v2.0.
+// See LICENSE-MIT and LICENSE-APACHE for license information.
+// SPDX-License-Identifier: MIT OR Apache-2.0
+//
+
+use sycl_rs::prelude::*;
+
+static SAXPY_SRC: &str = r#"
+#include
+namespace syclext = sycl::ext::oneapi;
+namespace syclexp = sycl::ext::oneapi::experimental;
+
+extern "C"
+SYCL_EXT_ONEAPI_FUNCTION_PROPERTY((syclexp::nd_range_kernel<1>))
+void saxpy(float a, const float *x, float *y) {
+ size_t id = syclext::this_work_item::get_nd_item<1>().get_global_linear_id();
+ y[id] = a * x[id] + y[id];
+}
+"#;
+
+// Mixes a plain value with USM pointers, which some backends (such as the OpenCL CPU device)
+// only accept when the pointers are passed through their own entry point.
+#[test]
+fn launch_with_value_and_pointer_arguments() -> sycl_rs::Result<()> {
+ const LEN: usize = 64;
+ // Queue::new() picks a GPU when there is one. Prefer the CPU, whose OpenCL runtime rejects
+ // pointers passed as plain bytes.
+ let mut queue = Device::get_devices()
+ .iter()
+ .find(|device| device.get_info::() == info::DeviceType::Cpu)
+ .map_or_else(Queue::new, Queue::from);
+ let mut x = queue.alloc_shared::(LEN)?.wait()?;
+ let mut y = queue.alloc_shared::(LEN)?.wait()?;
+ for (index, (x, y)) in x.iter_mut().zip(y.iter_mut()).enumerate() {
+ *x = index as f32;
+ *y = 1.0;
+ }
+
+ let kernel = queue
+ .get_context()
+ .create_kernel_bundle_from_source(SAXPY_SRC)?
+ .build()?
+ .get_kernel("saxpy")?;
+ unsafe {
+ queue.launch(
+ NdRange::new([LEN as u64], [16]),
+ &kernel,
+ (2.0f32, &x, &mut y),
+ )
+ }?
+ .wait()?;
+
+ let expected: Vec = (0..LEN).map(|index| 2.0 * index as f32 + 1.0).collect();
+ assert_eq!(y.as_ref(), expected.as_slice());
+ Ok(())
+}