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