Home | History | Annotate | Line # | Download | only in DesignDocs
UniquePtrTrivialAbi.rst revision 1.1
      1  1.1  joerg =============================================
      2  1.1  joerg Enable std::unique_ptr [[clang::trivial_abi]]
      3  1.1  joerg =============================================
      4  1.1  joerg 
      5  1.1  joerg Background
      6  1.1  joerg ==========
      7  1.1  joerg 
      8  1.1  joerg Consider the follow snippets
      9  1.1  joerg 
     10  1.1  joerg 
     11  1.1  joerg .. code-block:: cpp
     12  1.1  joerg 
     13  1.1  joerg     void raw_func(Foo* raw_arg) { ... }
     14  1.1  joerg     void smart_func(std::unique_ptr<Foo> smart_arg) { ... }
     15  1.1  joerg 
     16  1.1  joerg     Foo* raw_ptr_retval() { ... }
     17  1.1  joerg     std::unique_ptr<Foo*> smart_ptr_retval() { ... }
     18  1.1  joerg 
     19  1.1  joerg 
     20  1.1  joerg 
     21  1.1  joerg The argument ``raw_arg`` could be passed in a register but ``smart_arg`` could not, due to current
     22  1.1  joerg implementation.
     23  1.1  joerg 
     24  1.1  joerg Specifically, in the ``smart_arg`` case, the caller secretly constructs a temporary ``std::unique_ptr``
     25  1.1  joerg in its stack-frame, and then passes a pointer to it to the callee in a hidden parameter.
     26  1.1  joerg Similarly, the return value from ``smart_ptr_retval`` is secretly allocated in the caller and
     27  1.1  joerg passed as a secret reference to the callee.
     28  1.1  joerg 
     29  1.1  joerg 
     30  1.1  joerg Goal
     31  1.1  joerg ===================
     32  1.1  joerg 
     33  1.1  joerg ``std::unique_ptr`` is passed directly in a register.
     34  1.1  joerg 
     35  1.1  joerg Design
     36  1.1  joerg ======
     37  1.1  joerg 
     38  1.1  joerg * Annotate the two definitions of ``std::unique_ptr``  with ``clang::trivial_abi`` attribute.
     39  1.1  joerg * Put the attribuate behind a flag because this change has potential compilation and runtime breakages.
     40  1.1  joerg 
     41  1.1  joerg 
     42  1.1  joerg This comes with some side effects:
     43  1.1  joerg 
     44  1.1  joerg * ``std::unique_ptr`` parameters will now be destroyed by callees, rather than callers.
     45  1.1  joerg   It is worth noting that destruction by callee is not unique to the use of trivial_abi attribute.
     46  1.1  joerg   In most Microsoft's ABIs, arguments are always destroyed by the callee.
     47  1.1  joerg 
     48  1.1  joerg   Consequently, this may change the destruction order for function parameters to an order that is non-conforming to the standard.
     49  1.1  joerg   For example:
     50  1.1  joerg 
     51  1.1  joerg 
     52  1.1  joerg   .. code-block:: cpp
     53  1.1  joerg 
     54  1.1  joerg     struct A { ~A(); };
     55  1.1  joerg     struct B { ~B(); };
     56  1.1  joerg     struct C { C(A, unique_ptr<B>, A) {} };
     57  1.1  joerg     C c{{}, make_unique<B>, {}};
     58  1.1  joerg 
     59  1.1  joerg 
     60  1.1  joerg   In a conforming implementation, the destruction order for C::C's parameters is required to be ``~A(), ~B(), ~A()`` but with this mode enabled, we'll instead see ``~B(), ~A(), ~A()``.
     61  1.1  joerg 
     62  1.1  joerg * Reduced code-size.
     63  1.1  joerg 
     64  1.1  joerg 
     65  1.1  joerg Performance impact
     66  1.1  joerg ------------------
     67  1.1  joerg 
     68  1.1  joerg Google has measured performance improvements of up to 1.6% on some large server macrobenchmarks, and a small reduction in binary sizes.
     69  1.1  joerg 
     70  1.1  joerg This also affects null pointer optimization
     71  1.1  joerg 
     72  1.1  joerg Clang's optimizer can now figure out when a `std::unique_ptr` is known to contain *non*-null.
     73  1.1  joerg (Actually, this has been a *missed* optimization all along.)
     74  1.1  joerg 
     75  1.1  joerg 
     76  1.1  joerg .. code-block:: cpp
     77  1.1  joerg 
     78  1.1  joerg     struct Foo {
     79  1.1  joerg       ~Foo();
     80  1.1  joerg     };
     81  1.1  joerg     std::unique_ptr<Foo> make_foo();
     82  1.1  joerg     void do_nothing(const Foo&)
     83  1.1  joerg 
     84  1.1  joerg     void bar() {
     85  1.1  joerg       auto x = make_foo();
     86  1.1  joerg       do_nothing(*x);
     87  1.1  joerg     }
     88  1.1  joerg 
     89  1.1  joerg 
     90  1.1  joerg With this change, ``~Foo()`` will be called even if ``make_foo`` returns ``unique_ptr<Foo>(nullptr)``.
     91  1.1  joerg The compiler can now assume that ``x.get()`` cannot be null by the end of ``bar()``, because
     92  1.1  joerg the deference of ``x`` would be UB if it were ``nullptr``. (This dereference would not have caused
     93  1.1  joerg a segfault, because no load is generated for dereferencing a pointer to a reference. This can be detected with ``-fsanitize=null``).
     94  1.1  joerg 
     95  1.1  joerg 
     96  1.1  joerg Potential breakages
     97  1.1  joerg -------------------
     98  1.1  joerg 
     99  1.1  joerg The following breakages were discovered by enabling this change and fixing the resulting issues in a large code base.
    100  1.1  joerg 
    101  1.1  joerg - Compilation failures
    102  1.1  joerg 
    103  1.1  joerg  - Function definitions now require complete type ``T`` for parameters with type ``std::unique_ptr<T>``. The following code will no longer compile.
    104  1.1  joerg 
    105  1.1  joerg    .. code-block:: cpp
    106  1.1  joerg 
    107  1.1  joerg        class Foo;
    108  1.1  joerg        void func(std::unique_ptr<Foo> arg) { /* never use `arg` directly */ }
    109  1.1  joerg 
    110  1.1  joerg  - Fix: Remove forward-declaration of ``Foo`` and include its proper header.
    111  1.1  joerg 
    112  1.1  joerg - Runtime Failures
    113  1.1  joerg 
    114  1.1  joerg  - Lifetime of ``std::unique_ptr<>`` arguments end earlier (at the end of the callee's body, rather than at the end of the full expression containing the call).
    115  1.1  joerg 
    116  1.1  joerg    .. code-block:: cpp
    117  1.1  joerg 
    118  1.1  joerg      util::Status run_worker(std::unique_ptr<Foo>);
    119  1.1  joerg      void func() {
    120  1.1  joerg         std::unique_ptr<Foo> smart_foo = ...;
    121  1.1  joerg         Foo* owned_foo = smart_foo.get();
    122  1.1  joerg         // Currently, the following would "work" because the argument to run_worker() is deleted at the end of func()
    123  1.1  joerg         // With the new calling convention, it will be deleted at the end of run_worker(),
    124  1.1  joerg         // making this an access to freed memory.
    125  1.1  joerg         owned_foo->Bar(run_worker(std::move(smart_foo)));
    126  1.1  joerg                   ^
    127  1.1  joerg                  // <<<Crash expected here
    128  1.1  joerg      }
    129  1.1  joerg 
    130  1.1  joerg  - Lifetime of local *returned* ``std::unique_ptr<>`` ends earlier.
    131  1.1  joerg 
    132  1.1  joerg    Spot the bug:
    133  1.1  joerg 
    134  1.1  joerg     .. code-block:: cpp
    135  1.1  joerg 
    136  1.1  joerg      std::unique_ptr<Foo> create_and_subscribe(Bar* subscriber) {
    137  1.1  joerg        auto foo = std::make_unique<Foo>();
    138  1.1  joerg        subscriber->sub([&foo] { foo->do_thing();} );
    139  1.1  joerg        return foo;
    140  1.1  joerg      }
    141  1.1  joerg 
    142  1.1  joerg    One could point out this is an obvious stack-use-after return bug.
    143  1.1  joerg    With the current calling convention, running this code with ASAN enabled, however, would not yield any "issue".
    144  1.1  joerg    So is this a bug in ASAN? (Spoiler: No)
    145  1.1  joerg 
    146  1.1  joerg    This currently would "work" only because the storage for ``foo`` is in the caller's stackframe.
    147  1.1  joerg    In other words, ``&foo`` in callee and ``&foo`` in the caller are the same address.
    148  1.1  joerg 
    149  1.1  joerg ASAN can be used to detect both of these.
    150