Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ members = [
"vortex-duckdb",
"vortex-cuda",
"vortex-cuda/cub",
"vortex-cuda/driver",
"vortex-cuda/ffi",
"vortex-cuda/gpu-scan-cli",
"vortex-cuda/macros",
Expand Down Expand Up @@ -348,6 +349,7 @@ vortex-zstd = { version = "0.1.0", path = "./encodings/zstd", default-features =
vortex-bench = { path = "./vortex-bench", default-features = false }
vortex-bench-support = { path = "./benchmarks/bench-support" }
vortex-cuda = { path = "./vortex-cuda", default-features = false }
vortex-cuda-driver = { path = "./vortex-cuda/driver" }
vortex-cuda-macros = { path = "./vortex-cuda/macros" }
vortex-duckdb = { path = "./vortex-duckdb", default-features = false }
vortex-tui = { path = "./vortex-tui" }
Expand Down
23 changes: 23 additions & 0 deletions vortex-cuda/driver/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright the Vortex contributors

[package]
name = "vortex-cuda-driver"
authors.workspace = true
description = "Minimal CUDA driver API bindings for Vortex"
edition = { workspace = true }
categories = { workspace = true }
include = { workspace = true }
keywords = { workspace = true }
license = { workspace = true }
readme = { workspace = true }
repository = { workspace = true }
rust-version = { workspace = true }
version = { workspace = true }
publish = false

[lints]
workspace = true

[dependencies]
libloading = { workspace = true }
12 changes: 12 additions & 0 deletions vortex-cuda/driver/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

//! Minimal CUDA driver API bindings for Vortex.
//!
//! The driver library is loaded lazily, so dependents build and run without CUDA until
//! they first use it. The raw bindings and result wrappers operate on CUDA driver handles.

pub mod result;
pub mod sys;

pub use result::DriverError;
257 changes: 257 additions & 0 deletions vortex-cuda/driver/src/result.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright the Vortex contributors

//! Driver calls returning [`Result`]s, for callers that need the raw handles.

use std::ffi::CStr;
use std::ffi::c_uint;
use std::ffi::c_void;
use std::fmt;
use std::mem::MaybeUninit;

use crate::sys;
use crate::sys::CUresult;

/// A failed CUDA driver call.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct DriverError(pub CUresult);

impl DriverError {
/// The driver's name for the error, such as `CUDA_ERROR_INVALID_VALUE`.
pub fn error_name(&self) -> Result<&'static CStr, DriverError> {
let mut name = std::ptr::null();
// SAFETY: The driver writes a pointer to a static string.
unsafe { sys::cuGetErrorName(self.0, &raw mut name) }.result()?;
// SAFETY: Successful calls return a NUL-terminated static string.
Ok(unsafe { CStr::from_ptr(name) })
}

/// The driver's description of the error.
pub fn error_string(&self) -> Result<&'static CStr, DriverError> {
let mut description = std::ptr::null();
// SAFETY: The driver writes a pointer to a static string.
unsafe { sys::cuGetErrorString(self.0, &raw mut description) }.result()?;
// SAFETY: Successful calls return a NUL-terminated static string.
Ok(unsafe { CStr::from_ptr(description) })
}
}

impl fmt::Debug for DriverError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.error_name() {
Ok(name) => write!(f, "DriverError({}, {})", self.0.0, name.to_string_lossy()),
Err(_) => write!(f, "DriverError({})", self.0.0),
}
}
}

impl fmt::Display for DriverError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (self.error_name(), self.error_string()) {
(Ok(name), Ok(description)) => write!(
f,
"{}: {}",
name.to_string_lossy(),
description.to_string_lossy()
),
_ => write!(f, "CUDA driver error {}", self.0.0),
}
}
}

impl std::error::Error for DriverError {}

impl CUresult {
/// `Ok` for [`CUresult::CUDA_SUCCESS`], otherwise the error.
#[inline]
pub fn result(self) -> Result<(), DriverError> {
if self == CUresult::CUDA_SUCCESS {
Ok(())
} else {
Err(DriverError(self))
}
}
}

/// Initializes the driver; repeated calls are cheap.
pub fn init() -> Result<(), DriverError> {
// SAFETY: cuInit has no preconditions.
unsafe { sys::cuInit(0) }.result()
}

/// Allocates `bytes` of page-locked host memory with the given `cuMemHostAlloc` flags.
///
/// # Safety
///
/// A CUDA context must be current; the memory is uninitialized.
pub unsafe fn malloc_host(bytes: usize, flags: c_uint) -> Result<*mut c_void, DriverError> {
let mut pointer = MaybeUninit::uninit();
// SAFETY: The driver writes the allocation into `pointer` on success.
unsafe { sys::cuMemHostAlloc(pointer.as_mut_ptr(), bytes, flags) }.result()?;
// SAFETY: Initialized by the successful call.
Ok(unsafe { pointer.assume_init() })
}

/// Frees memory allocated by [`malloc_host`].
///
/// # Safety
///
/// `pointer` must come from [`malloc_host`] and no pending copy may still use it.
pub unsafe fn free_host(pointer: *mut c_void) -> Result<(), DriverError> {
// SAFETY: Under the caller's guarantees.
unsafe { sys::cuMemFreeHost(pointer) }.result()
}

/// Synchronously copies `target.len()` elements from device memory at `source`.
///
/// # Safety
///
/// `source` must address at least `size_of_val(target)` readable device bytes.
pub unsafe fn memcpy_dtoh_sync<T>(
target: &mut [T],
source: sys::CUdeviceptr,
) -> Result<(), DriverError> {
// SAFETY: Under the caller's guarantees; `target` is writable for its whole size.
unsafe { sys::cuMemcpyDtoH_v2(target.as_mut_ptr().cast(), source, size_of_val(target)) }
.result()
}

/// Device queries.
pub mod device {
use super::*;

/// The device with the given ordinal.
pub fn get(ordinal: i32) -> Result<sys::CUdevice, DriverError> {
let mut device = MaybeUninit::uninit();
// SAFETY: The driver writes the device into `device` on success.
unsafe { sys::cuDeviceGet(device.as_mut_ptr(), ordinal) }.result()?;
// SAFETY: Initialized by the successful call.
Ok(unsafe { device.assume_init() })
}

/// The number of devices.
pub fn get_count() -> Result<i32, DriverError> {
let mut count = 0;
// SAFETY: The driver writes the count.
unsafe { sys::cuDeviceGetCount(&raw mut count) }.result()?;
Ok(count)
}

/// A device attribute.
///
/// # Safety
///
/// `device` must be a valid device handle.
pub unsafe fn get_attribute(
device: sys::CUdevice,
attribute: sys::CUdevice_attribute,
) -> Result<i32, DriverError> {
let mut value = 0;
// SAFETY: Under the caller's guarantees.
unsafe { sys::cuDeviceGetAttribute(&raw mut value, attribute, device) }.result()?;
Ok(value)
}

/// The device's current memory pool.
///
/// # Safety
///
/// `device` must be a valid device handle.
pub unsafe fn get_mem_pool(device: sys::CUdevice) -> Result<sys::CUmemoryPool, DriverError> {
let mut pool = MaybeUninit::uninit();
// SAFETY: Under the caller's guarantees.
unsafe { sys::cuDeviceGetMemPool(pool.as_mut_ptr(), device) }.result()?;
// SAFETY: Initialized by the successful call.
Ok(unsafe { pool.assume_init() })
}
}

/// Memory pool attributes.
pub mod mem_pool {
use super::*;

/// Reads a pool attribute into `value`.
///
/// # Safety
///
/// `value` must point to storage of the attribute's type.
pub unsafe fn get_attribute(
pool: sys::CUmemoryPool,
attribute: sys::CUmemPool_attribute,
value: *mut c_void,
) -> Result<(), DriverError> {
// SAFETY: Under the caller's guarantees.
unsafe { sys::cuMemPoolGetAttribute(pool, attribute, value) }.result()
}

/// Sets a pool attribute from `value`.
///
/// # Safety
///
/// `value` must point to a value of the attribute's type.
pub unsafe fn set_attribute(
pool: sys::CUmemoryPool,
attribute: sys::CUmemPool_attribute,
value: *mut c_void,
) -> Result<(), DriverError> {
// SAFETY: Under the caller's guarantees.
unsafe { sys::cuMemPoolSetAttribute(pool, attribute, value) }.result()
}
}

/// Stream operations on raw handles.
pub mod stream {
use super::*;

/// The (least, greatest) stream priorities of the current context.
pub fn get_priority_range() -> Result<(i32, i32), DriverError> {
let (mut least, mut greatest) = (0, 0);
// SAFETY: The driver writes both values.
unsafe { sys::cuCtxGetStreamPriorityRange(&raw mut least, &raw mut greatest) }.result()?;
Ok((least, greatest))
}

/// Makes `stream` wait for the work recorded in `event`.
///
/// # Safety
///
/// Both handles must be valid and belong to the current context.
pub unsafe fn wait_event(
stream: sys::CUstream,
event: sys::CUevent,
flags: sys::CUevent_wait_flags,
) -> Result<(), DriverError> {
// SAFETY: Under the caller's guarantees.
unsafe { sys::cuStreamWaitEvent(stream, event, flags.0) }.result()
}

/// Enqueues a host function on `stream`.
///
/// # Safety
///
/// `stream` must be valid, and `function` must be safe to call once with `argument` from a
/// driver thread, without making CUDA calls.
pub unsafe fn launch_host_function(
stream: sys::CUstream,
function: unsafe extern "C" fn(*mut c_void),
argument: *mut c_void,
) -> Result<(), DriverError> {
// SAFETY: Under the caller's guarantees.
unsafe { sys::cuLaunchHostFunc(stream, Some(function), argument) }.result()
}
}

/// Event operations on raw handles.
pub mod event {
use super::*;

/// Blocks until the work recorded in `event` completes.
///
/// # Safety
///
/// `event` must be a valid event handle.
pub unsafe fn synchronize(event: sys::CUevent) -> Result<(), DriverError> {
// SAFETY: Under the caller's guarantees.
unsafe { sys::cuEventSynchronize(event) }.result()
}
}
Loading
Loading