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
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to