diff --git a/Cargo.lock b/Cargo.lock index 275eb5a..ac1796a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "allocator-api2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880" + [[package]] name = "anstyle" version = "1.0.14" @@ -170,8 +176,10 @@ dependencies = [ name = "oneapi-rs" version = "0.1.0" dependencies = [ + "allocator-api2", "cxx", "oneapi-rs-sys", + "thiserror", ] [[package]] @@ -269,6 +277,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/oneapi-rs-sys/build.rs b/oneapi-rs-sys/build.rs index d0bd169..63ef79e 100644 --- a/oneapi-rs-sys/build.rs +++ b/oneapi-rs-sys/build.rs @@ -17,17 +17,23 @@ fn main() { "src/types-sys.rs", "src/platform-sys.rs", "src/device-sys.rs", + "src/queue-sys.rs", + "src/usm-sys.rs", ]; let cpp_sources = [ "src/platform.cpp", "src/device.cpp", + "src/queue.cpp", + "src/usm.cpp", ]; let cpp_headers = [ "include/types.hpp", "include/platform.hpp", "include/device.hpp", + "include/queue.hpp", + "include/usm.hpp", ]; cxx_build::bridges(&rust_sources) diff --git a/oneapi-rs-sys/include/queue.hpp b/oneapi-rs-sys/include/queue.hpp new file mode 100644 index 0000000..55a6877 --- /dev/null +++ b/oneapi-rs-sys/include/queue.hpp @@ -0,0 +1,19 @@ +// +// 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 +// + +#pragma once + +#include "oneapi-rs-sys/include/types.hpp" +#include "rust/cxx.h" + +#include + +namespace sycl_shims::queue { +std::unique_ptr new_queue(); +std::unique_ptr new_queue_from_device(Device const &); +} // namespace sycl_shims::queue diff --git a/oneapi-rs-sys/include/types.hpp b/oneapi-rs-sys/include/types.hpp index 1592c70..f97d0af 100644 --- a/oneapi-rs-sys/include/types.hpp +++ b/oneapi-rs-sys/include/types.hpp @@ -13,4 +13,5 @@ namespace sycl_shims { using Device = sycl::device; using Platform = sycl::platform; +using Queue = sycl::queue; } diff --git a/oneapi-rs-sys/include/usm.hpp b/oneapi-rs-sys/include/usm.hpp new file mode 100644 index 0000000..d946121 --- /dev/null +++ b/oneapi-rs-sys/include/usm.hpp @@ -0,0 +1,21 @@ +// +// 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 +// + +#pragma once + +#include "oneapi-rs-sys/include/types.hpp" +#include "rust/cxx.h" + +#include + +namespace sycl_shims::usm { +std::uint8_t* aligned_alloc_device(std::size_t alignment, std::size_t num_bytes, Queue const &); +std::uint8_t* aligned_alloc_host(std::size_t alignment, std::size_t num_bytes, Queue const &); +std::uint8_t* aligned_alloc_shared(std::size_t alignment, std::size_t num_bytes, Queue const &); +void free(std::uint8_t*, Queue const &); +} // namespace sycl_shims::usm diff --git a/oneapi-rs-sys/src/lib.rs b/oneapi-rs-sys/src/lib.rs index 4163989..9f71b49 100644 --- a/oneapi-rs-sys/src/lib.rs +++ b/oneapi-rs-sys/src/lib.rs @@ -14,3 +14,9 @@ pub mod platform; #[path = "device-sys.rs"] pub mod device; + +#[path = "queue-sys.rs"] +pub mod queue; + +#[path = "usm-sys.rs"] +pub mod usm; diff --git a/oneapi-rs-sys/src/queue-sys.rs b/oneapi-rs-sys/src/queue-sys.rs new file mode 100644 index 0000000..b7b1c65 --- /dev/null +++ b/oneapi-rs-sys/src/queue-sys.rs @@ -0,0 +1,22 @@ +// +// 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 +// + +#[cxx::bridge(namespace = "sycl_shims::queue")] +pub mod ffi { + unsafe extern "C++" { + include!("oneapi-rs-sys/include/queue.hpp"); + + #[namespace = "sycl_shims"] + type Queue = crate::types::ffi::Queue; + #[namespace = "sycl_shims"] + type Device = crate::types::ffi::Device; + + fn new_queue() -> UniquePtr; + fn new_queue_from_device(device: &Device) -> UniquePtr; + } +} diff --git a/oneapi-rs-sys/src/queue.cpp b/oneapi-rs-sys/src/queue.cpp new file mode 100644 index 0000000..22e012c --- /dev/null +++ b/oneapi-rs-sys/src/queue.cpp @@ -0,0 +1,19 @@ +// +// 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 +// + +#include "oneapi-rs-sys/include/queue.hpp" +#include "oneapi-rs-sys/src/queue-sys.rs.h" + +namespace sycl_shims::queue { +std::unique_ptr new_queue() { + return std::make_unique(sycl::queue()); +} +std::unique_ptr new_queue_from_device(Device const & device) { + return std::make_unique(sycl::queue(device)); +} +} // namespace sycl_shims::queue diff --git a/oneapi-rs-sys/src/types-sys.rs b/oneapi-rs-sys/src/types-sys.rs index b14407f..5075deb 100644 --- a/oneapi-rs-sys/src/types-sys.rs +++ b/oneapi-rs-sys/src/types-sys.rs @@ -12,6 +12,7 @@ pub mod ffi { include!("oneapi-rs-sys/include/types.hpp"); type Device; type Platform; + type Queue; } // This is a workaround - cxx currently doesn't support passing @@ -39,6 +40,7 @@ pub mod ffi { impl UniquePtr {} impl UniquePtr {} + impl UniquePtr {} impl Vec {} impl Vec {} diff --git a/oneapi-rs-sys/src/usm-sys.rs b/oneapi-rs-sys/src/usm-sys.rs new file mode 100644 index 0000000..2f6cb51 --- /dev/null +++ b/oneapi-rs-sys/src/usm-sys.rs @@ -0,0 +1,23 @@ +// +// 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 +// + +#[cxx::bridge(namespace = "sycl_shims::usm")] +pub mod ffi { + unsafe extern "C++" { + #[namespace = "sycl_shims"] + type Queue = crate::types::ffi::Queue; + } + + extern "C++" { + include!("oneapi-rs-sys/include/usm.hpp"); + unsafe fn aligned_alloc_device(alignment: usize, num_bytes: usize, queue: &Queue) -> Result<*mut u8>; + unsafe fn aligned_alloc_host(alignment: usize, num_bytes: usize, queue: &Queue) -> Result<*mut u8>; + unsafe fn aligned_alloc_shared(alignment: usize, num_bytes: usize, queue: &Queue) -> Result<*mut u8>; + unsafe fn free(ptr: *mut u8, queue: &Queue); + } +} diff --git a/oneapi-rs-sys/src/usm.cpp b/oneapi-rs-sys/src/usm.cpp new file mode 100644 index 0000000..9004fa7 --- /dev/null +++ b/oneapi-rs-sys/src/usm.cpp @@ -0,0 +1,28 @@ +// +// 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 +// + +#include "oneapi-rs-sys/include/usm.hpp" +#include "oneapi-rs-sys/src/usm-sys.rs.h" + +namespace sycl_shims::usm { +std::uint8_t* aligned_alloc_device(std::size_t alignment, std::size_t num_bytes, Queue const & queue) { + return static_cast(sycl::aligned_alloc_device(alignment, num_bytes, queue)); +} + +std::uint8_t* aligned_alloc_host(std::size_t alignment, std::size_t num_bytes, Queue const & queue) { + return static_cast(sycl::aligned_alloc_host(alignment, num_bytes, queue)); +} + +std::uint8_t* aligned_alloc_shared(std::size_t alignment, std::size_t num_bytes, Queue const & queue) { + return static_cast(sycl::aligned_alloc_shared(alignment, num_bytes, queue)); +} + +void free(std::uint8_t* memory, Queue const & queue) { + sycl::free(static_cast(memory), queue); +} +} // namespace sycl_shims::usm diff --git a/oneapi-rs/Cargo.toml b/oneapi-rs/Cargo.toml index 0615d00..3bb6be3 100644 --- a/oneapi-rs/Cargo.toml +++ b/oneapi-rs/Cargo.toml @@ -5,5 +5,7 @@ edition = "2024" license = "MIT OR Apache-2.0" [dependencies] +allocator-api2 = "0.4.0" cxx = "1.0.194" oneapi-rs-sys = { path = "../oneapi-rs-sys" } +thiserror = "2.0.18" diff --git a/oneapi-rs/examples/alloc.rs b/oneapi-rs/examples/alloc.rs new file mode 100644 index 0000000..5cecde4 --- /dev/null +++ b/oneapi-rs/examples/alloc.rs @@ -0,0 +1,24 @@ +// +// 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 oneapi_rs::queue::Queue; + +fn main() { + let queue = Queue::new(); + let mut buffer = unsafe { queue.alloc_uninit_shared::(10) }; + + for i in 0..buffer.len() { + buffer[i] = i as u32; + } + + for e in buffer.iter() { + print!("{e} ") + } + + println!(); +} diff --git a/oneapi-rs/src/buffer.rs b/oneapi-rs/src/buffer.rs new file mode 100644 index 0000000..c174815 --- /dev/null +++ b/oneapi-rs/src/buffer.rs @@ -0,0 +1,72 @@ +// +// 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 std::{alloc::{Layout, handle_alloc_error}, ops::{Deref, DerefMut}, ptr::NonNull, slice}; + +use crate::usm::UsmAlloc; + +/// The Buffer struct defines a shared array of one, two or three dimensions that can be used +/// by the SYCL kernel. Buffers are templated on the type of their data, and the number of +/// dimensions that the data is stored and accessed through. +/// +/// A Buffer does not map to only one underlying backend object, and all SYCL backend memory objects +/// may be temporary for use on a specific device. +/// +/// Buffers can be constructed by methods provided by the [`Queue`](`crate::queue::Queue`) class. +/// +/// The Buffer struct template takes a template parameter [`UsmAlloc`](`crate::usm::UsmAlloc`) for +/// specifying an allocator which is used by the SYCL runtime when allocating temporary memory on +/// the host. +pub struct Buffer { + data: NonNull, + len: usize, + layout: Layout, + allocator: A, +} + +impl Buffer { + /// Creates a new buffer given an allocator. + /// Safety: returns uninitialized memory. + pub(crate) unsafe fn new(allocator: A, len: usize) -> Self { + let layout = Layout::array::(len).unwrap(); + let ptr = match allocator.allocate(layout.clone()) { + Ok(ptr) => ptr, + _ => handle_alloc_error(layout) + }; + + Self { + data: ptr.cast(), + len, + layout, + allocator, + } + } +} + +impl Deref for Buffer { + type Target = [T]; + fn deref(&self) -> &Self::Target { + unsafe { + slice::from_raw_parts(self.data.as_ptr(), self.len) + } + } +} + +impl DerefMut for Buffer { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { + slice::from_raw_parts_mut(self.data.as_ptr(), self.len) + } + } +} + +impl Drop for Buffer { + fn drop(&mut self) { + unsafe { self.allocator.deallocate(self.data.cast(), self.layout); } + } +} diff --git a/oneapi-rs/src/lib.rs b/oneapi-rs/src/lib.rs index c3dac94..82cd93a 100644 --- a/oneapi-rs/src/lib.rs +++ b/oneapi-rs/src/lib.rs @@ -9,3 +9,6 @@ pub mod info; pub mod platform; pub mod device; +pub mod queue; +pub mod usm; +pub mod buffer; diff --git a/oneapi-rs/src/queue.rs b/oneapi-rs/src/queue.rs new file mode 100644 index 0000000..d1000e9 --- /dev/null +++ b/oneapi-rs/src/queue.rs @@ -0,0 +1,43 @@ +// +// 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 oneapi_rs_sys::queue::ffi; + +use crate::{buffer::Buffer, device::Device, usm::{HostAllocator, SharedAllocator, UsmAllocator}}; + +/// The `Queue` connects a host program to a single device. Programs submit tasks to a device via the +/// `Queue` and may monitor the `Queue` for completion. A program initiates the task by submitting +/// a kernel. +pub struct Queue(pub(crate) cxx::UniquePtr); + +impl Queue { + /// Construct a `Queue` based on the device returned from the default selector. + pub fn new() -> Self { + Self(ffi::new_queue()) + } + + /// Allocates memory and creates a host-side [`Buffer`] that can store an array of T. + /// Safety: the buffer contents are uninitialized. + pub unsafe fn alloc_uninit_host(&self, len: usize) -> Buffer> { + let allocator = UsmAllocator::from(self); + unsafe { Buffer::new(allocator, len) } + } + + /// Allocates memory and creates a shared [`Buffer`] that can store an array of T. + /// Safety: the buffer contents are uninitialized. + pub unsafe fn alloc_uninit_shared(&self, len: usize) -> Buffer> { + let allocator = UsmAllocator::from(self); + unsafe { Buffer::new(allocator, len) } + } +} + +impl From<&Device> for Queue { + fn from(value: &Device) -> Self { + Self(ffi::new_queue_from_device(&value.0)) + } +} diff --git a/oneapi-rs/src/usm.rs b/oneapi-rs/src/usm.rs new file mode 100644 index 0000000..a4d0060 --- /dev/null +++ b/oneapi-rs/src/usm.rs @@ -0,0 +1,85 @@ +// +// 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 crate::queue::Queue; + +use oneapi_rs_sys::usm::ffi; +use allocator_api2::alloc::{Allocator, AllocError}; + +use std::{alloc::Layout, marker::PhantomData, ptr::NonNull}; + +type CxxResult = cxx::core::result::Result; + +/// An instance of a USM allocator. +pub struct UsmAllocator<'a, T: UsmAllocatorKind> { + queue: &'a Queue, + _kind: PhantomData +} + +/// A marker trait for USM allocators. +pub unsafe trait UsmAlloc : Allocator {} + +unsafe impl<'a, T: UsmAllocatorKind> UsmAlloc for UsmAllocator<'a, T> {} + +pub trait UsmAllocatorKind { + unsafe fn alloc(alignment: usize, num_bytes: usize, queue: &Queue) -> CxxResult<*mut u8>; +} + +impl<'a, T: UsmAllocatorKind> From<&'a Queue> for UsmAllocator<'a, T> { + fn from(queue: &'a Queue) -> Self { + Self { + queue, + _kind: PhantomData + } + } +} + +unsafe impl Allocator for UsmAllocator<'_, T> { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + let ptr = unsafe { T::alloc(layout.align(), layout.size(), &self.queue) } + .map_err(|_e| AllocError)?; + + let ptr = NonNull::new(ptr).ok_or(AllocError)?; + let slice = NonNull::slice_from_raw_parts(ptr, layout.size()); + + Ok(slice) + } + + unsafe fn deallocate(&self, ptr: NonNull, _layout: Layout) { + unsafe { ffi::free(ptr.as_ptr(), &self.queue.0); } + } +} + +/// An allocator for Device-side buffers +/// Safety: memory allocated by this allocator cannot be accessed on the host side +#[allow(dead_code)] +pub(crate) struct DeviceAllocator; + +impl UsmAllocatorKind for DeviceAllocator { + unsafe fn alloc(alignment: usize, num_bytes: usize, queue: &Queue) -> CxxResult<*mut u8> { + unsafe { ffi::aligned_alloc_device(alignment, num_bytes, &queue.0) } + } +} + +/// An allocator for Host-side buffers +pub struct HostAllocator; + +impl UsmAllocatorKind for HostAllocator { + unsafe fn alloc(alignment: usize, num_bytes: usize, queue: &Queue) -> CxxResult<*mut u8> { + unsafe { ffi::aligned_alloc_host(alignment, num_bytes, &queue.0) } + } +} + +/// An allocator for shared memory buffers +pub struct SharedAllocator; + +impl UsmAllocatorKind for SharedAllocator { + unsafe fn alloc(alignment: usize, num_bytes: usize, queue: &Queue) -> CxxResult<*mut u8> { + unsafe { ffi::aligned_alloc_shared(alignment, num_bytes, &queue.0) } + } +}