Skip to content

[flang] Use correct int extension flags for C-ABI calls on aarch64 #137105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 25, 2025

Conversation

ashermancinelli
Copy link
Contributor

@ashermancinelli ashermancinelli commented Apr 24, 2025

The AArch64 procedure call standard does not mandate that the callee extends the return value. Clang does not add signext to functions returning i8 or i16 on linux aarch64, but flang does.

This means that runtime routines returning i8's will have signext on the callsite/declaration, but not on the implementation, and the call site will assume the return value has already been sign extended when it has not. This showed up in a test case calling MINVAL on an array of INTEGER*1.

Adjust our integer extension flags to match clang and aarch64pcs on linux. The behavior on Darwin should be preserved. This is listed on the apple developer guide as a divergence from aarch64pcs.

@ashermancinelli ashermancinelli self-assigned this Apr 24, 2025
@llvmbot llvmbot added flang Flang issues not falling into any other category flang:fir-hlfir flang:codegen labels Apr 24, 2025
@ashermancinelli ashermancinelli changed the title [flang] Omit signext for i8 and i16 on aarch64 [flang] Use correct int extension flags on aarch64 Apr 24, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 24, 2025

@llvm/pr-subscribers-flang-fir-hlfir

Author: Asher Mancinelli (ashermancinelli)

Changes

The AArch64 procedure call standard does not mandate that the callee extends the return value. Clang does not add signext to functions returning i8 or i16 on linux aarch64, but flang does.

This means that runtime routines returning i8's (e.g. MINVAL on an array of INTEGER(KIND=1)) will have signext on the callsite/declaration, but not on the implementation, and the call site will assume the return value has already been sign extended when it has not.

Adjust our integer extension flags to match clang and aarch64pcs on linux. The behavior on Darwin should be preserved. This is listed on the apple developer guide as a divergence from aarch64pcs.


Full diff: https://github.com/llvm/llvm-project/pull/137105.diff

3 Files Affected:

  • (modified) flang/lib/Optimizer/CodeGen/Target.cpp (+28-1)
  • (modified) flang/test/Fir/convert-to-llvm-target.fir (+1-1)
  • (modified) flang/test/Fir/target-rewrite-integer.fir (+19-2)
diff --git a/flang/lib/Optimizer/CodeGen/Target.cpp b/flang/lib/Optimizer/CodeGen/Target.cpp
index 374308fa58947..b2223d95729a0 100644
--- a/flang/lib/Optimizer/CodeGen/Target.cpp
+++ b/flang/lib/Optimizer/CodeGen/Target.cpp
@@ -784,7 +784,7 @@ struct TargetX86_64Win : public GenericTarget<TargetX86_64Win> {
 } // namespace
 
 //===----------------------------------------------------------------------===//
-// AArch64 linux target specifics.
+// AArch64 target specifics.
 //===----------------------------------------------------------------------===//
 
 namespace {
@@ -810,6 +810,33 @@ struct TargetAArch64 : public GenericTarget<TargetAArch64> {
     return marshal;
   }
 
+  CodeGenSpecifics::Marshalling
+  integerArgumentType(mlir::Location loc,
+                      mlir::IntegerType argTy) const override {
+    if (argTy.getWidth() < getCIntTypeWidth() && argTy.isSignless()) {
+      AT::IntegerExtension intExt;
+      if (argTy.getWidth() == 1) {
+        // Zero extend for 'i1'.
+        intExt = AT::IntegerExtension::Zero;
+      } else {
+        if (triple.isOSDarwin())
+          // On Darwin, sign extend. The apple developer guide specifies this as
+          // a divergence from the AArch64PCS:
+          // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Pass-arguments-to-functions-correctly
+          intExt = AT::IntegerExtension::Sign;
+        else
+          // On linux, pass directly and do not extend.
+          intExt = AT::IntegerExtension::None;
+      }
+      CodeGenSpecifics::Marshalling marshal;
+      marshal.emplace_back(argTy, AT{/*alignment=*/0, /*byval=*/false,
+                                     /*sret=*/false, /*append=*/false,
+                                     /*intExt=*/intExt});
+      return marshal;
+    }
+    return GenericTarget::integerArgumentType(loc, argTy);
+  }
+
   CodeGenSpecifics::Marshalling
   complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
     CodeGenSpecifics::Marshalling marshal;
diff --git a/flang/test/Fir/convert-to-llvm-target.fir b/flang/test/Fir/convert-to-llvm-target.fir
index bb873080072fa..6adc5cd4b737f 100644
--- a/flang/test/Fir/convert-to-llvm-target.fir
+++ b/flang/test/Fir/convert-to-llvm-target.fir
@@ -1,7 +1,7 @@
 // RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" %s | FileCheck %s --check-prefix INT64
 // RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=aarch64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes INT64
 // RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=i386-unknown-linux-gnu" %s | FileCheck %s --check-prefixes INT32
-// RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=powerpc64le-unknown-linux-gn" %s | FileCheck %s --check-prefixes INT64
+// RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=powerpc64le-unknown-linux-gnu" %s | FileCheck %s --check-prefixes INT64
 
 //=============================================================================
 // SUMMARY: Tests for FIR --> LLVM MLIR conversion that *depend* on the target
diff --git a/flang/test/Fir/target-rewrite-integer.fir b/flang/test/Fir/target-rewrite-integer.fir
index 3be76e21a420f..4cb2026cf9ee0 100644
--- a/flang/test/Fir/target-rewrite-integer.fir
+++ b/flang/test/Fir/target-rewrite-integer.fir
@@ -1,6 +1,8 @@
 // RUN: fir-opt --split-input-file --target-rewrite="target=i386-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=I32,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=x86_64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=X64,ALL
+// RUN: fir-opt --split-input-file --target-rewrite="target=x86_64-apple-darwin" %s | FileCheck %s --check-prefixes=X64,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=aarch64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=AARCH64,ALL
+// RUN: fir-opt --split-input-file --target-rewrite="target=aarch64-apple-darwin" %s | FileCheck %s --check-prefixes=AARCH64DARWIN,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=powerpc64le-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=PPC,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=sparc64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=SPARCV9,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=sparcv9-sun-solaris2.11" %s | FileCheck %s --check-prefixes=SPARCV9,ALL
@@ -17,6 +19,7 @@
 // I32: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // X64: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // AARCH64: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
+// AARCH64DARWIN: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // PPC: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // SPARCV9: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // LOONGARCH64: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
@@ -49,6 +52,7 @@ func.func private @_FortranAioEndIoStatement(!fir.ref<i8>) -> i32 attributes {fi
 // I32: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // X64: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // AARCH64: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
+// AARCH64DARWIN: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // PPC: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // SPARCV9: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // LOONGARCH64: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
@@ -69,6 +73,7 @@ func.func private @_SomeFunc_si1(si1) -> si1 attributes {fir.runtime}
 // I32: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // X64: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // AARCH64: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
+// AARCH64DARWIN: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // PPC: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // SPARCV9: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // LOONGARCH64: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
@@ -99,8 +104,20 @@ func.func private @_SomeFunc_ui1(ui1) -> ui1 attributes {fir.runtime}
 // end subroutine test
 
 // ALL-LABEL: @_QPtest_bindc
-// ALL: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
-// ALL: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// I32: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// I32: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// X64: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// X64: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// AARCH64: func.func private @cfun8(i8) -> i8 attributes {fir.bindc_name = "cfun8"}
+// AARCH64: func.func private @cfun16(i16) -> i16 attributes {fir.bindc_name = "cfun16"}
+// AARCH64DARWIN: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// AARCH64DARWIN: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// PPC: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// PPC: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// SPARCV9: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// SPARCV9: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// LOONGARCH64: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// LOONGARCH64: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
 func.func @_QPtest_bindc(%arg0: !fir.ref<i8> {fir.bindc_name = "x"}, %arg1: !fir.ref<i16> {fir.bindc_name = "y"}) {
   %0 = fir.load %arg0 : !fir.ref<i8>
   %1 = fir.call @cfun8(%0) fastmath<contract> : (i8) -> i8

@llvmbot
Copy link
Member

llvmbot commented Apr 24, 2025

@llvm/pr-subscribers-flang-codegen

Author: Asher Mancinelli (ashermancinelli)

Changes

The AArch64 procedure call standard does not mandate that the callee extends the return value. Clang does not add signext to functions returning i8 or i16 on linux aarch64, but flang does.

This means that runtime routines returning i8's (e.g. MINVAL on an array of INTEGER(KIND=1)) will have signext on the callsite/declaration, but not on the implementation, and the call site will assume the return value has already been sign extended when it has not.

Adjust our integer extension flags to match clang and aarch64pcs on linux. The behavior on Darwin should be preserved. This is listed on the apple developer guide as a divergence from aarch64pcs.


Full diff: https://github.com/llvm/llvm-project/pull/137105.diff

3 Files Affected:

  • (modified) flang/lib/Optimizer/CodeGen/Target.cpp (+28-1)
  • (modified) flang/test/Fir/convert-to-llvm-target.fir (+1-1)
  • (modified) flang/test/Fir/target-rewrite-integer.fir (+19-2)
diff --git a/flang/lib/Optimizer/CodeGen/Target.cpp b/flang/lib/Optimizer/CodeGen/Target.cpp
index 374308fa58947..b2223d95729a0 100644
--- a/flang/lib/Optimizer/CodeGen/Target.cpp
+++ b/flang/lib/Optimizer/CodeGen/Target.cpp
@@ -784,7 +784,7 @@ struct TargetX86_64Win : public GenericTarget<TargetX86_64Win> {
 } // namespace
 
 //===----------------------------------------------------------------------===//
-// AArch64 linux target specifics.
+// AArch64 target specifics.
 //===----------------------------------------------------------------------===//
 
 namespace {
@@ -810,6 +810,33 @@ struct TargetAArch64 : public GenericTarget<TargetAArch64> {
     return marshal;
   }
 
+  CodeGenSpecifics::Marshalling
+  integerArgumentType(mlir::Location loc,
+                      mlir::IntegerType argTy) const override {
+    if (argTy.getWidth() < getCIntTypeWidth() && argTy.isSignless()) {
+      AT::IntegerExtension intExt;
+      if (argTy.getWidth() == 1) {
+        // Zero extend for 'i1'.
+        intExt = AT::IntegerExtension::Zero;
+      } else {
+        if (triple.isOSDarwin())
+          // On Darwin, sign extend. The apple developer guide specifies this as
+          // a divergence from the AArch64PCS:
+          // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Pass-arguments-to-functions-correctly
+          intExt = AT::IntegerExtension::Sign;
+        else
+          // On linux, pass directly and do not extend.
+          intExt = AT::IntegerExtension::None;
+      }
+      CodeGenSpecifics::Marshalling marshal;
+      marshal.emplace_back(argTy, AT{/*alignment=*/0, /*byval=*/false,
+                                     /*sret=*/false, /*append=*/false,
+                                     /*intExt=*/intExt});
+      return marshal;
+    }
+    return GenericTarget::integerArgumentType(loc, argTy);
+  }
+
   CodeGenSpecifics::Marshalling
   complexReturnType(mlir::Location loc, mlir::Type eleTy) const override {
     CodeGenSpecifics::Marshalling marshal;
diff --git a/flang/test/Fir/convert-to-llvm-target.fir b/flang/test/Fir/convert-to-llvm-target.fir
index bb873080072fa..6adc5cd4b737f 100644
--- a/flang/test/Fir/convert-to-llvm-target.fir
+++ b/flang/test/Fir/convert-to-llvm-target.fir
@@ -1,7 +1,7 @@
 // RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" %s | FileCheck %s --check-prefix INT64
 // RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=aarch64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes INT64
 // RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=i386-unknown-linux-gnu" %s | FileCheck %s --check-prefixes INT32
-// RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=powerpc64le-unknown-linux-gn" %s | FileCheck %s --check-prefixes INT64
+// RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=powerpc64le-unknown-linux-gnu" %s | FileCheck %s --check-prefixes INT64
 
 //=============================================================================
 // SUMMARY: Tests for FIR --> LLVM MLIR conversion that *depend* on the target
diff --git a/flang/test/Fir/target-rewrite-integer.fir b/flang/test/Fir/target-rewrite-integer.fir
index 3be76e21a420f..4cb2026cf9ee0 100644
--- a/flang/test/Fir/target-rewrite-integer.fir
+++ b/flang/test/Fir/target-rewrite-integer.fir
@@ -1,6 +1,8 @@
 // RUN: fir-opt --split-input-file --target-rewrite="target=i386-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=I32,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=x86_64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=X64,ALL
+// RUN: fir-opt --split-input-file --target-rewrite="target=x86_64-apple-darwin" %s | FileCheck %s --check-prefixes=X64,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=aarch64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=AARCH64,ALL
+// RUN: fir-opt --split-input-file --target-rewrite="target=aarch64-apple-darwin" %s | FileCheck %s --check-prefixes=AARCH64DARWIN,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=powerpc64le-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=PPC,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=sparc64-unknown-linux-gnu" %s | FileCheck %s --check-prefixes=SPARCV9,ALL
 // RUN: fir-opt --split-input-file --target-rewrite="target=sparcv9-sun-solaris2.11" %s | FileCheck %s --check-prefixes=SPARCV9,ALL
@@ -17,6 +19,7 @@
 // I32: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // X64: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // AARCH64: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
+// AARCH64DARWIN: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // PPC: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // SPARCV9: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
 // LOONGARCH64: func.func{{.*}}@_FortranAioOutputLogical({{.*}}i1 {llvm.zeroext}) -> (i1 {llvm.zeroext})
@@ -49,6 +52,7 @@ func.func private @_FortranAioEndIoStatement(!fir.ref<i8>) -> i32 attributes {fi
 // I32: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // X64: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // AARCH64: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
+// AARCH64DARWIN: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // PPC: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // SPARCV9: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
 // LOONGARCH64: func.func{{.*}}@_SomeFunc_si1(si1 {llvm.signext}) -> (si1 {llvm.signext})
@@ -69,6 +73,7 @@ func.func private @_SomeFunc_si1(si1) -> si1 attributes {fir.runtime}
 // I32: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // X64: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // AARCH64: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
+// AARCH64DARWIN: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // PPC: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // SPARCV9: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
 // LOONGARCH64: func.func{{.*}}@_SomeFunc_ui1(ui1 {llvm.zeroext}) -> (ui1 {llvm.zeroext})
@@ -99,8 +104,20 @@ func.func private @_SomeFunc_ui1(ui1) -> ui1 attributes {fir.runtime}
 // end subroutine test
 
 // ALL-LABEL: @_QPtest_bindc
-// ALL: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
-// ALL: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// I32: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// I32: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// X64: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// X64: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// AARCH64: func.func private @cfun8(i8) -> i8 attributes {fir.bindc_name = "cfun8"}
+// AARCH64: func.func private @cfun16(i16) -> i16 attributes {fir.bindc_name = "cfun16"}
+// AARCH64DARWIN: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// AARCH64DARWIN: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// PPC: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// PPC: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// SPARCV9: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// SPARCV9: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
+// LOONGARCH64: func.func private @cfun8(i8 {llvm.signext}) -> (i8 {llvm.signext}) attributes {fir.bindc_name = "cfun8"}
+// LOONGARCH64: func.func private @cfun16(i16 {llvm.signext}) -> (i16 {llvm.signext}) attributes {fir.bindc_name = "cfun16"}
 func.func @_QPtest_bindc(%arg0: !fir.ref<i8> {fir.bindc_name = "x"}, %arg1: !fir.ref<i16> {fir.bindc_name = "y"}) {
   %0 = fir.load %arg0 : !fir.ref<i8>
   %1 = fir.call @cfun8(%0) fastmath<contract> : (i8) -> i8

The AArch64 procedure call standard does not mandate that the callee extends the return
value. Clang does not add signext to functions returning i8 or i16 on aarch64, except on
Darwin, but flang does.

This means that runtime routines returning i8's (e.g. MINVAL on an array of
INTEGER(KIND=1)) will have signext on the callsite/declaration in user code,
but not on the implementation, and the call site will assume the return value has already
been sign extended when it has not.

Adjust our integer extension flags to match clang and aarch64pcs on linux. The behavior
on Darwin should be preserved. This is listed on the apple developer guide as a divergence
from aarch64pcs.
@ashermancinelli ashermancinelli changed the title [flang] Use correct int extension flags on aarch64 [flang] Use correct int extension flags for C-ABI calls on aarch64 Apr 24, 2025
@vzakhari
Copy link
Contributor

Looks good to me. Thank you, Asher!

@kiranchandramohan can you please also review?

Copy link
Contributor

@kiranchandramohan kiranchandramohan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ashermancinelli for the patch. A question and a Nit.

if (triple.isOSDarwin())
// On Darwin, sign extend. The apple developer guide specifies this as
// a divergence from the AArch64PCS:
// https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms#Pass-arguments-to-functions-correctly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it talk only about arguments and not return values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The apple dev guide does not mention sign/zero extension of returned integers, as far as I can tell. The aa64pcs explains that register types ought to be handled the same way, whether passed or returned:

The manner in which a result is returned from a function is determined by the type of that result:

If the type, T, of the result of a function is such that

void func(T arg)
would require that arg be passed as a value in a register (or set of registers) according to the rules in Parameter passing, then the result is returned in the same registers as would be used for such an argument.

Otherwise, the caller shall reserve a block of memory of sufficient size and alignment to hold the result. The address of the memory block shall be passed as an additional argument to the function in x8. The callee may modify the result memory block at any point during the execution of the subroutine (there is no requirement for the callee to preserve the value stored in x8).

Clang does the same sign/zero extension for the return type, which is what I used as my guide. I wouldn't mind if the apple dev guide were updated to explicitly say one way or the other, though.

Copy link
Contributor

@kiranchandramohan kiranchandramohan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@ashermancinelli ashermancinelli merged commit 41f1663 into llvm:main Apr 25, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flang:codegen flang:fir-hlfir flang Flang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants