go/test/codegen/switch.go
qmuntal 2a902c8a8a cmd/compile: optimize switch statements using lookup tables
Switch statement containing integer constant cases and case bodies just
returning a constant should be optimizable to a simpler and faster table
lookup instead of a jump table.

That is, a switch like this:

    switch x {
    case 0: return 10
    case 1: return 20
    case 2: return 30
    case 3: return 40
    default: return -1
    }

Could be optimized to this:

    var table = [4]int{10, 20, 30, 40}
    if uint(x) < 4 { return table[x] }
    return -1

The resulting code is smaller and faster, especially on platforms where
jump tables are not supported.

goos: windows
goarch: arm64
pkg: cmd/compile/internal/test
                               │  .\old.txt  │              .\new.txt              │
                               │   sec/op    │   sec/op     vs base                │
SwitchLookup8Predictable-12      2.708n ± 6%   2.249n ± 5%  -16.97% (p=0.000 n=10)
SwitchLookup8Unpredictable-12    8.758n ± 7%   3.272n ± 4%  -62.65% (p=0.000 n=10)
SwitchLookup32Predictable-12     2.672n ± 5%   2.373n ± 6%  -11.21% (p=0.000 n=10)
SwitchLookup32Unpredictable-12   9.372n ± 7%   3.385n ± 6%  -63.89% (p=0.000 n=10)
geomean                          4.937n        2.772n       -43.84%

Fixes #78203

Change-Id: I74fa3d77ef618412951b2e5c3cb6ebc760ce4ff1
Reviewed-on: https://go-review.googlesource.com/c/go/+/756340
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2026-04-03 16:46:16 -07:00

229 lines
4.4 KiB
Go

// asmcheck
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// These tests check code generation of switch statements.
package codegen
// see issue 33934
func f(x string) int {
// amd64:-`cmpstring`
switch x {
case "":
return -1
case "1", "2", "3":
return -2
default:
return -3
}
}
// use jump tables for 8+ string cases
// Using multiple return values prevent lookup tables.
func squareJump(x int) (int, int) {
// amd64:`JMP \(.*\)\(.*\)$`
// arm64:`MOVD \(R.*\)\(R.*<<3\)` `JMP \(R.*\)$`
// loong64: `ALSLV` `MOVV` `JMP`
switch x {
case 1:
return 1, 1
case 2:
return 4, 2
case 3:
return 9, 3
case 4:
return 16, 4
case 5:
return 25, 5
case 6:
return 36, 6
case 7:
return 49, 7
case 8:
return 64, 8
default:
return x * x, x
}
}
// use lookup tables for 8+ int cases returning constants
func squareLookup(x int) int {
// amd64:`LEAQ .*\(SB\)` `MOVQ .*\(.*\)\(.*\*8\)` -`JMP \(.*\)\(.*\)$`
// arm64:`MOVD \(R.*\)\(R.*<<3\)` -`JMP \(R.*\)$`
// loong64:`SLLV` `MOVV \(R.*\)\(R.*\)` -`ALSLV`
switch x {
case 1:
return 1
case 2:
return 4
case 3:
return 9
case 4:
return 16
case 5:
return 25
case 6:
return 36
case 7:
return 49
case 8:
return 64
default:
return x * x
}
}
// use jump tables for 8+ string lengths
func length(x string) int {
// amd64:`JMP \(.*\)\(.*\)$`
// arm64:`MOVD \(R.*\)\(R.*<<3\)` `JMP \(R.*\)$`
// loong64:`ALSLV` `MOVV` `JMP`
switch x {
case "a":
return 1
case "bb":
return 2
case "ccc":
return 3
case "dddd":
return 4
case "eeeee":
return 5
case "ffffff":
return 6
case "ggggggg":
return 7
case "hhhhhhhh":
return 8
default:
return len(x)
}
}
// Use single-byte ordered comparisons for binary searching strings.
// See issue 53333.
func mimetype(ext string) string {
// amd64: `CMPB 1\(.*\), \$104$` -`cmpstring`
// arm64: `MOVB 1\(R.*\), R.*$` `CMPW \$104, R.*$` -`cmpstring`
switch ext {
// amd64: `CMPL \(.*\), \$1836345390$`
// arm64: `MOVD \$1836345390` `CMPW R.*, R.*$`
case ".htm":
return "A"
// amd64: `CMPL \(.*\), \$1953457454$`
// arm64: `MOVD \$1953457454` `CMPW R.*, R.*$`
case ".eot":
return "B"
// amd64: `CMPL \(.*\), \$1735815982$`
// arm64: `MOVD \$1735815982` `CMPW R.*, R.*$`
case ".svg":
return "C"
// amd64: `CMPL \(.*\), \$1718907950$`
// arm64: `MOVD \$1718907950` `CMPW R.*, R.*$`
case ".ttf":
return "D"
default:
return ""
}
}
// use jump tables for type switches to concrete types.
func typeSwitch(x any) int {
// amd64:`JMP \(.*\)\(.*\)$`
// arm64:`MOVD \(R.*\)\(R.*<<3\)` `JMP \(R.*\)$`
switch x.(type) {
case int:
return 0
case int8:
return 1
case int16:
return 2
case int32:
return 3
case int64:
return 4
}
return 7
}
type I interface {
foo()
}
type J interface {
bar()
}
type IJ interface {
I
J
}
type K interface {
baz()
}
// use a runtime call for type switches to interface types.
func interfaceSwitch(x any) int {
// amd64:`CALL runtime.interfaceSwitch` `MOVL 16\(AX\)` `MOVQ 8\(.*\)(.*\*8)`
// arm64:`CALL runtime.interfaceSwitch` `LDAR` `MOVWU 16\(R0\)` `MOVD \(R.*\)\(R.*\)`
switch x.(type) {
case I:
return 1
case J:
return 2
default:
return 3
}
}
func interfaceSwitch2(x K) int {
// amd64:`CALL runtime.interfaceSwitch` `MOVL 16\(AX\)` `MOVQ 8\(.*\)(.*\*8)`
// arm64:`CALL runtime.interfaceSwitch` `LDAR` `MOVWU 16\(R0\)` `MOVD \(R.*\)\(R.*\)`
switch x.(type) {
case I:
return 1
case J:
return 2
default:
return 3
}
}
func interfaceCast(x any) int {
// amd64:`CALL runtime.typeAssert` `MOVL 16\(AX\)` `MOVQ 8\(.*\)(.*\*1)`
// arm64:`CALL runtime.typeAssert` `LDAR` `MOVWU 16\(R0\)` `MOVD \(R.*\)\(R.*\)`
if _, ok := x.(I); ok {
return 3
}
return 5
}
func interfaceCast2(x K) int {
// amd64:`CALL runtime.typeAssert` `MOVL 16\(AX\)` `MOVQ 8\(.*\)(.*\*1)`
// arm64:`CALL runtime.typeAssert` `LDAR` `MOVWU 16\(R0\)` `MOVD \(R.*\)\(R.*\)`
if _, ok := x.(I); ok {
return 3
}
return 5
}
func interfaceConv(x IJ) I {
// amd64:`CALL runtime.typeAssert` `MOVL 16\(AX\)` `MOVQ 8\(.*\)(.*\*1)`
// arm64:`CALL runtime.typeAssert` `LDAR` `MOVWU 16\(R0\)` `MOVD \(R.*\)\(R.*\)`
return x
}
// Make sure we can constant fold after inlining. See issue 71699.
func stringSwitchInlineable(s string) {
switch s {
case "foo", "bar", "baz", "goo":
default:
println("no")
}
}
func stringSwitch() {
// amd64:-"CMP" -"CALL"
// arm64:-"CMP" -"CALL"
stringSwitchInlineable("foo")
}