fortrangoingonforty/armfortas / 5d084e7

Browse files

Audit external i128 ABI

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
5d084e7ef0914465d2d81543bc3bff676521b711
Parents
853a6d4
Tree
616b007

4 changed files

StatusFile+-
M .docs/sprints/sprint29_10.md 4 2
M src/driver/mod.rs 30 0
A tests/fixtures/integer16_external_call.f90 23 0
A tests/i128_external_backend.rs 92 0
.docs/sprints/sprint29_10.mdmodified
@@ -211,11 +211,13 @@ Landed so far:
211211
 - local `-O0` stack-backed `i128` memory traffic, add/sub/neg, equality, ordered compares,
212212
   and `select`
213213
 - internal-only `-O0` pair-register `i128` params, returns, and same-module calls
214
+- external `-O0` pair-register `i128` call/return codegen through real Fortran interface
215
+  declarations, with asm/object determinism coverage
214216
 - source-level and selector-level regressions with object determinism coverage for the
215217
   supported local `-O0` surface
216218
 
217219
 Still missing inside the same cleanup item:
218
-- any external/runtime interop surface that passes `i128` across a function boundary
220
+- linked cross-object/runtime interop that executes those external `i128` calls end to end
219221
 - optimized (`-O1+`) pipeline support
220222
 
221223
 ### Planned ABI Jump
@@ -228,7 +230,7 @@ Working assumption from local clang ABI probes on Apple ARM64:
228230
 - `__int128` params consume register pairs `xN/xN+1`
229231
 
230232
 Staged plan:
231
-1. external/runtime interop coverage for the same pair-register ABI surface
233
+1. linked cross-object/runtime coverage for the same pair-register ABI surface
232234
 2. only after the ABI surface is real, widen optimizer support beyond raw `-O0`
233235
 
234236
 Timing:
src/driver/mod.rsmodified
@@ -483,6 +483,12 @@ mod tests {
483483
         path
484484
     }
485485
 
486
+    fn i128_external_call_fixture() -> PathBuf {
487
+        let path = PathBuf::from("tests/fixtures").join("integer16_external_call.f90");
488
+        assert!(path.exists(), "missing test fixture {}", path.display());
489
+        path
490
+    }
491
+
486492
     #[test]
487493
     fn emit_ir_allows_integer16_staging_at_o0() {
488494
         let output = std::env::temp_dir().join(format!(
@@ -600,4 +606,28 @@ mod tests {
600606
         assert!(asm.contains("stp x0, x1"), "expected pair-register i128 ABI spill in asm:\n{}", asm);
601607
         let _ = fs::remove_file(output);
602608
     }
609
+
610
+    #[test]
611
+    fn backend_allows_external_integer16_call_codegen_at_o0() {
612
+        let output = std::env::temp_dir().join(format!(
613
+            "armfortas_i128_external_call_{}_{}.s",
614
+            std::process::id(),
615
+            "o0"
616
+        ));
617
+        let opts = Options {
618
+            input: i128_external_call_fixture(),
619
+            output: Some(output.clone()),
620
+            emit_asm: true,
621
+            emit_obj: false,
622
+            emit_ir: false,
623
+            preprocess_only: false,
624
+            opt_level: OptLevel::O0,
625
+        };
626
+
627
+        compile(&opts).expect("external integer(16) call should codegen at O0");
628
+        let asm = fs::read_to_string(&output).expect("missing emitted assembly");
629
+        assert!(asm.contains("bl _add_ext"), "expected external helper call in asm:\n{}", asm);
630
+        assert!(asm.contains("stp x0, x1"), "expected pair-register i128 ABI spill in asm:\n{}", asm);
631
+        let _ = fs::remove_file(output);
632
+    }
603633
 }
tests/fixtures/integer16_external_call.f90added
@@ -0,0 +1,23 @@
1
+program integer16_external_call
2
+  implicit none
3
+
4
+  interface
5
+    integer(16) function add_ext(x) bind(c, name='add_ext')
6
+      integer(16), value :: x
7
+    end function add_ext
8
+  end interface
9
+
10
+  integer(16) :: x, y
11
+  integer :: score
12
+
13
+  x = 41_16
14
+  y = add_ext(x)
15
+
16
+  if (y == 42_16) then
17
+    score = 1
18
+  else
19
+    score = 0
20
+  end if
21
+
22
+  print *, score
23
+end program integer16_external_call
tests/i128_external_backend.rsadded
@@ -0,0 +1,92 @@
1
+use std::collections::BTreeSet;
2
+use std::path::PathBuf;
3
+
4
+use armfortas::driver::OptLevel;
5
+use armfortas::testing::{capture_from_path, CaptureRequest, CapturedStage, Stage};
6
+
7
+fn fixture(name: &str) -> PathBuf {
8
+    let path = PathBuf::from("tests/fixtures").join(name);
9
+    assert!(path.exists(), "missing test fixture {}", path.display());
10
+    path
11
+}
12
+
13
+fn capture_text(request: CaptureRequest, stage: Stage) -> String {
14
+    let result = capture_from_path(&request).expect("capture should succeed");
15
+    match result.get(stage) {
16
+        Some(CapturedStage::Text(text)) => text.clone(),
17
+        Some(CapturedStage::Run(_)) => panic!("expected text stage for {}", stage.as_str()),
18
+        None => panic!("missing requested stage {}", stage.as_str()),
19
+    }
20
+}
21
+
22
+#[test]
23
+fn external_i128_call_uses_pair_arg_and_return_regs_at_o0() {
24
+    let asm = capture_text(
25
+        CaptureRequest {
26
+            input: fixture("integer16_external_call.f90"),
27
+            requested: BTreeSet::from([Stage::Asm]),
28
+            opt_level: OptLevel::O0,
29
+        },
30
+        Stage::Asm,
31
+    );
32
+
33
+    assert!(
34
+        asm.contains("bl _add_ext"),
35
+        "external integer(16) call should branch to the declared symbol:\n{}",
36
+        asm
37
+    );
38
+    assert!(
39
+        asm.matches("ldp x0, x1").count() >= 1,
40
+        "external integer(16) ABI should load the outgoing pair-register arg with LDP x0, x1:\n{}",
41
+        asm
42
+    );
43
+    assert!(
44
+        asm.matches("stp x0, x1").count() >= 1,
45
+        "external integer(16) ABI should spill the returned pair-register value with STP x0, x1:\n{}",
46
+        asm
47
+    );
48
+}
49
+
50
+#[test]
51
+fn external_i128_call_object_snapshot_tracks_external_symbol_at_o0() {
52
+    let obj = capture_text(
53
+        CaptureRequest {
54
+            input: fixture("integer16_external_call.f90"),
55
+            requested: BTreeSet::from([Stage::Obj]),
56
+            opt_level: OptLevel::O0,
57
+        },
58
+        Stage::Obj,
59
+    );
60
+
61
+    assert!(
62
+        obj.contains("external _add_ext"),
63
+        "object snapshot should preserve the unresolved external integer(16) symbol:\n{}",
64
+        obj
65
+    );
66
+}
67
+
68
+#[test]
69
+fn external_i128_call_object_snapshot_is_deterministic_at_o0() {
70
+    let source = fixture("integer16_external_call.f90");
71
+    let first = capture_text(
72
+        CaptureRequest {
73
+            input: source.clone(),
74
+            requested: BTreeSet::from([Stage::Obj]),
75
+            opt_level: OptLevel::O0,
76
+        },
77
+        Stage::Obj,
78
+    );
79
+    let second = capture_text(
80
+        CaptureRequest {
81
+            input: source,
82
+            requested: BTreeSet::from([Stage::Obj]),
83
+            opt_level: OptLevel::O0,
84
+        },
85
+        Stage::Obj,
86
+    );
87
+
88
+    assert_eq!(
89
+        first, second,
90
+        "external integer(16) call object snapshots should be deterministic at O0"
91
+    );
92
+}