brintos

brintos / llvm-project-archived public Read only

0
0
Text · 22.1 KiB · 3f2c4db Raw
536 lines · plain
1========================2Function Effect Analysis3========================4 5.. contents::6  :depth: 37  :local:8 9 10Introduction11============12 13Clang Function Effect Analysis is a language extension which can warn about "unsafe"14constructs. The feature is currently tailored for the Performance Constraint attributes15``nonblocking`` and ``nonallocating``; functions with these attributes are verified as not16containing any language constructs or calls to other functions which violate the constraint.17(See :doc:`AttributeReference`.)18 19 20The ``nonblocking`` and ``nonallocating`` attributes21====================================================22 23Attribute syntax24----------------25 26The ``nonblocking`` and ``nonallocating`` attributes apply to function types, allowing them to be27attached to functions, blocks, function pointers, lambdas, and member functions.28 29.. code-block:: c++30 31  // Functions32  void nonblockingFunction() [[clang::nonblocking]];33  void nonallocatingFunction() [[clang::nonallocating]];34 35  // Function pointers36  void (*nonblockingFunctionPtr)() [[clang::nonblocking]];37 38  // Typedefs, type aliases.39  typedef void (*NBFunctionPtrTypedef)() [[clang::nonblocking]];40  using NBFunctionPtrTypeAlias_gnu = __attribute__((nonblocking)) void (*)();41  using NBFunctionPtrTypeAlias_std = void (*)() [[clang::nonblocking]];42 43  // C++ methods44  struct Struct {45    void NBMethod() [[clang::nonblocking]];46  };47 48  // C++ lambdas49  auto nbLambda = []() [[clang::nonblocking]] {};50 51  // Blocks52  void (^nbBlock)() = ^() [[clang::nonblocking]] {};53 54The attribute applies only to the function itself. In particular, it does not apply to any nested55functions or declarations, such as blocks, lambdas, and local classes.56 57This document uses the C++/C23 syntax ``[[clang::nonblocking]]``, since it parallels the placement58of the ``noexcept`` specifier, and the attributes have other similarities to ``noexcept``. The GNU59``__attribute__((nonblocking))`` syntax is also supported. Note that it requires a different60placement on a C++ type alias.61 62Like ``noexcept``, ``nonblocking`` and ``nonallocating`` have an optional argument, a compile-time63constant boolean expression. By default, the argument is ``true``, so ``[[clang::nonblocking]]``64is equivalent to ``[[clang::nonblocking(true)]]``, and declares the function type as never blocking.65 66 67Attribute semantics68-------------------69 70Together with ``noexcept``, the ``nonallocating`` and ``nonblocking`` attributes define an ordered71series of performance constraints. From weakest to strongest:72 73- ``noexcept`` (as per the C++ standard): The function type will never throw an exception.74- ``nonallocating``: The function type will never allocate memory on the heap or throw an75  exception.76- ``nonblocking``: The function type will never block on a lock, allocate memory on the heap,77  or throw an exception.78 79``nonblocking`` includes the ``nonallocating`` guarantee.80 81While ``nonblocking`` and ``nonallocating`` are conceptually a superset of ``noexcept``, neither82attribute implicitly specifies ``noexcept``. Further, ``noexcept`` has a specified runtime behavior of83aborting if an exception is thrown, while the ``nonallocating`` and ``nonblocking`` attributes are84mainly for compile-time analysis and have no runtime behavior, except in code built85with Clang's :doc:`RealtimeSanitizer`. Nonetheless, Clang emits a86warning if, in C++, a function is declared ``nonblocking`` or ``nonallocating`` without87``noexcept``. This diagnostic is controlled by ``-Wperf-constraint-implies-noexcept``.88 89``nonblocking(true)`` and ``nonallocating(true)`` apply to function *types*, and by extension, to90function-like declarations. When applied to a declaration with a body, the compiler verifies the91function, as described in the section "Analysis and warnings", below.92 93``blocking`` and ``allocating`` are synonyms for ``nonblocking(false)`` and94``nonallocating(false)``, respectively. They can be used on a function-like declaration to95explicitly disable any potential inference of ``nonblocking`` or ``nonallocating`` during96verification. (Inference is described later in this document). ``nonblocking(false)`` and97``nonallocating(false)`` are legal, but superfluous  when applied to a function *type*98that is not part of a declarator: ``float (int) [[nonblocking(false)]]`` and99``float (int)`` are identical types.100 101For functions with no explicit performance constraint, the worst is assumed: the function102allocates memory and potentially blocks, unless it can be inferred otherwise. This is detailed in the103discussion of verification.104 105The following example describes the meanings of all permutations of the two attributes and arguments:106 107.. code-block:: c++108 109  void nb1_na1() [[clang::nonblocking(true)]] [[clang::nonallocating(true)]];110  // Valid; nonallocating(true) is superfluous but doesn't contradict the guarantee.111 112  void nb1_na0() [[clang::nonblocking(true)]] [[clang::nonallocating(false)]];113  // error: 'allocating' and 'nonblocking' attributes are not compatible114 115  void nb0_na1() [[clang::nonblocking(false)]] [[clang::nonallocating(true)]];116  // Valid; the function does not allocate memory, but may lock for other reasons.117 118  void nb0_na0() [[clang::nonblocking(false)]] [[clang::nonallocating(false)]];119  // Valid.120 121 122Type conversions123----------------124 125A performance constraint can be removed or weakened via an implicit conversion. An attempt to add126or strengthen a performance constraint is unsafe and results in a warning. The rules for this127are comparable to that for ``noexcept`` in C++17 and later.128 129.. code-block:: c++130 131  void unannotated();132  void nonblocking() [[clang::nonblocking]];133  void nonallocating() [[clang::nonallocating]];134 135  void example()136  {137    // It's fine to remove a performance constraint.138    void (*fp_plain)();139    fp_plain = unannotated;140    fp_plain = nonblocking;141    fp_plain = nonallocating;142 143    // Adding/spoofing nonblocking is unsafe.144    void (*fp_nonblocking)() [[clang::nonblocking]];145    fp_nonblocking = nullptr;146    fp_nonblocking = nonblocking;147    fp_nonblocking = unannotated;148    // ^ warning: attribute 'nonblocking' should not be added via type conversion149    fp_nonblocking = nonallocating;150    // ^ warning: attribute 'nonblocking' should not be added via type conversion151 152    // Adding/spoofing nonallocating is unsafe.153    void (*fp_nonallocating)() [[clang::nonallocating]];154    fp_nonallocating = nullptr;155    fp_nonallocating = nonallocating;156    fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating157    fp_nonallocating = unannotated;158    // ^ warning: attribute 'nonallocating' should not be added via type conversion159  }160 161Virtual methods162---------------163 164In C++, when a virtual method has a performance constraint, overriding methods in165subclasses inherit the constraint.166 167.. code-block:: c++168 169  struct Base {170    virtual void unsafe();171    virtual void safe() noexcept [[clang::nonblocking]];172  };173 174  struct Derived : public Base {175    void unsafe() [[clang::nonblocking]] override;176    // It's okay for an overridden method to be more constrained177 178    void safe() noexcept override;179    // This method is implicitly declared `nonblocking`, inherited from Base.180  };181 182Redeclarations, overloads, and name mangling183--------------------------------------------184 185The ``nonblocking`` and ``nonallocating`` attributes, like ``noexcept``, do not factor into186argument-dependent lookup and overloaded functions/methods.187 188First, consider that ``noexcept`` is integral to a function's type:189 190.. code-block:: c++191 192  void f1(int);193  void f1(int) noexcept;194  // error: exception specification in declaration does not match previous195  //   declaration196 197Unlike ``noexcept``, a redeclaration of ``f2`` with an added or stronger performance constraint is198legal and propagates the attribute to the previous declaration:199 200.. code-block:: c++201 202  int f2();203  int f2() [[clang::nonblocking]]; // redeclaration with stronger constraint is OK.204 205This greatly eases adoption by making it possible to annotate functions in external libraries206without modifying library headers.207 208A redeclaration with a removed or weaker performance constraint produces a warning, paralleling209the behavior of ``noexcept``:210 211.. code-block:: c++212 213  int f2() { return 42; }214  // warning: attribute 'nonblocking' on function does not match previous declaration215 216In C++14, the following two declarations of `f3` are identical (a single function). In C++17 they217are separate overloads:218 219.. code-block:: c++220 221  void f3(void (*)());222  void f3(void (*)() noexcept);223 224Similarly, the following two declarations of `f4` are separate overloads. This pattern may pose225difficulties due to ambiguity:226 227.. code-block:: c++228 229  void f4(void (*)());230  void f4(void (*)() [[clang::nonblocking]]);231 232The attributes have no effect on the mangling of function and method names.233 234Objective-C235-----------236 237The attributes are currently unsupported on Objective-C methods.238 239Analysis and warnings240=====================241 242Constraints243-----------244 245Functions declared ``nonallocating`` or ``nonblocking``, when defined, are verified according to the246following rules. Such functions:247 2481. May not allocate or deallocate memory on the heap. The analysis follows the calls to249   ``operator new`` and ``operator delete`` generated by the ``new`` and ``delete`` keywords, and250   treats them like any other function call. The global ``operator new`` and ``operator delete``251   aren't declared ``nonblocking`` or ``nonallocating`` and so they are considered unsafe. (This252   is correct because most memory allocators are not lock-free. Note that the placement form of253   ``operator new`` is implemented inline in libc++'s ``<new>`` header, and is verifiably254   ``nonblocking``, since it merely casts the supplied pointer to the result type.)255 2562. May not throw or catch exceptions. To throw, the compiler must allocate the exception on the257   heap. (Also, many subclasses of ``std::exception`` allocate a string). Exceptions are258   deallocated when caught.259 2603. May not make any indirect function call, via a virtual method, function pointer, or261   pointer-to-member function, unless the target is explicitly declared with the same262   ``nonblocking`` or ``nonallocating`` attribute (or stronger).263 2644. May not make direct calls to any other function, with the following exceptions:265 266  a. The callee is also explicitly declared with the same ``nonblocking`` or ``nonallocating``267     attribute (or stronger).268  b. The callee is defined in the same translation unit as the caller, does not have the ``false``269     form of the required attribute, and can be verified to have the same attribute or stronger,270     according to these same rules.271  c. The callee is a built-in function that is known not to block or allocate.272  d. The callee is declared ``noreturn`` and, if compiling C++, the callee is also declared273     ``noexcept``. This special case excludes functions such as ``abort()`` and ``std::terminate()``274     from the analysis. (The reason for requiring ``noexcept`` in C++ is that a function declared275     ``noreturn`` could be a wrapper for ``throw``.)276 2775. May not invoke or access an Objective-C method or property, since ``objc_msgSend()`` calls into278   the Objective-C runtime, which may allocate memory or otherwise block.279 2806. May not access thread-local variables. Typically, thread-local variables are allocated on the281   heap when first accessed.282 283Functions declared ``nonblocking`` have an additional constraint:284 2857. May not declare static local variables (e.g. Meyers singletons). The compiler generates a lock286   protecting the initialization of the variable.287 288Violations of any of these rules result in warnings, in the ``-Wfunction-effects`` category:289 290.. code-block:: c++291 292  void notInline();293 294  void example() [[clang::nonblocking]]295  {296    auto* x = new int;297    // warning: function with 'nonblocking' attribute must not allocate or deallocate298    //   memory299 300    if (x == nullptr) {301      static Logger* logger = createLogger();302      // warning: function with 'nonblocking' attribute must not have static local variables303 304      throw std::runtime_warning{ "null" };305      // warning: 'nonblocking" function 'example' must not throw exceptions306    }307    notInline();308    // warning: 'function with 'nonblocking' attribute must not call non-'nonblocking' function309    //   'notInline'310    // note (on notInline()): declaration cannot be inferred 'nonblocking' because it has no311    //   definition in this translation unit312  }313 314Inferring ``nonblocking`` or ``nonallocating``315----------------------------------------------316 317In the absence of a ``nonblocking`` or ``nonallocating`` attribute (whether ``true`` or ``false``),318a function that is called from a performance-constrained function may be analyzed to319infer whether it has a desired attribute. This analysis happens when the function is not a virtual320method, and it has a visible definition within the current translation unit (i.e. its body can be321traversed).322 323.. code-block:: c++324 325  void notInline();326  int implicitlySafe() { return 42; }327  void implicitlyUnsafe() { notInline(); }328 329  void example() [[clang::nonblocking]]330  {331    int x = implicitlySafe(); // OK332    implicitlyUnsafe();333    // warning: function with 'nonblocking' attribute must not call non-'nonblocking' function334    //   'implicitlyUnsafe'335    // note (on implicitlyUnsafe): function cannot be inferred 'nonblocking' because it calls336    //   non-'nonblocking' function 'notInline'337    // note (on notInline()): declaration cannot be inferred 'nonblocking' because it has no338    //   definition in this translation unit339  }340 341Lambdas and blocks342------------------343 344As mentioned earlier, the performance constraint attributes apply only to a single function and not345to any code nested inside it, including blocks, lambdas, and local classes. It is possible for a346nonblocking function to schedule the execution of a blocking lambda on another thread. Similarly, a347blocking function may create a ``nonblocking`` lambda for use in a realtime context.348 349Operations which create, destroy, copy, and move lambdas and blocks are analyzed in terms of the350underlying function calls. For example, the creation of a lambda with captures generates a function351call to an anonymous struct's constructor, passing the captures as parameters.352 353Implicit function calls in the AST354----------------------------------355 356The ``nonblocking`` / ``nonallocating`` analysis occurs at the Sema phase of analysis in Clang.357During Sema, there are some constructs which will eventually become function calls, but do not358appear as function calls in the AST. For example, ``auto* foo = new Foo;`` becomes a declaration359containing a ``CXXNewExpr`` which is understood as a function call to the global ``operator new``360(in this example), and a ``CXXConstructExpr``, which, for analysis purposes, is a function call to361``Foo``'s constructor. Most gaps in the analysis would be due to incomplete knowledge of AST362constructs which become function calls.363 364Disabling diagnostics365---------------------366 367Function effect diagnostics are controlled by ``-Wfunction-effects``.368 369A construct like this can be used to exempt code from the checks described here:370 371.. code-block:: c++372 373  #define NONBLOCKING_UNSAFE(...)                                    \374    _Pragma("clang diagnostic push")                                 \375    _Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \376    _Pragma("clang diagnostic ignored \"-Wfunction-effects\"")       \377    __VA_ARGS__                                                      \378    _Pragma("clang diagnostic pop")379 380Disabling the diagnostic allows for:381 382- constructs which do block, but which in practice are used in ways to avoid unbounded blocking,383  e.g. a thread pool with semaphores to coordinate multiple realtime threads;384- using libraries which are safe but not yet annotated;385- incremental adoption in a large codebase.386 387Adoption388========389 390There are a few common issues that arise when adopting the ``nonblocking`` and ``nonallocating``391attributes.392 393C++ exceptions394--------------395 396Exceptions pose a challenge to the adoption of the performance constraints. Common library functions397which throw exceptions include:398 399+----------------------------------+-----------------------------------------------------------------------+400| Method                           | Alternative                                                           |401+==================================+=======================================================================+402| ``std::vector<T>::at()``         | ``operator[](size_t)``, after verifying that the index is in range.   |403+----------------------------------+-----------------------------------------------------------------------+404| ``std::optional<T>::value()``    | ``operator*``, after checking ``has_value()`` or ``operator bool()``. |405+----------------------------------+-----------------------------------------------------------------------+406| ``std::expected<T, E>::value()`` | Same as for ``std::optional<T>::value()``.                            |407+----------------------------------+-----------------------------------------------------------------------+408 409 410``std::function<R(Args...)>``411-----------------------------412 413``std::function<R(Args...)>`` is generally incompatible with ``nonblocking`` and ``nonallocating``414code, because a typical implementation may allocate heap memory in the constructor.415 416Alternatives:417 418- ``std::function_ref`` (available in C++26 or as ``llvm::function_ref``). This is appropriate and419  optimal when a functor's lifetime does not need to extend past the function that created it.420 421- ``inplace_function`` from WG14. This solves the allocation problem by giving the functor wrapper422  a fixed size known at compile time and using an inline buffer.423 424While these alternatives both address the heap allocation of ``std::function``, they are still425obstacles to ``nonblocking/nonallocating`` verification, for reasons detailed in the next section.426 427 428Interactions with type-erasure techniques429-----------------------------------------430 431``std::function<R(Args...)>`` illustrates a common C++ type-erasure technique. Using template432argument deduction, it decomposes a function type into its return and parameter types. Additional433components of the function type, including ``noexcept``, ``nonblocking``, ``nonallocating``, and any434other attributes, are discarded.435 436Standard library support for these components of a function type is not immediately forthcoming.437 438Code can work around this limitation in either of two ways:439 4401. Avoid abstractions like ``std::function`` and instead work directly with the original lambda type.441 4422. Create a specialized alternative, e.g. ``nonblocking_function_ref<R(Args...)>`` where all function443   pointers used in the implementation and its interface are ``nonblocking``.444 445As an example of the first approach, when using a lambda as a *Callable* template parameter, the446attribute is preserved:447 448.. code-block:: c++449 450  std::sort(vec.begin(), vec.end(),451    [](const Elem& a, const Elem& b) [[clang::nonblocking]] { return a.mem < b.mem; });452 453Here, the type of the ``Compare`` template parameter is an anonymous class generated from the454lambda, with an ``operator()`` method holding the ``nonblocking`` attribute.455 456A complication arises when a *Callable* template parameter, instead of being a lambda or class457implementing ``operator()``, is a function pointer:458 459.. code-block:: c++460 461  static bool compare_elems(const Elem& a, const Elem& b) [[clang::nonblocking]] {462    return a.mem < b.mem; };463 464  std::sort(vec.begin(), vec.end(), compare_elems);465 466Here, the type of ``compare_elems`` is decomposed to ``bool(const Elem&, const Elem&)``, without467``nonblocking``, when forming the template parameter. This can be solved using the second approach,468creating a specialized alternative which explicitly requires the attribute. In this case, it's469possible to use a small wrapper to transform the function pointer into a functor:470 471.. code-block:: c++472 473  template <typename>474  class nonblocking_fp;475 476  template <typename R, typename... Args>477  class nonblocking_fp<R(Args...)> {478  public:479    using impl_t = R (*)(Args...) [[clang::nonblocking]];480 481  private:482    impl_t mImpl{ nullptr_t };483  public:484    nonblocking_fp() = default;485    nonblocking_fp(impl_t f) : mImpl{ f } {}486 487    R operator()(Args... args) const488    {489      return mImpl(std::forward<Args>(args)...);490    }491  };492 493  // deduction guide (like std::function's)494  template< class R, class... ArgTypes >495  nonblocking_fp( R(*)(ArgTypes...) ) -> nonblocking_fp<R(ArgTypes...)>;496 497  // --498 499  // Wrap the function pointer in a functor which preserves ``nonblocking``.500  std::sort(vec.begin(), vec.end(), nonblocking_fp{ compare_elems });501 502Now, the ``nonblocking`` attribute of ``compare_elems`` is verified when it is converted to a503``nonblocking`` function pointer, as the argument to ``nonblocking_fp``'s constructor. The template504parameter is the functor class ``nonblocking_fp``.505 506 507Static local variables508----------------------509 510Static local variables are often used for lazily-constructed globals (Meyers singletons). Beyond the511compiler's use of a lock to ensure thread-safe initialization, it is dangerously easy to512inadvertently trigger initialization, involving heap allocation, from a ``nonblocking`` or513``nonallocating`` context.514 515Generally, such singletons need to be replaced by globals, and care must be taken to ensure their516initialization before they are used from ``nonblocking`` or ``nonallocating`` contexts.517 518 519Annotating libraries520--------------------521 522It can be surprising that the analysis does not depend on knowledge of any primitives; it simply523assumes the worst, that all function calls are unsafe unless explicitly marked as safe or able to be524inferred as safe. With ``nonblocking``, this appears to suffice for all but the most primitive of525spinlocks.526 527At least for an operating system's C functions, it is possible to define an override header which528redeclares safe common functions (e.g. ``pthread_self()``) with the addition of ``nonblocking``.529This may help in adopting the feature incrementally.530 531It also helps that many of the functions in the standard C libraries (notably ``<math.h>``)532are treated as built-in functions by Clang, which the diagnosis understands to be safe.533 534Much of the C++ standard library consists of inline templated functions which work well with535inference. A small number of primitives may need explicit ``nonblocking/nonallocating`` attributes.536