go/ssa: emit short-circuit control flow for boolean switch cases
Previously, switch { case x && y: ... } would materialize the
condition as a boolean. In this case, because it contains &&,
it would be a phi node. This extra phi caused the yield analyzer
to emit false positives compared to the equivalent if/else chain.
Now, the SSA builder emits short-circuit control flow for boolean
switch cases. Happily, this crosses off a TODO that has been there
since day 1: https://go.dev/issue/77681#issuecomment-3969760732A
For example, this input:
func f(x, y bool) {
switch {
case x && y:
print("hello")
}
}
formerly generated this SSA code:
0:
if x goto 3 else 4
1:
return
2:
t0 = print("hello":string)
jump 1
3:
jump 4
4:
t1 = phi [0: false:bool, 3: y
if t1 goto 2 else 1
but now generates this code:
0:
if x goto 3 else 1
1:
return
2:
t0 = print("hello":string)
jump 1
3:
if y goto 2 else 1
+ regression test for gopls' yield analyzer
Fixes golang/go#77681
Change-Id: Ife9dee8443c0f2c09e95cacbfcefd5baacc97d99
Reviewed-on: https://go-review.googlesource.com/c/tools/+/749580
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Alan Donovan <adonovan@google.com>
Commit-Queue: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/ssa/builder.go b/go/ssa/builder.go
index a75257c..3336203 100644
--- a/go/ssa/builder.go
+++ b/go/ssa/builder.go
@@ -1467,13 +1467,14 @@
var nextCond *BasicBlock
for _, cond := range cc.List {
nextCond = fn.newBasicBlock("switch.next")
- // TODO(adonovan): opt: when tag==vTrue, we'd
- // get better code if we use b.cond(cond)
- // instead of BinOp(EQL, tag, b.expr(cond))
- // followed by If. Don't forget conversions
- // though.
- cond := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond.Pos())
- emitIf(fn, cond, body, nextCond)
+ // For boolean switches, emit short-circuit control flow,
+ // just like an if/else-chain.
+ if tag == vTrue && !isNonTypeParamInterface(fn.info.Types[cond].Type) {
+ b.cond(fn, cond, body, nextCond)
+ } else {
+ c := emitCompare(fn, token.EQL, tag, b.expr(fn, cond), cond.Pos())
+ emitIf(fn, c, body, nextCond)
+ }
fn.currentBlock = nextCond
}
fn.currentBlock = body
diff --git a/gopls/internal/analysis/yield/testdata/src/a/a.go b/gopls/internal/analysis/yield/testdata/src/a/a.go
index 820cabd..15ee3f5 100644
--- a/gopls/internal/analysis/yield/testdata/src/a/a.go
+++ b/gopls/internal/analysis/yield/testdata/src/a/a.go
@@ -145,3 +145,21 @@
}
}
}
+
+// Regression test for issue #77681. A boolean switch is now
+// handled just like an if/else chain in the SSA builder.
+func switchShortCircuit(seq iter.Seq[int]) iter.Seq[int] {
+ return func(yield func(int) bool) {
+ for item := range seq {
+ isZero := item == 0
+ switch {
+ case !isZero && !yield(item): // ok
+ return
+ case !isZero:
+ continue
+ case !yield(0): // ok
+ return
+ }
+ }
+ }
+}