1#[cfg(feature = "std")]
2use alloc::{borrow::ToOwned, format, string::ToString, vec};
3use alloc::{string::String, sync::Arc, vec::Vec};
4#[cfg(feature = "std")]
5use std::ffi::OsString;
6
7#[cfg(feature = "std")]
8use clap::{Parser, builder::ArgPredicate};
9use midenc_session::{
10 ColorChoice, DebugInfo, InputFile, LinkLibrary, OptLevel, Options, OutputFile, OutputType,
11 OutputTypeSpec, OutputTypes, Path, PathBuf, ProjectType, Session, TargetEnv, Verbosity,
12 Warnings, add_target_link_libraries,
13 diagnostics::{DefaultSourceManager, Emitter},
14};
15
16#[derive(Debug)]
18#[cfg_attr(feature = "std", derive(Parser))]
19#[cfg_attr(feature = "std", command(name = "midenc"))]
20pub struct Compiler {
21 #[cfg_attr(
25 feature = "std",
26 arg(
27 long,
28 value_name = "DIR",
29 env = "MIDENC_TARGET_DIR",
30 default_value = "target/midenc",
31 help_heading = "Output"
32 )
33 )]
34 pub target_dir: PathBuf,
35 #[cfg_attr(
39 feature = "std",
40 arg(long, value_name = "DIR", help_heading = "Output")
41 )]
42 pub working_dir: Option<PathBuf>,
43 #[cfg_attr(
47 feature = "std",
48 arg(
49 long,
50 value_name = "DIR",
51 env = "MIDENC_SYSROOT",
52 help_heading = "Compiler"
53 )
54 )]
55 pub sysroot: Option<PathBuf>,
56 #[cfg_attr(
58 feature = "std",
59 arg(
60 long,
61 short = 'O',
62 value_name = "DIR",
63 env = "MIDENC_OUT_DIR",
64 help_heading = "Output"
65 )
66 )]
67 pub output_dir: Option<PathBuf>,
68 #[cfg_attr(
70 feature = "std",
71 arg(long, short = 'o', value_name = "FILENAME", help_heading = "Output")
72 )]
73 pub output_file: Option<PathBuf>,
74 #[cfg_attr(
76 feature = "std",
77 arg(long, conflicts_with("output_file"), help_heading = "Output")
78 )]
79 pub stdout: bool,
80 #[cfg_attr(feature = "std", arg(
85 long,
86 short = 'n',
87 value_name = "NAME",
88 default_value = None,
89 help_heading = "Diagnostics"
90 ))]
91 pub name: Option<String>,
92 #[cfg_attr(feature = "std", arg(
94 long = "verbose",
95 short = 'v',
96 value_enum,
97 value_name = "LEVEL",
98 default_value_t = Verbosity::Info,
99 default_missing_value = "debug",
100 num_args(0..=1),
101 help_heading = "Diagnostics"
102 ))]
103 pub verbosity: Verbosity,
104 #[cfg_attr(feature = "std", arg(
106 long,
107 short = 'W',
108 value_enum,
109 value_name = "LEVEL",
110 default_value_t = Warnings::All,
111 default_missing_value = "all",
112 num_args(0..=1),
113 help_heading = "Diagnostics"
114 ))]
115 pub warn: Warnings,
116 #[cfg_attr(feature = "std", arg(
118 long,
119 value_enum,
120 default_value_t = ColorChoice::Auto,
121 default_missing_value = "auto",
122 num_args(0..=1),
123 help_heading = "Diagnostics"
124 ))]
125 pub color: ColorChoice,
126 #[cfg_attr(feature = "std", arg(
128 long,
129 value_name = "TARGET",
130 default_value_t = TargetEnv::Base,
131 help_heading = "Compiler"
132 ))]
133 pub target: TargetEnv,
134 #[cfg_attr(feature = "std", arg(long, help_heading = "Compiler", hide(true)))]
137 pub entrypoint: Option<String>,
138 #[cfg_attr(feature = "std", arg(
142 long = "exe",
143 default_value_t = true,
144 default_value_ifs([
145 ("target", "rollup".into(), Some("false")),
147 ("entrypoint", ArgPredicate::IsPresent, Some("true")),
149 ]),
150 help_heading = "Linker"
151 ))]
152 pub is_program: bool,
153 #[cfg_attr(feature = "std", arg(
157 long = "lib",
158 conflicts_with("is_program"),
159 conflicts_with("entrypoint"),
160 default_value_t = false,
161 default_value_ifs([
162 ("entrypoint", ArgPredicate::IsPresent, Some("false")),
164 ("target", "rollup".into(), Some("true")),
166 ]),
167 help_heading = "Linker"
168 ))]
169 pub is_library: bool,
170 #[cfg_attr(
172 feature = "std",
173 arg(
174 long = "search-path",
175 short = 'L',
176 value_name = "PATH",
177 help_heading = "Linker"
178 )
179 )]
180 pub search_path: Vec<PathBuf>,
181 #[cfg_attr(
192 feature = "std",
193 arg(
194 long = "link-library",
195 short = 'l',
196 value_name = "[KIND=]NAME",
197 value_delimiter = ',',
198 next_line_help(true),
199 help_heading = "Linker"
200 )
201 )]
202 pub link_libraries: Vec<LinkLibrary>,
203 #[cfg_attr(
211 feature = "std",
212 arg(
213 long = "emit",
214 value_name = "SPEC",
215 value_delimiter = ',',
216 env = "MIDENC_EMIT",
217 next_line_help(true),
218 help_heading = "Output"
219 )
220 )]
221 pub output_types: Vec<OutputTypeSpec>,
222 #[cfg_attr(feature = "std", arg(
224 long,
225 value_enum,
226 value_name = "LEVEL",
227 next_line_help(true),
228 default_value_t = DebugInfo::Full,
229 default_missing_value = "full",
230 num_args(0..=1),
231 help_heading = "Output"
232 ))]
233 pub debug: DebugInfo,
234 #[cfg_attr(feature = "std", arg(
237 long = "optimize",
238 value_enum,
239 value_name = "LEVEL",
240 next_line_help(true),
241 default_value_t = OptLevel::None,
242 default_missing_value = "balanced",
243 num_args(0..=1),
244 help_heading = "Output"
245 ))]
246 pub opt_level: OptLevel,
247 #[cfg_attr(
251 feature = "std",
252 arg(
253 long,
254 short = 'C',
255 value_name = "OPT[=VALUE]",
256 help_heading = "Compiler"
257 )
258 )]
259 pub codegen: Vec<String>,
260 #[cfg_attr(
264 feature = "std",
265 arg(
266 long,
267 short = 'Z',
268 value_name = "OPT[=VALUE]",
269 help_heading = "Compiler"
270 )
271 )]
272 pub unstable: Vec<String>,
273
274 #[cfg_attr(feature = "std", arg(long, hide = true, default_value_t = false))]
278 pub release: bool,
279 #[cfg_attr(feature = "std", arg(long, value_name = "PATH", hide = true))]
281 pub manifest_path: Option<PathBuf>,
282 #[cfg_attr(feature = "std", arg(long, hide = true, default_value_t = false))]
284 pub workspace: bool,
285 #[cfg_attr(feature = "std", arg(long, value_name = "SPEC", hide = true))]
287 pub package: Vec<String>,
288}
289
290#[derive(Default, Debug, Clone)]
291#[cfg_attr(feature = "std", derive(Parser))]
292#[cfg_attr(feature = "std", command(name = "-C"))]
293pub struct CodegenOptions {
294 #[cfg_attr(feature = "std", arg(
296 long,
297 conflicts_with_all(["analyze_only", "link_only"]),
298 default_value_t = false,
299 ))]
300 pub parse_only: bool,
301 #[cfg_attr(feature = "std", arg(
303 long,
304 conflicts_with_all(["parse_only", "link_only"]),
305 default_value_t = false,
306 ))]
307 pub analyze_only: bool,
308 #[cfg_attr(feature = "std", arg(
310 long,
311 conflicts_with_all(["no_link"]),
312 default_value_t = false,
313 ))]
314 pub link_only: bool,
315 #[cfg_attr(feature = "std", arg(long, default_value_t = false))]
317 pub no_link: bool,
318}
319
320#[derive(Default, Debug, Clone)]
321#[cfg_attr(feature = "std", derive(Parser))]
322#[cfg_attr(feature = "std", command(name = "-Z"))]
323pub struct UnstableOptions {
324 #[cfg_attr(
326 feature = "std",
327 arg(long, default_value_t = false, help_heading = "Passes")
328 )]
329 pub print_cfg_after_all: bool,
330 #[cfg_attr(
332 feature = "std",
333 arg(
334 long,
335 value_name = "PASS",
336 value_delimiter = ',',
337 help_heading = "Passes"
338 )
339 )]
340 pub print_cfg_after_pass: Vec<String>,
341 #[cfg_attr(
343 feature = "std",
344 arg(long, default_value_t = false, help_heading = "Passes")
345 )]
346 pub print_ir_after_all: bool,
347 #[cfg_attr(
349 feature = "std",
350 arg(
351 long,
352 value_name = "PASS",
353 value_delimiter = ',',
354 help_heading = "Passes"
355 )
356 )]
357 pub print_ir_after_pass: Vec<String>,
358 #[cfg_attr(
361 feature = "std",
362 arg(long, default_value_t = false, help_heading = "Passes")
363 )]
364 pub print_ir_after_modified: bool,
365 #[cfg_attr(
370 feature = "std",
371 arg(
372 long = "print-hir-source-locations",
373 default_value_t = false,
374 help_heading = "Printers"
375 )
376 )]
377 pub print_hir_source_locations: bool,
378 #[cfg_attr(
380 feature = "std",
381 arg(
382 long = "trim-path-prefix",
383 value_name = "PATH",
384 help_heading = "Debugging"
385 )
386 )]
387 pub trim_path_prefixes: Vec<PathBuf>,
388}
389
390impl CodegenOptions {
391 #[cfg(feature = "std")]
392 fn parse_argv(argv: Vec<String>) -> Self {
393 let command = <CodegenOptions as clap::CommandFactory>::command()
394 .no_binary_name(true)
395 .arg_required_else_help(false)
396 .help_template(
397 "\
398Available codegen options:
399
400Usage: midenc compile -C <opt>
401
402{all-args}{after-help}
403
404NOTE: When specifying these options, strip the leading '--'",
405 );
406
407 let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
408 vec!["--help".to_string()]
409 } else {
410 argv.into_iter()
411 .flat_map(|arg| match arg.split_once('=') {
412 None => vec![format!("--{arg}")],
413 Some((opt, value)) => {
414 vec![format!("--{opt}"), value.to_string()]
415 }
416 })
417 .collect::<Vec<_>>()
418 };
419
420 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
421 <CodegenOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
422 .map_err(format_error::<CodegenOptions>)
423 .unwrap_or_else(|err| err.exit())
424 }
425
426 #[cfg(not(feature = "std"))]
427 fn parse_argv(_argv: Vec<String>) -> Self {
428 Self::default()
429 }
430}
431
432impl UnstableOptions {
433 #[cfg(feature = "std")]
434 fn parse_argv(argv: Vec<String>) -> Self {
435 let command = <UnstableOptions as clap::CommandFactory>::command()
436 .no_binary_name(true)
437 .arg_required_else_help(false)
438 .help_template(
439 "\
440Available unstable options:
441
442Usage: midenc compile -Z <opt>
443
444{all-args}{after-help}
445
446NOTE: When specifying these options, strip the leading '--'",
447 );
448
449 let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
450 vec!["--help".to_string()]
451 } else {
452 argv.into_iter()
453 .flat_map(|arg| match arg.split_once('=') {
454 None => vec![format!("--{arg}")],
455 Some((opt, value)) => {
456 vec![format!("--{opt}"), value.to_string()]
457 }
458 })
459 .collect::<Vec<_>>()
460 };
461
462 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
463 <UnstableOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
464 .map_err(format_error::<UnstableOptions>)
465 .unwrap_or_else(|err| err.exit())
466 }
467
468 #[cfg(not(feature = "std"))]
469 fn parse_argv(_argv: Vec<String>) -> Self {
470 Self::default()
471 }
472}
473
474impl Compiler {
475 #[cfg(feature = "std")]
481 pub fn try_parse_from<I, T>(iter: I) -> Result<Self, clap::Error>
482 where
483 I: IntoIterator<Item = T>,
484 T: Into<OsString> + Clone,
485 {
486 let argv = [OsString::from("midenc")]
487 .into_iter()
488 .chain(iter.into_iter().map(|arg| arg.into()));
489 let command = <Self as clap::CommandFactory>::command();
490 let command = midenc_session::flags::register_flags(command);
491 let mut matches = command.try_get_matches_from(argv)?;
492
493 <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
494 .map_err(format_error::<Self>)
495 }
496
497 #[cfg(feature = "std")]
499 pub fn new_session<I, A, S>(inputs: I, emitter: Option<Arc<dyn Emitter>>, argv: A) -> Session
500 where
501 I: IntoIterator<Item = InputFile>,
502 A: IntoIterator<Item = S>,
503 S: Into<std::ffi::OsString> + Clone,
504 {
505 let argv = [OsString::from("midenc")]
506 .into_iter()
507 .chain(argv.into_iter().map(|arg| arg.into()));
508 let command = <Self as clap::CommandFactory>::command();
509 let command = midenc_session::flags::register_flags(command);
510 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
511 let compile_matches = matches.clone();
512
513 let opts = <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
514 .map_err(format_error::<Self>)
515 .unwrap_or_else(|err| err.exit());
516
517 let inputs = inputs.into_iter().collect();
518 opts.into_session(inputs, emitter).with_extra_flags(compile_matches.into())
519 }
520
521 pub fn into_session(
523 self,
524 inputs: Vec<InputFile>,
525 emitter: Option<Arc<dyn Emitter>>,
526 ) -> Session {
527 let cwd = self.working_dir.unwrap_or_else(current_dir);
528
529 log::trace!(target: "driver", "current working directory = {}", cwd.display());
530
531 let output_file = match self.output_file {
533 Some(path) => Some(OutputFile::Real(path)),
534 None if self.stdout => Some(OutputFile::Stdout),
535 None => None,
536 };
537
538 let mut output_types = OutputTypes::new(self.output_types).unwrap_or_else(|err| err.exit());
540 if output_types.is_empty() {
541 output_types.insert(OutputType::Masp, output_file.clone());
542 } else if output_file.is_some() && output_types.get(&OutputType::Masp).is_some() {
543 output_types.insert(OutputType::Masp, output_file.clone());
545 }
546
547 let project_type = if self.is_program {
549 ProjectType::Program
550 } else {
551 ProjectType::Library
552 };
553
554 let codegen = CodegenOptions::parse_argv(self.codegen);
555 let unstable = UnstableOptions::parse_argv(self.unstable);
556
557 let mut options = Options::new(self.name, self.target, project_type, cwd, self.sysroot)
559 .with_color(self.color)
560 .with_verbosity(self.verbosity)
561 .with_warnings(self.warn)
562 .with_debug_info(self.debug)
563 .with_optimization(self.opt_level)
564 .with_output_types(output_types);
565 options.search_paths = self.search_path;
566 let link_libraries = add_target_link_libraries(self.link_libraries, &self.target);
567 options.link_libraries = link_libraries;
568 options.entrypoint = self.entrypoint;
569 options.parse_only = codegen.parse_only;
570 options.analyze_only = codegen.analyze_only;
571 options.link_only = codegen.link_only;
572 options.no_link = codegen.no_link;
573 options.print_cfg_after_all = unstable.print_cfg_after_all;
574 options.print_cfg_after_pass = unstable.print_cfg_after_pass;
575 options.print_ir_after_all = unstable.print_ir_after_all;
576 options.print_ir_after_pass = unstable.print_ir_after_pass;
577 options.print_ir_after_modified = unstable.print_ir_after_modified;
578 options.print_hir_source_locations = unstable.print_hir_source_locations;
579 options.trim_path_prefixes = unstable.trim_path_prefixes;
580
581 let target_dir = if self.target_dir.is_absolute() {
583 self.target_dir
584 } else {
585 options.current_dir.join(&self.target_dir)
586 };
587 create_target_dir(target_dir.as_path());
588
589 let source_manager = Arc::new(DefaultSourceManager::default());
590 Session::new(
591 inputs,
592 self.output_dir,
593 output_file,
594 target_dir,
595 options,
596 emitter,
597 source_manager,
598 )
599 }
600}
601
602#[cfg(feature = "std")]
603fn format_error<I: clap::CommandFactory>(err: clap::Error) -> clap::Error {
604 let mut cmd = I::command();
605 err.format(&mut cmd)
606}
607
608#[cfg(feature = "std")]
609fn current_dir() -> PathBuf {
610 std::env::current_dir().expect("no working directory available")
611}
612
613#[cfg(not(feature = "std"))]
614fn current_dir() -> PathBuf {
615 <str as AsRef<Path>>::as_ref(".").to_path_buf()
616}
617
618#[cfg(feature = "std")]
619fn create_target_dir(path: &Path) {
620 std::fs::create_dir_all(path)
621 .unwrap_or_else(|err| panic!("unable to create --target-dir '{}': {err}", path.display()));
622}
623
624#[cfg(not(feature = "std"))]
625fn create_target_dir(_path: &Path) {}