With this patch, llgo uses ssautil.Switches to reconstitute (and synthesise) switches, which can then be lowered to lookup tables, trees, etc. We currently only handle integer const case switches. We erase the comparison blocks (other than the initial block), and generate a switch instruction at the end of the block starting the if-else-if chain. ssautil.Switches does not remove duplicate const cases (e.g. same operands for "||"), so we do this in llgo for now.
Details
Diff Detail
Event Timeline
I wonder if there is a way to do this without modifying the go/ssa IR. In particular, I don't feel very comfortable about any attempt to define an instruction outside the ssa package. Maybe we can build a mapping to keep track of which ssa.If instructions are to be treated as switch instructions?
I believe LLVM already has a transformation to convert chains of branch instructions into switches, at least in some cases. Have you measured a performance improvement with this change at all? (Asking just out of curiosity. I think it would be worth exploring the performance impact of extending this to support type switches, so I have no objection to this change in principle.)
It is certainly possible to do that, and I did go down that road initially. I don't really like the kludginess of the current approach either, but the reason I did it this way was to keep it relatively self-contained. Aside from being (IMHO) a bit messy, adding a check to each "ssa.If" would mean we have a performance hit in the frontend for each If instruction, rather than just the ones that we care about. Maybe that's nothing to worry about; I haven't measured it.
I don't know if this will ever be necessary, but another option is for llgo to grow its own IR on top of go/ssa. I'd rather not go there for this, though.
I believe LLVM already has a transformation to convert chains of branch instructions into switches, at least in some cases. Have you measured a performance improvement with this change at all? (Asking just out of curiosity. I think it would be worth exploring the performance impact of extending this to support type switches, so I have no objection to this change in principle.)
Geo-mean improvement of 0.49% (0.23% - 0.75% @ 95% CI)
There seems to be a fair bit of noise in some of the tests, since I'm getting supposed improvements in BenchmarkAppendGrowString, and BenchmarkTCP6ConcurrentReadWrite. One significant improvement that looks genuine is:
BenchmarkCSSEscaper 2983.000000 2476.000000 1.204766
Standard optimisations do not appear to convert branches to switches AFAICT, but perhaps I'm doing something wrong. I did look to see if there was an existing pass, but again I couldn't see anything that looked relevant. There is a pass that goes the opposite direction, converting switches to branches, for targets that don't implement switch.
adding a check to each "ssa.If" would mean we have a performance hit in the frontend for each If instruction
I very much doubt that the performance impact would be measurable. Remember that the switch analysis has to look at each if instruction anyway.
Regardless, I agree that both approaches are a little messy, and I can't see a neater way without modifying go/ssa, so LGTM.
I don't know if this will ever be necessary, but another option is for llgo to grow its own IR on top of go/ssa. I'd rather not go there for this, though.
Agreed. FWIW, the long term direction I'd like to see is attempting to extend go/ssa with what we need (in this particular case, a native switch instruction).
I did look to see if there was an existing pass, but again I couldn't see anything that looked relevant
If you grep for SimplifyCFGOpt::FoldValueComparisonIntoPredecessors you should find something.
Geo-mean improvement of 0.49% (0.23% - 0.75% @ 95% CI)
Great! I'm a little surprised that we can do better than LLVM on its own, but maybe I don't understand what the simplifycfg code is doing.