xref: /aosp_15_r20/external/mesa3d/src/gallium/frontends/rusticl/api/program.rs (revision 6104692788411f58d303aa86923a9ff6ecaded22)
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