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 @@ -90,6 +90,8 @@ def warn_fe_backend_warning_attr : Warning<"call to '%0' declared with 'warning' attribute: %1">, BackendInfo, InGroup; +def note_fe_backend_in : Note<"called by function '%0'">; +def note_fe_backend_inlined : Note<"inlined by function '%0'">; def err_fe_invalid_code_complete_file : Error< "cannot locate code-completion file %0">, DefaultFatal; 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 @@ -50,6 +50,8 @@ #include #include +#include + using namespace clang; using namespace llvm; @@ -854,6 +856,14 @@ ? diag::err_fe_backend_error_attr : diag::warn_fe_backend_warning_attr) << llvm::demangle(D.getFunctionName().str()) << D.getNote(); + + SmallVector InliningDecisions; + D.getInliningDecisions(InliningDecisions); + InliningDecisions.push_back(D.getCaller().str()); + for (auto Dec : llvm::enumerate(InliningDecisions)) + Diags.Report(Dec.index() ? diag::note_fe_backend_inlined + : diag::note_fe_backend_in) + << llvm::demangle(Dec.value()); } void BackendConsumer::MisExpectDiagHandler( diff --git a/clang/test/Frontend/backend-attribute-error-warning-optimize.c b/clang/test/Frontend/backend-attribute-error-warning-optimize.c --- a/clang/test/Frontend/backend-attribute-error-warning-optimize.c +++ b/clang/test/Frontend/backend-attribute-error-warning-optimize.c @@ -9,6 +9,7 @@ } void baz(void) { foo(); // expected-error {{call to 'foo' declared with 'error' attribute: oh no foo}} + // expected-note@* {{called by function 'baz'}} if (x()) bar(); } @@ -20,3 +21,23 @@ quux = foo; quux(); } + +static inline void a(int x) { + if (x == 10) + foo(); // expected-error {{call to 'foo' declared with 'error' attribute: oh no foo}} + // expected-note@* {{called by function 'a'}} + // expected-note@* {{inlined by function 'b'}} + // expected-note@* {{inlined by function 'd'}} +} + +static inline void b() { + a(10); +} + +void c() { + a(9); +} + +void d() { + b(); +} diff --git a/clang/test/Frontend/backend-attribute-error-warning.c b/clang/test/Frontend/backend-attribute-error-warning.c --- a/clang/test/Frontend/backend-attribute-error-warning.c +++ b/clang/test/Frontend/backend-attribute-error-warning.c @@ -23,11 +23,17 @@ void baz(void) { foo(); // expected-error {{call to 'foo' declared with 'error' attribute: oh no foo}} + // expected-note@* {{called by function 'baz'}} if (x()) bar(); // expected-error {{call to 'bar' declared with 'error' attribute: oh no bar}} + // expected-note@* {{called by function 'baz'}} quux(); // enabled-warning {{call to 'quux' declared with 'warning' attribute: oh no quux}} + // enabled-note@* {{called by function 'baz'}} __compiletime_assert_455(); // expected-error {{call to '__compiletime_assert_455' declared with 'error' attribute: demangle me}} + // expected-note@* {{called by function 'baz'}} duplicate_errors(); // expected-error {{call to 'duplicate_errors' declared with 'error' attribute: two}} + // expected-note@* {{called by function 'baz'}} duplicate_warnings(); // enabled-warning {{call to 'duplicate_warnings' declared with 'warning' attribute: two}} + // enabled-note@* {{called by function 'baz'}} } diff --git a/clang/test/Frontend/backend-attribute-error-warning.cpp b/clang/test/Frontend/backend-attribute-error-warning.cpp --- a/clang/test/Frontend/backend-attribute-error-warning.cpp +++ b/clang/test/Frontend/backend-attribute-error-warning.cpp @@ -24,13 +24,19 @@ void baz(void) { foo(); // expected-error {{call to 'foo()' declared with 'error' attribute: oh no foo}} + // expected-note@* {{called by function 'baz()'}} if (x()) bar(); // expected-error {{call to 'bar()' declared with 'error' attribute: oh no bar}} + // expected-note@* {{called by function 'baz()'}} quux(); // enabled-warning {{call to 'quux()' declared with 'warning' attribute: oh no quux}} + // enabled-note@* {{called by function 'baz()'}} __compiletime_assert_455(); // expected-error {{call to '__compiletime_assert_455()' declared with 'error' attribute: demangle me}} + // expected-note@* {{called by function 'baz()'}} duplicate_errors(); // expected-error {{call to 'duplicate_errors()' declared with 'error' attribute: two}} + // expected-note@* {{called by function 'baz()'}} duplicate_warnings(); // enabled-warning {{call to 'duplicate_warnings()' declared with 'warning' attribute: two}} + // enabled-note@* {{called by function 'baz()'}} } #ifdef __cplusplus @@ -46,15 +52,21 @@ void baz_cpp(void) { foo(); // expected-error {{call to 'foo()' declared with 'error' attribute: oh no foo}} + // expected-note@* {{called by function 'baz_cpp()'}} if (x()) bar(); // expected-error {{call to 'bar()' declared with 'error' attribute: oh no bar}} + // expected-note@* {{called by function 'baz_cpp()'}} quux(); // enabled-warning {{call to 'quux()' declared with 'warning' attribute: oh no quux}} + // enabled-note@* {{called by function 'baz_cpp()'}} // 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}} + // expected-note@* {{called by function 'baz_cpp()'}} nocall(42); // expected-error {{call to 'int nocall(int)' declared with 'error' attribute: demangle me, too}} + // expected-note@* {{called by function 'baz_cpp()'}} Widget W; int w = W; // enabled-warning {{call to 'Widget::operator int()' declared with 'warning' attribute: don't call me!}} + // enabled-note@* {{called by function 'baz_cpp()'}} } #endif diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -1774,13 +1774,17 @@ 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. A string value can be provided as a note. + call came from. A string value can be provided as a note. The optimizer may + add the optional ``inlined.from`` metadata to call sites which front ends + might consume to display more precise diagnostics. ``"dontcall-warn"`` This attribute denotes that a warning 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. A string value can be provided as a note. + call came from. A string value can be provided as a note. The optimizer may + add the optional ``inlined.from`` metadata to call sites which front ends + might consume to display more precise diagnostics. ``fn_ret_thunk_extern`` This attribute tells the code generator that returns from functions should be replaced with jumps to externally-defined architecture-specific symbols. 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 @@ -1099,15 +1099,19 @@ void diagnoseDontCall(const CallInst &CI); class DiagnosticInfoDontCall : public DiagnosticInfo { + StringRef CallerName; StringRef CalleeName; StringRef Note; unsigned LocCookie; + MDNode *MDN; public: - DiagnosticInfoDontCall(StringRef CalleeName, StringRef Note, - DiagnosticSeverity DS, unsigned LocCookie) - : DiagnosticInfo(DK_DontCall, DS), CalleeName(CalleeName), Note(Note), - LocCookie(LocCookie) {} + DiagnosticInfoDontCall(StringRef CallerName, StringRef CalleeName, + StringRef Note, DiagnosticSeverity DS, + unsigned LocCookie, MDNode *MDN) + : DiagnosticInfo(DK_DontCall, DS), CallerName(CallerName), + CalleeName(CalleeName), Note(Note), LocCookie(LocCookie), MDN(MDN) {} + StringRef getCaller() const { return CallerName; } StringRef getFunctionName() const { return CalleeName; } StringRef getNote() const { return Note; } unsigned getLocCookie() const { return LocCookie; } @@ -1115,6 +1119,8 @@ static bool classof(const DiagnosticInfo *DI) { return DI->getKind() == DK_DontCall; } + void + getInliningDecisions(SmallVectorImpl &InliningDecisions) const; }; } // end namespace llvm 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 @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "llvm/IR/DiagnosticInfo.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/Twine.h" #include "llvm/ADT/iterator_range.h" @@ -433,8 +434,9 @@ if (MDNode *MD = CI.getMetadata("srcloc")) LocCookie = mdconst::extract(MD->getOperand(0))->getZExtValue(); - DiagnosticInfoDontCall D(F->getName(), A.getValueAsString(), Sev, - LocCookie); + DiagnosticInfoDontCall D(CI.getParent()->getParent()->getName(), + F->getName(), A.getValueAsString(), Sev, + LocCookie, CI.getMetadata("inlined.from")); F->getContext().diagnose(D); } } @@ -450,3 +452,20 @@ if (!getNote().empty()) DP << ": " << getNote(); } + +void DiagnosticInfoDontCall::getInliningDecisions( + SmallVectorImpl &InliningDecisions) const { + if (!MDN) + return; + + const MDOperand &MO = MDN->getOperand(0); + if (auto *MDT = dyn_cast(MO)) { + for (const MDOperand &MO : MDT->operands()) { + if (auto *S = dyn_cast(MO)) { + InliningDecisions.push_back(S->getString().str()); + } + } + } else if (auto *S = dyn_cast(MO)) { + InliningDecisions.push_back(S->getString().str()); + } +} diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp --- a/llvm/lib/Transforms/Utils/InlineFunction.cpp +++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp @@ -2388,6 +2388,19 @@ // inlining, commonly when the callee is an intrinsic. if (MarkNoUnwind && !CI->doesNotThrow()) CI->setDoesNotThrow(); + + const Function *Callee = CI->getCalledFunction(); + if (Callee && (Callee->hasFnAttribute("dontcall-error") || + Callee->hasFnAttribute("dontcall-warn"))) { + Metadata *MD = MDString::get(CI->getContext(), CalledFunc->getName()); + if (MDNode *N = CI->getMetadata("inlined.from")) { + TempMDTuple Temp = cast(N)->clone(); + Temp->push_back(MD); + MD = MDNode::replaceWithUniqued(std::move(Temp)); + } + MDTuple *MDT = MDNode::get(CI->getContext(), {MD}); + CI->setMetadata("inlined.from", MDT); + } } } } diff --git a/llvm/test/Transforms/Inline/dontcall-attributes.ll b/llvm/test/Transforms/Inline/dontcall-attributes.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/dontcall-attributes.ll @@ -0,0 +1,84 @@ +; RUN: opt -S -o - -passes=inline %s \ +; RUN: | FileCheck %s --check-prefixes=CHECK-BOTH,CHECK +; RUN: opt -S -o - -passes=always-inline %s \ +; RUN: | FileCheck %s --check-prefixes=CHECK-BOTH,CHECK-ALWAYS + +declare void @foo() "dontcall-warn"="oh no" +declare void @fof() "dontcall-error"="oh no" + +define void @bar(i32 %x) { + %cmp = icmp eq i32 %x, 10 + br i1 %cmp, label %if.then, label %if.end + +if.then: + call void @foo() + br label %if.end + +if.end: + ret void +} + +define void @quux() { + call void @bar(i32 9) + ret void +} + +; Test that @baz's call to @foo has metadata with inlining info. +define void @baz() { +; CHECK-LABEL: @baz( +; CHECK-NEXT: call void @foo(), !inlined.from !0 +; + call void @bar(i32 10) + ret void +} + +; Test that @zing's call to @foo has unique metadata from @baz's call to @foo. +define void @zing() { +; CHECK-LABEL: @zing( +; CHECK-NEXT: call void @foo(), !inlined.from !1 +; + call void @baz() + ret void +} + +; Same test but @fof has fn attr "dontcall-error"="..." rather than +; "dontcall-warn"="...". +define void @a() { + call void @fof() + ret void +} +define void @b() { +; CHECK-LABEL: @b( +; CHECK-NEXT: call void @fof(), !inlined.from !3 + call void @a() + ret void +} + +; Add some tests for alwaysinline. +define void @always_callee() alwaysinline { + call void @fof() + ret void +} +define void @always_caller() alwaysinline { +; CHECK-BOTH-LABEL: @always_caller( +; CHECK-NEXT: call void @fof(), !inlined.from !4 +; CHECK-ALWAYS-NEXT: call void @fof(), !inlined.from !0 + call void @always_callee() + ret void +} +define void @always_caller2() alwaysinline { +; CHECK-BOTH-LABEL: @always_caller2( +; CHECK-NEXT: call void @fof(), !inlined.from !5 +; CHECK-ALWAYS-NEXT: call void @fof(), !inlined.from !1 + call void @always_caller() + ret void +} + +; CHECK: !0 = !{!"bar"} +; CHECK-NEXT: !1 = !{!2} +; CHECK-NEXT: !2 = !{!"bar", !"baz"} +; CHECK-NEXT: !3 = !{!"a"} +; CHECK-NEXT: !4 = !{!"always_callee"} +; CHECK-ALWAYS: !0 = !{!"always_callee"} +; CHECK-ALWAYS-NEXT: !1 = !{!2} +; CHECK-ALWAYS-NEXT: !2 = !{!"always_callee", !"always_caller"}