<div dir="ltr">On Thu, Mar 15, 2018 at 6:44 PM, Nick Lewycky <span dir="ltr">&lt;<a href="mailto:nlewycky@google.com" target="_blank">nlewycky@google.com</a>&gt;</span> wrote:<br><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_quote"><span class="gmail-m_7149448886896573704gmail-"><div dir="ltr"><br></div></span><div>As-if transformations maintain the actions of the written program under the assumption that undefined behaviour does not occur. The two are fundamentally intertwined.</div><div><br></div><div>Here&#39;s a few thought experiments: [...]</div><div>Today, all these optimizations are justified by the fact that any attempt to observe the difference before and after optimization must necessarily execute undefined behaviour, and executing undefined behaviour is a priori assumed to be impossible.<br></div><div><br></div><div>It&#39;s possible that there&#39;s another way to show that these and other optimizations are valid, but I would need to see a proposal on how to do that. I&#39;ve tried to create one myself and failed. Frankly, I&#39;m starting to believe that our current situation with UB really is the best possible design choice.</div></div></div></blockquote><div><br></div><div>This is beautiful, by the way. Hear hear.</div><div><br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_quote"><span class="gmail-m_7149448886896573704gmail-"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_extra"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_quote"><div><div style="color:rgb(34,34,34);font-family:sans-serif;font-size:13px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;background-color:rgb(255,255,255)">Can you fix this without adding a listing of CPU instructions to the language standard</div></div></div></div></blockquote><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);padding-left:1ex"><div dir="ltr"><div class="gmail_quote"><div><div style="color:rgb(34,34,34);font-family:sans-serif;font-size:13px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;text-align:start;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;background-color:rgb(255,255,255)">and without fully defining it?</div></div></div></div></blockquote><div><br>I don&#39;t need to.  The C++ Standard already specifies that atomic integers obey the rules<br>of 2&#39;s-complement arithmetic, and it doesn&#39;t spell out what those rules are.<br></div></div></div></div></blockquote><div><br></div></span><div>I would consider it shall &quot;obey the rules of 2&#39;s complement arithmetic&quot; to be fully defined, as 1&#39;s complement machines would have to simulate 2&#39;s complement arithmetic. (Offhand, I think this is what happens with atomics today; 1&#39;s complement machines are expected to use a library call to implement the atomic and ensure 2&#39;s complement?)</div></div></div></blockquote><div><br></div><div>This is relevant to JF&#39;s <a href="http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r0.html" target="_blank">P0907R0</a> (and to <a href="https://quuxplusone.github.io/draft/twosc-conservative.html" target="_blank">my UB-preserving fork</a> too), so I&#39;m curious to explore this.</div><div>What N4700 says on the subject is...</div><div><br></div><div>&gt; There are specializations of the `atomic` template for the integral types [...] `int`, [...]</div><div>&gt;</div><div>&gt;     template&lt;&gt; struct atomic&lt;<i>integral</i>&gt; {</div><div>&gt;         [...]</div><div>&gt;         <i>integral</i> operator++() noexcept;</div><div>&gt;         <i>integral</i> operator+=(<i>integral</i>) noexcept;</div><div>&gt;         [...]</div><div>&gt;     };</div><div>&gt;</div><div>&gt; Descriptions are provided below only for members that differ from the primary template.</div><div>&gt; The following operations perform arithmetic computations. [... a table follows; it does not include operator++ ...]</div><div>&gt; [...]</div><div>&gt; T fetch_<i>key</i>(T operand, memory_order order = memory_order_seq_cst) noexcept;</div><div>&gt; [...]</div><div>&gt; Remarks: For signed integer types, arithmetic is defined to use two&#39;s complement representation. There are no undefined results.</div><div>&gt;</div><div>&gt; T operator <i>op</i>=(T operand) noexcept;</div><div>&gt; Effects: Equivalent to: return fetch_<i>key</i>(operand) <i>op</i> operand;</div><div><br></div><div>So, first of all, there&#39;s no official semantics provided for e.g.</div><div>    std::atomic&lt;int&gt; x(42);</div><div>    ++x;</div><div>But we can intuitively assume that `++x` should be equivalent to `x+=1`, and move on from there.</div><div>`x+=1` is officially defined to be equivalent to `x.fetch_add(1) + 1`.</div><div><br></div><div>Now things start breaking down, on non-two&#39;s-complement machines. We have to somehow make `x.fetch_add(1)` perform an operation which is equivalent to a &quot;two&#39;s complement&quot; addition, i.e., it needs to modify the bitwise representation of `x` in some way that corresponds to a two&#39;s-complement increment.</div><div>    std::atomic&lt;int&gt; y(INT_MAX);  // for the sake of argument, let&#39;s use a 16-bit sign-magnitude machine</div><div>    y+=1;</div><div>    y+=1;</div><div>This means that if we have the bit-pattern 0x7FFF (a 16-bit INT_MAX), incrementing it <i>must</i> produce the bit-pattern 0x8000, because that&#39;s what a two&#39;s complement addition would produce.<br></div><div>And then we do the second `+=`. It adds 1 to the bit-pattern 0x8000 and stores 0x8001 safely back into it.</div><div>But look at the return type of `y.fetch_add(1)`!  It is specified to return <i>integral</i>, i.e. int, i.e. it will return 0x8000, which is &quot;negative zero&quot; on this machine.</div><div>And then operator+= is specified to behave as-if `y.fetch_add(1) + 1`!<br></div><div>So we have &quot;negative zero&quot; 0x8000, and we&#39;re trying to &quot;+1&quot; to it, in the native ones&#39;-complement arithmetic. If negative zero is a trap representation, we will get a trap. Otherwise, we will observe that &quot;INT_MAX plus 1 plus 1&quot; is &quot;negative zero plus 1&quot; is int(1) — anyway, it is certainly not INT_MIN as we were naively hoping!</div><div><br></div><div>The (intermediate) result is that atomics do not visibly behave in a two&#39;s-complement manner on a ones&#39;-complement machine.</div><div><br></div><div>Now let&#39;s do the same operation on a two&#39;s-complement machine.</div><div><br></div><div>    std::atomic&lt;int&gt; y(INT_MAX);</div><div>    y+=1;</div><div><br></div><div>We add 1 to the bit-pattern 0x7FFF and store 0x8000 safely back into it.</div><div><div>But look at the return type of `y.fetch_add(1)`!  It is specified to return <i>integral</i>, i.e. int, i.e. it will return 0x7FFF, which is INT_MAX on this machine.</div></div><div><div>And then operator+= is specified to behave as-if `y.fetch_add(1) + 1`!<br></div></div><div>So we have &quot;INT_MAX&quot; 0x7FFF, and we&#39;re trying to &quot;+1&quot; to it, in the native arithmetic. If overflow checking is enabled, we will get a trap. In general, we have undefined behavior here.  (That is, fetch_add has defined behavior on overflow, but operator+= has undefined behavior on overflow.)<br></div><div><br></div><div>The (ultimate) result is that atomics do not visibly behave in a two&#39;s-complement manner even on a <i>two&#39;s</i>-complement machine.</div><div><br></div><div>Now, I don&#39;t think this is fatal to C++. I think this is fatal to <i>platforms attempting to implement a dialect of C++ where native `int` has an arithmetic other than two&#39;s complement</i>. I strongly support the idea of <a href="https://quuxplusone.github.io/draft/twosc-conservative.html">dropping</a> non-two&#39;s-complement arithmetic from C++ altogether. (And, incidentally, the wording for atomics is broken as demonstrated above; but that&#39;s a separate issue IMHO.)</div><div><br></div><div>–Arthur</div></div></div></div>