]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: constant-fold switches early in compilation
authorKeith Randall <khr@golang.org>
Mon, 7 Feb 2022 07:25:04 +0000 (23:25 -0800)
committerKeith Randall <khr@golang.org>
Thu, 14 Apr 2022 19:37:30 +0000 (19:37 +0000)
So that the inliner knows all the other cases are dead and doesn't
accumulate any cost for them.

The canonical case for this is switching on runtime.GOOS, which occurs
several places in the stdlib.

Fixes #50253

Change-Id: I44823aaebb6c1b03c9b0c12d10086db81954350f
Reviewed-on: https://go-review.googlesource.com/c/go/+/399694
Run-TryBot: Keith Randall <khr@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Russ Cox <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/cmd/compile/internal/deadcode/deadcode.go
test/inline.go

index c37a5a6990ced7fa656ba0868492621650955aa2..decd2611836fb159d38a338e0f123ec553e0101f 100644 (file)
@@ -6,6 +6,7 @@ package deadcode
 
 import (
        "go/constant"
+       "go/token"
 
        "cmd/compile/internal/base"
        "cmd/compile/internal/ir"
@@ -86,6 +87,85 @@ func stmts(nn *ir.Nodes) {
                                }
                        }
                }
+               if n.Op() == ir.OSWITCH {
+                       n := n.(*ir.SwitchStmt)
+                       // Use a closure wrapper here so we can use "return" to abort the analysis.
+                       func() {
+                               if n.Tag != nil && n.Tag.Op() == ir.OTYPESW {
+                                       return // no special type-switch case yet.
+                               }
+                               var x constant.Value // value we're switching on
+                               if n.Tag != nil {
+                                       if ir.ConstType(n.Tag) == constant.Unknown {
+                                               return
+                                       }
+                                       x = n.Tag.Val()
+                               } else {
+                                       x = constant.MakeBool(true) // switch { ... }  =>  switch true { ... }
+                               }
+                               var def *ir.CaseClause
+                               for _, cas := range n.Cases {
+                                       if len(cas.List) == 0 { // default case
+                                               def = cas
+                                               continue
+                                       }
+                                       for _, c := range cas.List {
+                                               if ir.ConstType(c) == constant.Unknown {
+                                                       return // can't statically tell if it matches or not - give up.
+                                               }
+                                               if constant.Compare(x, token.EQL, c.Val()) {
+                                                       for _, n := range cas.Body {
+                                                               if n.Op() == ir.OFALL {
+                                                                       return // fallthrough makes it complicated - abort.
+                                                               }
+                                                       }
+                                                       // This switch entry is the one that always triggers.
+                                                       for _, cas2 := range n.Cases {
+                                                               for _, c2 := range cas2.List {
+                                                                       if cas2 != cas || c2 != c {
+                                                                               ir.Visit(c2, markHiddenClosureDead)
+                                                                       }
+                                                               }
+                                                               if cas2 != cas {
+                                                                       ir.VisitList(cas2.Body, markHiddenClosureDead)
+                                                               }
+                                                       }
+
+                                                       cas.List[0] = c
+                                                       cas.List = cas.List[:1]
+                                                       n.Cases[0] = cas
+                                                       n.Cases = n.Cases[:1]
+                                                       return
+                                               }
+                                       }
+                               }
+                               if def != nil {
+                                       for _, n := range def.Body {
+                                               if n.Op() == ir.OFALL {
+                                                       return // fallthrough makes it complicated - abort.
+                                               }
+                                       }
+                                       for _, cas := range n.Cases {
+                                               if cas != def {
+                                                       ir.VisitList(cas.List, markHiddenClosureDead)
+                                                       ir.VisitList(cas.Body, markHiddenClosureDead)
+                                               }
+                                       }
+                                       n.Cases[0] = def
+                                       n.Cases = n.Cases[:1]
+                                       return
+                               }
+
+                               // TODO: handle case bodies ending with panic/return as we do in the IF case above.
+
+                               // entire switch is a nop - no case ever triggers
+                               for _, cas := range n.Cases {
+                                       ir.VisitList(cas.List, markHiddenClosureDead)
+                                       ir.VisitList(cas.Body, markHiddenClosureDead)
+                               }
+                               n.Cases = n.Cases[:0]
+                       }()
+               }
 
                if len(n.Init()) != 0 {
                        stmts(n.(ir.InitNode).PtrInit())
index cb8403e9ce1216e79dfa11e28c32c996ccd67712..400898bceeb284dca30506e3d54717055ad58495 100644 (file)
@@ -160,6 +160,51 @@ func switchType(x interface{}) int { // ERROR "can inline switchType" "x does no
        }
 }
 
+// Test that switches on constant things, with constant cases, only cost anything for
+// the case that matches. See issue 50253.
+func switchConst1(p func(string)) { // ERROR "can inline switchConst" "p does not escape"
+       const c = 1
+       switch c {
+       case 0:
+               p("zero")
+       case 1:
+               p("one")
+       case 2:
+               p("two")
+       default:
+               p("other")
+       }
+}
+
+func switchConst2() string { // ERROR "can inline switchConst2"
+       switch runtime.GOOS {
+       case "linux":
+               return "Leenooks"
+       case "windows":
+               return "Windoze"
+       case "darwin":
+               return "MackBone"
+       case "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100":
+               return "Numbers"
+       default:
+               return "oh nose!"
+       }
+}
+func switchConst3() string { // ERROR "can inline switchConst3"
+       switch runtime.GOOS {
+       case "Linux":
+               panic("Linux")
+       case "Windows":
+               panic("Windows")
+       case "Darwin":
+               panic("Darwin")
+       case "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "100":
+               panic("Numbers")
+       default:
+               return "oh nose!"
+       }
+}
+
 func inlineRangeIntoMe(data []int) { // ERROR "can inline inlineRangeIntoMe" "data does not escape"
        rangeFunc(data, 12) // ERROR "inlining call to rangeFunc"
 }