[clang] 846e562 - [Clang] add support for error+warning fn attrs

Nick Desaulniers via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 25 10:34:37 PDT 2021


Author: Nick Desaulniers
Date: 2021-08-25T10:34:18-07:00
New Revision: 846e562dcc6a9a611d844dc0d123da95629a0567

URL: https://github.com/llvm/llvm-project/commit/846e562dcc6a9a611d844dc0d123da95629a0567
DIFF: https://github.com/llvm/llvm-project/commit/846e562dcc6a9a611d844dc0d123da95629a0567.diff

LOG: [Clang] add support for error+warning fn attrs

Add support for the GNU C style __attribute__((error(""))) and
__attribute__((warning(""))). These attributes are meant to be put on
declarations of functions whom should not be called.

They are frequently used to provide compile time diagnostics similar to
_Static_assert, but which may rely on non-ICE conditions (ie. relying on
compiler optimizations). This is also similar to diagnose_if function
attribute, but can diagnose after optimizations have been run.

While users may instead simply call undefined functions in such cases to
get a linkage failure from the linker, these provide a much more
ergonomic and actionable diagnostic to users and do so at compile time
rather than at link time. Users instead may be able use inline asm .err
directives.

These are used throughout the Linux kernel in its implementation of
BUILD_BUG and BUILD_BUG_ON macros. These macros generally cannot be
converted to use _Static_assert because many of the parameters are not
ICEs. The Linux kernel still needs to be modified to make use of these
when building with Clang; I have a patch that does so I will send once
this feature is landed.

To do so, we create a new IR level Function attribute, "dontcall" (both
error and warning boil down to one IR Fn Attr).  Then, similar to calls
to inline asm, we attach a !srcloc Metadata node to call sites of such
attributed callees.

The backend diagnoses these during instruction selection, while we still
know that a call is a call (vs say a JMP that's a tail call) in an arch
agnostic manner.

The frontend then reconstructs the SourceLocation from that Metadata,
and determines whether to emit an error or warning based on the callee's
attribute.

Link: https://bugs.llvm.org/show_bug.cgi?id=16428
Link: https://github.com/ClangBuiltLinux/linux/issues/1173

Reviewed By: aaron.ballman

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

Added: 
    clang/test/CodeGen/attr-error.c
    clang/test/CodeGen/attr-warning.c
    clang/test/Frontend/backend-attribute-error-warning-optimize.c
    clang/test/Frontend/backend-attribute-error-warning.c
    clang/test/Sema/attr-error.c
    clang/test/Sema/attr-warning.c
    llvm/test/CodeGen/X86/attr-dontcall.ll
    llvm/test/ThinLTO/X86/dontcall.ll

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Basic/DiagnosticFrontendKinds.td
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Sema/Sema.h
    clang/lib/CodeGen/CGCall.cpp
    clang/lib/CodeGen/CodeGenAction.cpp
    clang/lib/CodeGen/CodeGenModule.cpp
    clang/lib/Sema/SemaDecl.cpp
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/test/Misc/pragma-attribute-supported-attributes-list.test
    llvm/docs/LangRef.rst
    llvm/include/llvm/IR/DiagnosticInfo.h
    llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
    llvm/lib/CodeGen/SelectionDAG/FastISel.cpp
    llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
    llvm/lib/IR/DiagnosticInfo.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 8b62f026765e3..4ed7543a2b865 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -103,6 +103,8 @@ C Language Changes in Clang
 - Wide multi-characters literals such as ``L'ab'`` that would previously be interpreted as ``L'b'``
   are now ill-formed in all language modes. The motivation for this change is outlined in
   `P2362 <wg21.link/P2362>`_.
+- Support for ``__attribute__((error("")))`` and
+  ``__attribute__((warning("")))`` function attributes have been added.
 
 C++ Language Changes in Clang
 -----------------------------

diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index f9ae1b8d86281..a54039501a7f7 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3838,3 +3838,12 @@ def EnforceTCBLeaf : InheritableAttr {
   let Documentation = [EnforceTCBLeafDocs];
   bit InheritEvenIfAlreadyPresent = 1;
 }
+
+def Error : InheritableAttr {
+  let Spellings = [GCC<"error">, GCC<"warning">];
+  let Accessors = [Accessor<"isError", [GCC<"error">]>,
+                   Accessor<"isWarning", [GCC<"warning">]>];
+  let Args = [StringArgument<"UserDiagnostic">];
+  let Subjects = SubjectList<[Function], ErrorDiag>;
+  let Documentation = [ErrorAttrDocs];
+}

diff  --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 3e25041ba5510..b2b41c3bb4197 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6071,3 +6071,29 @@ def EnforceTCBLeafDocs : Documentation {
   - ``enforce_tcb_leaf(Name)`` indicates that this function is a part of the TCB named ``Name``
   }];
 }
+
+def ErrorAttrDocs : Documentation {
+  let Category = DocCatFunction;
+  let Heading = "error, warning";
+  let Content = [{
+The ``error`` and ``warning`` function attributes can be used to specify a
+custom diagnostic to be emitted when a call to such a function is not
+eliminated via optimizations. This can be used to create compile time
+assertions that depend on optimizations, while providing diagnostics
+pointing to precise locations of the call site in the source.
+
+.. code-block:: c++
+
+  __attribute__((warning("oh no"))) void dontcall();
+  void foo() {
+    if (someCompileTimeAssertionThatsTrue)
+      dontcall(); // Warning
+
+    dontcall(); // Warning
+
+    if (someCompileTimeAssertionThatsFalse)
+      dontcall(); // No Warning
+    sizeof(dontcall()); // No Warning
+  }
+  }];
+}

diff  --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index fceafb93eda28..35e0d2649f5ee 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -73,6 +73,12 @@ def note_fe_backend_invalid_loc : Note<"could "
 def err_fe_backend_unsupported : Error<"%0">, BackendInfo;
 def warn_fe_backend_unsupported : Warning<"%0">, BackendInfo;
 
+def err_fe_backend_error_attr :
+  Error<"call to %0 declared with 'error' attribute: %1">, BackendInfo;
+def warn_fe_backend_warning_attr :
+  Warning<"call to %0 declared with 'warning' attribute: %1">, BackendInfo,
+  InGroup<BackendWarningAttributes>;
+
 def err_fe_invalid_code_complete_file : Error<
     "cannot locate code-completion file %0">, DefaultFatal;
 def err_fe_dependency_file_requires_MT : Error<

diff  --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 95023b8b34375..f48598eec3262 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1222,6 +1222,7 @@ def BackendOptimizationRemark : DiagGroup<"pass">;
 def BackendOptimizationRemarkMissed : DiagGroup<"pass-missed">;
 def BackendOptimizationRemarkAnalysis : DiagGroup<"pass-analysis">;
 def BackendOptimizationFailure : DiagGroup<"pass-failed">;
+def BackendWarningAttributes : DiagGroup<"attribute-warning">;
 
 // Instrumentation based profiling warnings.
 def ProfileInstrMissing : DiagGroup<"profile-instr-missing">;

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 0205c28c48569..188e94a9645be 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3338,6 +3338,8 @@ class Sema final {
                                             const AttributeCommonInfo &CI,
                                             bool BestCase,
                                             MSInheritanceModel Model);
+  ErrorAttr *mergeErrorAttr(Decl *D, const AttributeCommonInfo &CI,
+                            StringRef NewUserDiagnostic);
   FormatAttr *mergeFormatAttr(Decl *D, const AttributeCommonInfo &CI,
                               IdentifierInfo *Format, int FormatIdx,
                               int FirstArg);

diff  --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 8c38b4f732f2f..1ddd4d160b4bd 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5317,6 +5317,15 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
       TargetDecl->hasAttr<MSAllocatorAttr>())
     getDebugInfo()->addHeapAllocSiteMetadata(CI, RetTy->getPointeeType(), Loc);
 
+  // Add metadata if calling an __attribute__((error(""))) or warning fn.
+  if (TargetDecl && TargetDecl->hasAttr<ErrorAttr>()) {
+    llvm::ConstantInt *Line =
+        llvm::ConstantInt::get(Int32Ty, Loc.getRawEncoding());
+    llvm::ConstantAsMetadata *MD = llvm::ConstantAsMetadata::get(Line);
+    llvm::MDTuple *MDT = llvm::MDNode::get(getLLVMContext(), {MD});
+    CI->setMetadata("srcloc", MDT);
+  }
+
   // 4. Finish the call.
 
   // If the call doesn't return, finish the basic block and clear the

diff  --git a/clang/lib/CodeGen/CodeGenAction.cpp b/clang/lib/CodeGen/CodeGenAction.cpp
index e66e41d1278e3..789c06a15b71c 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -401,6 +401,7 @@ namespace clang {
         const llvm::OptimizationRemarkAnalysisAliasing &D);
     void OptimizationFailureHandler(
         const llvm::DiagnosticInfoOptimizationFailure &D);
+    void DontCallDiagHandler(const DiagnosticInfoDontCall &D);
   };
 
   void BackendConsumer::anchor() {}
@@ -758,6 +759,33 @@ void BackendConsumer::OptimizationFailureHandler(
   EmitOptimizationMessage(D, diag::warn_fe_backend_optimization_failure);
 }
 
+void BackendConsumer::DontCallDiagHandler(const DiagnosticInfoDontCall &D) {
+  if (const Decl *DE = Gen->GetDeclForMangledName(D.getFunctionName()))
+    if (const auto *FD = dyn_cast<FunctionDecl>(DE)) {
+      assert(FD->hasAttr<ErrorAttr>() &&
+             "expected error or warning function attribute");
+
+      if (const auto *EA = FD->getAttr<ErrorAttr>()) {
+        assert((EA->isError() || EA->isWarning()) &&
+               "ErrorAttr neither error or warning");
+
+        SourceLocation LocCookie =
+            SourceLocation::getFromRawEncoding(D.getLocCookie());
+
+        // FIXME: we can't yet diagnose indirect calls. When/if we can, we
+        // should instead assert that LocCookie.isValid().
+        if (!LocCookie.isValid())
+          return;
+
+        Diags.Report(LocCookie, EA->isError()
+                                    ? diag::err_fe_backend_error_attr
+                                    : diag::warn_fe_backend_warning_attr)
+            << FD << EA->getUserDiagnostic();
+      }
+    }
+  // TODO: assert if DE or FD were nullptr?
+}
+
 /// This function is invoked when the backend needs
 /// to report something to the user.
 void BackendConsumer::DiagnosticHandlerImpl(const DiagnosticInfo &DI) {
@@ -829,6 +857,9 @@ void BackendConsumer::DiagnosticHandlerImpl(const DiagnosticInfo &DI) {
   case llvm::DK_Unsupported:
     UnsupportedDiagHandler(cast<DiagnosticInfoUnsupported>(DI));
     return;
+  case llvm::DK_DontCall:
+    DontCallDiagHandler(cast<DiagnosticInfoDontCall>(DI));
+    return;
   default:
     // Plugin IDs are not bound to any value as they are set dynamically.
     ComputeDiagRemarkID(Severity, backend_plugin, DiagID);

diff  --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 0940980461cd7..87a15dbeb1b7f 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -2136,6 +2136,9 @@ void CodeGenModule::SetFunctionAttributes(GlobalDecl GD, llvm::Function *F,
   else if (const auto *SA = FD->getAttr<SectionAttr>())
      F->setSection(SA->getName());
 
+  if (FD->hasAttr<ErrorAttr>())
+    F->addFnAttr("dontcall");
+
   // If we plan on emitting this inline builtin, we can't treat it as a builtin.
   if (FD->isInlineBuiltinDeclaration()) {
     const FunctionDecl *FDBody;

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 00a46422dd339..985d3e25e173a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -2628,6 +2628,8 @@ static bool mergeDeclAttribute(Sema &S, NamedDecl *D,
     NewAttr = S.mergeDLLImportAttr(D, *ImportA);
   else if (const auto *ExportA = dyn_cast<DLLExportAttr>(Attr))
     NewAttr = S.mergeDLLExportAttr(D, *ExportA);
+  else if (const auto *EA = dyn_cast<ErrorAttr>(Attr))
+    NewAttr = S.mergeErrorAttr(D, *EA, EA->getUserDiagnostic());
   else if (const auto *FA = dyn_cast<FormatAttr>(Attr))
     NewAttr = S.mergeFormatAttr(D, *FA, FA->getType(), FA->getFormatIdx(),
                                 FA->getFirstArg());
@@ -3363,6 +3365,14 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD,
       New->dropAttr<InternalLinkageAttr>();
     }
 
+  if (auto *EA = New->getAttr<ErrorAttr>()) {
+    if (!Old->hasAttr<ErrorAttr>()) {
+      Diag(EA->getLocation(), diag::err_attribute_missing_on_first_decl) << EA;
+      Diag(Old->getLocation(), diag::note_previous_declaration);
+      New->dropAttr<ErrorAttr>();
+    }
+  }
+
   if (CheckRedeclarationModuleOwnership(New, Old))
     return true;
 

diff  --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 203180d90a4ba..e65ab97203ad7 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -947,6 +947,14 @@ static void handleEnableIfAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
     D->addAttr(::new (S.Context) EnableIfAttr(S.Context, AL, Cond, Msg));
 }
 
+static void handleErrorAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
+  StringRef NewUserDiagnostic;
+  if (!S.checkStringLiteralArgumentAttr(AL, 0, NewUserDiagnostic))
+    return;
+  if (ErrorAttr *EA = S.mergeErrorAttr(D, AL, NewUserDiagnostic))
+    D->addAttr(EA);
+}
+
 namespace {
 /// Determines if a given Expr references any of the given function's
 /// ParmVarDecls, or the function's implicit `this` parameter (if applicable).
@@ -3458,6 +3466,29 @@ static void handleInitPriorityAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
   D->addAttr(::new (S.Context) InitPriorityAttr(S.Context, AL, prioritynum));
 }
 
+ErrorAttr *Sema::mergeErrorAttr(Decl *D, const AttributeCommonInfo &CI,
+                                StringRef NewUserDiagnostic) {
+  if (const auto *EA = D->getAttr<ErrorAttr>()) {
+    std::string NewAttr = CI.getNormalizedFullName();
+    assert((NewAttr == "error" || NewAttr == "warning") &&
+           "unexpected normalized full name");
+    bool Match = (EA->isError() && NewAttr == "error") ||
+                 (EA->isWarning() && NewAttr == "warning");
+    if (!Match) {
+      Diag(EA->getLocation(), diag::err_attributes_are_not_compatible)
+          << CI << EA;
+      Diag(CI.getLoc(), diag::note_conflicting_attribute);
+      return nullptr;
+    }
+    if (EA->getUserDiagnostic() != NewUserDiagnostic) {
+      Diag(CI.getLoc(), diag::warn_duplicate_attribute) << EA;
+      Diag(EA->getLoc(), diag::note_previous_attribute);
+    }
+    D->dropAttr<ErrorAttr>();
+  }
+  return ::new (Context) ErrorAttr(Context, CI, NewUserDiagnostic);
+}
+
 FormatAttr *Sema::mergeFormatAttr(Decl *D, const AttributeCommonInfo &CI,
                                   IdentifierInfo *Format, int FormatIdx,
                                   int FirstArg) {
@@ -7979,6 +8010,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
   case ParsedAttr::AT_EnableIf:
     handleEnableIfAttr(S, D, AL);
     break;
+  case ParsedAttr::AT_Error:
+    handleErrorAttr(S, D, AL);
+    break;
   case ParsedAttr::AT_DiagnoseIf:
     handleDiagnoseIfAttr(S, D, AL);
     break;

diff  --git a/clang/test/CodeGen/attr-error.c b/clang/test/CodeGen/attr-error.c
new file mode 100644
index 0000000000000..da56793a23920
--- /dev/null
+++ b/clang/test/CodeGen/attr-error.c
@@ -0,0 +1,11 @@
+// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s
+__attribute__((error("oh no"))) void foo(void);
+
+void bar(void) {
+  foo();
+}
+
+// CHECK: call void @foo(), !srcloc [[SRCLOC:![0-9]+]]
+// CHECK: declare{{.*}} void @foo() [[ATTR:#[0-9]+]]
+// CHECK: attributes [[ATTR]] = {{{.*}}"dontcall"
+// CHECK: [[SRCLOC]] = !{i32 {{[0-9]+}}}

diff  --git a/clang/test/CodeGen/attr-warning.c b/clang/test/CodeGen/attr-warning.c
new file mode 100644
index 0000000000000..daa53b6616513
--- /dev/null
+++ b/clang/test/CodeGen/attr-warning.c
@@ -0,0 +1,11 @@
+// RUN: %clang_cc1 -emit-llvm -o - %s | FileCheck %s
+__attribute__((warning("oh no"))) void foo(void);
+
+void bar(void) {
+  foo();
+}
+
+// CHECK: call void @foo(), !srcloc [[SRCLOC:![0-9]+]]
+// CHECK: declare{{.*}} void @foo() [[ATTR:#[0-9]+]]
+// CHECK: attributes [[ATTR]] = {{{.*}}"dontcall"
+// CHECK: [[SRCLOC]] = !{i32 {{[0-9]+}}}

diff  --git a/clang/test/Frontend/backend-attribute-error-warning-optimize.c b/clang/test/Frontend/backend-attribute-error-warning-optimize.c
new file mode 100644
index 0000000000000..d3951e3b6b1f5
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-error-warning-optimize.c
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 -O2 -verify -emit-codegen-only %s
+
+__attribute__((error("oh no foo"))) void foo(void);
+
+__attribute__((error("oh no bar"))) void bar(void);
+
+int x(void) {
+  return 8 % 2 == 1;
+}
+void baz(void) {
+  foo(); // expected-error {{call to 'foo' declared with 'error' attribute: oh no foo}}
+  if (x())
+    bar();
+}
+
+// FIXME: indirect call detection not yet supported.
+void (*quux)(void);
+
+void indirect(void) {
+  quux = foo;
+  quux();
+}

diff  --git a/clang/test/Frontend/backend-attribute-error-warning.c b/clang/test/Frontend/backend-attribute-error-warning.c
new file mode 100644
index 0000000000000..4e96f771d54cb
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-error-warning.c
@@ -0,0 +1,61 @@
+// RUN: %clang_cc1 -verify=expected,enabled -emit-codegen-only %s
+// RUN: %clang_cc1 -verify=expected,enabled -emit-codegen-only %s -x c++
+// RUN: %clang_cc1 -verify -emit-codegen-only -Wno-attribute-warning %s
+// RUN: %clang_cc1 -verify -emit-codegen-only -Wno-attribute-warning %s -x c++
+
+__attribute__((error("oh no foo"))) void foo(void);
+
+__attribute__((error("oh no bar"))) void bar(void);
+
+int x(void) {
+  return 8 % 2 == 1;
+}
+
+__attribute__((warning("oh no quux"))) void quux(void);
+
+__attribute__((error("demangle me"))) void __compiletime_assert_455(void);
+
+__attribute__((error("one"), error("two"))) // expected-warning {{attribute 'error' is already applied with 
diff erent arguments}}
+void                                        // expected-note at -1 {{previous attribute is here}}
+duplicate_errors(void);
+
+__attribute__((warning("one"), warning("two"))) // expected-warning {{attribute 'warning' is already applied with 
diff erent arguments}}
+void                                            // expected-note at -1 {{previous attribute is here}}
+duplicate_warnings(void);
+
+void baz(void) {
+  foo(); // expected-error {{call to 'foo' declared with 'error' attribute: oh no foo}}
+  if (x())
+    bar(); // expected-error {{call to 'bar' declared with 'error' attribute: oh no bar}}
+
+  quux();                     // enabled-warning {{call to 'quux' declared with 'warning' attribute: oh no quux}}
+  __compiletime_assert_455(); // expected-error {{call to '__compiletime_assert_455' declared with 'error' attribute: demangle me}}
+  duplicate_errors();         // expected-error {{call to 'duplicate_errors' declared with 'error' attribute: two}}
+  duplicate_warnings();       // enabled-warning {{call to 'duplicate_warnings' declared with 'warning' attribute: two}}
+}
+
+#ifdef __cplusplus
+template <typename T>
+__attribute__((error("demangle me, too")))
+T
+nocall(T t);
+
+struct Widget {
+  __attribute__((warning("don't call me!")))
+  operator int() { return 42; }
+};
+
+void baz_cpp(void) {
+  foo(); // expected-error {{call to 'foo' declared with 'error' attribute: oh no foo}}
+  if (x())
+    bar(); // expected-error {{call to 'bar' declared with 'error' attribute: oh no bar}}
+  quux();  // enabled-warning {{call to 'quux' declared with 'warning' attribute: oh no quux}}
+
+  // Test that we demangle correctly in the diagnostic for C++.
+  __compiletime_assert_455(); // expected-error {{call to '__compiletime_assert_455' declared with 'error' attribute: demangle me}}
+  nocall<int>(42);            // expected-error {{call to 'nocall<int>' declared with 'error' attribute: demangle me, too}}
+
+  Widget W;
+  int w = W; // enabled-warning {{'operator int' declared with 'warning' attribute: don't call me!}}
+}
+#endif

diff  --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 88488d210945f..8ea6dc45c9479 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -63,6 +63,7 @@
 // CHECK-NEXT: EnforceTCB (SubjectMatchRule_function)
 // CHECK-NEXT: EnforceTCBLeaf (SubjectMatchRule_function)
 // CHECK-NEXT: EnumExtensibility (SubjectMatchRule_enum)
+// CHECK-NEXT: Error (SubjectMatchRule_function)
 // CHECK-NEXT: ExcludeFromExplicitInstantiation (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record)
 // CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable))
 // CHECK-NEXT: FlagEnum (SubjectMatchRule_enum)

diff  --git a/clang/test/Sema/attr-error.c b/clang/test/Sema/attr-error.c
new file mode 100644
index 0000000000000..581bfc43cbc06
--- /dev/null
+++ b/clang/test/Sema/attr-error.c
@@ -0,0 +1,40 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+#if !__has_attribute(error)
+#error "error attribute missing"
+#endif
+
+__attribute__((error("don't call me!"))) int good0(void);
+
+__attribute__((error)) // expected-error {{'error' attribute takes one argument}}
+int
+bad0(void);
+
+int bad1(__attribute__((error("bad1"))) int param); // expected-error {{'error' attribute only applies to functions}}
+
+int bad2(void) {
+  __attribute__((error("bad2"))); // expected-error {{'error' attribute cannot be applied to a statement}}
+}
+
+__attribute__((error(3))) // expected-error {{'error' attribute requires a string}}
+int
+bad3(void);
+
+__attribute__((error("foo"), error("foo"))) int good1(void);
+__attribute__((error("foo"))) int good1(void);
+__attribute__((error("foo"))) int good1(void) {}
+
+__attribute__((error("foo"), warning("foo"))) // expected-error {{'warning' and 'error' attributes are not compatible}}
+int
+bad4(void);
+// expected-note at -3 {{conflicting attribute is here}}
+
+__attribute__((error("foo"))) int bad5(void);   // expected-note {{conflicting attribute is here}}
+__attribute__((warning("foo"))) int bad5(void); // expected-error {{'error' and 'warning' attributes are not compatible}}
+
+/*
+ * Note: we 
diff er from GCC here; rather than support redeclarations that add
+ * or remove this fn attr, we diagnose such 
diff erences.
+ */
+
+void foo(void);                                     // expected-note {{previous declaration is here}}
+__attribute__((error("oh no foo"))) void foo(void); // expected-error {{'error' attribute does not appear on the first declaration}}

diff  --git a/clang/test/Sema/attr-warning.c b/clang/test/Sema/attr-warning.c
new file mode 100644
index 0000000000000..0973f3c8eb4bd
--- /dev/null
+++ b/clang/test/Sema/attr-warning.c
@@ -0,0 +1,37 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+#if !__has_attribute(warning)
+#warning "warning attribute missing"
+#endif
+
+__attribute__((warning("don't call me!"))) int good0(void);
+
+__attribute__((warning)) // expected-error {{'warning' attribute takes one argument}}
+int
+bad0(void);
+
+int bad1(__attribute__((warning("bad1"))) int param); // expected-error {{'warning' attribute only applies to functions}}
+
+int bad2(void) {
+  __attribute__((warning("bad2"))); // expected-error {{'warning' attribute cannot be applied to a statement}}
+}
+
+__attribute__((warning(3))) // expected-error {{'warning' attribute requires a string}}
+int
+bad3(void);
+
+__attribute__((warning("foo"), warning("foo"))) int good1(void);
+__attribute__((warning("foo"))) int good1(void);
+__attribute__((warning("foo"))) int good1(void) {}
+
+__attribute__((warning("foo"), error("foo"))) // expected-error {{'error' and 'warning' attributes are not compatible}}
+int
+bad4(void);
+// expected-note at -3 {{conflicting attribute is here}}
+
+/*
+ * Note: we 
diff er from GCC here; rather than support redeclarations that add
+ * or remove this fn attr, we diagnose such 
diff erences.
+ */
+
+void foo(void);                                       // expected-note {{previous declaration is here}}
+__attribute__((warning("oh no foo"))) void foo(void); // expected-error {{'warning' attribute does not appear on the first declaration}}

diff  --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 543783a8a90d2..ac9ad802e1485 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1594,7 +1594,12 @@ example:
     ``disable_sanitizer_instrumentation`` disables all kinds of instrumentation,
     taking precedence over the ``sanitize_<name>`` attributes and other compiler
     flags.
-
+``"dontcall"``
+    This attribute denotes that a diagnostic should be emitted when a call of a
+    function with this attribute is not eliminated via optimization. Front ends
+    can provide optional ``srcloc`` metadata nodes on call sites of such
+    callees to attach information about where in the source language such a
+    call came from.
 ``"frame-pointer"``
     This attribute tells the code generator whether the function
     should keep the frame pointer. The code generator may emit the frame pointer

diff  --git a/llvm/include/llvm/IR/DiagnosticInfo.h b/llvm/include/llvm/IR/DiagnosticInfo.h
index 5064f4f4edf77..164c43ee84bfe 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -79,6 +79,7 @@ enum DiagnosticKind {
   DK_PGOProfile,
   DK_Unsupported,
   DK_SrcMgr,
+  DK_DontCall,
   DK_FirstPluginKind // Must be last value to work with
                      // getNextAvailablePluginDiagnosticKind
 };
@@ -1070,6 +1071,22 @@ class DiagnosticInfoSrcMgr : public DiagnosticInfo {
   }
 };
 
+class DiagnosticInfoDontCall : public DiagnosticInfo {
+  StringRef CalleeName;
+  unsigned LocCookie;
+
+public:
+  DiagnosticInfoDontCall(StringRef CalleeName, unsigned LocCookie)
+      : DiagnosticInfo(DK_DontCall, DS_Error), CalleeName(CalleeName),
+        LocCookie(LocCookie) {}
+  StringRef getFunctionName() const { return CalleeName; }
+  unsigned getLocCookie() const { return LocCookie; }
+  void print(DiagnosticPrinter &DP) const override;
+  static bool classof(const DiagnosticInfo *DI) {
+    return DI->getKind() == DK_DontCall;
+  }
+};
+
 } // end namespace llvm
 
 #endif // LLVM_IR_DIAGNOSTICINFO_H

diff  --git a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
index 989caa450009e..ecfbe801fca1e 100644
--- a/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/IRTranslator.cpp
@@ -47,6 +47,7 @@
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/DebugInfo.h"
 #include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/DiagnosticInfo.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/GetElementPtrTypeIterator.h"
 #include "llvm/IR/InlineAsm.h"
@@ -2333,6 +2334,15 @@ bool IRTranslator::translateCall(const User &U, MachineIRBuilder &MIRBuilder) {
   if (CI.isInlineAsm())
     return translateInlineAsm(CI, MIRBuilder);
 
+  if (F && F->hasFnAttribute("dontcall")) {
+    unsigned LocCookie = 0;
+    if (MDNode *MD = CI.getMetadata("srcloc"))
+      LocCookie =
+          mdconst::extract<ConstantInt>(MD->getOperand(0))->getZExtValue();
+    DiagnosticInfoDontCall D(F->getName(), LocCookie);
+    F->getContext().diagnose(D);
+  }
+
   Intrinsic::ID ID = Intrinsic::not_intrinsic;
   if (F && F->isIntrinsic()) {
     ID = F->getIntrinsicID();

diff  --git a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp
index f350354075daa..084bcac0b8679 100644
--- a/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/FastISel.cpp
@@ -75,6 +75,7 @@
 #include "llvm/IR/DebugInfo.h"
 #include "llvm/IR/DebugLoc.h"
 #include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/DiagnosticInfo.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/GetElementPtrTypeIterator.h"
 #include "llvm/IR/GlobalValue.h"
@@ -1151,6 +1152,16 @@ bool FastISel::lowerCall(const CallInst *CI) {
   CLI.setCallee(RetTy, FuncTy, CI->getCalledOperand(), std::move(Args), *CI)
       .setTailCall(IsTailCall);
 
+  if (const Function *F = CI->getCalledFunction())
+    if (F->hasFnAttribute("dontcall")) {
+      unsigned LocCookie = 0;
+      if (MDNode *MD = CI->getMetadata("srcloc"))
+        LocCookie =
+            mdconst::extract<ConstantInt>(MD->getOperand(0))->getZExtValue();
+      DiagnosticInfoDontCall D(F->getName(), LocCookie);
+      F->getContext().diagnose(D);
+    }
+
   return lowerCallTo(CLI);
 }
 

diff  --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index fd31078e640a2..a067defc3757e 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -69,6 +69,7 @@
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/DiagnosticInfo.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/GetElementPtrTypeIterator.h"
 #include "llvm/IR/InlineAsm.h"
@@ -7952,6 +7953,15 @@ void SelectionDAGBuilder::visitCall(const CallInst &I) {
   }
 
   if (Function *F = I.getCalledFunction()) {
+    if (F->hasFnAttribute("dontcall")) {
+      unsigned LocCookie = 0;
+      if (MDNode *MD = I.getMetadata("srcloc"))
+        LocCookie =
+            mdconst::extract<ConstantInt>(MD->getOperand(0))->getZExtValue();
+      DiagnosticInfoDontCall D(F->getName(), LocCookie);
+      DAG.getContext()->diagnose(D);
+    }
+
     if (F->isDeclaration()) {
       // Is this an LLVM intrinsic or a target-specific intrinsic?
       unsigned IID = F->getIntrinsicID();

diff  --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index f921382748018..792ae3ed3085a 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -401,3 +401,7 @@ std::string DiagnosticInfoOptimizationBase::getMsg() const {
 
 void OptimizationRemarkAnalysisFPCommute::anchor() {}
 void OptimizationRemarkAnalysisAliasing::anchor() {}
+
+void DiagnosticInfoDontCall::print(DiagnosticPrinter &DP) const {
+  DP << "call to " << getFunctionName() << " marked \"dontcall\"";
+}

diff  --git a/llvm/test/CodeGen/X86/attr-dontcall.ll b/llvm/test/CodeGen/X86/attr-dontcall.ll
new file mode 100644
index 0000000000000..c0104fa7e4a9f
--- /dev/null
+++ b/llvm/test/CodeGen/X86/attr-dontcall.ll
@@ -0,0 +1,11 @@
+; RUN: not llc -global-isel=0 -fast-isel=0 -stop-after=finalize-isel %s 2>&1 | FileCheck %s
+; RUN: not llc -global-isel=0 -fast-isel=1 -stop-after=finalize-isel %s 2>&1 | FileCheck %s
+; RUN: not llc -global-isel=1 -fast-isel=0 -stop-after=irtranslator %s 2>&1 | FileCheck %s
+
+declare void @foo() "dontcall"
+define void @bar() {
+  call void @foo()
+  ret void
+}
+
+; CHECK: error: call to foo marked "dontcall"

diff  --git a/llvm/test/ThinLTO/X86/dontcall.ll b/llvm/test/ThinLTO/X86/dontcall.ll
new file mode 100644
index 0000000000000..ef5eae9b4b829
--- /dev/null
+++ b/llvm/test/ThinLTO/X86/dontcall.ll
@@ -0,0 +1,33 @@
+; RUN: split-file %s %t
+; RUN: opt -module-summary %t/a.s -o %t/a.bc
+; RUN: opt -module-summary %t/b.s -o %t/b.bc
+; RUN: not llvm-lto2 run %t/a.bc %t/b.bc -o %t/out -save-temps 2>&1 \
+; RUN:   -r=%t/a.bc,callee,px \
+; RUN:   -r=%t/b.bc,callee,x  \
+; RUN:   -r=%t/b.bc,caller,px
+
+; TODO: As part of LTO, we check that types match, but *we don't yet check that
+; attributes match!!! What should happen if we remove "dontcall" from the
+; definition or declaration of @callee?
+
+; CHECK: call to callee marked "dontcall"
+
+;--- a.s
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+define i32 @callee() "dontcall" noinline {
+  ret i32 42
+}
+
+;--- b.s
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+declare i32 @callee() "dontcall"
+
+define i32 @caller() {
+entry:
+  %0 = call i32 @callee()
+  ret i32 %0
+}


        


More information about the cfe-commits mailing list