mirror of
https://github.com/elwrcl/intel-hard-graphics.git
synced 2026-11-01 03:18:02 +03:00
init
This commit is contained in:
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(())
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user