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