Skip to content

Commit

Permalink
[X86][clang] Disable long double type for -mno-x87 option
Browse files Browse the repository at this point in the history
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
  • Loading branch information
asavonic committed Nov 3, 2021
1 parent 3d32218 commit a8083d4
Show file tree
Hide file tree
Showing 10 changed files with 389 additions and 12 deletions.
4 changes: 2 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/TargetInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ class TargetInfo : public virtual TransferrableTargetInfo,
bool HasFloat16;
bool HasBFloat16;
bool HasIbm128;
bool HasLongDouble;
bool HasFPReturn;
bool HasStrictFP;

unsigned char MaxAtomicPromoteWidth, MaxAtomicInlineWidth;
Expand Down Expand Up @@ -601,6 +603,13 @@ class TargetInfo : public virtual TransferrableTargetInfo,
/// 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; }

Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Basic/TargetInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions clang/lib/Basic/Targets/X86.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions clang/lib/Basic/Targets/X86.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
55 changes: 47 additions & 8 deletions clang/lib/Sema/Sema.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down
5 changes: 3 additions & 2 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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;
}

Expand Down
164 changes: 164 additions & 0 deletions clang/test/Sema/x86-no-x87.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
Loading

0 comments on commit a8083d4

Please sign in to comment.