Brady asked:

> 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?

> Have you identified a situation where explicitly calling out a nasty 
> templated type, like in my example, added to readability over using auto?

Darin asked:

> I’d love to see examples where using auto substantially hurts readability so 
> we could debate them.


In many places, I agree that auto is better.  But there are a bunch of 
algorithms in the compiler and GC where knowing the type helps me read the code 
more quickly.  Here are a few examples.  (1) is a loop, (2) is loop-like, and 
(3) is a nasty templated type.

1) B3 compiler code cloning loop.

I find that this code is easier to read with types:

                Value* clone = m_proc.clone(value);
                for (Value*& child : clone->children()) {
                    if (Value* newChild = mappings[i].get(child))
                        child = newChild;
                }
                if (value->type() != Void)
                    mappings[i].add(value, clone);

                cases[i]->append(clone);
                if (value->type() != Void)
                    cases[i]->appendNew<UpsilonValue>(m_proc, value->origin(), 
clone, value);

Here's another code cloning loop I found - sort of the same basic algorithm:

            for (Value* value : *tail) {
                Value* clone = m_proc.clone(value);
                for (Value*& child : clone->children()) {
                    if (Value* replacement = map.get(child))
                        child = replacement;
                }
                if (value->type() != Void)
                    map.add(value, clone);
                block->append(clone);
            }

When reading this code, it's pretty important to know that value, clone, child, 
newChild, and replacement are all Values.  As soon as you know this piece of 
information the algorithm - its purpose and how it functions - becomes clear.  
You can infer this information if you know where to look - m_proc.clone() and 
clone->children() are give-aways - but this isn't as immediately obvious as the 
use of the type name.

If someone refactored this code to use auto, it would be harder for me to read 
this code.  I would spend more time reading it than I would have spent if it 
spelled out the type.  I like seeing the type spelled out because that's how I 
recognize if the loop is over blocks, values, or something else.  I like to 
spell out the type even when it's super obvious:

        for (BasicBlock* block : m_proc) {
            for (Value* value : *block) {
                if (value->opcode() == Phi && candidates.contains(block))
                    valuesToDemote.add(value);
                for (Value* child : value->children()) {
                    if (child->owner != block && 
candidates.contains(child->owner))
                        valuesToDemote.add(child);
                }
            }
        }

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.

2) Various GC loops

The GC usually loops using lambdas, but the same question comes into play: is 
the value's type auto or is it spelled out?

    forEachFreeCell(
        freeList,
        [&] (HeapCell* cell) {
            if (false)
                dataLog("Free cell: ", RawPointer(cell), "\n");
            if (m_attributes.destruction == NeedsDestruction)
                cell->zap();
            clearNewlyAllocated(cell);
        });

It's useful to know that 'cell' is a HeapCell, not a FreeCell or a JSCell.  I 
believe that this code will only compile if you say HeapCell.  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).  This also 
tells you that the cell wasn't necessarily a JSCell before it was freed - it 
could have been a raw backing store.  That's important because then we don't 
have a guarantee about the format of its header.

I think that spelling out the type really helps here.  In the GC, we often 
assert everything, everywhere, all the time.  Typical review comes back with 
suggestions for more assertions.  Types are a form of assertion, so they are 
consistent with how we hack the GC.

3) AirEmitShuffle

My least favorite part of compilers is the shuffle.  That's the algorithm that 
figures out how to move data from one set of registers to another, where the 
sets may overlap.

It has code like this:

            while (Arg src = worklist.pop()) {
                HashMap<Arg, Vector<ShufflePair>>::iterator iter = 
mapping.find(src);
                if (iter == mapping.end()) {
                    // With a shift it's possible that we previously built the 
tail of this shift.
                    // See if that's the case now.
                    if (verbose)
                        dataLog("Trying to append shift at ", src, "\n");
                    currentPairs.appendVector(shifts.take(src));
                    continue;
                }
                Vector<ShufflePair> pairs = WTFMove(iter->value);
                mapping.remove(iter);

                for (const ShufflePair& pair : pairs) {
                    currentPairs.append(pair);
                    ASSERT(pair.src() == src);
                    worklist.push(pair.dst());
                }
            }

I sure appreciate the type of that iterator.  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.

But in this code, seeing the type of that iterator makes it much easier to tell 
at a glance what the code is doing.  Without that type, I'd have to scroll 
around to see what mapping really is.  It's great to know that the HashMap maps 
Args to Vector<ShufflePair>, since this reassures me that there are no fancy 
conversions when src is used as a key and when the iter->value is WTFMoved to 
my pairs variable.

Oh, and if the while loop used auto instead of Arg, I'd be super confused.  In 
the compiler, I think that loops that benefit from explicit type far outnumber 
loops that don't.

-Filip
_______________________________________________
webkit-dev mailing list
webkit-dev@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-dev

Reply via email to