@@ -64,86 +64,149 @@ impl OptLevel { |
| 64 | 64 | } |
| 65 | 65 | } |
| 66 | 66 | |
| 67 | +/// Source-form override requested on the command line. None means |
| 68 | +/// detect from the file extension (.f90 → free, .f / .for → fixed). |
| 69 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 70 | +pub enum SourceFormOverride { |
| 71 | + Free, |
| 72 | + Fixed, |
| 73 | +} |
| 74 | + |
| 75 | +/// Action that should run when args parsing completes successfully |
| 76 | +/// without producing a compile job (e.g. --help, --version). |
| 77 | +#[derive(Debug, Clone, PartialEq, Eq)] |
| 78 | +pub enum InfoAction { |
| 79 | + Help, |
| 80 | + Version, |
| 81 | + DumpVersion, |
| 82 | +} |
| 83 | + |
| 84 | +/// Result of parsing CLI args — either a real compile job or an |
| 85 | +/// informational request. |
| 86 | +pub enum ParsedCli { |
| 87 | + Compile(Box<Options>), |
| 88 | + Info(InfoAction), |
| 89 | +} |
| 90 | + |
| 67 | 91 | /// Compilation options. |
| 68 | 92 | pub struct Options { |
| 93 | + // ---- I/O ---- |
| 69 | 94 | pub input: PathBuf, |
| 70 | 95 | /// Additional input files for multi-source mode. |
| 71 | 96 | pub extra_inputs: Vec<PathBuf>, |
| 72 | 97 | pub output: Option<PathBuf>, |
| 98 | + |
| 99 | + // ---- Mode ---- |
| 73 | 100 | pub emit_asm: bool, // -S |
| 74 | 101 | pub emit_obj: bool, // -c |
| 75 | 102 | pub emit_ir: bool, // --emit-ir |
| 103 | + pub emit_ast: bool, // --emit-ast |
| 104 | + pub emit_tokens: bool, // --emit-tokens |
| 76 | 105 | pub preprocess_only: bool, // -E |
| 77 | | - pub opt_level: OptLevel, // -O0 .. -Ofast |
| 106 | + |
| 107 | + // ---- Language ---- |
| 108 | + pub std: Option<crate::sema::validate::FortranStandard>, |
| 109 | + pub source_form_override: Option<SourceFormOverride>, |
| 110 | + pub default_integer_8: bool, |
| 111 | + pub default_real_8: bool, |
| 112 | + pub force_implicit_none: bool, |
| 113 | + pub recursive_default: bool, |
| 114 | + pub backslash_escapes: bool, |
| 115 | + pub max_stack_var_size: Option<u64>, |
| 116 | + |
| 117 | + // ---- Optimization ---- |
| 118 | + pub opt_level: OptLevel, |
| 119 | + |
| 120 | + // ---- Warnings ---- |
| 121 | + pub warn_all: bool, |
| 122 | + pub warn_extra: bool, |
| 123 | + pub warn_pedantic: bool, |
| 124 | + pub warn_deprecated: bool, |
| 125 | + pub warn_as_error: bool, |
| 126 | + pub disabled_warnings: Vec<String>, |
| 127 | + |
| 128 | + // ---- Debug / introspection ---- |
| 129 | + pub debug_info: bool, // -g (accepted; DWARF deferred) |
| 130 | + pub verbose: bool, // -v |
| 131 | + pub time_report: bool, // --time-report |
| 132 | + pub diagnostics_format: DiagnosticsFormat, // --diagnostics-format= |
| 133 | + pub check_bounds: bool, // -fcheck=bounds |
| 134 | + pub check_all: bool, // -fcheck=all |
| 135 | + |
| 136 | + // ---- Search paths / linking ---- |
| 78 | 137 | /// Directories to search for `.amod` module files (`-I <dir>`). |
| 79 | 138 | pub module_search_paths: Vec<PathBuf>, |
| 139 | + /// Directory to write generated `.amod` files (`-J <dir>`). |
| 140 | + pub module_output_dir: Option<PathBuf>, |
| 141 | + /// `-L <dir>` library search paths passed to `ld`. |
| 142 | + pub library_search_paths: Vec<PathBuf>, |
| 143 | + /// `-l<name>` libraries passed to `ld`. |
| 144 | + pub link_libs: Vec<String>, |
| 145 | + /// `-shared` / `-static`. |
| 146 | + pub shared: bool, |
| 147 | + pub static_link: bool, |
| 148 | + /// `-rpath` entries passed to `ld`. |
| 149 | + pub rpath: Vec<PathBuf>, |
| 80 | 150 | } |
| 81 | 151 | |
| 82 | | -impl Options { |
| 83 | | - pub fn from_args(args: &[String]) -> Result<Self, String> { |
| 84 | | - let mut inputs = Vec::new(); |
| 85 | | - let mut output = None; |
| 86 | | - let mut emit_asm = false; |
| 87 | | - let mut emit_obj = false; |
| 88 | | - let mut emit_ir = false; |
| 89 | | - let mut preprocess_only = false; |
| 90 | | - let mut opt_level = OptLevel::O0; |
| 91 | | - let mut module_search_paths = Vec::new(); |
| 92 | | - |
| 93 | | - let mut i = 0; |
| 94 | | - while i < args.len() { |
| 95 | | - match args[i].as_str() { |
| 96 | | - "-o" => { |
| 97 | | - i += 1; |
| 98 | | - if i < args.len() { |
| 99 | | - output = Some(PathBuf::from(&args[i])); |
| 100 | | - } else { |
| 101 | | - return Err("-o requires an argument".into()); |
| 102 | | - } |
| 103 | | - } |
| 104 | | - "-S" => emit_asm = true, |
| 105 | | - "-c" => emit_obj = true, |
| 106 | | - "-E" => preprocess_only = true, |
| 107 | | - "--emit-ir" => emit_ir = true, |
| 108 | | - "-I" => { |
| 109 | | - i += 1; |
| 110 | | - if i < args.len() { |
| 111 | | - module_search_paths.push(PathBuf::from(&args[i])); |
| 112 | | - } else { |
| 113 | | - return Err("-I requires a directory argument".into()); |
| 114 | | - } |
| 115 | | - } |
| 116 | | - arg if arg.starts_with("-I") => { |
| 117 | | - module_search_paths.push(PathBuf::from(&arg[2..])); |
| 118 | | - } |
| 119 | | - arg if arg.starts_with("-O") => { |
| 120 | | - let tail = &arg[1..]; |
| 121 | | - opt_level = OptLevel::parse_flag(tail) |
| 122 | | - .ok_or_else(|| format!("unknown optimization level: {}", arg))?; |
| 123 | | - } |
| 124 | | - arg if !arg.starts_with('-') => { |
| 125 | | - inputs.push(PathBuf::from(arg)); |
| 126 | | - } |
| 127 | | - other => return Err(format!("unknown option: {}", other)), |
| 128 | | - } |
| 129 | | - i += 1; |
| 152 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 153 | +pub enum DiagnosticsFormat { |
| 154 | + Text, |
| 155 | + Json, |
| 156 | +} |
| 157 | + |
| 158 | +impl Default for Options { |
| 159 | + fn default() -> Self { |
| 160 | + Self { |
| 161 | + input: PathBuf::new(), |
| 162 | + extra_inputs: Vec::new(), |
| 163 | + output: None, |
| 164 | + emit_asm: false, |
| 165 | + emit_obj: false, |
| 166 | + emit_ir: false, |
| 167 | + emit_ast: false, |
| 168 | + emit_tokens: false, |
| 169 | + preprocess_only: false, |
| 170 | + std: None, |
| 171 | + source_form_override: None, |
| 172 | + default_integer_8: false, |
| 173 | + default_real_8: false, |
| 174 | + force_implicit_none: false, |
| 175 | + recursive_default: false, |
| 176 | + backslash_escapes: false, |
| 177 | + max_stack_var_size: None, |
| 178 | + opt_level: OptLevel::O0, |
| 179 | + warn_all: false, |
| 180 | + warn_extra: false, |
| 181 | + warn_pedantic: false, |
| 182 | + warn_deprecated: false, |
| 183 | + warn_as_error: false, |
| 184 | + disabled_warnings: Vec::new(), |
| 185 | + debug_info: false, |
| 186 | + verbose: false, |
| 187 | + time_report: false, |
| 188 | + diagnostics_format: DiagnosticsFormat::Text, |
| 189 | + check_bounds: false, |
| 190 | + check_all: false, |
| 191 | + module_search_paths: Vec::new(), |
| 192 | + module_output_dir: None, |
| 193 | + library_search_paths: Vec::new(), |
| 194 | + link_libs: Vec::new(), |
| 195 | + shared: false, |
| 196 | + static_link: false, |
| 197 | + rpath: Vec::new(), |
| 130 | 198 | } |
| 199 | + } |
| 200 | +} |
| 131 | 201 | |
| 132 | | - if inputs.is_empty() { |
| 133 | | - return Err("no input file".into()); |
| 202 | +impl Options { |
| 203 | + /// Old name preserved for callers that haven't been migrated. |
| 204 | + /// New code should call `parse_cli` and dispatch on `ParsedCli`. |
| 205 | + pub fn from_args(args: &[String]) -> Result<Self, String> { |
| 206 | + match parse_cli(args)? { |
| 207 | + ParsedCli::Compile(opts) => Ok(*opts), |
| 208 | + ParsedCli::Info(_) => Err("info request — call parse_cli".into()), |
| 134 | 209 | } |
| 135 | | - let input = inputs.remove(0); |
| 136 | | - Ok(Self { |
| 137 | | - input, |
| 138 | | - extra_inputs: inputs, |
| 139 | | - output, |
| 140 | | - emit_asm, |
| 141 | | - emit_obj, |
| 142 | | - emit_ir, |
| 143 | | - preprocess_only, |
| 144 | | - opt_level, |
| 145 | | - module_search_paths, |
| 146 | | - }) |
| 147 | 210 | } |
| 148 | 211 | |
| 149 | 212 | /// Determine the output path based on input and flags. |
@@ -169,6 +232,354 @@ impl Options { |
| 169 | 232 | } |
| 170 | 233 | } |
| 171 | 234 | |
| 235 | +/// Parse the command line. Returns either a compile job or a request |
| 236 | +/// for informational output (so main.rs can branch and exit cleanly |
| 237 | +/// without a compile attempt). Supports response files via `@file`, |
| 238 | +/// joined-form short options (`-Idir`, `-O2`, `-llib`), and |
| 239 | +/// `--key=value` style for the long options that take a value. |
| 240 | +pub fn parse_cli(raw_args: &[String]) -> Result<ParsedCli, String> { |
| 241 | + let args = expand_response_files(raw_args)?; |
| 242 | + let mut opts = Options::default(); |
| 243 | + let mut inputs: Vec<PathBuf> = Vec::new(); |
| 244 | + let mut info_action: Option<InfoAction> = None; |
| 245 | + |
| 246 | + let mut i = 0; |
| 247 | + while i < args.len() { |
| 248 | + let arg = args[i].clone(); |
| 249 | + match arg.as_str() { |
| 250 | + // ---- Information ---- |
| 251 | + "--help" | "-h" => info_action = Some(InfoAction::Help), |
| 252 | + "--version" | "-V" => info_action = Some(InfoAction::Version), |
| 253 | + "-dumpversion" => info_action = Some(InfoAction::DumpVersion), |
| 254 | + |
| 255 | + // ---- Output path ---- |
| 256 | + "-o" => { |
| 257 | + i += 1; |
| 258 | + opts.output = Some(PathBuf::from(args.get(i).ok_or("-o requires an argument")?)); |
| 259 | + } |
| 260 | + |
| 261 | + // ---- Mode ---- |
| 262 | + "-S" => opts.emit_asm = true, |
| 263 | + "-c" => opts.emit_obj = true, |
| 264 | + "-E" => opts.preprocess_only = true, |
| 265 | + "--emit-ir" => opts.emit_ir = true, |
| 266 | + "--emit-ast" => opts.emit_ast = true, |
| 267 | + "--emit-tokens" => opts.emit_tokens = true, |
| 268 | + |
| 269 | + // ---- Optimization ---- |
| 270 | + "-O" => opts.opt_level = OptLevel::O0, |
| 271 | + arg if arg.starts_with("-O") => { |
| 272 | + opts.opt_level = OptLevel::parse_flag(&arg[1..]) |
| 273 | + .ok_or_else(|| format!("unknown optimization level: {}", arg))?; |
| 274 | + } |
| 275 | + |
| 276 | + // ---- Module / include search paths ---- |
| 277 | + "-I" => { |
| 278 | + i += 1; |
| 279 | + opts.module_search_paths |
| 280 | + .push(PathBuf::from(args.get(i).ok_or("-I requires a directory")?)); |
| 281 | + } |
| 282 | + arg if arg.starts_with("-I") => opts.module_search_paths.push(PathBuf::from(&arg[2..])), |
| 283 | + |
| 284 | + "-J" => { |
| 285 | + i += 1; |
| 286 | + opts.module_output_dir = |
| 287 | + Some(PathBuf::from(args.get(i).ok_or("-J requires a directory")?)); |
| 288 | + } |
| 289 | + arg if arg.starts_with("-J") => { |
| 290 | + opts.module_output_dir = Some(PathBuf::from(&arg[2..])); |
| 291 | + } |
| 292 | + |
| 293 | + // ---- Linker search / libs / rpath ---- |
| 294 | + "-L" => { |
| 295 | + i += 1; |
| 296 | + opts.library_search_paths |
| 297 | + .push(PathBuf::from(args.get(i).ok_or("-L requires a directory")?)); |
| 298 | + } |
| 299 | + arg if arg.starts_with("-L") => { |
| 300 | + opts.library_search_paths.push(PathBuf::from(&arg[2..])) |
| 301 | + } |
| 302 | + |
| 303 | + "-l" => { |
| 304 | + i += 1; |
| 305 | + opts.link_libs |
| 306 | + .push(args.get(i).ok_or("-l requires a library name")?.clone()); |
| 307 | + } |
| 308 | + arg if arg.starts_with("-l") => opts.link_libs.push(arg[2..].to_string()), |
| 309 | + |
| 310 | + "-rpath" | "--rpath" => { |
| 311 | + i += 1; |
| 312 | + opts.rpath |
| 313 | + .push(PathBuf::from(args.get(i).ok_or("-rpath requires a path")?)); |
| 314 | + } |
| 315 | + |
| 316 | + "-shared" => opts.shared = true, |
| 317 | + "-static" => opts.static_link = true, |
| 318 | + |
| 319 | + // ---- Standards / language flags ---- |
| 320 | + arg if arg.starts_with("--std=") => { |
| 321 | + let val = &arg["--std=".len()..]; |
| 322 | + opts.std = Some( |
| 323 | + crate::sema::validate::FortranStandard::parse_flag(val) |
| 324 | + .ok_or_else(|| format!("unknown --std value: {}", val))?, |
| 325 | + ); |
| 326 | + } |
| 327 | + "--std" => { |
| 328 | + i += 1; |
| 329 | + let val = args.get(i).ok_or("--std requires a value")?; |
| 330 | + opts.std = Some( |
| 331 | + crate::sema::validate::FortranStandard::parse_flag(val) |
| 332 | + .ok_or_else(|| format!("unknown --std value: {}", val))?, |
| 333 | + ); |
| 334 | + } |
| 335 | + "-ffree-form" => opts.source_form_override = Some(SourceFormOverride::Free), |
| 336 | + "-ffixed-form" => opts.source_form_override = Some(SourceFormOverride::Fixed), |
| 337 | + "-fdefault-integer-8" => opts.default_integer_8 = true, |
| 338 | + "-fdefault-real-8" => opts.default_real_8 = true, |
| 339 | + "-fimplicit-none" => opts.force_implicit_none = true, |
| 340 | + "-frecursive" => opts.recursive_default = true, |
| 341 | + "-fbackslash" => opts.backslash_escapes = true, |
| 342 | + "-fno-backslash" => opts.backslash_escapes = false, |
| 343 | + arg if arg.starts_with("-fmax-stack-var-size=") => { |
| 344 | + let val = &arg["-fmax-stack-var-size=".len()..]; |
| 345 | + opts.max_stack_var_size = Some( |
| 346 | + val.parse() |
| 347 | + .map_err(|_| format!("invalid -fmax-stack-var-size value: {}", val))?, |
| 348 | + ); |
| 349 | + } |
| 350 | + |
| 351 | + // ---- Runtime checks ---- |
| 352 | + "-fcheck=bounds" => opts.check_bounds = true, |
| 353 | + "-fcheck=all" => { |
| 354 | + opts.check_bounds = true; |
| 355 | + opts.check_all = true; |
| 356 | + } |
| 357 | + |
| 358 | + // ---- Warnings (accepted; gating is gradual sprint work) ---- |
| 359 | + "-Wall" => opts.warn_all = true, |
| 360 | + "-Wextra" => opts.warn_extra = true, |
| 361 | + "-Wpedantic" | "-pedantic" => opts.warn_pedantic = true, |
| 362 | + "-Wdeprecated" => opts.warn_deprecated = true, |
| 363 | + "-Werror" => opts.warn_as_error = true, |
| 364 | + arg if arg.starts_with("-Wno-") => { |
| 365 | + opts.disabled_warnings.push(arg[5..].to_string()); |
| 366 | + } |
| 367 | + arg if arg.starts_with("-W") => { |
| 368 | + // Unknown -Wfoo: accept silently rather than fail |
| 369 | + // (gfortran has hundreds of these and projects pass |
| 370 | + // them blindly). Record so it's queryable later. |
| 371 | + opts.disabled_warnings.push(arg.to_string()); |
| 372 | + } |
| 373 | + |
| 374 | + // ---- Debug / introspection ---- |
| 375 | + "-g" | "-g1" | "-g2" | "-g3" | "-g0" => opts.debug_info = true, |
| 376 | + arg if arg.starts_with("-g") => opts.debug_info = true, |
| 377 | + "-v" | "--verbose" => opts.verbose = true, |
| 378 | + "--time-report" => opts.time_report = true, |
| 379 | + arg if arg.starts_with("--diagnostics-format=") => { |
| 380 | + let val = &arg["--diagnostics-format=".len()..]; |
| 381 | + opts.diagnostics_format = match val { |
| 382 | + "text" => DiagnosticsFormat::Text, |
| 383 | + "json" => DiagnosticsFormat::Json, |
| 384 | + other => { |
| 385 | + return Err(format!("unknown --diagnostics-format value: {}", other)) |
| 386 | + } |
| 387 | + }; |
| 388 | + } |
| 389 | + |
| 390 | + // ---- Positional input file ---- |
| 391 | + arg if !arg.starts_with('-') => inputs.push(PathBuf::from(arg)), |
| 392 | + |
| 393 | + other => return Err(format!("unknown option: {}", other)), |
| 394 | + } |
| 395 | + i += 1; |
| 396 | + } |
| 397 | + |
| 398 | + if let Some(action) = info_action { |
| 399 | + return Ok(ParsedCli::Info(action)); |
| 400 | + } |
| 401 | + |
| 402 | + if inputs.is_empty() { |
| 403 | + return Err("no input file".into()); |
| 404 | + } |
| 405 | + opts.input = inputs.remove(0); |
| 406 | + opts.extra_inputs = inputs; |
| 407 | + Ok(ParsedCli::Compile(Box::new(opts))) |
| 408 | +} |
| 409 | + |
| 410 | +/// Expand any `@file` argument into the lines of `file`, treating |
| 411 | +/// each whitespace-separated token as an additional argument. |
| 412 | +fn expand_response_files(args: &[String]) -> Result<Vec<String>, String> { |
| 413 | + let mut expanded: Vec<String> = Vec::with_capacity(args.len()); |
| 414 | + for arg in args { |
| 415 | + if let Some(path) = arg.strip_prefix('@') { |
| 416 | + let body = fs::read_to_string(path) |
| 417 | + .map_err(|e| format!("cannot read response file '{}': {}", path, e))?; |
| 418 | + for tok in body.split_whitespace() { |
| 419 | + expanded.push(tok.to_string()); |
| 420 | + } |
| 421 | + } else { |
| 422 | + expanded.push(arg.clone()); |
| 423 | + } |
| 424 | + } |
| 425 | + Ok(expanded) |
| 426 | +} |
| 427 | + |
| 428 | +/// Help text printed by `--help`. |
| 429 | +pub const HELP_TEXT: &str = "\ |
| 430 | +USAGE: armfortas [OPTIONS] <files...> |
| 431 | + afs [OPTIONS] <files...> |
| 432 | + |
| 433 | +COMPILATION: |
| 434 | + -c Compile to object file only (no linking) |
| 435 | + -S Emit assembly text |
| 436 | + -E Preprocess only |
| 437 | + -o <file> Output file name |
| 438 | + |
| 439 | +LANGUAGE: |
| 440 | + --std=<standard> Fortran standard (f77, f90, f95, f2003, f2008, f2018, f2023) |
| 441 | + -ffree-form Force free-form source |
| 442 | + -ffixed-form Force fixed-form source |
| 443 | + -fdefault-integer-8 Make default integer kind 8 bytes |
| 444 | + -fdefault-real-8 Make default real kind 8 bytes |
| 445 | + -fimplicit-none Force implicit none in all scopes |
| 446 | + -frecursive Make all procedures recursive by default |
| 447 | + -fbackslash Interpret backslash in strings as escape |
| 448 | + -fmax-stack-var-size=<n> Stack variable size threshold (bytes) |
| 449 | + |
| 450 | +OPTIMIZATION: |
| 451 | + -O0, -O1, -O2, -O3 Optimization level (default -O0) |
| 452 | + -Os Optimize for size |
| 453 | + -Ofast Aggressive optimization |
| 454 | + |
| 455 | +WARNINGS: |
| 456 | + -Wall All standard warnings |
| 457 | + -Wextra Extra warnings |
| 458 | + -Wpedantic Pedantic standard conformance warnings |
| 459 | + -Wdeprecated Deprecated feature warnings |
| 460 | + -Werror Treat warnings as errors |
| 461 | + -Wno-<name> Disable specific warning |
| 462 | + |
| 463 | +DEBUGGING: |
| 464 | + -g Generate debug information (DWARF emission TODO) |
| 465 | + --emit-ir Dump IR to the output path |
| 466 | + --emit-ast Dump AST to the output path |
| 467 | + --emit-tokens Dump token stream to the output path |
| 468 | + -v, --verbose Verbose output (show compilation phases) |
| 469 | + --time-report Show time spent in each compilation phase |
| 470 | + -fcheck=bounds Enable runtime array bounds checking |
| 471 | + -fcheck=all Enable all runtime checks |
| 472 | + --diagnostics-format=text|json |
| 473 | + Diagnostic output format |
| 474 | + |
| 475 | +DIRECTORIES: |
| 476 | + -I <dir> Module/include search path |
| 477 | + -J <dir> Module output directory |
| 478 | + -L <dir> Library search path |
| 479 | + -l <lib> Link library |
| 480 | + |
| 481 | +LINKING: |
| 482 | + -shared Produce shared library |
| 483 | + -static Static linking |
| 484 | + -rpath <path> Runtime library path |
| 485 | + |
| 486 | +INFORMATION: |
| 487 | + --version, -V Print version |
| 488 | + --help, -h Print help |
| 489 | + -dumpversion Print version number only |
| 490 | + |
| 491 | +OTHER: |
| 492 | + @<file> Read additional arguments from <file> (one per token) |
| 493 | +"; |
| 494 | + |
| 495 | +/// Version string emitted by `--version`. |
| 496 | +pub fn version_string() -> String { |
| 497 | + format!( |
| 498 | + "armfortas {} (aarch64-apple-darwin)", |
| 499 | + env!("CARGO_PKG_VERSION") |
| 500 | + ) |
| 501 | +} |
| 502 | + |
| 503 | +/// Just the version number, for `-dumpversion`. |
| 504 | +pub fn dump_version_string() -> String { |
| 505 | + env!("CARGO_PKG_VERSION").to_string() |
| 506 | +} |
| 507 | + |
| 508 | +/// Tracks per-phase wall-clock time for `--time-report`. When |
| 509 | +/// disabled, all operations are zero-overhead (no Instant calls, no |
| 510 | +/// allocation). |
| 511 | +struct PhaseTimer { |
| 512 | + enabled: bool, |
| 513 | + samples: Vec<(&'static str, std::time::Duration)>, |
| 514 | + start: Option<std::time::Instant>, |
| 515 | +} |
| 516 | + |
| 517 | +struct PhaseGuard { |
| 518 | + name: &'static str, |
| 519 | + started: Option<std::time::Instant>, |
| 520 | +} |
| 521 | + |
| 522 | +impl PhaseTimer { |
| 523 | + fn new(enabled: bool) -> Self { |
| 524 | + Self { |
| 525 | + enabled, |
| 526 | + samples: Vec::new(), |
| 527 | + start: if enabled { |
| 528 | + Some(std::time::Instant::now()) |
| 529 | + } else { |
| 530 | + None |
| 531 | + }, |
| 532 | + } |
| 533 | + } |
| 534 | + fn start(&self, name: &'static str) -> PhaseGuard { |
| 535 | + PhaseGuard { |
| 536 | + name, |
| 537 | + started: if self.enabled { |
| 538 | + Some(std::time::Instant::now()) |
| 539 | + } else { |
| 540 | + None |
| 541 | + }, |
| 542 | + } |
| 543 | + } |
| 544 | + fn record(&mut self, name: &'static str, dur: std::time::Duration) { |
| 545 | + if self.enabled { |
| 546 | + self.samples.push((name, dur)); |
| 547 | + } |
| 548 | + } |
| 549 | + fn report(&self) { |
| 550 | + if !self.enabled { |
| 551 | + return; |
| 552 | + } |
| 553 | + let total: std::time::Duration = self |
| 554 | + .samples |
| 555 | + .iter() |
| 556 | + .map(|(_, d)| *d) |
| 557 | + .sum::<std::time::Duration>(); |
| 558 | + let total_ms = total.as_secs_f64() * 1000.0; |
| 559 | + eprintln!("Phase Time (ms) %"); |
| 560 | + eprintln!("─────────────────────────────────"); |
| 561 | + for (name, d) in &self.samples { |
| 562 | + let ms = d.as_secs_f64() * 1000.0; |
| 563 | + let pct = if total_ms > 0.0 { ms / total_ms * 100.0 } else { 0.0 }; |
| 564 | + eprintln!("{:<16} {:>8.2} {:>4.0}%", name, ms, pct); |
| 565 | + } |
| 566 | + eprintln!("─────────────────────────────────"); |
| 567 | + let wall = self |
| 568 | + .start |
| 569 | + .map(|s| s.elapsed().as_secs_f64() * 1000.0) |
| 570 | + .unwrap_or(0.0); |
| 571 | + eprintln!("{:<16} {:>8.2} {:>4.0}%", "Total", wall, 100.0); |
| 572 | + } |
| 573 | +} |
| 574 | + |
| 575 | +impl PhaseGuard { |
| 576 | + fn end(self, timer: &mut PhaseTimer) { |
| 577 | + if let Some(start) = self.started { |
| 578 | + timer.record(self.name, start.elapsed()); |
| 579 | + } |
| 580 | + } |
| 581 | +} |
| 582 | + |
| 172 | 583 | fn main_wrapper_target(allocated: &[MachineFunction]) -> Option<&str> { |
| 173 | 584 | // Only emit _main if there's a __prog_* function (a Fortran PROGRAM |
| 174 | 585 | // body). The previous .or_else fallback picked any non-"main" |
@@ -181,12 +592,34 @@ fn main_wrapper_target(allocated: &[MachineFunction]) -> Option<&str> { |
| 181 | 592 | |
| 182 | 593 | /// Compile a Fortran source file through the full pipeline. |
| 183 | 594 | pub fn compile(opts: &Options) -> Result<(), String> { |
| 595 | + let mut phases = PhaseTimer::new(opts.time_report); |
| 596 | + if opts.verbose { |
| 597 | + eprintln!("{}", version_string()); |
| 598 | + } |
| 599 | + |
| 184 | 600 | // 1. Read source. |
| 601 | + if opts.verbose { |
| 602 | + eprintln!(" reading: {}", opts.input.display()); |
| 603 | + } |
| 604 | + let phase = phases.start("read"); |
| 185 | 605 | let source = fs::read_to_string(&opts.input) |
| 186 | 606 | .map_err(|e| format!("cannot read '{}': {}", opts.input.display(), e))?; |
| 607 | + phase.end(&mut phases); |
| 187 | 608 | |
| 188 | 609 | // 2. Preprocess. |
| 189 | | - let source_form = detect_source_form(&opts.input.to_string_lossy()); |
| 610 | + let source_form = match opts.source_form_override { |
| 611 | + Some(SourceFormOverride::Free) => SourceForm::FreeForm, |
| 612 | + Some(SourceFormOverride::Fixed) => SourceForm::FixedForm, |
| 613 | + None => detect_source_form(&opts.input.to_string_lossy()), |
| 614 | + }; |
| 615 | + if opts.verbose { |
| 616 | + let form = match source_form { |
| 617 | + SourceForm::FreeForm => "free-form", |
| 618 | + SourceForm::FixedForm => "fixed-form", |
| 619 | + }; |
| 620 | + eprintln!(" preprocessing: {} ({})", opts.input.display(), form); |
| 621 | + } |
| 622 | + let phase = phases.start("preprocess"); |
| 190 | 623 | let pp_config = crate::preprocess::PreprocConfig { |
| 191 | 624 | filename: opts.input.to_str().unwrap_or("<input>").to_string(), |
| 192 | 625 | fixed_form: matches!(source_form, SourceForm::FixedForm), |
@@ -194,6 +627,7 @@ pub fn compile(opts: &Options) -> Result<(), String> { |
| 194 | 627 | }; |
| 195 | 628 | let pp_result = |
| 196 | 629 | crate::preprocess::preprocess(&source, &pp_config).map_err(|e| format!("{}", e))?; |
| 630 | + phase.end(&mut phases); |
| 197 | 631 | let preprocessed = pp_result.text; |
| 198 | 632 | |
| 199 | 633 | if opts.preprocess_only { |
@@ -204,10 +638,15 @@ pub fn compile(opts: &Options) -> Result<(), String> { |
| 204 | 638 | fs::write(&out, &preprocessed) |
| 205 | 639 | .map_err(|e| format!("cannot write '{}': {}", out.display(), e))?; |
| 206 | 640 | } |
| 641 | + if opts.verbose { |
| 642 | + eprintln!(" preprocess-only: wrote {}", out.display()); |
| 643 | + } |
| 644 | + phases.report(); |
| 207 | 645 | return Ok(()); |
| 208 | 646 | } |
| 209 | 647 | |
| 210 | 648 | // 3. Lex. |
| 649 | + let phase = phases.start("lex"); |
| 211 | 650 | let tokens = tokenize(&preprocessed, 0, source_form).map_err(|e| { |
| 212 | 651 | format!( |
| 213 | 652 | "{}:{}:{}: lexer error: {}", |
@@ -217,8 +656,23 @@ pub fn compile(opts: &Options) -> Result<(), String> { |
| 217 | 656 | e.msg |
| 218 | 657 | ) |
| 219 | 658 | })?; |
| 659 | + phase.end(&mut phases); |
| 660 | + if opts.verbose { |
| 661 | + eprintln!(" lexed: {} tokens", tokens.len()); |
| 662 | + } |
| 663 | + if opts.emit_tokens { |
| 664 | + let out = opts.output_path(); |
| 665 | + let mut buf = String::new(); |
| 666 | + for t in &tokens { |
| 667 | + buf.push_str(&format!("{:?}\n", t)); |
| 668 | + } |
| 669 | + fs::write(&out, &buf) |
| 670 | + .map_err(|e| format!("cannot write '{}': {}", out.display(), e))?; |
| 671 | + return Ok(()); |
| 672 | + } |
| 220 | 673 | |
| 221 | 674 | // 4. Parse. |
| 675 | + let phase = phases.start("parse"); |
| 222 | 676 | let mut parser = Parser::new(&tokens); |
| 223 | 677 | let units = parser.parse_file().map_err(|e| { |
| 224 | 678 | format!( |
@@ -229,8 +683,23 @@ pub fn compile(opts: &Options) -> Result<(), String> { |
| 229 | 683 | e.msg |
| 230 | 684 | ) |
| 231 | 685 | })?; |
| 686 | + phase.end(&mut phases); |
| 687 | + if opts.verbose { |
| 688 | + eprintln!(" parsed: {} top-level units", units.len()); |
| 689 | + } |
| 690 | + if opts.emit_ast { |
| 691 | + let out = opts.output_path(); |
| 692 | + let mut buf = String::new(); |
| 693 | + for u in &units { |
| 694 | + buf.push_str(&format!("{:#?}\n", u)); |
| 695 | + } |
| 696 | + fs::write(&out, &buf) |
| 697 | + .map_err(|e| format!("cannot write '{}': {}", out.display(), e))?; |
| 698 | + return Ok(()); |
| 699 | + } |
| 232 | 700 | |
| 233 | 701 | // 5. Semantic analysis. |
| 702 | + let phase = phases.start("sema"); |
| 234 | 703 | let resolve_result = resolve::resolve_file(&units, &opts.module_search_paths).map_err(|e| { |
| 235 | 704 | format!( |
| 236 | 705 | "{}:{}:{}: {}", |
@@ -249,18 +718,41 @@ pub fn compile(opts: &Options) -> Result<(), String> { |
| 249 | 718 | external_globals.extend(crate::sema::amod::extract_module_globals(ext_mod)); |
| 250 | 719 | } |
| 251 | 720 | |
| 252 | | - let diags = validate::validate_file_with_layouts(&units, &st, None, &type_layouts); |
| 721 | + let diags = validate::validate_file_with_layouts(&units, &st, opts.std, &type_layouts); |
| 722 | + let mut had_error = false; |
| 253 | 723 | for d in &diags { |
| 254 | | - if d.kind == validate::DiagKind::Error { |
| 255 | | - return Err(format!( |
| 256 | | - "{}:{}:{}: error: {}", |
| 257 | | - opts.input.display(), |
| 258 | | - d.span.start.line, |
| 259 | | - d.span.start.col, |
| 260 | | - d.msg |
| 261 | | - )); |
| 724 | + match d.kind { |
| 725 | + validate::DiagKind::Error => { |
| 726 | + eprintln!( |
| 727 | + "{}:{}:{}: error: {}", |
| 728 | + opts.input.display(), |
| 729 | + d.span.start.line, |
| 730 | + d.span.start.col, |
| 731 | + d.msg |
| 732 | + ); |
| 733 | + had_error = true; |
| 734 | + } |
| 735 | + validate::DiagKind::Warning => { |
| 736 | + eprintln!( |
| 737 | + "{}:{}:{}: warning: {}", |
| 738 | + opts.input.display(), |
| 739 | + d.span.start.line, |
| 740 | + d.span.start.col, |
| 741 | + d.msg |
| 742 | + ); |
| 743 | + if opts.warn_as_error { |
| 744 | + had_error = true; |
| 745 | + } |
| 746 | + } |
| 262 | 747 | } |
| 263 | 748 | } |
| 749 | + if had_error { |
| 750 | + return Err(format!("aborting due to errors in {}", opts.input.display())); |
| 751 | + } |
| 752 | + phase.end(&mut phases); |
| 753 | + if opts.verbose { |
| 754 | + eprintln!(" sema: {} diagnostics", diags.len()); |
| 755 | + } |
| 264 | 756 | |
| 265 | 757 | // 6. Lower to IR. |
| 266 | 758 | // Build external char_len_star_params from .amod-loaded modules. |
@@ -280,12 +772,16 @@ pub fn compile(opts: &Options) -> Result<(), String> { |
| 280 | 772 | return Err(format!("internal error: IR verification failed:\n{}", msg)); |
| 281 | 773 | } |
| 282 | 774 | let module_has_i128 = ir_module.contains_i128(); |
| 775 | + if opts.verbose { |
| 776 | + eprintln!(" IR: {} functions", ir_module.functions.len()); |
| 777 | + } |
| 283 | 778 | // 6.5. Run IR optimization pipeline. |
| 284 | 779 | // |
| 285 | 780 | // This is where const_fold, mem2reg, LICM, DSE, loop unrolling, and |
| 286 | 781 | // every other IR-level pass actually fire. At O0 the pipeline is empty |
| 287 | 782 | // so nothing changes. The pipeline runs to fixpoint; the pass manager |
| 288 | 783 | // verifies the IR after every pass. |
| 784 | + let phase = phases.start("opt"); |
| 289 | 785 | { |
| 290 | 786 | use crate::opt::pipeline::OptLevel as IrOpt; |
| 291 | 787 | let ir_opt = match opts.opt_level { |
@@ -308,6 +804,10 @@ pub fn compile(opts: &Options) -> Result<(), String> { |
| 308 | 804 | }; |
| 309 | 805 | pm.run(&mut ir_module); |
| 310 | 806 | } |
| 807 | + phase.end(&mut phases); |
| 808 | + if opts.verbose { |
| 809 | + eprintln!(" optimization: -{}", opts.opt_level.as_str()); |
| 810 | + } |
| 311 | 811 | |
| 312 | 812 | if opts.emit_ir { |
| 313 | 813 | let ir_text = ir_printer::print_module(&ir_module); |
@@ -325,6 +825,7 @@ pub fn compile(opts: &Options) -> Result<(), String> { |
| 325 | 825 | } |
| 326 | 826 | |
| 327 | 827 | // 7. Instruction selection. |
| 828 | + let phase = phases.start("codegen"); |
| 328 | 829 | let machine_funcs = isel::select_module(&ir_module); |
| 329 | 830 | |
| 330 | 831 | // 7.5. Backend peephole (O2+): FMA fusion, etc. |
@@ -391,10 +892,18 @@ _main: |
| 391 | 892 | } |
| 392 | 893 | } |
| 393 | 894 | |
| 895 | + phase.end(&mut phases); |
| 896 | + if opts.verbose { |
| 897 | + eprintln!(" codegen: {} machine functions", allocated.len()); |
| 898 | + } |
| 394 | 899 | if opts.emit_asm { |
| 395 | 900 | let out = opts.output_path(); |
| 396 | 901 | fs::write(&out, &asm_text) |
| 397 | 902 | .map_err(|e| format!("cannot write '{}': {}", out.display(), e))?; |
| 903 | + if opts.verbose { |
| 904 | + eprintln!(" wrote: {}", out.display()); |
| 905 | + } |
| 906 | + phases.report(); |
| 398 | 907 | return Ok(()); |
| 399 | 908 | } |
| 400 | 909 | |
@@ -442,18 +951,25 @@ _main: |
| 442 | 951 | |
| 443 | 952 | fs::write(&asm_path, &asm_text).map_err(|e| format!("cannot write temp assembly: {}", e))?; |
| 444 | 953 | |
| 954 | + let phase = phases.start("assemble"); |
| 445 | 955 | let as_result = Command::new("as") |
| 446 | 956 | .args(["-o", obj_path.to_str().unwrap(), asm_path.to_str().unwrap()]) |
| 447 | 957 | .output() |
| 448 | 958 | .map_err(|e| format!("cannot run assembler: {}", e))?; |
| 959 | + phase.end(&mut phases); |
| 449 | 960 | |
| 450 | 961 | if !as_result.status.success() { |
| 451 | 962 | let stderr = String::from_utf8_lossy(&as_result.stderr); |
| 452 | 963 | return Err(format!("assembler failed:\n{}", stderr)); |
| 453 | 964 | } |
| 965 | + if opts.verbose { |
| 966 | + eprintln!(" assembled: {}", obj_path.display()); |
| 967 | + } |
| 454 | 968 | |
| 455 | 969 | if opts.emit_obj { |
| 456 | 970 | // Emit .amod files for each MODULE in the compilation unit. |
| 971 | + // -J <dir> overrides where they go; default is the parent of |
| 972 | + // the output .o. |
| 457 | 973 | for unit in &units { |
| 458 | 974 | if let crate::ast::unit::ProgramUnit::Module { name, .. } = &unit.node { |
| 459 | 975 | let mod_key = name.to_lowercase(); |
@@ -469,55 +985,72 @@ _main: |
| 469 | 985 | &ir_module, |
| 470 | 986 | &std::collections::HashMap::new(), // char_len_star computed by writer from scope |
| 471 | 987 | ); |
| 472 | | - let amod_path = opts.output_path() |
| 473 | | - .parent() |
| 474 | | - .unwrap_or_else(|| std::path::Path::new(".")) |
| 475 | | - .join(format!("{}.amod", mod_key)); |
| 988 | + let amod_dir: std::path::PathBuf = opts |
| 989 | + .module_output_dir |
| 990 | + .clone() |
| 991 | + .unwrap_or_else(|| { |
| 992 | + opts.output_path() |
| 993 | + .parent() |
| 994 | + .unwrap_or_else(|| std::path::Path::new(".")) |
| 995 | + .to_path_buf() |
| 996 | + }); |
| 997 | + let amod_path = amod_dir.join(format!("{}.amod", mod_key)); |
| 476 | 998 | if let Err(e) = fs::write(&amod_path, &amod_text) { |
| 477 | 999 | eprintln!("warning: cannot write {}: {}", amod_path.display(), e); |
| 1000 | + } else if opts.verbose { |
| 1001 | + eprintln!(" amod: {}", amod_path.display()); |
| 478 | 1002 | } |
| 479 | 1003 | } |
| 480 | 1004 | } |
| 481 | 1005 | } |
| 1006 | + phases.report(); |
| 482 | 1007 | return Ok(()); |
| 483 | 1008 | } |
| 484 | 1009 | |
| 485 | 1010 | // 11. Link. |
| 486 | 1011 | let binary_path = opts.output_path(); |
| 487 | | - link(&obj_path, &binary_path)?; |
| 1012 | + let phase = phases.start("link"); |
| 1013 | + link(&obj_path, &binary_path, opts)?; |
| 1014 | + phase.end(&mut phases); |
| 1015 | + if opts.verbose { |
| 1016 | + eprintln!(" linked: {}", binary_path.display()); |
| 1017 | + } |
| 488 | 1018 | |
| 489 | 1019 | // Cleanup. |
| 490 | 1020 | let _ = fs::remove_file(&asm_path); |
| 491 | 1021 | let _ = fs::remove_file(&obj_path); |
| 492 | 1022 | |
| 1023 | + phases.report(); |
| 493 | 1024 | Ok(()) |
| 494 | 1025 | } |
| 495 | 1026 | |
| 496 | 1027 | /// Link an object file with the runtime library to produce a binary. |
| 497 | | -fn link(obj: &Path, output: &Path) -> Result<(), String> { |
| 498 | | - // Find the runtime library. |
| 1028 | +/// `opts` contributes the user-supplied `-L`, `-l`, `-rpath`, |
| 1029 | +/// `-shared`, and `-static` flags that need to make it through to ld. |
| 1030 | +fn link(obj: &Path, output: &Path, opts: &Options) -> Result<(), String> { |
| 499 | 1031 | let rt_path = find_runtime_lib()?; |
| 500 | | - |
| 501 | | - // Find the SDK sysroot. |
| 502 | 1032 | let sdk = Command::new("xcrun") |
| 503 | 1033 | .args(["--show-sdk-path"]) |
| 504 | 1034 | .output() |
| 505 | 1035 | .map_err(|e| format!("cannot run xcrun: {}", e))?; |
| 506 | 1036 | let sysroot = String::from_utf8_lossy(&sdk.stdout).trim().to_string(); |
| 507 | 1037 | |
| 1038 | + let mut args: Vec<String> = vec![ |
| 1039 | + obj.to_string_lossy().into_owned(), |
| 1040 | + rt_path, |
| 1041 | + "-lSystem".into(), |
| 1042 | + "-no_uuid".into(), |
| 1043 | + "-syslibroot".into(), |
| 1044 | + sysroot, |
| 1045 | + "-e".into(), |
| 1046 | + "_main".into(), |
| 1047 | + "-o".into(), |
| 1048 | + output.to_string_lossy().into_owned(), |
| 1049 | + ]; |
| 1050 | + push_link_flags(&mut args, opts); |
| 1051 | + |
| 508 | 1052 | let ld_result = Command::new("ld") |
| 509 | | - .args([ |
| 510 | | - obj.to_str().unwrap(), |
| 511 | | - &rt_path, |
| 512 | | - "-lSystem", |
| 513 | | - "-no_uuid", |
| 514 | | - "-syslibroot", |
| 515 | | - &sysroot, |
| 516 | | - "-e", |
| 517 | | - "_main", |
| 518 | | - "-o", |
| 519 | | - output.to_str().unwrap(), |
| 520 | | - ]) |
| 1053 | + .args(&args) |
| 521 | 1054 | .output() |
| 522 | 1055 | .map_err(|e| format!("cannot run linker: {}", e))?; |
| 523 | 1056 | |
@@ -529,8 +1062,34 @@ fn link(obj: &Path, output: &Path) -> Result<(), String> { |
| 529 | 1062 | Ok(()) |
| 530 | 1063 | } |
| 531 | 1064 | |
| 1065 | +/// Append the user-supplied linker flags from `opts` to `args`. |
| 1066 | +/// `-L<dir>` and `-l<name>` map directly; `-rpath` is passed as a |
| 1067 | +/// pair; `-shared` switches output type; `-static` discourages |
| 1068 | +/// dynamic linking on supported platforms. |
| 1069 | +fn push_link_flags(args: &mut Vec<String>, opts: &Options) { |
| 1070 | + for dir in &opts.library_search_paths { |
| 1071 | + args.push(format!("-L{}", dir.display())); |
| 1072 | + } |
| 1073 | + for lib in &opts.link_libs { |
| 1074 | + args.push(format!("-l{}", lib)); |
| 1075 | + } |
| 1076 | + for path in &opts.rpath { |
| 1077 | + args.push("-rpath".into()); |
| 1078 | + args.push(path.to_string_lossy().into_owned()); |
| 1079 | + } |
| 1080 | + if opts.shared { |
| 1081 | + args.push("-dylib".into()); |
| 1082 | + } |
| 1083 | + if opts.static_link { |
| 1084 | + // Apple ld doesn't have a true -static; the closest is |
| 1085 | + // -search_paths_first to bias toward .a archives. Keep the |
| 1086 | + // intent visible without breaking link. |
| 1087 | + args.push("-search_paths_first".into()); |
| 1088 | + } |
| 1089 | +} |
| 1090 | + |
| 532 | 1091 | /// Link multiple object files with the runtime to produce a binary. |
| 533 | | -fn link_multi(objs: &[PathBuf], output: &Path) -> Result<(), String> { |
| 1092 | +fn link_multi(objs: &[PathBuf], output: &Path, opts: &Options) -> Result<(), String> { |
| 534 | 1093 | let rt_path = find_runtime_lib()?; |
| 535 | 1094 | let sdk = Command::new("xcrun") |
| 536 | 1095 | .args(["--show-sdk-path"]) |
@@ -553,6 +1112,7 @@ fn link_multi(objs: &[PathBuf], output: &Path) -> Result<(), String> { |
| 553 | 1112 | "-e".into(), |
| 554 | 1113 | "_main".into(), |
| 555 | 1114 | ]); |
| 1115 | + push_link_flags(&mut args, opts); |
| 556 | 1116 | let ld_result = Command::new("ld") |
| 557 | 1117 | .args(&args) |
| 558 | 1118 | .output() |
@@ -594,21 +1154,41 @@ pub fn compile_multi(opts: &Options) -> Result<(), String> { |
| 594 | 1154 | let stem = src.file_stem().unwrap_or_default().to_str().unwrap_or("out"); |
| 595 | 1155 | let obj_path = tmp_dir.join(format!("{}.o", stem)); |
| 596 | 1156 | |
| 597 | | - // Build a single-file Options for this source. |
| 598 | | - let sub_opts = Options { |
| 1157 | + // Build a single-file Options for this source by inheriting |
| 1158 | + // the user-facing flags and overriding only the per-file bits. |
| 1159 | + let mut sub_opts = Options { |
| 599 | 1160 | input: src.clone(), |
| 600 | 1161 | extra_inputs: vec![], |
| 601 | 1162 | output: Some(obj_path.clone()), |
| 602 | | - emit_asm: false, |
| 603 | 1163 | emit_obj: true, |
| 604 | | - emit_ir: false, |
| 605 | | - preprocess_only: false, |
| 606 | | - opt_level: opts.opt_level, |
| 607 | | - module_search_paths: { |
| 608 | | - let mut paths = opts.module_search_paths.clone(); |
| 609 | | - paths.push(tmp_dir.clone()); // find .amod from earlier compilations |
| 610 | | - paths |
| 611 | | - }, |
| 1164 | + ..Options::default() |
| 1165 | + }; |
| 1166 | + sub_opts.opt_level = opts.opt_level; |
| 1167 | + sub_opts.std = opts.std; |
| 1168 | + sub_opts.source_form_override = opts.source_form_override; |
| 1169 | + sub_opts.default_integer_8 = opts.default_integer_8; |
| 1170 | + sub_opts.default_real_8 = opts.default_real_8; |
| 1171 | + sub_opts.force_implicit_none = opts.force_implicit_none; |
| 1172 | + sub_opts.recursive_default = opts.recursive_default; |
| 1173 | + sub_opts.backslash_escapes = opts.backslash_escapes; |
| 1174 | + sub_opts.max_stack_var_size = opts.max_stack_var_size; |
| 1175 | + sub_opts.warn_all = opts.warn_all; |
| 1176 | + sub_opts.warn_extra = opts.warn_extra; |
| 1177 | + sub_opts.warn_pedantic = opts.warn_pedantic; |
| 1178 | + sub_opts.warn_deprecated = opts.warn_deprecated; |
| 1179 | + sub_opts.warn_as_error = opts.warn_as_error; |
| 1180 | + sub_opts.disabled_warnings = opts.disabled_warnings.clone(); |
| 1181 | + sub_opts.debug_info = opts.debug_info; |
| 1182 | + sub_opts.verbose = opts.verbose; |
| 1183 | + sub_opts.time_report = opts.time_report; |
| 1184 | + sub_opts.diagnostics_format = opts.diagnostics_format; |
| 1185 | + sub_opts.check_bounds = opts.check_bounds; |
| 1186 | + sub_opts.check_all = opts.check_all; |
| 1187 | + sub_opts.module_output_dir = opts.module_output_dir.clone(); |
| 1188 | + sub_opts.module_search_paths = { |
| 1189 | + let mut paths = opts.module_search_paths.clone(); |
| 1190 | + paths.push(tmp_dir.clone()); // find .amod from earlier compilations |
| 1191 | + paths |
| 612 | 1192 | }; |
| 613 | 1193 | compile(&sub_opts)?; |
| 614 | 1194 | object_files.push(obj_path); |
@@ -616,7 +1196,7 @@ pub fn compile_multi(opts: &Options) -> Result<(), String> { |
| 616 | 1196 | |
| 617 | 1197 | // Link all object files. |
| 618 | 1198 | let output = opts.output.clone().unwrap_or_else(|| PathBuf::from("a.out")); |
| 619 | | - link_multi(&object_files, &output)?; |
| 1199 | + link_multi(&object_files, &output, opts)?; |
| 620 | 1200 | |
| 621 | 1201 | // Cleanup. |
| 622 | 1202 | let _ = fs::remove_dir_all(&tmp_dir); |
@@ -780,6 +1360,7 @@ mod tests { |
| 780 | 1360 | opt_level: OptLevel::O0, |
| 781 | 1361 | extra_inputs: vec![], |
| 782 | 1362 | module_search_paths: vec![], |
| 1363 | + ..Options::default() |
| 783 | 1364 | }; |
| 784 | 1365 | |
| 785 | 1366 | compile(&opts).expect("O0 --emit-ir should support integer(16) staging"); |
@@ -805,6 +1386,7 @@ mod tests { |
| 805 | 1386 | opt_level: OptLevel::O0, |
| 806 | 1387 | extra_inputs: vec![], |
| 807 | 1388 | module_search_paths: vec![], |
| 1389 | + ..Options::default() |
| 808 | 1390 | }; |
| 809 | 1391 | |
| 810 | 1392 | let err = compile(&opts).expect_err("backend should reject integer(16) until i128 codegen lands"); |
@@ -832,6 +1414,7 @@ mod tests { |
| 832 | 1414 | opt_level: OptLevel::O0, |
| 833 | 1415 | extra_inputs: vec![], |
| 834 | 1416 | module_search_paths: vec![], |
| 1417 | + ..Options::default() |
| 835 | 1418 | }; |
| 836 | 1419 | |
| 837 | 1420 | compile(&opts).expect("simple integer(16) memory traffic should codegen at O0"); |
@@ -857,6 +1440,7 @@ mod tests { |
| 857 | 1440 | opt_level: OptLevel::O0, |
| 858 | 1441 | extra_inputs: vec![], |
| 859 | 1442 | module_search_paths: vec![], |
| 1443 | + ..Options::default() |
| 860 | 1444 | }; |
| 861 | 1445 | |
| 862 | 1446 | compile(&opts).expect("simple integer(16) add should codegen at O0"); |
@@ -882,6 +1466,7 @@ mod tests { |
| 882 | 1466 | opt_level: OptLevel::O0, |
| 883 | 1467 | extra_inputs: vec![], |
| 884 | 1468 | module_search_paths: vec![], |
| 1469 | + ..Options::default() |
| 885 | 1470 | }; |
| 886 | 1471 | |
| 887 | 1472 | compile(&opts).expect("internal integer(16) call should codegen at O0"); |
@@ -908,6 +1493,7 @@ mod tests { |
| 908 | 1493 | opt_level: OptLevel::O0, |
| 909 | 1494 | extra_inputs: vec![], |
| 910 | 1495 | module_search_paths: vec![], |
| 1496 | + ..Options::default() |
| 911 | 1497 | }; |
| 912 | 1498 | |
| 913 | 1499 | compile(&opts).expect("external integer(16) call should codegen at O0"); |
@@ -934,6 +1520,7 @@ mod tests { |
| 934 | 1520 | opt_level: OptLevel::O1, |
| 935 | 1521 | extra_inputs: vec![], |
| 936 | 1522 | module_search_paths: vec![], |
| 1523 | + ..Options::default() |
| 937 | 1524 | }; |
| 938 | 1525 | |
| 939 | 1526 | compile(&opts).expect("integer(16) multiply should codegen at O1 after const fold"); |