This is an archive of the discontinued LLVM Phabricator instance.

[pseudo] Track heads as GSS nodes, rather than as "pending actions".
ClosedPublic

Authored by sammccall on Jun 21 2022, 12:23 PM.

Details

Summary

IMO this model is simpler to understand (borrowed from the LR0 patch D127357).
It also makes error recovery easier to implement, as we have a simple list of
head nodes lying around to recover from when needed.
(It's not quite as nice as LR0 in this respect though).

It's slightly slower (2.24 -> 2.12 MB/S on my machine = 5%) but nothing close
to as bad as LR0.
However

  • I think we'd have to eat a litle performance loss otherwise to implement error recovery.
  • this frees up some complexity budget for optimizations like fastpath push/pop (this + fastpath is already faster than head)
  • I haven't changed the data structure here and it's now pretty dumb, we can make it faster

Diff Detail

Event Timeline

sammccall created this revision.Jun 21 2022, 12:23 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 21 2022, 12:23 PM
sammccall requested review of this revision.Jun 21 2022, 12:23 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 21 2022, 12:23 PM

Thanks, this looks a reasonable change to me.

clang-tools-extra/pseudo/include/clang-pseudo/GLR.h
140

nit: change NewHeads to a pointer? it seems clearer that NewHeads is the output of the function from the caller side glrShift(OldHeads, ..., &NewHeads).

I think it would be clearer if glrShift just returns NewHeads, but I understand we want to avoid temporary object for performance reasons.

clang-tools-extra/pseudo/include/clang-pseudo/grammar/LRTable.h
133

the function signature (return type) of getGotoState and getShiftState is not symmetry, it is fine and by design (we never all getGoToState on an invalid Nonterminal, it is guaranteed by the LR parser, but we might call getShiftState on a dead head). It is probably worth a comment explicitly specifying the difference.

clang-tools-extra/pseudo/lib/GLR.cpp
67

I can see the benefit of keep them tight in the loop (and doing a shift before reduce), but I found the code is a bit confusing, it took me a while to understand.

  • the concept Heads and NextHeads are different for glrShift, and glrReduce -- The NextHeads returned by the glrShift should be the Heads used in glrReduce, so I was confused when reading glrReduce(Heads) the code at first glance -- until I realized that there is a swap(Heads, NextHeads) on L68...
  • it seems a little weird that at the end of the loop, we do a cleanup on NextHeads directly (I would image there is a swap(Head, NextHead) and then cleanup)

Instead of naming the two lists as Heads and NewHeads, how about naming them HeadsForShift (or ShiftHeads) and HeadsForReduce? the code would look like

for ( ... ) {
  glrShift(HeadsForShift, ..., &HeadsForReduce);
  ....
  glrReduce(HeadsForReduce, ..., &HeadsForShift);
}

It looks clearer to me.

69

I'd probably leave a blank line deliberately after glrShift, because the glrReduce work on the *next* I+1 token.

114

nit: add a NewHeads.empty assertion?

203–205

IIRC, in glrReduce, we only append newly-created GSS nodes in Heads, and never to do deleting, so the Heads will end up with lots of unnecessary heads (heads created for reduces), and we will call getShiftState on them.

We know that after glrReduce, active heads are heads with available shift actions, so we can optimize it further, we could just use a vector<GSS::Nodes> which just contains active heads , this could be done in the popPending. I think it will increase the performance by reducing the number of unnecessary heads.

267

might be name it PoppedHeadIndex?

361–362

the comment is stale now.

clang-tools-extra/pseudo/lib/grammar/LRTable.cpp
78

instead of using find directly, just use getActions().

79

nit: it is worth a comment saying that if there is a shift action, it must be exactly 1, this is guaranteed by the LR parser (no shift-shift conflict)

sammccall marked 6 inline comments as done.Jun 23 2022, 8:20 AM

As discussed offline, I'm going to land this patch as other approved optimizations are based on it, and there are no big objections.

Please feel free to keep discussing details here and I'll land followups.

clang-tools-extra/pseudo/include/clang-pseudo/GLR.h
140

I think that's only clearer if there's a convention that references are immutable and not used for output, and I don't think that convention exists in LLVM.
Maybe there's some association, but using a pointer can also communicate other things (like nullability) that we don't intend here.

clang-tools-extra/pseudo/lib/GLR.cpp
69

I think we have different mental models here.

With loop index I:

  • glrShift consumes token I
  • glrReduce consumes no tokens, just rearranges things

I think of glrReduce as:
a) not operating on a token, at least in the sense that glrShift does
b) being part of the work for token I, not token I+1 (it's consuming stack items corresponding to token I)

It happens to look ahead at token I+1, but I see that as mostly incidental: with LR(0) we wouldn't do that, with SLR(2) we'd look at both token I+1 and I+2. Token I+1 isn't fundamentally important.

(I'm happy to add the blank line, but wanted to clarify because this is one reason I see this formulation as simpler)

114

Why does this function care about that? It just appends to NewHeads.

203–205

Thanks for the offline discussion - I understand this better now!

You're right that at the moment we're doing a lookup that happens to yield this information. This is because shift + reduce info is stored in the same table.
This would make glrShift cheaper, which could be worth up to 9% now and up to 15% later (after glrReduce optimizations).

However D128318 splits these data structures apart to exploit different patterns in them (shift actions are sparser, reduces are denser but have patterns that allows them to be compressed).
I don't want to implement this now as that change is a bigger speedup (24%).

Fundamentally the idea is to avoid a single hashtable lookup. Storing one bit per (state, terminal) to see whether shift is possible is only 74kB, maybe a big std::bitset would work?

Added a FIXME to LRTable::getShiftState.

267

No, it points to the first head that *isn't* popped.
Renamed to NextPopHead

clang-tools-extra/pseudo/lib/grammar/LRTable.cpp
78

OK, but I think we should get rid of getActions soon.

sammccall updated this revision to Diff 439407.Jun 23 2022, 8:20 AM
sammccall marked 3 inline comments as done.

address review comments

This revision was not accepted when it landed; it landed in state Needs Review.Jun 23 2022, 8:27 AM
This revision was landed with ongoing or failed builds.
This revision was automatically updated to reflect the committed changes.
sammccall marked an inline comment as done.