Skip to content

Commit ee64db6

Browse files
Gilbert Morgangopherbot
authored andcommitted
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>
1 parent 591ed42 commit ee64db6

3 files changed

Lines changed: 183 additions & 34 deletions

File tree

src/cmd/compile/internal/walk/convert.go

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,26 +152,35 @@ func dataWord(conv *ir.ConvExpr, init *ir.Nodes) ir.Node {
152152

153153
// Try a bunch of cases to avoid an allocation.
154154
var value ir.Node
155+
byteVal, isConst := smallIntConst(n)
155156
switch {
156157
case fromType.Size() == 0:
157158
// n is zero-sized. Use zerobase.
158159
diagnose("using global for zero-sized interface value", n)
159160
cheapExpr(n, init) // Evaluate n for side-effects. See issue 19246.
160161
value = ir.NewLinksymExpr(base.Pos, ir.Syms.Zerobase, types.Types[types.TUINTPTR])
161-
case isBool || fromType.Size() == 1 && isInteger:
162-
// n is a bool/byte. Use staticuint64s[n * 8] on little-endian
163-
// and staticuint64s[n * 8 + 7] on big-endian.
164-
diagnose("using global for single-byte interface value", n)
165-
n = cheapExpr(n, init)
166-
n = soleComponent(init, n)
162+
case isBool || isInteger && (fromType.Size() == 1 || isConst):
163+
// n is a bool, a single-byte integer, or a compile-time constant in [0, 255].
164+
// Use staticuint64s[n * 8] on little-endian and staticuint64s[n * 8 + 7] on big-endian.
165+
diagnose("using global for small integer/boolean interface value", n)
166+
if isConst {
167+
n = ir.NewBasicLit(base.Pos, types.Types[types.TUINT8], constant.MakeInt64(int64(byteVal)))
168+
} else {
169+
n = cheapExpr(n, init)
170+
n = soleComponent(init, n)
171+
base.Assert(fromType.Size() == 1)
172+
}
173+
167174
// byteindex widens n so that the multiplication doesn't overflow.
168175
index := ir.NewBinaryExpr(base.Pos, ir.OLSH, byteindex(n), ir.NewInt(base.Pos, 3))
169176
if ssagen.Arch.LinkArch.ByteOrder == binary.BigEndian {
170-
index = ir.NewBinaryExpr(base.Pos, ir.OADD, index, ir.NewInt(base.Pos, 7))
177+
index = ir.NewBinaryExpr(base.Pos, ir.OADD, index, ir.NewInt(base.Pos, 8-fromType.Size()))
171178
}
172179
// The actual type is [256]uint64, but we use [256*8]uint8 so we can address
173180
// individual bytes.
174-
staticuint64s := ir.NewLinksymExpr(base.Pos, ir.Syms.Staticuint64s, types.NewArray(types.Types[types.TUINT8], 256*8))
181+
t := types.NewArray(types.Types[types.TUINT8], 256*8)
182+
types.CalcSize(t)
183+
staticuint64s := ir.NewLinksymExpr(base.Pos, ir.Syms.Staticuint64s, t)
175184
xe := ir.NewIndexExpr(base.Pos, staticuint64s, index)
176185
xe.SetBounded(true)
177186
value = xe
@@ -244,6 +253,65 @@ func dataWord(conv *ir.ConvExpr, init *ir.Nodes) ir.Node {
244253
return safeExpr(walkExpr(typecheck.Expr(call), init), init)
245254
}
246255

256+
// smallIntConst returns the byte value of n if it is a compile-time
257+
// constant in the range [0, 255] (either a literal or a readonly global stmp).
258+
func smallIntConst(n ir.Node) (byte, bool) {
259+
if ir.IsConst(n, constant.Int) {
260+
val := ir.Int64Val(n)
261+
if val >= 0 && val <= 255 {
262+
return byte(val), true
263+
}
264+
}
265+
if ir.IsConst(n, constant.Bool) {
266+
if ir.BoolVal(n) {
267+
return 1, true
268+
}
269+
return 0, true
270+
}
271+
272+
if n.Op() == ir.ONAME && n.Name().Readonly() {
273+
t := n.Type()
274+
if !t.IsBoolean() && !t.IsInteger() {
275+
return 0, false
276+
}
277+
size := t.Size()
278+
279+
sym := n.Name().Linksym()
280+
if sym == nil {
281+
return 0, false
282+
}
283+
p := sym.P
284+
var byteVal byte
285+
286+
// Determine the Least Significant Byte (LSB) index based on architecture.
287+
lsbIdx := int64(0)
288+
if ssagen.Arch.LinkArch.ByteOrder == binary.BigEndian {
289+
lsbIdx = size - 1
290+
}
291+
292+
if lsbIdx < int64(len(p)) {
293+
byteVal = p[lsbIdx]
294+
}
295+
296+
// Ensure all non-LSB bytes in the integer representation are zero (value fits in a byte).
297+
for i := int64(0); i < size; i++ {
298+
if i == lsbIdx {
299+
continue
300+
}
301+
var b byte
302+
if i < int64(len(p)) {
303+
b = p[i]
304+
}
305+
if b != 0 {
306+
return 0, false
307+
}
308+
}
309+
return byteVal, true
310+
}
311+
312+
return 0, false
313+
}
314+
247315
// walkBytesRunesToString walks an OBYTES2STR or ORUNES2STR node.
248316
func walkBytesRunesToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node {
249317
a := typecheck.NodNil()

test/codegen/smallintiface.go

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,103 @@
11
// asmcheck
22

3-
package codegen
4-
53
// Copyright 2020 The Go Authors. All rights reserved.
64
// Use of this source code is governed by a BSD-style
75
// license that can be found in the LICENSE file.
86

7+
package codegen
8+
99
func booliface() interface{} {
10-
// amd64:`LEAQ runtime.staticuint64s\+8\(SB\)`
10+
// 386:`LEAL\sruntime.staticuint64s\+8\(SB\)`
11+
// amd64:`LEAQ\sruntime.staticuint64s\+8\(SB\)`
12+
// mips:`MOVW\s[$]runtime.staticuint64s\+15\(SB\),\sR\d+`
13+
// mips64:`MOVV\s[$]runtime.staticuint64s\+15\(SB\),\sR\d+`
1114
return true
1215
}
1316

1417
func smallint8iface() interface{} {
15-
// amd64:`LEAQ runtime.staticuint64s\+2024\(SB\)`
18+
// 386:`LEAL\sruntime.staticuint64s\+2024\(SB\)`
19+
// amd64:`LEAQ\sruntime.staticuint64s\+2024\(SB\)`
20+
// mips:`MOVW\s[$]runtime.staticuint64s\+2031\(SB\)`
21+
// mips64:`MOVV\s[$]runtime.staticuint64s\+2031\(SB\)`
1622
return int8(-3)
1723
}
1824

1925
func smalluint8iface() interface{} {
20-
// amd64:`LEAQ runtime.staticuint64s\+24\(SB\)`
26+
// 386:`LEAL\sruntime.staticuint64s\+24\(SB\)`
27+
// amd64:`LEAQ\sruntime.staticuint64s\+24\(SB\)`
28+
// mips:`MOVW\s[$]runtime.staticuint64s\+31\(SB\),\sR\d+`
29+
// mips64:`MOVV\s[$]runtime.staticuint64s\+31\(SB\),\sR\d+`
2130
return uint8(3)
2231
}
32+
33+
func smallintiface() interface{} {
34+
// 386:`LEAL\sruntime.staticuint64s\+8\(SB\)`
35+
// amd64:`LEAQ\sruntime.staticuint64s\+8\(SB\)`
36+
// mips:`MOVW\s[$]runtime.staticuint64s\+12\(SB\),\sR\d+`
37+
// mips64:`MOVV\s[$]runtime.staticuint64s\+8\(SB\),\sR\d+`
38+
return 1
39+
}
40+
41+
func smallint16iface() interface{} {
42+
// 386:`LEAL\sruntime.staticuint64s\+1016\(SB\)`
43+
// amd64:`LEAQ\sruntime.staticuint64s\+1016\(SB\)`
44+
// mips:`MOVW\s[$]runtime.staticuint64s\+1022\(SB\),\sR\d+`
45+
// mips64:`MOVV\s[$]runtime.staticuint64s\+1022\(SB\),\sR\d+`
46+
return int16(127)
47+
}
48+
49+
func smallint32iface() interface{} {
50+
// 386:`LEAL\sruntime.staticuint64s\+2040\(SB\)`
51+
// amd64:`LEAQ\sruntime.staticuint64s\+2040\(SB\)`
52+
// mips:`MOVW\s[$]runtime.staticuint64s\+2044\(SB\),\sR\d+`
53+
// mips64:`MOVV\s[$]runtime.staticuint64s\+2044\(SB\),\sR\d+`
54+
return int32(255)
55+
}
56+
57+
func smallint64iface() interface{} {
58+
// 386:`LEAL\sruntime.staticuint64s\+2040\(SB\)`
59+
// amd64:`LEAQ\sruntime.staticuint64s\+2040\(SB\)`
60+
// mips:`MOVW\s[$]runtime.staticuint64s\+2040\(SB\),\sR\d+`
61+
// mips64:`MOVV\s[$]runtime.staticuint64s\+2040\(SB\),\sR\d+`
62+
return int64(255)
63+
}
64+
65+
func smalluintface() interface{} {
66+
// 386:`LEAL\sruntime.staticuint64s\+16\(SB\)`
67+
// amd64:`LEAQ\sruntime.staticuint64s\+16\(SB\)`
68+
// mips:`MOVW\s[$]runtime.staticuint64s\+20\(SB\),\sR\d+`
69+
// mips64:`MOVV\s[$]runtime.staticuint64s\+16\(SB\),\sR\d+`
70+
return uint(2)
71+
}
72+
73+
func smalluintptriface() interface{} {
74+
// 386:`LEAL\sruntime.staticuint64s\+24\(SB\)`
75+
// amd64:`LEAQ\sruntime.staticuint64s\+24\(SB\)`
76+
// mips:`MOVW\s[$]runtime.staticuint64s\+28\(SB\),\sR\d+`
77+
// mips64:`MOVV\s[$]runtime.staticuint64s\+24\(SB\),\sR\d+`
78+
return uintptr(3)
79+
}
80+
81+
func smalluint16iface() interface{} {
82+
// 386:`LEAL\sruntime.staticuint64s\+80\(SB\)`
83+
// amd64:`LEAQ\sruntime.staticuint64s\+80\(SB\)`
84+
// mips:`MOVW\s[$]runtime.staticuint64s\+86\(SB\),\sR\d+`
85+
// mips64:`MOVV\s[$]runtime.staticuint64s\+86\(SB\),\sR\d+`
86+
return uint16(10)
87+
}
88+
89+
func smalluint32iface() interface{} {
90+
// 386:`LEAL\sruntime.staticuint64s\+8\(SB\)`
91+
// amd64:`LEAQ\sruntime.staticuint64s\+8\(SB\)`
92+
// mips:`MOVW\s[$]runtime.staticuint64s\+12\(SB\),\sR\d+`
93+
// mips64:`MOVV\s[$]runtime.staticuint64s\+12\(SB\),\sR\d+`
94+
return uint32(1)
95+
}
96+
97+
func smalluint64iface() interface{} {
98+
// 386:`LEAL\sruntime.staticuint64s\+56\(SB\)`
99+
// amd64:`LEAQ\sruntime.staticuint64s\+56\(SB\)`
100+
// mips:`MOVW\s[$]runtime.staticuint64s\+56\(SB\),\sR\d+`
101+
// mips64:`MOVV\s[$]runtime.staticuint64s\+56\(SB\),\sR\d+`
102+
return uint64(7)
103+
}

test/escape_iface_data.go

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -82,37 +82,37 @@ func string11() {
8282
}
8383

8484
func integer1() {
85-
sink = 42 // ERROR "using global for interface value"
85+
sink = 42 // ERROR "using global for small integer/boolean interface value"
8686
}
8787

8888
func integer2() {
8989
v := 42
90-
sink = v // ERROR "using global for interface value"
90+
sink = v // ERROR "using global for small integer/boolean interface value"
9191
}
9292

9393
func integer3() {
94-
sink = 0 // ERROR "using global for interface value"
94+
sink = 0 // ERROR "using global for small integer/boolean interface value"
9595
}
9696

9797
func integer4a() {
9898
v := 0
99-
sink = v // ERROR "using global for interface value"
99+
sink = v // ERROR "using global for small integer/boolean interface value"
100100
}
101101

102102
func integer4b() {
103103
v := uint8(0)
104-
sink = v // ERROR "using global for single-byte interface value"
104+
sink = v // ERROR "using global for small integer/boolean interface value"
105105
}
106106

107107
func integer5() {
108-
var a any = 42 // ERROR "using global for interface value"
108+
var a any = 42 // ERROR "using global for small integer/boolean interface value"
109109
_ = a
110110
}
111111

112112
func integer6() {
113113
var a any
114114
v := 42
115-
a = v // ERROR "using global for interface value"
115+
a = v // ERROR "using global for small integer/boolean interface value"
116116
_ = a
117117
}
118118

@@ -131,69 +131,69 @@ func escapes(m M) {
131131
}
132132

133133
func named1a() {
134-
sink = MyInt(42) // ERROR "using global for interface value"
134+
sink = MyInt(42) // ERROR "using global for small integer/boolean interface value"
135135
}
136136

137137
func named1b() {
138-
escapes(MyInt(42)) // ERROR "using global for interface value"
138+
escapes(MyInt(42)) // ERROR "using global for small integer/boolean interface value"
139139
}
140140

141141
func named2a() {
142142
v := MyInt(0)
143-
sink = v // ERROR "using global for interface value"
143+
sink = v // ERROR "using global for small integer/boolean interface value"
144144
}
145145

146146
func named2b() {
147147
v := MyInt(42)
148-
escapes(v) // ERROR "using global for interface value"
148+
escapes(v) // ERROR "using global for small integer/boolean interface value"
149149
}
150150

151151
func named2c() {
152152
v := 42
153-
sink = MyInt(v) // ERROR "using global for interface value"
153+
sink = MyInt(v) // ERROR "using global for small integer/boolean interface value"
154154
}
155155

156156
func named2d() {
157157
v := 42
158-
escapes(MyInt(v)) // ERROR "using global for interface value"
158+
escapes(MyInt(v)) // ERROR "using global for small integer/boolean interface value"
159159
}
160160
func named3a() {
161-
sink = MyInt(42) // ERROR "using global for interface value"
161+
sink = MyInt(42) // ERROR "using global for small integer/boolean interface value"
162162
}
163163

164164
func named3b() {
165-
escapes(MyInt(0)) // ERROR "using global for interface value"
165+
escapes(MyInt(0)) // ERROR "using global for small integer/boolean interface value"
166166
}
167167

168168
func named4a() {
169169
v := MyInt(0)
170-
sink = v // ERROR "using global for interface value"
170+
sink = v // ERROR "using global for small integer/boolean interface value"
171171
}
172172

173173
func named4b() {
174174
v := MyInt(0)
175-
escapes(v) // ERROR "using global for interface value"
175+
escapes(v) // ERROR "using global for small integer/boolean interface value"
176176
}
177177

178178
func named4c() {
179179
v := 0
180-
sink = MyInt(v) // ERROR "using global for interface value"
180+
sink = MyInt(v) // ERROR "using global for small integer/boolean interface value"
181181
}
182182

183183
func named4d() {
184184
v := 0
185-
escapes(MyInt(v)) // ERROR "using global for interface value"
185+
escapes(MyInt(v)) // ERROR "using global for small integer/boolean interface value"
186186
}
187187

188188
func named5() {
189-
var a any = MyInt(42) // ERROR "using global for interface value"
189+
var a any = MyInt(42) // ERROR "using global for small integer/boolean interface value"
190190
_ = a
191191
}
192192

193193
func named6() {
194194
var a any
195195
v := MyInt(42)
196-
a = v // ERROR "using global for interface value"
196+
a = v // ERROR "using global for small integer/boolean interface value"
197197
_ = a
198198
}
199199

0 commit comments

Comments
 (0)