ocl/standard/
program.rs

1//! An `OpenCL` program.
2use std;
3use std::collections::HashSet;
4use std::convert::Into;
5use std::ffi::CString;
6use std::fs::File;
7use std::io::Read;
8use std::ops::{Deref, DerefMut};
9use std::path::PathBuf;
10
11use crate::core::{
12    self, Context as ContextCore, Program as ProgramCore, ProgramBuildInfo, ProgramBuildInfoResult,
13    ProgramInfo, ProgramInfoResult, Result as OclCoreResult,
14};
15use crate::error::{Error as OclError, Result as OclResult};
16use crate::standard::{Context, Device, DeviceSpecifier};
17#[cfg(feature = "opencl_version_2_1")]
18use core::ClVersions;
19
20/// A program from which kernels can be created from.
21///
22/// To use with multiple devices, create manually with `::from_parts()`.
23///
24/// ## Destruction
25///
26/// Handled automatically. Feel free to store, clone, and share among threads
27/// as you please.
28///
29#[derive(Clone, Debug)]
30pub struct Program(ProgramCore);
31
32impl Program {
33    /// Returns a new `ProgramBuilder`.
34    pub fn builder<'b>() -> ProgramBuilder<'b> {
35        ProgramBuilder::new()
36    }
37
38    /// Returns a new program built from pre-created build components and device
39    /// list.
40    ///
41    /// Prefer `::builder` to create a new `Program`.
42    ///
43    pub fn with_source(
44        context: &ContextCore,
45        src_strings: &[CString],
46        devices: Option<&[Device]>,
47        cmplr_opts: &CString,
48    ) -> OclResult<Program> {
49        let program = core::create_program_with_source(context, src_strings)?;
50        core::build_program(&program, devices, cmplr_opts, None, None)?;
51        Ok(Program(program))
52    }
53
54    /// Returns a new program built from pre-created build components and device
55    /// list.
56    ///
57    /// Prefer `::builder` to create a new `Program`.
58    ///
59    pub fn with_binary(
60        context: &ContextCore,
61        devices: &[Device],
62        binaries: &[&[u8]],
63        cmplr_opts: &CString,
64    ) -> OclResult<Program> {
65        let program = core::create_program_with_binary(context, devices, binaries)?;
66        core::build_program(&program, Some(devices), cmplr_opts, None, None)?;
67        Ok(Program(program))
68    }
69
70    /// Returns a new program built from pre-created build components and device
71    /// list for programs with intermediate language byte source.
72    #[cfg(feature = "opencl_version_2_1")]
73    pub fn with_il(
74        il: &[u8],
75        devices: Option<&[Device]>,
76        cmplr_opts: &CString,
77        context: &ContextCore,
78    ) -> OclResult<Program> {
79        let device_versions = context.device_versions()?;
80        let program = core::create_program_with_il(context, il, Some(&device_versions))?;
81        core::build_program(&program, devices, cmplr_opts, None, None)?;
82
83        Ok(Program(program))
84    }
85
86    /// Returns a reference to the core pointer wrapper, usable by functions in
87    /// the `core` module.
88    #[inline]
89    pub fn as_core(&self) -> &ProgramCore {
90        &self.0
91    }
92
93    /// Returns info about this program.
94    pub fn info(&self, info_kind: ProgramInfo) -> OclCoreResult<ProgramInfoResult> {
95        core::get_program_info(&self.0, info_kind)
96    }
97
98    /// Returns info about this program's build.
99    ///
100    /// * TODO: Check that device is valid.
101    pub fn build_info(
102        &self,
103        device: Device,
104        info_kind: ProgramBuildInfo,
105    ) -> OclCoreResult<ProgramBuildInfoResult> {
106        core::get_program_build_info(&self.0, &device, info_kind)
107    }
108
109    fn fmt_info(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
110        f.debug_struct("Program")
111            .field("ReferenceCount", &self.info(ProgramInfo::ReferenceCount))
112            .field("Context", &self.info(ProgramInfo::Context))
113            .field("NumDevices", &self.info(ProgramInfo::NumDevices))
114            .field("Devices", &self.info(ProgramInfo::Devices))
115            .field("Source", &self.info(ProgramInfo::Source))
116            .field("BinarySizes", &self.info(ProgramInfo::BinarySizes))
117            .field("Binaries", &self.info(ProgramInfo::Binaries))
118            .field("NumKernels", &self.info(ProgramInfo::NumKernels))
119            .field("KernelNames", &self.info(ProgramInfo::KernelNames))
120            .finish()
121    }
122}
123
124impl From<ProgramCore> for Program {
125    fn from(core: ProgramCore) -> Program {
126        Program(core)
127    }
128}
129
130impl std::fmt::Display for Program {
131    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
132        self.fmt_info(f)
133    }
134}
135
136impl Deref for Program {
137    type Target = ProgramCore;
138
139    fn deref(&self) -> &ProgramCore {
140        &self.0
141    }
142}
143
144impl DerefMut for Program {
145    fn deref_mut(&mut self) -> &mut ProgramCore {
146        &mut self.0
147    }
148}
149
150/// A build option used by ProgramBuilder.
151///
152/// Strings intended for use either by the compiler as a command line switch
153/// or for inclusion in the final build source code.
154///
155/// A few of the often used variants have constructors for convenience.
156///
157/// * [FIXME] TODO: Explain how each variant is used.
158///
159/// * [FIXME] TODO: Examples.
160#[derive(Clone, Debug)]
161pub enum BuildOpt {
162    CmplrDefine { ident: String, val: String },
163    CmplrInclDir { path: String },
164    CmplrOther(String),
165    IncludeDefine { ident: String, val: String },
166    IncludeRaw(String),
167    IncludeRawEof(String),
168}
169
170impl BuildOpt {
171    /// Returns a `BuildOpt::CmplrDefine`.
172    pub fn cmplr_def<S: Into<String>>(ident: S, val: i32) -> BuildOpt {
173        BuildOpt::CmplrDefine {
174            ident: ident.into(),
175            val: val.to_string(),
176        }
177    }
178
179    /// Returns a `BuildOpt::IncludeDefine`.
180    pub fn include_def<S: Into<String>>(ident: S, val: String) -> BuildOpt {
181        BuildOpt::IncludeDefine {
182            ident: ident.into(),
183            val,
184        }
185    }
186}
187
188/// Options for program creation.
189#[allow(dead_code)]
190#[derive(Clone, Debug)]
191enum CreateWith<'b> {
192    None,
193    Source(Vec<PathBuf>),
194    Binaries(&'b [&'b [u8]]),
195    Il(&'b [u8]),
196}
197
198/// A builder for `Program`.
199///
200// * [SOMEDAY TODO]: Keep track of line number range for each string and print
201// out during build failure.
202//
203#[must_use = "builders do nothing unless '::build' is called"]
204#[derive(Clone, Debug)]
205pub struct ProgramBuilder<'b> {
206    options: Vec<BuildOpt>,
207    with: CreateWith<'b>,
208    device_spec: Option<DeviceSpecifier>,
209}
210
211impl<'b> ProgramBuilder<'b> {
212    /// Returns a new, empty, build configuration object.
213    pub fn new() -> ProgramBuilder<'b> {
214        ProgramBuilder {
215            options: Vec::with_capacity(64),
216            with: CreateWith::None,
217            device_spec: None,
218        }
219    }
220
221    /// Adds a build option containing a compiler command line definition.
222    /// Formatted as `-D {name}={val}`.
223    ///
224    /// ## Example
225    ///
226    /// `...cmplr_def("MAX_ITERS", 500)...`
227    ///
228    pub fn cmplr_def<'a, S: Into<String>>(
229        &'a mut self,
230        name: S,
231        val: i32,
232    ) -> &'a mut ProgramBuilder<'b> {
233        self.options.push(BuildOpt::cmplr_def(name, val));
234        self
235    }
236
237    /// Adds a build option containing a raw compiler command line parameter.
238    /// Formatted as `{}` (exact text).
239    ///
240    /// ## Example
241    ///
242    /// `...cmplr_opt("-g")...`
243    ///
244    pub fn cmplr_opt<'a, S: Into<String>>(&'a mut self, co: S) -> &'a mut ProgramBuilder<'b> {
245        self.options.push(BuildOpt::CmplrOther(co.into()));
246        self
247    }
248
249    /// Pushes pre-created build option to the list of options.
250    ///
251    /// If either `::il` or `::binaries` are used and raw source is added, it
252    /// will be ignored.
253    pub fn bo<'a>(&'a mut self, bo: BuildOpt) -> &'a mut ProgramBuilder<'b> {
254        self.options.push(bo);
255        self
256    }
257
258    /// Adds the contents of a file to the program.
259    //
260    // TODO: Deprecate
261    // #[deprecated(since = "0.19.2", note = "Use `::source_file` instead.")]
262    pub fn src_file<'a, P: Into<PathBuf>>(
263        &'a mut self,
264        file_path: P,
265    ) -> &'a mut ProgramBuilder<'b> {
266        self.source_file(file_path)
267    }
268
269    /// Opens a file and adds its contents to the program source.
270    pub fn source_file<'a, P: Into<PathBuf>>(
271        &'a mut self,
272        file_path: P,
273    ) -> &'a mut ProgramBuilder<'b> {
274        let file_path = file_path.into();
275        assert!(
276            file_path.is_file(),
277            "ProgramBuilder::src_file(): Source file error: \
278            '{}' does not exist.",
279            file_path.display()
280        );
281        match self.with {
282            CreateWith::None => {
283                let mut paths = Vec::with_capacity(8);
284                paths.push(file_path);
285                self.with = CreateWith::Source(paths);
286            }
287            CreateWith::Source(ref mut paths) => paths.push(file_path),
288            _ => panic!("Source may not be used with binaries or il."),
289        }
290        self
291    }
292
293    /// Adds raw text to the program source.
294    //
295    // TODO: Deprecate
296    // #[deprecated(since = "0.19.2", note = "Use `::source` instead.")]
297    pub fn src<'a, S: Into<String>>(&'a mut self, src: S) -> &'a mut ProgramBuilder<'b> {
298        self.source(src)
299    }
300
301    /// Adds raw text to the program source.
302    //
303    // [TODO]: Possibly accept Into<CString>
304    pub fn source<'a, S: Into<String>>(&'a mut self, src: S) -> &'a mut ProgramBuilder<'b> {
305        match self.with {
306            CreateWith::None => {
307                self.with = CreateWith::Source(Vec::with_capacity(8));
308                self.options.push(BuildOpt::IncludeRawEof(src.into()));
309            }
310            CreateWith::Source(_) => {
311                self.options.push(BuildOpt::IncludeRawEof(src.into()));
312            }
313            _ => panic!("Source may not be used with binaries or il."),
314        }
315
316        self
317    }
318
319    /// Adds a binary to be loaded.
320    ///
321    /// There must be one binary for each device listed in `::devices`.
322    pub fn binaries<'a>(&'a mut self, bins: &'b [&'b [u8]]) -> &'a mut ProgramBuilder<'b> {
323        match self.with {
324            CreateWith::None => self.with = CreateWith::Binaries(bins),
325            CreateWith::Binaries(_) => panic!("Binaries have already been specified."),
326            _ => panic!("Binaries may not be used with source or il."),
327        }
328        self
329    }
330
331    /// Adds
332
333    /// Adds SPIR-V or an implementation-defined intermediate language to this program.
334    ///
335    /// Any source files or source text added to this build will cause an
336    /// error upon building.
337    ///
338    /// Use the `include_bytes!` macro to include source code from a file statically.
339    ///
340    /// * TODO: Future addition: Allow IL to be loaded directly from a file
341    /// in the same way that text source is.
342    ///
343    #[cfg(feature = "opencl_version_2_1")]
344    pub fn il<'a>(&'a mut self, il: &'b [u8]) -> &'a mut ProgramBuilder<'b> {
345        match self.with {
346            CreateWith::None => self.with = CreateWith::Il(il),
347            CreateWith::Il(_) => panic!("Il has already been specified."),
348            _ => panic!("Il may not be used with source or binaries."),
349        }
350        self
351    }
352
353    /// Specifies a list of devices to build this program on. The devices must
354    /// be associated with the context passed to `::build` later on.
355    ///
356    /// Devices may be specified in any number of ways including simply
357    /// passing a device or slice of devices. See the [`impl
358    /// From`][device_specifier_from] section of
359    /// [`DeviceSpecifier`][device_specifier] for more information.
360    ///
361    ///
362    /// ## Panics
363    ///
364    /// Devices must not have already been specified.
365    ///
366    /// [device_specifier_from]: enum.DeviceSpecifier.html#method.from
367    /// [device_specifier]: enum.DeviceSpecifier.html
368    ///
369    pub fn devices<'a, D: Into<DeviceSpecifier>>(
370        &'a mut self,
371        device_spec: D,
372    ) -> &'a mut ProgramBuilder<'b> {
373        assert!(
374            self.device_spec.is_none(),
375            "ocl::ProgramBuilder::devices(): Devices already specified"
376        );
377        self.device_spec = Some(device_spec.into());
378        self
379    }
380
381    /// Returns the devices specified to be associated the program.
382    pub fn get_device_spec(&self) -> &Option<DeviceSpecifier> {
383        &self.device_spec
384    }
385
386    /// Returns a concatenated string of command line options to be passed to
387    /// the compiler when building this program.
388    pub fn get_compiler_options(&self) -> OclResult<CString> {
389        let mut opts: Vec<String> = Vec::with_capacity(64);
390
391        for option in &self.options {
392            match *option {
393                BuildOpt::CmplrDefine { ref ident, ref val } => {
394                    opts.push(format!("-D {}={}", ident, val))
395                }
396
397                BuildOpt::CmplrInclDir { ref path } => opts.push(format!("-I {}", path)),
398
399                BuildOpt::CmplrOther(ref s) => opts.push(s.clone()),
400
401                _ => (),
402            }
403        }
404
405        CString::new(opts.join(" ").into_bytes()).map_err(OclError::from)
406    }
407
408    /// Parses `self.options` for options intended for inclusion at the beginning of
409    /// the final program source and returns them as a list of strings.
410    ///
411    /// Generally used for #define directives, constants, etc. Normally called from
412    /// `::get_src_strings()`.
413    fn get_includes(&self) -> OclResult<Vec<CString>> {
414        let mut strings = Vec::with_capacity(64);
415        strings.push(CString::new("\n".as_bytes())?);
416
417        for option in &self.options {
418            match *option {
419                BuildOpt::IncludeDefine { ref ident, ref val } => {
420                    strings.push(CString::new(
421                        format!("#define {}  {}\n", ident, val).into_bytes(),
422                    )?);
423                }
424                BuildOpt::IncludeRaw(ref text) => {
425                    strings.push(CString::new(text.clone().into_bytes())?);
426                }
427                _ => (),
428            };
429        }
430
431        strings.shrink_to_fit();
432        Ok(strings)
433    }
434
435    /// Parses `self.options` for options intended for inclusion at the end of
436    /// the final program source and returns them as a list of strings.
437    fn get_includes_eof(&self) -> OclResult<Vec<CString>> {
438        let mut strings = Vec::with_capacity(64);
439        strings.push(CString::new("\n".as_bytes())?);
440
441        for option in &self.options {
442            if let BuildOpt::IncludeRawEof(ref text) = *option {
443                strings.push(CString::new(text.clone().into_bytes())?);
444            }
445        }
446
447        strings.shrink_to_fit();
448        Ok(strings)
449    }
450
451    /// Returns the final program source code as a list of strings.
452    ///
453    /// ### Order of Inclusion
454    ///
455    /// 1. Macro definitions and code strings specified by a
456    ///    `BuildOpt::IncludeDefine` or `BuildOpt::IncludeRaw` via `::bo`
457    /// 2. Contents of files specified via `::src_file`
458    /// 3. Contents of strings specified via `::src` or a
459    ///   `BuildOpt::IncludeRawEof` via `::bo`
460    ///
461    pub fn get_src_strings(&self) -> OclResult<Vec<CString>> {
462        let mut src_strings: Vec<CString> = Vec::with_capacity(64);
463        let mut src_file_history: HashSet<PathBuf> = HashSet::with_capacity(64);
464
465        src_strings.extend_from_slice(&self.get_includes()?);
466
467        let src_paths = match self.with {
468            CreateWith::Source(ref paths) => paths,
469            _ => panic!("Cannot build program. No source specified."),
470        };
471
472        for src_path in src_paths {
473            let mut src_bytes: Vec<u8> = Vec::with_capacity(100_000);
474
475            if src_file_history.contains(src_path) {
476                continue;
477            }
478            src_file_history.insert(src_path.clone());
479
480            let mut src_file_handle = File::open(src_path)?;
481
482            src_file_handle.read_to_end(&mut src_bytes)?;
483            src_bytes.shrink_to_fit();
484            src_strings.push(CString::new(src_bytes)?);
485        }
486
487        src_strings.extend_from_slice(&self.get_includes_eof()?);
488        src_strings.shrink_to_fit();
489        Ok(src_strings)
490    }
491
492    /// Returns a newly built Program.
493    //
494    // * TODO: If the context is associated with more than one device,
495    // check that at least one of those devices has been specified. An empty
496    // device list will cause an `OpenCL` error in that case.
497    // * TODO: Check for duplicate devices in the final device list.
498    // * TODO: Consider moving context to its own method.
499    #[cfg(not(feature = "opencl_version_2_1"))]
500    pub fn build(&self, context: &Context) -> OclResult<Program> {
501        let device_list = match self.device_spec {
502            Some(ref ds) => ds.to_device_list(context.platform()?)?,
503            None => context.devices(),
504        };
505
506        match self.with {
507            CreateWith::Il(_) => {
508                return Err("ocl::ProgramBuilder::build: Unreachable section (IL).".into());
509            }
510            CreateWith::Source(_) => Program::with_source(
511                context,
512                &self.get_src_strings()?,
513                Some(&device_list[..]),
514                &self.get_compiler_options()?,
515            )
516            .map_err(OclError::from),
517            CreateWith::Binaries(bins) => Program::with_binary(
518                context,
519                &device_list[..],
520                bins,
521                &self.get_compiler_options()?,
522            ),
523            CreateWith::None => {
524                return Err("Unable to build program: no source, binary, \
525                or IL has been specified"
526                    .into())
527            }
528        }
529    }
530
531    /// Returns a newly built Program.
532    //
533    // * TODO: If the context is associated with more than one device,
534    // check that at least one of those devices has been specified. An empty
535    // device list will cause an `OpenCL` error in that case.
536    //
537    // * TODO: Check for duplicate devices in the final device list.
538    #[cfg(feature = "opencl_version_2_1")]
539    pub fn build(&self, context: &Context) -> OclResult<Program> {
540        let device_list = match self.device_spec {
541            Some(ref ds) => ds.to_device_list(context.platform()?)?,
542            None => context.devices().to_owned(),
543        };
544
545        match self.with {
546            CreateWith::Il(il) => Program::with_il(
547                il,
548                Some(&device_list[..]),
549                &self.get_compiler_options()?,
550                context,
551            ),
552            CreateWith::Source(_) => Program::with_source(
553                context,
554                &self.get_src_strings()?,
555                Some(&device_list[..]),
556                &self.get_compiler_options()?,
557            ),
558            CreateWith::Binaries(bins) => Program::with_binary(
559                context,
560                &device_list[..],
561                bins,
562                &self.get_compiler_options()?,
563            ),
564            CreateWith::None => Err("Unable to build program: no source, binary, \
565                or IL has been specified"
566                .into()),
567        }
568    }
569}