<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">Brady asked:<div class=""><br class=""></div><div class=""><div class=""><blockquote type="cite" class="">Have you identified any situation where explicitly calling out the type in a range-based for loop has been better than using the proper form of auto?</blockquote></div><div class=""><div class=""></div></div><blockquote type="cite" class=""><div class=""><div class="">Have you identified a situation where explicitly calling out a nasty templated type, like in my example, added to readability over using auto?</div></div></blockquote><div class=""><br class=""></div><div class="">Darin asked:</div><div class=""><br class=""></div><div class=""><blockquote type="cite" class="">I’d love to see examples where using auto substantially hurts readability so we could debate them.<br class=""></blockquote></div><div class=""><br class=""></div><div class="">In many places, I agree that auto is better. &nbsp;But there are a bunch of algorithms in the compiler and GC where knowing the type helps me read the code more quickly. &nbsp;Here are a few examples. &nbsp;(1) is a loop, (2) is loop-like, and (3) is a nasty templated type.</div><div class=""><br class=""></div><div class=""><b class="">1) B3 compiler code cloning loop.</b></div><div class=""><b class=""><br class=""></b></div><div class="">I find that this code is easier to read with types:</div><div class=""><b class=""><br class=""></b></div><div class=""><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Value* clone = m_proc.clone(value);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (Value*&amp; child : clone-&gt;children()) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (Value* newChild = mappings[i].get(child))</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; child = newChild;</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (value-&gt;type() != Void)</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mappings[i].add(value, clone);</div><div class=""><br class=""></div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cases[i]-&gt;append(clone);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (value-&gt;type() != Void)</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cases[i]-&gt;appendNew&lt;UpsilonValue&gt;(m_proc, value-&gt;origin(), clone, value);</div><div class=""><br class=""></div><div class="">Here's another code cloning loop I found - sort of the same basic algorithm:</div><div class=""><br class=""></div><div class=""><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (Value* value : *tail) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Value* clone = m_proc.clone(value);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (Value*&amp; child : clone-&gt;children()) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (Value* replacement = map.get(child))</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; child = replacement;</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (value-&gt;type() != Void)</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; map.add(value, clone);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; block-&gt;append(clone);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div></div><div class="" style="font-weight: bold;"><br class=""></div><div class="">When reading this code, it's pretty important to know that value, clone, child, newChild, and replacement are all Values. &nbsp;As soon as you know this piece of information the algorithm - its purpose and how it functions - becomes clear. &nbsp;You can infer this information if you know where to look - m_proc.clone() and clone-&gt;children() are give-aways - but this isn't as immediately obvious as the use of the type name.</div><div class=""><br class=""></div><div class="">If someone refactored this code to use auto, it would be harder for me to read this code. &nbsp;I would spend more time reading it than I would have spent if it spelled out the type. &nbsp;I like seeing the type spelled out because that's how I recognize if the loop is over blocks, values, or something else. &nbsp;I like to spell out the type even when it's super obvious:</div><div class=""><br class=""></div><div class=""><div class="">&nbsp; &nbsp; &nbsp; &nbsp; for (BasicBlock* block : m_proc) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (Value* value : *block) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (value-&gt;opcode() == Phi &amp;&amp; candidates.contains(block))</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; valuesToDemote.add(value);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (Value* child : value-&gt;children()) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (child-&gt;owner != block &amp;&amp; candidates.contains(child-&gt;owner))</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; valuesToDemote.add(child);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; }</div></div><div class=""><br class=""></div><div class="">Sticking to this format for compiler loops means that I spend less time reading the code, because I can recognize important patterns at a glance.</div><div class=""><br class=""></div><div class=""><b class="">2) Various GC loops</b></div><div class=""><b class=""><br class=""></b></div><div class="">The GC usually loops using lambdas, but the same question comes into play: is the value's type auto or is it spelled out?</div><div class=""><br class=""></div><div class=""><div class="">&nbsp; &nbsp; forEachFreeCell(</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; freeList,</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; [&amp;] (HeapCell* cell) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (false)</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dataLog("Free cell: ", RawPointer(cell), "\n");</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (m_attributes.destruction == NeedsDestruction)</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; cell-&gt;zap();</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; clearNewlyAllocated(cell);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; });</div></div><div class=""><br class=""></div><div class="">It's useful to know that 'cell' is a HeapCell, not a FreeCell or a JSCell. &nbsp;I believe that this code will only compile if you say HeapCell. &nbsp;Combined with the function name, this tells you that this is a cell that is free, but not necessarily on a free-list ('cause then it would be a FreeCell). &nbsp;This also tells you that the cell wasn't necessarily a JSCell before it was freed - it could have been a raw backing store. &nbsp;That's important because then we don't have a guarantee about the format of its header.</div><div class=""><br class=""></div><div class="">I think that spelling out the type really helps here. &nbsp;In the GC, we often assert everything, everywhere, all the time. &nbsp;Typical review comes back with suggestions for more assertions. &nbsp;Types are a form of assertion, so they are consistent with how we hack the GC.</div><div class=""><br class=""></div><div class=""><b class="">3) AirEmitShuffle</b></div><div class=""><b class=""><br class=""></b></div><div class="">My least favorite part of compilers is the shuffle. &nbsp;That's the algorithm that figures out how to move data from one set of registers to another, where the sets may overlap.</div><div class=""><br class=""></div><div class="">It has code like this:</div><div class=""><br class=""></div><div class=""><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; while (Arg src = worklist.pop()) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; HashMap&lt;Arg, Vector&lt;ShufflePair&gt;&gt;::iterator iter = mapping.find(src);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (iter == mapping.end()) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // With a shift it's possible that we previously built the tail of this shift.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // See if that's the case now.</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (verbose)</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dataLog("Trying to append shift at ", src, "\n");</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; currentPairs.appendVector(shifts.take(src));</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; continue;</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Vector&lt;ShufflePair&gt; pairs = WTFMove(iter-&gt;value);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mapping.remove(iter);</div><div class=""><br class=""></div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (const ShufflePair&amp; pair : pairs) {</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; currentPairs.append(pair);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ASSERT(pair.src() == src);</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; worklist.push(pair.dst());</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div><div class="">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }</div></div><div class=""><br class=""></div><div class="">I sure appreciate the type of that iterator. &nbsp;I totally agree that in 99% of cases, the type of an iterator should be auto because you really don't want to know its type.</div><div class=""><br class=""></div><div class="">But in this code, seeing the type of that iterator makes it much easier to tell at a glance what the code is doing. &nbsp;Without that type, I'd have to scroll around to see what mapping really is. &nbsp;It's great to know that the HashMap maps Args to Vector&lt;ShufflePair&gt;, since this reassures me that there are no fancy conversions when src is used as a key and when the iter-&gt;value is WTFMoved to my pairs variable.</div><div class=""><br class=""></div><div class="">Oh, and if the while loop used auto instead of Arg, I'd be super confused. &nbsp;In the compiler, I think that loops that benefit from explicit type far outnumber loops that don't.</div><div class=""><br class=""></div><div class="">-Filip</div></div></div></body></html>