• 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::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(cl_get_program_info)]
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.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.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(cl_get_program_build_info)]
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]
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]
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     // CL_INVALID_VALUE if lengths or binaries is NULL
193     if lengths.is_null() || binaries.is_null() {
194         return Err(CL_INVALID_VALUE);
195     }
196 
197     // CL_INVALID_DEVICE if any device in device_list is not in the list of devices associated with
198     // context.
199     if !devs.iter().all(|d| c.devs.contains(d)) {
200         return Err(CL_INVALID_DEVICE);
201     }
202 
203     let lengths = unsafe { slice::from_raw_parts(lengths, num_devices as usize) };
204     let binaries = unsafe { slice::from_raw_parts(binaries, num_devices as usize) };
205 
206     // now device specific stuff
207     let mut err = 0;
208     let mut bins: Vec<&[u8]> = vec![&[]; num_devices as usize];
209     for i in 0..num_devices as usize {
210         let mut dev_err = 0;
211 
212         // CL_INVALID_VALUE if lengths[i] is zero or if binaries[i] is a NULL value
213         if lengths[i] == 0 || binaries[i].is_null() {
214             dev_err = CL_INVALID_VALUE;
215         }
216 
217         if !binary_status.is_null() {
218             unsafe { binary_status.add(i).write(dev_err) };
219         }
220 
221         // just return the last one
222         err = dev_err;
223         bins[i] = unsafe { slice::from_raw_parts(binaries[i], lengths[i]) };
224     }
225 
226     if err != 0 {
227         return Err(err);
228     }
229 
230     let prog = Program::from_bins(c, devs, &bins);
231 
232     Ok(prog.into_cl())
233     //• CL_INVALID_BINARY if an invalid program binary was encountered for any device. binary_status will return specific status for each device.
234 }
235 
236 #[cl_entrypoint]
create_program_with_il( context: cl_context, il: *const ::std::os::raw::c_void, length: usize, ) -> CLResult<cl_program>237 fn create_program_with_il(
238     context: cl_context,
239     il: *const ::std::os::raw::c_void,
240     length: usize,
241 ) -> CLResult<cl_program> {
242     let c = Context::arc_from_raw(context)?;
243 
244     // CL_INVALID_VALUE if il is NULL or if length is zero.
245     if il.is_null() || length == 0 {
246         return Err(CL_INVALID_VALUE);
247     }
248 
249     // SAFETY: according to API spec
250     let spirv = unsafe { slice::from_raw_parts(il.cast(), length) };
251     Ok(Program::from_spirv(c, spirv).into_cl())
252 }
253 
254 #[cl_entrypoint]
retain_program(program: cl_program) -> CLResult<()>255 fn retain_program(program: cl_program) -> CLResult<()> {
256     Program::retain(program)
257 }
258 
259 #[cl_entrypoint]
release_program(program: cl_program) -> CLResult<()>260 fn release_program(program: cl_program) -> CLResult<()> {
261     Program::release(program)
262 }
263 
debug_logging(p: &Program, devs: &[&Device])264 fn debug_logging(p: &Program, devs: &[&Device]) {
265     if Platform::dbg().program {
266         for dev in devs {
267             let msg = p.log(dev);
268             if !msg.is_empty() {
269                 eprintln!("{}", msg);
270             }
271         }
272     }
273 }
274 
275 #[cl_entrypoint]
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<()>276 fn build_program(
277     program: cl_program,
278     num_devices: cl_uint,
279     device_list: *const cl_device_id,
280     options: *const c_char,
281     pfn_notify: Option<FuncProgramCB>,
282     user_data: *mut ::std::os::raw::c_void,
283 ) -> CLResult<()> {
284     let mut res = true;
285     let p = Program::ref_from_raw(program)?;
286     let devs = validate_devices(device_list, num_devices, &p.devs)?;
287 
288     // SAFETY: The requirements on `ProgramCB::try_new` match the requirements
289     // imposed by the OpenCL specification. It is the caller's duty to uphold them.
290     let cb_opt = unsafe { ProgramCB::try_new(pfn_notify, user_data)? };
291 
292     // CL_INVALID_OPERATION if there are kernel objects attached to program.
293     if p.active_kernels() {
294         return Err(CL_INVALID_OPERATION);
295     }
296 
297     // CL_BUILD_PROGRAM_FAILURE if there is a failure to build the program executable. This error
298     // will be returned if clBuildProgram does not return until the build has completed.
299     for dev in &devs {
300         res &= p.build(dev, c_string_to_string(options));
301     }
302 
303     if let Some(cb) = cb_opt {
304         cb.call(p);
305     }
306 
307     //• CL_INVALID_BINARY if program is created with clCreateProgramWithBinary and devices listed in device_list do not have a valid program binary loaded.
308     //• CL_INVALID_BUILD_OPTIONS if the build options specified by options are invalid.
309     //• 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.
310     //• CL_INVALID_OPERATION if program was not created with clCreateProgramWithSource, clCreateProgramWithIL or clCreateProgramWithBinary.
311 
312     debug_logging(p, &devs);
313     if res {
314         Ok(())
315     } else {
316         Err(CL_BUILD_PROGRAM_FAILURE)
317     }
318 }
319 
320 #[cl_entrypoint]
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<()>321 fn compile_program(
322     program: cl_program,
323     num_devices: cl_uint,
324     device_list: *const cl_device_id,
325     options: *const c_char,
326     num_input_headers: cl_uint,
327     input_headers: *const cl_program,
328     header_include_names: *mut *const c_char,
329     pfn_notify: Option<FuncProgramCB>,
330     user_data: *mut ::std::os::raw::c_void,
331 ) -> CLResult<()> {
332     let mut res = true;
333     let p = Program::ref_from_raw(program)?;
334     let devs = validate_devices(device_list, num_devices, &p.devs)?;
335 
336     // SAFETY: The requirements on `ProgramCB::try_new` match the requirements
337     // imposed by the OpenCL specification. It is the caller's duty to uphold them.
338     let cb_opt = unsafe { ProgramCB::try_new(pfn_notify, user_data)? };
339 
340     // CL_INVALID_VALUE if num_input_headers is zero and header_include_names or input_headers are
341     // not NULL or if num_input_headers is not zero and header_include_names or input_headers are
342     // NULL.
343     if num_input_headers == 0 && (!header_include_names.is_null() || !input_headers.is_null())
344         || num_input_headers != 0 && (header_include_names.is_null() || input_headers.is_null())
345     {
346         return Err(CL_INVALID_VALUE);
347     }
348 
349     let mut headers = Vec::new();
350 
351     // If program was created using clCreateProgramWithIL, then num_input_headers, input_headers,
352     // and header_include_names are ignored.
353     if !p.is_il() {
354         for h in 0..num_input_headers as usize {
355             // SAFETY: have to trust the application here
356             let header = Program::ref_from_raw(unsafe { *input_headers.add(h) })?;
357             match &header.src {
358                 ProgramSourceType::Src(src) => headers.push(spirv::CLCHeader {
359                     // SAFETY: have to trust the application here
360                     name: unsafe { CStr::from_ptr(*header_include_names.add(h)).to_owned() },
361                     source: src,
362                 }),
363                 _ => return Err(CL_INVALID_OPERATION),
364             }
365         }
366     }
367 
368     // CL_INVALID_OPERATION if program has no source or IL available, i.e. it has not been created
369     // with clCreateProgramWithSource or clCreateProgramWithIL.
370     if !(p.is_src() || p.is_il()) {
371         return Err(CL_INVALID_OPERATION);
372     }
373 
374     // CL_INVALID_OPERATION if there are kernel objects attached to program.
375     if p.active_kernels() {
376         return Err(CL_INVALID_OPERATION);
377     }
378 
379     // CL_COMPILE_PROGRAM_FAILURE if there is a failure to compile the program source. This error
380     // will be returned if clCompileProgram does not return until the compile has completed.
381     for dev in &devs {
382         res &= p.compile(dev, c_string_to_string(options), &headers);
383     }
384 
385     if let Some(cb) = cb_opt {
386         cb.call(p);
387     }
388 
389     // • CL_INVALID_COMPILER_OPTIONS if the compiler options specified by options are invalid.
390     // • 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.
391 
392     debug_logging(p, &devs);
393     if res {
394         Ok(())
395     } else {
396         Err(CL_COMPILE_PROGRAM_FAILURE)
397     }
398 }
399 
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)>400 pub fn link_program(
401     context: cl_context,
402     num_devices: cl_uint,
403     device_list: *const cl_device_id,
404     options: *const ::std::os::raw::c_char,
405     num_input_programs: cl_uint,
406     input_programs: *const cl_program,
407     pfn_notify: Option<FuncProgramCB>,
408     user_data: *mut ::std::os::raw::c_void,
409 ) -> CLResult<(cl_program, cl_int)> {
410     let c = Context::arc_from_raw(context)?;
411     let devs = validate_devices(device_list, num_devices, &c.devs)?;
412     let progs = Program::arcs_from_arr(input_programs, num_input_programs)?;
413 
414     // SAFETY: The requirements on `ProgramCB::try_new` match the requirements
415     // imposed by the OpenCL specification. It is the caller's duty to uphold them.
416     let cb_opt = unsafe { ProgramCB::try_new(pfn_notify, user_data)? };
417 
418     // CL_INVALID_VALUE if num_input_programs is zero and input_programs is NULL
419     if progs.is_empty() {
420         return Err(CL_INVALID_VALUE);
421     }
422 
423     // CL_INVALID_DEVICE if any device in device_list is not in the list of devices associated with
424     // context.
425     if !devs.iter().all(|d| c.devs.contains(d)) {
426         return Err(CL_INVALID_DEVICE);
427     }
428 
429     // CL_INVALID_OPERATION if the compilation or build of a program executable for any of the
430     // devices listed in device_list by a previous call to clCompileProgram or clBuildProgram for
431     // program has not completed.
432     for d in &devs {
433         if progs
434             .iter()
435             .map(|p| p.status(d))
436             .any(|s| s != CL_BUILD_SUCCESS as cl_build_status)
437         {
438             return Err(CL_INVALID_OPERATION);
439         }
440     }
441 
442     // CL_LINK_PROGRAM_FAILURE if there is a failure to link the compiled binaries and/or libraries.
443     let res = Program::link(c, &devs, &progs, c_string_to_string(options));
444     let code = if devs
445         .iter()
446         .map(|d| res.status(d))
447         .all(|s| s == CL_BUILD_SUCCESS as cl_build_status)
448     {
449         CL_SUCCESS as cl_int
450     } else {
451         CL_LINK_PROGRAM_FAILURE
452     };
453 
454     if let Some(cb) = cb_opt {
455         cb.call(&res);
456     }
457 
458     debug_logging(&res, &devs);
459     Ok((res.into_cl(), code))
460 
461     //• CL_INVALID_LINKER_OPTIONS if the linker options specified by options are invalid.
462     //• CL_INVALID_OPERATION if the rules for devices containing compiled binaries or libraries as described in input_programs argument above are not followed.
463 }
464 
465 #[cl_entrypoint]
set_program_specialization_constant( program: cl_program, spec_id: cl_uint, spec_size: usize, spec_value: *const ::std::os::raw::c_void, ) -> CLResult<()>466 fn set_program_specialization_constant(
467     program: cl_program,
468     spec_id: cl_uint,
469     spec_size: usize,
470     spec_value: *const ::std::os::raw::c_void,
471 ) -> CLResult<()> {
472     let program = Program::ref_from_raw(program)?;
473 
474     // CL_INVALID_PROGRAM if program is not a valid program object created from an intermediate
475     // language (e.g. SPIR-V)
476     // TODO: or if the intermediate language does not support specialization constants.
477     if !program.is_il() {
478         return Err(CL_INVALID_PROGRAM);
479     }
480 
481     if spec_size != program.get_spec_constant_size(spec_id).into() {
482         // CL_INVALID_VALUE if spec_size does not match the size of the specialization constant in
483         // the module,
484         return Err(CL_INVALID_VALUE);
485     }
486 
487     // or if spec_value is NULL.
488     if spec_value.is_null() {
489         return Err(CL_INVALID_VALUE);
490     }
491 
492     // SAFETY: according to API spec
493     program.set_spec_constant(spec_id, unsafe {
494         slice::from_raw_parts(spec_value.cast(), spec_size)
495     });
496 
497     Ok(())
498 }
499 
500 #[cl_entrypoint]
set_program_release_callback( _program: cl_program, _pfn_notify: ::std::option::Option<FuncProgramCB>, _user_data: *mut ::std::os::raw::c_void, ) -> CLResult<()>501 fn set_program_release_callback(
502     _program: cl_program,
503     _pfn_notify: ::std::option::Option<FuncProgramCB>,
504     _user_data: *mut ::std::os::raw::c_void,
505 ) -> CLResult<()> {
506     Err(CL_INVALID_OPERATION)
507 }
508