1pub mod opt;
24
25pub use bindgen;
26
27use std::collections::BTreeSet;
28use std::env;
29use std::fmt::Display;
30use std::fs::File;
31use std::io::{BufRead, BufReader, Write};
32use std::path::{Path, PathBuf};
33use std::process::{Command, ExitStatus};
34
35use regex::Regex;
36use semver::{BuildMetadata, Prerelease, Version};
37
38pub use crate::opt::{
39 Addressing, Architecture, MathLib, OptimizationOpt, TargetISA, TargetOS, CPU,
40};
41
42pub fn compile_library(lib: &str, files: &[&str]) {
57 let mut cfg = Config::new();
58 for f in files {
59 cfg.file(*f);
60 }
61 cfg.compile(lib)
62}
63
64macro_rules! exit_failure {
67 ($fmt:expr) => {{
68 eprintln!($fmt);
69 std::process::exit(libc::EXIT_FAILURE);
70 }};
71 ($fmt:expr, $($arg:tt)*) => {{
72 eprintln!($fmt, $($arg)*);
73 std::process::exit(libc::EXIT_FAILURE);
74 }}
75}
76
77pub struct Config {
79 ispc_version: Version,
80 ispc_files: Vec<PathBuf>,
81 include_paths: Vec<PathBuf>,
82 out_dir: Option<PathBuf>,
84 debug: Option<bool>,
85 opt_level: Option<u32>,
86 target: Option<String>,
87 cargo_metadata: bool,
88 defines: Vec<(String, Option<String>)>,
90 math_lib: MathLib,
91 addressing: Option<Addressing>,
92 optimization_opts: BTreeSet<OptimizationOpt>,
93 cpu_target: Option<CPU>,
94 force_alignment: Option<u32>,
95 no_omit_frame_ptr: bool,
96 no_stdlib: bool,
97 no_cpp: bool,
98 quiet: bool,
99 werror: bool,
100 woff: bool,
101 wno_perf: bool,
102 instrument: bool,
103 enable_llvm_intrinsics: bool,
104 target_isa: Option<Vec<TargetISA>>,
105 architecture: Option<Architecture>,
106 target_os: Option<TargetOS>,
107 darwin_version_min: Option<(u32, u32)>,
108 bindgen_builder: bindgen::Builder,
109}
110
111impl Config {
112 pub fn new() -> Config {
113 let cmd_output = Command::new("ispc")
116 .arg("--version")
117 .output()
118 .expect("Failed to find ISPC compiler in PATH");
119 if !cmd_output.status.success() {
120 exit_failure!("Failed to get ISPC version, is it in your PATH?");
121 }
122 let ver_string = String::from_utf8_lossy(&cmd_output.stdout);
123 let re = Regex::new(r"(\d+\.\d+\.\d+)").unwrap();
125 let ispc_ver = Version::parse(
126 re.captures_iter(&ver_string)
127 .next()
128 .expect("Failed to parse ISPC version")
129 .get(1)
130 .expect("Failed to parse ISPC version")
131 .as_str(),
132 )
133 .expect("Failed to parse ISPC version");
134
135 Config {
136 ispc_version: ispc_ver,
137 ispc_files: Vec::new(),
138 include_paths: Vec::new(),
139 out_dir: None,
140 debug: None,
141 opt_level: None,
142 target: None,
143 cargo_metadata: true,
144 defines: Vec::new(),
145 math_lib: MathLib::ISPCDefault,
146 addressing: None,
147 optimization_opts: BTreeSet::new(),
148 cpu_target: None,
149 force_alignment: None,
150 no_omit_frame_ptr: false,
151 no_stdlib: false,
152 no_cpp: false,
153 quiet: false,
154 werror: false,
155 woff: false,
156 wno_perf: false,
157 instrument: false,
158 enable_llvm_intrinsics: false,
159 target_isa: None,
160 architecture: None,
161 target_os: None,
162 darwin_version_min: None,
163 bindgen_builder: Default::default(),
164 }
165 }
166 pub fn file<P: AsRef<Path>>(&mut self, file: P) -> &mut Config {
168 self.ispc_files.push(file.as_ref().to_path_buf());
169 self
170 }
171 pub fn out_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Config {
173 self.out_dir = Some(dir.as_ref().to_path_buf());
174 self
175 }
176 pub fn debug(&mut self, debug: bool) -> &mut Config {
179 self.debug = Some(debug);
180 self
181 }
182 pub fn opt_level(&mut self, opt_level: u32) -> &mut Config {
184 self.opt_level = Some(opt_level);
185 self
186 }
187 pub fn target(&mut self, target: &str) -> &mut Config {
189 self.target = Some(target.to_string());
190 self
191 }
192 pub fn add_define(&mut self, define: &str, value: Option<&str>) -> &mut Config {
195 self.defines
196 .push((define.to_string(), value.map(|s| s.to_string())));
197 self
198 }
199 pub fn addressing(&mut self, addressing: Addressing) -> &mut Config {
201 self.addressing = Some(addressing);
202 self
203 }
204 pub fn math_lib(&mut self, math_lib: MathLib) -> &mut Config {
206 self.math_lib = math_lib;
207 self
208 }
209 pub fn optimization_opt(&mut self, opt: OptimizationOpt) -> &mut Config {
211 self.optimization_opts.insert(opt);
212 self
213 }
214 pub fn cpu(&mut self, cpu: CPU) -> &mut Config {
217 self.cpu_target = Some(cpu);
218 self
219 }
220 pub fn force_alignment(&mut self, alignment: u32) -> &mut Config {
222 self.force_alignment = Some(alignment);
223 self
224 }
225 pub fn include_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Config {
227 self.include_paths.push(path.as_ref().to_path_buf());
228 self
229 }
230 pub fn no_omit_frame_pointer(&mut self) -> &mut Config {
233 self.no_omit_frame_ptr = true;
234 self
235 }
236 pub fn no_stdlib(&mut self) -> &mut Config {
238 self.no_stdlib = true;
239 self
240 }
241 pub fn no_cpp(&mut self) -> &mut Config {
243 self.no_cpp = true;
244 self
245 }
246 pub fn quiet(&mut self) -> &mut Config {
248 self.quiet = true;
249 self
250 }
251 pub fn werror(&mut self) -> &mut Config {
253 self.werror = true;
254 self
255 }
256 pub fn woff(&mut self) -> &mut Config {
258 self.woff = true;
259 self
260 }
261 pub fn wno_perf(&mut self) -> &mut Config {
263 self.wno_perf = true;
264 self
265 }
266 pub fn instrument(&mut self) -> &mut Config {
269 let min_ver = Version {
270 major: 1,
271 minor: 9,
272 patch: 1,
273 pre: Prerelease::EMPTY,
274 build: BuildMetadata::EMPTY,
275 };
276 if self.ispc_version < min_ver {
277 exit_failure!(
278 "Error: instrumentation is not supported on ISPC versions \
279 older than 1.9.1 as it generates a non-C compatible header"
280 );
281 }
282 self.instrument = true;
283 self
284 }
285 pub fn enable_llvm_intrinsics(&mut self) -> &mut Config {
287 self.enable_llvm_intrinsics = true;
288 self
289 }
290 pub fn target_isa(&mut self, target: TargetISA) -> &mut Config {
293 self.target_isa = Some(vec![target]);
294 self
295 }
296 pub fn target_isas(&mut self, targets: Vec<TargetISA>) -> &mut Config {
301 self.target_isa = Some(targets);
302 self
303 }
304 pub fn target_arch(&mut self, arch: Architecture) -> &mut Config {
306 self.architecture = Some(arch);
307 self
308 }
309 pub fn target_os(&mut self, os: TargetOS) -> &mut Config {
311 self.target_os = Some(os);
312 self
313 }
314 pub fn darwin_version_min(&mut self, major: u32, minor: u32) -> &mut Config {
316 self.darwin_version_min = Some((major, minor));
317 self
318 }
319 pub fn cargo_metadata(&mut self, metadata: bool) -> &mut Config {
321 self.cargo_metadata = metadata;
322 self
323 }
324 pub fn bindgen_builder(&mut self, builder: bindgen::Builder) -> &mut Self {
325 self.bindgen_builder = builder;
326 self
327 }
328 pub fn compile(&self, lib: &str) {
331 let dst = self.get_out_dir();
332 let build_dir = self.get_build_dir();
333 let default_args = self.default_args();
334 dbg!(&default_args);
335 let mut objects = vec![];
336 let mut headers = vec![];
337 for s in &self.ispc_files {
338 let fname = s
339 .file_stem()
340 .expect("ISPC source files must be files")
341 .to_str()
342 .expect("ISPC source file names must be valid UTF-8");
343 self.print(&format!("cargo:rerun-if-changed={}", s.display()));
344
345 let ispc_fname = String::from(fname) + "_ispc";
346 let object = build_dir.join(ispc_fname.clone()).with_extension("o");
347 let header = build_dir.join(ispc_fname.clone()).with_extension("h");
348 let deps = build_dir.join(ispc_fname.clone()).with_extension("idep");
349 let output = Command::new("ispc")
350 .args(&default_args)
351 .arg(s)
352 .arg("-o")
353 .arg(&object)
354 .arg("-h")
355 .arg(&header)
356 .arg("-MMM")
357 .arg(&deps)
358 .output()
359 .unwrap();
360
361 if !output.stderr.is_empty() {
362 let stderr = String::from_utf8_lossy(&output.stderr);
363 for l in stderr.lines() {
364 self.print(&format!("cargo:warning=(ISPC) {l}"));
365 }
366 }
367 if !output.status.success() {
368 exit_failure!("Failed to compile ISPC source file {}", s.display());
369 }
370 objects.push(object);
371 headers.push(header);
372
373 let deps_list = File::open(deps)
375 .unwrap_or_else(|_| panic!("Failed to open dependencies list for {}", s.display()));
376 let reader = BufReader::new(deps_list);
377 for d in reader.lines() {
378 let dep_name = d.unwrap();
380 self.print(&format!("cargo:rerun-if-changed={dep_name}"));
381 }
382
383 if let Some(ref t) = self.target_isa {
385 if t.len() > 1 {
386 for isa in t.iter() {
387 let isa_fname = ispc_fname.clone() + "_" + &isa.lib_suffix();
388 let isa_obj = build_dir.join(isa_fname).with_extension("o");
389 objects.push(isa_obj);
390 }
391 }
392 }
393 }
394 let libfile = lib.to_owned() + &self.get_target();
395 if !self.assemble(&libfile, &objects).success() {
396 exit_failure!("Failed to assemble ISPC objects into library {lib}");
397 }
398 self.print(&format!("cargo:rustc-link-lib=static={libfile}"));
399
400 let bindgen_header = self.generate_bindgen_header(lib, &headers);
402 let bindings = self
403 .bindgen_builder
404 .clone()
405 .header(bindgen_header.to_str().unwrap());
406
407 let bindgen_file = dst.join(lib).with_extension("rs");
408
409 let generated_bindings = match bindings.generate() {
410 Ok(b) => b.to_string(),
411 Err(_) => exit_failure!("Failed to generating Rust bindings to {}", lib),
412 };
413 let mut file = match File::create(bindgen_file) {
414 Ok(f) => f,
415 Err(e) => exit_failure!("Failed to open bindgen mod file for writing: {}", e),
416 };
417 file.write_all("#[allow(non_camel_case_types,dead_code,non_upper_case_globals,non_snake_case,improper_ctypes)]\n"
418 .as_bytes()).unwrap();
419 file.write_all(format!("pub mod {lib} {{\n").as_bytes())
420 .unwrap();
421 file.write_all(generated_bindings.as_bytes()).unwrap();
422 file.write_all(b"}").unwrap();
423
424 self.print(&format!("cargo:rustc-link-search=native={}", dst.display()));
425 self.print(&format!("cargo:rustc-env=ISPC_OUT_DIR={}", dst.display()));
426 }
427 pub fn ispc_version(&self) -> &Version {
429 &self.ispc_version
430 }
431 #[cfg(unix)]
433 fn assemble(&self, lib: &str, objects: &[PathBuf]) -> ExitStatus {
434 Command::new("ar")
435 .arg("crus")
436 .arg(format!("lib{lib}.a"))
437 .args(objects)
438 .current_dir(self.get_out_dir())
439 .status()
440 .unwrap()
441 }
442 #[cfg(windows)]
444 fn assemble(&self, lib: &str, objects: &[PathBuf]) -> ExitStatus {
445 let target = self.get_target();
446 let mut lib_cmd = cc::windows_registry::find_tool(&target, "lib.exe")
447 .expect("Failed to find lib.exe for MSVC toolchain, aborting")
448 .to_command();
449 lib_cmd
450 .arg(format!("/OUT:{lib}.lib"))
451 .args(objects)
452 .current_dir(self.get_out_dir())
453 .status()
454 .unwrap()
455 }
456 fn generate_bindgen_header(&self, lib: &str, headers: &[PathBuf]) -> PathBuf {
459 let bindgen_header = self
460 .get_build_dir()
461 .join(format!("_{lib}_ispc_bindgen_header.h"));
462 let mut include_file = File::create(&bindgen_header).unwrap();
463
464 writeln!(include_file, "#include <stdint.h>").unwrap();
465 writeln!(include_file, "#include <stdbool.h>").unwrap();
466
467 for h in headers {
468 writeln!(include_file, "#include \"{}\"", h.display()).unwrap();
469 }
470 bindgen_header
471 }
472 fn default_args(&self) -> Vec<String> {
474 let mut ispc_args = Vec::new();
475 let opt_level = self.get_opt_level();
476 if self.get_debug() {
477 ispc_args.push(String::from("-g"));
478 }
479 if let Some(ref c) = self.cpu_target {
480 ispc_args.push(c.to_string());
481 if *c != CPU::Generic || (*c == CPU::Generic && opt_level != 0) {
484 ispc_args.push(format!("-O{opt_level}"));
485 } else {
486 self.print(
487 &"cargo:warning=ispc-rs: Omitting -O0 on CPU::Generic target, ispc bug 1223",
488 );
489 }
490 } else {
491 ispc_args.push(format!("-O{opt_level}"));
492 }
493
494 if cfg!(unix) {
496 ispc_args.push(String::from("--pic"));
497 }
498 let target = self.get_target();
499 if target.starts_with("i686") {
500 ispc_args.push(String::from("--arch=x86"));
501 } else if target.starts_with("x86_64") {
502 ispc_args.push(String::from("--arch=x86-64"));
503 } else if target.starts_with("aarch64") {
504 ispc_args.push(String::from("--arch=aarch64"));
505 }
506 for (name, value) in &self.defines {
507 match value {
508 Some(value) => ispc_args.push(format!("-D{name}={value}")),
509 None => ispc_args.push(format!("-D{name}")),
510 }
511 }
512 ispc_args.push(self.math_lib.to_string());
513 if let Some(ref s) = self.addressing {
514 ispc_args.push(s.to_string());
515 }
516 if let Some(ref f) = self.force_alignment {
517 ispc_args.push(format!("--force-alignment={f}"));
518 }
519 for o in &self.optimization_opts {
520 ispc_args.push(o.to_string());
521 }
522 for p in &self.include_paths {
523 ispc_args.push(format!("-I{}", p.display()));
524 }
525 if self.no_omit_frame_ptr {
526 ispc_args.push(String::from("--no-omit-frame-pointer"));
527 }
528 if self.no_stdlib {
529 ispc_args.push(String::from("--nostdlib"));
530 }
531 if self.no_cpp {
532 ispc_args.push(String::from("--nocpp"));
533 }
534 if self.quiet {
535 ispc_args.push(String::from("--quiet"));
536 }
537 if self.werror {
538 ispc_args.push(String::from("--werror"));
539 }
540 if self.woff {
541 ispc_args.push(String::from("--woff"));
542 }
543 if self.wno_perf {
544 ispc_args.push(String::from("--wno-perf"));
545 }
546 if self.instrument {
547 ispc_args.push(String::from("--instrument"));
548 }
549 if self.enable_llvm_intrinsics {
550 ispc_args.push(String::from("--enable-llvm-intrinsics"));
551 }
552 if let Some(ref t) = self.target_isa {
553 let mut isa_str = String::from("--target=");
554 for (i, isa) in t.iter().enumerate() {
555 if i > 0 {
556 isa_str.push(',');
557 }
558 use std::fmt::Write as _;
559 let _ = write!(isa_str, "{isa}");
560 }
561 ispc_args.push(isa_str);
562 } else if target.starts_with("aarch64") {
563 ispc_args.push(String::from("--target=neon-i32x4"));
568 }
569 if let Some(ref a) = self.architecture {
570 ispc_args.push(a.to_string());
571 }
572 if let Some(ref o) = self.target_os {
573 ispc_args.push(o.to_string());
574 }
575 if let Some((maj, min)) = self.darwin_version_min {
576 ispc_args.push(format!("--darwin-version-min={maj}.{min}"));
577 }
578 ispc_args
579 }
580 fn get_out_dir(&self) -> PathBuf {
583 let p = self
584 .out_dir
585 .clone()
586 .unwrap_or_else(|| env::var_os("OUT_DIR").map(PathBuf::from).unwrap());
587 if p.is_relative() {
588 env::current_dir().unwrap().join(p)
589 } else {
590 p
591 }
592 }
593 fn get_build_dir(&self) -> PathBuf {
595 env::var_os("OUT_DIR").map(PathBuf::from).unwrap()
596 }
597 fn get_debug(&self) -> bool {
600 self.debug
601 .unwrap_or_else(|| env::var("DEBUG").unwrap() == "true")
602 }
603 fn get_opt_level(&self) -> u32 {
606 self.opt_level.unwrap_or_else(|| {
607 let opt = env::var("OPT_LEVEL").unwrap();
608 opt.parse::<u32>().unwrap()
609 })
610 }
611 fn get_target(&self) -> String {
614 self.target
615 .clone()
616 .unwrap_or_else(|| env::var("TARGET").unwrap())
617 }
618 fn print<T: Display>(&self, s: &T) {
620 if self.cargo_metadata {
621 println!("{s}");
622 }
623 }
624}
625
626impl Default for Config {
627 fn default() -> Config {
628 Config::new()
629 }
630}