Audit external i128 ABI
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
5d084e7ef0914465d2d81543bc3bff676521b711- Parents
-
853a6d4 - Tree
616b007
5d084e7
5d084e7ef0914465d2d81543bc3bff676521b711853a6d4
616b007| Status | File | + | - |
|---|---|---|---|
| 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: | ||
| 211 | 211 | - local `-O0` stack-backed `i128` memory traffic, add/sub/neg, equality, ordered compares, |
| 212 | 212 | and `select` |
| 213 | 213 | - 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 | |
| 214 | 216 | - source-level and selector-level regressions with object determinism coverage for the |
| 215 | 217 | supported local `-O0` surface |
| 216 | 218 | |
| 217 | 219 | 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 | |
| 219 | 221 | - optimized (`-O1+`) pipeline support |
| 220 | 222 | |
| 221 | 223 | ### Planned ABI Jump |
@@ -228,7 +230,7 @@ Working assumption from local clang ABI probes on Apple ARM64: | ||
| 228 | 230 | - `__int128` params consume register pairs `xN/xN+1` |
| 229 | 231 | |
| 230 | 232 | 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 | |
| 232 | 234 | 2. only after the ABI surface is real, widen optimizer support beyond raw `-O0` |
| 233 | 235 | |
| 234 | 236 | Timing: |
src/driver/mod.rsmodified@@ -483,6 +483,12 @@ mod tests { | ||
| 483 | 483 | path |
| 484 | 484 | } |
| 485 | 485 | |
| 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 | + | |
| 486 | 492 | #[test] |
| 487 | 493 | fn emit_ir_allows_integer16_staging_at_o0() { |
| 488 | 494 | let output = std::env::temp_dir().join(format!( |
@@ -600,4 +606,28 @@ mod tests { | ||
| 600 | 606 | assert!(asm.contains("stp x0, x1"), "expected pair-register i128 ABI spill in asm:\n{}", asm); |
| 601 | 607 | let _ = fs::remove_file(output); |
| 602 | 608 | } |
| 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 | + } | |
| 603 | 633 | } |
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 | +} | |