This commit is contained in:
elwrcl committed 2026-06-06 13:25:15 +03:00
1 parent 04d4dc8b09
commit 818cab85dc
12511 files changed
+6156641

No files matched your search

@@ -0,0 +1,262 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
use crate::api::icd::*;
use crate::api::types::*;
use crate::api::util::*;
use crate::core::context::*;
use crate::core::device::*;
use crate::core::gl::*;
use crate::core::platform::*;
use mesa_rust::pipe::screen::UUID_SIZE;
use mesa_rust_util::properties::Properties;
use rusticl_opencl_gen::*;
use rusticl_proc_macros::cl_entrypoint;
use rusticl_proc_macros::cl_info_entrypoint;
use std::ffi::c_char;
use std::ffi::c_void;
use std::ptr;
use std::slice;
#[cl_info_entrypoint(clGetContextInfo)]
unsafe impl CLInfo<cl_context_info> for cl_context {
fn query(&self, q: cl_context_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
let ctx = Context::ref_from_raw(*self)?;
match q {
CL_CONTEXT_DEVICES => {
v.write_iter::<cl_device_id>(ctx.devs.iter().map(|&d| cl_device_id::from_ptr(d)))
}
CL_CONTEXT_NUM_DEVICES => v.write::<cl_uint>(ctx.devs.len() as u32),
// need to return None if no properties exist
CL_CONTEXT_PROPERTIES => v.write::<&Properties<cl_context_properties>>(&ctx.properties),
CL_CONTEXT_REFERENCE_COUNT => v.write::<cl_uint>(Context::refcnt(*self)?),
// CL_INVALID_VALUE if param_name is not one of the supported values
_ => Err(CL_INVALID_VALUE),
}
}
}
unsafe impl CLInfo<cl_gl_context_info> for GLCtxManager {
fn query(&self, q: cl_gl_context_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
let info = self.interop_dev_info;
match q {
CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR => {
let ptr = match get_dev_for_uuid(info.device_uuid) {
Some(dev) => dev,
None => ptr::null(),
};
v.write::<cl_device_id>(cl_device_id::from_ptr(ptr))
}
CL_DEVICES_FOR_GL_CONTEXT_KHR => {
// TODO: support multiple devices
v.write_iter::<cl_device_id>(
get_dev_for_uuid(info.device_uuid)
.iter()
.map(|&d| cl_device_id::from_ptr(d)),
)
}
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_entrypoint(clGetGLContextInfoKHR)]
pub fn get_gl_context_info_khr(
properties: *const cl_context_properties,
param_name: cl_gl_context_info,
param_value_size: usize,
param_value: *mut ::std::os::raw::c_void,
param_value_size_ret: *mut usize,
) -> CLResult<()> {
let mut egl_display: EGLDisplay = ptr::null_mut();
let mut glx_display: *mut _XDisplay = ptr::null_mut();
let mut gl_context: *mut c_void = ptr::null_mut();
// CL_INVALID_PROPERTY [...] if the same property name is specified more than once.
// SAFETY: properties is a 0 terminated array by spec.
let props = unsafe { Properties::new(properties) }.ok_or(CL_INVALID_PROPERTY)?;
for (&key, &val) in props.iter() {
let key = u32::try_from(key).or(Err(CL_INVALID_PROPERTY))?;
match key {
// CL_INVALID_PLATFORM [...] if platform value specified in properties is not a valid platform.
CL_CONTEXT_PLATFORM => {
(val as cl_platform_id).get_ref()?;
}
CL_EGL_DISPLAY_KHR => {
egl_display = val as *mut _;
}
CL_GL_CONTEXT_KHR => {
gl_context = val as *mut _;
}
CL_GLX_DISPLAY_KHR => {
glx_display = val as *mut _;
}
// CL_INVALID_PROPERTY if context property name in properties is not a supported property name
_ => return Err(CL_INVALID_PROPERTY),
}
}
let gl_ctx_manager = GLCtxManager::new(gl_context, glx_display, egl_display)?;
unsafe {
gl_ctx_manager
.ok_or(CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR)?
.get_info(
param_name,
param_value_size,
param_value,
param_value_size_ret,
)
}
}
#[cl_entrypoint(clCreateContext)]
fn create_context(
properties: *const cl_context_properties,
num_devices: cl_uint,
devices: *const cl_device_id,
pfn_notify: Option<FuncCreateContextCB>,
user_data: *mut ::std::os::raw::c_void,
) -> CLResult<cl_context> {
// TODO: Actually hook this callback up so it gets called when appropriate.
// SAFETY: The requirements on `CreateContextCB::try_new` match the requirements
// imposed by the OpenCL specification. It is the caller's duty to uphold them.
let _cb_opt = unsafe { CreateContextCB::try_new(pfn_notify, user_data)? };
// CL_INVALID_VALUE if devices is NULL.
if devices.is_null() {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_VALUE if num_devices is equal to zero.
if num_devices == 0 {
return Err(CL_INVALID_VALUE);
}
let mut egl_display: EGLDisplay = ptr::null_mut();
let mut glx_display: *mut _XDisplay = ptr::null_mut();
let mut gl_context: *mut c_void = ptr::null_mut();
// CL_INVALID_PROPERTY [...] if the same property name is specified more than once.
// SAFETY: properties is a 0 terminated array by spec.
let props = unsafe { Properties::new(properties) }.ok_or(CL_INVALID_PROPERTY)?;
for (&key, &val) in props.iter() {
let key = u32::try_from(key).or(Err(CL_INVALID_PROPERTY))?;
match key {
// CL_INVALID_PLATFORM [...] if platform value specified in properties is not a valid platform.
CL_CONTEXT_PLATFORM => {
(val as cl_platform_id).get_ref()?;
}
CL_CONTEXT_INTEROP_USER_SYNC => {
check_cl_bool(val).ok_or(CL_INVALID_PROPERTY)?;
}
CL_EGL_DISPLAY_KHR => {
egl_display = val as *mut _;
}
CL_GL_CONTEXT_KHR => {
gl_context = val as *mut _;
}
CL_GLX_DISPLAY_KHR => {
glx_display = val as *mut _;
}
// CL_INVALID_PROPERTY if context property name in properties is not a supported property name
_ => return Err(CL_INVALID_PROPERTY),
}
}
// Duplicate devices specified in devices are ignored.
let mut devs = unsafe { slice::from_raw_parts(devices, num_devices as usize) }.to_owned();
devs.sort();
devs.dedup();
let devs: Result<_, _> = devs.into_iter().map(Device::ref_from_raw).collect();
let devs: Vec<&Device> = devs?;
let gl_ctx_manager = GLCtxManager::new(gl_context, glx_display, egl_display)?;
if let Some(gl_ctx_manager) = &gl_ctx_manager {
// errcode_ret returns CL_INVALID_OPERATION if a context was specified as described above
// and any of the following conditions hold:
// ...
// Any of the devices specified in the devices argument cannot support OpenCL objects which
// share the data store of an OpenGL object.
let [dev] = devs.as_slice() else {
return Err(CL_INVALID_OPERATION);
};
if !dev.is_gl_sharing_supported() {
return Err(CL_INVALID_OPERATION);
}
// gl sharing is only supported on devices with an UUID, so we can simply unwrap it
let dev_uuid: [c_char; UUID_SIZE] = dev
.screen()
.device_uuid()
.unwrap_or_default()
.map(|val| val as c_char);
if gl_ctx_manager.interop_dev_info.device_uuid != dev_uuid {
// we only support gl_sharing on the same device
return Err(CL_INVALID_OPERATION);
}
}
Ok(Context::new(devs, props, gl_ctx_manager).into_cl())
}
#[cl_entrypoint(clCreateContextFromType)]
fn create_context_from_type(
properties: *const cl_context_properties,
device_type: cl_device_type,
pfn_notify: Option<FuncCreateContextCB>,
user_data: *mut ::std::os::raw::c_void,
) -> CLResult<cl_context> {
// CL_INVALID_DEVICE_TYPE if device_type is not a valid value.
check_cl_device_type(device_type)?;
let devs: Vec<_> = get_devs_for_type(device_type)
.into_iter()
.map(|d| cl_device_id::from_ptr(d))
.collect();
// CL_DEVICE_NOT_FOUND if no devices that match device_type and property values specified in properties were found.
if devs.is_empty() {
return Err(CL_DEVICE_NOT_FOUND);
}
// errors are essentially the same and we will always pass in a valid
// device list, so that's fine as well.
create_context(
properties,
devs.len() as u32,
devs.as_ptr(),
pfn_notify,
user_data,
)
}
#[cl_entrypoint(clRetainContext)]
fn retain_context(context: cl_context) -> CLResult<()> {
Context::retain(context)
}
#[cl_entrypoint(clReleaseContext)]
fn release_context(context: cl_context) -> CLResult<()> {
Context::release(context)
}
#[cl_entrypoint(clSetContextDestructorCallback)]
fn set_context_destructor_callback(
context: cl_context,
pfn_notify: ::std::option::Option<FuncDeleteContextCB>,
user_data: *mut ::std::os::raw::c_void,
) -> CLResult<()> {
let c = Context::ref_from_raw(context)?;
// SAFETY: The requirements on `DeleteContextCB::new` match the requirements
// imposed by the OpenCL specification. It is the caller's duty to uphold them.
let cb = unsafe { DeleteContextCB::new(pfn_notify, user_data)? };
c.dtors.lock().unwrap().push(cb);
Ok(())
}
+490
View File
@@ -0,0 +1,490 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
use crate::api::icd::*;
use crate::api::types::IdpAccelProps;
use crate::api::util::*;
use crate::core::device::*;
use crate::core::platform::*;
use crate::core::version::*;
use mesa_rust_gen::*;
use mesa_rust_util::ptr::*;
use rusticl_opencl_gen::*;
use rusticl_proc_macros::cl_entrypoint;
use rusticl_proc_macros::cl_info_entrypoint;
use std::cmp::min;
use std::ffi::c_char;
use std::ffi::CStr;
use std::ptr;
const SPIRV_SUPPORT_STRING: &CStr =
c"SPIR-V_1.0 SPIR-V_1.1 SPIR-V_1.2 SPIR-V_1.3 SPIR-V_1.4 SPIR-V_1.5 SPIR-V_1.6";
const SPIRV_SUPPORT: [cl_name_version; 7] = [
mk_cl_version_ext(1, 0, 0, "SPIR-V"),
mk_cl_version_ext(1, 1, 0, "SPIR-V"),
mk_cl_version_ext(1, 2, 0, "SPIR-V"),
mk_cl_version_ext(1, 3, 0, "SPIR-V"),
mk_cl_version_ext(1, 4, 0, "SPIR-V"),
mk_cl_version_ext(1, 5, 0, "SPIR-V"),
mk_cl_version_ext(1, 6, 0, "SPIR-V"),
];
type ClDevIdpAccelProps = cl_device_integer_dot_product_acceleration_properties;
#[cl_info_entrypoint(clGetDeviceInfo)]
unsafe impl CLInfo<cl_device_info> for cl_device_id {
fn query(&self, q: cl_device_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
let dev = Device::ref_from_raw(*self)?;
// curses you CL_DEVICE_INTEGER_DOT_PRODUCT_ACCELERATION_PROPERTIES_4x8BIT_PACKED_KHR
#[allow(non_upper_case_globals)]
match q {
CL_DEVICE_ADDRESS_BITS => v.write::<cl_uint>(dev.address_bits()),
CL_DEVICE_ATOMIC_FENCE_CAPABILITIES => v.write::<cl_device_atomic_capabilities>(
(CL_DEVICE_ATOMIC_ORDER_RELAXED
| CL_DEVICE_ATOMIC_ORDER_ACQ_REL
| CL_DEVICE_ATOMIC_SCOPE_WORK_GROUP)
as cl_device_atomic_capabilities,
),
CL_DEVICE_ATOMIC_MEMORY_CAPABILITIES => v.write::<cl_device_atomic_capabilities>(
(CL_DEVICE_ATOMIC_ORDER_RELAXED | CL_DEVICE_ATOMIC_SCOPE_WORK_GROUP)
as cl_device_atomic_capabilities,
),
CL_DEVICE_AVAILABLE => v.write::<bool>(true),
CL_DEVICE_BUILT_IN_KERNELS => v.write::<&CStr>(c""),
CL_DEVICE_BUILT_IN_KERNELS_WITH_VERSION => v.write::<&[cl_name_version]>(&[]),
CL_DEVICE_COMPILER_AVAILABLE => v.write::<bool>(true),
CL_DEVICE_CROSS_DEVICE_SHARED_MEM_CAPABILITIES_INTEL => {
v.write::<cl_device_device_enqueue_capabilities>(0)
}
CL_DEVICE_DEVICE_ENQUEUE_CAPABILITIES => {
v.write::<cl_device_device_enqueue_capabilities>(0)
}
CL_DEVICE_DEVICE_MEM_CAPABILITIES_INTEL => {
v.write::<cl_device_unified_shared_memory_capabilities_intel>(0)
}
CL_DEVICE_DOUBLE_FP_CONFIG => v.write::<cl_device_fp_config>(
if dev.fp64_supported() {
let mut fp64_config = CL_FP_FMA
| CL_FP_ROUND_TO_NEAREST
| CL_FP_ROUND_TO_ZERO
| CL_FP_ROUND_TO_INF
| CL_FP_INF_NAN
| CL_FP_DENORM;
if dev.fp64_is_softfp() {
fp64_config |= CL_FP_SOFT_FLOAT;
}
fp64_config
} else {
0
}
.into(),
),
CL_DEVICE_ENDIAN_LITTLE => v.write::<bool>(dev.little_endian()),
CL_DEVICE_ERROR_CORRECTION_SUPPORT => v.write::<bool>(false),
CL_DEVICE_EXECUTION_CAPABILITIES => {
v.write::<cl_device_exec_capabilities>(CL_EXEC_KERNEL.into())
}
CL_DEVICE_EXTENSIONS => v.write::<&str>(&dev.extension_string),
CL_DEVICE_EXTENSIONS_WITH_VERSION => v.write::<&[cl_name_version]>(&dev.extensions),
CL_DEVICE_GENERIC_ADDRESS_SPACE_SUPPORT => v.write::<bool>(false),
CL_DEVICE_GLOBAL_MEM_CACHE_TYPE => v.write::<cl_device_mem_cache_type>(CL_NONE),
CL_DEVICE_GLOBAL_MEM_CACHE_SIZE => v.write::<cl_ulong>(0),
CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE => v.write::<cl_uint>(0),
CL_DEVICE_GLOBAL_MEM_SIZE => v.write::<cl_ulong>(dev.global_mem_size()),
CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE => v.write::<usize>(0),
// Provided by the cl_khr_fp16 extension.
CL_DEVICE_HALF_FP_CONFIG if dev.fp16_supported() => {
v.write::<cl_device_fp_config>((CL_FP_ROUND_TO_NEAREST | CL_FP_INF_NAN).into())
}
CL_DEVICE_HOST_MEM_CAPABILITIES_INTEL => {
v.write::<cl_device_unified_shared_memory_capabilities_intel>(0)
}
CL_DEVICE_HOST_UNIFIED_MEMORY => v.write::<bool>(dev.unified_memory()),
CL_DEVICE_IL_VERSION => v.write::<&CStr>(SPIRV_SUPPORT_STRING),
CL_DEVICE_ILS_WITH_VERSION => v.write::<&[cl_name_version]>(&SPIRV_SUPPORT),
CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT => {
v.write::<cl_uint>(dev.image_base_address_alignment())
}
CL_DEVICE_IMAGE_MAX_ARRAY_SIZE => v.write::<usize>(dev.image_array_size()),
CL_DEVICE_IMAGE_MAX_BUFFER_SIZE => v.write::<usize>(dev.image_buffer_max_size_pixels()),
CL_DEVICE_IMAGE_PITCH_ALIGNMENT => v.write::<cl_uint>(dev.image_pitch_alignment()),
CL_DEVICE_IMAGE_SUPPORT => v.write::<bool>(dev.caps.has_images),
CL_DEVICE_IMAGE2D_MAX_HEIGHT => v.write::<usize>(dev.caps.image_2d_size as usize),
CL_DEVICE_IMAGE2D_MAX_WIDTH => v.write::<usize>(dev.caps.image_2d_size as usize),
CL_DEVICE_IMAGE3D_MAX_HEIGHT => v.write::<usize>(dev.image_3d_size()),
CL_DEVICE_IMAGE3D_MAX_WIDTH => v.write::<usize>(dev.image_3d_size()),
CL_DEVICE_IMAGE3D_MAX_DEPTH => v.write::<usize>(dev.image_3d_size()),
CL_DEVICE_INTEGER_DOT_PRODUCT_CAPABILITIES => {
v.write::<cl_device_integer_dot_product_capabilities>(
(CL_DEVICE_INTEGER_DOT_PRODUCT_INPUT_4x8BIT_PACKED
| CL_DEVICE_INTEGER_DOT_PRODUCT_INPUT_4x8BIT)
.into(),
)
}
CL_DEVICE_INTEGER_DOT_PRODUCT_ACCELERATION_PROPERTIES_8BIT => v
.write::<ClDevIdpAccelProps>({
let pack = dev.pack_32_4x8_supported();
let sdot = dev.sdot_4x8_supported() && pack;
let udot = dev.udot_4x8_supported() && pack;
let sudot = dev.sudot_4x8_supported() && pack;
let sdot_sat = dev.sdot_4x8_sat_supported() && pack;
let udot_sat = dev.udot_4x8_sat_supported() && pack;
let sudot_sat = dev.sudot_4x8_sat_supported() && pack;
IdpAccelProps::new(
sdot.into(),
udot.into(),
sudot.into(),
sdot_sat.into(),
udot_sat.into(),
sudot_sat.into(),
)
}),
CL_DEVICE_INTEGER_DOT_PRODUCT_ACCELERATION_PROPERTIES_4x8BIT_PACKED => {
v.write::<ClDevIdpAccelProps>({
IdpAccelProps::new(
dev.sdot_4x8_supported().into(),
dev.udot_4x8_supported().into(),
dev.sudot_4x8_supported().into(),
dev.sdot_4x8_sat_supported().into(),
dev.udot_4x8_sat_supported().into(),
dev.sudot_4x8_sat_supported().into(),
)
})
}
CL_DEVICE_KERNEL_CLOCK_CAPABILITIES_KHR if dev.kernel_clock_supported() => {
v.write::<cl_device_kernel_clock_capabilities_khr>(
(CL_DEVICE_KERNEL_CLOCK_SCOPE_DEVICE_KHR
| CL_DEVICE_KERNEL_CLOCK_SCOPE_SUB_GROUP_KHR)
.into(),
)
}
CL_DEVICE_LATEST_CONFORMANCE_VERSION_PASSED => {
v.write::<&CStr>(dev.screen().cl_cts_version())
}
CL_DEVICE_LINKER_AVAILABLE => v.write::<bool>(true),
CL_DEVICE_LOCAL_MEM_SIZE => v.write::<cl_ulong>(dev.local_mem_size()),
// TODO add query for CL_LOCAL vs CL_GLOBAL
CL_DEVICE_LOCAL_MEM_TYPE => v.write::<cl_device_local_mem_type>(CL_GLOBAL),
CL_DEVICE_LUID => v.write::<[cl_uchar; CL_LUID_SIZE as usize]>(
dev.screen().device_luid().unwrap_or_default(),
),
CL_DEVICE_LUID_VALID => v.write::<cl_bool>(dev.screen().device_luid().is_some().into()),
CL_DEVICE_MAX_CLOCK_FREQUENCY => v.write::<cl_uint>(dev.max_clock_freq()),
CL_DEVICE_MAX_COMPUTE_UNITS => v.write::<cl_uint>(dev.max_compute_units()),
// TODO atm implemented as mem_const
CL_DEVICE_MAX_CONSTANT_ARGS => v.write::<cl_uint>(dev.const_max_count()),
CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE => v.write::<cl_ulong>(dev.const_max_size()),
CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE => v.write::<usize>(0),
CL_DEVICE_MAX_MEM_ALLOC_SIZE => v.write::<cl_ulong>(dev.max_mem_alloc()),
CL_DEVICE_MAX_NUM_SUB_GROUPS => v.write::<cl_uint>(if dev.subgroups_supported() {
dev.max_subgroups()
} else {
0
}),
CL_DEVICE_MAX_ON_DEVICE_EVENTS => v.write::<cl_uint>(0),
CL_DEVICE_MAX_ON_DEVICE_QUEUES => v.write::<cl_uint>(0),
CL_DEVICE_MAX_PARAMETER_SIZE => v.write::<usize>(dev.param_max_size()),
CL_DEVICE_MAX_PIPE_ARGS => v.write::<cl_uint>(0),
CL_DEVICE_MAX_READ_IMAGE_ARGS => v.write::<cl_uint>(dev.caps.max_read_images),
CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS => v.write::<cl_uint>(if dev.caps.has_rw_images {
dev.caps.max_write_images
} else {
0
}),
CL_DEVICE_MAX_SAMPLERS => v.write::<cl_uint>(dev.max_samplers()),
CL_DEVICE_MAX_WORK_GROUP_SIZE => v.write::<usize>(dev.max_threads_per_block()),
CL_DEVICE_MAX_WORK_GROUP_SIZES => v.write::<&[usize]>(&dev.max_block_sizes()),
CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS => v.write::<cl_uint>(dev.max_grid_dimensions()),
CL_DEVICE_MAX_WRITE_IMAGE_ARGS => v.write::<cl_uint>(dev.caps.max_write_images),
CL_DEVICE_MEM_BASE_ADDR_ALIGN => v.write::<cl_uint>(dev.mem_base_addr_align_bits()),
CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE => {
v.write::<cl_uint>(16 * size_of::<cl_ulong>() as cl_uint)
}
CL_DEVICE_NAME => v.write::<&CStr>(dev.screen().name()),
CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR => v.write::<cl_uint>(1),
CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE => v.write::<cl_uint>(dev.fp64_supported().into()),
CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT => v.write::<cl_uint>(1),
CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF => v.write::<cl_uint>(dev.fp16_supported().into()),
CL_DEVICE_NATIVE_VECTOR_WIDTH_INT => v.write::<cl_uint>(1),
CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG => v.write::<cl_uint>(1),
CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT => v.write::<cl_uint>(1),
CL_DEVICE_NODE_MASK => {
v.write::<cl_uint>(dev.screen().device_node_mask().unwrap_or_default())
}
CL_DEVICE_NON_UNIFORM_WORK_GROUP_SUPPORT => v.write::<bool>(false),
CL_DEVICE_NUMERIC_VERSION => v.write::<cl_version>(dev.cl_version.into()),
CL_DEVICE_OPENCL_C_ALL_VERSIONS => v.write::<&[cl_name_version]>(&dev.clc_versions),
CL_DEVICE_OPENCL_C_FEATURES => v.write::<&[cl_name_version]>(&dev.clc_features),
CL_DEVICE_OPENCL_C_NUMERIC_VERSION_KHR => {
v.write::<cl_version_khr>(dev.clc_version.into())
}
CL_DEVICE_OPENCL_C_VERSION => {
v.write::<&str>(&format!("OpenCL C {} ", dev.clc_version.api_str()))
}
// TODO subdevice support
CL_DEVICE_PARENT_DEVICE => v.write::<cl_device_id>(cl_device_id::from_ptr(ptr::null())),
CL_DEVICE_PARTITION_AFFINITY_DOMAIN => v.write::<cl_device_affinity_domain>(0),
CL_DEVICE_PARTITION_MAX_SUB_DEVICES => v.write::<cl_uint>(0),
CL_DEVICE_PARTITION_PROPERTIES => v.write::<&[cl_device_partition_property]>(&[0]),
CL_DEVICE_PARTITION_TYPE => v.write::<&[cl_device_partition_property]>(&[]),
CL_DEVICE_PCI_BUS_INFO_KHR => {
v.write::<cl_device_pci_bus_info_khr>(dev.pci_info().ok_or(CL_INVALID_VALUE)?)
}
CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS => v.write::<cl_uint>(0),
CL_DEVICE_PIPE_MAX_PACKET_SIZE => v.write::<cl_uint>(0),
CL_DEVICE_PIPE_SUPPORT => v.write::<bool>(false),
CL_DEVICE_PLATFORM => v.write::<cl_platform_id>(Platform::get().as_ptr()),
CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT => v.write::<cl_uint>(0),
CL_DEVICE_PREFERRED_INTEROP_USER_SYNC => v.write::<bool>(true),
CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT => v.write::<cl_uint>(0),
CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT => v.write::<cl_uint>(0),
CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR => v.write::<cl_uint>(1),
CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE => {
v.write::<cl_uint>(dev.fp64_supported().into())
}
CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT => v.write::<cl_uint>(1),
CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF => {
v.write::<cl_uint>(dev.fp16_supported().into())
}
CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT => v.write::<cl_uint>(1),
CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG => v.write::<cl_uint>(1),
CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT => v.write::<cl_uint>(1),
CL_DEVICE_PREFERRED_WORK_GROUP_SIZE_MULTIPLE => {
v.write::<usize>(dev.subgroup_sizes().next().unwrap())
}
CL_DEVICE_PRINTF_BUFFER_SIZE => v.write::<usize>(dev.printf_buffer_size()),
CL_DEVICE_PROFILE => v.write::<&CStr>(if dev.embedded {
c"EMBEDDED_PROFILE"
} else {
c"FULL_PROFILE"
}),
CL_DEVICE_PROFILING_TIMER_RESOLUTION => {
v.write::<usize>(dev.caps.timer_resolution as usize)
}
CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE => v.write::<cl_uint>(0),
CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE => v.write::<cl_uint>(0),
CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES => v.write::<cl_command_queue_properties>(0),
CL_DEVICE_QUEUE_ON_HOST_PROPERTIES => v.write::<cl_command_queue_properties>(
(CL_QUEUE_PROFILING_ENABLE | CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE).into(),
),
CL_DEVICE_REFERENCE_COUNT => v.write::<cl_uint>(1),
CL_DEVICE_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR
if dev.are_external_semaphores_supported() =>
{
v.write::<&[cl_external_semaphore_handle_type_khr]>(&[
CL_SEMAPHORE_HANDLE_SYNC_FD_KHR,
])
}
CL_DEVICE_SEMAPHORE_IMPORT_HANDLE_TYPES_KHR
if dev.are_external_semaphores_supported() =>
{
v.write::<&[cl_external_semaphore_handle_type_khr]>(&[
CL_SEMAPHORE_HANDLE_SYNC_FD_KHR,
])
}
CL_DEVICE_SEMAPHORE_TYPES_KHR if dev.are_semaphores_supported() => {
v.write::<&[cl_semaphore_type_khr]>(&[CL_SEMAPHORE_TYPE_BINARY_KHR])
}
CL_DEVICE_SHARED_SYSTEM_MEM_CAPABILITIES_INTEL => {
v.write::<cl_device_unified_shared_memory_capabilities_intel>(0)
}
CL_DEVICE_SINGLE_DEVICE_SHARED_MEM_CAPABILITIES_INTEL => {
v.write::<cl_device_unified_shared_memory_capabilities_intel>(0)
}
CL_DEVICE_SINGLE_FP_CONFIG => v.write::<cl_device_fp_config>(
(CL_FP_ROUND_TO_NEAREST | CL_FP_INF_NAN) as cl_device_fp_config,
),
CL_DEVICE_SPIRV_CAPABILITIES => {
v.write_iter::<cl_uint>(dev.spirv_caps_vec.iter().map(|&cap| cap as _))
}
CL_DEVICE_SPIRV_EXTENDED_INSTRUCTION_SETS => {
// use static memory as we hand out pointers to the values here.
static instr_sets: [&CStr; 1] = [c"OpenCL.std"];
v.write_iter::<*const c_char>(instr_sets.iter().map(|str| str.as_ptr()))
}
CL_DEVICE_SPIRV_EXTENSIONS => {
v.write_iter::<*const c_char>(dev.spirv_extensions.iter().map(|str| str.as_ptr()))
}
CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS => v.write::<bool>(false),
CL_DEVICE_SUB_GROUP_SIZES_INTEL => {
if dev.subgroups_supported() {
v.write_iter::<usize>(dev.subgroup_sizes())
} else {
v.write::<&[usize]>(&[0; 1])
}
}
CL_DEVICE_SVM_CAPABILITIES | CL_DEVICE_SVM_CAPABILITIES_ARM => {
let mut caps = 0;
if dev.api_svm_supported() {
caps |= CL_DEVICE_SVM_COARSE_GRAIN_BUFFER;
}
if dev.system_svm_supported() {
caps |= CL_DEVICE_SVM_FINE_GRAIN_BUFFER | CL_DEVICE_SVM_FINE_GRAIN_SYSTEM;
}
v.write::<cl_device_svm_capabilities>(caps.into())
}
CL_DEVICE_TYPE => {
// CL_DEVICE_TYPE_DEFAULT ... will never be returned in CL_DEVICE_TYPE for any
// OpenCL device.
v.write::<cl_device_type>((dev.device_type & !CL_DEVICE_TYPE_DEFAULT).into())
}
CL_DEVICE_UUID => v.write::<[cl_uchar; CL_UUID_SIZE as usize]>(
dev.screen().device_uuid().unwrap_or_default(),
),
CL_DEVICE_VENDOR => v.write::<&CStr>(dev.screen().device_vendor()),
CL_DEVICE_VENDOR_ID => v.write::<cl_uint>(dev.vendor_id()),
CL_DEVICE_VERSION => v.write::<&str>(&format!("OpenCL {} ", dev.cl_version.api_str())),
CL_DRIVER_UUID => v.write::<[cl_char; CL_UUID_SIZE as usize]>(
dev.screen().driver_uuid().unwrap_or_default(),
),
CL_DRIVER_VERSION => v.write::<&CStr>(unsafe { CStr::from_ptr(mesa_version_string()) }),
CL_DEVICE_WORK_GROUP_COLLECTIVE_FUNCTIONS_SUPPORT => v.write::<bool>(false),
// CL_INVALID_VALUE if param_name is not one of the supported values
// CL_INVALID_VALUE [...] if param_name is a value that is available as an extension and the corresponding extension is not supported by the device.
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_entrypoint(clGetDeviceIDs)]
fn get_device_ids(
platform: cl_platform_id,
device_type: cl_device_type,
num_entries: cl_uint,
devices: *mut cl_device_id,
num_devices: *mut cl_uint,
) -> CLResult<()> {
// CL_INVALID_PLATFORM if platform is not a valid platform.
platform.get_ref()?;
// CL_INVALID_DEVICE_TYPE if device_type is not a valid value.
check_cl_device_type(device_type)?;
// CL_INVALID_VALUE if num_entries is equal to zero and devices is not NULL
if num_entries == 0 && !devices.is_null() {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_VALUE [...] if both num_devices and devices are NULL.
if num_devices.is_null() && devices.is_null() {
return Err(CL_INVALID_VALUE);
}
let devs = get_devs_for_type(device_type);
// CL_DEVICE_NOT_FOUND if no OpenCL devices that matched device_type were found
if devs.is_empty() {
return Err(CL_DEVICE_NOT_FOUND);
}
debug_assert!(
devs.len() <= cl_uint::MAX as usize,
"number of available devices exceeds `cl_uint::MAX`"
);
// num_devices returns the number of OpenCL devices available that match device_type. If
// num_devices is NULL, this argument is ignored.
// SAFETY: Caller is responsible for providing a null pointer or one valid
// for a write of `size_of::<cl_uint>()`.
unsafe { num_devices.write_checked(devs.len() as cl_uint) };
if !devices.is_null() {
let n = min(num_entries as usize, devs.len());
#[allow(clippy::needless_range_loop)]
for i in 0..n {
unsafe {
*devices.add(i) = cl_device_id::from_ptr(devs[i]);
}
}
}
Ok(())
}
#[cl_entrypoint(clRetainDevice)]
fn retain_device(device: cl_device_id) -> CLResult<()> {
let _ = Device::ref_from_raw(device)?;
Ok(())
}
#[cl_entrypoint(clReleaseDevice)]
fn release_device(device: cl_device_id) -> CLResult<()> {
let _ = Device::ref_from_raw(device)?;
Ok(())
}
#[cl_entrypoint(clCreateSubDevices)]
fn create_sub_devices(
_device: cl_device_id,
_properties: *const cl_device_partition_property,
_num_devices: cl_uint,
_out_devices: *mut cl_device_id,
_num_devices_ret: *mut cl_uint,
) -> CLResult<()> {
// CL_INVALID_VALUE if values specified in properties are not valid or
// if values specified in properties are valid but not supported by the
// device.
Err(CL_INVALID_VALUE)
}
#[cl_entrypoint(clGetDeviceAndHostTimer)]
fn get_device_and_host_timer(
device: cl_device_id,
device_timestamp: *mut cl_ulong,
host_timestamp: *mut cl_ulong,
) -> CLResult<()> {
if device_timestamp.is_null() {
// CL_INVALID_VALUE if host_timestamp or device_timestamp is NULL
return Err(CL_INVALID_VALUE);
}
get_host_timer(device, host_timestamp)?;
// There is a requirement that the two timestamps
// are synchronised, but don't need to be the same,
// but as it is, the same timestamp is the best to
// use for both
// Safe because null check on device_timestamp above
// and host_timestamp null check in get_host_timer
unsafe {
*device_timestamp = *host_timestamp;
};
Ok(())
}
#[cl_entrypoint(clGetHostTimer)]
fn get_host_timer(device_id: cl_device_id, host_timestamp: *mut cl_ulong) -> CLResult<()> {
if host_timestamp.is_null() {
// CL_INVALID_VALUE if host_timestamp is NULL
return Err(CL_INVALID_VALUE);
}
let device = Device::ref_from_raw(device_id)?;
if !device.caps.has_timestamp {
// CL_INVALID_OPERATION if the platform associated with device does not support device and host timer synchronization
return Err(CL_INVALID_OPERATION);
}
// Currently the best clock we have for the host_timestamp
// SAFETY: Caller is responsible for providing a pointer valid for a write
// of `size_of::<cl_ulong>()`.
unsafe { host_timestamp.write_checked(device.screen().get_timestamp()) };
Ok(())
}
#[cl_entrypoint(clSetDefaultDeviceCommandQueue)]
fn set_default_device_command_queue(
_context: cl_context,
_device: cl_device_id,
_command_queue: cl_command_queue,
) -> CLResult<()> {
Err(CL_INVALID_OPERATION)
}
+194
View File
@@ -0,0 +1,194 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
use crate::api::icd::*;
use crate::api::types::*;
use crate::api::util::*;
use crate::core::context::*;
use crate::core::event::*;
use crate::core::queue::*;
use rusticl_opencl_gen::*;
use rusticl_proc_macros::cl_entrypoint;
use rusticl_proc_macros::cl_info_entrypoint;
use std::ptr;
use std::sync::Arc;
use std::sync::Weak;
#[cl_info_entrypoint(clGetEventInfo)]
unsafe impl CLInfo<cl_event_info> for cl_event {
fn query(&self, q: cl_event_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
let event = Event::ref_from_raw(*self)?;
match *q {
CL_EVENT_COMMAND_EXECUTION_STATUS => v.write::<cl_int>(event.status()),
CL_EVENT_CONTEXT => {
// Note we use as_ptr here which doesn't increase the reference count.
let ptr = Arc::as_ptr(&event.context);
v.write::<cl_context>(cl_context::from_ptr(ptr))
}
CL_EVENT_COMMAND_QUEUE => {
let ptr = match event.queue.as_ref() {
// Note we use as_ptr here which doesn't increase the reference count.
Some(queue) => Weak::as_ptr(queue),
None => ptr::null_mut(),
};
v.write::<cl_command_queue>(cl_command_queue::from_ptr(ptr))
}
CL_EVENT_REFERENCE_COUNT => v.write::<cl_uint>(Event::refcnt(*self)?),
CL_EVENT_COMMAND_TYPE => v.write::<cl_command_type>(event.cmd_type),
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_info_entrypoint(clGetEventProfilingInfo)]
unsafe impl CLInfo<cl_profiling_info> for cl_event {
fn query(&self, q: cl_profiling_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
let event = Event::ref_from_raw(*self)?;
if event.cmd_type == CL_COMMAND_USER {
// CL_PROFILING_INFO_NOT_AVAILABLE [...] if event is a user event object.
return Err(CL_PROFILING_INFO_NOT_AVAILABLE);
}
match *q {
CL_PROFILING_COMMAND_QUEUED => v.write::<cl_ulong>(event.get_time(EventTimes::Queued)),
CL_PROFILING_COMMAND_SUBMIT => v.write::<cl_ulong>(event.get_time(EventTimes::Submit)),
CL_PROFILING_COMMAND_START => v.write::<cl_ulong>(event.get_time(EventTimes::Start)),
CL_PROFILING_COMMAND_END => v.write::<cl_ulong>(event.get_time(EventTimes::End)),
// For now, we treat Complete the same as End
CL_PROFILING_COMMAND_COMPLETE => v.write::<cl_ulong>(event.get_time(EventTimes::End)),
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_entrypoint(clCreateUserEvent)]
fn create_user_event(context: cl_context) -> CLResult<cl_event> {
let c = Context::arc_from_raw(context)?;
Ok(Event::new_user(c).into_cl())
}
#[cl_entrypoint(clRetainEvent)]
fn retain_event(event: cl_event) -> CLResult<()> {
Event::retain(event)
}
#[cl_entrypoint(clReleaseEvent)]
fn release_event(event: cl_event) -> CLResult<()> {
Event::release(event)
}
#[cl_entrypoint(clWaitForEvents)]
fn wait_for_events(num_events: cl_uint, event_list: *const cl_event) -> CLResult<()> {
let evs = Event::arcs_from_arr(event_list, num_events)?;
if let Some((first, rest)) = evs.split_first() {
// > CL_INVALID_CONTEXT if events specified in event_list do not belong
// > to the same context.
if rest.iter().any(|e| e.context != first.context) {
return Err(CL_INVALID_CONTEXT);
}
} else {
// > CL_INVALID_VALUE if num_events is zero or event_list is NULL.
return Err(CL_INVALID_VALUE);
}
// find all queues we have to flush
for q in Event::deep_unflushed_queues(&evs) {
q.flush(false)?;
}
// now wait on all events and check if we got any errors
let mut err = false;
for e in evs {
err |= e.wait() < 0;
}
// CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST if the execution status of any of the events
// in event_list is a negative integer value.
if err {
return Err(CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST);
}
Ok(())
}
#[cl_entrypoint(clSetEventCallback)]
fn set_event_callback(
event: cl_event,
command_exec_callback_type: cl_int,
pfn_event_notify: Option<FuncEventCB>,
user_data: *mut ::std::os::raw::c_void,
) -> CLResult<()> {
let e = Event::ref_from_raw(event)?;
// CL_INVALID_VALUE [...] if command_exec_callback_type is not CL_SUBMITTED, CL_RUNNING, or CL_COMPLETE.
if ![CL_SUBMITTED, CL_RUNNING, CL_COMPLETE].contains(&(command_exec_callback_type as cl_uint)) {
return Err(CL_INVALID_VALUE);
}
// SAFETY: The requirements on `EventCB::new` match the requirements
// imposed by the OpenCL specification. It is the caller's duty to uphold them.
let cb = unsafe { EventCB::new(pfn_event_notify, user_data)? };
e.add_cb(command_exec_callback_type, cb);
Ok(())
}
#[cl_entrypoint(clSetUserEventStatus)]
fn set_user_event_status(event: cl_event, execution_status: cl_int) -> CLResult<()> {
let e = Event::ref_from_raw(event)?;
// CL_INVALID_VALUE if the execution_status is not CL_COMPLETE or a negative integer value.
if execution_status != CL_COMPLETE as cl_int && execution_status > 0 {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_OPERATION if the execution_status for event has already been changed by a
// previous call to clSetUserEventStatus.
if e.status() != CL_SUBMITTED as cl_int {
return Err(CL_INVALID_OPERATION);
}
e.set_user_status(execution_status);
Ok(())
}
/// implements CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST when `block = true`
pub fn create_and_queue(
q: Arc<Queue>,
cmd_type: cl_command_type,
deps: Vec<Arc<Event>>,
event: *mut cl_event,
block: bool,
work: EventSig,
) -> CLResult<()> {
if deps.iter().any(|dep| dep.is_error()) {
return Err(CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST);
}
let e = Event::new(&q, cmd_type, deps, work);
if !event.is_null() {
// SAFETY: we check for null and valid API use is to pass in a valid pointer
unsafe {
event.write(Arc::clone(&e).into_cl());
}
}
if block {
q.queue(Arc::clone(&e));
q.flush(true)?;
if e.deps.iter().any(|dep| dep.is_error()) {
return Err(CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST);
}
// return any execution errors when blocking
let err = e.status();
if err < 0 {
return Err(err);
}
} else {
q.queue(e);
}
Ok(())
}
+680
View File
@@ -0,0 +1,680 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
#![allow(non_snake_case)]
use crate::api::context::*;
use crate::api::device::*;
use crate::api::event::*;
use crate::api::kernel::*;
use crate::api::memory::*;
use crate::api::platform;
use crate::api::platform::*;
use crate::api::program::*;
use crate::api::queue::*;
use crate::api::semaphore::*;
use crate::api::types::*;
use crate::api::util::*;
use crate::core::platform::*;
use mesa_rust_util::ptr::*;
use rusticl_opencl_gen::*;
use std::ffi::c_char;
use std::ffi::c_void;
use std::ffi::CStr;
use std::mem;
use std::ptr;
use std::sync::Arc;
// somehow bindgen ignores those
#[cfg(target_pointer_width = "32")]
const CL_ICD2_TAG_KHR: isize = 0x434C3331;
#[cfg(target_pointer_width = "64")]
const CL_ICD2_TAG_KHR: isize = 0x4F50454E434C3331;
const fn cl_icd_dispatch_default() -> cl_icd_dispatch {
const ELEMS: usize = size_of::<cl_icd_dispatch>() / size_of::<Option<fn()>>();
// SAFETY: cl_icd_dispatch is a list of function pointers and we set them all to None
unsafe { mem::transmute([None::<fn()>; ELEMS]) }
}
macro_rules! cl_dispatch {
([$($func:ident: Some($val:tt)),*$(,)?]) => {
pub static DISPATCH: cl_icd_dispatch = cl_icd_dispatch {
anon_1: _cl_icd_dispatch__bindgen_ty_1 {
clGetPlatformIDs_icd2_tag: CL_ICD2_TAG_KHR,
},
anon_2: _cl_icd_dispatch__bindgen_ty_2 {
clUnloadCompiler_icd2_tag: CL_ICD2_TAG_KHR,
},
$($func: Some($val)),*,
..cl_icd_dispatch_default()
};
unsafe extern "C" fn clIcdGetFunctionAddressForPlatformKHR(
platform: cl_platform_id,
func_name: *const c_char,
) -> *mut c_void {
// A return value of NULL indicates that [..] platform is not a valid platform for the
// implementation.
if platform.get_ref().is_err() {
return ptr::null_mut();
};
// SAFETY: func_name is a proper UTF-8 encoded nul terminated string.
let Ok(func_name) = unsafe { CStr::from_ptr(func_name) }.to_str() else {
return ptr::null_mut();
};
match func_name {
$(stringify!($func) => $val as _,)*
// A return value of NULL indicates that the specified function does not exist for
// platform.
_ => ptr::null_mut()
}
}
};
}
cl_dispatch!([
clGetPlatformInfo: Some(clGetPlatformInfo),
clGetDeviceIDs: Some(clGetDeviceIDs),
clGetDeviceInfo: Some(clGetDeviceInfo),
clCreateContext: Some(clCreateContext),
clCreateContextFromType: Some(clCreateContextFromType),
clRetainContext: Some(clRetainContext),
clReleaseContext: Some(clReleaseContext),
clGetContextInfo: Some(clGetContextInfo),
clCreateCommandQueue: Some(clCreateCommandQueue),
clRetainCommandQueue: Some(clRetainCommandQueue),
clReleaseCommandQueue: Some(clReleaseCommandQueue),
clGetCommandQueueInfo: Some(clGetCommandQueueInfo),
clSetCommandQueueProperty: Some(clSetCommandQueueProperty),
clCreateBuffer: Some(clCreateBuffer),
clCreateImage2D: Some(clCreateImage2D),
clCreateImage3D: Some(clCreateImage3D),
clRetainMemObject: Some(clRetainMemObject),
clReleaseMemObject: Some(clReleaseMemObject),
clGetSupportedImageFormats: Some(clGetSupportedImageFormats),
clGetMemObjectInfo: Some(clGetMemObjectInfo),
clGetImageInfo: Some(clGetImageInfo),
clCreateSampler: Some(clCreateSampler),
clRetainSampler: Some(clRetainSampler),
clReleaseSampler: Some(clReleaseSampler),
clGetSamplerInfo: Some(clGetSamplerInfo),
clCreateProgramWithSource: Some(clCreateProgramWithSource),
clCreateProgramWithBinary: Some(clCreateProgramWithBinary),
clRetainProgram: Some(clRetainProgram),
clReleaseProgram: Some(clReleaseProgram),
clBuildProgram: Some(clBuildProgram),
clGetProgramInfo: Some(clGetProgramInfo),
clGetProgramBuildInfo: Some(clGetProgramBuildInfo),
clCreateKernel: Some(clCreateKernel),
clCreateKernelsInProgram: Some(clCreateKernelsInProgram),
clRetainKernel: Some(clRetainKernel),
clReleaseKernel: Some(clReleaseKernel),
clSetKernelArg: Some(clSetKernelArg),
clGetKernelInfo: Some(clGetKernelInfo),
clGetKernelWorkGroupInfo: Some(clGetKernelWorkGroupInfo),
clWaitForEvents: Some(clWaitForEvents),
clGetEventInfo: Some(clGetEventInfo),
clRetainEvent: Some(clRetainEvent),
clReleaseEvent: Some(clReleaseEvent),
clGetEventProfilingInfo: Some(clGetEventProfilingInfo),
clFlush: Some(clFlush),
clFinish: Some(clFinish),
clEnqueueReadBuffer: Some(clEnqueueReadBuffer),
clEnqueueWriteBuffer: Some(clEnqueueWriteBuffer),
clEnqueueCopyBuffer: Some(clEnqueueCopyBuffer),
clEnqueueReadImage: Some(clEnqueueReadImage),
clEnqueueWriteImage: Some(clEnqueueWriteImage),
clEnqueueCopyImage: Some(clEnqueueCopyImage),
clEnqueueCopyImageToBuffer: Some(clEnqueueCopyImageToBuffer),
clEnqueueCopyBufferToImage: Some(clEnqueueCopyBufferToImage),
clEnqueueMapBuffer: Some(clEnqueueMapBuffer),
clEnqueueMapImage: Some(clEnqueueMapImage),
clEnqueueUnmapMemObject: Some(clEnqueueUnmapMemObject),
clEnqueueNDRangeKernel: Some(clEnqueueNDRangeKernel),
clEnqueueTask: Some(clEnqueueTask),
// clEnqueueNativeKernel: None,
clEnqueueMarker: Some(clEnqueueMarker),
// clEnqueueWaitForEvents: None,
clEnqueueBarrier: Some(clEnqueueBarrier),
clGetExtensionFunctionAddress: Some(clGetExtensionFunctionAddress),
clCreateFromGLBuffer: Some(clCreateFromGLBuffer),
clCreateFromGLTexture2D: Some(clCreateFromGLTexture2D),
clCreateFromGLTexture3D: Some(clCreateFromGLTexture3D),
clCreateFromGLRenderbuffer: Some(clCreateFromGLRenderbuffer),
clGetGLObjectInfo: Some(clGetGLObjectInfo),
clGetGLTextureInfo: Some(clGetGLTextureInfo),
clEnqueueAcquireGLObjects: Some(clEnqueueAcquireGLObjects),
clEnqueueReleaseGLObjects: Some(clEnqueueReleaseGLObjects),
clGetGLContextInfoKHR: Some(clGetGLContextInfoKHR),
clSetEventCallback: Some(clSetEventCallback),
clCreateSubBuffer: Some(clCreateSubBuffer),
clSetMemObjectDestructorCallback: Some(clSetMemObjectDestructorCallback),
clCreateUserEvent: Some(clCreateUserEvent),
clSetUserEventStatus: Some(clSetUserEventStatus),
clEnqueueReadBufferRect: Some(clEnqueueReadBufferRect),
clEnqueueWriteBufferRect: Some(clEnqueueWriteBufferRect),
clEnqueueCopyBufferRect: Some(clEnqueueCopyBufferRect),
// clCreateSubDevicesEXT: None,
// clRetainDeviceEXT: None,
// clReleaseDeviceEXT: None,
// clCreateEventFromGLsyncKHR: None,
clCreateSubDevices: Some(clCreateSubDevices),
clRetainDevice: Some(clRetainDevice),
clReleaseDevice: Some(clReleaseDevice),
clCreateImage: Some(clCreateImage),
// clCreateProgramWithBuiltInKernels: None,
clCompileProgram: Some(clCompileProgram),
clLinkProgram: Some(clLinkProgram),
clUnloadPlatformCompiler: Some(clUnloadPlatformCompiler),
clGetKernelArgInfo: Some(clGetKernelArgInfo),
clEnqueueFillBuffer: Some(clEnqueueFillBuffer),
clEnqueueFillImage: Some(clEnqueueFillImage),
clEnqueueMigrateMemObjects: Some(clEnqueueMigrateMemObjects),
clEnqueueMarkerWithWaitList: Some(clEnqueueMarkerWithWaitList),
clEnqueueBarrierWithWaitList: Some(clEnqueueBarrierWithWaitList),
clGetExtensionFunctionAddressForPlatform: Some(clGetExtensionFunctionAddressForPlatform),
clCreateFromGLTexture: Some(clCreateFromGLTexture),
// clCreateFromEGLImageKHR: None,
// clEnqueueAcquireEGLObjectsKHR: None,
// clEnqueueReleaseEGLObjectsKHR: None,
// clCreateEventFromEGLSyncKHR: None,
clCreateCommandQueueWithProperties: Some(clCreateCommandQueueWithProperties),
clCreatePipe: Some(clCreatePipe),
clGetPipeInfo: Some(clGetPipeInfo),
clSVMAlloc: Some(clSVMAlloc),
clSVMFree: Some(clSVMFree),
clEnqueueSVMFree: Some(clEnqueueSVMFree),
clEnqueueSVMMemcpy: Some(clEnqueueSVMMemcpy),
clEnqueueSVMMemFill: Some(clEnqueueSVMMemFill),
clEnqueueSVMMap: Some(clEnqueueSVMMap),
clEnqueueSVMUnmap: Some(clEnqueueSVMUnmap),
clCreateSamplerWithProperties: Some(clCreateSamplerWithProperties),
clSetKernelArgSVMPointer: Some(clSetKernelArgSVMPointer),
clSetKernelExecInfo: Some(clSetKernelExecInfo),
clGetKernelSubGroupInfoKHR: Some(clGetKernelSubGroupInfo),
clCloneKernel: Some(clCloneKernel),
clCreateProgramWithIL: Some(clCreateProgramWithIL),
clEnqueueSVMMigrateMem: Some(clEnqueueSVMMigrateMem),
clGetDeviceAndHostTimer: Some(clGetDeviceAndHostTimer),
clGetHostTimer: Some(clGetHostTimer),
clGetKernelSubGroupInfo: Some(clGetKernelSubGroupInfo),
clSetDefaultDeviceCommandQueue: Some(clSetDefaultDeviceCommandQueue),
clSetProgramReleaseCallback: Some(clSetProgramReleaseCallback),
clSetProgramSpecializationConstant: Some(clSetProgramSpecializationConstant),
clCreateBufferWithProperties: Some(clCreateBufferWithProperties),
clCreateImageWithProperties: Some(clCreateImageWithProperties),
clSetContextDestructorCallback: Some(clSetContextDestructorCallback),
clGetKernelSuggestedLocalWorkSize: Some(clGetKernelSuggestedLocalWorkSize),
]);
pub type CLError = cl_int;
pub type CLResult<T> = Result<T, CLError>;
#[derive(Clone, Copy, PartialEq)]
#[repr(u32)]
pub enum RusticlTypes {
// random number
Buffer = 0xec4cf9a9,
Context,
Device,
Event,
Image,
Kernel,
Program,
Queue,
Sampler,
Semaphore,
}
impl RusticlTypes {
pub const fn u32(&self) -> u32 {
*self as u32
}
pub const fn from_u32(val: u32) -> Option<Self> {
let result = match val {
0xec4cf9a9 => Self::Buffer,
0xec4cf9aa => Self::Context,
0xec4cf9ab => Self::Device,
0xec4cf9ac => Self::Event,
0xec4cf9ad => Self::Image,
0xec4cf9ae => Self::Kernel,
0xec4cf9af => Self::Program,
0xec4cf9b0 => Self::Queue,
0xec4cf9b1 => Self::Sampler,
0xec4cf9b2 => Self::Semaphore,
_ => return None,
};
debug_assert!(result.u32() == val);
Some(result)
}
}
#[repr(C)]
pub struct CLObjectBase<const ERR: i32> {
dispatch: &'static cl_icd_dispatch,
pub dispatch_data: usize,
rusticl_type: u32,
}
impl<const ERR: i32> CLObjectBase<ERR> {
pub fn new(t: RusticlTypes) -> Self {
Self {
dispatch: &DISPATCH,
dispatch_data: Platform::get().dispatch_data,
rusticl_type: t.u32(),
}
}
pub fn new_no_dispatch(t: RusticlTypes) -> Self {
Self {
dispatch: &DISPATCH,
dispatch_data: 0,
rusticl_type: t.u32(),
}
}
pub fn check_ptr(ptr: *const Self) -> CLResult<RusticlTypes> {
if ptr.is_null() {
return Err(ERR);
}
unsafe {
if !::std::ptr::eq((*ptr).dispatch, &DISPATCH) {
return Err(ERR);
}
let Some(ty) = RusticlTypes::from_u32((*ptr).rusticl_type) else {
return Err(ERR);
};
Ok(ty)
}
}
pub fn get_type(&self) -> CLResult<RusticlTypes> {
RusticlTypes::from_u32(self.rusticl_type).ok_or(ERR)
}
}
pub trait ReferenceCountedAPIPointer<T, const ERR: i32> {
fn get_ptr(&self) -> CLResult<*const T>;
// TODO: I can't find a trait that would let me say T: pointer so that
// I can do the cast in the main trait implementation. So we need to
// implement that as part of the macro where we know the real type.
fn from_ptr(ptr: *const T) -> Self;
}
pub trait BaseCLObject<'a, const ERR: i32, CL: ReferenceCountedAPIPointer<Self, ERR> + 'a>:
Sized
{
fn ref_from_raw(obj: CL) -> CLResult<&'a Self> {
let obj = obj.get_ptr()?;
// SAFETY: `get_ptr` already checks if it's one of our pointers and not null
Ok(unsafe { &*obj })
}
fn refs_from_arr(objs: *const CL, count: u32) -> CLResult<Vec<&'a Self>>
where
CL: Copy,
{
// CL spec requires validation for obj arrays, both values have to make sense
if objs.is_null() && count > 0 || !objs.is_null() && count == 0 {
return Err(CL_INVALID_VALUE);
}
let mut res = Vec::new();
if objs.is_null() || count == 0 {
return Ok(res);
}
for i in 0..count as usize {
res.push(Self::ref_from_raw(unsafe { *objs.add(i) })?);
}
Ok(res)
}
}
pub trait ArcedCLObject<'a, const ERR: i32, CL: ReferenceCountedAPIPointer<Self, ERR> + 'a>:
Sized + BaseCLObject<'a, ERR, CL>
{
/// Note: this operation increases the internal ref count as `ref_from_raw` is the better option
/// when an Arc is not needed.
fn arc_from_raw(ptr: CL) -> CLResult<Arc<Self>> {
let ptr = ptr.get_ptr()?;
// SAFETY: `get_ptr` already checks if it's one of our pointers.
Ok(unsafe {
Arc::increment_strong_count(ptr);
Arc::from_raw(ptr)
})
}
fn arcs_from_arr(objs: *const CL, count: u32) -> CLResult<Vec<Arc<Self>>>
where
CL: Copy,
{
// CL spec requires validation for obj arrays, both values have to make sense
if objs.is_null() && count > 0 || !objs.is_null() && count == 0 {
return Err(CL_INVALID_VALUE);
}
let mut res = Vec::new();
if objs.is_null() || count == 0 {
return Ok(res);
}
for i in 0..count as usize {
unsafe {
res.push(Self::arc_from_raw(*objs.add(i))?);
}
}
Ok(res)
}
fn refcnt(ptr: CL) -> CLResult<u32> {
let ptr = ptr.get_ptr()?;
// SAFETY: `get_ptr` already checks if it's one of our pointers.
let arc = unsafe { Arc::from_raw(ptr) };
let res = Arc::strong_count(&arc);
// leak the arc again, so we don't reduce the refcount by dropping `arc`
let _ = Arc::into_raw(arc);
Ok(res as u32)
}
fn into_cl(self: Arc<Self>) -> CL {
CL::from_ptr(Arc::into_raw(self))
}
fn release(ptr: CL) -> CLResult<()> {
let ptr = ptr.get_ptr()?;
// SAFETY: `get_ptr` already checks if it's one of our pointers.
unsafe { Arc::decrement_strong_count(ptr) };
Ok(())
}
fn retain(ptr: CL) -> CLResult<()> {
let ptr = ptr.get_ptr()?;
// SAFETY: `get_ptr` already checks if it's one of our pointers.
unsafe { Arc::increment_strong_count(ptr) };
Ok(())
}
}
#[macro_export]
macro_rules! impl_cl_type_trait_base {
(@BASE $cl: ident, $t: ident, [$($types: ident),+], $err: ident, $($field:ident).+) => {
impl $crate::api::icd::ReferenceCountedAPIPointer<$t, $err> for $cl {
fn get_ptr(&self) -> $crate::api::icd::CLResult<*const $t> {
type Base = $crate::api::icd::CLObjectBase<$err>;
let t = Base::check_ptr(self.cast())?;
if ![$($crate::api::icd::RusticlTypes::$types),+].contains(&t) {
return Err($err);
}
let offset = ::std::mem::offset_of!($t, $($field).+);
// SAFETY: We offset the pointer back from the ICD specified base type to our
// internal type.
let obj_ptr: *const $t = unsafe { self.byte_sub(offset) }.cast();
// Check at compile-time that we indeed got the right path
unsafe { let _: &Base = &(*obj_ptr).$($field).+; }
Ok(obj_ptr)
}
fn from_ptr(ptr: *const $t) -> Self {
if ptr.is_null() {
return std::ptr::null_mut();
}
let offset = ::std::mem::offset_of!($t, $($field).+);
// SAFETY: The resulting pointer is safe as we simply offset into the ICD specified
// base type.
unsafe { ptr.byte_add(offset) as Self }
}
}
impl $crate::api::icd::BaseCLObject<'_, $err, $cl> for $t {}
impl $t {
fn _ensure_send_sync(&self) -> impl Send + Sync + '_ {
self
}
}
// there are two reason to implement those traits for all objects
// 1. it speeds up operations
// 2. we want to check for real equality more explicit to stay conformant with the API
// and to not break in subtle ways e.g. using CL objects as keys in HashMaps.
impl std::cmp::Eq for $t {}
impl std::cmp::PartialEq for $t {
fn eq(&self, other: &Self) -> bool {
std::ptr::addr_eq(self, other)
}
}
impl std::hash::Hash for $t {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::ptr::from_ref(self).hash(state);
}
}
};
($cl: ident, $t: ident, [$($types: ident),+], $err: ident, $($field:ident).+) => {
$crate::impl_cl_type_trait_base!(@BASE $cl, $t, [$($types),+], $err, $($field).+);
};
($cl: ident, $t: ident, [$($types: ident),+], $err: ident) => {
$crate::impl_cl_type_trait_base!($cl, $t, [$($types),+], $err, base);
};
}
#[macro_export]
macro_rules! impl_cl_type_trait {
($cl: ident, $t: ident, $err: ident, $($field:ident).+) => {
$crate::impl_cl_type_trait_base!(@BASE $cl, $t, [$t], $err, $($field).+);
impl $crate::api::icd::ArcedCLObject<'_, $err, $cl> for $t {}
};
($cl: ident, $t: ident, $err: ident) => {
$crate::impl_cl_type_trait!($cl, $t, $err, base);
};
}
// SAFETY: The OpenCL spec demands this function to be exported with its plain C name
#[unsafe(no_mangle)]
pub unsafe extern "C" fn clGetPlatformInfo(
platform: cl_platform_id,
param_name: cl_platform_info,
param_value_size: usize,
param_value: *mut ::std::ffi::c_void,
param_value_size_ret: *mut usize,
) -> cl_int {
unsafe {
platform::clGetPlatformInfo(
platform,
param_name,
param_value_size,
param_value,
param_value_size_ret,
)
}
}
// SAFETY: The OpenCL spec demands this function to be exported with its plain C name
#[unsafe(no_mangle)]
pub unsafe extern "C" fn clIcdGetPlatformIDsKHR(
num_entries: cl_uint,
platforms: *mut cl_platform_id,
num_platforms: *mut cl_uint,
) -> cl_int {
unsafe { clGetPlatformIDs(num_entries, platforms, num_platforms) }
}
macro_rules! cl_ext_func {
($func:ident: $api_type:ident) => {{
let _func: $api_type = Some($func);
$func as *mut ::std::ffi::c_void
}};
}
#[rustfmt::skip]
// SAFETY: The OpenCL spec demands this function to be exported with its plain C name
#[unsafe(no_mangle)]
pub extern "C" fn clGetExtensionFunctionAddress(
function_name: *const c_char,
) -> *mut c_void {
if function_name.is_null() {
return ptr::null_mut();
}
match unsafe { CStr::from_ptr(function_name) }.to_str().unwrap() {
// cl_khr_create_command_queue
"clCreateCommandQueueWithPropertiesKHR" => cl_ext_func!(clCreateCommandQueueWithProperties: clCreateCommandQueueWithPropertiesKHR_fn),
// cl_khr_external_memory
"clEnqueueAcquireExternalMemObjectsKHR" => cl_ext_func!(clEnqueueAcquireExternalMemObjectsKHR: clEnqueueAcquireExternalMemObjectsKHR_fn),
"clEnqueueReleaseExternalMemObjectsKHR" => cl_ext_func!(clEnqueueReleaseExternalMemObjectsKHR: clEnqueueReleaseExternalMemObjectsKHR_fn),
// cl_khr_external_semaphore
"clGetSemaphoreHandleForTypeKHR" => cl_ext_func!(clGetSemaphoreHandleForTypeKHR: clGetSemaphoreHandleForTypeKHR_fn),
// cl_khr_external_semaphore_sync_fd
"clReImportSemaphoreSyncFdKHR" => cl_ext_func!(clReImportSemaphoreSyncFdKHR: clReImportSemaphoreSyncFdKHR_fn),
// cl_khr_gl_sharing
"clCreateFromGLBuffer" => cl_ext_func!(clCreateFromGLBuffer: clCreateFromGLBuffer_fn),
"clCreateFromGLRenderbuffer" => cl_ext_func!(clCreateFromGLRenderbuffer: clCreateFromGLRenderbuffer_fn),
"clCreateFromGLTexture" => cl_ext_func!(clCreateFromGLTexture: clCreateFromGLTexture_fn),
"clCreateFromGLTexture2D" => cl_ext_func!(clCreateFromGLTexture2D: clCreateFromGLTexture2D_fn),
"clCreateFromGLTexture3D" => cl_ext_func!(clCreateFromGLTexture3D: clCreateFromGLTexture3D_fn),
"clEnqueueAcquireGLObjects" => cl_ext_func!(clEnqueueAcquireGLObjects: clEnqueueAcquireGLObjects_fn),
"clEnqueueReleaseGLObjects" => cl_ext_func!(clEnqueueReleaseGLObjects: clEnqueueReleaseGLObjects_fn),
"clGetGLContextInfoKHR" => cl_ext_func!(clGetGLContextInfoKHR: clGetGLContextInfoKHR_fn),
"clGetGLObjectInfo" => cl_ext_func!(clGetGLObjectInfo: clGetGLObjectInfo_fn),
"clGetGLTextureInfo" => cl_ext_func!(clGetGLTextureInfo: clGetGLTextureInfo_fn),
// cl_khr_icd
"clGetPlatformInfo" => cl_ext_func!(clGetPlatformInfo: clGetPlatformInfo_fn),
"clIcdGetPlatformIDsKHR" => cl_ext_func!(clIcdGetPlatformIDsKHR: clIcdGetPlatformIDsKHR_fn),
"clIcdGetFunctionAddressForPlatformKHR" => cl_ext_func!(clIcdGetFunctionAddressForPlatformKHR: clIcdGetFunctionAddressForPlatformKHR_fn),
"clIcdSetPlatformDispatchDataKHR" => cl_ext_func!(clIcdSetPlatformDispatchDataKHR: clIcdSetPlatformDispatchDataKHR_fn),
// cl_khr_il_program
"clCreateProgramWithILKHR" => cl_ext_func!(clCreateProgramWithIL: clCreateProgramWithILKHR_fn),
// cl_khr_semaphore
"clCreateSemaphoreWithPropertiesKHR" => cl_ext_func!(clCreateSemaphoreWithPropertiesKHR: clCreateSemaphoreWithPropertiesKHR_fn),
"clEnqueueSignalSemaphoresKHR" => cl_ext_func!(clEnqueueSignalSemaphoresKHR: clEnqueueSignalSemaphoresKHR_fn),
"clEnqueueWaitSemaphoresKHR" => cl_ext_func!(clEnqueueWaitSemaphoresKHR: clEnqueueWaitSemaphoresKHR_fn),
"clGetSemaphoreInfoKHR" => cl_ext_func!(clGetSemaphoreInfoKHR: clGetSemaphoreInfoKHR_fn),
"clReleaseSemaphoreKHR" => cl_ext_func!(clReleaseSemaphoreKHR: clReleaseSemaphoreKHR_fn),
"clRetainSemaphoreKHR" => cl_ext_func!(clRetainSemaphoreKHR: clRetainSemaphoreKHR_t),
// cl_khr_suggested_local_work_size
"clGetKernelSuggestedLocalWorkSizeKHR" => cl_ext_func!(clGetKernelSuggestedLocalWorkSize: clGetKernelSuggestedLocalWorkSizeKHR_fn),
// cl_ext_buffer_device_address
"clSetKernelArgDevicePointerEXT" => cl_ext_func!(clSetKernelArgDevicePointerEXT: clSetKernelArgDevicePointerEXT_fn),
// cl_arm_shared_virtual_memory
"clEnqueueSVMFreeARM" => cl_ext_func!(clEnqueueSVMFreeARM: clEnqueueSVMFreeARM_fn),
"clEnqueueSVMMapARM" => cl_ext_func!(clEnqueueSVMMapARM: clEnqueueSVMMapARM_fn),
"clEnqueueSVMMemcpyARM" => cl_ext_func!(clEnqueueSVMMemcpyARM: clEnqueueSVMMemcpyARM_fn),
"clEnqueueSVMMemFillARM" => cl_ext_func!(clEnqueueSVMMemFillARM: clEnqueueSVMMemFillARM_fn),
"clEnqueueSVMUnmapARM" => cl_ext_func!(clEnqueueSVMUnmapARM: clEnqueueSVMUnmapARM_fn),
"clSetKernelArgSVMPointerARM" => cl_ext_func!(clSetKernelArgSVMPointer: clSetKernelArgSVMPointerARM_fn),
"clSetKernelExecInfoARM" => cl_ext_func!(clSetKernelExecInfo: clSetKernelExecInfoARM_fn),
"clSVMAllocARM" => cl_ext_func!(clSVMAlloc: clSVMAllocARM_fn),
"clSVMFreeARM" => cl_ext_func!(clSVMFree: clSVMFreeARM_fn),
// DPCPP bug https://github.com/intel/llvm/issues/9964
"clSetProgramSpecializationConstant" => cl_ext_func!(clSetProgramSpecializationConstant: clSetProgramSpecializationConstant_fn),
_ => ptr::null_mut(),
}
}
extern "C" fn clLinkProgram(
context: cl_context,
num_devices: cl_uint,
device_list: *const cl_device_id,
options: *const ::std::os::raw::c_char,
num_input_programs: cl_uint,
input_programs: *const cl_program,
pfn_notify: Option<FuncProgramCB>,
user_data: *mut ::std::os::raw::c_void,
errcode_ret: *mut cl_int,
) -> cl_program {
let (ptr, err) = match link_program(
context,
num_devices,
device_list,
options,
num_input_programs,
input_programs,
pfn_notify,
user_data,
) {
Ok((prog, code)) => (prog, code),
Err(e) => (ptr::null_mut(), e),
};
// Correct behavior when `errcode_ret` is null is unspecified, but by
// analogy, we fail silently in that case.
// SAFETY: Caller is responsible for providing a pointer valid for a write
// of `size_of::<cl_int>()`.
unsafe { errcode_ret.write_checked(err) };
ptr
}
extern "C" fn clGetExtensionFunctionAddressForPlatform(
_platform: cl_platform_id,
function_name: *const ::std::os::raw::c_char,
) -> *mut ::std::os::raw::c_void {
clGetExtensionFunctionAddress(function_name)
}
extern "C" fn clSVMAlloc(
context: cl_context,
flags: cl_svm_mem_flags,
size: usize,
alignment: ::std::os::raw::c_uint,
) -> *mut ::std::os::raw::c_void {
svm_alloc(context, flags, size, alignment).unwrap_or(ptr::null_mut())
}
extern "C" fn clSVMFree(context: cl_context, svm_pointer: *mut ::std::os::raw::c_void) {
svm_free(context, svm_pointer as usize).ok();
}
unsafe extern "C" fn clGetKernelSubGroupInfo(
kernel: cl_kernel,
device: cl_device_id,
param_name: cl_kernel_sub_group_info,
input_value_size: usize,
input_value: *const ::std::os::raw::c_void,
param_value_size: usize,
param_value: *mut ::std::os::raw::c_void,
param_value_size_ret: *mut usize,
) -> cl_int {
match unsafe {
kernel.get_info_obj(
(device, input_value_size, input_value, param_value_size),
param_name,
param_value_size,
param_value,
param_value_size_ret,
)
} {
Ok(_) => CL_SUCCESS as cl_int,
Err(e) => e,
}
}
+956
View File
@@ -0,0 +1,956 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
use crate::api::event::create_and_queue;
use crate::api::icd::*;
use crate::api::util::*;
use crate::core::device::*;
use crate::core::event::*;
use crate::core::kernel::*;
use crate::core::memory::*;
use crate::core::program::*;
use crate::core::queue::*;
use mesa_rust_util::ptr::*;
use rusticl_opencl_gen::*;
use rusticl_proc_macros::cl_entrypoint;
use rusticl_proc_macros::cl_info_entrypoint;
use std::cmp;
use std::ffi::CStr;
use std::os::raw::c_void;
use std::ptr;
use std::slice;
use std::sync::Arc;
#[cl_info_entrypoint(clGetKernelInfo)]
unsafe impl CLInfo<cl_kernel_info> for cl_kernel {
fn query(&self, q: cl_kernel_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
let kernel = Kernel::ref_from_raw(*self)?;
match q {
CL_KERNEL_ATTRIBUTES => v.write::<&str>(&kernel.kernel_info.attributes_string),
CL_KERNEL_CONTEXT => {
let ptr = Arc::as_ptr(&kernel.prog.context);
v.write::<cl_context>(cl_context::from_ptr(ptr))
}
CL_KERNEL_FUNCTION_NAME => v.write::<&CStr>(&kernel.name),
CL_KERNEL_NUM_ARGS => v.write::<cl_uint>(kernel.kernel_info.args.len() as cl_uint),
CL_KERNEL_PROGRAM => {
let ptr = Arc::as_ptr(&kernel.prog);
v.write::<cl_program>(cl_program::from_ptr(ptr))
}
CL_KERNEL_REFERENCE_COUNT => v.write::<cl_uint>(Kernel::refcnt(*self)?),
// CL_INVALID_VALUE if param_name is not one of the supported values
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_info_entrypoint(clGetKernelArgInfo)]
unsafe impl CLInfoObj<cl_kernel_arg_info, cl_uint> for cl_kernel {
fn query(&self, idx: cl_uint, q: cl_kernel_arg_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
let kernel = Kernel::ref_from_raw(*self)?;
let idx = idx as usize;
// CL_INVALID_ARG_INDEX if arg_index is not a valid argument index.
if idx >= kernel.kernel_info.args.len() {
return Err(CL_INVALID_ARG_INDEX);
}
match *q {
CL_KERNEL_ARG_ACCESS_QUALIFIER => {
v.write::<cl_kernel_arg_access_qualifier>(kernel.access_qualifier(idx))
}
CL_KERNEL_ARG_ADDRESS_QUALIFIER => {
v.write::<cl_kernel_arg_address_qualifier>(kernel.address_qualifier(idx))
}
CL_KERNEL_ARG_NAME => v.write::<&CStr>(
kernel
.arg_name(idx)
.ok_or(CL_KERNEL_ARG_INFO_NOT_AVAILABLE)?,
),
CL_KERNEL_ARG_TYPE_NAME => v.write::<&CStr>(
kernel
.arg_type_name(idx)
.ok_or(CL_KERNEL_ARG_INFO_NOT_AVAILABLE)?,
),
CL_KERNEL_ARG_TYPE_QUALIFIER => {
v.write::<cl_kernel_arg_type_qualifier>(kernel.type_qualifier(idx))
}
// CL_INVALID_VALUE if param_name is not one of the supported values
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_info_entrypoint(clGetKernelWorkGroupInfo)]
unsafe impl CLInfoObj<cl_kernel_work_group_info, cl_device_id> for cl_kernel {
fn query(
&self,
dev: cl_device_id,
q: cl_kernel_work_group_info,
v: CLInfoValue,
) -> CLResult<CLInfoRes> {
let kernel = Kernel::ref_from_raw(*self)?;
// CL_INVALID_DEVICE [..] if device is NULL but there is more than one device associated with kernel.
let dev = if dev.is_null() {
if kernel.prog.devs.len() > 1 {
return Err(CL_INVALID_DEVICE);
} else {
kernel.prog.devs[0]
}
} else {
let dev = Device::ref_from_raw(dev)?;
// CL_INVALID_DEVICE if device is not in the list of devices associated with kernel
if !kernel.prog.devs.contains(&dev) {
return Err(CL_INVALID_DEVICE);
}
dev
};
match *q {
CL_KERNEL_COMPILE_WORK_GROUP_SIZE => v.write::<[usize; 3]>(kernel.work_group_size()),
CL_KERNEL_GLOBAL_WORK_SIZE => {
// If device is not a custom device and kernel is not a built-in kernel
// clGetKernelWorkGroupInfo returns the error CL_INVALID_VALUE.
if dev.device_type & CL_DEVICE_TYPE_CUSTOM == 0 {
return Err(CL_INVALID_VALUE);
}
// We don't have any enqueue limits.
v.write::<[usize; 3]>([usize::MAX; 3])
}
CL_KERNEL_LOCAL_MEM_SIZE => v.write::<cl_ulong>(kernel.local_mem_size(dev)),
CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE => {
v.write::<usize>(kernel.preferred_simd_size(dev))
}
CL_KERNEL_PRIVATE_MEM_SIZE => v.write::<cl_ulong>(kernel.priv_mem_size(dev)),
CL_KERNEL_SPILL_MEM_SIZE_INTEL => v.write::<cl_ulong>(kernel.priv_mem_size(dev)),
CL_KERNEL_WORK_GROUP_SIZE => v.write::<usize>(kernel.max_threads_per_block(dev)),
// CL_INVALID_VALUE if param_name is not one of the supported values
_ => Err(CL_INVALID_VALUE),
}
}
}
unsafe impl CLInfoObj<cl_kernel_sub_group_info, (cl_device_id, usize, *const c_void, usize)>
for cl_kernel
{
fn query(
&self,
(dev, input_value_size, input_value, output_value_size): (
cl_device_id,
usize,
*const c_void,
usize,
),
q: cl_program_build_info,
v: CLInfoValue,
) -> CLResult<CLInfoRes> {
let kernel = Kernel::ref_from_raw(*self)?;
// CL_INVALID_DEVICE [..] if device is NULL but there is more than one device associated
// with kernel.
let dev = if dev.is_null() {
if kernel.prog.devs.len() > 1 {
return Err(CL_INVALID_DEVICE);
} else {
kernel.prog.devs[0]
}
} else {
Device::ref_from_raw(dev)?
};
// CL_INVALID_DEVICE if device is not in the list of devices associated with kernel
if !kernel.prog.devs.contains(&dev) {
return Err(CL_INVALID_DEVICE);
}
// CL_INVALID_OPERATION if device does not support subgroups.
if !dev.subgroups_supported() {
return Err(CL_INVALID_OPERATION);
}
let usize_byte = size_of::<usize>();
// first we have to convert the input to a proper thing
let input: &[usize] = match q {
CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE | CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE => {
// CL_INVALID_VALUE if param_name is CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE,
// CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE or ... and the size in bytes specified by
// input_value_size is not valid or if input_value is NULL.
if ![usize_byte, 2 * usize_byte, 3 * usize_byte].contains(&input_value_size) {
return Err(CL_INVALID_VALUE);
}
// SAFETY: we verified the size as best as possible, with the rest we trust the client
unsafe { slice::from_raw_parts(input_value.cast(), input_value_size / usize_byte) }
}
CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT => {
// CL_INVALID_VALUE if param_name is ... CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT
// and the size in bytes specified by input_value_size is not valid or if
// input_value is NULL.
if input_value_size != usize_byte || input_value.is_null() {
return Err(CL_INVALID_VALUE);
}
// SAFETY: we trust the client here
unsafe { slice::from_raw_parts(input_value.cast(), 1) }
}
_ => &[],
};
match q {
CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE => {
v.write::<usize>(kernel.subgroups_for_block(dev, input))
}
CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE => {
v.write::<usize>(kernel.subgroup_size_for_block(dev, input))
}
CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT => {
let subgroups = input[0];
let mut res = [0; 3];
for subgroup_size in kernel.subgroup_sizes(dev) {
let threads = subgroups * subgroup_size;
if threads > dev.max_threads_per_block() {
continue;
}
let block = [threads, 1, 1];
let real_subgroups = kernel.subgroups_for_block(dev, &block);
if real_subgroups == subgroups {
res = block;
break;
}
}
v.write_iter::<usize>(res.into_iter().take(output_value_size / usize_byte))
}
CL_KERNEL_MAX_NUM_SUB_GROUPS => {
let threads = kernel.max_threads_per_block(dev);
let max_groups = dev.max_subgroups();
let mut result = 0;
for sgs in kernel.subgroup_sizes(dev) {
result = cmp::max(result, threads / sgs);
result = cmp::min(result, max_groups as usize);
}
v.write::<usize>(result)
}
CL_KERNEL_COMPILE_NUM_SUB_GROUPS => v.write::<usize>(kernel.num_subgroups()),
CL_KERNEL_COMPILE_SUB_GROUP_SIZE_INTEL => v.write::<usize>(kernel.subgroup_size()),
// CL_INVALID_VALUE if param_name is not one of the supported values
_ => Err(CL_INVALID_VALUE),
}
}
}
const ZERO_ARR: [usize; 3] = [0; 3];
/// # Safety
///
/// This function is only safe when called on an array of `work_dim` length
unsafe fn kernel_work_arr_or_default<'a>(arr: *const usize, work_dim: cl_uint) -> &'a [usize] {
if !arr.is_null() {
unsafe { slice::from_raw_parts(arr, work_dim as usize) }
} else {
&ZERO_ARR
}
}
/// # Safety
///
/// This function is only safe when called on an array of `work_dim` length
unsafe fn kernel_work_arr_mut<'a>(arr: *mut usize, work_dim: cl_uint) -> Option<&'a mut [usize]> {
if !arr.is_null() {
unsafe { Some(slice::from_raw_parts_mut(arr, work_dim as usize)) }
} else {
None
}
}
#[cl_entrypoint(clCreateKernel)]
fn create_kernel(
program: cl_program,
kernel_name: *const ::std::os::raw::c_char,
) -> CLResult<cl_kernel> {
let p = Program::arc_from_raw(program)?;
// CL_INVALID_VALUE if kernel_name is NULL.
if kernel_name.is_null() {
return Err(CL_INVALID_VALUE);
}
// SAFETY: kernel_name is pointing to a valid C string.
let name = unsafe { CStr::from_ptr(kernel_name) }.to_owned();
let build = p.build_info();
// CL_INVALID_PROGRAM_EXECUTABLE if there is no successfully built executable for program.
if build.kernels().is_empty() {
return Err(CL_INVALID_PROGRAM_EXECUTABLE);
}
// CL_INVALID_KERNEL_NAME if kernel_name is not found in program.
if !build.kernels().contains(&name) {
return Err(CL_INVALID_KERNEL_NAME);
}
// CL_INVALID_KERNEL_DEFINITION if the function definition for __kernel function given by
// kernel_name such as the number of arguments, the argument types are not the same for all
// devices for which the program executable has been built.
if !p.has_unique_kernel_signatures(&name) {
return Err(CL_INVALID_KERNEL_DEFINITION);
}
Ok(Kernel::new(name, Arc::clone(&p), &build).into_cl())
}
#[cl_entrypoint(clRetainKernel)]
fn retain_kernel(kernel: cl_kernel) -> CLResult<()> {
Kernel::retain(kernel)
}
#[cl_entrypoint(clReleaseKernel)]
fn release_kernel(kernel: cl_kernel) -> CLResult<()> {
Kernel::release(kernel)
}
#[cl_entrypoint(clCreateKernelsInProgram)]
fn create_kernels_in_program(
program: cl_program,
num_kernels: cl_uint,
kernels: *mut cl_kernel,
num_kernels_ret: *mut cl_uint,
) -> CLResult<()> {
let p = Program::arc_from_raw(program)?;
let build = p.build_info();
// CL_INVALID_PROGRAM_EXECUTABLE if there is no successfully built executable for any device in
// program.
if build.kernels().is_empty() {
return Err(CL_INVALID_PROGRAM_EXECUTABLE);
}
// CL_INVALID_VALUE if kernels is not NULL and num_kernels is less than the number of kernels
// in program.
if !kernels.is_null() && build.kernels().len() > num_kernels as usize {
return Err(CL_INVALID_VALUE);
}
let mut num_kernels = 0;
for name in build.kernels() {
// Kernel objects are not created for any __kernel functions in program that do not have the
// same function definition across all devices for which a program executable has been
// successfully built.
if !p.has_unique_kernel_signatures(name) {
continue;
}
if !kernels.is_null() {
// we just assume the client isn't stupid
unsafe {
kernels
.add(num_kernels as usize)
.write(Kernel::new(name.clone(), Arc::clone(&p), &build).into_cl());
}
}
num_kernels += 1;
}
// SAFETY: Caller is responsible for providing a pointer valid for a write
// of `size_of::<cl_uint>()`.
unsafe { num_kernels_ret.write_checked(num_kernels) };
Ok(())
}
#[cl_entrypoint(clSetKernelArg)]
fn set_kernel_arg(
kernel: cl_kernel,
arg_index: cl_uint,
arg_size: usize,
arg_value: *const ::std::os::raw::c_void,
) -> CLResult<()> {
// SAFETY: Per spec, "OpenCL calls that modify the state of `cl_kernel`
// objects are not thread-safe: clSetKernelArg, ...", so no other threads
// must hold a reference to this `cl_kernel`. Additionally, we may require
// callers to pass either null or a valid CL object, satisfying validity
// constraints.
let k = unsafe { Kernel::mut_ref_from_raw(kernel) }?;
let arg_index = arg_index as usize;
// CL_INVALID_ARG_INDEX if arg_index is not a valid argument index.
if let Some(arg) = k.kernel_info.args.get(arg_index) {
// CL_INVALID_ARG_SIZE if arg_size does not match the size of the data type for an argument
// that is not a memory object or if the argument is a memory object and
// arg_size != sizeof(cl_mem) or if arg_size is zero and the argument is declared with the
// local qualifier or if the argument is a sampler and arg_size != sizeof(cl_sampler).
match arg.kind {
KernelArgType::MemLocal => {}
KernelArgType::MemGlobal
| KernelArgType::MemConstant
| KernelArgType::Image
| KernelArgType::RWImage
| KernelArgType::Texture => {
if arg_size != size_of::<cl_mem>() {
return Err(CL_INVALID_ARG_SIZE);
}
}
KernelArgType::Sampler => {
if arg_size != size_of::<cl_sampler>() {
return Err(CL_INVALID_ARG_SIZE);
}
}
KernelArgType::Constant(size) => {
if size as usize != arg_size {
return Err(CL_INVALID_ARG_SIZE);
}
}
}
// CL_INVALID_ARG_VALUE if arg_value specified is not a valid value.
match arg.kind {
// If the argument is declared with the local qualifier, the arg_value entry must be
// NULL.
KernelArgType::MemLocal => {
if !arg_value.is_null() {
return Err(CL_INVALID_ARG_VALUE);
}
}
KernelArgType::Constant(_) => {
if arg_value.is_null() {
return Err(CL_INVALID_ARG_VALUE);
}
}
KernelArgType::Sampler => {
// CL_INVALID_SAMPLER for an argument declared to be of type sampler_t when the
// specified arg_value is not a valid sampler object.
if arg_value.is_null() {
return Err(CL_INVALID_SAMPLER);
}
}
_ => {}
};
// let's create the arg now
let arg = match arg.kind {
KernelArgType::Constant(_) if !arg.dead => KernelArgValue::Constant(
// SAFETY: for constant args arg_value points to raw data.
unsafe { slice::from_raw_parts(arg_value.cast(), arg_size) }.to_vec(),
),
KernelArgType::MemConstant | KernelArgType::MemGlobal => {
let ptr: *const cl_mem = arg_value.cast();
// SAFETY: for buffer args arg_value points to a cl_mem object or NULL.
if ptr.is_null() || unsafe { *ptr }.is_null() {
KernelArgValue::None
} else {
// SAFETY: as above
let buffer = Buffer::arc_from_raw(unsafe { *ptr })?;
// We are required to prevent mutable access to immutable memory objects,
// however no explicit error code has been specified yet.
if arg.kind == KernelArgType::MemGlobal
&& bit_check(buffer.flags, CL_MEM_IMMUTABLE_EXT)
{
return Err(CL_INVALID_ARG_VALUE);
}
KernelArgValue::Buffer(Arc::downgrade(&buffer))
}
}
KernelArgType::MemLocal if !arg.dead => KernelArgValue::LocalMem(arg_size),
KernelArgType::Image | KernelArgType::RWImage | KernelArgType::Texture if !arg.dead => {
let img: *const cl_mem = arg_value.cast();
// SAFETY: for image args arg_value pointers to a cl_mem object.
let img = Image::arc_from_raw(unsafe { *img })?;
// CL_INVALID_ARG_VALUE if the argument is an image declared with the read_only
// qualifier and arg_value refers to an image object created with cl_mem_flags
// of CL_MEM_WRITE_ONLY or if the image argument is declared with the write_only
// qualifier and arg_value refers to an image object created with cl_mem_flags
// of CL_MEM_READ_ONLY.
// We are required to prevent mutable access to immutable memory objects, however no
// explicit error code has been specified yet.
if arg.kind == KernelArgType::Texture && bit_check(img.flags, CL_MEM_WRITE_ONLY)
|| arg.kind == KernelArgType::Image
&& bit_check(img.flags, CL_MEM_READ_ONLY | CL_MEM_IMMUTABLE_EXT)
|| arg.kind == KernelArgType::RWImage
&& bit_check(img.flags, CL_MEM_IMMUTABLE_EXT)
{
return Err(CL_INVALID_ARG_VALUE);
}
KernelArgValue::Image(Arc::downgrade(&img))
}
KernelArgType::Sampler if !arg.dead => {
let ptr: *const cl_sampler = arg_value.cast();
// SAFETY: for a sampler arg arg_value points to a cl_sampler object.
KernelArgValue::Sampler(Sampler::arc_from_raw(unsafe { *ptr })?)
}
_ => {
debug_assert!(
arg.dead
|| matches!(
arg.kind,
KernelArgType::MemConstant | KernelArgType::MemGlobal
)
);
KernelArgValue::None
}
};
k.set_kernel_arg(arg_index, arg)
} else {
Err(CL_INVALID_ARG_INDEX)
}
//• CL_INVALID_DEVICE_QUEUE for an argument declared to be of type queue_t when the specified arg_value is not a valid device queue object. This error code is missing before version 2.0.
//• CL_MAX_SIZE_RESTRICTION_EXCEEDED if the size in bytes of the memory object (if the argument is a memory object) or arg_size (if the argument is declared with local qualifier) exceeds a language- specified maximum size restriction for this argument, such as the MaxByteOffset SPIR-V decoration. This error code is missing before version 2.2.
}
#[cl_entrypoint(clSetKernelArgSVMPointer)]
fn set_kernel_arg_svm_pointer(
kernel: cl_kernel,
arg_index: cl_uint,
arg_value: *const ::std::os::raw::c_void,
) -> CLResult<()> {
// SAFETY: Per spec, "OpenCL calls that modify the state of `cl_kernel`
// objects are not thread-safe: ..., clSetKernelArgSVMPointer, ...", so no
// other threads must hold a reference to this `cl_kernel`. Additionally, we
// may require callers to pass either null or a valid CL object, satisfying
// validity constraints.
let kernel = unsafe { Kernel::mut_ref_from_raw(kernel) }?;
let arg_index = arg_index as usize;
let arg_value = arg_value as usize;
if !kernel.has_svm_devs() {
return Err(CL_INVALID_OPERATION);
}
if let Some(arg) = kernel.kernel_info.args.get(arg_index) {
if !matches!(
arg.kind,
KernelArgType::MemConstant | KernelArgType::MemGlobal
) {
return Err(CL_INVALID_ARG_INDEX);
}
let arg_value = KernelArgValue::SVM(arg_value);
kernel.set_kernel_arg(arg_index, arg_value)
} else {
Err(CL_INVALID_ARG_INDEX)
}
// CL_INVALID_ARG_VALUE if arg_value specified is not a valid value.
}
#[cl_entrypoint(clSetKernelArgDevicePointerEXT)]
fn set_kernel_arg_device_pointer(
kernel: cl_kernel,
arg_index: cl_uint,
arg_value: cl_mem_device_address_ext,
) -> CLResult<()> {
// SAFETY: Per spec, "OpenCL calls that modify the state of `cl_kernel`
// objects are not thread-safe...", so no other threads must hold a
// reference to this `cl_kernel`. Additionally, we may require callers to
// pass either null or a valid CL object, satisfying validity constraints.
let kernel = unsafe { Kernel::mut_ref_from_raw(kernel) }?;
let arg_index = arg_index as usize;
let devs = &kernel.prog.context.devs;
// CL_INVALID_OPERATION if no devices in the context associated with kernel support the device
// pointer.
if devs.iter().any(|dev| !dev.bda_supported()) {
return Err(CL_INVALID_OPERATION);
}
// CL_INVALID_ARG_INDEX if arg_index is not a valid argument index.
let Some(arg) = kernel.kernel_info.args.get(arg_index) else {
return Err(CL_INVALID_ARG_INDEX);
};
// The device pointer can only be used for arguments that are declared to be a pointer to global
// memory allocated with clCreateBufferWithProperties with the CL_MEM_DEVICE_PRIVATE_ADDRESS_EXT
// property.
if arg.kind != KernelArgType::MemGlobal {
return Err(CL_INVALID_ARG_INDEX);
}
// we set the arg also when it's a dead argument, as we need to ensure the buffer gets migrated.
kernel.set_kernel_arg(arg_index, KernelArgValue::BDA(arg_value))
}
#[cl_entrypoint(clSetKernelExecInfo)]
fn set_kernel_exec_info(
kernel: cl_kernel,
param_name: cl_kernel_exec_info,
param_value_size: usize,
param_value: *const ::std::os::raw::c_void,
) -> CLResult<()> {
// SAFETY: Per spec, "OpenCL calls that modify the state of `cl_kernel`
// objects are not thread-safe: ..., clSetKernelExecInfo, ...", so no other
// threads must hold a reference to this `cl_kernel`. Additionally, we may
// require callers to pass either null or a valid CL object, satisfying
// validity constraints.
let k = unsafe { Kernel::mut_ref_from_raw(kernel) }?;
let devs = &k.prog.devs;
// CL_INVALID_OPERATION if param_name is CL_KERNEL_EXEC_INFO_DEVICE_PTRS_EXT and no devices in
// the context associated with kernel support the cl_ext_buffer_device_address extension.
let check_bda_support = || {
if devs.iter().all(|dev| !dev.bda_supported()) {
Err(CL_INVALID_OPERATION)
} else {
Ok(())
}
};
// CL_INVALID_OPERATION if param_name is CL_KERNEL_EXEC_INFO_SVM_PTRS and no devices in the
// context associated with kernel support SVM.
let check_svm_support = || {
if devs.iter().all(|dev| !dev.api_svm_supported()) {
Err(CL_INVALID_OPERATION)
} else {
Ok(())
}
};
// CL_INVALID_VALUE ... if the size specified by param_value_size is not valid.
match param_name {
CL_KERNEL_EXEC_INFO_DEVICE_PTRS_EXT => {
check_bda_support()?;
let handles = unsafe {
cl_slice::from_raw_parts_bytes_len::<cl_mem_device_address_ext>(
param_value,
param_value_size,
)?
};
handles.clone_into(&mut k.bdas);
}
CL_KERNEL_EXEC_INFO_SVM_PTRS | CL_KERNEL_EXEC_INFO_SVM_PTRS_ARM => {
check_svm_support()?;
// To specify that no SVM allocations will be accessed by a kernel other than those set
// as kernel arguments, specify an empty set by passing param_value_size equal to zero
// and param_value equal to NULL.
if param_value_size == 0 && param_value.is_null() {
k.svms.clear();
} else {
let pointers = unsafe {
cl_slice::from_raw_parts_bytes_len::<*const c_void>(
param_value,
param_value_size,
)?
};
// We need to clear _after_ the error checking above. We could just assign a new
// container, however we also want to reuse the allocations.
k.svms.clear();
pointers
.iter()
// Each of the pointers can be the pointer returned by clSVMAlloc or can be a
// pointer to the middle of an SVM allocation. It is sufficient to specify one
// pointer for each SVM allocation.
//
// So we'll simply fetch the base and store that one.
.filter_map(|&handle| k.prog.context.find_svm_alloc(handle as usize))
.for_each(|(base, _)| {
k.svms.insert(base as usize);
});
}
}
CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM
| CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM_ARM => {
let val = unsafe {
cl_slice::from_raw_parts_bytes_len::<cl_bool>(param_value, param_value_size)?
};
// we must explicitly check that we only got one element
if val.len() != 1 {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_OPERATION if param_name is CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM and
// param_value is CL_TRUE and no devices in the context associated with kernel support
// fine-grain system SVM allocations.
if val[0] == CL_TRUE && devs.iter().all(|dev| !dev.system_svm_supported()) {
return Err(CL_INVALID_OPERATION);
}
}
// CL_INVALID_VALUE if param_name is not valid
_ => return Err(CL_INVALID_VALUE),
}
Ok(())
}
#[cl_entrypoint(clEnqueueNDRangeKernel)]
fn enqueue_ndrange_kernel(
command_queue: cl_command_queue,
kernel: cl_kernel,
work_dim: cl_uint,
global_work_offset: *const usize,
global_work_size: *const usize,
local_work_size_ptr: *const usize,
num_events_in_wait_list: cl_uint,
event_wait_list: *const cl_event,
event: *mut cl_event,
) -> CLResult<()> {
let q = Queue::arc_from_raw(command_queue)?;
let k = Kernel::arc_from_raw(kernel)?;
let evs = event_list_from_cl(&q, num_events_in_wait_list, event_wait_list)?;
// CL_INVALID_CONTEXT if context associated with command_queue and kernel are not the same
if q.context != k.prog.context {
return Err(CL_INVALID_CONTEXT);
}
// CL_INVALID_PROGRAM_EXECUTABLE if there is no successfully built program executable available
// for device associated with command_queue.
if k.prog.status(q.device) != CL_BUILD_SUCCESS as cl_build_status {
return Err(CL_INVALID_PROGRAM_EXECUTABLE);
}
// CL_INVALID_KERNEL_ARGS if the kernel argument values have not been specified.
if k.arg_values().iter().any(|v| v.is_none()) {
return Err(CL_INVALID_KERNEL_ARGS);
}
// CL_INVALID_WORK_DIMENSION if work_dim is not a valid value (i.e. a value between 1 and
// CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS).
if work_dim == 0 || work_dim > q.device.max_grid_dimensions() {
return Err(CL_INVALID_WORK_DIMENSION);
}
// we assume the application gets it right and doesn't pass shorter arrays then actually needed.
let global_work_size = unsafe { kernel_work_arr_or_default(global_work_size, work_dim) };
let local_work_size = unsafe { kernel_work_arr_or_default(local_work_size_ptr, work_dim) };
let global_work_offset = unsafe { kernel_work_arr_or_default(global_work_offset, work_dim) };
// CL_INVALID_WORK_GROUP_SIZE if local_work_size is not NULL and if the total number of
// work-items in the work-group is zero
if !local_work_size_ptr.is_null() && local_work_size.iter().any(|&l| l == 0) {
return Err(CL_INVALID_WORK_GROUP_SIZE);
}
let device_bits = q.device.address_bits();
let device_max = u64::MAX >> (u64::BITS - device_bits);
let mut threads = 1;
for i in 0..work_dim as usize {
let lws = local_work_size[i];
let gws = global_work_size[i];
let gwo = global_work_offset[i];
threads *= lws;
// CL_INVALID_WORK_ITEM_SIZE if the number of work-items specified in any of
// local_work_size[0], … local_work_size[work_dim - 1] is greater than the corresponding
// values specified by
// CL_DEVICE_MAX_WORK_ITEM_SIZES[0], …, CL_DEVICE_MAX_WORK_ITEM_SIZES[work_dim - 1].
if lws > q.device.max_block_sizes()[i] {
return Err(CL_INVALID_WORK_ITEM_SIZE);
}
// CL_INVALID_WORK_GROUP_SIZE if the work-group size must be uniform and the
// local_work_size is not NULL, [...] if the global_work_size is not evenly divisible by
// the local_work_size.
if lws != 0 && gws % lws != 0 {
return Err(CL_INVALID_WORK_GROUP_SIZE);
}
// CL_INVALID_WORK_GROUP_SIZE if local_work_size is specified and does not match the
// required work-group size for kernel in the program source.
if lws != 0 && k.work_group_size()[i] != 0 && lws != k.work_group_size()[i] {
return Err(CL_INVALID_WORK_GROUP_SIZE);
}
// CL_INVALID_GLOBAL_WORK_SIZE if any of the values specified in global_work_size[0], …
// global_work_size[work_dim - 1] exceed the maximum value representable by size_t on
// the device on which the kernel-instance will be enqueued.
if gws as u64 > device_max {
return Err(CL_INVALID_GLOBAL_WORK_SIZE);
}
// CL_INVALID_GLOBAL_OFFSET if the value specified in global_work_size + the
// corresponding values in global_work_offset for any dimensions is greater than the
// maximum value representable by size t on the device on which the kernel-instance
// will be enqueued
if u64::checked_add(gws as u64, gwo as u64)
.filter(|&x| x <= device_max)
.is_none()
{
return Err(CL_INVALID_GLOBAL_OFFSET);
}
}
// CL_INVALID_WORK_GROUP_SIZE if local_work_size is specified and the total number of work-items
// in the work-group computed as local_work_size[0] × … local_work_size[work_dim - 1] is greater
// than the value specified by CL_KERNEL_WORK_GROUP_SIZE in the Kernel Object Device Queries
// table.
if threads != 0 && threads > k.max_threads_per_block(q.device) {
return Err(CL_INVALID_WORK_GROUP_SIZE);
}
// If global_work_size is NULL, or the value in any passed dimension is 0 then the kernel
// command will trivially succeed after its event dependencies are satisfied and subsequently
// update its completion event.
let cb: EventSig = if global_work_size.contains(&0) {
Box::new(|_, _| Ok(()))
} else {
k.launch(
&q,
work_dim,
local_work_size,
global_work_size,
global_work_offset,
)?
};
create_and_queue(q, CL_COMMAND_NDRANGE_KERNEL, evs, event, false, cb)
//• CL_INVALID_WORK_GROUP_SIZE if local_work_size is specified and is not consistent with the required number of sub-groups for kernel in the program source.
//• CL_MISALIGNED_SUB_BUFFER_OFFSET if a sub-buffer object is specified as the value for an argument that is a buffer object and the offset specified when the sub-buffer object is created is not aligned to CL_DEVICE_MEM_BASE_ADDR_ALIGN value for device associated with queue. This error code
//• CL_INVALID_IMAGE_SIZE if an image object is specified as an argument value and the image dimensions (image width, height, specified or compute row and/or slice pitch) are not supported by device associated with queue.
//• CL_IMAGE_FORMAT_NOT_SUPPORTED if an image object is specified as an argument value and the image format (image channel order and data type) is not supported by device associated with queue.
//• CL_OUT_OF_RESOURCES if there is a failure to queue the execution instance of kernel on the command-queue because of insufficient resources needed to execute the kernel. For example, the explicitly specified local_work_size causes a failure to execute the kernel because of insufficient resources such as registers or local memory. Another example would be the number of read-only image args used in kernel exceed the CL_DEVICE_MAX_READ_IMAGE_ARGS value for device or the number of write-only and read-write image args used in kernel exceed the CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS value for device or the number of samplers used in kernel exceed CL_DEVICE_MAX_SAMPLERS for device.
//• CL_MEM_OBJECT_ALLOCATION_FAILURE if there is a failure to allocate memory for data store associated with image or buffer objects specified as arguments to kernel.
//• CL_INVALID_OPERATION if SVM pointers are passed as arguments to a kernel and the device does not support SVM or if system pointers are passed as arguments to a kernel and/or stored inside SVM allocations passed as kernel arguments and the device does not support fine grain system SVM allocations.
}
#[cl_entrypoint(clEnqueueTask)]
fn enqueue_task(
command_queue: cl_command_queue,
kernel: cl_kernel,
num_events_in_wait_list: cl_uint,
event_wait_list: *const cl_event,
event: *mut cl_event,
) -> CLResult<()> {
// clEnqueueTask is equivalent to calling clEnqueueNDRangeKernel with work_dim set to 1,
// global_work_offset set to NULL, global_work_size[0] set to 1, and local_work_size[0] set to
// 1.
enqueue_ndrange_kernel(
command_queue,
kernel,
1,
ptr::null(),
[1, 1, 1].as_ptr(),
[1, 0, 0].as_ptr(),
num_events_in_wait_list,
event_wait_list,
event,
)
}
#[cl_entrypoint(clCloneKernel)]
fn clone_kernel(source_kernel: cl_kernel) -> CLResult<cl_kernel> {
let k = Kernel::ref_from_raw(source_kernel)?;
Ok(Arc::new(k.clone()).into_cl())
}
#[cl_entrypoint(clGetKernelSuggestedLocalWorkSize)]
fn get_kernel_suggested_local_work_size(
command_queue: cl_command_queue,
kernel: cl_kernel,
work_dim: cl_uint,
global_work_offset: *const usize,
global_work_size: *const usize,
suggested_local_work_size: *mut usize,
) -> CLResult<()> {
// CL_INVALID_GLOBAL_WORK_SIZE if global_work_size is NULL or if any of the values specified in
// global_work_size are 0.
if global_work_size.is_null() {
return Err(CL_INVALID_GLOBAL_WORK_SIZE);
}
// CL_INVALID_VALUE if suggested_local_work_size is NULL.
if suggested_local_work_size.is_null() {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_COMMAND_QUEUE if command_queue is not a valid host command-queue.
let queue = Queue::ref_from_raw(command_queue)?;
// CL_INVALID_KERNEL if kernel is not a valid kernel object.
let kernel = Kernel::ref_from_raw(kernel)?;
// CL_INVALID_CONTEXT if the context associated with kernel is not the same as the context
// associated with command_queue.
if queue.context != kernel.prog.context {
return Err(CL_INVALID_CONTEXT);
}
// CL_INVALID_PROGRAM_EXECUTABLE if there is no successfully built program executable available
// for kernel for the device associated with command_queue.
if kernel.prog.status(queue.device) != CL_BUILD_SUCCESS as cl_build_status {
return Err(CL_INVALID_PROGRAM_EXECUTABLE);
}
// CL_INVALID_KERNEL_ARGS if all argument values for kernel have not been set.
if kernel.arg_values().iter().any(|v| v.is_none()) {
return Err(CL_INVALID_KERNEL_ARGS);
}
// CL_INVALID_WORK_DIMENSION if work_dim is not a valid value (i.e. a value between 1 and
// CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS).
if work_dim == 0 || work_dim > queue.device.max_grid_dimensions() {
return Err(CL_INVALID_WORK_DIMENSION);
}
let mut global_work_size =
unsafe { kernel_work_arr_or_default(global_work_size, work_dim).to_vec() };
let suggested_local_work_size = unsafe {
kernel_work_arr_mut(suggested_local_work_size, work_dim).ok_or(CL_INVALID_VALUE)?
};
let global_work_offset = unsafe { kernel_work_arr_or_default(global_work_offset, work_dim) };
let device_bits = queue.device.address_bits();
let device_max = u64::MAX >> (u64::BITS - device_bits);
for i in 0..work_dim as usize {
let gws = global_work_size[i];
let gwo = global_work_offset[i];
// CL_INVALID_GLOBAL_WORK_SIZE if global_work_size is NULL or if any of the values specified
// in global_work_size are 0.
if gws == 0 {
return Err(CL_INVALID_GLOBAL_WORK_SIZE);
}
// CL_INVALID_GLOBAL_WORK_SIZE if any of the values specified in global_work_size exceed the
// maximum value representable by size_t on the device associated with command_queue.
if gws as u64 > device_max {
return Err(CL_INVALID_GLOBAL_WORK_SIZE);
}
// CL_INVALID_GLOBAL_OFFSET if the value specified in global_work_size plus the
// corresponding value in global_work_offset for dimension exceeds the maximum value
// representable by size_t on the device associated with command_queue.
if u64::checked_add(gws as u64, gwo as u64)
.filter(|&x| x <= device_max)
.is_none()
{
return Err(CL_INVALID_GLOBAL_OFFSET);
}
}
kernel.suggest_local_size(
queue.device,
work_dim as usize,
&mut global_work_size,
suggested_local_work_size,
);
Ok(())
// CL_MISALIGNED_SUB_BUFFER_OFFSET if a sub-buffer object is set as an argument to kernel and the offset specified when the sub-buffer object was created is not aligned to CL_DEVICE_MEM_BASE_ADDR_ALIGN for the device associated with command_queue.
// CL_INVALID_IMAGE_SIZE if an image object is set as an argument to kernel and the image dimensions are not supported by device associated with command_queue.
// CL_IMAGE_FORMAT_NOT_SUPPORTED if an image object is set as an argument to kernel and the image format is not supported by the device associated with command_queue.
// CL_INVALID_OPERATION if an SVM pointer is set as an argument to kernel and the device associated with command_queue does not support SVM or the required SVM capabilities for the SVM pointer.
// CL_OUT_OF_RESOURCES if there is a failure to allocate resources required by the OpenCL implementation on the device.
// CL_OUT_OF_HOST_MEMORY if there is a failure to allocate resources required by the OpenCL implementation on the host.
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,140 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
use crate::api::icd::CLResult;
use crate::api::util::*;
use crate::core::platform::*;
use crate::core::version::*;
use mesa_rust_util::ptr::*;
use rusticl_opencl_gen::*;
use rusticl_proc_macros::cl_entrypoint;
use rusticl_proc_macros::cl_info_entrypoint;
use std::ffi::CStr;
#[cl_info_entrypoint(clGetPlatformInfo)]
unsafe impl CLInfo<cl_platform_info> for cl_platform_id {
fn query(&self, q: cl_platform_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
self.get_ref()?;
match q {
CL_PLATFORM_EXTENSIONS => v.write::<&str>(&Platform::get().extension_string),
CL_PLATFORM_EXTENSIONS_WITH_VERSION => {
v.write::<&[cl_name_version]>(&Platform::get().extensions)
}
CL_PLATFORM_HOST_TIMER_RESOLUTION => v.write::<cl_ulong>(1),
CL_PLATFORM_ICD_SUFFIX_KHR => v.write::<&CStr>(c"MESA"),
CL_PLATFORM_NAME => v.write::<&CStr>(c"rusticl"),
CL_PLATFORM_NUMERIC_VERSION => v.write::<cl_version>(CLVersion::Cl3_1.into()),
CL_PLATFORM_PROFILE => v.write::<&CStr>(c"FULL_PROFILE"),
CL_PLATFORM_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR => {
v.write::<&[cl_external_semaphore_handle_type_khr]>(
if Platform::get().all_devs_have_external_semaphores() {
&[CL_SEMAPHORE_HANDLE_SYNC_FD_KHR]
} else {
&[]
},
)
}
CL_PLATFORM_SEMAPHORE_IMPORT_HANDLE_TYPES_KHR => {
v.write::<&[cl_external_semaphore_handle_type_khr]>(
if Platform::get().all_devs_have_external_semaphores() {
&[CL_SEMAPHORE_HANDLE_SYNC_FD_KHR]
} else {
&[]
},
)
}
CL_PLATFORM_SEMAPHORE_TYPES_KHR => {
v.write::<&[cl_semaphore_type_khr]>(if Platform::get().all_devs_have_semaphores() {
&[CL_SEMAPHORE_TYPE_BINARY_KHR]
} else {
&[]
})
}
CL_PLATFORM_VENDOR => v.write::<&CStr>(c"Mesa/X.org"),
// OpenCL<space><major_version.minor_version><space><platform-specific information>
CL_PLATFORM_VERSION => v.write::<&CStr>(c"OpenCL 3.1 "),
// CL_INVALID_VALUE if param_name is not one of the supported values
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_entrypoint(clGetPlatformIDs)]
fn get_platform_ids(
num_entries: cl_uint,
platforms: *mut cl_platform_id,
num_platforms: *mut cl_uint,
) -> CLResult<()> {
// CL_INVALID_VALUE if num_entries is equal to zero and platforms is not NULL
if num_entries == 0 && !platforms.is_null() {
return Err(CL_INVALID_VALUE);
}
// or if both num_platforms and platforms are NULL."
if num_platforms.is_null() && platforms.is_null() {
return Err(CL_INVALID_VALUE);
}
// run initialization code once
Platform::init_once();
// platforms returns a list of OpenCL platforms available for access through the Khronos ICD Loader.
// The cl_platform_id values returned in platforms are ICD compatible and can be used to identify a
// specific OpenCL platform. If the platforms argument is NULL, then this argument is ignored. The
// number of OpenCL platforms returned is the minimum of the value specified by num_entries or the
// number of OpenCL platforms available.
// SAFETY: Caller is responsible for providing a null pointer or one valid
// for a write of `num_entries * size_of::<cl_platform_id>()`. We are
// guaranteed to write at most one value, and if `platforms` is non-null,
// `num_entries` is guaranteed to be at least 1.
unsafe { platforms.write_checked(Platform::get().as_ptr()) };
// num_platforms returns the number of OpenCL platforms available. If num_platforms is NULL, then
// this argument is ignored.
// SAFETY: Caller is responsible for providing a null pointer or one valid
// for a write of `size_of::<cl_uint>()`.
unsafe { num_platforms.write_checked(1) };
Ok(())
}
#[cl_entrypoint(clUnloadPlatformCompiler)]
fn unload_platform_compiler(platform: cl_platform_id) -> CLResult<()> {
platform.get_ref()?;
// TODO unload the compiler
Ok(())
}
#[cl_entrypoint(clIcdSetPlatformDispatchDataKHR)]
fn icd_set_platform_dispatch_data(
platform: cl_platform_id,
dispatch_data: *mut ::std::os::raw::c_void,
) -> CLResult<()> {
// SAFETY: this API is expected to be called from the ICD loader at initialization time and
// therefore no concurrent access should exist.
let p = unsafe { platform.get_mut()? };
p.init_icd_dispatch_data(dispatch_data);
Ok(())
}
#[test]
fn test_get_platform_info() {
let mut s: usize = 0;
// Being a lil sneaky sneak, shhhh
Platform::init_once();
let p = Platform::get().as_ptr();
let mut r =
unsafe { clGetPlatformInfo(p, CL_PLATFORM_EXTENSIONS, 0, std::ptr::null_mut(), &mut s) };
assert_eq!(r, CL_SUCCESS as i32);
assert!(s > 0);
let mut v: Vec<u8> = vec![0; s];
r = unsafe { clGetPlatformInfo(p, CL_PLATFORM_EXTENSIONS, s, v.as_mut_ptr().cast(), &mut s) };
assert_eq!(r, CL_SUCCESS as i32);
assert_eq!(s, v.len());
assert!(!v[0..s - 2].contains(&0));
assert_eq!(v[s - 1], 0);
}
@@ -0,0 +1,532 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
use crate::api::icd::*;
use crate::api::types::*;
use crate::api::util::*;
use crate::core::context::*;
use crate::core::device::*;
use crate::core::program::*;
use mesa_rust_util::string::*;
use rusticl_opencl_gen::*;
use rusticl_proc_macros::cl_entrypoint;
use rusticl_proc_macros::cl_info_entrypoint;
use std::ffi::CStr;
use std::ffi::CString;
use std::iter;
use std::num::NonZeroUsize;
use std::os::raw::c_char;
use std::ptr;
use std::slice;
use std::sync::Arc;
#[cl_info_entrypoint(clGetProgramInfo)]
unsafe impl CLInfo<cl_program_info> for cl_program {
fn query(&self, q: cl_program_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
let prog = Program::ref_from_raw(*self)?;
// CL_INVALID_PROGRAM_EXECUTABLE if param_name is CL_PROGRAM_NUM_KERNELS,
// CL_PROGRAM_KERNEL_NAMES, CL_PROGRAM_SCOPE_GLOBAL_CTORS_PRESENT, or
// CL_PROGRAM_SCOPE_GLOBAL_DTORS_PRESENT and a successful program executable has not been
// built for at least one device in the list of devices associated with program.
if matches!(
q,
CL_PROGRAM_NUM_KERNELS
| CL_PROGRAM_KERNEL_NAMES
| CL_PROGRAM_SCOPE_GLOBAL_CTORS_PRESENT
| CL_PROGRAM_SCOPE_GLOBAL_DTORS_PRESENT
) && !prog.build_info().has_successful_build()
{
return Err(CL_INVALID_PROGRAM_EXECUTABLE);
}
match q {
CL_PROGRAM_BINARIES => {
let input = v.input::<*mut u8>()?;
// This query is a bit weird. At least the CTS is. We need to return the proper size
// of the buffer to hold all pointers, but when actually doing the query, we'd just
// parse the pointers out and write to them.
if !input.is_empty() {
// SAFETY: Per spec it contains an array of pointers to write the binaries to,
// so we can assume the entire slice to be initialized.
let input = unsafe { slice_assume_init_ref(input) };
prog.binaries(input)?;
}
v.write_len_only::<&[*mut u8]>(prog.devs.len())
}
CL_PROGRAM_BINARY_SIZES => v.write_iter::<usize>(prog.bin_sizes()),
CL_PROGRAM_CONTEXT => {
// Note we use as_ptr here which doesn't increase the reference count.
let ptr = Arc::as_ptr(&prog.context);
v.write::<cl_context>(cl_context::from_ptr(ptr))
}
CL_PROGRAM_DEVICES => {
v.write_iter::<cl_device_id>(prog.devs.iter().map(|&d| cl_device_id::from_ptr(d)))
}
CL_PROGRAM_IL => match &prog.src {
ProgramSourceType::Il(il) => v.write::<&[u8]>(il.to_bin()),
// The spec _requires_ that we don't touch the buffer here.
_ => v.write_len_only::<&[u8]>(0),
},
CL_PROGRAM_KERNEL_NAMES => v.write::<&CStr>(&prog.build_info().kernels().join(c";")),
CL_PROGRAM_NUM_DEVICES => v.write::<cl_uint>(prog.devs.len() as cl_uint),
CL_PROGRAM_NUM_KERNELS => v.write::<usize>(prog.build_info().kernels().len()),
CL_PROGRAM_REFERENCE_COUNT => v.write::<cl_uint>(Program::refcnt(*self)?),
CL_PROGRAM_SCOPE_GLOBAL_CTORS_PRESENT => v.write::<cl_bool>(CL_FALSE),
CL_PROGRAM_SCOPE_GLOBAL_DTORS_PRESENT => v.write::<cl_bool>(CL_FALSE),
CL_PROGRAM_SOURCE => v.write::<&CStr>(match &prog.src {
ProgramSourceType::Src(src) => src,
// need to return a null string if no source is available.
_ => c"",
}),
// CL_INVALID_VALUE if param_name is not one of the supported values
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_info_entrypoint(clGetProgramBuildInfo)]
unsafe impl CLInfoObj<cl_program_build_info, cl_device_id> for cl_program {
fn query(
&self,
d: cl_device_id,
q: cl_program_build_info,
v: CLInfoValue,
) -> CLResult<CLInfoRes> {
let prog = Program::ref_from_raw(*self)?;
let dev = Device::ref_from_raw(d)?;
match q {
CL_PROGRAM_BINARY_TYPE => v.write::<cl_program_binary_type>(prog.bin_type(dev)),
CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE => v.write::<usize>(0),
CL_PROGRAM_BUILD_LOG => v.write::<&CStr>(&prog.log(dev)),
CL_PROGRAM_BUILD_OPTIONS => v.write::<&str>(&prog.options(dev)),
CL_PROGRAM_BUILD_STATUS => v.write::<cl_build_status>(prog.status(dev)),
// CL_INVALID_VALUE if param_name is not one of the supported values
_ => Err(CL_INVALID_VALUE),
}
}
}
fn validate_devices<'a>(
device_list: *const cl_device_id,
num_devices: cl_uint,
default: &[&'a Device],
) -> CLResult<Vec<&'a Device>> {
let mut devs = Device::refs_from_arr(device_list, num_devices)?;
// If device_list is a NULL value, the compile is performed for all devices associated with
// program.
if devs.is_empty() {
devs = default.to_vec();
}
Ok(devs)
}
#[cl_entrypoint(clCreateProgramWithSource)]
fn create_program_with_source(
context: cl_context,
count: cl_uint,
strings: *mut *const c_char,
lengths: *const usize,
) -> CLResult<cl_program> {
let c = Context::arc_from_raw(context)?;
// CL_INVALID_VALUE if count is zero or if strings ...
if count == 0 || strings.is_null() {
return Err(CL_INVALID_VALUE);
}
// ... or any entry in strings is NULL.
let srcs = unsafe { slice::from_raw_parts(strings, count as usize) };
if srcs.contains(&ptr::null()) {
return Err(CL_INVALID_VALUE);
}
// "lengths argument is an array with the number of chars in each string
// (the string length). If an element in lengths is zero, its accompanying
// string is null-terminated. If lengths is NULL, all strings in the
// strings argument are considered null-terminated."
// A length of zero represents "no length given", so semantically we're
// dealing not with a slice of usize but actually with a slice of
// Option<NonZeroUsize>. Handily those two are layout compatible, so simply
// reinterpret the data.
//
// Take either an iterator over the given slice or - if the `lengths`
// pointer is NULL - an iterator that always returns None (infinite, but
// later bounded by being zipped with the finite `srcs`).
//
// Looping over different iterators is no problem as long as they return
// the same item type. However, since we can only decide which to use at
// runtime, we need to use dynamic dispatch. The compiler also needs to
// know how much space to reserve on the stack, but different
// implementations of the `Iterator` trait will need different amounts of
// memory. This is resolved by putting the actual iterator on the heap
// with `Box` and only a reference to it on the stack.
let lengths: Box<dyn Iterator<Item = _>> = if lengths.is_null() {
Box::new(iter::repeat(&None))
} else {
// SAFETY: Option<NonZeroUsize> is guaranteed to be layout compatible
// with usize. The zero niche represents None.
let lengths = lengths as *const Option<NonZeroUsize>;
Box::new(unsafe { slice::from_raw_parts(lengths, count as usize) }.iter())
};
// We don't want encoding or any other problems with the source to prevent
// compilation, so don't convert this to a Rust `String`.
let mut source = Vec::new();
for (&string_ptr, len_opt) in iter::zip(srcs, lengths) {
let arr = match len_opt {
Some(len) => {
// The spec doesn't say how nul bytes should be handled here or
// if they are legal at all. Assume they truncate the string.
let arr = unsafe { slice::from_raw_parts(string_ptr.cast(), len.get()) };
// TODO: simplify this a bit with from_bytes_until_nul once
// that's stabilized and available in our msrv
arr.iter()
.position(|&x| x == 0)
.map_or(arr, |nul_index| &arr[..nul_index])
}
None => unsafe { CStr::from_ptr(string_ptr) }.to_bytes(),
};
source.extend_from_slice(arr);
}
Ok(Program::new(
c,
// SAFETY: We've constructed `source` such that it contains no nul bytes.
unsafe { CString::from_vec_unchecked(source) },
)
.into_cl())
}
#[cl_entrypoint(clCreateProgramWithBinary)]
fn create_program_with_binary(
context: cl_context,
num_devices: cl_uint,
device_list: *const cl_device_id,
lengths: *const usize,
binaries: *mut *const ::std::os::raw::c_uchar,
binary_status: *mut cl_int,
) -> CLResult<cl_program> {
let c = Context::arc_from_raw(context)?;
let devs = Device::refs_from_arr(device_list, num_devices)?;
// CL_INVALID_VALUE if device_list is NULL or num_devices is zero.
if devs.is_empty() {
return Err(CL_INVALID_VALUE);
}
// needs to happen after `devs.is_empty` check to protect against num_devices being 0
let mut binary_status =
unsafe { cl_slice::from_raw_parts_mut(binary_status, num_devices as usize) }.ok();
// CL_INVALID_VALUE if lengths or binaries is NULL
if lengths.is_null() || binaries.is_null() {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_DEVICE if any device in device_list is not in the list of devices associated with
// context.
if !devs.iter().all(|d| c.devs.contains(d)) {
return Err(CL_INVALID_DEVICE);
}
let lengths = unsafe { slice::from_raw_parts(lengths, num_devices as usize) };
let binaries = unsafe { slice::from_raw_parts(binaries, num_devices as usize) };
// now device specific stuff
let mut bins: Vec<&[u8]> = vec![&[]; num_devices as usize];
for i in 0..num_devices as usize {
// CL_INVALID_VALUE if lengths[i] is zero or if binaries[i] is a NULL value (handled inside
// [Program::from_bins])
if lengths[i] == 0 || binaries[i].is_null() {
bins[i] = &[];
} else {
bins[i] = unsafe { slice::from_raw_parts(binaries[i], lengths[i]) };
}
}
let prog = match Program::from_bins(c, devs, &bins) {
Ok(prog) => {
if let Some(binary_status) = &mut binary_status {
binary_status.fill(CL_SUCCESS as cl_int);
}
prog
}
Err(errors) => {
// CL_INVALID_BINARY if an invalid program binary was encountered for any device.
// binary_status will return specific status for each device.
if let Some(binary_status) = &mut binary_status {
binary_status.copy_from_slice(&errors);
}
// this should return either CL_INVALID_VALUE or CL_INVALID_BINARY
let err = errors.into_iter().find(|&err| err != 0).unwrap_or_default();
debug_assert!(err != 0);
return Err(err);
}
};
Ok(prog.into_cl())
}
#[cl_entrypoint(clCreateProgramWithIL)]
fn create_program_with_il(
context: cl_context,
il: *const ::std::os::raw::c_void,
length: usize,
) -> CLResult<cl_program> {
let c = Context::arc_from_raw(context)?;
// CL_INVALID_VALUE if il is NULL or if length is zero.
if il.is_null() || length == 0 {
return Err(CL_INVALID_VALUE);
}
// SAFETY: according to API spec
let spirv = unsafe { slice::from_raw_parts(il.cast(), length) };
Ok(Program::from_spirv(c, spirv).into_cl())
}
#[cl_entrypoint(clRetainProgram)]
fn retain_program(program: cl_program) -> CLResult<()> {
Program::retain(program)
}
#[cl_entrypoint(clReleaseProgram)]
fn release_program(program: cl_program) -> CLResult<()> {
Program::release(program)
}
#[cl_entrypoint(clBuildProgram)]
fn build_program(
program: cl_program,
num_devices: cl_uint,
device_list: *const cl_device_id,
options: *const c_char,
pfn_notify: Option<FuncProgramCB>,
user_data: *mut ::std::os::raw::c_void,
) -> CLResult<()> {
let program = Program::arc_from_raw(program)?;
let devices = validate_devices(device_list, num_devices, &program.devs)?;
// SAFETY: The requirements on `ProgramCB::try_new` match the requirements
// imposed by the OpenCL specification. It is the caller's duty to uphold them.
let callback = unsafe { ProgramCB::try_new(pfn_notify, user_data)? };
// CL_INVALID_OPERATION if there are kernel objects attached to program.
if program.active_kernels() {
return Err(CL_INVALID_OPERATION);
}
if program.any_device_in_progress(&devices) {
// CL_INVALID_OPERATION if the build of a program executable for any of
// the devices listed in device_list by a previous call to
// clBuildProgram for program has not completed.
return Err(CL_INVALID_OPERATION);
}
let options = c_string_to_string(options);
program.build(devices, options, callback)
//• CL_INVALID_BINARY if program is created with clCreateProgramWithBinary and devices listed in device_list do not have a valid program binary loaded.
//• CL_INVALID_BUILD_OPTIONS if the build options specified by options are invalid.
//• CL_INVALID_OPERATION if program was not created with clCreateProgramWithSource, clCreateProgramWithIL or clCreateProgramWithBinary.
}
#[cl_entrypoint(clCompileProgram)]
fn compile_program(
program: cl_program,
num_devices: cl_uint,
device_list: *const cl_device_id,
options: *const c_char,
num_input_headers: cl_uint,
input_headers: *const cl_program,
header_include_names: *mut *const c_char,
pfn_notify: Option<FuncProgramCB>,
user_data: *mut ::std::os::raw::c_void,
) -> CLResult<()> {
let program = Program::arc_from_raw(program)?;
let devices = validate_devices(device_list, num_devices, &program.devs)?;
// SAFETY: The requirements on `ProgramCB::try_new` match the requirements
// imposed by the OpenCL specification. It is the caller's duty to uphold them.
let callback = unsafe { ProgramCB::try_new(pfn_notify, user_data)? };
// CL_INVALID_VALUE if num_input_headers is zero and header_include_names or input_headers are
// not NULL or if num_input_headers is not zero and header_include_names or input_headers are
// NULL.
if num_input_headers == 0 && (!header_include_names.is_null() || !input_headers.is_null())
|| num_input_headers != 0 && (header_include_names.is_null() || input_headers.is_null())
{
return Err(CL_INVALID_VALUE);
}
let mut headers = Vec::new();
// If program was created using clCreateProgramWithIL, then num_input_headers, input_headers,
// and header_include_names are ignored.
if !program.is_il() {
for h in 0..num_input_headers as usize {
// SAFETY: The OpenCL spec requires that num_input_headers give the
// number of `cl_program` objects in input_headers. It is up to the
// caller to ensure that this requirement is met.
let header = Program::arc_from_raw(unsafe { *input_headers.add(h) })?;
if !header.is_src() {
// The OpenCL spec specifies that headers must be created with
// clCreateProgramWithSource, but does not specify an error code
// to return if they are not.
return Err(CL_INVALID_OPERATION);
}
// CL_INVALID_CONTEXT if the context associated with program and programs in
// input_headers are not the same.
if header.context != program.context {
return Err(CL_INVALID_CONTEXT);
}
headers.push(HeaderProgram {
// SAFETY: The OpenCL spec requires that there be a
// one-to-one correspondence between input_headers and
// header_include_names, containing valid C strings.
name: unsafe { CStr::from_ptr(*header_include_names.add(h)).to_owned() },
program: header,
});
}
}
// CL_INVALID_OPERATION if program has no source or IL available, i.e. it has not been created
// with clCreateProgramWithSource or clCreateProgramWithIL.
if !(program.is_src() || program.is_il()) {
return Err(CL_INVALID_OPERATION);
}
// CL_INVALID_OPERATION if there are kernel objects attached to program.
if program.active_kernels() {
return Err(CL_INVALID_OPERATION);
}
if program.any_device_in_progress(&devices) {
// CL_INVALID_OPERATION if the compilation or build of a program
// executable for any of the devices listed in device_list by a previous
// call to clCompileProgram or clBuildProgram for program has not
// completed.
return Err(CL_INVALID_OPERATION);
}
let options = c_string_to_string(options);
program.compile(devices, options, headers, callback)
// • CL_INVALID_COMPILER_OPTIONS if the compiler options specified by options are invalid.
}
pub fn link_program(
context: cl_context,
num_devices: cl_uint,
device_list: *const cl_device_id,
options: *const ::std::os::raw::c_char,
num_input_programs: cl_uint,
input_programs: *const cl_program,
pfn_notify: Option<FuncProgramCB>,
user_data: *mut ::std::os::raw::c_void,
) -> CLResult<(cl_program, cl_int)> {
let context = Context::arc_from_raw(context)?;
let devices = validate_devices(device_list, num_devices, &context.devs)?;
let input_programs = Program::arcs_from_arr(input_programs, num_input_programs)?;
// SAFETY: The requirements on `ProgramCB::try_new` match the requirements
// imposed by the OpenCL specification. It is the caller's duty to uphold them.
let callback = unsafe { ProgramCB::try_new(pfn_notify, user_data)? };
// CL_INVALID_VALUE if num_input_programs is zero and input_programs is NULL
if input_programs.is_empty() {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_DEVICE if any device in device_list is not in the list of devices associated with
// context.
if !devices.iter().all(|device| context.devs.contains(device)) {
return Err(CL_INVALID_DEVICE);
}
// CL_INVALID_CONTEXT if the context associated with programs in input_programs is not the same
// as context.
if input_programs
.iter()
.any(|program| program.context != context)
{
return Err(CL_INVALID_CONTEXT);
}
// CL_INVALID_OPERATION if the compilation or build of a program executable for any of the
// devices listed in device_list by a previous call to clCompileProgram or clBuildProgram for
// program has not completed.
if input_programs
.iter()
.any(|program| !program.all_devices_succeeded(&devices))
{
return Err(CL_INVALID_OPERATION);
}
let (res, code) = Program::link(
context,
devices,
input_programs,
c_string_to_string(options),
callback,
)?;
Ok((res.into_cl(), code))
//• CL_INVALID_LINKER_OPTIONS if the linker options specified by options are invalid.
//• CL_INVALID_OPERATION if the rules for devices containing compiled binaries or libraries as described in input_programs argument above are not followed.
}
#[cl_entrypoint(clSetProgramSpecializationConstant)]
fn set_program_specialization_constant(
program: cl_program,
spec_id: cl_uint,
spec_size: usize,
spec_value: *const ::std::os::raw::c_void,
) -> CLResult<()> {
let program = Program::ref_from_raw(program)?;
// CL_INVALID_PROGRAM if program is not a valid program object created from an intermediate
// language (e.g. SPIR-V)
// TODO: or if the intermediate language does not support specialization constants.
if !program.is_il() {
return Err(CL_INVALID_PROGRAM);
}
if spec_size != program.get_spec_constant_size(spec_id).into() {
// CL_INVALID_VALUE if spec_size does not match the size of the specialization constant in
// the module,
return Err(CL_INVALID_VALUE);
}
// or if spec_value is NULL.
if spec_value.is_null() {
return Err(CL_INVALID_VALUE);
}
// SAFETY: according to API spec
program.set_spec_constant(spec_id, unsafe {
slice::from_raw_parts(spec_value.cast(), spec_size)
});
Ok(())
}
#[cl_entrypoint(clSetProgramReleaseCallback)]
fn set_program_release_callback(
_program: cl_program,
_pfn_notify: ::std::option::Option<FuncProgramCB>,
_user_data: *mut ::std::os::raw::c_void,
) -> CLResult<()> {
Err(CL_INVALID_OPERATION)
}
+252
View File
@@ -0,0 +1,252 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
use crate::api::event::create_and_queue;
use crate::api::icd::*;
use crate::api::util::*;
use crate::core::context::*;
use crate::core::device::*;
use crate::core::event::*;
use crate::core::queue::*;
use mesa_rust_util::properties::*;
use rusticl_opencl_gen::*;
use rusticl_proc_macros::cl_entrypoint;
use rusticl_proc_macros::cl_info_entrypoint;
use std::ptr;
use std::sync::Arc;
#[cl_info_entrypoint(clGetCommandQueueInfo)]
unsafe impl CLInfo<cl_command_queue_info> for cl_command_queue {
fn query(&self, q: cl_command_queue_info, v: CLInfoValue) -> CLResult<CLInfoRes> {
let queue = Queue::ref_from_raw(*self)?;
match q {
CL_QUEUE_CONTEXT => {
// Note we use as_ptr here which doesn't increase the reference count.
let ptr = Arc::as_ptr(&queue.context);
v.write::<cl_context>(cl_context::from_ptr(ptr))
}
CL_QUEUE_DEVICE => v.write::<cl_device_id>(cl_device_id::from_ptr(queue.device)),
CL_QUEUE_DEVICE_DEFAULT => v.write::<cl_command_queue>(ptr::null_mut()),
CL_QUEUE_PROPERTIES => v.write::<cl_command_queue_properties>(queue.props),
CL_QUEUE_PROPERTIES_ARRAY => {
v.write::<&Properties<cl_queue_properties>>(&queue.props_v2)
}
CL_QUEUE_REFERENCE_COUNT => v.write::<cl_uint>(Queue::refcnt(*self)?),
// clGetCommandQueueInfo, passing CL_QUEUE_SIZE Returns CL_INVALID_COMMAND_QUEUE since
// command_queue cannot be a valid device command-queue.
CL_QUEUE_SIZE => Err(CL_INVALID_COMMAND_QUEUE),
// CL_INVALID_VALUE if param_name is not one of the supported values
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_entrypoint(clSetCommandQueueProperty)]
fn set_command_queue_property(
_command_queue: cl_command_queue,
_properties: cl_command_queue_properties,
_enable: cl_bool,
_old_properties: *mut cl_command_queue_properties,
) -> CLResult<()> {
// clSetCommandQueueProperty may unconditionally return an error if no devices in the context
// associated with command_queue support modifying the properties of a command-queue. Support
// for modifying the properties of a command-queue is required only for OpenCL 1.0 devices.
//
// CL_INVALID_OPERATION if no devices in the context associated with command_queue support
// modifying the properties of a command-queue.
Err(CL_INVALID_OPERATION)
}
fn valid_command_queue_properties(properties: cl_command_queue_properties) -> bool {
let valid_flags = cl_bitfield::from(
CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE
| CL_QUEUE_PROFILING_ENABLE
| CL_QUEUE_ON_DEVICE
| CL_QUEUE_ON_DEVICE_DEFAULT,
);
properties & !valid_flags == 0
}
fn supported_command_queue_properties(
dev: &Device,
properties: cl_command_queue_properties,
) -> bool {
let mut valid_flags = CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE;
if dev.caps.has_timestamp {
valid_flags |= CL_QUEUE_PROFILING_ENABLE;
}
// add on device queue properties here once supported and called from `clCreateCommandQueueWithProperties`
if properties & !cl_bitfield::from(valid_flags) != 0 {
return false;
}
true
}
pub fn create_command_queue_impl(
context: cl_context,
device: cl_device_id,
properties: cl_command_queue_properties,
properties_v2: Properties<cl_queue_properties>,
) -> CLResult<cl_command_queue> {
let c = Context::arc_from_raw(context)?;
let d = Device::ref_from_raw(device)?
.to_static()
.ok_or(CL_INVALID_DEVICE)?;
// CL_INVALID_DEVICE if device [...] is not associated with context.
if !c.devs.contains(&d) {
return Err(CL_INVALID_DEVICE);
}
// CL_INVALID_VALUE if values specified in properties are not valid.
if !valid_command_queue_properties(properties) {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_QUEUE_PROPERTIES if values specified in properties are valid but are not supported by the device.
if !supported_command_queue_properties(d, properties) {
return Err(CL_INVALID_QUEUE_PROPERTIES);
}
Ok(Queue::new(c, d, properties, properties_v2)?.into_cl())
}
#[cl_entrypoint(clCreateCommandQueue)]
fn create_command_queue(
context: cl_context,
device: cl_device_id,
properties: cl_command_queue_properties,
) -> CLResult<cl_command_queue> {
create_command_queue_impl(context, device, properties, Properties::default())
}
#[cl_entrypoint(clCreateCommandQueueWithProperties)]
fn create_command_queue_with_properties(
context: cl_context,
device: cl_device_id,
properties: *const cl_queue_properties,
) -> CLResult<cl_command_queue> {
let mut queue_properties = cl_command_queue_properties::default();
let d = Device::ref_from_raw(device)?;
// SAFETY: properties is a 0 terminated array by spec.
let properties = unsafe { Properties::new(properties) }.ok_or(CL_INVALID_VALUE)?;
for (&key, &val) in properties.iter() {
match u32::try_from(key).or(Err(CL_INVALID_VALUE))? {
CL_QUEUE_PROPERTIES => queue_properties = val,
CL_QUEUE_PRIORITY_KHR if d.context_priority_supported() != 0 => {
let valid_props: cl_queue_properties = (CL_QUEUE_PRIORITY_LOW_KHR
| CL_QUEUE_PRIORITY_MED_KHR
| CL_QUEUE_PRIORITY_HIGH_KHR)
.into();
if val & !valid_props != 0 {
return Err(CL_INVALID_VALUE);
}
}
// CL_INVALID_QUEUE_PROPERTIES if values specified in properties are valid but are not
// supported by the device.
CL_QUEUE_SIZE => return Err(CL_INVALID_QUEUE_PROPERTIES),
_ => return Err(CL_INVALID_VALUE),
}
}
create_command_queue_impl(context, device, queue_properties, properties)
}
#[cl_entrypoint(clEnqueueMarker)]
fn enqueue_marker(command_queue: cl_command_queue, event: *mut cl_event) -> CLResult<()> {
let q = Queue::arc_from_raw(command_queue)?;
// TODO marker makes sure previous commands did complete
create_and_queue(
q,
CL_COMMAND_MARKER,
Vec::new(),
event,
false,
Box::new(|_, _| Ok(())),
)
}
#[cl_entrypoint(clEnqueueMarkerWithWaitList)]
fn enqueue_marker_with_wait_list(
command_queue: cl_command_queue,
num_events_in_wait_list: cl_uint,
event_wait_list: *const cl_event,
event: *mut cl_event,
) -> CLResult<()> {
let q = Queue::arc_from_raw(command_queue)?;
let evs = event_list_from_cl(&q, num_events_in_wait_list, event_wait_list)?;
// TODO marker makes sure previous commands did complete
create_and_queue(
q,
CL_COMMAND_MARKER,
evs,
event,
false,
Box::new(|_, _| Ok(())),
)
}
#[cl_entrypoint(clEnqueueBarrier)]
fn enqueue_barrier(command_queue: cl_command_queue) -> CLResult<()> {
let q = Queue::arc_from_raw(command_queue)?;
// TODO barriers make sure previous commands did complete and other commands didn't start
let e = Event::new(&q, CL_COMMAND_BARRIER, Vec::new(), Box::new(|_, _| Ok(())));
q.queue(e);
Ok(())
}
#[cl_entrypoint(clEnqueueBarrierWithWaitList)]
fn enqueue_barrier_with_wait_list(
command_queue: cl_command_queue,
num_events_in_wait_list: cl_uint,
event_wait_list: *const cl_event,
event: *mut cl_event,
) -> CLResult<()> {
let q = Queue::arc_from_raw(command_queue)?;
let evs = event_list_from_cl(&q, num_events_in_wait_list, event_wait_list)?;
// TODO barriers make sure previous commands did complete and other commands didn't start
create_and_queue(
q,
CL_COMMAND_BARRIER,
evs,
event,
false,
Box::new(|_, _| Ok(())),
)
}
#[cl_entrypoint(clFlush)]
fn flush(command_queue: cl_command_queue) -> CLResult<()> {
// CL_INVALID_COMMAND_QUEUE if command_queue is not a valid host command-queue.
Queue::ref_from_raw(command_queue)?.flush(false)
}
#[cl_entrypoint(clFinish)]
fn finish(command_queue: cl_command_queue) -> CLResult<()> {
// CL_INVALID_COMMAND_QUEUE if command_queue is not a valid host command-queue.
Queue::ref_from_raw(command_queue)?.flush(true)
}
#[cl_entrypoint(clRetainCommandQueue)]
fn retain_command_queue(command_queue: cl_command_queue) -> CLResult<()> {
Queue::retain(command_queue)
}
#[cl_entrypoint(clReleaseCommandQueue)]
fn release_command_queue(command_queue: cl_command_queue) -> CLResult<()> {
// clReleaseCommandQueue performs an implicit flush to issue any previously queued OpenCL
// commands in command_queue.
flush(command_queue)?;
Queue::release(command_queue)
}
@@ -0,0 +1,365 @@
// Copyright 2025 Red Hat.
// SPDX-License-Identifier: MIT
use {
crate::{
api::{
event::create_and_queue,
icd::{ArcedCLObject, BaseCLObject, CLResult, ReferenceCountedAPIPointer},
util::{event_list_from_cl, CLInfo, CLInfoRes, CLInfoValue},
},
core::{
context::Context,
device::Device,
queue::Queue,
semaphore::{Semaphore, SemaphoreHandle},
},
},
mesa_rust_util::{
conversion::TryIntoWithErr,
properties::{MultiValProperties, Properties},
ptr::CheckedPtr,
},
rusticl_opencl_gen::*,
rusticl_proc_macros::{cl_entrypoint, cl_info_entrypoint},
std::{
ffi::{c_int, c_void},
mem,
sync::Arc,
},
};
#[cl_info_entrypoint(clGetSemaphoreInfoKHR)]
unsafe impl CLInfo<cl_semaphore_info_khr> for cl_semaphore_khr {
fn query(&self, q: cl_semaphore_info_khr, v: CLInfoValue) -> CLResult<CLInfoRes> {
let sema = Semaphore::ref_from_raw(*self)?;
match q {
CL_SEMAPHORE_CONTEXT_KHR => {
v.write::<cl_context>(cl_context::from_ptr(Arc::as_ptr(&sema.ctx)))
}
CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR => {
v.write::<&[cl_device_id]>(&[cl_device_id::from_ptr(sema.dev)])
}
CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR => {
// In reality it's a list, but we are not supporting more than one anyway, so Option
// is fine here.
v.write::<Option<cl_external_semaphore_handle_type_khr>>(None)
}
// Exporting a semaphore handle from a semaphore that was created by importing an
// external semaphore handle is not permitted.
CL_SEMAPHORE_EXPORTABLE_KHR => {
v.write::<cl_bool>((!sema.imported && sema.handle_type.is_some()).into())
}
CL_SEMAPHORE_PAYLOAD_KHR => {
v.write::<cl_semaphore_payload_khr>(sema.is_signalled().into())
}
CL_SEMAPHORE_PROPERTIES_KHR => {
v.write::<&MultiValProperties<cl_semaphore_properties_khr>>(&sema.props)
}
CL_SEMAPHORE_REFERENCE_COUNT_KHR => v.write::<cl_uint>(Semaphore::refcnt(*self)?),
CL_SEMAPHORE_TYPE_KHR => v.write::<cl_semaphore_type_khr>(CL_SEMAPHORE_TYPE_BINARY_KHR),
_ => Err(CL_INVALID_VALUE),
}
}
}
#[cl_entrypoint(clCreateSemaphoreWithPropertiesKHR)]
fn create_semaphore(
context: cl_context,
sema_props: *const cl_semaphore_properties_khr,
) -> CLResult<cl_semaphore_khr> {
let context = Context::arc_from_raw(context)?;
// CL_INVALID_VALUE if sema_props is NULL
if sema_props.is_null() {
return Err(CL_INVALID_VALUE);
}
let mut handle = None;
let mut sema_type = 0;
let mut dev = None;
let mut handle_type = None;
let sema_props = unsafe {
MultiValProperties::new(
sema_props,
&[
CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR.into(),
CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR.into(),
],
)
// CL_INVALID_PROPERTY [..] if the same property name is specified more than once.
.ok_or(CL_INVALID_PROPERTY)?
};
for (key, vals) in sema_props.iter() {
// CL_INVALID_PROPERTY if a property name in sema_props is not a supported property name
match u32::try_from(key).or(Err(CL_INVALID_PROPERTY))? {
CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR => {
// CL_INVALID_DEVICE if CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR is specified as part of
// sema_props, but it does not identify exactly one valid device;
let Some((&dev_in, &[])) = vals.split_first() else {
return Err(CL_INVALID_DEVICE);
};
let dev_in = Device::ref_from_raw(dev_in as _)?
.to_static()
.ok_or(CL_INVALID_DEVICE)?;
// CL_INVALID_DEVICE [..] if a device identified by
// CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR is not one of the devices within context.
if !context.devs.contains(&dev_in) {
return Err(CL_INVALID_DEVICE);
}
dev = Some(dev_in);
}
CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR => {
if let Some((&handle_type_in, remaining)) = vals.split_first() {
// CL_INVALID_VALUE if more than one semaphore handle type is specified in the
// CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR list.
if !remaining.is_empty() {
return Err(CL_INVALID_VALUE);
}
let handle_type_in = handle_type_in.try_into_with_err(CL_INVALID_PROPERTY)?;
if handle_type_in != CL_SEMAPHORE_HANDLE_SYNC_FD_KHR {
return Err(CL_INVALID_PROPERTY);
}
handle_type = Some(handle_type_in);
}
}
CL_SEMAPHORE_HANDLE_SYNC_FD_KHR => {
handle = Some(SemaphoreHandle::SyncFD(
// we cast to a signed int to be able to handle negative FDs such as -1.
//
// CL_INVALID_PROPERTY [..] if the value specified for a supported property name
// is not valid
(vals[0] as i64).try_into_with_err(CL_INVALID_PROPERTY)?,
));
}
CL_SEMAPHORE_TYPE_KHR => {
// CL_INVALID_PROPERTY [..] if the value specified for a supported property name is
// not valid
sema_type = vals[0].try_into_with_err(CL_INVALID_PROPERTY)?;
if sema_type != CL_SEMAPHORE_TYPE_BINARY_KHR {
return Err(CL_INVALID_PROPERTY);
}
}
// CL_INVALID_PROPERTY if a property name in sema_props is not a supported property name
_ => return Err(CL_INVALID_PROPERTY),
}
}
let dev = match dev {
Some(dev) => dev,
None => {
// CL_INVALID_PROPERTY [..] Additionally, if context is a multiple device context and
// sema_props does not specify CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR.
let Some((dev_from_ctx, &[])) = context.devs.split_first() else {
return Err(CL_INVALID_PROPERTY);
};
// If CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR is not specified as part of sema_props, the
// semaphore object created by clCreateSemaphoreWithPropertiesKHR is by default
// associated with all devices in the context.
dev_from_ctx
}
};
// CL_INVALID_VALUE [..] if sema_props do not specify <property, value> pairs for minimum set of
// properties (i.e. CL_SEMAPHORE_TYPE_KHR) required for successful creation of a
// cl_semaphore_khr
if sema_type == 0 {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_OPERATION If props_list specifies a cl_external_semaphore_handle_type_khr followed
// by a handle as well as CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR. Exporting a semaphore handle
// from a semaphore that was created by importing an external semaphore handle is not permitted.
if handle_type.is_some() && handle.is_some() {
return Err(CL_INVALID_OPERATION);
}
Ok(Semaphore::new(context, sema_props, dev, handle_type, handle)?.into_cl())
// CL_INVALID_DEVICE if one or more devices identified by properties CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR cannot import the requested external semaphore handle type.
}
#[cl_entrypoint(clEnqueueSignalSemaphoresKHR)]
fn enqueue_signal_semaphores(
command_queue: cl_command_queue,
num_sema_objects: cl_uint,
sema_objects: *const cl_semaphore_khr,
_sema_payload_list: *const cl_semaphore_payload_khr,
num_events_in_wait_list: cl_uint,
event_wait_list: *const cl_event,
event: *mut cl_event,
) -> CLResult<()> {
let q = Queue::arc_from_raw(command_queue)?;
let evs = event_list_from_cl(&q, num_events_in_wait_list, event_wait_list)?;
// CL_INVALID_VALUE if num_sema_objects is 0.
if num_sema_objects == 0 {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_SEMAPHORE_KHR if any of the semaphore objects specified by sema_objects is not
// valid.
let semas = Semaphore::arcs_from_arr(sema_objects, num_sema_objects)?;
// CL_INVALID_CONTEXT if the context associated with command_queue and any of the semaphore
// objects in sema_objects are not the same
if semas.iter().any(|sema| sema.ctx != q.context) {
return Err(CL_INVALID_CONTEXT);
}
// CL_INVALID_COMMAND_QUEUE [..] if the device associated with command_queue is not same as one
// of the devices specified by CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR at the time of creating one
// or more of sema_objects, or if one or more of sema_objects belong to a context that does not
// contain a device associated with command_queue.
for sema in &semas {
if q.device != sema.dev || !sema.ctx.devs.contains(&q.device) {
return Err(CL_INVALID_COMMAND_QUEUE);
}
}
let work = Semaphore::gpu_signal(semas, &q);
create_and_queue(q, CL_COMMAND_SEMAPHORE_SIGNAL_KHR, evs, event, false, work)
// CL_INVALID_VALUE if any of the semaphore objects specified by sema_objects requires a semaphore payload and sema_payload_list is NULL.
}
#[cl_entrypoint(clEnqueueWaitSemaphoresKHR)]
fn enqueue_wait_semaphores(
command_queue: cl_command_queue,
num_sema_objects: cl_uint,
sema_objects: *const cl_semaphore_khr,
_sema_payload_list: *const cl_semaphore_payload_khr,
num_events_in_wait_list: cl_uint,
event_wait_list: *const cl_event,
event: *mut cl_event,
) -> CLResult<()> {
let q = Queue::arc_from_raw(command_queue)?;
let evs = event_list_from_cl(&q, num_events_in_wait_list, event_wait_list)?;
// CL_INVALID_VALUE if num_sema_objects is 0.
if num_sema_objects == 0 {
return Err(CL_INVALID_VALUE);
}
// CL_INVALID_SEMAPHORE_KHR if any of the semaphore objects specified by sema_objects is not
// valid.
let semas = Semaphore::arcs_from_arr(sema_objects, num_sema_objects)?;
// CL_INVALID_CONTEXT if the context associated with command_queue and any of the semaphore
// objects in sema_objects are not the same
if semas.iter().any(|sema| sema.ctx != q.context) {
return Err(CL_INVALID_CONTEXT);
}
// CL_INVALID_COMMAND_QUEUE [..] if the device associated with command_queue is not same as one
// of the devices specified by CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR at the time of creating one
// or more of sema_objects, or if one or more of sema_objects belong to a context that does not
// contain a device associated with command_queue.
for sema in &semas {
if q.device != sema.dev || !sema.ctx.devs.contains(&q.device) {
return Err(CL_INVALID_COMMAND_QUEUE);
}
}
create_and_queue(
q,
CL_COMMAND_SEMAPHORE_WAIT_KHR,
evs,
event,
false,
Box::new(|_, ctx| {
for sema in semas {
sema.gpu_wait(ctx)?;
}
Ok(())
}),
)
// CL_INVALID_VALUE if any of the semaphore objects specified by sema_objects requires a semaphore payload and sema_payload_list is NULL.
}
#[cl_entrypoint(clGetSemaphoreHandleForTypeKHR)]
fn get_semaphore_handle_for_type(
sema_object: cl_semaphore_khr,
device: cl_device_id,
handle_type: cl_external_semaphore_handle_type_khr,
handle_size: usize,
handle_ptr: *mut c_void,
handle_size_ret: *mut usize,
) -> CLResult<()> {
let sema = Semaphore::ref_from_raw(sema_object)?;
let dev = Device::ref_from_raw(device)?;
// CL_INVALID_DEVICE [..] if sema_object belongs to a context that is not associated with
// device.
if !sema.ctx.devs.contains(&dev) {
return Err(CL_INVALID_DEVICE);
}
// CL_INVALID_VALUE if the requested external semaphore handle type was not specified when
// sema_object was created.
if sema.handle_type != Some(handle_type) {
return Err(CL_INVALID_VALUE);
}
unsafe { handle_size_ret.write_checked(mem::size_of::<c_int>()) };
if !handle_ptr.is_null() {
// CL_INVALID_VALUE if the size in bytes specified by handle_size is less than size of the
// requested handle and handle_ptr is not NULL.
if handle_size < mem::size_of::<c_int>() {
return Err(CL_INVALID_VALUE);
}
let fd = sema.fd()?;
unsafe {
handle_ptr.cast::<c_int>().write(fd);
}
}
Ok(())
}
#[cl_entrypoint(clReleaseSemaphoreKHR)]
fn release_semaphore(sema_object: cl_semaphore_khr) -> CLResult<()> {
Semaphore::release(sema_object)
}
#[cl_entrypoint(clRetainSemaphoreKHR)]
fn retain_semaphore(sema_object: cl_semaphore_khr) -> CLResult<()> {
Semaphore::retain(sema_object)
}
#[cl_entrypoint(clReImportSemaphoreSyncFdKHR)]
fn re_import_semaphore_sync_fd(
sema_object: cl_semaphore_khr,
reimport_props: *mut cl_semaphore_reimport_properties_khr,
fd: c_int,
) -> CLResult<()> {
let sema = Semaphore::ref_from_raw(sema_object)?;
// CL_INVALID_SEMAPHORE_KHR if a CL_SEMAPHORE_HANDLE_SYNC_FD_KHR handle was not imported when
// sema_object was created.
if !sema.imported {
return Err(CL_INVALID_SEMAPHORE_KHR);
}
// reimport_props is an optional list of properties that affect the re-import behavior. [..] If
// no properties are required, reimport_props may be NULL. This extension does not define any
// optional properties.
// SAFETY: The list is terminated with the special property 0.
let props = unsafe { Properties::new(reimport_props) }.ok_or(CL_INVALID_PROPERTY)?;
if !props.is_empty() {
// We don't support any optional properties, so just throw an error for now.
return Err(CL_INVALID_PROPERTY);
}
// CL_INVALID_VALUE if fd is invalid.
sema.reimport(fd)
}
+399
View File
@@ -0,0 +1,399 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
use crate::api::icd::CLResult;
use crate::api::icd::ReferenceCountedAPIPointer;
use crate::core::context::Context;
use crate::core::event::Event;
use crate::core::memory::MemBase;
use crate::core::program::Program;
use crate::core::queue::Queue;
use mesa_rust_util::conversion::*;
use rusticl_opencl_gen::*;
use std::borrow::Borrow;
use std::ffi::c_void;
use std::ffi::CStr;
use std::iter::Product;
macro_rules! cl_callback {
($cb:ident($fn_alias:ident) {
$($p:ident : $ty:ty,)*
}) => {
pub type $fn_alias = unsafe extern "C" fn(
$($p: $ty,)*
);
// INVARIANT:
// All safety requirements on `func` and `data` documented on `$cb::new` are invariants.
#[allow(dead_code)]
pub struct $cb {
func: $fn_alias,
data: *mut c_void,
}
#[allow(dead_code)]
impl $cb {
/// Creates a new `$cb`. Returns `Err(CL_INVALID_VALUE)` if `func` is `None`.
///
/// # SAFETY:
///
/// If `func` is `None`, there are no safety requirements. Otherwise:
///
/// - `func` must be a thread-safe fn.
/// - Passing `data` as the last parameter to `func` must not cause unsoundness.
/// - CreateContextCB: `func` must be soundly callable as documented on
/// [`clCreateContext`] in the OpenCL specification.
/// - DeleteContextCB: `func` must be soundly callable as documented on
/// [`clSetContextDestructorCallback`] in the OpenCL specification.
/// - EventCB: `func` must be soundly callable as documented on
/// [`clSetEventCallback`] in the OpenCL specification.
/// - MemCB: `func` must be soundly callable as documented on
/// [`clSetMemObjectDestructorCallback`] in the OpenCL specification.
/// - ProgramCB: `func` must be soundly callable as documented on
/// [`clBuildProgram`] in the OpenCL specification.
/// - SVMFreeCb: `func` must be soundly callable as documented on
/// [`clEnqueueSVMFree`] in the OpenCL specification.
///
/// [`clCreateContext`]: https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_API.html#clCreateContext
/// [`clSetContextDestructorCallback`]: https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_API.html#clSetContextDestructorCallback
/// [`clSetEventCallback`]: https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_API.html#clSetEventCallback
/// [`clSetMemObjectDestructorCallback`]: https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_API.html#clSetMemObjectDestructorCallback
/// [`clBuildProgram`]: https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_API.html#clBuildProgram
/// [`clEnqueueSVMFree`]: https://registry.khronos.org/OpenCL/specs/3.0-unified/html/OpenCL_API.html#clEnqueueSVMFree
pub unsafe fn new(func: Option<$fn_alias>, data: *mut c_void) -> CLResult<Self> {
let Some(func) = func else {
return Err(CL_INVALID_VALUE);
};
Ok(Self { func, data })
}
/// Creates a new Option(`$cb`). Returns:
/// - `Ok(Some($cb)) if `func` is `Some(_)`.
/// - `Ok(None)` if `func` is `None` and `data` is `null`.
/// - `Err(CL_INVALID_VALUE)` if `func` is `None` and `data` is not `null`.
///
/// # SAFETY:
///
/// The safety requirements are identical to those of [`new`].
pub unsafe fn try_new(func: Option<$fn_alias>, data: *mut c_void) -> CLResult<Option<Self>> {
let Some(func) = func else {
return if data.is_null() {
Ok(None)
} else {
Err(CL_INVALID_VALUE)
};
};
Ok(Some(Self { func, data }))
}
}
unsafe impl Send for $cb {}
unsafe impl Sync for $cb {}
}
}
cl_callback!(
CreateContextCB(FuncCreateContextCB) {
errinfo: *const ::std::os::raw::c_char,
private_info: *const c_void,
cb: usize,
user_data: *mut c_void,
}
);
impl CreateContextCB {
pub fn _call(self, err_msg: &CStr, private_info: &[u8]) {
let err_msg_ptr = err_msg.as_ptr();
let private_info_ptr = private_info.as_ptr().cast::<c_void>();
// SAFETY: The first parameter must be a valid pointer to a NUL-terminated C string. We
// know this is satisfied since that is `CStr`'s type invariant.
// The second parameter must be a valid pointer to binary data with the length given in the
// thrid parameter. We know both of these are correct since we just got them from a byte slice.
// All other requirements are covered by this callback's type invariants.
unsafe { (self.func)(err_msg_ptr, private_info_ptr, private_info.len(), self.data) };
}
}
cl_callback!(
DeleteContextCB(FuncDeleteContextCB) {
context: cl_context,
user_data: *mut c_void,
}
);
impl DeleteContextCB {
pub fn call(self, ctx: &Context) {
let cl = cl_context::from_ptr(ctx);
// SAFETY: `cl` must have pointed to an OpenCL context, which is where we just got it from.
// All other requirements are covered by this callback's type invariants.
unsafe { (self.func)(cl, self.data) };
}
}
cl_callback!(
EventCB(FuncEventCB) {
event: cl_event,
event_command_status: cl_int,
user_data: *mut c_void,
}
);
impl EventCB {
pub fn call(self, event: &Event, status: cl_int) {
let cl = cl_event::from_ptr(event);
// SAFETY: `cl` must be a valid pointer to an OpenCL event, which is where we just got it from.
// All other requirements are covered by this callback's type invariants.
unsafe { (self.func)(cl, status, self.data) };
}
}
cl_callback!(
MemCB(FuncMemCB) {
memobj: cl_mem,
user_data: *mut c_void,
}
);
impl MemCB {
pub fn call(self, mem: &MemBase) {
let cl = cl_mem::from_ptr(mem);
// SAFETY: `cl` must have pointed to an OpenCL context, which is where we just got it from.
// All other requirements are covered by this callback's type invariants.
unsafe { (self.func)(cl, self.data) };
}
}
cl_callback!(
ProgramCB(FuncProgramCB) {
program: cl_program,
user_data: *mut c_void,
}
);
impl ProgramCB {
pub fn call(self, program: &Program) {
let cl = cl_program::from_ptr(program);
// SAFETY: `cl` must have pointed to an OpenCL program, which is where we just got it from.
// All other requirements are covered by this callback's type invariants.
unsafe { (self.func)(cl, self.data) };
}
}
cl_callback!(
SVMFreeCb(FuncSVMFreeCb) {
queue: cl_command_queue,
num_svm_pointers: cl_uint,
svm_pointers: *mut *mut c_void,
user_data: *mut c_void,
}
);
impl SVMFreeCb {
pub fn call(self, queue: &Queue, svm_pointers: &mut [usize]) {
// `clEnqueueSVMFree()` takes the length of the `svm_pointers` list as a
// `cl_uint`, so this will only occur in cases of implementation error.
debug_assert!(
svm_pointers.len() <= cl_uint::MAX as usize,
"svm_pointers count must not exceed `cl_uint::MAX`"
);
let (num_svm_pointers, svm_pointers) = if !svm_pointers.is_empty() {
(
svm_pointers.len() as cl_uint,
svm_pointers.as_mut_ptr().cast(),
)
} else {
// The specification requires that an empty `svm_pointers` list be
// null when passed to enqueue and callbacks may expect it to be
// passed back in the same manner.
(0, std::ptr::null_mut())
};
let cl = cl_command_queue::from_ptr(queue);
// SAFETY: `cl` must be a valid pointer to an OpenCL queue, which is where we just got it from.
// All other requirements are covered by this callback's type invariants.
unsafe { (self.func)(cl, num_svm_pointers, svm_pointers, self.data) };
}
}
// a lot of APIs use 3 component vectors passed as C arrays
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct CLVec<T> {
vals: [T; 3],
}
impl<T: Copy> CLVec<T> {
pub fn new(vals: [T; 3]) -> Self {
Self { vals: vals }
}
/// # Safety
///
/// This function is intended for use around OpenCL vectors of size 3.
/// Most commonly for `origin` and `region` API arguments.
///
/// Using it for anything else is undefined.
pub unsafe fn from_raw(v: *const T) -> Self {
Self {
vals: unsafe { *v.cast() },
}
}
pub fn pixels<'a>(&'a self) -> T
where
T: Product<&'a T>,
{
self.vals.iter().product()
}
}
impl CLVec<usize> {
/// returns the offset of point in linear memory.
pub fn calc_offset<T: Borrow<Self>>(point: T, pitch: [usize; 3]) -> usize {
*point.borrow() * pitch
}
/// returns the scalar size of the described region in linear memory.
pub fn calc_size<T: Borrow<Self>>(region: T, pitch: [usize; 3]) -> usize {
(*region.borrow() - [0, 1, 1]) * pitch
}
pub fn calc_offset_size<T1: Borrow<Self>, T2: Borrow<Self>>(
base: T1,
region: T2,
pitch: [usize; 3],
) -> (usize, usize) {
(
Self::calc_offset(base, pitch),
Self::calc_size(region, pitch),
)
}
}
impl<T: Default + Copy> Default for CLVec<T> {
fn default() -> Self {
Self {
vals: [T::default(); 3],
}
}
}
// provides a ton of functions
impl<T> std::ops::Deref for CLVec<T> {
type Target = [T; 3];
fn deref(&self) -> &Self::Target {
&self.vals
}
}
impl<T> std::ops::DerefMut for CLVec<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.vals
}
}
impl<T: Copy + std::ops::Add<Output = T>> std::ops::Add for CLVec<T> {
type Output = Self;
fn add(self, other: Self) -> Self {
self + other.vals
}
}
impl<T: Copy + std::ops::Add<Output = T>> std::ops::Add<[T; 3]> for CLVec<T> {
type Output = Self;
fn add(self, other: [T; 3]) -> Self {
Self {
vals: [self[0] + other[0], self[1] + other[1], self[2] + other[2]],
}
}
}
impl<T: Copy + std::ops::Sub<Output = T>> std::ops::Sub<[T; 3]> for CLVec<T> {
type Output = Self;
fn sub(self, other: [T; 3]) -> Self {
Self {
vals: [self[0] - other[0], self[1] - other[1], self[2] - other[2]],
}
}
}
impl<T> std::ops::Mul for CLVec<T>
where
T: Copy + std::ops::Mul<Output = T> + std::ops::Add<Output = T>,
{
type Output = T;
fn mul(self, other: Self) -> T {
self * other.vals
}
}
impl<T> std::ops::Mul<[T; 3]> for CLVec<T>
where
T: Copy + std::ops::Mul<Output = T> + std::ops::Add<Output = T>,
{
type Output = T;
fn mul(self, other: [T; 3]) -> T {
self[0] * other[0] + self[1] * other[1] + self[2] * other[2]
}
}
impl<S, T> TryFrom<CLVec<S>> for [T; 3]
where
S: Copy,
T: TryFrom<S>,
{
type Error = cl_int;
fn try_from(value: CLVec<S>) -> Result<Self, Self::Error> {
// This is ugly, but the alternative seems to be collecting to a `Vec`
// (which allocates) and then (fallibly) converting that to an array.
// Since our array is small, we can do it by hand. Replace with
// `<[S; 3]>::try_map()` once stabilized.
// See https://github.com/rust-lang/rust/issues/79711
Ok([
T::try_from_with_err(value.vals[0], CL_OUT_OF_HOST_MEMORY)?,
T::try_from_with_err(value.vals[1], CL_OUT_OF_HOST_MEMORY)?,
T::try_from_with_err(value.vals[2], CL_OUT_OF_HOST_MEMORY)?,
])
}
}
impl<T> From<[T; 3]> for CLVec<T>
where
T: Copy,
{
fn from(arr: [T; 3]) -> Self {
Self::new(arr)
}
}
#[allow(non_snake_case)]
pub mod IdpAccelProps {
use rusticl_opencl_gen::cl_bool;
use rusticl_opencl_gen::cl_device_integer_dot_product_acceleration_properties;
pub fn new(
signed_accelerated: cl_bool,
unsigned_accelerated: cl_bool,
mixed_signedness_accelerated: cl_bool,
accumulating_saturating_signed_accelerated: cl_bool,
accumulating_saturating_unsigned_accelerated: cl_bool,
accumulating_saturating_mixed_signedness_accelerated: cl_bool,
) -> cl_device_integer_dot_product_acceleration_properties {
cl_device_integer_dot_product_acceleration_properties {
signed_accelerated,
unsigned_accelerated,
mixed_signedness_accelerated,
accumulating_saturating_signed_accelerated,
accumulating_saturating_unsigned_accelerated,
accumulating_saturating_mixed_signedness_accelerated,
}
}
}
+649
View File
@@ -0,0 +1,649 @@
// Copyright 2020 Red Hat.
// SPDX-License-Identifier: MIT
use crate::api::icd::{ArcedCLObject, CLResult};
use crate::api::types::*;
use crate::core::event::*;
use crate::core::queue::*;
use mesa_rust_util::properties::{MultiValProperties, Properties};
use rusticl_opencl_gen::*;
use std::cmp;
use std::convert::TryInto;
use std::ffi::{c_void, CStr};
use std::iter::zip;
use std::mem::MaybeUninit;
use std::ops::BitAnd;
use std::sync::Arc;
// TODO: use MaybeUninit::copy_from_slice once stable
pub fn maybe_uninit_copy_from_slice<T>(this: &mut [MaybeUninit<T>], src: &[T])
where
T: Copy,
{
// Assert this so we stick as close as possible to MaybeUninit::copy_from_slices behavior.
debug_assert_eq!(this.len(), src.len());
for (dest, val) in zip(this, src) {
*dest = MaybeUninit::new(*val);
}
}
// TODO: use MaybeUninit::slice_assume_init_ref once stable
pub const unsafe fn slice_assume_init_ref<T>(slice: &[MaybeUninit<T>]) -> &[T] {
// SAFETY: casting `slice` to a `*const [T]` is safe since the caller guarantees that
// `slice` is initialized, and `MaybeUninit` is guaranteed to have the same layout as `T`.
// The pointer obtained is valid since it refers to memory owned by `slice` which is a
// reference and thus guaranteed to be valid for reads.
unsafe { &*(slice as *const [_] as *const [T]) }
}
/// Token to make sure that ClInfoValue::write is called
pub struct CLInfoRes {
_private: (),
}
/// A helper class to simplify implementing so called "info" APIs in OpenCL, e.g. `clGetDeviceInfo`.
///
/// Those APIs generally operate on opaque memory buffers and needs to be interpreted according to
/// the specific query being made. The generic parameter to the input and write functions should
/// always be explicitly specified.
pub struct CLInfoValue<'a> {
param_value: Option<&'a mut [MaybeUninit<u8>]>,
param_value_size_ret: Option<&'a mut MaybeUninit<usize>>,
}
impl CLInfoValue<'_> {
/// # Safety
/// `param_value` and `param_value_size_ret` need to be valid memory allocations or null.
/// If `param_value` is not null it needs to point to an allocation of at least
/// `param_value_size` bytes.
unsafe fn new(
param_value_size: usize,
param_value: *mut c_void,
param_value_size_ret: *mut usize,
) -> CLResult<Self> {
let param_value_size_ret: *mut MaybeUninit<usize> = param_value_size_ret.cast();
let param_value = if !param_value.is_null() {
Some(unsafe { cl_slice::from_raw_parts_mut(param_value.cast(), param_value_size)? })
} else {
None
};
Ok(Self {
param_value: param_value,
param_value_size_ret: unsafe { param_value_size_ret.as_mut() },
})
}
fn finish() -> CLInfoRes {
CLInfoRes { _private: () }
}
/// Used to read from the application provided data.
pub fn input<T>(&self) -> CLResult<&[MaybeUninit<T>]> {
if let Some(param_value) = &self.param_value {
let count = param_value.len() / size_of::<T>();
unsafe { cl_slice::from_raw_parts(param_value.as_ptr().cast(), count) }
} else {
Ok(&[])
}
}
/// Writes the passed in value according to the generic. It is important to pass in the same
/// or compatible type as stated in the OpenCL specification.
///
/// It also verifies that if a buffer was provided by the application it's big enough to hold
/// `t` and returns `Err(CL_INVALID_VALUE)` otherwise.
///
/// Type specific details:
/// - Compatible with C arrays are `T` (if only one element is to be returned), `Vec<T>` or `&[T]`
/// types.
/// - Compatible with C strings are all basic Rust string types.
/// - `bool`s are automatically converted to `cl_bool`.
/// - For queries which can return no data, `Option<T>` can be used.
/// - For C property arrays (0-terminated arrays of `T`) the
/// [mesa_rust_util::properties::Properties] type can be used.
///
/// All types implementing [CLProp] are supported.
pub fn write<T: CLProp>(self, t: T) -> CLResult<CLInfoRes> {
let count = t.count();
let bytes = count * size_of::<T::Output>();
// param_value is a pointer to memory where the appropriate result being queried is
// returned. If param_value is NULL, it is ignored.
if let Some(param_value) = self.param_value {
// CL_INVALID_VALUE [...] if size in bytes specified by param_value_size is < size of
// return type as specified in the Context Attributes table and param_value is not a
// NULL value.
if param_value.len() < bytes {
return Err(CL_INVALID_VALUE);
}
// SAFETY: Casting between types wrapped with MaybeUninit is fine, because it's up to
// the caller to decide if it sound to read from it. Also the count passed in is
// just the type adjusted size of the `param_value` slice.
let out =
unsafe { cl_slice::from_raw_parts_mut(param_value.as_mut_ptr().cast(), count)? };
t.write_to(out);
}
// param_value_size_ret returns the actual size in bytes of data being queried by
// param_name. If param_value_size_ret is NULL, it is ignored.
if let Some(param_value_size_ret) = self.param_value_size_ret {
param_value_size_ret.write(bytes);
}
Ok(Self::finish())
}
/// Returns the size of the buffer to the application it needs to provide in order to
/// successfully execute the query.
///
/// Some queries simply provide pointers where the implementation needs to write data to, e.g.
/// `CL_PROGRAM_BINARIES`. In that case it's meaningless to write back the same pointers. This
/// function can be used to skip those writes.
pub fn write_len_only<T: CLProp>(self, len: usize) -> CLResult<CLInfoRes> {
let bytes = len * size_of::<T::Output>();
// param_value_size_ret returns the actual size in bytes of data being queried by
// param_name. If param_value_size_ret is NULL, it is ignored.
if let Some(param_value_size_ret) = self.param_value_size_ret {
param_value_size_ret.write(bytes);
}
Ok(Self::finish())
}
/// Similar to `write` with the only difference that instead of a precomputed value, this can
/// take an iterator instead.
///
/// This is useful when the data to be returned isn't cheap to compute or not already available.
///
/// When the application only asks for the size of the result, the iterator isn't advanced at
/// all, only its length is used.
pub fn write_iter<T: CLProp<Output = T> + Copy>(
self,
iter: impl ExactSizeIterator<Item = T>,
) -> CLResult<CLInfoRes> {
let count = iter.len();
let bytes = count * size_of::<T::Output>();
// param_value is a pointer to memory where the appropriate result being queried is
// returned. If param_value is NULL, it is ignored.
if let Some(param_value) = self.param_value {
// CL_INVALID_VALUE [...] if size in bytes specified by param_value_size is < size of
// return type as specified in the Context Attributes table and param_value is not a
// NULL value.
if param_value.len() < bytes {
return Err(CL_INVALID_VALUE);
}
let out =
unsafe { cl_slice::from_raw_parts_mut(param_value.as_mut_ptr().cast(), count)? };
for (item, out) in zip(iter, out.iter_mut()) {
*out = item;
}
}
// param_value_size_ret returns the actual size in bytes of data being queried by
// param_name. If param_value_size_ret is NULL, it is ignored.
if let Some(param_value_size_ret) = self.param_value_size_ret {
param_value_size_ret.write(bytes);
}
Ok(Self::finish())
}
}
/// # Safety
///
/// This trait helps implementing various OpenCL Query APIs, however care have to be taken that the
/// `query` implementation implements the corresponding query according to the OpenCL specification.
///
/// Queries which don't have a size known at compile time are expected to be called twice:
/// 1. To ask the implementation of how much data will be returned. The application uses this
/// to allocate enough memory to be passed into the next call.
/// 2. To actually execute the query.
///
/// This trait abstracts this pattern properly away to make it easier to implement it.
///
/// [CLInfoValue] contains helper functions to read and write data behind opaque buffers, the
/// applications using those OpenCL queries expect the implementation to behave accordingly.
///
/// It is advised to explicitly specify the types of [CLInfoValue::input] and the various write
/// helpers.
pub unsafe trait CLInfo<I> {
fn query(&self, q: I, v: CLInfoValue) -> CLResult<CLInfoRes>;
/// # Safety
///
/// Same requirements from [CLInfoValue::new] apply.
unsafe fn get_info(
&self,
param_name: I,
param_value_size: usize,
param_value: *mut ::std::os::raw::c_void,
param_value_size_ret: *mut usize,
) -> CLResult<()> {
// SAFETY: It's up to the caller as this function is marked unsafe.
let value =
unsafe { CLInfoValue::new(param_value_size, param_value, param_value_size_ret)? };
self.query(param_name, value)?;
Ok(())
}
}
/// # Safety
///
/// See [CLInfo]
pub unsafe trait CLInfoObj<I, O> {
fn query(&self, o: O, q: I, v: CLInfoValue) -> CLResult<CLInfoRes>;
/// # Safety
///
/// Same requirements from [CLInfoValue::new] apply.
unsafe fn get_info_obj(
&self,
obj: O,
param_name: I,
param_value_size: usize,
param_value: *mut ::std::os::raw::c_void,
param_value_size_ret: *mut usize,
) -> CLResult<()> {
// SAFETY: It's up to the caller as this function is marked unsafe.
let value =
unsafe { CLInfoValue::new(param_value_size, param_value, param_value_size_ret)? };
self.query(obj, param_name, value)?;
Ok(())
}
}
/// Trait to be implemented for the [CLInfo] and [CLInfoObj].
pub trait CLProp {
type Output: CLProp + Copy;
/// Returns the amount of `Self::Output` returned.
fn count(&self) -> usize;
/// Called to write the value into the `out` buffer.
fn write_to(&self, out: &mut [MaybeUninit<Self::Output>]);
}
macro_rules! cl_prop_for_type {
($ty: ty) => {
impl CLProp for $ty {
type Output = Self;
fn count(&self) -> usize {
1
}
fn write_to(&self, out: &mut [MaybeUninit<Self>]) {
out[0].write(*self);
}
}
};
}
cl_prop_for_type!(cl_char);
cl_prop_for_type!(cl_uchar);
cl_prop_for_type!(cl_ushort);
cl_prop_for_type!(cl_int);
cl_prop_for_type!(cl_uint);
cl_prop_for_type!(cl_ulong);
cl_prop_for_type!(isize);
cl_prop_for_type!(usize);
cl_prop_for_type!(cl_device_integer_dot_product_acceleration_properties);
cl_prop_for_type!(cl_device_pci_bus_info_khr);
cl_prop_for_type!(cl_image_format);
cl_prop_for_type!(cl_name_version);
impl CLProp for bool {
type Output = cl_bool;
fn count(&self) -> usize {
1
}
fn write_to(&self, out: &mut [MaybeUninit<cl_bool>]) {
if *self { CL_TRUE } else { CL_FALSE }.write_to(out);
}
}
impl CLProp for &str {
type Output = u8;
fn count(&self) -> usize {
// we need one additional byte for the nul terminator
self.len() + 1
}
fn write_to(&self, out: &mut [MaybeUninit<u8>]) {
let bytes = self.as_bytes();
maybe_uninit_copy_from_slice(&mut out[0..bytes.len()], bytes);
out[bytes.len()].write(b'\0');
}
}
impl CLProp for &CStr {
type Output = u8;
fn count(&self) -> usize {
self.to_bytes_with_nul().len()
}
fn write_to(&self, out: &mut [MaybeUninit<u8>]) {
self.to_bytes_with_nul().write_to(out);
}
}
impl<T> CLProp for &[T]
where
T: CLProp + Copy,
{
type Output = T;
fn count(&self) -> usize {
self.len()
}
fn write_to(&self, out: &mut [MaybeUninit<T>]) {
maybe_uninit_copy_from_slice(&mut out[0..self.len()], self);
}
}
impl<T, const I: usize> CLProp for [T; I]
where
T: CLProp + Copy,
{
type Output = Self;
fn count(&self) -> usize {
1
}
fn write_to(&self, out: &mut [MaybeUninit<Self>]) {
out[0].write(*self);
}
}
impl<T> CLProp for *mut T {
type Output = Self;
fn count(&self) -> usize {
1
}
fn write_to(&self, out: &mut [MaybeUninit<Self>]) {
out[0].write(*self);
}
}
impl<T> CLProp for *const T {
type Output = Self;
fn count(&self) -> usize {
1
}
fn write_to(&self, out: &mut [MaybeUninit<Self>]) {
out[0].write(*self);
}
}
impl<T> CLProp for &Properties<T>
where
T: CLProp + Copy + Default,
{
type Output = T;
fn count(&self) -> usize {
self.raw_data().count()
}
fn write_to(&self, out: &mut [MaybeUninit<T>]) {
self.raw_data().write_to(out);
}
}
impl<T> CLProp for &MultiValProperties<T>
where
T: CLProp + Copy,
{
type Output = T;
fn count(&self) -> usize {
self.as_raw_slice().count()
}
fn write_to(&self, out: &mut [MaybeUninit<T>]) {
self.as_raw_slice().write_to(out);
}
}
impl<T> CLProp for Option<T>
where
T: CLProp + Copy,
{
type Output = T::Output;
fn count(&self) -> usize {
match self {
Some(val) => val.count(),
None => 0,
}
}
fn write_to(&self, out: &mut [MaybeUninit<T::Output>]) {
if let Some(val) = self {
val.write_to(out);
};
}
}
const CL_DEVICE_TYPES: u32 = CL_DEVICE_TYPE_ACCELERATOR
| CL_DEVICE_TYPE_CPU
| CL_DEVICE_TYPE_GPU
| CL_DEVICE_TYPE_CUSTOM
| CL_DEVICE_TYPE_DEFAULT;
pub fn check_cl_device_type(val: cl_device_type) -> CLResult<()> {
let v: u32 = val.try_into().or(Err(CL_INVALID_DEVICE_TYPE))?;
if v != 0 && (v == CL_DEVICE_TYPE_ALL || v & CL_DEVICE_TYPES == v) {
return Ok(());
}
Err(CL_INVALID_DEVICE_TYPE)
}
pub const CL_IMAGE_TYPES: [cl_mem_object_type; 6] = [
CL_MEM_OBJECT_IMAGE1D,
CL_MEM_OBJECT_IMAGE2D,
CL_MEM_OBJECT_IMAGE3D,
CL_MEM_OBJECT_IMAGE1D_ARRAY,
CL_MEM_OBJECT_IMAGE2D_ARRAY,
CL_MEM_OBJECT_IMAGE1D_BUFFER,
];
pub fn check_cl_bool<T: PartialEq + TryInto<cl_uint>>(val: T) -> Option<bool> {
let c: u32 = val.try_into().ok()?;
if c != CL_TRUE && c != CL_FALSE {
return None;
}
Some(c == CL_TRUE)
}
pub fn event_list_from_cl(
q: &Queue,
num_events_in_wait_list: cl_uint,
event_wait_list: *const cl_event,
) -> CLResult<Vec<Arc<Event>>> {
// CL_INVALID_EVENT_WAIT_LIST if event_wait_list is NULL and num_events_in_wait_list > 0, or
// event_wait_list is not NULL and num_events_in_wait_list is 0, or if event objects in
// event_wait_list are not valid events.
let res = Event::arcs_from_arr(event_wait_list, num_events_in_wait_list)
.map_err(|_| CL_INVALID_EVENT_WAIT_LIST)?;
// CL_INVALID_CONTEXT if context associated with command_queue and events in event_list are not
// the same.
if res.iter().any(|e| e.context != q.context) {
return Err(CL_INVALID_CONTEXT);
}
Ok(res)
}
pub fn checked_compare(a: usize, o: cmp::Ordering, b: u64) -> bool {
if usize::BITS > u64::BITS {
a.cmp(&(b as usize)) == o
} else {
(a as u64).cmp(&b) == o
}
}
pub fn bit_check<A: BitAnd<Output = A> + PartialEq + Default, B: Into<A>>(a: A, b: B) -> bool {
a & b.into() != A::default()
}
// Taken from "Appendix D: Checking for Memory Copy Overlap"
// src_offset and dst_offset are additions to support sub-buffers
pub fn check_copy_overlap(
src_origin: &CLVec<usize>,
src_offset: usize,
dst_origin: &CLVec<usize>,
dst_offset: usize,
region: &CLVec<usize>,
row_pitch: usize,
slice_pitch: usize,
) -> bool {
let slice_size = (region[1] - 1) * row_pitch + region[0];
let block_size = (region[2] - 1) * slice_pitch + slice_size;
let src_start =
src_origin[2] * slice_pitch + src_origin[1] * row_pitch + src_origin[0] + src_offset;
let src_end = src_start + block_size;
let dst_start =
dst_origin[2] * slice_pitch + dst_origin[1] * row_pitch + dst_origin[0] + dst_offset;
let dst_end = dst_start + block_size;
/* No overlap if dst ends before src starts or if src ends
* before dst starts.
*/
if (dst_end <= src_start) || (src_end <= dst_start) {
return false;
}
/* No overlap if region[0] for dst or src fits in the gap
* between region[0] and row_pitch.
*/
{
let src_dx = (src_origin[0] + src_offset) % row_pitch;
let dst_dx = (dst_origin[0] + dst_offset) % row_pitch;
if ((dst_dx >= src_dx + region[0]) && (dst_dx + region[0] <= src_dx + row_pitch))
|| ((src_dx >= dst_dx + region[0]) && (src_dx + region[0] <= dst_dx + row_pitch))
{
return false;
}
}
/* No overlap if region[1] for dst or src fits in the gap
* between region[1] and slice_pitch.
*/
{
let src_dy = (src_origin[1] * row_pitch + src_origin[0] + src_offset) % slice_pitch;
let dst_dy = (dst_origin[1] * row_pitch + dst_origin[0] + dst_offset) % slice_pitch;
if ((dst_dy >= src_dy + slice_size) && (dst_dy + slice_size <= src_dy + slice_pitch))
|| ((src_dy >= dst_dy + slice_size) && (src_dy + slice_size <= dst_dy + slice_pitch))
{
return false;
}
}
/* Otherwise src and dst overlap. */
true
}
pub mod cl_slice {
use crate::api::util::CLResult;
use mesa_rust_util::ptr::addr;
use rusticl_opencl_gen::CL_INVALID_VALUE;
use std::ffi::c_void;
use std::slice;
/// Wrapper around [`std::slice::from_raw_parts`] that returns `Err(CL_INVALID_VALUE)` if any of these conditions is met:
/// - `data` is null
/// - `data` is not correctly aligned for `T`
/// - `len * size_of::<T>()` is larger than `isize::MAX`
/// - `data` + `len * size_of::<T>()` wraps around the address space
///
/// # Safety
/// The behavior is undefined if any of the other requirements imposed by
/// [`std::slice::from_raw_parts`] is violated.
#[inline]
pub unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> CLResult<&'a [T]> {
if allocation_obviously_invalid(data, len) {
return Err(CL_INVALID_VALUE);
}
// SAFETY: We've checked that `data` is not null and properly aligned. We've also checked
// that the total size in bytes does not exceed `isize::MAX` and that adding that size to
// `data` does not wrap around the address space.
//
// The caller has to uphold the other safety requirements imposed by [`std::slice::from_raw_parts`].
unsafe { Ok(slice::from_raw_parts(data, len)) }
}
/// same as [self::from_raw_parts] just that `len` is provided in bytes and must be a multiple
/// of Ts size.
#[inline]
pub unsafe fn from_raw_parts_bytes_len<'a, T>(
data: *const c_void,
len: usize,
) -> CLResult<&'a [T]> {
let size = size_of::<T>();
if len % size != 0 {
return Err(CL_INVALID_VALUE);
}
let len = len / size;
unsafe { self::from_raw_parts(data.cast(), len) }
}
/// Wrapper around [`std::slice::from_raw_parts_mut`] that returns `Err(CL_INVALID_VALUE)` if any of these conditions is met:
/// - `data` is null
/// - `data` is not correctly aligned for `T`
/// - `len * size_of::<T>()` is larger than `isize::MAX`
/// - `data` + `len * size_of::<T>()` wraps around the address space
///
/// # Safety
/// The behavior is undefined if any of the other requirements imposed by
/// [`std::slice::from_raw_parts_mut`] is violated.
#[inline]
pub unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> CLResult<&'a mut [T]> {
if allocation_obviously_invalid(data, len) {
return Err(CL_INVALID_VALUE);
}
// SAFETY: We've checked that `data` is not null and properly aligned. We've also checked
// that the total size in bytes does not exceed `isize::MAX` and that adding that size to
// `data` does not wrap around the address space.
//
// The caller has to uphold the other safety requirements imposed by [`std::slice::from_raw_parts_mut`].
unsafe { Ok(slice::from_raw_parts_mut(data, len)) }
}
#[must_use]
fn allocation_obviously_invalid<T>(data: *const T, len: usize) -> bool {
let Some(total_size) = size_of::<T>().checked_mul(len) else {
return true;
};
data.is_null()
|| !data.is_aligned()
|| total_size > isize::MAX as usize
|| addr(data).checked_add(total_size).is_none()
}
}