cmd/compile: generalize staticuint64s sharing to larger integer constant types

Optimize interface conversion of small integer constants of any size
(values in range [0, 255]) by redirecting their references to direct
offsets into runtime.staticuint64s during the walk lowering phase.

This avoids generating redundant static temporary backing variables
(stmp symbols) in the read-only data segment, allowing the linker's
dead-code elimination pass to discard them.

Consolidate the compile-time constant lookup with the dynamic size-1
variable path into a single switch case block in dataWord(),
preserving the evaluation order of expressions with side effects.

Fixes #37612

Change-Id: I81bd20bc4e98f690aad67804e23b104dfe2a6ed8
Reviewed-on: https://go-review.googlesource.com/c/go/+/802320
Reviewed-by: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
This commit is contained in:
Gilbert Morgan 2026-07-17 23:10:43 +00:00 committed by Gopher Robot
parent 591ed42daf
commit ee64db65cf
3 changed files with 183 additions and 34 deletions

View File

@ -152,26 +152,35 @@ func dataWord(conv *ir.ConvExpr, init *ir.Nodes) ir.Node {
// Try a bunch of cases to avoid an allocation.
var value ir.Node
byteVal, isConst := smallIntConst(n)
switch {
case fromType.Size() == 0:
// n is zero-sized. Use zerobase.
diagnose("using global for zero-sized interface value", n)
cheapExpr(n, init) // Evaluate n for side-effects. See issue 19246.
value = ir.NewLinksymExpr(base.Pos, ir.Syms.Zerobase, types.Types[types.TUINTPTR])
case isBool || fromType.Size() == 1 && isInteger:
// n is a bool/byte. Use staticuint64s[n * 8] on little-endian
// and staticuint64s[n * 8 + 7] on big-endian.
diagnose("using global for single-byte interface value", n)
n = cheapExpr(n, init)
n = soleComponent(init, n)
case isBool || isInteger && (fromType.Size() == 1 || isConst):
// n is a bool, a single-byte integer, or a compile-time constant in [0, 255].
// Use staticuint64s[n * 8] on little-endian and staticuint64s[n * 8 + 7] on big-endian.
diagnose("using global for small integer/boolean interface value", n)
if isConst {
n = ir.NewBasicLit(base.Pos, types.Types[types.TUINT8], constant.MakeInt64(int64(byteVal)))
} else {
n = cheapExpr(n, init)
n = soleComponent(init, n)
base.Assert(fromType.Size() == 1)
}
// byteindex widens n so that the multiplication doesn't overflow.
index := ir.NewBinaryExpr(base.Pos, ir.OLSH, byteindex(n), ir.NewInt(base.Pos, 3))
if ssagen.Arch.LinkArch.ByteOrder == binary.BigEndian {
index = ir.NewBinaryExpr(base.Pos, ir.OADD, index, ir.NewInt(base.Pos, 7))
index = ir.NewBinaryExpr(base.Pos, ir.OADD, index, ir.NewInt(base.Pos, 8-fromType.Size()))
}
// The actual type is [256]uint64, but we use [256*8]uint8 so we can address
// individual bytes.
staticuint64s := ir.NewLinksymExpr(base.Pos, ir.Syms.Staticuint64s, types.NewArray(types.Types[types.TUINT8], 256*8))
t := types.NewArray(types.Types[types.TUINT8], 256*8)
types.CalcSize(t)
staticuint64s := ir.NewLinksymExpr(base.Pos, ir.Syms.Staticuint64s, t)
xe := ir.NewIndexExpr(base.Pos, staticuint64s, index)
xe.SetBounded(true)
value = xe
@ -244,6 +253,65 @@ func dataWord(conv *ir.ConvExpr, init *ir.Nodes) ir.Node {
return safeExpr(walkExpr(typecheck.Expr(call), init), init)
}
// smallIntConst returns the byte value of n if it is a compile-time
// constant in the range [0, 255] (either a literal or a readonly global stmp).
func smallIntConst(n ir.Node) (byte, bool) {
if ir.IsConst(n, constant.Int) {
val := ir.Int64Val(n)
if val >= 0 && val <= 255 {
return byte(val), true
}
}
if ir.IsConst(n, constant.Bool) {
if ir.BoolVal(n) {
return 1, true
}
return 0, true
}
if n.Op() == ir.ONAME && n.Name().Readonly() {
t := n.Type()
if !t.IsBoolean() && !t.IsInteger() {
return 0, false
}
size := t.Size()
sym := n.Name().Linksym()
if sym == nil {
return 0, false
}
p := sym.P
var byteVal byte
// Determine the Least Significant Byte (LSB) index based on architecture.
lsbIdx := int64(0)
if ssagen.Arch.LinkArch.ByteOrder == binary.BigEndian {
lsbIdx = size - 1
}
if lsbIdx < int64(len(p)) {
byteVal = p[lsbIdx]
}
// Ensure all non-LSB bytes in the integer representation are zero (value fits in a byte).
for i := int64(0); i < size; i++ {
if i == lsbIdx {
continue
}
var b byte
if i < int64(len(p)) {
b = p[i]
}
if b != 0 {
return 0, false
}
}
return byteVal, true
}
return 0, false
}
// walkBytesRunesToString walks an OBYTES2STR or ORUNES2STR node.
func walkBytesRunesToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
a := typecheck.NodNil()

View File

@ -1,22 +1,103 @@
// asmcheck
package codegen
// Copyright 2020 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.
package codegen
func booliface() interface{} {
// amd64:`LEAQ runtime.staticuint64s\+8\(SB\)`
// 386:`LEAL\sruntime.staticuint64s\+8\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+8\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+15\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+15\(SB\),\sR\d+`
return true
}
func smallint8iface() interface{} {
// amd64:`LEAQ runtime.staticuint64s\+2024\(SB\)`
// 386:`LEAL\sruntime.staticuint64s\+2024\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+2024\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+2031\(SB\)`
// mips64:`MOVV\s[$]runtime.staticuint64s\+2031\(SB\)`
return int8(-3)
}
func smalluint8iface() interface{} {
// amd64:`LEAQ runtime.staticuint64s\+24\(SB\)`
// 386:`LEAL\sruntime.staticuint64s\+24\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+24\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+31\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+31\(SB\),\sR\d+`
return uint8(3)
}
func smallintiface() interface{} {
// 386:`LEAL\sruntime.staticuint64s\+8\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+8\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+12\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+8\(SB\),\sR\d+`
return 1
}
func smallint16iface() interface{} {
// 386:`LEAL\sruntime.staticuint64s\+1016\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+1016\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+1022\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+1022\(SB\),\sR\d+`
return int16(127)
}
func smallint32iface() interface{} {
// 386:`LEAL\sruntime.staticuint64s\+2040\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+2040\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+2044\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+2044\(SB\),\sR\d+`
return int32(255)
}
func smallint64iface() interface{} {
// 386:`LEAL\sruntime.staticuint64s\+2040\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+2040\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+2040\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+2040\(SB\),\sR\d+`
return int64(255)
}
func smalluintface() interface{} {
// 386:`LEAL\sruntime.staticuint64s\+16\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+16\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+20\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+16\(SB\),\sR\d+`
return uint(2)
}
func smalluintptriface() interface{} {
// 386:`LEAL\sruntime.staticuint64s\+24\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+24\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+28\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+24\(SB\),\sR\d+`
return uintptr(3)
}
func smalluint16iface() interface{} {
// 386:`LEAL\sruntime.staticuint64s\+80\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+80\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+86\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+86\(SB\),\sR\d+`
return uint16(10)
}
func smalluint32iface() interface{} {
// 386:`LEAL\sruntime.staticuint64s\+8\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+8\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+12\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+12\(SB\),\sR\d+`
return uint32(1)
}
func smalluint64iface() interface{} {
// 386:`LEAL\sruntime.staticuint64s\+56\(SB\)`
// amd64:`LEAQ\sruntime.staticuint64s\+56\(SB\)`
// mips:`MOVW\s[$]runtime.staticuint64s\+56\(SB\),\sR\d+`
// mips64:`MOVV\s[$]runtime.staticuint64s\+56\(SB\),\sR\d+`
return uint64(7)
}

View File

@ -82,37 +82,37 @@ func string11() {
}
func integer1() {
sink = 42 // ERROR "using global for interface value"
sink = 42 // ERROR "using global for small integer/boolean interface value"
}
func integer2() {
v := 42
sink = v // ERROR "using global for interface value"
sink = v // ERROR "using global for small integer/boolean interface value"
}
func integer3() {
sink = 0 // ERROR "using global for interface value"
sink = 0 // ERROR "using global for small integer/boolean interface value"
}
func integer4a() {
v := 0
sink = v // ERROR "using global for interface value"
sink = v // ERROR "using global for small integer/boolean interface value"
}
func integer4b() {
v := uint8(0)
sink = v // ERROR "using global for single-byte interface value"
sink = v // ERROR "using global for small integer/boolean interface value"
}
func integer5() {
var a any = 42 // ERROR "using global for interface value"
var a any = 42 // ERROR "using global for small integer/boolean interface value"
_ = a
}
func integer6() {
var a any
v := 42
a = v // ERROR "using global for interface value"
a = v // ERROR "using global for small integer/boolean interface value"
_ = a
}
@ -131,69 +131,69 @@ func escapes(m M) {
}
func named1a() {
sink = MyInt(42) // ERROR "using global for interface value"
sink = MyInt(42) // ERROR "using global for small integer/boolean interface value"
}
func named1b() {
escapes(MyInt(42)) // ERROR "using global for interface value"
escapes(MyInt(42)) // ERROR "using global for small integer/boolean interface value"
}
func named2a() {
v := MyInt(0)
sink = v // ERROR "using global for interface value"
sink = v // ERROR "using global for small integer/boolean interface value"
}
func named2b() {
v := MyInt(42)
escapes(v) // ERROR "using global for interface value"
escapes(v) // ERROR "using global for small integer/boolean interface value"
}
func named2c() {
v := 42
sink = MyInt(v) // ERROR "using global for interface value"
sink = MyInt(v) // ERROR "using global for small integer/boolean interface value"
}
func named2d() {
v := 42
escapes(MyInt(v)) // ERROR "using global for interface value"
escapes(MyInt(v)) // ERROR "using global for small integer/boolean interface value"
}
func named3a() {
sink = MyInt(42) // ERROR "using global for interface value"
sink = MyInt(42) // ERROR "using global for small integer/boolean interface value"
}
func named3b() {
escapes(MyInt(0)) // ERROR "using global for interface value"
escapes(MyInt(0)) // ERROR "using global for small integer/boolean interface value"
}
func named4a() {
v := MyInt(0)
sink = v // ERROR "using global for interface value"
sink = v // ERROR "using global for small integer/boolean interface value"
}
func named4b() {
v := MyInt(0)
escapes(v) // ERROR "using global for interface value"
escapes(v) // ERROR "using global for small integer/boolean interface value"
}
func named4c() {
v := 0
sink = MyInt(v) // ERROR "using global for interface value"
sink = MyInt(v) // ERROR "using global for small integer/boolean interface value"
}
func named4d() {
v := 0
escapes(MyInt(v)) // ERROR "using global for interface value"
escapes(MyInt(v)) // ERROR "using global for small integer/boolean interface value"
}
func named5() {
var a any = MyInt(42) // ERROR "using global for interface value"
var a any = MyInt(42) // ERROR "using global for small integer/boolean interface value"
_ = a
}
func named6() {
var a any
v := MyInt(42)
a = v // ERROR "using global for interface value"
a = v // ERROR "using global for small integer/boolean interface value"
_ = a
}