[X86][clang] Disable long double type for -mno-x87 option
authorAndrew Savonichev <andrew.savonichev@gmail.com>
Wed, 3 Nov 2021 09:08:39 +0000 (12:08 +0300)
committerAndrew Savonichev <andrew.savonichev@gmail.com>
Wed, 3 Nov 2021 09:08:39 +0000 (12:08 +0300)
This patch attempts to fix a compiler crash that occurs when long
double type is used with -mno-x87 compiler option.

The option disables x87 target feature, which in turn disables x87
registers, so CG cannot select them for x86_fp80 LLVM IR type. Long
double is lowered as x86_fp80 for some targets, so it leads to a
crash.

The option seems to contradict the SystemV ABI, which requires long
double to be represented as a 80-bit floating point, and it also
requires to use x87 registers.

To avoid that, `long double` type is disabled when -mno-x87 option is
set. In addition to that, `float` and `double` also use x87 registers
for return values on 32-bit x86, so they are disabled as well.

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

clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Basic/TargetInfo.h
clang/lib/Basic/TargetInfo.cpp
clang/lib/Basic/Targets/X86.cpp
clang/lib/Basic/Targets/X86.h
clang/lib/Sema/Sema.cpp
clang/lib/Sema/SemaDecl.cpp
clang/test/Sema/x86-no-x87.cpp [new file with mode: 0644]
clang/test/Sema/x86_64-no-x87.cpp [new file with mode: 0644]
clang/test/SemaSYCL/float128.cpp

index a67ef68..fd5d7fb 100644 (file)
@@ -10714,8 +10714,8 @@ def err_omp_invariant_or_linear_dependency : Error<
 def err_omp_wrong_dependency_iterator_type : Error<
   "expected an integer or a pointer type of the outer loop counter '%0' for non-rectangular nests">;
 def err_target_unsupported_type
-    : Error<"%0 requires %select{|%2 bit size}1 %3 type support, but target "
-            "'%4' does not support it">;
+    : Error<"%0 requires %select{|%2 bit size}1 %3 %select{|return }4type support,"
+            " but target '%5' does not support it">;
 def err_omp_lambda_capture_in_declare_target_not_to : Error<
   "variable captured in declare target region must appear in a to clause">;
 def err_omp_device_type_mismatch : Error<
index 77a5104..3e1e094 100644 (file)
@@ -203,6 +203,8 @@ protected:
   bool HasFloat16;
   bool HasBFloat16;
   bool HasIbm128;
+  bool HasLongDouble;
+  bool HasFPReturn;
   bool HasStrictFP;
 
   unsigned char MaxAtomicPromoteWidth, MaxAtomicInlineWidth;
@@ -601,6 +603,13 @@ public:
   /// Determine whether the __ibm128 type is supported on this target.
   virtual bool hasIbm128Type() const { return HasIbm128; }
 
+  /// Determine whether the long double type is supported on this target.
+  virtual bool hasLongDoubleType() const { return HasLongDouble; }
+
+  /// Determine whether return of a floating point value is supported
+  /// on this target.
+  virtual bool hasFPReturn() const { return HasFPReturn; }
+
   /// Determine whether constrained floating point is supported on this target.
   virtual bool hasStrictFP() const { return HasStrictFP; }
 
index 76855b0..646bbe8 100644 (file)
@@ -37,6 +37,8 @@ TargetInfo::TargetInfo(const llvm::Triple &T) : TargetOpts(), Triple(T) {
   HasIbm128 = false;
   HasFloat16 = false;
   HasBFloat16 = false;
+  HasLongDouble = true;
+  HasFPReturn = true;
   HasStrictFP = false;
   PointerWidth = PointerAlign = 32;
   BoolWidth = BoolAlign = 8;
index f40d8a6..454a774 100644 (file)
@@ -338,6 +338,8 @@ bool X86TargetInfo::handleTargetFeatures(std::vector<std::string> &Features,
       HasUINTR = true;
     } else if (Feature == "+crc32") {
       HasCRC32 = true;
+    } else if (Feature == "+x87") {
+      HasX87 = true;
     }
 
     X86SSEEnum Level = llvm::StringSwitch<X86SSEEnum>(Feature)
@@ -379,6 +381,14 @@ bool X86TargetInfo::handleTargetFeatures(std::vector<std::string> &Features,
 
   SimdDefaultAlign =
       hasFeature("avx512f") ? 512 : hasFeature("avx") ? 256 : 128;
+
+  if (!HasX87) {
+    if (LongDoubleFormat == &llvm::APFloat::x87DoubleExtended())
+      HasLongDouble = false;
+    if (getTriple().getArch() == llvm::Triple::x86)
+      HasFPReturn = false;
+  }
+
   return true;
 }
 
@@ -1038,6 +1048,7 @@ bool X86TargetInfo::hasFeature(StringRef Feature) const {
       .Case("x86", true)
       .Case("x86_32", getTriple().getArch() == llvm::Triple::x86)
       .Case("x86_64", getTriple().getArch() == llvm::Triple::x86_64)
+      .Case("x87", HasX87)
       .Case("xop", XOPLevel >= XOP)
       .Case("xsave", HasXSAVE)
       .Case("xsavec", HasXSAVEC)
index 2c4ab04..b9b2ac7 100644 (file)
@@ -144,6 +144,7 @@ class LLVM_LIBRARY_VISIBILITY X86TargetInfo : public TargetInfo {
   bool HasTSXLDTRK = false;
   bool HasUINTR = false;
   bool HasCRC32 = false;
+  bool HasX87 = false;
 
 protected:
   llvm::X86::CPUKind CPU = llvm::X86::CK_None;
index 9fae224..a2b8f47 100644 (file)
@@ -1855,9 +1855,6 @@ Sema::SemaDiagnosticBuilder Sema::Diag(SourceLocation Loc, unsigned DiagID,
 }
 
 void Sema::checkTypeSupport(QualType Ty, SourceLocation Loc, ValueDecl *D) {
-  if (!LangOpts.SYCLIsDevice && !(LangOpts.OpenMP && LangOpts.OpenMPIsDevice))
-    return;
-
   if (isUnevaluatedContext() || Ty.isNull())
     return;
 
@@ -1880,7 +1877,7 @@ void Sema::checkTypeSupport(QualType Ty, SourceLocation Loc, ValueDecl *D) {
   FunctionDecl *FD = isa<FunctionDecl>(C) ? cast<FunctionDecl>(C)
                                           : dyn_cast_or_null<FunctionDecl>(D);
 
-  auto CheckType = [&](QualType Ty) {
+  auto CheckDeviceType = [&](QualType Ty) {
     if (Ty->isDependentType())
       return;
 
@@ -1892,7 +1889,7 @@ void Sema::checkTypeSupport(QualType Ty, SourceLocation Loc, ValueDecl *D) {
         else
           PD << "expression";
         targetDiag(Loc, PD, FD)
-            << false /*show bit size*/ << 0 /*bitsize*/
+            << false /*show bit size*/ << 0 /*bitsize*/ << false /*return*/
             << Ty << Context.getTargetInfo().getTriple().str();
       }
       return;
@@ -1925,6 +1922,49 @@ void Sema::checkTypeSupport(QualType Ty, SourceLocation Loc, ValueDecl *D) {
       if (targetDiag(Loc, PD, FD)
           << true /*show bit size*/
           << static_cast<unsigned>(Context.getTypeSize(Ty)) << Ty
+          << false /*return*/ << Context.getTargetInfo().getTriple().str()) {
+        if (D)
+          D->setInvalidDecl();
+      }
+      if (D)
+        targetDiag(D->getLocation(), diag::note_defined_here, FD) << D;
+    }
+  };
+
+  auto CheckType = [&](QualType Ty, bool IsRetTy = false) {
+    if (LangOpts.SYCLIsDevice || (LangOpts.OpenMP && LangOpts.OpenMPIsDevice))
+      CheckDeviceType(Ty);
+
+    QualType UnqualTy = Ty.getCanonicalType().getUnqualifiedType();
+    const TargetInfo &TI = Context.getTargetInfo();
+    if (!TI.hasLongDoubleType() && UnqualTy == Context.LongDoubleTy) {
+      PartialDiagnostic PD = PDiag(diag::err_target_unsupported_type);
+      if (D)
+        PD << D;
+      else
+        PD << "expression";
+
+      if (Diag(Loc, PD, FD)
+          << false /*show bit size*/ << 0 << Ty << false /*return*/
+          << Context.getTargetInfo().getTriple().str()) {
+        if (D)
+          D->setInvalidDecl();
+      }
+      if (D)
+        targetDiag(D->getLocation(), diag::note_defined_here, FD) << D;
+    }
+
+    bool IsDouble = UnqualTy == Context.DoubleTy;
+    bool IsFloat = UnqualTy == Context.FloatTy;
+    if (IsRetTy && !TI.hasFPReturn() && (IsDouble || IsFloat)) {
+      PartialDiagnostic PD = PDiag(diag::err_target_unsupported_type);
+      if (D)
+        PD << D;
+      else
+        PD << "expression";
+
+      if (Diag(Loc, PD, FD)
+          << false /*show bit size*/ << 0 << Ty << true /*return*/
           << Context.getTargetInfo().getTriple().str()) {
         if (D)
           D->setInvalidDecl();
@@ -1935,14 +1975,13 @@ void Sema::checkTypeSupport(QualType Ty, SourceLocation Loc, ValueDecl *D) {
   };
 
   CheckType(Ty);
-
   if (const auto *FPTy = dyn_cast<FunctionProtoType>(Ty)) {
     for (const auto &ParamTy : FPTy->param_types())
       CheckType(ParamTy);
-    CheckType(FPTy->getReturnType());
+    CheckType(FPTy->getReturnType(), /*IsRetTy=*/true);
   }
   if (const auto *FNPTy = dyn_cast<FunctionNoProtoType>(Ty))
-    CheckType(FNPTy->getReturnType());
+    CheckType(FNPTy->getReturnType(), /*IsRetTy=*/true);
 }
 
 /// Looks through the macro-expansion chain for the given
index d9844f1..f4c0893 100644 (file)
@@ -9570,8 +9570,6 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
     }
   }
 
-  checkTypeSupport(NewFD->getType(), D.getBeginLoc(), NewFD);
-
   if (!getLangOpts().CPlusPlus) {
     // Perform semantic checking on the function declaration.
     if (!NewFD->isInvalidDecl() && NewFD->isMain())
@@ -14857,6 +14855,9 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body,
       DeclsToCheckForDeferredDiags.insert(FD);
   }
 
+  if (FD && !FD->isDeleted())
+    checkTypeSupport(FD->getType(), FD->getLocation(), FD);
+
   return dcl;
 }
 
diff --git a/clang/test/Sema/x86-no-x87.cpp b/clang/test/Sema/x86-no-x87.cpp
new file mode 100644 (file)
index 0000000..112f6bf
--- /dev/null
@@ -0,0 +1,164 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s -triple i686-linux-gnu -target-feature -x87 -DRET_ERROR
+// RUN: %clang_cc1 -fsyntax-only -verify %s -triple i686-linux-gnu -DNOERROR
+
+#ifdef NOERROR
+// expected-no-diagnostics
+#endif
+
+typedef long double long_double;
+
+// Declaration is fine, unless it is called or defined.
+double decl(long_double x, long_double y);
+
+template <typename T>
+T decl_ld_del(T);
+
+// No code is generated for deleted functions
+long_double decl_ld_del(long_double) = delete;
+double decl_ld_del(double) = delete;
+float decl_ld_del(float) = delete;
+
+#ifndef NOERROR
+// expected-error@+4{{'def' requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+// expected-note@+3{{'def' defined here}}
+// expected-note@+2{{'x' defined here}}
+#endif
+int def(long_double x) {
+#ifndef NOERROR
+// expected-error@+2{{'x' requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  return (int)x;
+}
+
+#ifndef NOERROR
+// expected-note@+3{{'ld_args' defined here}}
+// expected-note@+2{{'ld_args' defined here}}
+#endif
+int ld_args(long_double x, long_double y);
+
+int call1(float x, float y) {
+#ifndef NOERROR
+  // expected-error@+2 2{{'ld_args' requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  return ld_args(x, y);
+}
+
+#ifndef NOERROR
+// expected-note@+2{{'ld_ret' defined here}}
+#endif
+long_double ld_ret(double x, double y);
+
+int call2(float x, float y) {
+#ifndef NOERROR
+  // expected-error@+2{{'ld_ret' requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  return (int)ld_ret(x, y);
+}
+
+int binop(double x, double y) {
+#ifndef NOERROR
+  // expected-error@+2 2{{expression requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  double z = (long_double)x * (long_double)y;
+  return (int)z;
+}
+
+void assign1(long_double *ret, double x) {
+#ifndef NOERROR
+  // expected-error@+2{{expression requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  *ret = x;
+}
+
+struct st_long_double1 {
+#ifndef NOERROR
+  // expected-note@+2{{'ld' defined here}}
+#endif
+  long_double ld;
+};
+
+struct st_long_double2 {
+#ifndef NOERROR
+  // expected-note@+2{{'ld' defined here}}
+#endif
+  long_double ld;
+};
+
+struct st_long_double3 {
+#ifndef NOERROR
+  // expected-note@+2{{'ld' defined here}}
+#endif
+  long_double ld;
+};
+
+void assign2() {
+  struct st_long_double1 st;
+#ifndef NOERROR
+  // expected-error@+3{{expression requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+  // expected-error@+2{{'ld' requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  st.ld = 0.42;
+}
+
+void assign3() {
+  struct st_long_double2 st;
+#ifndef NOERROR
+  // expected-error@+3{{expression requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+  // expected-error@+2{{'ld' requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  st.ld = 42;
+}
+
+void assign4(double d) {
+  struct st_long_double3 st;
+#ifndef NOERROR
+  // expected-error@+3{{expression requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+  // expected-error@+2{{'ld' requires  'long_double' (aka 'long double') type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  st.ld = d;
+}
+
+void assign5() {
+  // unused variable declaration is fine
+  long_double ld = 0.42;
+}
+
+#ifndef NOERROR
+// expected-note@+3{{'d_ret1' defined here}}
+// expected-error@+2{{'d_ret1' requires  'double' return type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+double d_ret1(float x) {
+  return 0.0;
+}
+
+#ifndef NOERROR
+// expected-note@+2{{'d_ret2' defined here}}
+#endif
+double d_ret2(float x);
+
+int d_ret3(float x) {
+#ifndef NOERROR
+  // expected-error@+2{{'d_ret2' requires  'double' return type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  return (int)d_ret2(x);
+}
+
+#ifndef NOERROR
+// expected-note@+3{{'f_ret1' defined here}}
+// expected-error@+2{{'f_ret1' requires  'float' return type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+float f_ret1(float x) {
+  return 0.0f;
+}
+
+#ifndef NOERROR
+// expected-note@+2{{'f_ret2' defined here}}
+#endif
+float f_ret2(float x);
+
+int f_ret3(float x) {
+#ifndef NOERROR
+  // expected-error@+2{{'f_ret2' requires  'float' return type support, but target 'i686-unknown-linux-gnu' does not support it}}
+#endif
+  return (int)f_ret2(x);
+}
diff --git a/clang/test/Sema/x86_64-no-x87.cpp b/clang/test/Sema/x86_64-no-x87.cpp
new file mode 100644 (file)
index 0000000..b47e69e
--- /dev/null
@@ -0,0 +1,145 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s -triple x86_64-linux-gnu -target-feature -x87
+// RUN: %clang_cc1 -fsyntax-only -verify %s -triple x86_64-linux-gnu -DNOERROR
+
+#ifdef NOERROR
+// expected-no-diagnostics
+#endif
+
+typedef long double long_double;
+
+// Declaration is fine, unless it is called or defined.
+double decl(long_double x, long_double y);
+
+template <typename T>
+T decl_ld_del(T);
+
+// No code is generated for deleted functions
+long_double decl_ld_del(long_double) = delete;
+double decl_ld_del(double) = delete;
+float decl_ld_del(float) = delete;
+
+#ifndef NOERROR
+// expected-error@+4{{'def' requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+// expected-note@+3{{'def' defined here}}
+// expected-note@+2{{'x' defined here}}
+#endif
+int def(long_double x) {
+#ifndef NOERROR
+// expected-error@+2{{'x' requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+#endif
+  return (int)x;
+}
+
+#ifndef NOERROR
+// expected-note@+3{{'ld_args' defined here}}
+// expected-note@+2{{'ld_args' defined here}}
+#endif
+int ld_args(long_double x, long_double y);
+
+int call1(float x, float y) {
+#ifndef NOERROR
+  // expected-error@+2 2{{'ld_args' requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+#endif
+  return ld_args(x, y);
+}
+
+#ifndef NOERROR
+// expected-note@+2{{'ld_ret' defined here}}
+#endif
+long_double ld_ret(double x, double y);
+
+int call2(float x, float y) {
+#ifndef NOERROR
+  // expected-error@+2{{'ld_ret' requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+#endif
+  return (int)ld_ret(x, y);
+}
+
+int binop(double x, double y) {
+#ifndef NOERROR
+  // expected-error@+2 2{{expression requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+#endif
+  double z = (long_double)x * (long_double)y;
+  return (int)z;
+}
+
+void assign1(long_double *ret, double x) {
+#ifndef NOERROR
+  // expected-error@+2{{expression requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+#endif
+  *ret = x;
+}
+
+struct st_long_double1 {
+#ifndef NOERROR
+  // expected-note@+2{{'ld' defined here}}
+#endif
+  long_double ld;
+};
+
+struct st_long_double2 {
+#ifndef NOERROR
+  // expected-note@+2{{'ld' defined here}}
+#endif
+  long_double ld;
+};
+
+struct st_long_double3 {
+#ifndef NOERROR
+  // expected-note@+2{{'ld' defined here}}
+#endif
+  long_double ld;
+};
+
+void assign2() {
+  struct st_long_double1 st;
+#ifndef NOERROR
+  // expected-error@+3{{expression requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+  // expected-error@+2{{'ld' requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+#endif
+  st.ld = 0.42;
+}
+
+void assign3() {
+  struct st_long_double2 st;
+#ifndef NOERROR
+  // expected-error@+3{{expression requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+  // expected-error@+2{{'ld' requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+#endif
+  st.ld = 42;
+}
+
+void assign4(double d) {
+  struct st_long_double3 st;
+#ifndef NOERROR
+  // expected-error@+3{{expression requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+  // expected-error@+2{{'ld' requires  'long_double' (aka 'long double') type support, but target 'x86_64-unknown-linux-gnu' does not support it}}
+#endif
+  st.ld = d;
+}
+
+void assign5() {
+  // unused variable declaration is fine
+  long_double ld = 0.42;
+}
+
+// Double and Float return type on x86_64 do not use x87 registers
+double d_ret1(float x) {
+  return 0.0;
+}
+
+double d_ret2(float x);
+
+int d_ret3(float x) {
+  return (int)d_ret2(x);
+}
+
+float f_ret1(float x) {
+  return 0.0f;
+}
+
+float f_ret2(float x);
+
+int f_ret3(float x) {
+  return (int)f_ret2(x);
+}
index 4629437..5b1a93d 100644 (file)
@@ -22,6 +22,8 @@ void host_ok(void) {
   C.field1 = A;
 }
 
+long double ld_func(long double arg);
+
 void usage() {
   // expected-note@+1 3{{'A' defined here}}
   __float128 A;
@@ -48,6 +50,9 @@ void usage() {
     float F2 = 0.1f;
     // expected-error@+1 3{{expression requires 128 bit size '__float128' type support, but target 'spir64' does not support it}}
     float F3 = ((__float128)F1 * (__float128)F2) / 2.0f;
+
+    // assume that long double is supported
+    float F4 = ld_func(F3);
   };
 
   // expected-note@+1 {{called by 'usage'}}