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