diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -94,7 +94,8 @@ C Language Changes in Clang --------------------------- -- ... +- 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 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -3823,3 +3823,12 @@ let Documentation = [EnforceTCBLeafDocs]; bit InheritEvenIfAlreadyPresent = 1; } + +def Error : Attr { + 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 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -6045,3 +6045,29 @@ - ``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 --- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td +++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td @@ -72,6 +72,12 @@ 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; + 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 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1213,6 +1213,7 @@ 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/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -5329,6 +5329,15 @@ TargetDecl->hasAttr()) getDebugInfo()->addHeapAllocSiteMetadata(CI, RetTy->getPointeeType(), Loc); + // Add metadata if calling an __attribute__((error(""))) or warning fn. + if (TargetDecl && TargetDecl->hasAttr()) { + 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 --- a/clang/lib/CodeGen/CodeGenAction.cpp +++ b/clang/lib/CodeGen/CodeGenAction.cpp @@ -401,6 +401,7 @@ const llvm::OptimizationRemarkAnalysisAliasing &D); void OptimizationFailureHandler( const llvm::DiagnosticInfoOptimizationFailure &D); + void DontCallDiagHandler(const DiagnosticInfoDontCall &D); }; void BackendConsumer::anchor() {} @@ -758,6 +759,33 @@ 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(DE)) { + assert(FD->hasAttr() && + "expected error or warning function attribute"); + + if (const auto *EA = FD->getAttr()) { + 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->getDeclName() << 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) { @@ -833,6 +861,9 @@ case llvm::DK_Unsupported: UnsupportedDiagHandler(cast(DI)); return; + case llvm::DK_DontCall: + DontCallDiagHandler(cast(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 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -2136,6 +2136,9 @@ else if (const auto *SA = FD->getAttr()) F->setSection(SA->getName()); + if (FD->hasAttr()) + 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 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -3361,6 +3361,13 @@ New->dropAttr(); } + if (auto *EA = New->getAttr()) + if (!Old->hasAttr()) { + Diag(EA->getLocation(), diag::err_attribute_missing_on_first_decl) << EA; + Diag(Old->getLocation(), diag::note_previous_declaration); + New->dropAttr(); + } + if (CheckRedeclarationModuleOwnership(New, Old)) return true; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -947,6 +947,36 @@ D->addAttr(::new (S.Context) EnableIfAttr(S.Context, AL, Cond, Msg)); } +static void handleErrorAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + StringRef Str; + if (!S.checkStringLiteralArgumentAttr(AL, 0, Str)) + return; + + if (const auto *EA = D->getAttr()) { + unsigned NewAttrSpellingIndex = AL.getAttributeSpellingListIndex(); + + static_assert(ErrorAttr::GNU_error < ErrorAttr::GNU_warning, + "Spelling order changed unexpectedly"); + assert(NewAttrSpellingIndex != ErrorAttr::SpellingNotCalculated && + "Spelling not calculated"); + + bool Match = + (EA->isError() && NewAttrSpellingIndex < ErrorAttr::GNU_warning) || + (EA->isWarning() && NewAttrSpellingIndex >= ErrorAttr::GNU_warning); + if (!Match) { + S.Diag(AL.getLoc(), diag::err_attributes_are_not_compatible) << AL << EA; + S.Diag(EA->getLocation(), diag::note_conflicting_attribute); + return; + } + if (EA->getUserDiagnostic() != Str) { + S.Diag(AL.getLoc(), diag::warn_duplicate_attribute) << EA; + S.Diag(EA->getLoc(), diag::note_previous_attribute); + } + D->dropAttr(); + } + D->addAttr(::new (S.Context) ErrorAttr(S.Context, AL, Str)); +} + namespace { /// Determines if a given Expr references any of the given function's /// ParmVarDecls, or the function's implicit `this` parameter (if applicable). @@ -7952,6 +7982,9 @@ 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 --- /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 --- /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 --- /dev/null +++ b/clang/test/Frontend/backend-attribute-error-warning-optimize.c @@ -0,0 +1,23 @@ +// REQUIRES: x86-registered-target +// 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 --- /dev/null +++ b/clang/test/Frontend/backend-attribute-error-warning.c @@ -0,0 +1,44 @@ +// REQUIRES: x86-registered-target +// RUN: %clang_cc1 -verify -emit-codegen-only %s +// RUN: %clang_cc1 -verify -emit-codegen-only -Wno-attribute-warning %s -DWARNINGS_DISABLED + +__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 different arguments}} +void // expected-note@-1 {{previous attribute is here}} +duplicate_errors(void); + +__attribute__((warning("one"), warning("two"))) // expected-warning {{attribute 'warning' is already applied with different arguments}} +void // expected-note@-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}} + +#ifdef WARNINGS_DISABLED + quux(); +#else + quux(); // expected-warning {{call to 'quux' declared with 'warning' attribute: oh no quux}} +#endif + + __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}} + +#ifdef WARNINGS_DISABLED + duplicate_warnings(); +#else + duplicate_warnings(); // expected-warning {{call to 'duplicate_warnings' declared with 'warning' attribute: two}} +#endif +} diff --git a/clang/test/Frontend/backend-attribute-error-warning.cpp b/clang/test/Frontend/backend-attribute-error-warning.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Frontend/backend-attribute-error-warning.cpp @@ -0,0 +1,48 @@ +// REQUIRES: x86-registered-target +// RUN: %clang_cc1 -verify -emit-codegen-only %s +// RUN: %clang_cc1 -verify -emit-codegen-only -Wno-attribute-warning %s -DWARNINGS_DISABLED + +__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); + +template +__attribute__((error("demangle me, too"))) +T +nocall(T t); + +struct Widget { + __attribute__((warning("don't call me!"))) + operator int() { return 42; } +}; + +void baz(void) { + // Test that we demangle correctly in the diagnostic for C++. + 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}} + +#ifdef WARNINGS_DISABLED + quux(); +#else + quux(); // expected-warning {{call to 'quux' declared with 'warning' attribute: oh no quux}} +#endif + + __compiletime_assert_455(); // expected-error {{call to '__compiletime_assert_455' declared with 'error' attribute: demangle me}} + nocall(42); // expected-error {{call to 'nocall' declared with 'error' attribute: demangle me, too}} + + Widget W; +#ifdef WARNINGS_DISABLED + int w = W; +#else + int w = W; // expected-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 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -61,6 +61,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/Misc/serialized-diags-driver.c b/clang/test/Misc/serialized-diags-driver.c --- a/clang/test/Misc/serialized-diags-driver.c +++ b/clang/test/Misc/serialized-diags-driver.c @@ -8,7 +8,7 @@ // RUN: %clang -Wx-typoed-warning -Wall -fsyntax-only --serialize-diagnostics %t.diag %s // RUN: c-index-test -read-diagnostics %t.diag 2>&1 | FileCheck %s -// CHECK: warning: unknown warning option '-Wx-typoed-warning' [-Wunknown-warning-option] [] +// CHECK: warning: unknown warning option '-Wx-typoed-warning'; did you mean '-Wattribute-warning'? [-Wunknown-warning-option] [] // CHECK: warning: variable 'voodoo' is uninitialized when used here [-Wuninitialized] // CHECK: note: initialize the variable 'voodoo' to silence this warning [] diff --git a/clang/test/Sema/attr-error.c b/clang/test/Sema/attr-error.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-error.c @@ -0,0 +1,37 @@ +// 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@-3 {{conflicting attribute is here}} + +/* + * Note: we differ from GCC here; rather than support redeclarations that add + * or remove this fn attr, we diagnose such differences. + */ + +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 --- /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@-3 {{conflicting attribute is here}} + +/* + * Note: we differ from GCC here; rather than support redeclarations that add + * or remove this fn attr, we diagnose such differences. + */ + +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 --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -1582,6 +1582,12 @@ can prove that the function does not execute any convergent operations. Similarly, the optimizer may remove ``convergent`` on calls/invokes when it can prove that the call/invoke cannot call a convergent function. +``"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 --- a/llvm/include/llvm/IR/DiagnosticInfo.h +++ b/llvm/include/llvm/IR/DiagnosticInfo.h @@ -79,6 +79,7 @@ DK_PGOProfile, DK_Unsupported, DK_SrcMgr, + DK_DontCall, DK_FirstPluginKind // Must be last value to work with // getNextAvailablePluginDiagnosticKind }; @@ -1070,6 +1071,22 @@ } }; +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 --- 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" @@ -2321,6 +2322,15 @@ if (CI.isInlineAsm()) return translateInlineAsm(CI, MIRBuilder); + if (F && F->hasFnAttribute("dontcall")) { + unsigned LocCookie = 0; + if (MDNode *MD = CI.getMetadata("srcloc")) + LocCookie = + mdconst::extract(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 --- 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 @@ 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(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 --- 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" @@ -7948,6 +7949,15 @@ } if (Function *F = I.getCalledFunction()) { + if (F->hasFnAttribute("dontcall")) { + unsigned LocCookie = 0; + if (MDNode *MD = I.getMetadata("srcloc")) + LocCookie = + mdconst::extract(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 --- a/llvm/lib/IR/DiagnosticInfo.cpp +++ b/llvm/lib/IR/DiagnosticInfo.cpp @@ -401,3 +401,7 @@ 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 --- /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 --- /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 +}