https://github.com/dougsonos updated https://github.com/llvm/llvm-project/pull/109855
>From 085965b324efde41168c5d51db3a368578d3458f Mon Sep 17 00:00:00 2001 From: Doug Wyatt <dwy...@apple.com> Date: Mon, 23 Sep 2024 14:44:32 -0700 Subject: [PATCH 1/6] Add clang/docs/FunctionEffectAnalysis.rst. --- clang/docs/FunctionEffectAnalysis.rst | 503 ++++++++++++++++++++++++++ clang/docs/index.rst | 1 + 2 files changed, 504 insertions(+) create mode 100644 clang/docs/FunctionEffectAnalysis.rst diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst new file mode 100644 index 00000000000000..8c1cf8a483c5f1 --- /dev/null +++ b/clang/docs/FunctionEffectAnalysis.rst @@ -0,0 +1,503 @@ +======================== +Function Effect Analysis +======================== + +Introduction +============ + +Clang Function Effect Analysis is a C++ language extension which can warn about "unsafe" +constructs. The feature is currently tailored for the Performance Constraint attributes, +``nonblocking`` and ``nonallocating``; functions with these attributes are verified as not +containing any language constructs or calls to other functions which violate the constraint. +(See :doc:`AttributeReference`.) + + +The ``nonblocking`` and ``nonallocating`` attributes +==================================================== + +Attribute syntax +---------------- + +The ``nonblocking`` and ``nonallocating`` attributes apply to function types, allowing them to be +attached to functions, blocks, function pointers, lambdas, and member functions. + +.. code-block:: c++ + + // Functions + void nonblockingFunction() [[clang::nonblocking]]; + void nonallocatingFunction() [[clang::nonallocating]]; + + // Function pointers + void (*nonblockingFunctionPtr)() [[clang::nonblocking]]; + + // Typedefs, type aliases. + typedef void (*NBFunctionPtrTypedef)() [[clang::nonblocking]]; + using NBFunctionPtrTypeAlias_gnu = __attribute__((nonblocking)) void (*)(); + using NBFunctionPtrTypeAlias_std = void (*)() [[clang::nonblocking]]; + + // C++ methods + struct Struct { + void NBMethod() [[clang::nonblocking]]; + }; + + // C++ lambdas + auto nbLambda = []() [[clang::nonblocking]] {}; + + // Blocks + void (^nbBlock)() = ^() [[clang::nonblocking]] {}; + +The attribute applies only to the function itself. In particular, it does not apply to any nested +functions or declarations, such as blocks, lambdas, and local classes. + +This document uses the C++/C23 syntax ``[[clang::nonblocking]]``, since it parallels the placement +of the ``noexcept`` specifier, and the attributes have other similarities to ``noexcept``. The GNU +``__attribute__((nonblocking))`` syntax is also supported. Note that it requires a different +placement on a C++ type alias. + +Like ``noexcept``, ``nonblocking`` and ``nonallocating`` have an optional argument, a compile-time +constant boolean expression. By default, the argument is true, so ``[[clang::nonblocking(true)]]`` +is equivalent to ``[[clang::nonblocking]]``, and declares the function type as never locking. + + +Attribute semantics +------------------- + +Together with ``noexcept``, the ``nonallocating`` and ``nonblocking`` attributes define an ordered +series of performance constraints. From weakest to strongest: + +- ``noexcept`` (as per the C++ standard): The function type will never throw an exception. +- ``nonallocating``: The function type will never allocate memory on the heap, and never throw an + exception. +- ``nonblocking``: The function type will never block on a lock, never allocate memory on the heap, + and never throw an exception. + +``nonblocking`` includes the ``nonallocating`` guarantee. + +``nonblocking`` and ``nonallocating`` include the ``noexcept`` guarantee, but the presence of either +attribute does not implicitly specify ``noexcept``. (It would be inappropriate for a Clang +attribute, ignored by non-Clang compilers, to imply a standard language feature.) + +``nonblocking(true)`` and ``nonallocating(true)`` apply to function *types*, and by extension, to +function-like declarations. When applied to a declaration with a body, the compiler verifies the +function, as described in the section "Analysis and warnings", below. Functions without an explicit +performance constraint are not verified. + +``nonblocking(false)`` and ``nonallocating(false)`` are synonyms for the attributes ``blocking`` and +``allocating``. They can be used on a function-like declaration to explicitly disable any potential +inference of ``nonblocking`` or ``nonallocating`` during verification. (Inference is described later +in this document). ``nonblocking(false)`` and ``nonallocating(false)`` are legal, but superfluous +when applied to a function *type*. ``float (int) [[nonblocking(false)]]`` and ``float (int)`` are +identical types. + +For all functions with no explicit performance constraint, the worst is assumed, that the function +allocates memory and potentially blocks, unless it can be inferred otherwise, as described in the +discussion of verification. + +The following list describes the meanings of all permutations of the two attributes and arguments: + +- ``nonblocking(true)`` + ``nonallocating(true)``: valid; ``nonallocating(true)`` is superfluous but + does not contradict the guarantee. +- ``nonblocking(true)`` + ``nonallocating(false)``: error, contradictory. +- ``nonblocking(false)`` + ``nonallocating(true)``: valid; the function does not allocate memory, + but may lock for other reasons. +- ``nonblocking(false)`` + ``nonallocating(false)``: valid. + +Type conversions +---------------- + +A performance constraint can be removed or weakened via an implicit conversion. An attempt to add +or strengthen a performance constraint is unsafe and results in a warning. + +.. code-block:: c++ + + void unannotated(); + void nonblocking() [[clang::nonblocking]]; + void nonallocating() [[clang::nonallocating]]; + + void example() + { + // It's fine to remove a performance constraint. + void (*fp_plain)(); + fp_plain = unannotated; + fp_plain = nonblocking; + fp_plain = nonallocating; + + // Adding/spoofing nonblocking is unsafe. + void (*fp_nonblocking)() [[clang::nonblocking]]; + fp_nonblocking = nullptr; + fp_nonblocking = nonblocking; + fp_nonblocking = unannotated; + // ^ warning: attribute 'nonblocking' should not be added via type conversion + fp_nonblocking = nonallocating; + // ^ warning: attribute 'nonblocking' should not be added via type conversion + + // Adding/spoofing nonallocating is unsafe. + void (*fp_nonallocating)() [[clang::nonallocating]]; + fp_nonallocating = nullptr; + fp_nonallocating = nonallocating; + fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating + fp_nonallocating = unannotated; + // ^ warning: attribute 'nonallocating' should not be added via type conversion + } + +Virtual methods +--------------- + +In C++, when a base class's virtual method has a performance constraint, overriding methods in +subclasses inherit the attribute. + +.. code-block:: c++ + + struct Base { + virtual void unsafe(); + virtual void safe() noexcept [[clang::nonblocking]]; + }; + + struct Derived : public Base { + void unsafe() [[clang::nonblocking]] override; + // It's okay for an overridden method to be more constrained + + void safe() noexcept override; + // This method is implicitly declared `nonblocking`, inherited from Base. + }; + +Redeclarations, overloads, and name mangling +-------------------------------------------- + +The ``nonblocking`` and ``nonallocating`` attributes, like ``noexcept``, do not factor into +argument-dependent lookup and overloaded functions/methods. + +First, consider that ``noexcept`` is integral to a function's type: + +.. code-block:: c++ + + void f1(int); + void f1(int) noexcept; + // error: exception specification in declaration does not match previous + // declaration + +Unlike ``noexcept``, a redeclaration of `f2` with an added or stronger performance constraint is +legal, and propagates the attribute to the previous declaration: + +.. code-block:: c++ + + int f2(); + int f2() [[clang::nonblocking]]; // redeclaration with stronger constraint is OK. + +This greatly eases adoption, by making it possible to annotate functions in external libraries +without modifying library headers. + +A redeclaration with a removed or weaker performance constraint produces a warning, in order to +parallel the behavior of ``noexcept``: + +.. code-block:: c++ + + int f2() { return 42; } + // warning: attribute 'nonblocking' on function does not match previous declaration + +In C++14, the following two declarations of `f3` are identical (a single function). In C++17 they +are separate overloads: + +.. code-block:: c++ + + void f3(void (*)()); + void f3(void (*)() noexcept); + +Similarly, the following two declarations of `f4` are separate overloads. This pattern may pose +difficulties due to ambiguity: + +.. code-block:: c++ + + void f4(void (*)()); + void f4(void (*)() [[clang::nonblocking]]); + +The attributes have no effect on the mangling of function and method names. + +``noexcept`` +------------ + +``nonblocking`` and ``nonallocating`` are conceptually similar to a stronger form of C++'s +``noexcept``, but with further diagnostics, as described later in this document. Therefore, in C++, +a ``nonblocking`` or ``nonallocating`` function, method, block or lambda should also be declared +``noexcept``.[^6] If ``noexcept`` is missing, a warning is issued. In Clang, this diagnostic is +controlled by ``-Wperf-constraint-implies-noexcept``. + +Objective-C +----------- + +The attributes are currently unsupported on Objective-C methods. + +Analysis and warnings +===================== + +Constraints +----------- + +Functions declared ``nonallocating`` or ``nonblocking``, when defined, are verified according to the +following rules. Such functions: + +1. May not allocate or deallocate memory on the heap. The analysis follows the calls to + ``operator new`` and ``operator delete`` generated by the ``new`` and ``delete`` keywords, and + treats them like any other function call. The global ``operator new`` and ``operator delete`` + aren't declared ``nonblocking`` or ``nonallocating`` and so they are considered unsafe. (This + is correct because most memory allocators are not lock-free. Note that the placement form of + ``operator new`` is implemented inline in libc++'s ``<new>`` header, and is verifiably + ``nonblocking``, since it merely casts the supplied pointer to the result type.) + +2. May not throw or catch exceptions. To throw, the compiler must allocate the exception on the + heap. (Also, many subclasses of ``std::exception`` allocate a ``std::string``). Exceptions are + deallocated when caught. + +3. May not make any indirect function call, via a virtual method, function pointer, or + pointer-to-member function, unless the target is explicitly declared with the same + ``nonblocking`` or ``nonallocating`` attribute (or stronger). + +4. May not make direct calls to any other function, with the following exceptions: + + a. The callee is also explicitly declared with the same ``nonblocking`` or ``nonallocating`` + attribute (or stronger). + b. The callee is defined in the same translation unit as the caller, does not have the ``false`` + form of the required attribute, and can be verified to be have the same attribute or stronger, + according to these same rules. + c. The callee is a built-in function (other than builtins which are known to block or allocate). + d. The callee is declared ``noreturn`` and, if compiling C++, the callee is also declared + ``noexcept``. This exception excludes functions such as ``abort()`` and ``std::terminate()`` + from the analysis. + +5. May not invoke or access an Objective-C method or property, since ``objc_msgSend()`` calls into + the Objective-C runtime, which may allocate memory or otherwise block. + +Functions declared ``nonblocking`` have an additional constraint: + +6. May not declare static local variables (e.g. Meyers singletons). The compiler generates a lock + protecting the initialization of the variable. + +Violations of any of these rules result in warnings: + +.. code-block:: c++ + + void notInline(); + + void example() [[clang::nonblocking]] + { + auto* x = new int; + // warning: function with 'nonblocking' attribute must not allocate or deallocate + // memory + + if (x == nullptr) { + static Logger* logger = createLogger(); + // warning: function with 'nonblocking' attribute must not have static locals + + throw std::runtime_warning{ "null" }; + // warning: 'nonblocking" function 'example' must not throw exceptions + } + notInline(); + // warning: 'function with 'nonblocking' attribute must not call non-'nonblocking' function + // 'notInline' + // note (on notInline()): declaration cannot be inferred 'nonblocking' because it has no + // definition in this translation unit + } + +Inferring ``nonblocking`` or ``nonallocating`` +---------------------------------------------- + +In the absence of a ``nonblocking`` or ``nonallocating`` attribute (whether ``true`` or ``false``), +a function, when found to be called from a performance-constrained function, can be analyzed to +infer whether it has a desired attribute. This analysis happens when the function is not a virtual +method, and it has a visible definition within the current translation unit (i.e. its body can be +traversed). + +.. code-block:: c++ + + void notInline(); + int implicitlySafe() { return 42; } + void implicitlyUnsafe() { notInline(); } + + void example() [[clang::nonblocking]] + { + int x = implicitlySafe(); // OK + implicitlyUnsafe(); + // warning: function with 'nonblocking' attribute must not call non-'nonblocking' function + // 'implicitlyUnsafe' + // note (on implicitlyUnsafe): function cannot be inferred 'nonblocking' because it calls + // non-'nonblocking' function 'notInline' + // note (on notInline()): declaration cannot be inferred 'nonblocking' because it has no + // definition in this translation unit + } + +Lambdas and blocks +------------------ + +As mentioned earlier, the performance constraint attributes apply only to a single function and not +to any code nested inside it, including blocks, lambdas, and local classes. It is possible for a +lock-free function to schedule the execution of a blocking lambda on another thread. Similarly, a +blocking function may create a ``nonblocking`` lambda for use in a realtime context. + +Operations which create, destroy, copy, and move lambdas and blocks are analyzed in terms of the +underlying function calls. For example, the creation of a lambda with captures generates a function +call to an anonymous struct's constructor, passing the captures as parameters. + +Implicit function calls in the AST +---------------------------------- + +The ``nonblocking`` / ``nonallocating`` analysis occurs at the Sema phase of analysis in Clang. +During Sema, there are some constructs which will eventually become function calls, but do not +appear as function calls in the AST. For example, ``auto* foo = new Foo;`` becomes a declaration +containing a ``CXXNewExpr`` which is understood as a function call to the global ``operator new`` +(in this example), and a ``CXXConstructExpr``, which, for analysis purposes, is a function call to +``Foo``'s constructor. Most gaps in the analysis would be due to incomplete knowledge of AST +constructs which become function calls. + +Disabling diagnostics +--------------------- + +Function effect diagnostics are controlled by ``-Wfunction-effects``. + +A construct like this can be used to exempt code from the checks described here: + +.. code-block:: c++ + + #define NONBLOCKING_UNSAFE(...) \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \ + _Pragma("clang diagnostic ignored \"-Wfunction-effects\"") \ + __VA_ARGS__ \ + _Pragma("clang diagnostic pop") + +Disabling the diagnostic allows for: + +- constructs which do block, but which in practice are used in ways to avoid unbounded blocking, + e.g. a thread pool with semaphores to coordinate multiple realtime threads. +- using libraries which are safe but not yet annotated. +- incremental adoption in a large codebase. + +Adoption +======== + +There are a few common issues that arise when adopting the ``nonblocking`` and ``nonallocating`` +attributes. + +C++ exceptions +-------------- + +Exceptions pose a challenge to the adoption of the performance constraints. Common library functions +which throw exceptions include: + ++----------------------------------+-----------------------------------------------------------------------+ +| Method | Alternative | ++==================================+=======================================================================+ +| ``std::vector<T>::at()`` | ``operator[](size_t)``, after verifying that the index is in range. | ++----------------------------------+-----------------------------------------------------------------------+ +| ``std::optional<T>::value()`` | ``operator*``, after checking ``has_value()`` or ``operator bool()``. | ++----------------------------------+-----------------------------------------------------------------------+ +| ``std::expected<T, E>::value()`` | Same as for ``std::optional<T>::value()``. | ++----------------------------------+-----------------------------------------------------------------------+ + +Interactions with type-erasure techniques +----------------------------------------- + +``std::function<R(Args...)>`` illustrates a common C++ type-erasure technique. Using template +argument deduction, it decomposes a function type into its return and parameter types. Additional +components of the function type, including ``noexcept``, ``nonblocking``, ``nonallocating``, and any +other attributes, are discarded. + +Standard library support for these components of a function type is not immediately forthcoming. + +Code can work around this limitation in either of two ways: + +1. Avoid abstractions like ``std::function`` and instead work directly with the original lambda type. + +2. Create a specialized alternative, e.g. ``nonblocking_function<R(Args...)>`` where all function + pointers used in the implementation and its interface are ``nonblocking``. + +As an example of the first approach, when using a lambda as a *Callable* template parameter, the +attribute is preserved: + +.. code-block:: c++ + + std::sort(vec.begin(), vec.end(), + [](const Elem& a, const Elem& b) [[clang::nonblocking]] { return a.mem < b.mem; }); + +Here, the type of the ``Compare`` template parameter is an anonymous class generated from the +lambda, with an ``operator()`` method holding the ``nonblocking`` attribute. + +A complication arises when a *Callable* template parameter, instead of being a lambda or class +implementing ``operator()``, is a function pointer: + +.. code-block:: c++ + + static bool compare_elems(const Elem& a, const Elem& b) [[clang::nonblocking]] { + return a.mem < b.mem; }; + + std::sort(vec.begin(), vec.end(), compare_elems); + +Here, the type of ``compare_elems`` is decomposed to ``bool(const Elem&, const Elem&)``, without +``nonblocking``, when forming the template parameter. This can be solved using the second approach, +creating a specialized alternative which explicitly requires the attribute. In this case, it's +possible to use a small wrapper to transform the function pointer into a functor: + +.. code-block:: c++ + + template <typename> + class nonblocking_fp; + + template <typename R, typename... Args> + class nonblocking_fp<R(Args...)> { + public: + using impl_t = R (*)(Args...) [[clang::nonblocking]]; + + private: + impl_t mImpl{ nullptr_t }; + public: + nonblocking_fp() = default; + nonblocking_fp(impl_t f) : mImpl{ f } {} + + R operator()(Args... args) const + { + return mImpl(std::forward<Args>(args)...); + } + }; + + // deduction guide (like std::function's) + template< class R, class... ArgTypes > + nonblocking_fp( R(*)(ArgTypes...) ) -> nonblocking_fp<R(ArgTypes...)>; + + // -- + + // Wrap the function pointer in a functor which preserves ``nonblocking``. + std::sort(vec.begin(), vec.end(), nonblocking_fp{ compare_elems }); + +Now, the ``nonblocking`` attribute of ``compare_elems`` is verified when it is converted to a +``nonblocking`` function pointer, as the argument to ``nonblocking_fp``'s constructor. The template +parameter is the functor class ``nonblocking_fp``. + + +Static local variables +---------------------- + +Static local variables are often used for lazily-constructed globals (Meyers singletons). Beyond the +compiler's use of a lock to ensure thread-safe initialization, it is dangerously easy to +inadvertently trigger initialization, involving heap allocation, from a ``nonblocking`` or +``nonallocating`` context. + +Generally, such singletons need to be replaced by globals, and care must be taken to ensure their +initialization before they are used from ``nonblocking`` or ``nonallocating`` contexts. + + +Annotating libraries +-------------------- + +It can be surprising that the analysis does not depend on knowledge of any primitives; it simply +assumes the worst, that all function calls are unsafe unless explicitly marked as safe or able to be +inferred as safe. With ``nonblocking``, this appears to suffice for all but the most primitive of +spinlocks. + +At least for an operating system's C functions, it is possible to define an override header which +redeclares safe common functions (e.g. ``pthread_self()``) with the addition of ``nonblocking``. +This may help in adopting the feature incrementally. + +It also helps that for many of the functions in ``<math.h>``, Clang generates calls to built-in +functions, which the diagnosis understands to be safe. + +Much of the C++ standard library consists of inline templated functions which work well with +inference. A small number of primitives may need explicit ``nonblocking/nonallocating`` attributes. diff --git a/clang/docs/index.rst b/clang/docs/index.rst index 4a497f4d9bcc3c..1f4ff28d199b2d 100644 --- a/clang/docs/index.rst +++ b/clang/docs/index.rst @@ -26,6 +26,7 @@ Using Clang as a Compiler ClangStaticAnalyzer ThreadSafetyAnalysis DataFlowAnalysisIntro + FunctionEffectAnalysis AddressSanitizer ThreadSanitizer MemorySanitizer >From 0ab0c55d7761a52ad1b4f8ea8b56ffb29c5da36e Mon Sep 17 00:00:00 2001 From: Doug Wyatt <d...@sonosphere.com> Date: Thu, 26 Sep 2024 08:46:41 -0700 Subject: [PATCH 2/6] Apply suggestions from code review Co-authored-by: Sirraide <aeternalm...@gmail.com> --- clang/docs/FunctionEffectAnalysis.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst index 8c1cf8a483c5f1..ecfc1af4786c30 100644 --- a/clang/docs/FunctionEffectAnalysis.rst +++ b/clang/docs/FunctionEffectAnalysis.rst @@ -55,8 +55,8 @@ of the ``noexcept`` specifier, and the attributes have other similarities to ``n placement on a C++ type alias. Like ``noexcept``, ``nonblocking`` and ``nonallocating`` have an optional argument, a compile-time -constant boolean expression. By default, the argument is true, so ``[[clang::nonblocking(true)]]`` -is equivalent to ``[[clang::nonblocking]]``, and declares the function type as never locking. +constant boolean expression. By default, the argument is ``true``, so ``[[clang::nonblocking]]`` +is equivalent to ``[[clang::nonblocking(true)]]``, and declares the function type as never locking. Attribute semantics @@ -82,8 +82,8 @@ function-like declarations. When applied to a declaration with a body, the compi function, as described in the section "Analysis and warnings", below. Functions without an explicit performance constraint are not verified. -``nonblocking(false)`` and ``nonallocating(false)`` are synonyms for the attributes ``blocking`` and -``allocating``. They can be used on a function-like declaration to explicitly disable any potential +``blocking`` and ``allocating`` are synonyms for ``nonblocking(false)`` and +``nonallocating(false)``, respectively. They can be used on a function-like declaration to explicitly disable any potential inference of ``nonblocking`` or ``nonallocating`` during verification. (Inference is described later in this document). ``nonblocking(false)`` and ``nonallocating(false)`` are legal, but superfluous when applied to a function *type*. ``float (int) [[nonblocking(false)]]`` and ``float (int)`` are @@ -144,7 +144,7 @@ Virtual methods --------------- In C++, when a base class's virtual method has a performance constraint, overriding methods in -subclasses inherit the attribute. +subclasses inherit the constraint. .. code-block:: c++ @@ -259,7 +259,7 @@ following rules. Such functions: b. The callee is defined in the same translation unit as the caller, does not have the ``false`` form of the required attribute, and can be verified to be have the same attribute or stronger, according to these same rules. - c. The callee is a built-in function (other than builtins which are known to block or allocate). + c. The callee is a built-in function that is known not to block or allocated. d. The callee is declared ``noreturn`` and, if compiling C++, the callee is also declared ``noexcept``. This exception excludes functions such as ``abort()`` and ``std::terminate()`` from the analysis. >From 76396881af7c3a8de10aea22bf0cf26c6577740b Mon Sep 17 00:00:00 2001 From: Doug Wyatt <dwy...@apple.com> Date: Thu, 26 Sep 2024 09:59:24 -0700 Subject: [PATCH 3/6] Address helpful review feedback from @Sirraide and @cjappl. --- clang/docs/FunctionEffectAnalysis.rst | 101 ++++++++++++++++++-------- 1 file changed, 69 insertions(+), 32 deletions(-) diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst index ecfc1af4786c30..abe071afb84444 100644 --- a/clang/docs/FunctionEffectAnalysis.rst +++ b/clang/docs/FunctionEffectAnalysis.rst @@ -2,10 +2,15 @@ Function Effect Analysis ======================== +.. contents:: + :depth: 3 + :local: + + Introduction ============ -Clang Function Effect Analysis is a C++ language extension which can warn about "unsafe" +Clang Function Effect Analysis is a language extension which can warn about "unsafe" constructs. The feature is currently tailored for the Performance Constraint attributes, ``nonblocking`` and ``nonallocating``; functions with these attributes are verified as not containing any language constructs or calls to other functions which violate the constraint. @@ -73,9 +78,11 @@ series of performance constraints. From weakest to strongest: ``nonblocking`` includes the ``nonallocating`` guarantee. -``nonblocking`` and ``nonallocating`` include the ``noexcept`` guarantee, but the presence of either -attribute does not implicitly specify ``noexcept``. (It would be inappropriate for a Clang -attribute, ignored by non-Clang compilers, to imply a standard language feature.) +``nonblocking`` and ``nonallocating`` include the ``noexcept`` guarantee, but neither +attribute implicitly specifies ``noexcept``. (It would be inappropriate for a Clang +attribute, ignored by non-Clang compilers, to imply a standard language feature.) Nonetheless, +Clang emits a warning if, in C++, a function is declared ``nonblocking`` or ``nonallocating`` +without ``noexcept``. This diagnostic is controlled by ``-Wperf-constraint-implies-noexcept``. ``nonblocking(true)`` and ``nonallocating(true)`` apply to function *types*, and by extension, to function-like declarations. When applied to a declaration with a body, the compiler verifies the @@ -83,24 +90,32 @@ function, as described in the section "Analysis and warnings", below. Functions performance constraint are not verified. ``blocking`` and ``allocating`` are synonyms for ``nonblocking(false)`` and -``nonallocating(false)``, respectively. They can be used on a function-like declaration to explicitly disable any potential -inference of ``nonblocking`` or ``nonallocating`` during verification. (Inference is described later -in this document). ``nonblocking(false)`` and ``nonallocating(false)`` are legal, but superfluous -when applied to a function *type*. ``float (int) [[nonblocking(false)]]`` and ``float (int)`` are -identical types. - -For all functions with no explicit performance constraint, the worst is assumed, that the function -allocates memory and potentially blocks, unless it can be inferred otherwise, as described in the +``nonallocating(false)``, respectively. They can be used on a function-like declaration to +explicitly disable any potential inference of ``nonblocking`` or ``nonallocating`` during +verification. (Inference is described later in this document). ``nonblocking(false)`` and +``nonallocating(false)`` are legal, but superfluous when applied to a function *type*. +``float (int) [[nonblocking(false)]]`` and ``float (int)`` are identical types. + +For functions with no explicit performance constraint, the worst is assumed: the function +allocates memory and potentially blocks, unless it can be inferred otherwise. This is detailed in the discussion of verification. -The following list describes the meanings of all permutations of the two attributes and arguments: +The following example describes the meanings of all permutations of the two attributes and arguments: + +.. code-block:: c++ + + void nb1_na1() [[clang::nonblocking(true)]] [[clang::nonallocating(true)]]; + // Valid; nonallocating(true) is superfluous but doesn't contradict the guarantee. + + void nb1_na0() [[clang::nonblocking(true)]] [[clang::nonallocating(false)]]; + // error: 'allocating' and 'nonblocking' attributes are not compatible + + void nb0_na1() [[clang::nonblocking(false)]] [[clang::nonallocating(true)]]; + // Valid; the function does not allocate memory, but may lock for other reasons. + + void nb0_na0() [[clang::nonblocking(false)]] [[clang::nonallocating(false)]]; + // Valid. -- ``nonblocking(true)`` + ``nonallocating(true)``: valid; ``nonallocating(true)`` is superfluous but - does not contradict the guarantee. -- ``nonblocking(true)`` + ``nonallocating(false)``: error, contradictory. -- ``nonblocking(false)`` + ``nonallocating(true)``: valid; the function does not allocate memory, - but may lock for other reasons. -- ``nonblocking(false)`` + ``nonallocating(false)``: valid. Type conversions ---------------- @@ -219,8 +234,7 @@ The attributes have no effect on the mangling of function and method names. ``nonblocking`` and ``nonallocating`` are conceptually similar to a stronger form of C++'s ``noexcept``, but with further diagnostics, as described later in this document. Therefore, in C++, a ``nonblocking`` or ``nonallocating`` function, method, block or lambda should also be declared -``noexcept``.[^6] If ``noexcept`` is missing, a warning is issued. In Clang, this diagnostic is -controlled by ``-Wperf-constraint-implies-noexcept``. +``noexcept``. Objective-C ----------- @@ -245,7 +259,7 @@ following rules. Such functions: ``nonblocking``, since it merely casts the supplied pointer to the result type.) 2. May not throw or catch exceptions. To throw, the compiler must allocate the exception on the - heap. (Also, many subclasses of ``std::exception`` allocate a ``std::string``). Exceptions are + heap. (Also, many subclasses of ``std::exception`` allocate a string). Exceptions are deallocated when caught. 3. May not make any indirect function call, via a virtual method, function pointer, or @@ -259,20 +273,24 @@ following rules. Such functions: b. The callee is defined in the same translation unit as the caller, does not have the ``false`` form of the required attribute, and can be verified to be have the same attribute or stronger, according to these same rules. - c. The callee is a built-in function that is known not to block or allocated. + c. The callee is a built-in function that is known not to block or allocate. d. The callee is declared ``noreturn`` and, if compiling C++, the callee is also declared - ``noexcept``. This exception excludes functions such as ``abort()`` and ``std::terminate()`` - from the analysis. + ``noexcept``. This special case excludes functions such as ``abort()`` and ``std::terminate()`` + from the analysis. (The reason for requiring ``noexcept`` in C++ is that a function declared + ``noreturn`` could be a wrapper for ``throw``.) 5. May not invoke or access an Objective-C method or property, since ``objc_msgSend()`` calls into the Objective-C runtime, which may allocate memory or otherwise block. +6. May not access thread-local variables. Typically, thread-local variables are allocated on the + heap when first accessed. + Functions declared ``nonblocking`` have an additional constraint: -6. May not declare static local variables (e.g. Meyers singletons). The compiler generates a lock +7. May not declare static local variables (e.g. Meyers singletons). The compiler generates a lock protecting the initialization of the variable. -Violations of any of these rules result in warnings: +Violations of any of these rules result in warnings, in the ``-Wfunction-effects`` category: .. code-block:: c++ @@ -286,7 +304,7 @@ Violations of any of these rules result in warnings: if (x == nullptr) { static Logger* logger = createLogger(); - // warning: function with 'nonblocking' attribute must not have static locals + // warning: function with 'nonblocking' attribute must not have static local variables throw std::runtime_warning{ "null" }; // warning: 'nonblocking" function 'example' must not throw exceptions @@ -330,7 +348,7 @@ Lambdas and blocks As mentioned earlier, the performance constraint attributes apply only to a single function and not to any code nested inside it, including blocks, lambdas, and local classes. It is possible for a -lock-free function to schedule the execution of a blocking lambda on another thread. Similarly, a +nonblocking function to schedule the execution of a blocking lambda on another thread. Similarly, a blocking function may create a ``nonblocking`` lambda for use in a realtime context. Operations which create, destroy, copy, and move lambdas and blocks are analyzed in terms of the @@ -393,6 +411,25 @@ which throw exceptions include: | ``std::expected<T, E>::value()`` | Same as for ``std::optional<T>::value()``. | +----------------------------------+-----------------------------------------------------------------------+ + +``std::function<R(Args...)>`` +----------------------------- + +``std::function<R(Args...)>`` is generally incompatible with ``nonblocking`` and ``nonallocating`` +code, because an implementation typically allocates heap memory in the constructor. + +Alternatives: + +- ``std::function_ref`` (available in C++26 or as ``llvm::function_ref``). This is appropriate and + optimal when a functor's lifetime does not need to extend past the function that created it. + +- ``inplace_function`` from WG14. This solves the allocation problem by giving the functor wrapper + a fixed size known at compile time and using an inline buffer. + +While these alternatives both address the heap allocation of ``std::function``, they are still +obstacles to ``nonblocking/nonallocating`` verification, for reasons detailed in the next section. + + Interactions with type-erasure techniques ----------------------------------------- @@ -407,7 +444,7 @@ Code can work around this limitation in either of two ways: 1. Avoid abstractions like ``std::function`` and instead work directly with the original lambda type. -2. Create a specialized alternative, e.g. ``nonblocking_function<R(Args...)>`` where all function +2. Create a specialized alternative, e.g. ``nonblocking_function_ref<R(Args...)>`` where all function pointers used in the implementation and its interface are ``nonblocking``. As an example of the first approach, when using a lambda as a *Callable* template parameter, the @@ -496,8 +533,8 @@ At least for an operating system's C functions, it is possible to define an over redeclares safe common functions (e.g. ``pthread_self()``) with the addition of ``nonblocking``. This may help in adopting the feature incrementally. -It also helps that for many of the functions in ``<math.h>``, Clang generates calls to built-in -functions, which the diagnosis understands to be safe. +It also helps that for many of the functions in the standard C libraries (notably ``<math.h>``), +Clang generates calls to built-in functions, which the diagnosis understands to be safe. Much of the C++ standard library consists of inline templated functions which work well with inference. A small number of primitives may need explicit ``nonblocking/nonallocating`` attributes. >From 61464c00d27e0a4acb3c938f39cc23e3a0d40872 Mon Sep 17 00:00:00 2001 From: Doug Wyatt <dwy...@apple.com> Date: Thu, 26 Sep 2024 10:08:44 -0700 Subject: [PATCH 4/6] Tiny whitespace tweak. --- clang/docs/FunctionEffectAnalysis.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst index abe071afb84444..66feb29276de2f 100644 --- a/clang/docs/FunctionEffectAnalysis.rst +++ b/clang/docs/FunctionEffectAnalysis.rst @@ -375,7 +375,7 @@ A construct like this can be used to exempt code from the checks described here: .. code-block:: c++ - #define NONBLOCKING_UNSAFE(...) \ + #define NONBLOCKING_UNSAFE(...) \ _Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \ _Pragma("clang diagnostic ignored \"-Wfunction-effects\"") \ >From 2c3e35ef8f8d9b938a15cc389daa55e8741793d6 Mon Sep 17 00:00:00 2001 From: Doug Wyatt <dwy...@apple.com> Date: Fri, 11 Oct 2024 08:50:56 -0700 Subject: [PATCH 5/6] Clarify the relationship with noexcept, add a reference to the RealtimeSanitizer docs. --- clang/docs/FunctionEffectAnalysis.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst index 66feb29276de2f..b1be70919f92ab 100644 --- a/clang/docs/FunctionEffectAnalysis.rst +++ b/clang/docs/FunctionEffectAnalysis.rst @@ -78,11 +78,15 @@ series of performance constraints. From weakest to strongest: ``nonblocking`` includes the ``nonallocating`` guarantee. -``nonblocking`` and ``nonallocating`` include the ``noexcept`` guarantee, but neither -attribute implicitly specifies ``noexcept``. (It would be inappropriate for a Clang -attribute, ignored by non-Clang compilers, to imply a standard language feature.) Nonetheless, -Clang emits a warning if, in C++, a function is declared ``nonblocking`` or ``nonallocating`` -without ``noexcept``. This diagnostic is controlled by ``-Wperf-constraint-implies-noexcept``. +While ``nonblocking`` and ``nonallocating`` are conceptually a superset of ``noexcept``, neither +attribute implicitly specifies ``noexcept``. Further, ``noexcept`` has a specified runtime behavior of +aborting if an exception is thrown, while the ``nonallocating`` and ``nonblocking`` attributes are +purely for compile-time analysis and have no potential runtime behavior. Nonetheless, Clang emits a +warning if, in C++, a function is declared ``nonblocking`` or ``nonallocating`` without +``noexcept``. This diagnostic is controlled by ``-Wperf-constraint-implies-noexcept``. + +Also, the ``nonblocking`` and ``blocking`` attributes do have special runtime behavior in code built +with Clang's :doc:`RealtimeSanitizer`. ``nonblocking(true)`` and ``nonallocating(true)`` apply to function *types*, and by extension, to function-like declarations. When applied to a declaration with a body, the compiler verifies the >From e7ad0dbd1278c34433a569131eab1e8f8204db17 Mon Sep 17 00:00:00 2001 From: Doug Wyatt <d...@sonosphere.com> Date: Mon, 28 Oct 2024 10:38:26 -0700 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Sirraide <aeternalm...@gmail.com> --- clang/docs/FunctionEffectAnalysis.rst | 53 +++++++++++++-------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/clang/docs/FunctionEffectAnalysis.rst b/clang/docs/FunctionEffectAnalysis.rst index b1be70919f92ab..f44a72e1e6f3a4 100644 --- a/clang/docs/FunctionEffectAnalysis.rst +++ b/clang/docs/FunctionEffectAnalysis.rst @@ -11,7 +11,7 @@ Introduction ============ Clang Function Effect Analysis is a language extension which can warn about "unsafe" -constructs. The feature is currently tailored for the Performance Constraint attributes, +constructs. The feature is currently tailored for the Performance Constraint attributes ``nonblocking`` and ``nonallocating``; functions with these attributes are verified as not containing any language constructs or calls to other functions which violate the constraint. (See :doc:`AttributeReference`.) @@ -61,7 +61,7 @@ placement on a C++ type alias. Like ``noexcept``, ``nonblocking`` and ``nonallocating`` have an optional argument, a compile-time constant boolean expression. By default, the argument is ``true``, so ``[[clang::nonblocking]]`` -is equivalent to ``[[clang::nonblocking(true)]]``, and declares the function type as never locking. +is equivalent to ``[[clang::nonblocking(true)]]``, and declares the function type as never blocking. Attribute semantics @@ -71,34 +71,32 @@ Together with ``noexcept``, the ``nonallocating`` and ``nonblocking`` attributes series of performance constraints. From weakest to strongest: - ``noexcept`` (as per the C++ standard): The function type will never throw an exception. -- ``nonallocating``: The function type will never allocate memory on the heap, and never throw an +- ``nonallocating``: The function type will never allocate memory on the heap or throw an exception. -- ``nonblocking``: The function type will never block on a lock, never allocate memory on the heap, - and never throw an exception. +- ``nonblocking``: The function type will never block on a lock, allocate memory on the heap, + or throw an exception. ``nonblocking`` includes the ``nonallocating`` guarantee. While ``nonblocking`` and ``nonallocating`` are conceptually a superset of ``noexcept``, neither attribute implicitly specifies ``noexcept``. Further, ``noexcept`` has a specified runtime behavior of aborting if an exception is thrown, while the ``nonallocating`` and ``nonblocking`` attributes are -purely for compile-time analysis and have no potential runtime behavior. Nonetheless, Clang emits a +mainly for compile-time analysis and have no runtime behavior, except in code built +with Clang's :doc:`RealtimeSanitizer`. Nonetheless, Clang emits a warning if, in C++, a function is declared ``nonblocking`` or ``nonallocating`` without ``noexcept``. This diagnostic is controlled by ``-Wperf-constraint-implies-noexcept``. -Also, the ``nonblocking`` and ``blocking`` attributes do have special runtime behavior in code built -with Clang's :doc:`RealtimeSanitizer`. - ``nonblocking(true)`` and ``nonallocating(true)`` apply to function *types*, and by extension, to function-like declarations. When applied to a declaration with a body, the compiler verifies the -function, as described in the section "Analysis and warnings", below. Functions without an explicit -performance constraint are not verified. +function, as described in the section "Analysis and warnings", below. ``blocking`` and ``allocating`` are synonyms for ``nonblocking(false)`` and ``nonallocating(false)``, respectively. They can be used on a function-like declaration to explicitly disable any potential inference of ``nonblocking`` or ``nonallocating`` during verification. (Inference is described later in this document). ``nonblocking(false)`` and -``nonallocating(false)`` are legal, but superfluous when applied to a function *type*. -``float (int) [[nonblocking(false)]]`` and ``float (int)`` are identical types. +``nonallocating(false)`` are legal, but superfluous when applied to a function *type* +that is not part of a declarator: ``float (int) [[nonblocking(false)]]`` and +``float (int)`` are identical types. For functions with no explicit performance constraint, the worst is assumed: the function allocates memory and potentially blocks, unless it can be inferred otherwise. This is detailed in the @@ -125,7 +123,8 @@ Type conversions ---------------- A performance constraint can be removed or weakened via an implicit conversion. An attempt to add -or strengthen a performance constraint is unsafe and results in a warning. +or strengthen a performance constraint is unsafe and results in a warning. The rules for this +are comparable to that for ``noexcept`` in C++17 and later. .. code-block:: c++ @@ -162,7 +161,7 @@ or strengthen a performance constraint is unsafe and results in a warning. Virtual methods --------------- -In C++, when a base class's virtual method has a performance constraint, overriding methods in +In C++, when a virtual method has a performance constraint, overriding methods in subclasses inherit the constraint. .. code-block:: c++ @@ -195,19 +194,19 @@ First, consider that ``noexcept`` is integral to a function's type: // error: exception specification in declaration does not match previous // declaration -Unlike ``noexcept``, a redeclaration of `f2` with an added or stronger performance constraint is -legal, and propagates the attribute to the previous declaration: +Unlike ``noexcept``, a redeclaration of ``f2`` with an added or stronger performance constraint is +legal and propagates the attribute to the previous declaration: .. code-block:: c++ int f2(); int f2() [[clang::nonblocking]]; // redeclaration with stronger constraint is OK. -This greatly eases adoption, by making it possible to annotate functions in external libraries +This greatly eases adoption by making it possible to annotate functions in external libraries without modifying library headers. -A redeclaration with a removed or weaker performance constraint produces a warning, in order to -parallel the behavior of ``noexcept``: +A redeclaration with a removed or weaker performance constraint produces a warning, paralleling +the behavior of ``noexcept``: .. code-block:: c++ @@ -275,7 +274,7 @@ following rules. Such functions: a. The callee is also explicitly declared with the same ``nonblocking`` or ``nonallocating`` attribute (or stronger). b. The callee is defined in the same translation unit as the caller, does not have the ``false`` - form of the required attribute, and can be verified to be have the same attribute or stronger, + form of the required attribute, and can be verified to have the same attribute or stronger, according to these same rules. c. The callee is a built-in function that is known not to block or allocate. d. The callee is declared ``noreturn`` and, if compiling C++, the callee is also declared @@ -324,7 +323,7 @@ Inferring ``nonblocking`` or ``nonallocating`` ---------------------------------------------- In the absence of a ``nonblocking`` or ``nonallocating`` attribute (whether ``true`` or ``false``), -a function, when found to be called from a performance-constrained function, can be analyzed to +a function that is called from a performance-constrained function may be analyzed to infer whether it has a desired attribute. This analysis happens when the function is not a virtual method, and it has a visible definition within the current translation unit (i.e. its body can be traversed). @@ -389,8 +388,8 @@ A construct like this can be used to exempt code from the checks described here: Disabling the diagnostic allows for: - constructs which do block, but which in practice are used in ways to avoid unbounded blocking, - e.g. a thread pool with semaphores to coordinate multiple realtime threads. -- using libraries which are safe but not yet annotated. + e.g. a thread pool with semaphores to coordinate multiple realtime threads; +- using libraries which are safe but not yet annotated; - incremental adoption in a large codebase. Adoption @@ -420,7 +419,7 @@ which throw exceptions include: ----------------------------- ``std::function<R(Args...)>`` is generally incompatible with ``nonblocking`` and ``nonallocating`` -code, because an implementation typically allocates heap memory in the constructor. +code, because a typical implementation may allocate heap memory in the constructor. Alternatives: @@ -537,8 +536,8 @@ At least for an operating system's C functions, it is possible to define an over redeclares safe common functions (e.g. ``pthread_self()``) with the addition of ``nonblocking``. This may help in adopting the feature incrementally. -It also helps that for many of the functions in the standard C libraries (notably ``<math.h>``), -Clang generates calls to built-in functions, which the diagnosis understands to be safe. +It also helps that many of the functions in the standard C libraries (notably ``<math.h>``) +are treated as built-in functions by Clang, which the diagnosis understands to be safe. Much of the C++ standard library consists of inline templated functions which work well with inference. A small number of primitives may need explicit ``nonblocking/nonallocating`` attributes. _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits