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