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 diagnostics::{DefaultSourceManager, Emitter},
11 ColorChoice, DebugInfo, InputFile, LinkLibrary, OptLevel, Options, OutputFile, OutputType,
12 OutputTypeSpec, OutputTypes, Path, PathBuf, ProjectType, Session, TargetEnv, Verbosity,
13 Warnings,
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)))]
136 pub entrypoint: Option<String>,
137 #[cfg_attr(feature = "std", arg(
141 long = "exe",
142 default_value_t = true,
143 default_value_ifs([
144 ("target", "rollup".into(), Some("false")),
146 ("entrypoint", ArgPredicate::IsPresent, Some("true")),
148 ]),
149 help_heading = "Linker"
150 ))]
151 pub is_program: bool,
152 #[cfg_attr(feature = "std", arg(
156 long = "lib",
157 conflicts_with("is_program"),
158 conflicts_with("entrypoint"),
159 default_value_t = false,
160 default_value_ifs([
161 ("entrypoint", ArgPredicate::IsPresent, Some("false")),
163 ("target", "rollup".into(), Some("true")),
165 ]),
166 help_heading = "Linker"
167 ))]
168 pub is_library: bool,
169 #[cfg_attr(
171 feature = "std",
172 arg(
173 long = "search-path",
174 short = 'L',
175 value_name = "PATH",
176 help_heading = "Linker"
177 )
178 )]
179 pub search_path: Vec<PathBuf>,
180 #[cfg_attr(feature = "std", arg(
191 long = "link-library",
192 short = 'l',
193 value_name = "[KIND=]NAME",
194 value_delimiter = ',',
195 default_value_ifs([
196 ("target", "base", "std"),
197 ("target", "rollup", "std,base"),
198 ]),
199 next_line_help(true),
200 help_heading = "Linker"
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 next_line_help(true),
217 help_heading = "Output"
218 )
219 )]
220 pub output_types: Vec<OutputTypeSpec>,
221 #[cfg_attr(feature = "std", arg(
223 long,
224 value_enum,
225 value_name = "LEVEL",
226 next_line_help(true),
227 default_value_t = DebugInfo::Full,
228 default_missing_value = "full",
229 num_args(0..=1),
230 help_heading = "Output"
231 ))]
232 pub debug: DebugInfo,
233 #[cfg_attr(feature = "std", arg(
236 long = "optimize",
237 value_enum,
238 value_name = "LEVEL",
239 next_line_help(true),
240 default_value_t = OptLevel::None,
241 default_missing_value = "balanced",
242 num_args(0..=1),
243 help_heading = "Output"
244 ))]
245 pub opt_level: OptLevel,
246 #[cfg_attr(
250 feature = "std",
251 arg(
252 long,
253 short = 'C',
254 value_name = "OPT[=VALUE]",
255 help_heading = "Compiler"
256 )
257 )]
258 pub codegen: Vec<String>,
259 #[cfg_attr(
263 feature = "std",
264 arg(
265 long,
266 short = 'Z',
267 value_name = "OPT[=VALUE]",
268 help_heading = "Compiler"
269 )
270 )]
271 pub unstable: Vec<String>,
272}
273
274#[derive(Default, Debug, Clone)]
275#[cfg_attr(feature = "std", derive(Parser))]
276#[cfg_attr(feature = "std", command(name = "-C"))]
277pub struct CodegenOptions {
278 #[cfg_attr(feature = "std", arg(
280 long,
281 conflicts_with_all(["analyze_only", "link_only"]),
282 default_value_t = false,
283 ))]
284 pub parse_only: bool,
285 #[cfg_attr(feature = "std", arg(
287 long,
288 conflicts_with_all(["parse_only", "link_only"]),
289 default_value_t = false,
290 ))]
291 pub analyze_only: bool,
292 #[cfg_attr(feature = "std", arg(
294 long,
295 conflicts_with_all(["no_link"]),
296 default_value_t = false,
297 ))]
298 pub link_only: bool,
299 #[cfg_attr(feature = "std", arg(long, default_value_t = false))]
301 pub no_link: bool,
302}
303
304#[derive(Default, Debug, Clone)]
305#[cfg_attr(feature = "std", derive(Parser))]
306#[cfg_attr(feature = "std", command(name = "-Z"))]
307pub struct UnstableOptions {
308 #[cfg_attr(
310 feature = "std",
311 arg(long, default_value_t = false, help_heading = "Passes")
312 )]
313 pub print_cfg_after_all: bool,
314 #[cfg_attr(
316 feature = "std",
317 arg(
318 long,
319 value_name = "PASS",
320 value_delimiter = ',',
321 help_heading = "Passes"
322 )
323 )]
324 pub print_cfg_after_pass: Vec<String>,
325 #[cfg_attr(
327 feature = "std",
328 arg(long, default_value_t = false, help_heading = "Passes")
329 )]
330 pub print_ir_after_all: bool,
331 #[cfg_attr(
333 feature = "std",
334 arg(
335 long,
336 value_name = "PASS",
337 value_delimiter = ',',
338 help_heading = "Passes"
339 )
340 )]
341 pub print_ir_after_pass: Vec<String>,
342}
343
344impl CodegenOptions {
345 #[cfg(feature = "std")]
346 fn parse_argv(argv: Vec<String>) -> Self {
347 let command = <CodegenOptions as clap::CommandFactory>::command()
348 .no_binary_name(true)
349 .arg_required_else_help(false)
350 .help_template(
351 "\
352Available codegen options:
353
354Usage: midenc compile -C <opt>
355
356{all-args}{after-help}
357
358NOTE: When specifying these options, strip the leading '--'",
359 );
360
361 let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
362 vec!["--help".to_string()]
363 } else {
364 argv.into_iter()
365 .flat_map(|arg| match arg.split_once('=') {
366 None => vec![format!("--{arg}")],
367 Some((opt, value)) => {
368 vec![format!("--{opt}"), value.to_string()]
369 }
370 })
371 .collect::<Vec<_>>()
372 };
373
374 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
375 <CodegenOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
376 .map_err(format_error::<CodegenOptions>)
377 .unwrap_or_else(|err| err.exit())
378 }
379
380 #[cfg(not(feature = "std"))]
381 fn parse_argv(_argv: Vec<String>) -> Self {
382 Self::default()
383 }
384}
385
386impl UnstableOptions {
387 #[cfg(feature = "std")]
388 fn parse_argv(argv: Vec<String>) -> Self {
389 let command = <UnstableOptions as clap::CommandFactory>::command()
390 .no_binary_name(true)
391 .arg_required_else_help(false)
392 .help_template(
393 "\
394Available unstable options:
395
396Usage: midenc compile -Z <opt>
397
398{all-args}{after-help}
399
400NOTE: When specifying these options, strip the leading '--'",
401 );
402
403 let argv = if argv.iter().any(|arg| matches!(arg.as_str(), "--help" | "-h" | "help")) {
404 vec!["--help".to_string()]
405 } else {
406 argv.into_iter()
407 .flat_map(|arg| match arg.split_once('=') {
408 None => vec![format!("--{arg}")],
409 Some((opt, value)) => {
410 vec![format!("--{opt}"), value.to_string()]
411 }
412 })
413 .collect::<Vec<_>>()
414 };
415
416 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
417 <UnstableOptions as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
418 .map_err(format_error::<UnstableOptions>)
419 .unwrap_or_else(|err| err.exit())
420 }
421
422 #[cfg(not(feature = "std"))]
423 fn parse_argv(_argv: Vec<String>) -> Self {
424 Self::default()
425 }
426}
427
428impl Compiler {
429 #[cfg(feature = "std")]
431 pub fn new_session<I, A, S>(inputs: I, emitter: Option<Arc<dyn Emitter>>, argv: A) -> Session
432 where
433 I: IntoIterator<Item = InputFile>,
434 A: IntoIterator<Item = S>,
435 S: Into<std::ffi::OsString> + Clone,
436 {
437 let argv = [OsString::from("midenc")]
438 .into_iter()
439 .chain(argv.into_iter().map(|arg| arg.into()));
440 let command = <Self as clap::CommandFactory>::command();
441 let command = midenc_session::flags::register_flags(command);
442 let mut matches = command.try_get_matches_from(argv).unwrap_or_else(|err| err.exit());
443 let compile_matches = matches.clone();
444
445 let opts = <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
446 .map_err(format_error::<Self>)
447 .unwrap_or_else(|err| err.exit());
448
449 let inputs = inputs.into_iter().collect();
450 opts.into_session(inputs, emitter).with_extra_flags(compile_matches.into())
451 }
452
453 pub fn into_session(
455 self,
456 inputs: Vec<InputFile>,
457 emitter: Option<Arc<dyn Emitter>>,
458 ) -> Session {
459 let cwd = self.working_dir.unwrap_or_else(current_dir);
460
461 log::trace!(target: "driver", "current working directory = {}", cwd.display());
462
463 let output_file = match self.output_file {
465 Some(path) => Some(OutputFile::Real(path)),
466 None if self.stdout => Some(OutputFile::Stdout),
467 None => None,
468 };
469
470 let mut output_types = OutputTypes::new(self.output_types).unwrap_or_else(|err| err.exit());
472 if output_types.is_empty() {
473 output_types.insert(OutputType::Masp, output_file.clone());
474 } else if output_file.is_some() && output_types.get(&OutputType::Masp).is_some() {
475 output_types.insert(OutputType::Masp, output_file.clone());
477 }
478
479 let project_type = if self.is_program {
481 ProjectType::Program
482 } else {
483 ProjectType::Library
484 };
485
486 let codegen = CodegenOptions::parse_argv(self.codegen);
487 let unstable = UnstableOptions::parse_argv(self.unstable);
488
489 let mut options = Options::new(self.name, self.target, project_type, cwd, self.sysroot)
491 .with_color(self.color)
492 .with_verbosity(self.verbosity)
493 .with_warnings(self.warn)
494 .with_debug_info(self.debug)
495 .with_optimization(self.opt_level)
496 .with_output_types(output_types);
497 options.search_paths = self.search_path;
498 options.link_libraries = self.link_libraries;
499 options.entrypoint = self.entrypoint;
500 options.parse_only = codegen.parse_only;
501 options.analyze_only = codegen.analyze_only;
502 options.link_only = codegen.link_only;
503 options.no_link = codegen.no_link;
504 options.print_cfg_after_all = unstable.print_cfg_after_all;
505 options.print_cfg_after_pass = unstable.print_cfg_after_pass;
506 options.print_ir_after_all = unstable.print_ir_after_all;
507 options.print_ir_after_pass = unstable.print_ir_after_pass;
508
509 let target_dir = if self.target_dir.is_absolute() {
511 self.target_dir
512 } else {
513 options.current_dir.join(&self.target_dir)
514 };
515 create_target_dir(target_dir.as_path());
516
517 let source_manager = Arc::new(DefaultSourceManager::default());
518 Session::new(
519 inputs,
520 self.output_dir,
521 output_file,
522 target_dir,
523 options,
524 emitter,
525 source_manager,
526 )
527 }
528}
529
530#[cfg(feature = "std")]
531fn format_error<I: clap::CommandFactory>(err: clap::Error) -> clap::Error {
532 let mut cmd = I::command();
533 err.format(&mut cmd)
534}
535
536#[cfg(feature = "std")]
537fn current_dir() -> PathBuf {
538 std::env::current_dir().expect("no working directory available")
539}
540
541#[cfg(not(feature = "std"))]
542fn current_dir() -> PathBuf {
543 <str as AsRef<Path>>::as_ref(".").to_path_buf()
544}
545
546#[cfg(feature = "std")]
547fn create_target_dir(path: &Path) {
548 std::fs::create_dir_all(path)
549 .unwrap_or_else(|err| panic!("unable to create --target-dir '{}': {err}", path.display()));
550}
551
552#[cfg(not(feature = "std"))]
553fn create_target_dir(_path: &Path) {}