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