fortrangoingonforty/armfortas / 903718e

Browse files

Link prebuilt objects and archives

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
903718ed37c699d90eb7f71b4342ba20f5ecfb5b
Parents
bf424e8
Tree
cdf72cd

3 changed files

StatusFile+-
M src/driver/mod.rs 95 37
M src/lib.rs 1 7
M tests/cli_driver.rs 116 0
src/driver/mod.rsmodified
@@ -90,6 +90,12 @@ pub enum ParsedCli {
9090
     Info(InfoAction),
9191
 }
9292
 
93
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94
+enum CliInputKind {
95
+    FortranSource,
96
+    LinkArtifact,
97
+}
98
+
9399
 /// Compilation options.
94100
 pub struct Options {
95101
     // ---- I/O ----
@@ -831,6 +837,82 @@ fn main_wrapper_target(allocated: &[MachineFunction]) -> Option<&str> {
831837
         .map(|func| func.name.as_str())
832838
 }
833839
 
840
+fn all_input_paths(opts: &Options) -> Vec<PathBuf> {
841
+    let mut inputs = vec![opts.input.clone()];
842
+    inputs.extend(opts.extra_inputs.iter().cloned());
843
+    inputs
844
+}
845
+
846
+fn classify_cli_input(path: &Path) -> CliInputKind {
847
+    let ext = path
848
+        .extension()
849
+        .and_then(|ext| ext.to_str())
850
+        .map(|ext| ext.to_ascii_lowercase());
851
+    match ext.as_deref() {
852
+        Some("o" | "obj" | "a" | "dylib" | "so") => CliInputKind::LinkArtifact,
853
+        _ => CliInputKind::FortranSource,
854
+    }
855
+}
856
+
857
+fn validate_link_only_inputs(opts: &Options) -> Result<(), String> {
858
+    if opts.preprocess_only {
859
+        return Err("-E cannot be used when all inputs are prebuilt objects or archives".into());
860
+    }
861
+    if opts.emit_tokens {
862
+        return Err(
863
+            "--emit-tokens cannot be used when all inputs are prebuilt objects or archives".into(),
864
+        );
865
+    }
866
+    if opts.emit_ast {
867
+        return Err(
868
+            "--emit-ast cannot be used when all inputs are prebuilt objects or archives".into(),
869
+        );
870
+    }
871
+    if opts.emit_ir {
872
+        return Err(
873
+            "--emit-ir cannot be used when all inputs are prebuilt objects or archives".into(),
874
+        );
875
+    }
876
+    if opts.emit_asm {
877
+        return Err("-S cannot be used when all inputs are prebuilt objects or archives".into());
878
+    }
879
+    if opts.emit_obj {
880
+        return Err("-c cannot be used when all inputs are prebuilt objects or archives".into());
881
+    }
882
+    Ok(())
883
+}
884
+
885
+/// Execute a fully parsed CLI job, dispatching between source
886
+/// compilation and pure link steps based on the positional inputs.
887
+pub fn execute(opts: &Options) -> Result<(), String> {
888
+    let inputs = all_input_paths(opts);
889
+    let has_source = inputs
890
+        .iter()
891
+        .any(|path| classify_cli_input(path) == CliInputKind::FortranSource);
892
+    let has_link_artifact = inputs
893
+        .iter()
894
+        .any(|path| classify_cli_input(path) == CliInputKind::LinkArtifact);
895
+
896
+    match (has_source, has_link_artifact) {
897
+        (true, false) => {
898
+            if opts.extra_inputs.is_empty() {
899
+                compile(opts)
900
+            } else {
901
+                compile_multi(opts)
902
+            }
903
+        }
904
+        (false, true) => {
905
+            validate_link_only_inputs(opts)?;
906
+            let output = opts.output.clone().unwrap_or_else(|| PathBuf::from("a.out"));
907
+            link_inputs(&inputs, &output, opts)
908
+        }
909
+        (true, true) => Err(
910
+            "mixing Fortran sources with prebuilt object/archive inputs is not yet supported; compile the sources first and then link the resulting objects".into(),
911
+        ),
912
+        (false, false) => unreachable!("parse_cli guarantees at least one input"),
913
+    }
914
+}
915
+
834916
 /// Compile a Fortran source file through the full pipeline.
835917
 pub fn compile(opts: &Options) -> Result<(), String> {
836918
     let mut phases = PhaseTimer::new(opts.time_report);
@@ -1299,6 +1381,12 @@ _main:
12991381
 /// `opts` contributes the user-supplied `-L`, `-l`, `-rpath`,
13001382
 /// `-shared`, and `-static` flags that need to make it through to ld.
13011383
 fn link(obj: &Path, output: &Path, opts: &Options) -> Result<(), String> {
1384
+    link_inputs(&[obj.to_path_buf()], output, opts)
1385
+}
1386
+
1387
+/// Link prebuilt objects and archives with the runtime to produce a
1388
+/// binary or shared library, preserving the user-supplied input order.
1389
+fn link_inputs(inputs: &[PathBuf], output: &Path, opts: &Options) -> Result<(), String> {
13021390
     let rt_path = find_runtime_lib()?;
13031391
     let sdk = Command::new("xcrun")
13041392
         .args(["--show-sdk-path"])
@@ -1306,8 +1394,11 @@ fn link(obj: &Path, output: &Path, opts: &Options) -> Result<(), String> {
13061394
         .map_err(|e| format!("cannot run xcrun: {}", e))?;
13071395
     let sysroot = String::from_utf8_lossy(&sdk.stdout).trim().to_string();
13081396
 
1309
-    let mut args: Vec<String> = vec![
1310
-        obj.to_string_lossy().into_owned(),
1397
+    let mut args: Vec<String> = vec!["-o".into(), output.to_string_lossy().into_owned()];
1398
+    for input in inputs {
1399
+        args.push(input.to_string_lossy().into_owned());
1400
+    }
1401
+    args.extend([
13111402
         rt_path,
13121403
         "-lSystem".into(),
13131404
         "-no_uuid".into(),
@@ -1315,9 +1406,7 @@ fn link(obj: &Path, output: &Path, opts: &Options) -> Result<(), String> {
13151406
         sysroot,
13161407
         "-e".into(),
13171408
         "_main".into(),
1318
-        "-o".into(),
1319
-        output.to_string_lossy().into_owned(),
1320
-    ];
1409
+    ]);
13211410
     push_link_flags(&mut args, opts);
13221411
 
13231412
     let ld_result = Command::new("ld")
@@ -1361,38 +1450,7 @@ fn push_link_flags(args: &mut Vec<String>, opts: &Options) {
13611450
 
13621451
 /// Link multiple object files with the runtime to produce a binary.
13631452
 fn link_multi(objs: &[PathBuf], output: &Path, opts: &Options) -> Result<(), String> {
1364
-    let rt_path = find_runtime_lib()?;
1365
-    let sdk = Command::new("xcrun")
1366
-        .args(["--show-sdk-path"])
1367
-        .output()
1368
-        .map_err(|e| format!("cannot run xcrun: {}", e))?;
1369
-    let sysroot = String::from_utf8_lossy(&sdk.stdout).trim().to_string();
1370
-
1371
-    let mut args: Vec<String> = vec![
1372
-        "-o".into(), output.to_str().unwrap().into(),
1373
-    ];
1374
-    for obj in objs {
1375
-        args.push(obj.to_str().unwrap().into());
1376
-    }
1377
-    args.extend([
1378
-        rt_path,
1379
-        "-lSystem".into(),
1380
-        "-no_uuid".into(),
1381
-        "-syslibroot".into(),
1382
-        sysroot,
1383
-        "-e".into(),
1384
-        "_main".into(),
1385
-    ]);
1386
-    push_link_flags(&mut args, opts);
1387
-    let ld_result = Command::new("ld")
1388
-        .args(&args)
1389
-        .output()
1390
-        .map_err(|e| format!("cannot run linker: {}", e))?;
1391
-    if !ld_result.status.success() {
1392
-        let stderr = String::from_utf8_lossy(&ld_result.stderr);
1393
-        return Err(format!("linker failed:\n{}", stderr));
1394
-    }
1395
-    Ok(())
1453
+    link_inputs(objs, output, opts)
13961454
 }
13971455
 
13981456
 /// Compile multiple Fortran source files with automatic dependency
src/lib.rsmodified
@@ -77,13 +77,7 @@ pub fn cli_entry() -> ! {
7777
             // compile thunk so we can include it in any ICE report.
7878
             let input_for_ice = opts.input.display().to_string();
7979
             install_ice_hook();
80
-            let result = catch_unwind(AssertUnwindSafe(|| {
81
-                if opts.extra_inputs.is_empty() {
82
-                    driver::compile(&opts)
83
-                } else {
84
-                    driver::compile_multi(&opts)
85
-                }
86
-            }));
80
+            let result = catch_unwind(AssertUnwindSafe(|| driver::execute(&opts)));
8781
             match result {
8882
                 Ok(Ok(())) => process::exit(0),
8983
                 Ok(Err(e)) => {
tests/cli_driver.rsmodified
@@ -372,6 +372,122 @@ fn multi_input_dash_c_with_o_is_rejected() {
372372
     let _ = std::fs::remove_dir_all(&dir);
373373
 }
374374
 
375
+#[test]
376
+fn prebuilt_object_input_links_cleanly() {
377
+    let src = write_program("program p\n  print *, 9\nend program\n", "f90");
378
+    let obj = unique_path("link_only_obj", "o");
379
+    let compile = Command::new(compiler("armfortas"))
380
+        .args(["-c", src.to_str().unwrap(), "-o", obj.to_str().unwrap()])
381
+        .output()
382
+        .expect("object compile failed to spawn");
383
+    assert!(
384
+        compile.status.success(),
385
+        "object compile failed: {}",
386
+        String::from_utf8_lossy(&compile.stderr)
387
+    );
388
+
389
+    let exe = unique_path("link_only_obj", "bin");
390
+    let link = Command::new(compiler("armfortas"))
391
+        .args([obj.to_str().unwrap(), "-o", exe.to_str().unwrap()])
392
+        .output()
393
+        .expect("link-only spawn failed");
394
+    assert!(
395
+        link.status.success(),
396
+        "prebuilt object link failed: {}",
397
+        String::from_utf8_lossy(&link.stderr)
398
+    );
399
+    assert!(exe.exists(), "prebuilt object link should write the binary");
400
+
401
+    let _ = std::fs::remove_file(&exe);
402
+    let _ = std::fs::remove_file(&obj);
403
+    let _ = std::fs::remove_file(&src);
404
+}
405
+
406
+#[test]
407
+fn prebuilt_archive_input_links_after_objects() {
408
+    let dir = unique_dir("link_only_archive");
409
+    let helper_src = write_program_in(
410
+        &dir,
411
+        "helper.f90",
412
+        "subroutine helper()\n  print *, 7\nend subroutine helper\n",
413
+    );
414
+    let main_src = write_program_in(
415
+        &dir,
416
+        "main.f90",
417
+        "program p\n  call helper()\nend program p\n",
418
+    );
419
+
420
+    let helper_obj = dir.join("helper.o");
421
+    let compile_helper = Command::new(compiler("armfortas"))
422
+        .current_dir(&dir)
423
+        .args([
424
+            "-c",
425
+            helper_src.to_str().unwrap(),
426
+            "-o",
427
+            helper_obj.to_str().unwrap(),
428
+        ])
429
+        .output()
430
+        .expect("helper compile spawn failed");
431
+    assert!(
432
+        compile_helper.status.success(),
433
+        "helper compile failed: {}",
434
+        String::from_utf8_lossy(&compile_helper.stderr)
435
+    );
436
+
437
+    let archive = dir.join("libhelper.a");
438
+    let ar = Command::new("ar")
439
+        .current_dir(&dir)
440
+        .args([
441
+            "rcs",
442
+            archive.to_str().unwrap(),
443
+            helper_obj.to_str().unwrap(),
444
+        ])
445
+        .output()
446
+        .expect("archive spawn failed");
447
+    assert!(
448
+        ar.status.success(),
449
+        "archive creation failed: {}",
450
+        String::from_utf8_lossy(&ar.stderr)
451
+    );
452
+
453
+    let main_obj = dir.join("main.o");
454
+    let compile_main = Command::new(compiler("armfortas"))
455
+        .current_dir(&dir)
456
+        .args([
457
+            "-c",
458
+            main_src.to_str().unwrap(),
459
+            "-o",
460
+            main_obj.to_str().unwrap(),
461
+        ])
462
+        .output()
463
+        .expect("main compile spawn failed");
464
+    assert!(
465
+        compile_main.status.success(),
466
+        "main compile failed: {}",
467
+        String::from_utf8_lossy(&compile_main.stderr)
468
+    );
469
+
470
+    let exe = dir.join("linked_archive");
471
+    let link = Command::new(compiler("armfortas"))
472
+        .current_dir(&dir)
473
+        .args([
474
+            main_obj.to_str().unwrap(),
475
+            archive.to_str().unwrap(),
476
+            "-o",
477
+            exe.to_str().unwrap(),
478
+        ])
479
+        .output()
480
+        .expect("archive link spawn failed");
481
+    assert!(
482
+        link.status.success(),
483
+        "prebuilt archive link failed: {}",
484
+        String::from_utf8_lossy(&link.stderr)
485
+    );
486
+    assert!(exe.exists(), "prebuilt archive link should write the binary");
487
+
488
+    let _ = std::fs::remove_dir_all(&dir);
489
+}
490
+
375491
 #[test]
376492
 fn dash_capital_s_produces_assembly_text() {
377493
     let src = write_program("program p\n  print *, 1\nend program\n", "f90");