[clang] Add clang/docs/FunctionEffectAnalysis.rst. (PR #109855)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 28 10:24:33 PDT 2024
================
@@ -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
More information about the cfe-commits
mailing list