diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -1740,6 +1740,8 @@ ` instead of builtins, in order to reduce the number of builtins that we need to implement. +.. _langext-__builtin_assume: + ``__builtin_assume`` ------------------------------ 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 @@ -3477,6 +3477,14 @@ }]; } +def Assumption : InheritableAttr { + let Spellings = [Clang<"assume">]; + let Subjects = SubjectList<[Function]>; + let InheritEvenIfAlreadyPresent = 1; + let Documentation = [AssumptionDocs]; + let Args = [StringArgument<"Assumption">]; +} + def InternalLinkage : InheritableAttr { let Spellings = [Clang<"internal_linkage">]; let Subjects = SubjectList<[Var, Function, CXXRecord]>; 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 @@ -3919,6 +3919,38 @@ }]; } +def AssumptionDocs : Documentation { + let Category = DocCatFunction; + let Heading = "assume"; + let Content = [{ +Clang supports the ``__attribute__((assume("assumption")))`` attribute to +provide additional information to the optimizer. The string-literal, here +"assumption", will be attached to the function declaration such that later +analysis and optimization passes can assume the "assumption" to hold. +This is similar to :ref:`__builtin_assume ` but instead of an +expression that can be assumed to be non-zero, the assumption is expressed as +a string and it holds for the entire function. + +A function can have multiple assume attributes and they propagate from prior +declarations to later definitions. Multiple assumptions are aggregated into a +single comma separated string. Thus, one can provide multiple assumptions via +a comma separated string, i.a., +``__attribute__((assume("assumption1,assumption2")))``. + +While LLVM plugins might utilize others assumption strings as well, the LLVM +optimization passes are aware of the following assumptions: + + .. code-block:: none + + "omp_no_openmp" + "omp_no_openmp_routines" + "omp_no_parallelism" + +The OpenMP standard defines the meaning of OpenMP assumptions ("omp_XYZ"). + +}]; +} + def NoStackProtectorDocs : Documentation { let Category = DocCatFunction; let Content = [{ 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 @@ -19,6 +19,7 @@ def ODR : DiagGroup<"odr">; def : DiagGroup<"abi">; def AbsoluteValue : DiagGroup<"absolute-value">; +def Assumption : DiagGroup<"assumption">; def AddressOfTemporary : DiagGroup<"address-of-temporary">; def : DiagGroup<"aggregate-return">; def GNUAlignofExpression : DiagGroup<"gnu-alignof-expression">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -739,7 +739,14 @@ def warn_assume_side_effects : Warning< "the argument to %0 has side effects that will be discarded">, - InGroup>; + InGroup; +def warn_assume_attribute_string_unknown : Warning< + "the assumption string '%0' is not known and might be misspelled">, + InGroup; +def warn_assume_attribute_string_unknown_suggested : Warning< + "the assumption string '%0' is not known and might be misspelled; " + "did you mean '%1'?">, + InGroup; def warn_builtin_chk_overflow : Warning< "'%0' will always overflow; destination buffer has size %1," diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -58,9 +58,10 @@ #include "llvm/ADT/Optional.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallBitVector.h" -#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSet.h" #include "llvm/ADT/TinyPtrVector.h" #include "llvm/Frontend/OpenMP/OMPConstants.h" #include @@ -9998,6 +9999,13 @@ void AddAnnotationAttr(Decl *D, const AttributeCommonInfo &CI, StringRef Annot, MutableArrayRef Args); + /// A set of known assumption strings that are accepted without warning and + /// which can be recommended as typo correction. + static llvm::StringSet<> KnownAssumptionStrings; + + /// Check if \p AssumptionStr is a known assumption and warn if not. + void CheckAssumptionAttr(SourceLocation Loc, StringRef AssumptionStr); + /// AddLaunchBoundsAttr - Adds a launch_bounds attribute to a particular /// declaration. void AddLaunchBoundsAttr(Decl *D, const AttributeCommonInfo &CI, 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 @@ -2016,6 +2016,18 @@ llvm::toStringRef(CodeGenOpts.UniformWGSize)); } } + + std::string AssumptionValueStr; + for (AssumptionAttr *AssumptionA : + TargetDecl->specific_attrs()) { + std::string AS = AssumptionA->getAssumption().str(); + if (!AS.empty() && !AssumptionValueStr.empty()) + AssumptionValueStr += ","; + AssumptionValueStr += AS; + } + + if (!AssumptionValueStr.empty()) + FuncAttrs.addAttribute("llvm.assume", AssumptionValueStr); } // Attach "no-builtins" attributes to: 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 @@ -1675,6 +1675,45 @@ D->addAttr(::new (Context) AllocAlignAttr(Context, CI, Idx)); } +static void handleAssumumptionAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + // Handle the case where the attribute has a text message. + StringRef Str; + if (AL.getNumArgs() == 1 && !S.checkStringLiteralArgumentAttr(AL, 0, Str)) + return; + + S.CheckAssumptionAttr(AL.getLoc(), Str); + + D->addAttr(::new (S.Context) AssumptionAttr(S.Context, AL, Str)); +} + +llvm::StringSet<> Sema::KnownAssumptionStrings({ + "omp_no_openmp", // OpenMP 5.1 + "omp_no_openmp_routines", // OpenMP 5.1 + "omp_no_parallelism", // OpenMP 5.1 +}); + +void Sema::CheckAssumptionAttr(SourceLocation Loc, StringRef AssumptionStr) { + if (KnownAssumptionStrings.count(AssumptionStr)) + return; + + unsigned BestEditDistance = 3; + StringRef Suggestion; + for (const auto &KnownAssumptionIt : KnownAssumptionStrings) { + unsigned EditDistance = + AssumptionStr.edit_distance(KnownAssumptionIt.getKey()); + if (EditDistance < BestEditDistance) { + Suggestion = KnownAssumptionIt.getKey(); + BestEditDistance = EditDistance; + } + } + + if (!Suggestion.empty()) + Diag(Loc, diag::warn_assume_attribute_string_unknown_suggested) + << AssumptionStr << Suggestion; + else + Diag(Loc, diag::warn_assume_attribute_string_unknown) << AssumptionStr; +} + /// Normalize the attribute, __foo__ becomes foo. /// Returns true if normalization was applied. static bool normalizeName(StringRef &AttrName) { @@ -7722,6 +7761,9 @@ case ParsedAttr::AT_Unavailable: handleAttrWithMessage(S, D, AL); break; + case ParsedAttr::AT_Assumption: + handleAssumumptionAttr(S, D, AL); + break; case ParsedAttr::AT_ObjCDirect: handleObjCDirectAttr(S, D, AL); break; diff --git a/clang/test/CodeGen/assume_attr.c b/clang/test/CodeGen/assume_attr.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/assume_attr.c @@ -0,0 +1,58 @@ +// RUN: %clang_cc1 -emit-llvm -triple i386-linux-gnu %s -o - | FileCheck %s +// RUN: %clang_cc1 -x c -emit-pch -o %t %s +// RUN: %clang_cc1 -include-pch %t %s -emit-llvm -o - | FileCheck %s + +// TODO: for "foo" and "bar", "after" is not added as it appears "after" the first use or definition respectively. There might be a way to allow that. + +// CHECK: define{{.*}} void @bar() #0 +// CHECK: define{{.*}} void @baz() #1 +// CHECK: declare{{.*}} void @foo() #2 +// CHECK: attributes #0 +// CHECK-SAME: "llvm.assume"="bar:before1,bar:before2,bar:before3,bar:def1,bar:def2" +// CHECK: attributes #1 +// CHECK-SAME: "llvm.assume"="baz:before1,baz:before2,baz:before3,baz:def1,baz:def2,baz:after" +// CHECK: attributes #2 +// CHECK-SAME: "llvm.assume"="foo:before1,foo:before2,foo:before3" + +#ifndef HEADER +#define HEADER + +/// foo: declarations only + +__attribute__((assume("foo:before1"))) void foo(void); + +__attribute__((assume("foo:before2"))) +__attribute__((assume("foo:before3"))) void +foo(void); + +/// baz: static function declarations and a definition + +__attribute__((assume("baz:before1"))) static void baz(void); + +__attribute__((assume("baz:before2"))) +__attribute__((assume("baz:before3"))) static void +baz(void); + +// Definition +__attribute__((assume("baz:def1,baz:def2"))) static void baz(void) { foo(); } + +__attribute__((assume("baz:after"))) static void baz(void); + +/// bar: external function declarations and a definition + +__attribute__((assume("bar:before1"))) void bar(void); + +__attribute__((assume("bar:before2"))) +__attribute__((assume("bar:before3"))) void +bar(void); + +// Definition +__attribute__((assume("bar:def1,bar:def2"))) void bar(void) { baz(); } + +__attribute__((assume("bar:after"))) void bar(void); + +/// back to foo + +__attribute__((assume("foo:after"))) void foo(void); + +#endif diff --git a/clang/test/CodeGenCXX/assume_attr.cpp b/clang/test/CodeGenCXX/assume_attr.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/assume_attr.cpp @@ -0,0 +1,120 @@ +// RUN: %clang_cc1 -emit-llvm -triple i386-linux-gnu %s -o - | FileCheck %s +// RUN: %clang_cc1 -x c++ -emit-pch -triple i386-linux-gnu -o %t %s +// RUN: %clang_cc1 -include-pch %t %s -triple i386-linux-gnu -emit-llvm -o - | FileCheck %s +// expected-no-diagnostics + +#ifndef HEADER +#define HEADER + +/// foo: declarations only + +__attribute__((assume("foo:before1"))) void foo(); + +__attribute__((assume("foo:before2"))) +__attribute__((assume("foo:before3"))) void +foo(); + +/// baz: static function declarations and a definition + +__attribute__((assume("baz:before1"))) static void baz(); + +__attribute__((assume("baz:before2"))) +__attribute__((assume("baz:before3"))) static void +baz(); + +// Definition +__attribute__((assume("baz:def1,baz:def2"))) static void baz() { foo(); } + +__attribute__((assume("baz:after"))) static void baz(); + +/// bar: external function declarations and a definition + +__attribute__((assume("bar:before1"))) void bar(); + +__attribute__((assume("bar:before2"))) +__attribute__((assume("bar:before3"))) void +bar(); + +// Definition +__attribute__((assume("bar:def1,bar:def2"))) void bar() { baz(); } + +__attribute__((assume("bar:after"))) void bar(); + +/// back to foo + +__attribute__((assume("foo:after"))) void foo(); + +/// class tests +class C { + __attribute__((assume("C:private_method"))) void private_method(); + __attribute__((assume("C:private_static"))) static void private_static(); + +public: + __attribute__((assume("C:public_method1"))) void public_method(); + __attribute__((assume("C:public_static1"))) static void public_static(); +}; + +__attribute__((assume("C:public_method2"))) void C::public_method() { + private_method(); +} + +__attribute__((assume("C:public_static2"))) void C::public_static() { + private_static(); +} + +/// template tests +template +__attribute__((assume("template_func"))) void template_func() {} + +template <> +__attribute__((assume("template_func"))) void template_func() {} + +template <> +void template_func() {} + +template +struct S { + __attribute__((assume("S::method"))) void method(); +}; + +template <> +__attribute__((assume("S::method"))) void S::method() {} + +template <> +void S::method() {} + +// CHECK: define{{.*}} void @_Z3barv() #0 +// CHECK: define{{.*}} void @_ZL3bazv() #1 +// CHECK: define{{.*}} void @_ZN1C13public_methodEv({{.*}}) #2 +// CHECK: declare{{.*}} void @_ZN1C14private_methodEv({{.*}}) #3 +// CHECK: define{{.*}} void @_ZN1C13public_staticEv() #4 +// CHECK: declare{{.*}} void @_ZN1C14private_staticEv() #5 +// CHECK: define{{.*}} void @_Z13template_funcIfEvv() #6 +// CHECK: define{{.*}} void @_Z13template_funcIiEvv() #7 +// CHECK: define{{.*}} void @_ZN1SIfE6methodEv({{.*}}) #8 +// CHECK: define{{.*}} void @_ZN1SIiE6methodEv({{.*}}) #9 +// CHECK: declare{{.*}} void @_Z3foov() #10 +// CHECK: attributes #0 +// CHECK-SAME: "llvm.assume"="bar:before1,bar:before2,bar:before3,bar:def1,bar:def2" +// CHECK: attributes #1 +// CHECK-SAME: "llvm.assume"="baz:before1,baz:before2,baz:before3,baz:def1,baz:def2,baz:after" +// CHECK: attributes #2 +// CHECK-SAME: "llvm.assume"="C:public_method1,C:public_method2" +// CHECK: attributes #3 +// CHECK-SAME: "llvm.assume"="C:private_method" +// CHECK: attributes #4 +// CHECK-SAME: "llvm.assume"="C:public_static1,C:public_static2" +// CHECK: attributes #5 +// CHECK-SAME: "llvm.assume"="C:private_static" +// CHECK: attributes #6 +// CHECK-SAME: "llvm.assume"="template_func,template_func" +// CHECK: attributes #7 +// CHECK-SAME: "llvm.assume"="template_func" +// CHECK: attributes #8 +// CHECK-SAME: "llvm.assume"="S::method,S::method" +// CHECK: attributes #9 +// CHECK-SAME: "llvm.assume"="S::method" +// CHECK: attributes #10 +// CHECK-SAME: "llvm.assume"="foo:before1,foo:before2,foo:before3" + +#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 @@ -20,6 +20,7 @@ // CHECK-NEXT: ArcWeakrefUnavailable (SubjectMatchRule_objc_interface) // CHECK-NEXT: ArmBuiltinAlias (SubjectMatchRule_function) // CHECK-NEXT: AssumeAligned (SubjectMatchRule_objc_method, SubjectMatchRule_function) +// CHECK-NEXT: Assumption (SubjectMatchRule_function) // CHECK-NEXT: Availability ((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: BPFPreserveAccessIndex (SubjectMatchRule_record) // CHECK-NEXT: CFAuditedTransfer (SubjectMatchRule_function) diff --git a/clang/test/Sema/attr-assume.c b/clang/test/Sema/attr-assume.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-assume.c @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -triple i386-apple-darwin9 -fsyntax-only -verify %s + +void f1() __attribute__((assume(3))); // expected-error {{'assume' attribute requires a string}} +void f2() __attribute__((assume(int))); // expected-error {{expected expression}} +void f3() __attribute__((assume(for))); // expected-error {{expected expression}} +void f4() __attribute__((assume("QQQQ"))); // expected-warning {{the assumption string 'QQQQ' is not known and might be misspelled}} +void f5() __attribute__((assume("omp_no_openmp"))); +void f6() __attribute__((assume("omp_noopenmp"))); // expected-warning {{the assumption string 'omp_noopenmp' is not known and might be misspelled; did you mean 'omp_no_openmp'?}} +void f7() __attribute__((assume("omp_no_openmp_routine"))); // expected-warning {{the assumption string 'omp_no_openmp_routine' is not known and might be misspelled; did you mean 'omp_no_openmp_routines'?}} +void f8() __attribute__((assume("omp_no_openmp1"))); // expected-warning {{the assumption string 'omp_no_openmp1' is not known and might be misspelled; did you mean 'omp_no_openmp'?}} + +int g1 __attribute__((assume(0))); // expected-warning {{'assume' attribute only applies to functions}} +int g2 __attribute__((assume("omp_no_openmp"))); // expected-warning {{'assume' attribute only applies to functions}}