================ @@ -0,0 +1,544 @@ +======================== +Function Effect Analysis +======================== + +.. contents:: + :depth: 3 + :local: + + +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, +``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]]`` +is equivalent to ``[[clang::nonblocking(true)]]``, 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. + +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 +function, as described in the section "Analysis and warnings", below. Functions without an explicit +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 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 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. + + +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 constraint. + +.. 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``. + +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 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 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 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: + +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, in the ``-Wfunction-effects`` category: + +.. 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 local variables + + 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 ---------------- Sirraide wrote:
```suggestion a function that is called from a performance-constrained function may be analyzed to ``` https://github.com/llvm/llvm-project/pull/109855 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits