1 use crate::api::icd::*;
2 use crate::api::types::*;
3 use crate::api::util::*;
4 use crate::core::context::*;
5 use crate::core::device::*;
6 use crate::core::platform::*;
7 use crate::core::program::*;
8
9 use mesa_rust::compiler::clc::*;
10 use mesa_rust_util::string::*;
11 use rusticl_opencl_gen::*;
12 use rusticl_proc_macros::cl_entrypoint;
13 use rusticl_proc_macros::cl_info_entrypoint;
14
15 use std::ffi::CStr;
16 use std::ffi::CString;
17 use std::iter;
18 use std::mem::MaybeUninit;
19 use std::num::NonZeroUsize;
20 use std::os::raw::c_char;
21 use std::ptr;
22 use std::slice;
23 use std::sync::Arc;
24
25 #[cl_info_entrypoint(clGetProgramInfo)]
26 impl CLInfo<cl_program_info> for cl_program {
query(&self, q: cl_program_info, vals: &[u8]) -> CLResult<Vec<MaybeUninit<u8>>>27 fn query(&self, q: cl_program_info, vals: &[u8]) -> CLResult<Vec<MaybeUninit<u8>>> {
28 let prog = Program::ref_from_raw(*self)?;
29 Ok(match q {
30 CL_PROGRAM_BINARIES => cl_prop::<Vec<*mut u8>>(prog.binaries(vals)?),
31 CL_PROGRAM_BINARY_SIZES => cl_prop::<Vec<usize>>(prog.bin_sizes()),
32 CL_PROGRAM_CONTEXT => {
33 // Note we use as_ptr here which doesn't increase the reference count.
34 let ptr = Arc::as_ptr(&prog.context);
35 cl_prop::<cl_context>(cl_context::from_ptr(ptr))
36 }
37 CL_PROGRAM_DEVICES => cl_prop::<Vec<cl_device_id>>(
38 prog.devs
39 .iter()
40 .map(|&d| cl_device_id::from_ptr(d))
41 .collect(),
42 ),
43 CL_PROGRAM_IL => match &prog.src {
44 ProgramSourceType::Il(il) => to_maybeuninit_vec(il.to_bin().to_vec()),
45 _ => Vec::new(),
46 },
47 CL_PROGRAM_KERNEL_NAMES => cl_prop::<&str>(&*prog.build_info().kernels().join(";")),
48 CL_PROGRAM_NUM_DEVICES => cl_prop::<cl_uint>(prog.devs.len() as cl_uint),
49 CL_PROGRAM_NUM_KERNELS => cl_prop::<usize>(prog.build_info().kernels().len()),
50 CL_PROGRAM_REFERENCE_COUNT => cl_prop::<cl_uint>(Program::refcnt(*self)?),
51 CL_PROGRAM_SCOPE_GLOBAL_CTORS_PRESENT => cl_prop::<cl_bool>(CL_FALSE),
52 CL_PROGRAM_SCOPE_GLOBAL_DTORS_PRESENT => cl_prop::<cl_bool>(CL_FALSE),
53 CL_PROGRAM_SOURCE => match &prog.src {
54 ProgramSourceType::Src(src) => cl_prop::<&CStr>(src.as_c_str()),
55 _ => Vec::new(),
56 },
57 // CL_INVALID_VALUE if param_name is not one of the supported values
58 _ => return Err(CL_INVALID_VALUE),
59 })
60 }
61 }
62
63 #[cl_info_entrypoint(clGetProgramBuildInfo)]
64 impl CLInfoObj<cl_program_build_info, cl_device_id> for cl_program {
query(&self, d: cl_device_id, q: cl_program_build_info) -> CLResult<Vec<MaybeUninit<u8>>>65 fn query(&self, d: cl_device_id, q: cl_program_build_info) -> CLResult<Vec<MaybeUninit<u8>>> {
66 let prog = Program::ref_from_raw(*self)?;
67 let dev = Device::ref_from_raw(d)?;
68 Ok(match q {
69 CL_PROGRAM_BINARY_TYPE => cl_prop::<cl_program_binary_type>(prog.bin_type(dev)),
70 CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE => cl_prop::<usize>(0),
71 CL_PROGRAM_BUILD_LOG => cl_prop::<&str>(&prog.log(dev)),
72 CL_PROGRAM_BUILD_OPTIONS => cl_prop::<&str>(&prog.options(dev)),
73 CL_PROGRAM_BUILD_STATUS => cl_prop::<cl_build_status>(prog.status(dev)),
74 // CL_INVALID_VALUE if param_name is not one of the supported values
75 _ => return Err(CL_INVALID_VALUE),
76 })
77 }
78 }
79
validate_devices<'a>( device_list: *const cl_device_id, num_devices: cl_uint, default: &[&'a Device], ) -> CLResult<Vec<&'a Device>>80 fn validate_devices<'a>(
81 device_list: *const cl_device_id,
82 num_devices: cl_uint,
83 default: &[&'a Device],
84 ) -> CLResult<Vec<&'a Device>> {
85 let mut devs = Device::refs_from_arr(device_list, num_devices)?;
86
87 // If device_list is a NULL value, the compile is performed for all devices associated with
88 // program.
89 if devs.is_empty() {
90 devs = default.to_vec();
91 }
92
93 Ok(devs)
94 }
95
96 #[cl_entrypoint(clCreateProgramWithSource)]
create_program_with_source( context: cl_context, count: cl_uint, strings: *mut *const c_char, lengths: *const usize, ) -> CLResult<cl_program>97 fn create_program_with_source(
98 context: cl_context,
99 count: cl_uint,
100 strings: *mut *const c_char,
101 lengths: *const usize,
102 ) -> CLResult<cl_program> {
103 let c = Context::arc_from_raw(context)?;
104
105 // CL_INVALID_VALUE if count is zero or if strings ...
106 if count == 0 || strings.is_null() {
107 return Err(CL_INVALID_VALUE);
108 }
109
110 // ... or any entry in strings is NULL.
111 let srcs = unsafe { slice::from_raw_parts(strings, count as usize) };
112 if srcs.contains(&ptr::null()) {
113 return Err(CL_INVALID_VALUE);
114 }
115
116 // "lengths argument is an array with the number of chars in each string
117 // (the string length). If an element in lengths is zero, its accompanying
118 // string is null-terminated. If lengths is NULL, all strings in the
119 // strings argument are considered null-terminated."
120
121 // A length of zero represents "no length given", so semantically we're
122 // dealing not with a slice of usize but actually with a slice of
123 // Option<NonZeroUsize>. Handily those two are layout compatible, so simply
124 // reinterpret the data.
125 //
126 // Take either an iterator over the given slice or - if the `lengths`
127 // pointer is NULL - an iterator that always returns None (infinite, but
128 // later bounded by being zipped with the finite `srcs`).
129 //
130 // Looping over different iterators is no problem as long as they return
131 // the same item type. However, since we can only decide which to use at
132 // runtime, we need to use dynamic dispatch. The compiler also needs to
133 // know how much space to reserve on the stack, but different
134 // implementations of the `Iterator` trait will need different amounts of
135 // memory. This is resolved by putting the actual iterator on the heap
136 // with `Box` and only a reference to it on the stack.
137 let lengths: Box<dyn Iterator<Item = _>> = if lengths.is_null() {
138 Box::new(iter::repeat(&None))
139 } else {
140 // SAFETY: Option<NonZeroUsize> is guaranteed to be layout compatible
141 // with usize. The zero niche represents None.
142 let lengths = lengths as *const Option<NonZeroUsize>;
143 Box::new(unsafe { slice::from_raw_parts(lengths, count as usize) }.iter())
144 };
145
146 // We don't want encoding or any other problems with the source to prevent
147 // compilation, so don't convert this to a Rust `String`.
148 let mut source = Vec::new();
149 for (&string_ptr, len_opt) in iter::zip(srcs, lengths) {
150 let arr = match len_opt {
151 Some(len) => {
152 // The spec doesn't say how nul bytes should be handled here or
153 // if they are legal at all. Assume they truncate the string.
154 let arr = unsafe { slice::from_raw_parts(string_ptr.cast(), len.get()) };
155 // TODO: simplify this a bit with from_bytes_until_nul once
156 // that's stabilized and available in our msrv
157 arr.iter()
158 .position(|&x| x == 0)
159 .map_or(arr, |nul_index| &arr[..nul_index])
160 }
161 None => unsafe { CStr::from_ptr(string_ptr) }.to_bytes(),
162 };
163
164 source.extend_from_slice(arr);
165 }
166
167 Ok(Program::new(
168 c,
169 // SAFETY: We've constructed `source` such that it contains no nul bytes.
170 unsafe { CString::from_vec_unchecked(source) },
171 )
172 .into_cl())
173 }
174
175 #[cl_entrypoint(clCreateProgramWithBinary)]
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>176 fn create_program_with_binary(
177 context: cl_context,
178 num_devices: cl_uint,
179 device_list: *const cl_device_id,
180 lengths: *const usize,
181 binaries: *mut *const ::std::os::raw::c_uchar,
182 binary_status: *mut cl_int,
183 ) -> CLResult<cl_program> {
184 let c = Context::arc_from_raw(context)?;
185 let devs = Device::refs_from_arr(device_list, num_devices)?;
186
187 // CL_INVALID_VALUE if device_list is NULL or num_devices is zero.
188 if devs.is_empty() {
189 return Err(CL_INVALID_VALUE);
190 }
191
192 // needs to happen after `devs.is_empty` check to protect against num_devices being 0
193 let mut binary_status =
194 unsafe { cl_slice::from_raw_parts_mut(binary_status, num_devices as usize) }.ok();
195
196 // CL_INVALID_VALUE if lengths or binaries is NULL
197 if lengths.is_null() || binaries.is_null() {
198 return Err(CL_INVALID_VALUE);
199 }
200
201 // CL_INVALID_DEVICE if any device in device_list is not in the list of devices associated with
202 // context.
203 if !devs.iter().all(|d| c.devs.contains(d)) {
204 return Err(CL_INVALID_DEVICE);
205 }
206
207 let lengths = unsafe { slice::from_raw_parts(lengths, num_devices as usize) };
208 let binaries = unsafe { slice::from_raw_parts(binaries, num_devices as usize) };
209
210 // now device specific stuff
211 let mut bins: Vec<&[u8]> = vec![&[]; num_devices as usize];
212 for i in 0..num_devices as usize {
213 // CL_INVALID_VALUE if lengths[i] is zero or if binaries[i] is a NULL value (handled inside
214 // [Program::from_bins])
215 if lengths[i] == 0 || binaries[i].is_null() {
216 bins[i] = &[];
217 } else {
218 bins[i] = unsafe { slice::from_raw_parts(binaries[i], lengths[i]) };
219 }
220 }
221
222 let prog = match Program::from_bins(c, devs, &bins) {
223 Ok(prog) => {
224 if let Some(binary_status) = &mut binary_status {
225 binary_status.fill(CL_SUCCESS as cl_int);
226 }
227 prog
228 }
229 Err(errors) => {
230 // CL_INVALID_BINARY if an invalid program binary was encountered for any device.
231 // binary_status will return specific status for each device.
232 if let Some(binary_status) = &mut binary_status {
233 binary_status.copy_from_slice(&errors);
234 }
235
236 // this should return either CL_INVALID_VALUE or CL_INVALID_BINARY
237 let err = errors.into_iter().find(|&err| err != 0).unwrap_or_default();
238 debug_assert!(err != 0);
239 return Err(err);
240 }
241 };
242
243 Ok(prog.into_cl())
244 }
245
246 #[cl_entrypoint(clCreateProgramWithIL)]
create_program_with_il( context: cl_context, il: *const ::std::os::raw::c_void, length: usize, ) -> CLResult<cl_program>247 fn create_program_with_il(
248 context: cl_context,
249 il: *const ::std::os::raw::c_void,
250 length: usize,
251 ) -> CLResult<cl_program> {
252 let c = Context::arc_from_raw(context)?;
253
254 // CL_INVALID_VALUE if il is NULL or if length is zero.
255 if il.is_null() || length == 0 {
256 return Err(CL_INVALID_VALUE);
257 }
258
259 // SAFETY: according to API spec
260 let spirv = unsafe { slice::from_raw_parts(il.cast(), length) };
261 Ok(Program::from_spirv(c, spirv).into_cl())
262 }
263
264 #[cl_entrypoint(clRetainProgram)]
retain_program(program: cl_program) -> CLResult<()>265 fn retain_program(program: cl_program) -> CLResult<()> {
266 Program::retain(program)
267 }
268
269 #[cl_entrypoint(clReleaseProgram)]
release_program(program: cl_program) -> CLResult<()>270 fn release_program(program: cl_program) -> CLResult<()> {
271 Program::release(program)
272 }
273
debug_logging(p: &Program, devs: &[&Device])274 fn debug_logging(p: &Program, devs: &[&Device]) {
275 if Platform::dbg().program {
276 for dev in devs {
277 let msg = p.log(dev);
278 if !msg.is_empty() {
279 eprintln!("{}", msg);
280 }
281 }
282 }
283 }
284
285 #[cl_entrypoint(clBuildProgram)]
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<()>286 fn build_program(
287 program: cl_program,
288 num_devices: cl_uint,
289 device_list: *const cl_device_id,
290 options: *const c_char,
291 pfn_notify: Option<FuncProgramCB>,
292 user_data: *mut ::std::os::raw::c_void,
293 ) -> CLResult<()> {
294 let mut res = true;
295 let p = Program::ref_from_raw(program)?;
296 let devs = validate_devices(device_list, num_devices, &p.devs)?;
297
298 // SAFETY: The requirements on `ProgramCB::try_new` match the requirements
299 // imposed by the OpenCL specification. It is the caller's duty to uphold them.
300 let cb_opt = unsafe { ProgramCB::try_new(pfn_notify, user_data)? };
301
302 // CL_INVALID_OPERATION if there are kernel objects attached to program.
303 if p.active_kernels() {
304 return Err(CL_INVALID_OPERATION);
305 }
306
307 // CL_BUILD_PROGRAM_FAILURE if there is a failure to build the program executable. This error
308 // will be returned if clBuildProgram does not return until the build has completed.
309 for dev in &devs {
310 res &= p.build(dev, c_string_to_string(options));
311 }
312
313 if let Some(cb) = cb_opt {
314 cb.call(p);
315 }
316
317 //• CL_INVALID_BINARY if program is created with clCreateProgramWithBinary and devices listed in device_list do not have a valid program binary loaded.
318 //• CL_INVALID_BUILD_OPTIONS if the build options specified by options are invalid.
319 //• 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.
320 //• CL_INVALID_OPERATION if program was not created with clCreateProgramWithSource, clCreateProgramWithIL or clCreateProgramWithBinary.
321
322 debug_logging(p, &devs);
323 if res {
324 Ok(())
325 } else {
326 Err(CL_BUILD_PROGRAM_FAILURE)
327 }
328 }
329
330 #[cl_entrypoint(clCompileProgram)]
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<()>331 fn compile_program(
332 program: cl_program,
333 num_devices: cl_uint,
334 device_list: *const cl_device_id,
335 options: *const c_char,
336 num_input_headers: cl_uint,
337 input_headers: *const cl_program,
338 header_include_names: *mut *const c_char,
339 pfn_notify: Option<FuncProgramCB>,
340 user_data: *mut ::std::os::raw::c_void,
341 ) -> CLResult<()> {
342 let mut res = true;
343 let p = Program::ref_from_raw(program)?;
344 let devs = validate_devices(device_list, num_devices, &p.devs)?;
345
346 // SAFETY: The requirements on `ProgramCB::try_new` match the requirements
347 // imposed by the OpenCL specification. It is the caller's duty to uphold them.
348 let cb_opt = unsafe { ProgramCB::try_new(pfn_notify, user_data)? };
349
350 // CL_INVALID_VALUE if num_input_headers is zero and header_include_names or input_headers are
351 // not NULL or if num_input_headers is not zero and header_include_names or input_headers are
352 // NULL.
353 if num_input_headers == 0 && (!header_include_names.is_null() || !input_headers.is_null())
354 || num_input_headers != 0 && (header_include_names.is_null() || input_headers.is_null())
355 {
356 return Err(CL_INVALID_VALUE);
357 }
358
359 let mut headers = Vec::new();
360
361 // If program was created using clCreateProgramWithIL, then num_input_headers, input_headers,
362 // and header_include_names are ignored.
363 if !p.is_il() {
364 for h in 0..num_input_headers as usize {
365 // SAFETY: have to trust the application here
366 let header = Program::ref_from_raw(unsafe { *input_headers.add(h) })?;
367 match &header.src {
368 ProgramSourceType::Src(src) => headers.push(spirv::CLCHeader {
369 // SAFETY: have to trust the application here
370 name: unsafe { CStr::from_ptr(*header_include_names.add(h)).to_owned() },
371 source: src,
372 }),
373 _ => return Err(CL_INVALID_OPERATION),
374 }
375 }
376 }
377
378 // CL_INVALID_OPERATION if program has no source or IL available, i.e. it has not been created
379 // with clCreateProgramWithSource or clCreateProgramWithIL.
380 if !(p.is_src() || p.is_il()) {
381 return Err(CL_INVALID_OPERATION);
382 }
383
384 // CL_INVALID_OPERATION if there are kernel objects attached to program.
385 if p.active_kernels() {
386 return Err(CL_INVALID_OPERATION);
387 }
388
389 // CL_COMPILE_PROGRAM_FAILURE if there is a failure to compile the program source. This error
390 // will be returned if clCompileProgram does not return until the compile has completed.
391 for dev in &devs {
392 res &= p.compile(dev, c_string_to_string(options), &headers);
393 }
394
395 if let Some(cb) = cb_opt {
396 cb.call(p);
397 }
398
399 // • CL_INVALID_COMPILER_OPTIONS if the compiler options specified by options are invalid.
400 // • 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.
401
402 debug_logging(p, &devs);
403 if res {
404 Ok(())
405 } else {
406 Err(CL_COMPILE_PROGRAM_FAILURE)
407 }
408 }
409
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)>410 pub fn link_program(
411 context: cl_context,
412 num_devices: cl_uint,
413 device_list: *const cl_device_id,
414 options: *const ::std::os::raw::c_char,
415 num_input_programs: cl_uint,
416 input_programs: *const cl_program,
417 pfn_notify: Option<FuncProgramCB>,
418 user_data: *mut ::std::os::raw::c_void,
419 ) -> CLResult<(cl_program, cl_int)> {
420 let c = Context::arc_from_raw(context)?;
421 let devs = validate_devices(device_list, num_devices, &c.devs)?;
422 let progs = Program::arcs_from_arr(input_programs, num_input_programs)?;
423
424 // SAFETY: The requirements on `ProgramCB::try_new` match the requirements
425 // imposed by the OpenCL specification. It is the caller's duty to uphold them.
426 let cb_opt = unsafe { ProgramCB::try_new(pfn_notify, user_data)? };
427
428 // CL_INVALID_VALUE if num_input_programs is zero and input_programs is NULL
429 if progs.is_empty() {
430 return Err(CL_INVALID_VALUE);
431 }
432
433 // CL_INVALID_DEVICE if any device in device_list is not in the list of devices associated with
434 // context.
435 if !devs.iter().all(|d| c.devs.contains(d)) {
436 return Err(CL_INVALID_DEVICE);
437 }
438
439 // CL_INVALID_OPERATION if the compilation or build of a program executable for any of the
440 // devices listed in device_list by a previous call to clCompileProgram or clBuildProgram for
441 // program has not completed.
442 for d in &devs {
443 if progs
444 .iter()
445 .map(|p| p.status(d))
446 .any(|s| s != CL_BUILD_SUCCESS as cl_build_status)
447 {
448 return Err(CL_INVALID_OPERATION);
449 }
450 }
451
452 // CL_LINK_PROGRAM_FAILURE if there is a failure to link the compiled binaries and/or libraries.
453 let res = Program::link(c, &devs, &progs, c_string_to_string(options));
454 let code = if devs
455 .iter()
456 .map(|d| res.status(d))
457 .all(|s| s == CL_BUILD_SUCCESS as cl_build_status)
458 {
459 CL_SUCCESS as cl_int
460 } else {
461 CL_LINK_PROGRAM_FAILURE
462 };
463
464 if let Some(cb) = cb_opt {
465 cb.call(&res);
466 }
467
468 debug_logging(&res, &devs);
469 Ok((res.into_cl(), code))
470
471 //• CL_INVALID_LINKER_OPTIONS if the linker options specified by options are invalid.
472 //• CL_INVALID_OPERATION if the rules for devices containing compiled binaries or libraries as described in input_programs argument above are not followed.
473 }
474
475 #[cl_entrypoint(clSetProgramSpecializationConstant)]
set_program_specialization_constant( program: cl_program, spec_id: cl_uint, spec_size: usize, spec_value: *const ::std::os::raw::c_void, ) -> CLResult<()>476 fn set_program_specialization_constant(
477 program: cl_program,
478 spec_id: cl_uint,
479 spec_size: usize,
480 spec_value: *const ::std::os::raw::c_void,
481 ) -> CLResult<()> {
482 let program = Program::ref_from_raw(program)?;
483
484 // CL_INVALID_PROGRAM if program is not a valid program object created from an intermediate
485 // language (e.g. SPIR-V)
486 // TODO: or if the intermediate language does not support specialization constants.
487 if !program.is_il() {
488 return Err(CL_INVALID_PROGRAM);
489 }
490
491 if spec_size != program.get_spec_constant_size(spec_id).into() {
492 // CL_INVALID_VALUE if spec_size does not match the size of the specialization constant in
493 // the module,
494 return Err(CL_INVALID_VALUE);
495 }
496
497 // or if spec_value is NULL.
498 if spec_value.is_null() {
499 return Err(CL_INVALID_VALUE);
500 }
501
502 // SAFETY: according to API spec
503 program.set_spec_constant(spec_id, unsafe {
504 slice::from_raw_parts(spec_value.cast(), spec_size)
505 });
506
507 Ok(())
508 }
509
510 #[cl_entrypoint(clSetProgramReleaseCallback)]
set_program_release_callback( _program: cl_program, _pfn_notify: ::std::option::Option<FuncProgramCB>, _user_data: *mut ::std::os::raw::c_void, ) -> CLResult<()>511 fn set_program_release_callback(
512 _program: cl_program,
513 _pfn_notify: ::std::option::Option<FuncProgramCB>,
514 _user_data: *mut ::std::os::raw::c_void,
515 ) -> CLResult<()> {
516 Err(CL_INVALID_OPERATION)
517 }
518