ispc_rt/
lib.rs

1//! A small library meant to be used as a build dependency with Cargo for easily
2//! integrating [ISPC](https://ispc.github.io/) code into Rust projects. The
3//! `ispc_rt` crate is specifically targetted at linking with a previously
4//! compiled ISPC library and generated bindings (built with `ispc_compile`),
5//! to allow end users to link ISPC code without needing the ISPC compiler or clang.
6//!
7//! This crate also includes the various runtime components for the ISPC
8//! language, including the parallel task system and performance instrumentation.
9//!
10
11#![allow(dead_code)]
12
13extern crate libc;
14extern crate num_cpus;
15
16pub mod exec;
17pub mod instrument;
18pub mod task;
19
20use std::env;
21use std::ffi::CStr;
22use std::mem;
23use std::path::{Path, PathBuf};
24use std::sync::{Arc, Once};
25
26pub use crate::exec::{Parallel, TaskSystem};
27pub use crate::instrument::{Instrument, SimpleInstrument};
28pub use crate::task::ISPCTaskFn;
29
30/// Convenience macro for generating the module to hold the raw/unsafe ISPC bindings.
31///
32/// In addition to building the library with ISPC we use rust-bindgen to generate
33/// a rust module containing bindings to the functions exported from ISPC. These
34/// can be imported by passing the name of your library to the `ispc_module` macro.
35///
36/// # Example
37///
38/// ```ignore
39/// #[macro_use]
40/// extern crate ispc_rt;
41///
42/// // Functions exported from foo will be callable under foo::*
43/// ispc_module!(foo);
44/// ```
45#[macro_export]
46macro_rules! ispc_module {
47    ($lib:ident) => {
48        include!(concat!(env!("ISPC_OUT_DIR"), "/", stringify!($lib), ".rs"));
49    };
50}
51
52/// A `PackagedModule` refers to an ISPC module which was previously
53/// built using `ispc_compile`, and is now distributed with
54/// the crate.
55pub struct PackagedModule {
56    path: Option<PathBuf>,
57    lib: String,
58}
59
60impl PackagedModule {
61    /// Create a new `PackagedModule` to link against the previously compiled
62    /// library named `lib`. As in `ispc_compile`, the library name should not
63    /// have any prefix or suffix. For example, instead of `libexample.a` or
64    /// `example.lib`, simple pass `example`
65    pub fn new(lib: &str) -> PackagedModule {
66        PackagedModule {
67            path: None,
68            lib: lib.to_owned(),
69        }
70    }
71    /// Specify the path to search for the packaged ISPC libraries and bindings
72    pub fn lib_path<P: AsRef<Path>>(&mut self, path: P) -> &mut PackagedModule {
73        self.path = Some(path.as_ref().to_path_buf());
74        self
75    }
76    /// Link with a previously built ISPC library packaged with the crate
77    pub fn link(&self) {
78        let path = self.get_lib_path();
79        let libfile = self.lib.clone() + &env::var("TARGET").unwrap();
80        let bindgen_file = self.lib.clone() + ".rs";
81
82        println!("cargo:rustc-link-lib=static={libfile}");
83        println!(
84            "cargo:rerun-if-changed={}",
85            path.join(get_lib_filename(&libfile)).display()
86        );
87        println!(
88            "cargo:rerun-if-changed={}",
89            path.join(bindgen_file).display()
90        );
91        println!("cargo:rustc-link-search=native={}", path.display());
92        println!("cargo:rustc-env=ISPC_OUT_DIR={}", path.display());
93    }
94    /// Returns the user-set output directory if they've set one, otherwise
95    /// returns env("OUT_DIR")
96    fn get_lib_path(&self) -> PathBuf {
97        let p = self
98            .path
99            .clone()
100            .unwrap_or_else(|| env::var_os("OUT_DIR").map(PathBuf::from).unwrap());
101        if p.is_relative() {
102            env::current_dir().unwrap().join(p)
103        } else {
104            p
105        }
106    }
107}
108
109fn get_lib_filename(libfile: &str) -> String {
110    if libfile.contains("windows") {
111        format!("{libfile}.lib")
112    } else {
113        format!("lib{libfile}.a")
114    }
115}
116
117static mut TASK_SYSTEM: Option<&'static dyn TaskSystem> = None;
118static TASK_INIT: Once = Once::new();
119
120static mut INSTRUMENT: Option<&'static dyn Instrument> = None;
121static INSTRUMENT_INIT: Once = Once::new();
122
123/// If you have implemented your own task system you can provide it for use instead
124/// of the default threaded one. This must be done prior to calling ISPC code which
125/// spawns tasks otherwise the task system will have already been initialized to
126/// `Parallel`, which you can also see as an example for implementing a task system.
127///
128/// Use the function to do any extra initialization for your task system. Note that
129/// the task system will be leaked and not destroyed until the program exits and the
130/// memory space is cleaned up.
131pub fn set_task_system<F: FnOnce() -> Arc<dyn TaskSystem>>(f: F) {
132    TASK_INIT.call_once(|| {
133        let task_sys = f();
134        unsafe {
135            let s = &*task_sys as *const (dyn TaskSystem + 'static);
136            mem::forget(task_sys);
137            TASK_SYSTEM = Some(&*s);
138        }
139    });
140}
141
142fn get_task_system() -> &'static dyn TaskSystem {
143    // TODO: This is a bit nasty, but I'm not sure on a nicer solution. Maybe something that
144    // would let the user register the desired (or default) task system? But if
145    // mutable statics can't have destructors we still couldn't have an Arc or Box to something?
146    TASK_INIT.call_once(|| unsafe {
147        let task_sys = Parallel::new() as Arc<dyn TaskSystem>;
148        let s = &*task_sys as *const (dyn TaskSystem + 'static);
149        mem::forget(task_sys);
150        TASK_SYSTEM = Some(&*s);
151    });
152    unsafe { TASK_SYSTEM.unwrap() }
153}
154
155/// If you have implemented your own instrument for logging ISPC performance
156/// data you can use this function to provide it for use instead of the
157/// default one. This function **must** be called before calling into ISPC code,
158/// otherwise the instrumenter will already be set to the default.
159pub fn set_instrument<F: FnOnce() -> Arc<dyn Instrument>>(f: F) {
160    INSTRUMENT_INIT.call_once(|| {
161        let instrument = f();
162        unsafe {
163            let s = &*instrument as *const (dyn Instrument + 'static);
164            mem::forget(instrument);
165            INSTRUMENT = Some(&*s);
166        }
167    });
168}
169
170/// Print out a summary of performace data gathered from instrumenting ISPC.
171/// Must enable instrumenting to have this record and print data, see
172/// `Config::instrument`.
173pub fn print_instrumenting_summary() {
174    get_instrument().print_summary();
175}
176
177fn get_instrument() -> &'static dyn Instrument {
178    // TODO: This is a bit nasty, like above
179    INSTRUMENT_INIT.call_once(|| unsafe {
180        let instrument = Arc::new(SimpleInstrument) as Arc<dyn Instrument>;
181        let s = &*instrument as *const (dyn Instrument + 'static);
182        mem::forget(instrument);
183        INSTRUMENT = Some(&*s);
184    });
185    unsafe { INSTRUMENT.unwrap() }
186}
187
188#[allow(non_snake_case)]
189#[doc(hidden)]
190#[no_mangle]
191pub unsafe extern "C" fn ISPCAlloc(
192    handle_ptr: *mut *mut libc::c_void,
193    size: i64,
194    align: i32,
195) -> *mut libc::c_void {
196    get_task_system().alloc(handle_ptr, size, align)
197}
198
199#[allow(non_snake_case)]
200#[doc(hidden)]
201#[no_mangle]
202pub unsafe extern "C" fn ISPCLaunch(
203    handle_ptr: *mut *mut libc::c_void,
204    f: *mut libc::c_void,
205    data: *mut libc::c_void,
206    count0: libc::c_int,
207    count1: libc::c_int,
208    count2: libc::c_int,
209) {
210    let task_fn: ISPCTaskFn = mem::transmute(f);
211    get_task_system().launch(handle_ptr, task_fn, data, count0, count1, count2);
212}
213
214#[allow(non_snake_case)]
215#[doc(hidden)]
216#[no_mangle]
217pub unsafe extern "C" fn ISPCSync(handle: *mut libc::c_void) {
218    get_task_system().sync(handle);
219}
220
221#[allow(non_snake_case)]
222#[doc(hidden)]
223#[no_mangle]
224pub unsafe extern "C" fn ISPCInstrument(
225    cfile: *const libc::c_char,
226    cnote: *const libc::c_char,
227    line: libc::c_int,
228    mask: u64,
229) {
230    let file_name = CStr::from_ptr(cfile);
231    let note = CStr::from_ptr(cnote);
232    let active_count = mask.count_ones();
233    get_instrument().instrument(file_name, note, line, mask, active_count);
234}