george.burgess.iv created this revision.
Herald added a subscriber: javed.absar.
(Copy/pasting the reviewer list from https://reviews.llvm.org/D26856.)
Addresses https://bugs.llvm.org/show_bug.cgi?id=30792 .
In GCC, -mgeneral-regs-only emits errors upon trying to emit floating-point or
vector operations that originate from C/C++ (but not inline assembly).
Currently, our behavior is to accept those, but we proceed to "try to form
some new horribly unsupported soft-float ABI."
Additionally, the way that we disable vector/FP ops causes us to crash when
inline assembly uses any vector/FP operations, which is bad.
This patch attempts to address these by:
- making -mgeneral-regs-only behave exactly like -mno-implicit-float to the
backend, which lets inline assembly use FP regs/operations as much as it wants,
and
- emitting errors for any floating-point expressions/operations we encounter in
the frontend.
The latter is the more interesting bit. We want to allow declarations with
floats/vectors as much as possible, but the moment that we actually
try to use a float/vector, we should diagnose it. In less words:
float f(float a); // OK
int j = f(1); // Not OK on two points: returns a float, takes a float
float f(float a) { // Not OK: defines a function that takes a float and
returns
// a float
return 0; // Not OK: 0 is converted to a float.
}
A trivial implementation of this leaves us with a terrible diagnostic
experience (e.g.
int r() {
int i = 0, j = 0;
return 1.0 + i + j;
}
emits many, many diagnostics about implicit float casts, floating adds, etc.
Other kinds of diagnostics, like diagnosing default args, are also very
low-quality), so the majority of this patch is an attempt to handle common
cases more gracefully.
Since the target audience for this is presumably very small, and the cost of not
emitting a diagnostic when we should is *a lot* of debugging, I erred on the
side of simplicity for a lot of this. I think this patch does a reasonably good
job of offering targeted error messages in the majority of cases.
There are a few cases where we'll allow floating-point/vector values to
conceptually be used:
int i = 1.0 + 1; // OK: guaranteed to fold to an int
float foo();
int bar(int i = foo()); // OK: just a decl.
int baz() {
int a = bar(1); // OK: we never actually call foo().
int b = bar(); // Error: calling foo().
return a + b;
}
struct A { float b; };
void qux(struct A *a, struct A *b) {
// OK: we've been codegening @llvm.memcpys for this seemingly since 2012.
// For the moment, this bit is C-only, and very constrained (e.g.
assignments
// only, rhs must trivially be an lvalue, ...).
*a = *b;
}
The vibe I got from the bug is that the soft-float incantations we currently
emit when using -mgeneral-regs-only are basically unused, so I'm unsure
if we want a flag/option that lets users flip back to the current
-mgeneral-regs-only behavior. This patch lacks that feature, but I'm happy
to add it if people believe doing so would be valuable.
One final note: this may seem like a problem better solved in CodeGen.
I avoided doing so because that approach had a few downsides:
- how we codegen any expression that might have FP becomes observable,
- checks for this become spread out across many, many places, making it really
easy to miss a case/forget to add it in a new place (see the above "few users,
bugs are expensive" point), and
- it seems really difficult to "look upwards" in CodeGen to pattern match these
into nicer diagnostics, especially in the case of default arguments, etc.
https://reviews.llvm.org/D38479
Files:
docs/UsersManual.rst
include/clang/Basic/DiagnosticSemaKinds.td
include/clang/Basic/LangOptions.def
include/clang/Driver/CC1Options.td
include/clang/Sema/Sema.h
lib/Driver/ToolChains/Arch/AArch64.cpp
lib/Driver/ToolChains/Clang.cpp
lib/Frontend/CompilerInvocation.cpp
lib/Sema/SemaDecl.cpp
lib/Sema/SemaExprCXX.cpp
test/CodeGen/aarch64-mgeneral_regs_only.c
test/Driver/aarch64-mgeneral_regs_only.c
test/Sema/aarch64-mgeneral_regs_only.c
test/SemaCXX/aarch64-mgeneral_regs_only.cpp
Index: test/SemaCXX/aarch64-mgeneral_regs_only.cpp
===================================================================
--- /dev/null
+++ test/SemaCXX/aarch64-mgeneral_regs_only.cpp
@@ -0,0 +1,124 @@
+// RUN: %clang_cc1 -triple aarch64-linux-eabi -std=c++11 -general-regs-only %s -verify -DBANNED=float -Wno-unused-value
+// RUN: %clang_cc1 -triple aarch64-linux-eabi -std=c++11 -general-regs-only %s -verify -DBANNED=int '-DVECATTR=__attribute__((ext_vector_type(2)))' -Wno-unused-value
+// RUN: %clang_cc1 -triple aarch64-linux-eabi -std=c++11 -general-regs-only %s -verify -DBANNED=FloatTypedef -Wno-unused-value
+
+using FloatTypedef = float;
+
+#ifndef VECATTR
+#define VECATTR
+#define BannedToInt(x) (x)
+#else
+#define BannedToInt(x) ((x)[0])
+#endif
+
+typedef BANNED BannedTy VECATTR;
+
+namespace default_args {
+int foo(BannedTy = 0); // expected-error 2{{use of floating-point or vector values is disabled}}
+int bar(int = 1.0);
+
+void baz(int a = foo()); // expected-note{{from use of default argument here}}
+void bazz(int a = bar());
+
+void test() {
+ foo(); // expected-note{{from use of default argument here}}
+ bar();
+ baz(); // expected-note{{from use of default argument here}}
+ baz(4);
+ bazz();
+}
+}
+
+namespace conversions {
+struct ConvertToFloat { explicit operator BannedTy(); };
+struct ConvertToFloatImplicit { /* implicit */ operator BannedTy(); };
+struct ConvertFromFloat { ConvertFromFloat(BannedTy); };
+
+void takeFloat(BannedTy);
+void takeConvertFromFloat(ConvertFromFloat);
+void takeConvertFromFloatRef(const ConvertFromFloat &);
+
+void test() {
+ BannedTy(ConvertToFloat());
+
+ takeFloat(ConvertToFloatImplicit{}); // expected-error{{use of floating-point or vector values is disabled}}
+
+ ConvertFromFloat(0); // expected-error{{use of floating-point or vector values is disabled}}
+ ConvertFromFloat(ConvertToFloatImplicit{}); // expected-error{{use of floating-point or vector values is disabled}}
+
+ ConvertFromFloat{0}; // expected-error{{use of floating-point or vector values is disabled}}
+ ConvertFromFloat{ConvertToFloatImplicit{}}; // expected-error{{use of floating-point or vector values is disabled}}
+
+ takeConvertFromFloat(0); // expected-error{{use of floating-point or vector values is disabled}}
+ takeConvertFromFloatRef(0); // expected-error{{use of floating-point or vector values is disabled}}
+
+ takeConvertFromFloat(ConvertFromFloat(0)); // expected-error{{use of floating-point or vector values is disabled}}
+ takeConvertFromFloatRef(ConvertFromFloat(0)); // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+
+void takeImplicitFloat(BannedTy = ConvertToFloatImplicit()); // expected-error{{use of floating-point or vector values is disabled}}
+void test2() {
+ takeImplicitFloat(); // expected-note{{from use of default argument here}}
+}
+}
+
+namespace refs {
+ struct BannedRef {
+ const BannedTy &f;
+ BannedRef(const BannedTy &f): f(f) {}
+ };
+
+ BannedTy &getBanned();
+ BannedTy getBannedVal();
+
+ void foo() {
+ BannedTy &a = getBanned();
+ BannedTy b = getBanned(); // expected-error{{use of floating-point or vector values is disabled}}
+ const BannedTy &c = getBanned();
+ const BannedTy &d = getBannedVal(); // expected-error{{use of floating-point or vector values is disabled}}
+
+ const int &e = 1.0;
+ const int &f = BannedToInt(getBannedVal()); // expected-error{{use of floating-point or vector values is disabled}}
+
+ BannedRef{getBanned()};
+ BannedRef{getBannedVal()}; // expected-error{{use of floating-point or vector values is disabled}}
+ }
+}
+
+namespace class_init {
+ struct Foo {
+ float f = 1.0; // expected-error{{use of floating-point or vector values is disabled}}
+ int i = 1.0;
+ float j;
+
+ Foo():
+ j(1) // expected-error{{use of floating-point or vector values is disabled}}
+ {}
+ };
+}
+
+namespace copy_move_assign {
+ struct Foo { float f; }; // expected-error 2{{use of floating-point or vector values is disabled}}
+
+ void copyFoo(Foo &f) {
+ Foo a = f; // expected-error{{use of floating-point or vector values is disabled}}
+ Foo b(static_cast<Foo &&>(f)); // expected-error{{use of floating-point or vector values is disabled}}
+ f = a; // expected-note{{in implicit copy assignment operator}}
+ f = static_cast<Foo &&>(b); // expected-error{{use of floating-point or vector values is disabled}} expected-note {{in implicit move assignment operator}}
+ }
+}
+
+namespace templates {
+float bar();
+
+template <typename T>
+T foo(int t = bar()) { // expected-error 2{{use of floating-point or vector values is disabled}}
+ return t; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+void test() {
+ foo<float>(9); // expected-error{{use of floating-point or vector values is disabled}} expected-note{{in instantiation of function template specialization}}
+ foo<float>(); // expected-error{{use of floating-point or vector values is disabled}} expected-note{{in instantiation of default function argument}} expected-note{{from use of default argument}}
+}
+}
Index: test/Sema/aarch64-mgeneral_regs_only.c
===================================================================
--- /dev/null
+++ test/Sema/aarch64-mgeneral_regs_only.c
@@ -0,0 +1,250 @@
+// RUN: %clang_cc1 -triple aarch64-linux-eabi -general-regs-only %s -verify -DBANNED=int '-DVECATTR=__attribute__((ext_vector_type(2)))' -Wno-unused-value
+// RUN: %clang_cc1 -triple aarch64-linux-eabi -general-regs-only %s -verify -DBANNED=float -Wno-unused-value
+// RUN: %clang_cc1 -triple aarch64-linux-eabi -general-regs-only %s -verify -DBANNED=FloatTypedef -Wno-unused-value
+
+// Try to diagnose every use of a floating-point operation that we can't
+// trivially fold. Declaring floats/passing their addresses around/etc. is OK,
+// assuming we never actually use them in this TU.
+
+typedef float FloatTypedef;
+
+#ifndef VECATTR
+#define VECATTR
+#endif
+typedef BANNED BannedTy VECATTR;
+
+// Whether or not this is the actual definition for uintptr_t doesn't matter.
+typedef unsigned long long uintptr_t;
+
+// We only start caring when the user actually tries to do things with floats;
+// declarations on their own are fine.
+// This allows a user to #include some headers that happen to use floats. As
+// long as they're never actually used, no one cares.
+BannedTy foo();
+void bar(BannedTy);
+extern BannedTy gBaz;
+
+void calls() {
+ __auto_type a = foo(); // expected-error{{use of floating-point or vector values is disabled}}
+ BannedTy b = foo(); // expected-error{{use of floating-point or vector values is disabled}}
+ foo(); // expected-error{{use of floating-point or vector values is disabled}}
+ (void)foo(); // expected-error{{use of floating-point or vector values is disabled}}
+
+ bar(1); // expected-error{{use of floating-point or vector values is disabled}}
+ bar(1.); // expected-error{{use of floating-point or vector values is disabled}}
+
+ gBaz = 1; // expected-error{{use of floating-point or vector values is disabled}}
+ gBaz = 1.; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+BannedTy global_banned;
+
+void literals() {
+ volatile int i;
+ i = 1.;
+ i = 1.0 + 2;
+ i = (int)(1.0 + 2);
+
+ BannedTy j = 1; // expected-error{{use of floating-point or vector values is disabled}}
+ BannedTy k = (BannedTy)1.1; // expected-error{{use of floating-point or vector values is disabled}}
+ BannedTy l;
+ (BannedTy)3; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+struct Baz {
+ int i;
+ BannedTy f;
+};
+
+union Qux {
+ int i;
+ BannedTy j;
+ BannedTy *p;
+};
+
+struct Baz *getBaz(int i);
+
+void structs(void *p) {
+ struct Baz b;
+ union Qux q;
+ q.i = 1;
+ q.j = 2.; // expected-error{{use of floating-point or vector values is disabled}}
+ q.j = 2.f; // expected-error{{use of floating-point or vector values is disabled}}
+ q.j = 2; // expected-error{{use of floating-point or vector values is disabled}}
+ q.p = (BannedTy *)p;
+ q.p += 5;
+ *q.p += 5; // expected-error{{use of floating-point or vector values is disabled}}
+
+ b = (struct Baz){}; // expected-error{{use of floating-point or vector values is disabled}}
+ b = *getBaz(2. + b.i); // expected-error{{use of floating-point or vector values is disabled}}
+ *getBaz(2. + b.i) = b; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+struct Baz callBaz(struct Baz);
+union Qux callQux(union Qux);
+struct Baz *callBazPtr(struct Baz *);
+union Qux *callQuxPtr(union Qux *);
+
+void structCalls() {
+ void *p;
+ callBazPtr((struct Baz *)p);
+ callQuxPtr((union Qux *)p);
+
+ // One error for returning a `struct Baz`, one for taking a `struct Baz`, one
+ // for constructing a `struct Baz`. Not ideal, but...
+ callBaz((struct Baz){}); // expected-error 3{{use of floating-point or vector values is disabled}}
+ callQux((union Qux){});
+}
+
+extern BannedTy extern_arr[4];
+static BannedTy static_arr[4];
+
+void arrays() {
+ BannedTy bannedArr[] = { // expected-error{{use of floating-point or vector values is disabled}}
+ 1,
+ 1.0,
+ 2.0f,
+ };
+ int intArr[] = { 1.0, 2.0f };
+
+ intArr[0] = 1.0;
+ bannedArr[0] = 1; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+BannedTy *getMemberPtr(struct Baz *b, int i) {
+ if (i)
+ return &b->f;
+ return &((struct Baz *)(uintptr_t)((uintptr_t)b + 1.0))->f; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+void casts() {
+ void *volatile p;
+
+ (BannedTy *)p;
+ (void)*(BannedTy *)p; // expected-error{{use of floating-point or vector values is disabled}}
+
+ (void)*(struct Baz *)p; // expected-error{{use of floating-point or vector values is disabled}}
+ (void)*(union Qux *)p;
+ (void)((union Qux *)p)->i;
+ (void)((union Qux *)p)->j; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+BannedTy returns() { // expected-error{{use of floating-point or vector values is disabled}}
+ return 0; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+int unevaluated() {
+ return sizeof((BannedTy)0.0);
+}
+
+void moreUnevaluated(int x)
+ __attribute__((diagnose_if(x + 1.1 == 2.1, "oh no", "warning"))) {
+ moreUnevaluated(3);
+ moreUnevaluated(1); // expected-warning{{oh no}} expected-note@-2{{from 'diagnose_if'}}
+}
+
+void noSpam() {
+ float r = 1. + 2 + 3 + 4 + 5.; // expected-error 2{{use of floating-point or vector values is disabled}}
+ float r2 = 1. + r + 3 + 4 + 5.; // expected-error 3{{use of floating-point or vector values is disabled}}
+ float r3 = 1 + 2 + 3 + 4 + 5; // expected-error{{use of floating-point or vector values is disabled}}
+
+ // no errors expected below: they can be trivially folded to a constant.
+ int i = 1. + 2 + 3 + 4 + 5.; // no error: we can trivially fold this to a constant.
+ int j = (int)(1. + 2 + 3) + 4; // no error: we can trivially fold this to a constant.
+ int k = (int)(1. + 2 + 3) + j;
+ int l = (int)(1. + 2 + 3) +
+ r; //expected-error {{use of floating-point or vector values is disabled}}
+
+ const int cj = (int)(1. + 2 + 3) + 4; // no error: we can trivially fold this to a constant.
+ int ck = (int)(1. + cj + 3) +
+ r; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+float fooFloat();
+int exprStmt() {
+ return ({ fooFloat() + 1 + 2; }) + 3; // expected-error 2{{use of floating-point or vector values is disabled}}
+}
+
+int longExprs() {
+ // FIXME: Should we be able to fold this to a constant?
+ return 1 + ((struct Baz){.i = 1}).i; // expected-error{{use of floating-point or vector values is disabled}}
+}
+
+struct RecursiveTy { // expected-note{{is not complete until}}
+ int a;
+ BannedTy b;
+ struct RecursiveTy ty; // expected-error{{field has incomplete type}}
+};
+
+struct StructRec {
+ int a;
+ BannedTy b;
+ struct RecursiveTy ty;
+};
+
+union UnionRec {
+ int a;
+ BannedTy b;
+ struct RecursiveTy ty;
+};
+
+struct UndefType; // expected-note 3{{forward declaration}}
+
+struct StructUndef {
+ int a;
+ BannedTy b;
+ struct UndefType s; // expected-error{{field has incomplete type}}
+};
+
+union UnionUndef {
+ int a;
+ BannedTy b;
+ struct UndefType s; // expected-error{{field has incomplete type}}
+};
+
+// ...Just be sure we don't crash on these.
+void cornerCases() {
+ struct RecInl { // expected-note{{is not complete until the closing}}
+ struct RecInl s; // expected-error{{field has incomplete type}}
+ BannedTy f;
+ } inl;
+ __builtin_memset(&inl, 0, sizeof(inl));
+
+ BannedTy fs[] = {
+ ((struct RecursiveTy){}).a,
+ ((struct StructRec){}).a,
+ ((union UnionRec){}).a,
+ ((struct StructUndef){}).a,
+ ((union UnionUndef){}).a,
+ };
+
+ BannedTy fs2[] = {
+ ((struct RecursiveTy){}).b,
+ ((struct StructRec){}).b,
+ ((union UnionRec){}).b,
+ ((struct StructUndef){}).b,
+ ((union UnionUndef){}).b,
+ };
+
+ struct UndefType a = {}; // expected-error{{has incomplete type}}
+ struct RecursiveTy b = {};
+ struct StructRec c = {};
+ union UnionRec d = {};
+ struct StructUndef e = {};
+ union UnionUndef f = {};
+
+ __builtin_memset(&a, 0, sizeof(a));
+ __builtin_memset(&b, 0, sizeof(b));
+ __builtin_memset(&c, 0, sizeof(c));
+ __builtin_memset(&d, 0, sizeof(d));
+ __builtin_memset(&e, 0, sizeof(e));
+ __builtin_memset(&f, 0, sizeof(f));
+}
+
+void floatFunctionDefIn(BannedTy a) {} // expected-error{{use of floating-point or vector values is disabled}}
+BannedTy floatFunctionDefOut(void) {} // expected-error{{use of floating-point or vector values is disabled}}
+BannedTy // expected-error{{use of floating-point or vector values is disabled}}
+floatFunctionDefInOut(BannedTy a) {} // expected-error{{use of floating-point or vector values is disabled}}
+
+void floatInStructDefIn(struct Baz a) {} // expected-error{{use of floating-point or vector values is disabled}}
+struct Baz floatInStructDefOut(void) {} // expected-error{{use of floating-point or vector values is disabled}}
Index: test/Driver/aarch64-mgeneral_regs_only.c
===================================================================
--- test/Driver/aarch64-mgeneral_regs_only.c
+++ /dev/null
@@ -1,9 +0,0 @@
-// Test the -mgeneral-regs-only option
-
-// RUN: %clang -target aarch64-linux-eabi -mgeneral-regs-only %s -### 2>&1 \
-// RUN: | FileCheck --check-prefix=CHECK-NO-FP %s
-// RUN: %clang -target arm64-linux-eabi -mgeneral-regs-only %s -### 2>&1 \
-// RUN: | FileCheck --check-prefix=CHECK-NO-FP %s
-// CHECK-NO-FP: "-target-feature" "-fp-armv8"
-// CHECK-NO-FP: "-target-feature" "-crypto"
-// CHECK-NO-FP: "-target-feature" "-neon"
Index: test/CodeGen/aarch64-mgeneral_regs_only.c
===================================================================
--- /dev/null
+++ test/CodeGen/aarch64-mgeneral_regs_only.c
@@ -0,0 +1,64 @@
+// RUN: %clang -target aarch64 -mgeneral-regs-only %s -o - -S -emit-llvm | FileCheck %s
+// %clang -target aarch64 -mgeneral-regs-only %s -o - -S | FileCheck %s --check-prefix=ASM
+
+// CHECK-LABEL: @j = constant i32 3
+
+float arr[8];
+
+// CHECK-LABEL: noimplicitfloat
+// CHECK-NEXT: define void @foo
+const int j = 1.0 + 2.0;
+void foo(int *i) {
+ // CHECK: store i32 4
+ *i = 1.0 + j;
+}
+
+// CHECK-LABEL: noimplicitfloat
+// CHECK-NEXT: define void @bar
+void bar(float **j) {
+ __builtin_memcpy(arr, j, sizeof(arr));
+ *j = arr;
+}
+
+struct OneFloat { float i; };
+struct TwoFloats { float i, j; };
+
+static struct OneFloat oneFloat;
+static struct TwoFloats twoFloats;
+
+// CHECK-LABEL: testOneFloat
+void testOneFloat(const struct OneFloat *o, struct OneFloat *f) {
+ // These memcpys are necessary, so we don't generate FP ops in LLVM.
+ // CHECK: @llvm.memcpy
+ // CHECK: @llvm.memcpy
+ *f = *o;
+ oneFloat = *o;
+}
+
+// CHECK-LABEL: testTwoFloats
+void testTwoFloats(const struct TwoFloats *o, struct TwoFloats *f) {
+ // CHECK: @llvm.memcpy
+ // CHECK: @llvm.memcpy
+ *f = *o;
+ twoFloats = *o;
+}
+
+// -mgeneral-regs-only implies that the compiler can't invent
+// floating-point/vector instructions. The user should be allowed to do that,
+// though.
+//
+// CHECK-LABEL: baz
+// ASM-LABEL: baz:
+void baz(float *i) {
+ // CHECK: call float* asm sideeffect
+ // ASM: fmov s1, #
+ // ASM: fadd s0, s0, s1
+ asm volatile("ldr s0, [%0]\n"
+ "fmov s1, #1.00000000\n"
+ "fadd s0, s0, s1\n"
+ "str s0, [%0]\n"
+ "ret\n"
+ : "=r"(i)
+ :
+ : "s0", "s1");
+}
Index: lib/Sema/SemaExprCXX.cpp
===================================================================
--- lib/Sema/SemaExprCXX.cpp
+++ lib/Sema/SemaExprCXX.cpp
@@ -20,6 +20,7 @@
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/DeclObjC.h"
+#include "clang/AST/EvaluatedExprVisitor.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/RecursiveASTVisitor.h"
@@ -7472,6 +7473,247 @@
return E;
}
+static bool typeHasFloatingOrVectorComponent(
+ QualType Ty, llvm::SmallDenseMap<const RecordType *, bool, 8> &TypeCache) {
+ if (Ty.isNull())
+ return false;
+
+ while (const auto *AT = Ty->getAsArrayTypeUnsafe())
+ Ty = AT->getElementType();
+
+ if (Ty->isScalarType())
+ return Ty->hasFloatingRepresentation();
+
+ if (Ty->isVectorType())
+ return true;
+
+ const auto *StructTy = Ty->getAsStructureType();
+ if (!StructTy)
+ return false;
+
+ // We may see recursive types in broken code.
+ auto InsertPair = TypeCache.insert({StructTy, false});
+ if (!InsertPair.second)
+ return InsertPair.first->second;
+
+ bool R;
+ if (Ty->isUnionType()) {
+ R = llvm::any_of(StructTy->getDecl()->fields(), [&](const FieldDecl *FD) {
+ return !typeHasFloatingOrVectorComponent(FD->getType(), TypeCache);
+ });
+ } else {
+ R = llvm::any_of(StructTy->getDecl()->fields(), [&](const FieldDecl *FD) {
+ return typeHasFloatingOrVectorComponent(FD->getType(), TypeCache);
+ });
+ }
+
+ TypeCache[StructTy] = R;
+ return R;
+}
+
+bool Sema::typeHasFloatingOrVectorComponent(
+ QualType Ty, llvm::SmallDenseMap<const RecordType *, bool, 8> &TypeCache) {
+ return ::typeHasFloatingOrVectorComponent(Ty, TypeCache);
+}
+
+namespace {
+// Diagnoses any uses of vector/floating-point values that we can't trivially
+// fold to a non-vector/non-floating constant in codegen.
+class NonGeneralOpDiagnoser
+ : public ConstEvaluatedExprVisitor<NonGeneralOpDiagnoser> {
+ using Super = ConstEvaluatedExprVisitor<NonGeneralOpDiagnoser>;
+
+ struct DiagnosticInfo {
+ SourceLocation Loc;
+ SourceRange Range;
+
+ // Default arguments are only diagnosed when they're used, but the error
+ // diagnostic points to the default argument itself. This contains the
+ // series of calls that brought us to that default arg.
+ llvm::TinyPtrVector<const CallExpr *> DefaultArgLocs;
+
+ // These should only exist in their fully-constructed form.
+ DiagnosticInfo() = delete;
+
+ DiagnosticInfo(const Expr *E)
+ : Loc(E->getLocStart()), Range(E->getSourceRange()) {}
+
+ DiagnosticInfo(const DiagnosticInfo &) = default;
+ DiagnosticInfo(DiagnosticInfo &&) = default;
+
+ DiagnosticInfo &operator=(const DiagnosticInfo &) = default;
+ DiagnosticInfo &operator=(DiagnosticInfo &&) = default;
+ };
+
+ Sema &S;
+ llvm::SmallDenseMap<const RecordType *, bool, 8> TypeCache;
+ llvm::SmallVector<DiagnosticInfo, 8> DiagnosticsToEmit;
+
+ bool isGeneralType(const Expr *E) {
+ return !typeHasFloatingOrVectorComponent(E->getType(), TypeCache);
+ }
+
+ void enqueueDiagnosticFor(const Expr *E) {
+ DiagnosticsToEmit.emplace_back(E);
+ }
+
+ bool isRValueOfIllegalType(const Expr *E) {
+ return E->getValueKind() != VK_LValue && !isGeneralType(E);
+ }
+
+ bool diagnoseIfNonGeneralRValue(const Expr *E) {
+ if (!isRValueOfIllegalType(E))
+ return false;
+
+ enqueueDiagnosticFor(E);
+ return true;
+ }
+
+ static const Expr *ignoreParenImpFloatCastsAndSplats(const Expr *E) {
+ const Expr *Cur = E->IgnoreParens();
+ if (const auto *Sub = dyn_cast<ImplicitCastExpr>(Cur))
+ if (Sub->getCastKind() == CK_IntegralToFloating ||
+ Sub->getCastKind() == CK_VectorSplat)
+ return Sub->getSubExpr()->IgnoreParens();
+ return Cur;
+ }
+
+ struct DiagnosticState {
+ unsigned InitialSize;
+ };
+
+ DiagnosticState saveDiagnosticState() const {
+ return {static_cast<unsigned>(DiagnosticsToEmit.size())};
+ }
+
+ MutableArrayRef<DiagnosticInfo>
+ diagnosticsIssuedSince(const DiagnosticState &S) {
+ assert(S.InitialSize <= DiagnosticsToEmit.size() &&
+ "DiagnosticsToEmit shouldn't shrink across saves!");
+ return {DiagnosticsToEmit.begin() + S.InitialSize, DiagnosticsToEmit.end()};
+ }
+
+ void resetDiagnosticState(const DiagnosticState &S) {
+ DiagnosticsToEmit.erase(DiagnosticsToEmit.begin() + S.InitialSize,
+ DiagnosticsToEmit.end());
+ }
+
+ // Special case: Don't diagnose patterns that will ultimately turn into a
+ // memcpy in CodeGen. Returns true if we matched the pattern (and emitted
+ // diagnostics, if necessary). This is a small convenience to the user, so
+ // they don't have to write out memcpy() for simple cases. For that reason,
+ // it's very limited in what it will detect.
+ //
+ // N.B. this is C-specific, since C++ doesn't really deal in assignments of
+ // structs.
+ bool diagnoseTrivialRecordAssignmentExpr(const BinaryOperator *BO) {
+ if (BO->getOpcode() != BO_Assign || !BO->getType()->isRecordType())
+ return false;
+
+ const auto *RHS = dyn_cast<ImplicitCastExpr>(BO->getRHS()->IgnoreParens());
+ if (!RHS || RHS->getCastKind() != CK_LValueToRValue)
+ return false;
+
+ Visit(BO->getLHS());
+ Visit(RHS->getSubExpr());
+ return true;
+ }
+
+public:
+ NonGeneralOpDiagnoser(Sema &S) : Super(S.getASTContext()), S(S) {}
+
+ void VisitExpr(const Expr *E) {
+ if (!diagnoseIfNonGeneralRValue(E))
+ Super::VisitExpr(E);
+ }
+
+ void VisitCXXDefaultArgExpr(const CXXDefaultArgExpr *E) {
+ Visit(E->getExpr());
+ }
+
+ void VisitCallExpr(const CallExpr *E) {
+ diagnoseIfNonGeneralRValue(E);
+ Visit(E->getCallee());
+
+ for (const Expr *Arg : E->arguments()) {
+ DiagnosticState Saved = saveDiagnosticState();
+ Visit(Arg);
+ if (Arg->isDefaultArgument())
+ for (DiagnosticInfo &DI : diagnosticsIssuedSince(Saved))
+ DI.DefaultArgLocs.push_back(E);
+ }
+ }
+
+ void VisitCastExpr(const CastExpr *E) {
+ DiagnosticState InitialState = saveDiagnosticState();
+
+ Visit(E->getSubExpr());
+
+ bool IssuedDiagnostics = !diagnosticsIssuedSince(InitialState).empty();
+ // Ignore diagnostics for subexpressions that we can trivially fold to a
+ // constant in CodeGen.
+ if (IssuedDiagnostics && isRValueOfIllegalType(E->getSubExpr()) &&
+ !isRValueOfIllegalType(E) &&
+ E->isConstantInitializer(S.getASTContext(), /*ForRef=*/false)) {
+ resetDiagnosticState(InitialState);
+ return;
+ }
+
+ if (isRValueOfIllegalType(E) && !isRValueOfIllegalType(E->getSubExpr()))
+ enqueueDiagnosticFor(E);
+ }
+
+ void VisitParenExpr(const ParenExpr *E) { Visit(E->getSubExpr()); }
+ void VisitDeclRefExpr(const DeclRefExpr *E) { diagnoseIfNonGeneralRValue(E); }
+
+ void VisitBinaryOperator(const BinaryOperator *BO) {
+ if (isGeneralType(BO)) {
+ Visit(BO->getLHS());
+ Visit(BO->getRHS());
+ return;
+ }
+
+ if (diagnoseTrivialRecordAssignmentExpr(BO))
+ return;
+
+ DiagnosticState InitialState = saveDiagnosticState();
+ // Ignore implicit casts to illegal types so we minimize diagnostics for
+ // things like `1 + 2. + 3 + 4`.
+ Visit(ignoreParenImpFloatCastsAndSplats(BO->getLHS()));
+ Visit(ignoreParenImpFloatCastsAndSplats(BO->getRHS()));
+
+ // Since we don't diagnose LValues, this is somewhat common.
+ if (diagnosticsIssuedSince(InitialState).empty())
+ enqueueDiagnosticFor(BO);
+ }
+
+ void VisitExprWithCleanups(const ExprWithCleanups *E) {
+ Visit(E->getSubExpr());
+ }
+
+ static void diagnoseExpr(Sema &S, const Expr *E) {
+ NonGeneralOpDiagnoser Diagnoser(S);
+ Diagnoser.Visit(E);
+ for (const DiagnosticInfo &DI : Diagnoser.DiagnosticsToEmit) {
+ SourceLocation Loc = DI.Loc;
+ SourceRange Range = DI.Range;
+ // There are rare cases (e.g. `(struct Foo){}`, where Foo
+ // isNonGeneralType), where the expression we're trying to point to
+ // doesn't exist. Fall back to complaining about the full expr.
+ if (Loc.isInvalid()) {
+ Loc = E->getLocStart();
+ Range = E->getSourceRange();
+ assert(!Loc.isInvalid());
+ }
+ S.Diag(Loc, diag::err_non_general_ops_disabled) << Range;
+
+ for (const CallExpr *CE : DI.DefaultArgLocs)
+ S.Diag(CE->getRParenLoc(), diag::note_from_use_of_default_arg);
+ }
+ }
+};
+} // end anonymous namespace
+
ExprResult Sema::ActOnFinishFullExpr(Expr *FE, SourceLocation CC,
bool DiscardedValue,
bool IsConstexpr,
@@ -7524,6 +7766,9 @@
CheckCompletedExpr(FullExpr.get(), CC, IsConstexpr);
+ if (getLangOpts().GeneralOpsOnly)
+ NonGeneralOpDiagnoser::diagnoseExpr(*this, FullExpr.get());
+
// At the end of this full expression (which could be a deeply nested
// lambda), if there is a potential capture within the nested lambda,
// have the outer capture-able lambda try and capture it.
Index: lib/Sema/SemaDecl.cpp
===================================================================
--- lib/Sema/SemaDecl.cpp
+++ lib/Sema/SemaDecl.cpp
@@ -8702,6 +8702,20 @@
// Finally, we know we have the right number of parameters, install them.
NewFD->setParams(Params);
+ if (getLangOpts().GeneralOpsOnly &&
+ D.getFunctionDefinitionKind() == FDK_Definition) {
+ llvm::SmallDenseMap<const RecordType *, bool, 8> TypeCache;
+ if (typeHasFloatingOrVectorComponent(NewFD->getReturnType(), TypeCache)) {
+ SourceRange Range = NewFD->getReturnTypeSourceRange();
+ Diag(Range.getBegin(), diag::err_non_general_ops_disabled) << Range;
+ }
+
+ for (const ParmVarDecl *PVD : NewFD->parameters())
+ if (typeHasFloatingOrVectorComponent(PVD->getType(), TypeCache))
+ Diag(PVD->getLocStart(), diag::err_non_general_ops_disabled)
+ << PVD->getSourceRange();
+ }
+
if (D.getDeclSpec().isNoreturnSpecified())
NewFD->addAttr(
::new(Context) C11NoReturnAttr(D.getDeclSpec().getNoreturnSpecLoc(),
Index: lib/Frontend/CompilerInvocation.cpp
===================================================================
--- lib/Frontend/CompilerInvocation.cpp
+++ lib/Frontend/CompilerInvocation.cpp
@@ -1988,6 +1988,9 @@
if (Opts.CUDAIsDevice && Args.hasArg(OPT_fcuda_approx_transcendentals))
Opts.CUDADeviceApproxTranscendentals = 1;
+ if (Args.hasArg(OPT_general_regs_only))
+ Opts.GeneralOpsOnly = 1;
+
if (Opts.ObjC1) {
if (Arg *arg = Args.getLastArg(OPT_fobjc_runtime_EQ)) {
StringRef value = arg->getValue();
Index: lib/Driver/ToolChains/Clang.cpp
===================================================================
--- lib/Driver/ToolChains/Clang.cpp
+++ lib/Driver/ToolChains/Clang.cpp
@@ -1310,6 +1310,24 @@
}
}
+static void addGeneralRegsOnlyArgs(const Driver &D, const ArgList &Args,
+ ArgStringList &CmdArgs) {
+ if (Args.getLastArg(options::OPT_mgeneral_regs_only)) {
+ if (Args.hasFlag(options::OPT_mimplicit_float,
+ options::OPT_mno_implicit_float, false)) {
+ D.Diag(diag::err_drv_argument_not_allowed_with) << "-mimplicit-float"
+ << "-mgeneral-regs-only";
+ return;
+ }
+
+ CmdArgs.push_back("-general-regs-only");
+ CmdArgs.push_back("-no-implicit-float");
+ } else if (!Args.hasFlag(options::OPT_mimplicit_float,
+ options::OPT_mno_implicit_float, true)) {
+ CmdArgs.push_back("-no-implicit-float");
+ }
+}
+
void Clang::AddARMTargetArgs(const llvm::Triple &Triple, const ArgList &Args,
ArgStringList &CmdArgs, bool KernelOrKext) const {
// Select the ABI to use.
@@ -1355,9 +1373,7 @@
CmdArgs.push_back("-arm-global-merge=true");
}
- if (!Args.hasFlag(options::OPT_mimplicit_float,
- options::OPT_mno_implicit_float, true))
- CmdArgs.push_back("-no-implicit-float");
+ addGeneralRegsOnlyArgs(getToolChain().getDriver(), Args, CmdArgs);
}
void Clang::RenderTargetOptions(const llvm::Triple &EffectiveTriple,
@@ -1440,9 +1456,7 @@
Args.hasArg(options::OPT_fapple_kext))
CmdArgs.push_back("-disable-red-zone");
- if (!Args.hasFlag(options::OPT_mimplicit_float,
- options::OPT_mno_implicit_float, true))
- CmdArgs.push_back("-no-implicit-float");
+ addGeneralRegsOnlyArgs(getToolChain().getDriver(), Args, CmdArgs);
const char *ABIName = nullptr;
if (Arg *A = Args.getLastArg(options::OPT_mabi_EQ))
Index: lib/Driver/ToolChains/Arch/AArch64.cpp
===================================================================
--- lib/Driver/ToolChains/Arch/AArch64.cpp
+++ lib/Driver/ToolChains/Arch/AArch64.cpp
@@ -172,12 +172,6 @@
if (!success)
D.Diag(diag::err_drv_clang_unsupported) << A->getAsString(Args);
- if (Args.getLastArg(options::OPT_mgeneral_regs_only)) {
- Features.push_back("-fp-armv8");
- Features.push_back("-crypto");
- Features.push_back("-neon");
- }
-
// En/disable crc
if (Arg *A = Args.getLastArg(options::OPT_mcrc, options::OPT_mnocrc)) {
if (A->getOption().matches(options::OPT_mcrc))
Index: include/clang/Sema/Sema.h
===================================================================
--- include/clang/Sema/Sema.h
+++ include/clang/Sema/Sema.h
@@ -10206,6 +10206,14 @@
unsigned ByteNo) const;
private:
+ // Tests to see if the given type is or contains a float or vector, as defined
+ // by -mgeneral-regs-only.
+ //
+ // `Cache` can be used shared across runs to potentially speed up later
+ // queries.
+ static bool typeHasFloatingOrVectorComponent(
+ QualType Ty, llvm::SmallDenseMap<const RecordType *, bool, 8> &Cache);
+
void CheckArrayAccess(const Expr *BaseExpr, const Expr *IndexExpr,
const ArraySubscriptExpr *ASE=nullptr,
bool AllowOnePastEnd=true, bool IndexNegated=false);
Index: include/clang/Driver/CC1Options.td
===================================================================
--- include/clang/Driver/CC1Options.td
+++ include/clang/Driver/CC1Options.td
@@ -204,6 +204,8 @@
HelpText<"Emit an error if a C++ static local initializer would need a guard variable">;
def no_implicit_float : Flag<["-"], "no-implicit-float">,
HelpText<"Don't generate implicit floating point instructions">;
+def general_regs_only : Flag<["-"], "general-regs-only">,
+ HelpText<"Don't allow the generation of floating point or vector instructions">;
def fdump_vtable_layouts : Flag<["-"], "fdump-vtable-layouts">,
HelpText<"Dump the layouts of all vtables that will be emitted in a translation unit">;
def fmerge_functions : Flag<["-"], "fmerge-functions">,
Index: include/clang/Basic/LangOptions.def
===================================================================
--- include/clang/Basic/LangOptions.def
+++ include/clang/Basic/LangOptions.def
@@ -136,6 +136,7 @@
LANGOPT(GNUAsm , 1, 1, "GNU-style inline assembly")
LANGOPT(CoroutinesTS , 1, 0, "C++ coroutines TS")
LANGOPT(RelaxedTemplateTemplateArgs, 1, 0, "C++17 relaxed matching of template template arguments")
+LANGOPT(GeneralOpsOnly , 1, 0, "Whether to diagnose the use of floating-point or vector operations")
BENIGN_LANGOPT(ThreadsafeStatics , 1, 1, "thread-safe static initializers")
LANGOPT(POSIXThreads , 1, 0, "POSIX thread support")
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -6067,6 +6067,11 @@
def ext_freestanding_complex : Extension<
"complex numbers are an extension in a freestanding C99 implementation">;
+def err_non_general_ops_disabled : Error<
+ "use of floating-point or vector values is disabled by "
+ "'-mgeneral-regs-only'">;
+def note_from_use_of_default_arg : Note<"from use of default argument here">;
+
// FIXME: Remove when we support imaginary.
def err_imaginary_not_supported : Error<"imaginary types are not supported">;
Index: docs/UsersManual.rst
===================================================================
--- docs/UsersManual.rst
+++ docs/UsersManual.rst
@@ -1247,7 +1247,8 @@
Generate code which only uses the general purpose registers.
This option restricts the generated code to use general registers
- only. This only applies to the AArch64 architecture.
+ only. This only applies to the AArch64 architecture. This restriction does
+ not apply to inline assembly.
.. option:: -mcompact-branches=[values]
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits