<div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr">On Fri, Jan 18, 2019 at 10:44 AM Kostya Serebryany via cfe-dev <<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a>> wrote:<br></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Wed, Jan 16, 2019 at 7:58 PM Richard Smith <<a href="mailto:richard@metafoo.co.uk" target="_blank">richard@metafoo.co.uk</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="auto"><div><div class="gmail_quote"><div dir="ltr">On Wed, 16 Jan 2019, 19:35 Kostya Serebryany via cfe-dev <<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir="auto"><div dir="auto"><br><br></div><div dir="auto">Should main be involved, though? </div></div></blockquote><div><br></div><div>How else?</div></div></div></blockquote></div></div><div dir="auto"><br></div><div dir="auto">By putting the initialisation where it belongs, in the constructor.</div></div></blockquote><div><br></div><div>As already discussed below, initializing in CTORs will also initialize heap memory, </div><div>i.e. will have different performance characteristics. </div><div>Still a good idea. </div></div></div></blockquote><div><br></div><div>Hi folks,</div><div><br></div><div>I've been investigating the approach of moving the initialization into the constructors that we've been discussing on this thread, and I've hit a problem that would prevent us from arranging for the constructor prologue to always pattern initialize. The problem is related to objects with static storage duration (i.e. globals), and it has to do with the following clause from C++17 [basic.start.static]p2:</div><div><br></div><div>"If constant initialization is not performed, a variable with static storage duration (6.7.1) or thread storage duration (6.7.2) is zero-initialized (11.6). Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. All static initialization strongly happens before (4.7.1) any dynamic initialization."</div><div><br></div><div>What this essentially means is that an object with static storage duration is initialized twice: once with zeros (i.e. static initialization) and once with the constructor (i.e. dynamic initialization). This means that the code in the constructor is allowed to rely on the object having first been zero initialized, provided that the object has static storage duration. Real-world code, such as Chromium's SpinLock class:</div><div><div><a href="https://cs.chromium.org/chromium/src/third_party/tcmalloc/chromium/src/base/spinlock.h?l=52" target="_blank">https://cs.chromium.org/chromium/src/third_party/tcmalloc/chromium/src/base/spinlock.h?l=52</a></div></div><div>relies on this guarantee.</div><div><br></div><div>What I think this means is that, in order to avoid creating a dialect of C++ in which globals are not first zero initialized, we would need to teach the compiler to emit a separate set of constructors that perform pattern initialization and use the non-initializing constructors to initialize globals. Since we need to emit two sets of constructors anyway, we may as well also use the non-initializing constructors to initialize heap objects in order to reduce overhead. I've prototyped this and found that, despite the fact that we would be emitting more constructor variants into object files, there is still a code size improvement because the extra constructor copies can typically be eliminated by the linker, either via --gc-sections (for objects that are only constructed on the stack or only on the heap or in globals) or ICF (in the case where the constructor fully initializes its object). In Chromium for Android, I've observed a binary size decrease of 400KB/0.5% for 64-bit ARM, and 290KB/0.7% for 32-bit ARM, versus pattern initialization with the current approach. (That's a bit of a lie, though. In both the before and after compilers, I changed the compiler to emit a call to memset in emitStoresForPatternInit instead of calling emitStoresForConstant, as I've done here: <a href="https://github.com/pcc/llvm-project/tree/tvi-memset">https://github.com/pcc/llvm-project/tree/tvi-memset</a> . I've found that in Chromium as well as in the Linux kernel, this also results in substantial binary size savings. But I'll cover that topic separately.)</div><div><br></div><div>Since this change involves new ABI, it would need to be opted into with a command line flag. Note that a library compiled with pattern initializing constructors may be used by code compiled without pattern initializing constructors, so it isn't a total ABI break. So if libc++ were compiled with this flag, for example, it could still be used by applications compiled without it. If the user does not opt into pattern initializing constructors, the behaviour would be as it is now: we would need to rely as much as we can on dead store elimination and other optimizations.</div><div><br></div><div>It's also worth noting that this problem is exclusive to pattern initialization. For zero initialization we would only need one set of constructors which always zero initialize because, upon entry, a constructor may assume that either the allocated memory is uninitialized (in the non-global case) or zero (in the global case), making the zero initialization a no-op in the global case.</div><div><br></div><div>To produce the pattern initializing constructors, as well as the pattern initialization for stack variables, we perform initialization as usual, calling into the pattern constructors where possible. The frontend keeps track of the calls to pattern constructors; each call will punch a hole in the initialization that the frontend will later insert in the prologue. In my prototype, this works quite well.</div><div><br></div><div>It has also been suggested that, as an alternative to punching holes in the frontend, we could make dead store elimination smart enough that it can punch holes itself. But given what this would actually entail, I do not consider this to be the correct approach due to the complexities involved. Consider the following code:</div><div><br></div><div><div><font face="monospace, monospace">A *the_a;</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">struct A {</font></div><div><font face="monospace, monospace">  A() : foo(1) { // assume that all ctors are actually out-of-line</font></div><div><font face="monospace, monospace">    the_a = this;</font></div><div><font face="monospace, monospace">  }</font></div><div><font face="monospace, monospace">  size_t foo;</font></div><div><font face="monospace, monospace">};</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">struct B {</font></div><div><font face="monospace, monospace">  B() : bar(2) {}</font></div><div><font face="monospace, monospace">  size_t bar;</font></div><div><font face="monospace, monospace">};</font></div><div><font face="monospace, monospace"><br></font></div><div><font face="monospace, monospace">struct C : A, B {</font></div><div><font face="monospace, monospace">  C() : baz(3) {}</font></div><div><font face="monospace, monospace">  size_t baz;</font></div><div><font face="monospace, monospace">};</font></div></div><div><br></div><div>Our goal is to eliminate the memset in the following code:<br></div><div><br></div><div><div><font face="monospace, monospace">C c;</font></div><div><font face="monospace, monospace">memset(&c, 0xaa, sizeof(C));</font></div><div><font face="monospace, monospace">c.C();</font></div><div><br></div><div>The problem that the optimizer has is that it needs to prove that none of the constructors read from c before it is fully initialized. Note that a pointer to c escapes from the A constructor, so the <font face="monospace, monospace">written</font> attribute that JF proposed upthread is not sufficient for expressing that the initialization is never read by the B constructor, since at the IR level there's nothing stopping the B constructor from, for example, reading a pointer from the_a, static_casting it to type C and reading the pattern from baz. Such a static_cast results in undefined behaviour according to the C++ standard, but only until the object is fully initialized. So we could conceivably introduce an attribute that means that, if the pointer does escape, it may not result in an out-of-bounds read before the object is fully initialized, and relying on the language guarantee, we could attach it to the this pointer of every constructor. But then we'd need a way of associating the attribute with the point at which the object is fully initialized (with nested or inlined initialization there could be many), LLVM attributes are not well suited to expressing this, and it would mean introducing an attribute with very subtle (and language specific) semantics.</div><div><br></div><div>Alternatively, we could relax this to an attribute that means whether the pointer escapes at all, but this could not be applied to every constructor (which means that we'd need an analysis to propagate it), and we'd be leaving some optimization potential on the table because we aren't taking into account the semantics of construction lifetimes. It seems fine to do this as a local analysis for dealing with C-like initialization functions that are emitted into the same translation unit but not inlined (e.g. because of -Oz), but the scary part is trying to propagate it through something like a ThinLTO summary. I think that the way that this would need to work is that you'd need some way of expressing "if argument A does not escape in this function, argument B does not escape from its callers", and this seems very tricky to get right. We would need a similar approach for the <font face="monospace, monospace">written</font> attribute if we did not have pattern constructor variants.<br></div></div><div><br></div><div>In the end, we know what IR we want to end up with for a constructor call, so it makes far more sense to me to simply have the frontend generate the IR that we want using its knowledge of the language semantics, instead of relying on a complex analysis to produce it for us. And given that we are trying to ensure a security property, the analysis should be as simple as reasonably possible.</div><div><br></div><div>Thanks,</div></div>-- <br><div dir="ltr" class="gmail-m_-3302084362095392845m_1868036400448883750gmail-m_340448818562119482gmail-m_7472163906676764605gmail_signature"><div dir="ltr">-- <div>Peter</div></div></div></div></div></div></div></div></div></div></div></div>