1use derive_builder::Builder;
18use derive_more::Display;
19use teeny_compiler::compiler::backend::llvm::compiler::LogLevel;
20
21use crate::compiler::target::Capability;
22use crate::errors::{Error, Result};
23
24#[derive(Debug, Clone, Copy, Display)]
26pub enum Sanitizer {
27 #[display("memcheck")]
29 MemCheck,
30}
31
32#[derive(Debug, Clone, Copy, Display)]
34pub enum OptLevel {
35 #[display("0")]
37 O0,
38 #[display("1")]
40 O1,
41 #[display("2")]
43 O2,
44 #[display("3")]
46 O3,
47}
48
49#[derive(Builder)]
53pub struct Options {
54 #[builder(default = "false")]
56 pub allow_expensive_optimizations: bool,
57
58 #[builder(default = "false")]
60 pub compile_as_tools_patch: bool,
61
62 #[builder(default = "false")]
64 pub compile_only: bool,
65
66 #[builder(default = "false")]
68 pub def_load_cache: bool,
69
70 #[builder(default = "false")]
72 pub def_store_cache: bool,
73
74 #[builder(default = "false")]
76 pub device_debug: bool,
77
78 #[builder(default = "None")]
80 pub device_function_maxrregcount: Option<u32>,
81
82 #[builder(default = "false")]
84 pub disable_optimizer_constants: bool,
85
86 #[builder(default = "false")]
88 pub disable_warnings: bool,
89
90 #[builder(default = "false")]
92 pub dont_merge_basicblocks: bool,
93
94 #[builder(default = "String::from(\"entry_point\")")]
96 pub entry: String,
97
98 #[builder(default = "false")]
100 pub extensible_whole_program: bool,
101
102 #[builder(default = "false")]
104 pub fmad: bool,
105
106 #[builder(default = "false")]
108 pub force_load_cache: bool,
109
110 #[builder(default = "false")]
112 pub force_store_cache: bool,
113
114 #[builder(default = "false")]
116 pub generate_line_info: bool,
117
118 #[builder]
120 pub gpu_name: Capability,
121
122 #[builder(default = "None")]
130 pub ptx_version: Option<u32>,
131
132 #[builder(default = "None")]
138 pub log_level: Option<LogLevel>,
139
140 #[builder(default = "None")]
151 pub sm_count: Option<u32>,
152
153 #[builder(default = "None")]
155 pub maxrregcount: Option<u32>,
156
157 #[builder(default = "None")]
159 pub opt_level: Option<OptLevel>,
160
161 #[builder(default = "false")]
163 pub position_independent_code: bool,
164
165 #[builder(default = "false")]
167 pub preserve_relocs: bool,
168
169 #[builder(default = "false")]
171 pub return_at_end: bool,
172
173 #[builder(default = "None")]
175 pub sanitize: Option<Sanitizer>,
176
177 #[builder(default = "false")]
179 pub suppress_async_bulk_multicast_advisory_warning: bool,
180
181 #[builder(default = "false")]
183 pub suppress_stack_size_warning: bool,
184
185 #[builder(default = "false")]
187 pub verbose: bool,
188
189 #[builder(default = "false")]
191 pub warn_on_double_precision_use: bool,
192
193 #[builder(default = "false")]
195 pub warn_on_local_memory_usage: bool,
196
197 #[builder(default = "false")]
199 pub warn_on_spills: bool,
200
201 #[builder(default = "false")]
203 pub warning_as_error: bool,
204
205 #[builder(default = "None")]
207 pub maxntid: Option<u32>,
208
209 #[builder(default = "None")]
211 pub minnctapersm: Option<u32>,
212
213 #[builder(default = "false")]
215 pub override_directive_values: bool,
216
217 #[builder(default = "false")]
219 pub make_errors_visible_at_exit: bool,
220
221 #[builder(default = "None")]
223 pub ofast_compile: Option<u32>,
224
225 #[builder(default = "false")]
227 pub device_stack_protector: bool,
228
229 #[builder(default = "false")]
231 pub g_tensor_memory_access_check: bool,
232
233 #[builder(default = "false")]
235 pub gno_tensor_memory_access_check: bool,
236
237 #[builder(default = "None")]
239 pub split_compile: Option<u32>,
240}
241
242impl Options {
243 pub fn to_compile_options(&self) -> Vec<String> {
245 let mut args: Vec<String> = Vec::new();
246
247 if self.allow_expensive_optimizations {
248 args.push(String::from("--allow-expensive-optimizations"));
249 }
250
251 if self.compile_as_tools_patch {
252 args.push(String::from("--compile-as-tools-patch"));
253 }
254
255 if self.compile_only {
256 args.push(String::from("--compile-only"));
257 }
258
259 if self.def_load_cache {
260 args.push(String::from("--def-load-cache"));
261 }
262
263 if self.def_store_cache {
264 args.push(String::from("--def-store-cache"));
265 }
266
267 if self.device_debug {
268 args.push(String::from("--device-debug"));
269 }
270
271 if let Some(device_function_maxrregcount) = self.device_function_maxrregcount {
272 args.push(format!(
273 "--device-function-maxrregcount={}",
274 device_function_maxrregcount
275 ));
276 }
277
278 if self.disable_optimizer_constants {
279 args.push(String::from("--disable-optimizer-constants"));
280 }
281
282 if self.disable_warnings {
283 args.push(String::from("--disable-warnings"));
284 }
285
286 if self.dont_merge_basicblocks {
287 args.push(String::from("--dont-merge-basicblocks"));
288 }
289
290 args.push(format!("--entry={}", self.entry));
291
292 if self.extensible_whole_program {
293 args.push(String::from("--extensible-whole-program"));
294 }
295
296 if self.fmad {
297 args.push(String::from("--fmad"));
298 }
299
300 if self.force_load_cache {
301 args.push(String::from("--force-load-cache"));
302 }
303
304 if self.force_store_cache {
305 args.push(String::from("--force-store-cache"));
306 }
307
308 if self.generate_line_info {
309 args.push(String::from("--generate-line-info"));
310 }
311
312 args.push(format!("--gpu-name={}", self.gpu_name));
313
314 if let Some(maxrregcount) = self.maxrregcount {
315 args.push(format!("--maxrregcount={}", maxrregcount));
316 }
317
318 if let Some(opt_level) = self.opt_level {
319 args.push(format!("--opt-level={}", opt_level));
320 }
321
322 if self.position_independent_code {
323 args.push(String::from("--position-independent-code"));
324 }
325
326 if self.preserve_relocs {
327 args.push(String::from("--preserve-relocs"));
328 }
329
330 if self.return_at_end {
331 args.push(String::from("--return-at-end"));
332 }
333
334 if let Some(sanitize) = self.sanitize {
335 args.push(format!("--sanitize={}", sanitize));
336 }
337
338 if self.suppress_async_bulk_multicast_advisory_warning {
339 args.push(String::from(
340 "--suppress-async-bulk-multicast-advisory-warning",
341 ));
342 }
343
344 if self.suppress_stack_size_warning {
345 args.push(String::from("--suppress-stack-size-warning"));
346 }
347
348 if self.verbose {
349 args.push(String::from("--verbose"));
350 }
351
352 if self.warn_on_double_precision_use {
353 args.push(String::from("--warn-on-double-precision-use"));
354 }
355
356 if self.warn_on_local_memory_usage {
357 args.push(String::from("--warn-on-local-memory-usage"));
358 }
359
360 if self.warn_on_spills {
361 args.push(String::from("--warn-on-spills"));
362 }
363
364 if self.warning_as_error {
365 args.push(String::from("--warning-as-error"));
366 }
367
368 if let Some(maxntid) = self.maxntid {
369 args.push(format!("--maxntid={}", maxntid));
370 }
371
372 if let Some(minnctapersm) = self.minnctapersm {
373 args.push(format!("--minnctapersm={}", minnctapersm));
374 }
375
376 if self.override_directive_values {
377 args.push(String::from("--override-directive-values"));
378 }
379
380 if self.make_errors_visible_at_exit {
381 args.push(String::from("--make-errors-visible-at-exit"));
382 }
383
384 if let Some(ofast_compile) = self.ofast_compile {
385 args.push(format!("--oFast-compile={}", ofast_compile));
386 }
387
388 if self.device_stack_protector {
389 args.push(String::from("--device-stack-protector"));
390 }
391
392 if self.g_tensor_memory_access_check {
393 args.push(String::from("--g-tensor-memory-access-check"));
394 }
395
396 if self.gno_tensor_memory_access_check {
397 args.push(String::from("--gno-tensor-memory-access-check"));
398 }
399
400 if let Some(split_compile) = self.split_compile {
401 args.push(format!("--split-compile={}", split_compile));
402 }
403
404 args
405 }
406}
407
408impl Options {
409 pub fn parse(input: &str) -> Result<Options> {
417 let mut builder = OptionsBuilder::default();
418 let mut capability: Option<Capability> = None;
419
420 for pair in input.split(',').map(str::trim).filter(|s| !s.is_empty()) {
421 let (raw_key, value) = match pair.split_once('=') {
422 Some((k, v)) => (k.trim(), Some(v.trim())),
423 None => (pair, None),
424 };
425 let key = raw_key.to_ascii_lowercase().replace('_', "-");
426
427 match key.as_str() {
428 "capability" | "gpu-name" => {
429 let v = require_value(input, &key, value)?;
430 capability =
431 Some(
432 v.parse::<Capability>()
433 .map_err(|reason| Error::InvalidOptions {
434 input: input.to_string(),
435 reason,
436 })?,
437 );
438 }
439 "ptx-version" => {
440 builder.ptx_version(Some(parse_u32(input, &key, value)?));
441 }
442 "sm-count" => {
443 builder.sm_count(Some(parse_u32(input, &key, value)?));
444 }
445 "log-level" => {
446 let v = require_value(input, &key, value)?;
447 builder.log_level(Some(v.parse::<LogLevel>().map_err(|reason| {
448 Error::InvalidOptions {
449 input: input.to_string(),
450 reason,
451 }
452 })?));
453 }
454 "allow-expensive-optimizations" => {
455 builder.allow_expensive_optimizations(parse_bool(input, &key, value)?);
456 }
457 "compile-as-tools-patch" => {
458 builder.compile_as_tools_patch(parse_bool(input, &key, value)?);
459 }
460 "compile-only" => {
461 builder.compile_only(parse_bool(input, &key, value)?);
462 }
463 "def-load-cache" => {
464 builder.def_load_cache(parse_bool(input, &key, value)?);
465 }
466 "def-store-cache" => {
467 builder.def_store_cache(parse_bool(input, &key, value)?);
468 }
469 "device-debug" => {
470 builder.device_debug(parse_bool(input, &key, value)?);
471 }
472 "device-function-maxrregcount" => {
473 builder.device_function_maxrregcount(Some(parse_u32(input, &key, value)?));
474 }
475 "disable-optimizer-constants" => {
476 builder.disable_optimizer_constants(parse_bool(input, &key, value)?);
477 }
478 "disable-warnings" => {
479 builder.disable_warnings(parse_bool(input, &key, value)?);
480 }
481 "dont-merge-basicblocks" => {
482 builder.dont_merge_basicblocks(parse_bool(input, &key, value)?);
483 }
484 "entry" => {
485 builder.entry(require_value(input, &key, value)?.to_string());
486 }
487 "extensible-whole-program" => {
488 builder.extensible_whole_program(parse_bool(input, &key, value)?);
489 }
490 "fmad" => {
491 builder.fmad(parse_bool(input, &key, value)?);
492 }
493 "force-load-cache" => {
494 builder.force_load_cache(parse_bool(input, &key, value)?);
495 }
496 "force-store-cache" => {
497 builder.force_store_cache(parse_bool(input, &key, value)?);
498 }
499 "generate-line-info" => {
500 builder.generate_line_info(parse_bool(input, &key, value)?);
501 }
502 "maxnreg" | "maxrregcount" => {
503 builder.maxrregcount(Some(parse_u32(input, &key, value)?));
504 }
505 "opt-level" => {
506 builder.opt_level(Some(parse_opt_level(input, &key, value)?));
507 }
508 "position-independent-code" => {
509 builder.position_independent_code(parse_bool(input, &key, value)?);
510 }
511 "preserve-relocs" => {
512 builder.preserve_relocs(parse_bool(input, &key, value)?);
513 }
514 "return-at-end" => {
515 builder.return_at_end(parse_bool(input, &key, value)?);
516 }
517 "sanitize" => {
518 builder.sanitize(Some(parse_sanitizer(input, &key, value)?));
519 }
520 "suppress-async-bulk-multicast-advisory-warning" => {
521 builder.suppress_async_bulk_multicast_advisory_warning(parse_bool(
522 input, &key, value,
523 )?);
524 }
525 "suppress-stack-size-warning" => {
526 builder.suppress_stack_size_warning(parse_bool(input, &key, value)?);
527 }
528 "verbose" => {
529 builder.verbose(parse_bool(input, &key, value)?);
530 }
531 "warn-on-double-precision-use" => {
532 builder.warn_on_double_precision_use(parse_bool(input, &key, value)?);
533 }
534 "warn-on-local-memory-usage" => {
535 builder.warn_on_local_memory_usage(parse_bool(input, &key, value)?);
536 }
537 "warn-on-spills" => {
538 builder.warn_on_spills(parse_bool(input, &key, value)?);
539 }
540 "warning-as-error" => {
541 builder.warning_as_error(parse_bool(input, &key, value)?);
542 }
543 "maxntid" => {
544 builder.maxntid(Some(parse_u32(input, &key, value)?));
545 }
546 "minnctapersm" => {
547 builder.minnctapersm(Some(parse_u32(input, &key, value)?));
548 }
549 "override-directive-values" => {
550 builder.override_directive_values(parse_bool(input, &key, value)?);
551 }
552 "make-errors-visible-at-exit" => {
553 builder.make_errors_visible_at_exit(parse_bool(input, &key, value)?);
554 }
555 "ofast-compile" => {
556 builder.ofast_compile(Some(parse_u32(input, &key, value)?));
557 }
558 "device-stack-protector" => {
559 builder.device_stack_protector(parse_bool(input, &key, value)?);
560 }
561 "g-tensor-memory-access-check" => {
562 builder.g_tensor_memory_access_check(parse_bool(input, &key, value)?);
563 }
564 "gno-tensor-memory-access-check" => {
565 builder.gno_tensor_memory_access_check(parse_bool(input, &key, value)?);
566 }
567 "split-compile" => {
568 builder.split_compile(Some(parse_u32(input, &key, value)?));
569 }
570 other => {
571 return Err(Error::InvalidOptions {
572 input: input.to_string(),
573 reason: format!("unknown option key '{other}'"),
574 }
575 .into());
576 }
577 }
578 }
579
580 let capability = capability.ok_or_else(|| Error::InvalidOptions {
581 input: input.to_string(),
582 reason: "missing required 'capability' key, e.g. capability=sm_90".to_string(),
583 })?;
584 builder.gpu_name(capability);
585
586 builder.build().map_err(|e| {
587 Error::InvalidOptions {
588 input: input.to_string(),
589 reason: e.to_string(),
590 }
591 .into()
592 })
593 }
594}
595
596fn require_value<'a>(input: &str, key: &str, value: Option<&'a str>) -> Result<&'a str> {
597 value.ok_or_else(|| {
598 Error::InvalidOptions {
599 input: input.to_string(),
600 reason: format!("'{key}' requires a value, e.g. {key}=<value>"),
601 }
602 .into()
603 })
604}
605
606fn parse_bool(input: &str, key: &str, value: Option<&str>) -> Result<bool> {
607 match value {
608 None => Ok(true),
609 Some(v) => match v.to_ascii_lowercase().as_str() {
610 "true" | "1" | "yes" => Ok(true),
611 "false" | "0" | "no" => Ok(false),
612 other => Err(Error::InvalidOptions {
613 input: input.to_string(),
614 reason: format!("invalid boolean value '{other}' for '{key}'"),
615 }
616 .into()),
617 },
618 }
619}
620
621fn parse_u32(input: &str, key: &str, value: Option<&str>) -> Result<u32> {
622 let v = require_value(input, key, value)?;
623 v.parse::<u32>().map_err(|_| {
624 Error::InvalidOptions {
625 input: input.to_string(),
626 reason: format!("invalid integer value '{v}' for '{key}'"),
627 }
628 .into()
629 })
630}
631
632fn parse_opt_level(input: &str, key: &str, value: Option<&str>) -> Result<OptLevel> {
633 let v = require_value(input, key, value)?;
634 match v.to_ascii_lowercase().as_str() {
635 "0" | "o0" => Ok(OptLevel::O0),
636 "1" | "o1" => Ok(OptLevel::O1),
637 "2" | "o2" => Ok(OptLevel::O2),
638 "3" | "o3" => Ok(OptLevel::O3),
639 other => Err(Error::InvalidOptions {
640 input: input.to_string(),
641 reason: format!("invalid opt-level '{other}'; expected one of 0, 1, 2, 3"),
642 }
643 .into()),
644 }
645}
646
647fn parse_sanitizer(input: &str, key: &str, value: Option<&str>) -> Result<Sanitizer> {
648 let v = require_value(input, key, value)?;
649 match v.to_ascii_lowercase().as_str() {
650 "memcheck" => Ok(Sanitizer::MemCheck),
651 other => Err(Error::InvalidOptions {
652 input: input.to_string(),
653 reason: format!("invalid sanitize value '{other}'; expected 'memcheck'"),
654 }
655 .into()),
656 }
657}
658
659#[cfg(test)]
660mod parse_tests {
661 use super::*;
662
663 #[test]
664 fn parses_capability_and_maxnreg_alias() {
665 let opts = Options::parse("capability=sm_90,maxnreg=16").unwrap();
666 assert_eq!(opts.gpu_name, Capability::Sm90);
667 assert_eq!(opts.maxrregcount, Some(16));
668 }
669
670 #[test]
671 fn bare_bool_flag_means_true() {
672 let opts = Options::parse("capability=sm_90,verbose").unwrap();
673 assert!(opts.verbose);
674 }
675
676 #[test]
677 fn missing_capability_errors() {
678 assert!(Options::parse("maxnreg=16").is_err());
679 }
680
681 #[test]
682 fn unknown_key_errors() {
683 assert!(Options::parse("capability=sm_90,shared-memory=25k").is_err());
684 }
685
686 #[test]
687 fn parses_ptx_version_override() {
688 let opts = Options::parse("capability=sm_87,ptx-version=82").unwrap();
689 assert_eq!(opts.gpu_name, Capability::Sm87);
690 assert_eq!(opts.ptx_version, Some(82));
691 }
692
693 #[test]
694 fn ptx_version_defaults_to_none() {
695 let opts = Options::parse("capability=sm_87").unwrap();
696 assert_eq!(opts.ptx_version, None);
697 }
698
699 #[test]
700 fn parses_sm_count_override() {
701 let opts = Options::parse("capability=sm_87,sm-count=20").unwrap();
702 assert_eq!(opts.gpu_name, Capability::Sm87);
703 assert_eq!(opts.sm_count, Some(20));
704 }
705
706 #[test]
707 fn sm_count_defaults_to_none() {
708 let opts = Options::parse("capability=sm_87").unwrap();
709 assert_eq!(opts.sm_count, None);
710 }
711
712 #[test]
713 fn parses_log_level() {
714 let opts = Options::parse("capability=sm_87,log-level=debug").unwrap();
715 assert_eq!(opts.log_level, Some(LogLevel::Debug));
716 }
717
718 #[test]
719 fn log_level_defaults_to_none() {
720 let opts = Options::parse("capability=sm_87").unwrap();
721 assert_eq!(opts.log_level, None);
722 }
723
724 #[test]
725 fn invalid_log_level_errors() {
726 assert!(Options::parse("capability=sm_87,log-level=verbose").is_err());
727 }
728}