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::{builder::ArgPredicate, Parser};
9use midenc_session::{
10 add_target_link_libraries,
11 diagnostics::{DefaultSourceManager, Emitter},
12 ColorChoice, DebugInfo, InputFile, LinkLibrary, OptLevel, Options, OutputFile, OutputType,
13 OutputTypeSpec, OutputTypes, Path, PathBuf, ProjectType, Session, TargetEnv, Verbosity,
14 Warnings,
15};
16
17#[derive(Debug)]
19#[cfg_attr(feature = "std", derive(Parser))]
20#[cfg_attr(feature = "std", command(name = "midenc"))]
21pub struct Compiler {
22 #[cfg_attr(
26 feature = "std",
27 arg(
28 long,
29 value_name = "DIR",
30 env = "MIDENC_TARGET_DIR",
31 default_value = "target/midenc",
32 help_heading = "Output"
33 )
34 )]
35 pub target_dir: PathBuf,
36 #[cfg_attr(
40 feature = "std",
41 arg(long, value_name = "DIR", help_heading = "Output")
42 )]
43 pub working_dir: Option<PathBuf>,
44 #[cfg_attr(
48 feature = "std",
49 arg(
50 long,
51 value_name = "DIR",
52 env = "MIDENC_SYSROOT",
53 help_heading = "Compiler"
54 )
55 )]
56 pub sysroot: Option<PathBuf>,
57 #[cfg_attr(
59 feature = "std",
60 arg(
61 long,
62 short = 'O',
63 value_name = "DIR",
64 env = "MIDENC_OUT_DIR",
65 help_heading = "Output"
66 )
67 )]
68 pub output_dir: Option<PathBuf>,
69 #[cfg_attr(
71 feature = "std",
72 arg(long, short = 'o', value_name = "FILENAME", help_heading = "Output")
73 )]
74 pub output_file: Option<PathBuf>,
75 #[cfg_attr(
77 feature = "std",
78 arg(long, conflicts_with("output_file"), help_heading = "Output")
79 )]
80 pub stdout: bool,
81 #[cfg_attr(feature = "std", arg(
86 long,
87 short = 'n',
88 value_name = "NAME",
89 default_value = None,
90 help_heading = "Diagnostics"
91 ))]
92 pub name: Option<String>,
93 #[cfg_attr(feature = "std", arg(
95 long = "verbose",
96 short = 'v',
97 value_enum,
98 value_name = "LEVEL",
99 default_value_t = Verbosity::Info,
100 default_missing_value = "debug",
101 num_args(0..=1),
102 help_heading = "Diagnostics"
103 ))]
104 pub verbosity: Verbosity,
105 #[cfg_attr(feature = "std", arg(
107 long,
108 short = 'W',
109 value_enum,
110 value_name = "LEVEL",
111 default_value_t = Warnings::All,
112 default_missing_value = "all",
113 num_args(0..=1),
114 help_heading = "Diagnostics"
115 ))]
116 pub warn: Warnings,
117 #[cfg_attr(feature = "std", arg(
119 long,
120 value_enum,
121 default_value_t = ColorChoice::Auto,
122 default_missing_value = "auto",
123 num_args(0..=1),
124 help_heading = "Diagnostics"
125 ))]
126 pub color: ColorChoice,
127 #[cfg_attr(feature = "std", arg(
129 long,
130 value_name = "TARGET",
131 default_value_t = TargetEnv::Base,
132 help_heading = "Compiler"
133 ))]
134 pub target: TargetEnv,
135 #[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
275#[derive(Default, Debug, Clone)]
276#[cfg_attr(feature = "std", derive(Parser))]
277#[cfg_attr(feature = "std", command(name = "-C"))]
278pub struct CodegenOptions {
279 #[cfg_attr(feature = "std", arg(
281 long,
282 conflicts_with_all(["analyze_only", "link_only"]),
283 default_value_t = false,
284 ))]
285 pub parse_only: bool,
286 #[cfg_attr(feature = "std", arg(
288 long,
289 conflicts_with_all(["parse_only", "link_only"]),
290 default_value_t = false,
291 ))]
292 pub analyze_only: bool,
293 #[cfg_attr(feature = "std", arg(
295 long,
296 conflicts_with_all(["no_link"]),
297 default_value_t = false,
298 ))]
299 pub link_only: bool,
300 #[cfg_attr(feature = "std", arg(long, default_value_t = false))]
302 pub no_link: bool,
303}
304
305#[derive(Default, Debug, Clone)]
306#[cfg_attr(feature = "std", derive(Parser))]
307#[cfg_attr(feature = "std", command(name = "-Z"))]
308pub struct UnstableOptions {
309 #[cfg_attr(
311 feature = "std",
312 arg(long, default_value_t = false, help_heading = "Passes")
313 )]
314 pub print_cfg_after_all: bool,
315 #[cfg_attr(
317 feature = "std",
318 arg(
319 long,
320 value_name = "PASS",
321 value_delimiter = ',',
322 help_heading = "Passes"
323 )
324 )]
325 pub print_cfg_after_pass: Vec<String>,
326 #[cfg_attr(
328 feature = "std",
329 arg(long, default_value_t = false, help_heading = "Passes")
330 )]
331 pub print_ir_after_all: bool,
332 #[cfg_attr(
334 feature = "std",
335 arg(
336 long,
337 value_name = "PASS",
338 value_delimiter = ',',
339 help_heading = "Passes"
340 )
341 )]
342 pub print_ir_after_pass: Vec<String>,
343 #[cfg_attr(
346 feature = "std",
347 arg(long, default_value_t = false, help_heading = "Passes")
348 )]
349 pub print_ir_after_modified: bool,
350}
351
352impl CodegenOptions {
353 #[cfg(feature = "std")]
354 fn parse_argv(argv: Vec<String>) -> Self {
355 let command = <CodegenOptions as clap::CommandFactory>::command()
356 .no_binary_name(true)
357 .arg_required_else_help(false)
358 .help_template(
359 "\
360Available codegen options:
361
362Usage: midenc compile -C <opt>
363
364{all-args}{after-help}
365
366NOTE: When specifying these options, strip the leading '--'",
367 );
368
369 let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
370 vec!["--help".to_string()]
371 } else {
372 argv.into_iter()
373 .flat_map(|arg| match arg.split_once('=') {
374 None => vec![format!("--{arg}")],
375 Some((opt, value)) => {
376 vec![format!("--{opt}"), value.to_string()]
377 }
378 })
379 .collect::<Vec<_>>()
380 };
381
382 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
383 <CodegenOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
384 .map_err(format_error::<CodegenOptions>)
385 .unwrap_or_else(|err| err.exit())
386 }
387
388 #[cfg(not(feature = "std"))]
389 fn parse_argv(_argv: Vec<String>) -> Self {
390 Self::default()
391 }
392}
393
394impl UnstableOptions {
395 #[cfg(feature = "std")]
396 fn parse_argv(argv: Vec<String>) -> Self {
397 let command = <UnstableOptions as clap::CommandFactory>::command()
398 .no_binary_name(true)
399 .arg_required_else_help(false)
400 .help_template(
401 "\
402Available unstable options:
403
404Usage: midenc compile -Z <opt>
405
406{all-args}{after-help}
407
408NOTE: When specifying these options, strip the leading '--'",
409 );
410
411 let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
412 vec!["--help".to_string()]
413 } else {
414 argv.into_iter()
415 .flat_map(|arg| match arg.split_once('=') {
416 None => vec![format!("--{arg}")],
417 Some((opt, value)) => {
418 vec![format!("--{opt}"), value.to_string()]
419 }
420 })
421 .collect::<Vec<_>>()
422 };
423
424 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
425 <UnstableOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
426 .map_err(format_error::<UnstableOptions>)
427 .unwrap_or_else(|err| err.exit())
428 }
429
430 #[cfg(not(feature = "std"))]
431 fn parse_argv(_argv: Vec<String>) -> Self {
432 Self::default()
433 }
434}
435
436impl Compiler {
437 #[cfg(feature = "std")]
439 pub fn new_session<I, A, S>(inputs: I, emitter: Option<Arc<dyn Emitter>>, argv: A) -> Session
440 where
441 I: IntoIterator<Item = InputFile>,
442 A: IntoIterator<Item = S>,
443 S: Into<std::ffi::OsString> + Clone,
444 {
445 let argv = [OsString::from("midenc")]
446 .into_iter()
447 .chain(argv.into_iter().map(|arg| arg.into()));
448 let command = <Self as clap::CommandFactory>::command();
449 let command = midenc_session::flags::register_flags(command);
450 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
451 let compile_matches = matches.clone();
452
453 let opts = <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
454 .map_err(format_error::<Self>)
455 .unwrap_or_else(|err| err.exit());
456
457 let inputs = inputs.into_iter().collect();
458 opts.into_session(inputs, emitter).with_extra_flags(compile_matches.into())
459 }
460
461 pub fn into_session(
463 self,
464 inputs: Vec<InputFile>,
465 emitter: Option<Arc<dyn Emitter>>,
466 ) -> Session {
467 let cwd = self.working_dir.unwrap_or_else(current_dir);
468
469 log::trace!(target: "driver", "current working directory = {}", cwd.display());
470
471 let output_file = match self.output_file {
473 Some(path) => Some(OutputFile::Real(path)),
474 None if self.stdout => Some(OutputFile::Stdout),
475 None => None,
476 };
477
478 let mut output_types = OutputTypes::new(self.output_types).unwrap_or_else(|err| err.exit());
480 if output_types.is_empty() {
481 output_types.insert(OutputType::Masp, output_file.clone());
482 } else if output_file.is_some() && output_types.get(&OutputType::Masp).is_some() {
483 output_types.insert(OutputType::Masp, output_file.clone());
485 }
486
487 let project_type = if self.is_program {
489 ProjectType::Program
490 } else {
491 ProjectType::Library
492 };
493
494 let codegen = CodegenOptions::parse_argv(self.codegen);
495 let unstable = UnstableOptions::parse_argv(self.unstable);
496
497 let mut options = Options::new(self.name, self.target, project_type, cwd, self.sysroot)
499 .with_color(self.color)
500 .with_verbosity(self.verbosity)
501 .with_warnings(self.warn)
502 .with_debug_info(self.debug)
503 .with_optimization(self.opt_level)
504 .with_output_types(output_types);
505 options.search_paths = self.search_path;
506 let link_libraries = add_target_link_libraries(self.link_libraries, &self.target);
507 options.link_libraries = link_libraries;
508 options.entrypoint = self.entrypoint;
509 options.parse_only = codegen.parse_only;
510 options.analyze_only = codegen.analyze_only;
511 options.link_only = codegen.link_only;
512 options.no_link = codegen.no_link;
513 options.print_cfg_after_all = unstable.print_cfg_after_all;
514 options.print_cfg_after_pass = unstable.print_cfg_after_pass;
515 options.print_ir_after_all = unstable.print_ir_after_all;
516 options.print_ir_after_pass = unstable.print_ir_after_pass;
517 options.print_ir_after_modified = unstable.print_ir_after_modified;
518
519 let target_dir = if self.target_dir.is_absolute() {
521 self.target_dir
522 } else {
523 options.current_dir.join(&self.target_dir)
524 };
525 create_target_dir(target_dir.as_path());
526
527 let source_manager = Arc::new(DefaultSourceManager::default());
528 Session::new(
529 inputs,
530 self.output_dir,
531 output_file,
532 target_dir,
533 options,
534 emitter,
535 source_manager,
536 )
537 }
538}
539
540#[cfg(feature = "std")]
541fn format_error<I: clap::CommandFactory>(err: clap::Error) -> clap::Error {
542 let mut cmd = I::command();
543 err.format(&mut cmd)
544}
545
546#[cfg(feature = "std")]
547fn current_dir() -> PathBuf {
548 std::env::current_dir().expect("no working directory available")
549}
550
551#[cfg(not(feature = "std"))]
552fn current_dir() -> PathBuf {
553 <str as AsRef<Path>>::as_ref(".").to_path_buf()
554}
555
556#[cfg(feature = "std")]
557fn create_target_dir(path: &Path) {
558 std::fs::create_dir_all(path)
559 .unwrap_or_else(|err| panic!("unable to create --target-dir '{}': {err}", path.display()));
560}
561
562#[cfg(not(feature = "std"))]
563fn create_target_dir(_path: &Path) {}