- 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
-----------------------------
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];
+}
- ``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
+ }
+ }];
+}
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<
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">;
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);
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
const llvm::OptimizationRemarkAnalysisAliasing &D);
void OptimizationFailureHandler(
const llvm::DiagnosticInfoOptimizationFailure &D);
+ void DontCallDiagHandler(const DiagnosticInfoDontCall &D);
};
void BackendConsumer::anchor() {}
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) {
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);
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;
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());
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;
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).
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) {
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;
--- /dev/null
+// 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]+}}}
--- /dev/null
+// 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]+}}}
--- /dev/null
+// 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();
+}
--- /dev/null
+// 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 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}}
+
+ 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
// 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)
--- /dev/null
+// 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}}
+
+__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 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}}
--- /dev/null
+// 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}}
``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
DK_PGOProfile,
DK_Unsupported,
DK_SrcMgr,
+ DK_DontCall,
DK_FirstPluginKind // Must be last value to work with
// getNextAvailablePluginDiagnosticKind
};
}
};
+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
#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"
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();
#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"
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);
}
#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"
}
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();
void OptimizationRemarkAnalysisFPCommute::anchor() {}
void OptimizationRemarkAnalysisAliasing::anchor() {}
+
+void DiagnosticInfoDontCall::print(DiagnosticPrinter &DP) const {
+ DP << "call to " << getFunctionName() << " marked \"dontcall\"";
+}
--- /dev/null
+; 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"
--- /dev/null
+; 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
+}