[flang] Improve runtime interface with C99 complex

Follow up of https://reviews.llvm.org/D83397.

In folding, make pgmath usage conditional to C99 complex
support in C++. Disable warning in such case.

In lowering, use an empty class type to indicate C99 complex
type in runtime interface.

Add a unit test enforcing C99 complex can be processed
by FIR runtime interface builder.

Differential Revision: https://reviews.llvm.org/D110860

GitOrigin-RevId: b7c07ce15ffe6da9dcd69d457a3eca987452edc7
diff --git a/include/flang/Evaluate/pgmath.h.inc b/include/flang/Evaluate/pgmath.h.inc
index 02d8ae3..f22e6cf 100644
--- a/include/flang/Evaluate/pgmath.h.inc
+++ b/include/flang/Evaluate/pgmath.h.inc
@@ -22,6 +22,7 @@
 // Control Macros
 #ifdef PGMATH_DECLARE
 #undef PGMATH_DECLARE
+#define DEFINE_C_COMPLEX_TYPES
 #define PGMATH_DECLARE(x) extern "C" x;
 #define PGMATH_FAST
 #define PGMATH_PRECISE
@@ -33,6 +34,8 @@
 #ifdef PGMATH_USE_ALL_TYPES
 #define PGMATH_USE_S(name, func) PGMATH_USE_ALL_TYPES(name, func)
 #define PGMATH_USE_D(name, func) PGMATH_USE_ALL_TYPES(name, func)
+#define PGMATH_USE_C(name, func) PGMATH_USE_ALL_TYPES(name, func)
+#define PGMATH_USE_Z(name, func) PGMATH_USE_ALL_TYPES(name, func)
 #define PGMATH_USE_OTHER(name, func) PGMATH_USE_ALL_TYPES(name, func)
 #endif
 
@@ -56,14 +59,53 @@
 #define PGMATH_USE_OTHER(name, x)
 #endif
 
+// Handle the C99 _Complex vs C++ std::complex call interface issue.
+// _Complex and std::complex are layout compatible (they are the same when
+// in memory), but they are not guaranteed to be compatible in call interface
+// (they may be passed/returned differently). For instance on X86 32 bits,
+// float _complex is returned in a pair of register, but std::complex<float>
+// is returned in memory.
+// Pgmath is defined in C using _Complex (and windows _Fcomplex/_DComplex
+// equivalents). Since this file defines the call interface with the runtime
+// for both folding and code generation (through template introspection), it
+// is crucial to make a distinction between std::complex and _Complex here.
+// Unfortunately, _Complex support is not standard in C++.
+// Reserve pgmath usage at compile time (folding) when _Complex is available
+// (cmake is responsible to detect this).
+// For code generation, define type c_float_complex_t that can be used in
+// introspection to indicate that the C99 _Complex ABI has to be used for the
+// related value.
+#ifdef DEFINE_C_COMPLEX_TYPES
+#ifdef PGMATH_LINKING
+#ifdef _WIN32
+using c_float_complex_t = _Fcomplex;
+using c_double_complex_t = _Dcomplex;
+#else
+using c_float_complex_t = float _Complex;
+using c_double_complex_t = double _Complex;
+#endif
+#else
+struct c_float_complex_t {};
+struct c_double_complex_t {};
+#endif
+#endif
+
 #define PGMATH_REAL_IMPL(impl, func) \
   PGMATH_DECLARE(float __##impl##s_##func##_1(float)) \
   PGMATH_DECLARE(double __##impl##d_##func##_1(double)) \
   PGMATH_USE_S(func, __##impl##s_##func##_1) \
   PGMATH_USE_D(func, __##impl##d_##func##_1)
 
+#define PGMATH_COMPLEX_IMPL(impl, func) \
+  PGMATH_DECLARE(c_float_complex_t __##impl##c_##func##_1(c_float_complex_t)) \
+  PGMATH_DECLARE( \
+      c_double_complex_t __##impl##z_##func##_1(c_double_complex_t)) \
+  PGMATH_USE_C(func, __##impl##c_##func##_1) \
+  PGMATH_USE_Z(func, __##impl##z_##func##_1)
+
 #define PGMATH_ALL_FP_IMPL(impl, func) \
   PGMATH_REAL_IMPL(impl, func) \
+  PGMATH_FAST_COMPLEX_IMPL(impl, func)
 
 #define PGMATH_REAL2_IMPL(impl, func) \
   PGMATH_DECLARE(float __##impl##s_##func##_1(float, float)) \
@@ -71,8 +113,17 @@
   PGMATH_USE_S(func, __##impl##s_##func##_1) \
   PGMATH_USE_D(func, __##impl##d_##func##_1)
 
+#define PGMATH_COMPLEX2_IMPL(impl, func) \
+  PGMATH_DECLARE(c_float_complex_t __##impl##c_##func##_1( \
+      c_float_complex_t, c_float_complex_t)) \
+  PGMATH_DECLARE(c_double_complex_t __##impl##z_##func##_1( \
+      c_double_complex_t, c_double_complex_t)) \
+  PGMATH_USE_C(func, __##impl##c_##func##_1) \
+  PGMATH_USE_Z(func, __##impl##z_##func##_1)
+
 #define PGMATH_ALL_FP2_IMPL(impl, func) \
   PGMATH_REAL2_IMPL(func) \
+  PGMATH_COMPLEX2_IMPL(func)
 
 #undef PGMATH_FAST_REAL
 #undef PGMATH_FAST_COMPLEX
@@ -82,8 +133,10 @@
 #undef PGMATH_FAST_ALL_FP2
 #ifdef PGMATH_FAST
 #define PGMATH_FAST_REAL(func) PGMATH_REAL_IMPL(f, func)
+#define PGMATH_FAST_COMPLEX(func) PGMATH_COMPLEX_IMPL(f, func)
 #define PGMATH_FAST_ALL_FP(func) PGMATH_ALL_IMPL(f, func)
 #define PGMATH_FAST_REAL2(func) PGMATH_REAL2_IMPL(f, func)
+#define PGMATH_FAST_COMPLEX2(func) PGMATH_COMPLEX2_IMPL(f, func)
 #define PGMATH_FAST_ALL_FP2(func) PGMATH_ALL_FP2_IMPL(f, func)
 #else
 #define PGMATH_FAST_REAL(func)
@@ -102,8 +155,10 @@
 #undef PGMATH_RELAXED_ALL_FP2
 #ifdef PGMATH_RELAXED
 #define PGMATH_RELAXED_REAL(func) PGMATH_REAL_IMPL(r, func)
+#define PGMATH_RELAXED_COMPLEX(func) PGMATH_COMPLEX_IMPL(r, func)
 #define PGMATH_RELAXED_ALL_FP(func) PGMATH_ALL_IMPL(r, func)
 #define PGMATH_RELAXED_REAL2(func) PGMATH_REAL2_IMPL(r, func)
+#define PGMATH_RELAXED_COMPLEX2(func) PGMATH_COMPLEX2_IMPL(r, func)
 #define PGMATH_RELAXED_ALL_FP2(func) PGMATH_ALL_FP2_IMPL(r, func)
 #else
 #define PGMATH_RELAXED_REAL(func)
@@ -122,8 +177,10 @@
 #undef PGMATH_PRECISE_ALL_FP2
 #ifdef PGMATH_PRECISE
 #define PGMATH_PRECISE_REAL(func) PGMATH_REAL_IMPL(p, func)
+#define PGMATH_PRECISE_COMPLEX(func) PGMATH_COMPLEX_IMPL(p, func)
 #define PGMATH_PRECISE_ALL_FP(func) PGMATH_ALL_IMPL(p, func)
 #define PGMATH_PRECISE_REAL2(func) PGMATH_REAL2_IMPL(p, func)
+#define PGMATH_PRECISE_COMPLEX2(func) PGMATH_COMPLEX2_IMPL(p, func)
 #define PGMATH_PRECISE_ALL_FP2(func) PGMATH_ALL_FP2_IMPL(p, func)
 #else
 #define PGMATH_PRECISE_REAL(func)
@@ -139,16 +196,28 @@
   PGMATH_PRECISE_REAL(func) \
   PGMATH_RELAXED_REAL(func)
 
+#define PGMATH_COMPLEX(func) \
+  PGMATH_FAST_COMPLEX(func) \
+  PGMATH_PRECISE_COMPLEX(func) \
+  PGMATH_RELAXED_COMPLEX(func)
+
 #define PGMATH_ALL(func) \
   PGMATH_REAL(func) \
+  PGMATH_COMPLEX(func)
 
 #define PGMATH_REAL2(func) \
   PGMATH_FAST_REAL2(func) \
   PGMATH_PRECISE_REAL2(func) \
   PGMATH_RELAXED_REAL2(func)
 
+#define PGMATH_COMPLEX2(func) \
+  PGMATH_FAST_COMPLEX2(func) \
+  PGMATH_PRECISE_COMPLEX2(func) \
+  PGMATH_RELAXED_COMPLEX2(func)
+
 #define PGMATH_ALL2(func) \
   PGMATH_REAL2(func) \
+  PGMATH_COMPLEX2(func)
 
 // Marcos to declare __mth_i libpgmath variants
 #define PGMATH_MTH_VERSION_REAL(func) \
@@ -207,12 +276,19 @@
 #define PGMATH_DELCARE_POW(impl) \
   PGMATH_DECLARE(float __##impl##s_powi_1(float, int)) \
   PGMATH_DECLARE(double __##impl##d_powi_1(double, int)) \
+  PGMATH_DECLARE(c_float_complex_t __##impl##c_powi_1(c_float_complex_t, int)) \
+  PGMATH_DECLARE( \
+      c_double_complex_t __##impl##z_powi_1(c_double_complex_t, int)) \
   PGMATH_USE_S(pow, __##impl##s_powi_1) \
   PGMATH_USE_D(pow, __##impl##d_powi_1) \
   PGMATH_USE_C(pow, __##impl##c_powi_1) \
   PGMATH_USE_Z(pow, __##impl##z_powi_1) \
   PGMATH_DECLARE(float __##impl##s_powk_1(float, int64_t)) \
   PGMATH_DECLARE(double __##impl##d_powk_1(double, int64_t)) \
+  PGMATH_DECLARE( \
+      c_float_complex_t __##impl##c_powk_1(c_float_complex_t, int64_t)) \
+  PGMATH_DECLARE( \
+      c_double_complex_t __##impl##z_powk_1(c_double_complex_t, int64_t)) \
   PGMATH_USE_S(pow, __##impl##s_powk_1) \
   PGMATH_USE_D(pow, __##impl##d_powk_1) \
   PGMATH_USE_C(pow, __##impl##c_powk_1) \
@@ -237,6 +313,7 @@
 PGMATH_ALL(sin)
 PGMATH_ALL(sinh)
 PGMATH_MTH_VERSION_REAL(sqrt)
+PGMATH_COMPLEX(sqrt) // real versions are __mth_i...
 PGMATH_ALL(tan)
 PGMATH_ALL(tanh)
 
@@ -250,3 +327,5 @@
 #undef PGMATH_USE_Z
 #undef PGMATH_USE_OTHER
 #undef PGMATH_USE_ALL_TYPES
+#undef PGMATH_LINKING
+#undef DEFINE_C_COMPLEX_TYPES
diff --git a/lib/Evaluate/CMakeLists.txt b/lib/Evaluate/CMakeLists.txt
index 2b8eafa..1bb82ac 100644
--- a/lib/Evaluate/CMakeLists.txt
+++ b/lib/Evaluate/CMakeLists.txt
@@ -2,8 +2,18 @@
   # If pgmath library is found, it can be used for constant folding.
   find_library(LIBPGMATH pgmath PATHS ${LIBPGMATH_DIR})
   if(LIBPGMATH)
-    add_compile_definitions(LINK_WITH_LIBPGMATH)
-    message(STATUS "Found libpgmath: ${LIBPGMATH}")
+    # pgmath uses _Complex, so only enable linking pgmath with flang in environments
+    # that support it (MSVC is OK, pgmath uses _Fcomplex/_Dcomplex there).
+    if (CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU|MSVC")
+      check_cxx_compiler_flag("-Werror -Wc99-extensions" HAS_WC99_EXTENSIONS_FLAG)
+      if (HAS_WC99_EXTENSIONS_FLAG)
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c99-extensions")
+      endif()
+      add_compile_definitions(LINK_WITH_LIBPGMATH)
+      message(STATUS "Found libpgmath: ${LIBPGMATH}")
+    else()
+      message(STATUS "Libpgmath will not be used because C99 complex is not supported.")
+    endif()
   else()
     message(STATUS "Libpgmath not found in: ${LIBPGMATH_DIR}")
   endif()
diff --git a/lib/Lower/RTBuilder.h b/lib/Lower/RTBuilder.h
index 4b4e63d..9f23ea0 100644
--- a/lib/Lower/RTBuilder.h
+++ b/lib/Lower/RTBuilder.h
@@ -27,6 +27,13 @@
 // List the runtime headers we want to be able to dissect
 #include "flang/Runtime/io-api.h"
 
+// Incomplete type indicating C99 complex ABI in interfaces. Beware, _Complex
+// and std::complex are layout compatible, but not compatible in all ABI call
+// interface (e.g. X86 32 bits). _Complex is not standard C++, so do not use
+// it here.
+struct c_float_complex_t;
+struct c_double_complex_t;
+
 namespace Fortran::lower {
 
 using TypeBuilderFunc = mlir::Type (*)(mlir::MLIRContext *);
@@ -156,7 +163,18 @@
     return fir::ReferenceType::get(f(context));
   };
 }
-
+template <>
+constexpr TypeBuilderFunc getModel<c_float_complex_t>() {
+  return [](mlir::MLIRContext *context) -> mlir::Type {
+    return fir::ComplexType::get(context, sizeof(float));
+  };
+}
+template <>
+constexpr TypeBuilderFunc getModel<c_double_complex_t>() {
+  return [](mlir::MLIRContext *context) -> mlir::Type {
+    return fir::ComplexType::get(context, sizeof(double));
+  };
+}
 template <>
 constexpr TypeBuilderFunc getModel<const Fortran::runtime::Descriptor &>() {
   return [](mlir::MLIRContext *context) -> mlir::Type {
diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt
index 5acebce..95e3f6d 100644
--- a/unittests/CMakeLists.txt
+++ b/unittests/CMakeLists.txt
@@ -39,5 +39,6 @@
 add_subdirectory(Optimizer)
 add_subdirectory(Decimal)
 add_subdirectory(Evaluate)
+add_subdirectory(Lower)
 add_subdirectory(Runtime)
 add_subdirectory(Frontend)
diff --git a/unittests/Lower/CMakeLists.txt b/unittests/Lower/CMakeLists.txt
new file mode 100644
index 0000000..a3f61f0
--- /dev/null
+++ b/unittests/Lower/CMakeLists.txt
@@ -0,0 +1,15 @@
+get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)
+
+set(LIBS
+  FIROptimizer
+  MLIRLLVMIR
+  ${dialect_libs}
+)
+
+add_flang_unittest(FlangLoweringTests
+  RTBuilder.cpp
+)
+
+target_link_libraries(FlangLoweringTests
+  PRIVATE
+  ${LIBS})
diff --git a/unittests/Lower/RTBuilder.cpp b/unittests/Lower/RTBuilder.cpp
new file mode 100644
index 0000000..9c7238c
--- /dev/null
+++ b/unittests/Lower/RTBuilder.cpp
@@ -0,0 +1,36 @@
+//===- RTBuilder.cpp -- Runtime Interface unit tests ----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "../../lib/Lower/RTBuilder.h"
+#include "gtest/gtest.h"
+#include "flang/Optimizer/Support/InitFIR.h"
+
+// Check that it is possible to make a difference between complex runtime
+// function using C99 complex and C++ std::complex. This is important since
+// they are layout compatible but not link time compatible (returned differently
+// in X86 32 ABI for instance). At high level fir, we need to convey that the
+// signature are different regardless of the target ABI.
+
+// Fake runtime header to be introspected.
+c_float_complex_t c99_cacosf(c_float_complex_t);
+
+TEST(RTBuilderTest, ComplexRuntimeInterface) {
+  mlir::DialectRegistry registry;
+  fir::support::registerDialects(registry);
+  mlir::MLIRContext ctx(registry);
+  fir::support::loadDialects(ctx);
+  mlir::Type c99_cacosf_signature{
+      Fortran::lower::RuntimeTableKey<decltype(c99_cacosf)>::getTypeModel()(
+          &ctx)};
+  auto c99_cacosf_funcTy = c99_cacosf_signature.cast<mlir::FunctionType>();
+  EXPECT_EQ(c99_cacosf_funcTy.getNumInputs(), 1u);
+  EXPECT_EQ(c99_cacosf_funcTy.getNumResults(), 1u);
+  auto cplx_ty = fir::ComplexType::get(&ctx, 4);
+  EXPECT_EQ(c99_cacosf_funcTy.getInput(0), cplx_ty);
+  EXPECT_EQ(c99_cacosf_funcTy.getResult(0), cplx_ty);
+}