Skip to content

Commit 74163c8

Browse files
committed
cmd/compile: use STP/LDP around morestack on arm64
The spill/restore code around morestack is almost never exectued, so we should make it as small as possible. Using 2-register loads/stores makes sense here. Also, the offsets from SP are pretty small so the offset almost always fits in the (smaller than a normal load/store) offset field of the instruction. Makes cmd/go 0.6% smaller. Change-Id: I8845283c1b269a259498153924428f6173bda293 Reviewed-on: https://go-review.googlesource.com/c/go/+/621556 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Keith Randall <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 4e70258 commit 74163c8

File tree

3 files changed

+110
-7
lines changed

3 files changed

+110
-7
lines changed

src/cmd/compile/internal/arm64/ssa.go

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,48 @@ func storeByType(t *types.Type) obj.As {
7878
panic("bad store type")
7979
}
8080

81+
// loadByType2 returns an opcode that can load consecutive memory locations into 2 registers with type t.
82+
// returns obj.AXXX if no such opcode exists.
83+
func loadByType2(t *types.Type) obj.As {
84+
if t.IsFloat() {
85+
switch t.Size() {
86+
case 4:
87+
return arm64.AFLDPS
88+
case 8:
89+
return arm64.AFLDPD
90+
}
91+
} else {
92+
switch t.Size() {
93+
case 4:
94+
return arm64.ALDPW
95+
case 8:
96+
return arm64.ALDP
97+
}
98+
}
99+
return obj.AXXX
100+
}
101+
102+
// storeByType2 returns an opcode that can store registers with type t into 2 consecutive memory locations.
103+
// returns obj.AXXX if no such opcode exists.
104+
func storeByType2(t *types.Type) obj.As {
105+
if t.IsFloat() {
106+
switch t.Size() {
107+
case 4:
108+
return arm64.AFSTPS
109+
case 8:
110+
return arm64.AFSTPD
111+
}
112+
} else {
113+
switch t.Size() {
114+
case 4:
115+
return arm64.ASTPW
116+
case 8:
117+
return arm64.ASTP
118+
}
119+
}
120+
return obj.AXXX
121+
}
122+
81123
// makeshift encodes a register shifted by a constant, used as an Offset in Prog.
82124
func makeshift(v *ssa.Value, reg int16, typ int64, s int64) int64 {
83125
if s < 0 || s >= 64 {
@@ -167,17 +209,38 @@ func ssaGenValue(s *ssagen.State, v *ssa.Value) {
167209
p.From.Reg = v.Args[0].Reg()
168210
ssagen.AddrAuto(&p.To, v)
169211
case ssa.OpArgIntReg, ssa.OpArgFloatReg:
212+
ssagen.CheckArgReg(v)
170213
// The assembler needs to wrap the entry safepoint/stack growth code with spill/unspill
171214
// The loop only runs once.
172-
for _, a := range v.Block.Func.RegArgs {
173-
// Pass the spill/unspill information along to the assembler, offset by size of
174-
// the saved LR slot.
215+
args := v.Block.Func.RegArgs
216+
if len(args) == 0 {
217+
break
218+
}
219+
v.Block.Func.RegArgs = nil // prevent from running again
220+
221+
for i := 0; i < len(args); i++ {
222+
a := args[i]
223+
// Offset by size of the saved LR slot.
175224
addr := ssagen.SpillSlotAddr(a, arm64.REGSP, base.Ctxt.Arch.FixedFrameSize)
176-
s.FuncInfo().AddSpill(
177-
obj.RegSpill{Reg: a.Reg, Addr: addr, Unspill: loadByType(a.Type), Spill: storeByType(a.Type)})
225+
// Look for double-register operations if we can.
226+
if i < len(args)-1 {
227+
b := args[i+1]
228+
if a.Type.Size() == b.Type.Size() &&
229+
a.Type.IsFloat() == b.Type.IsFloat() &&
230+
b.Offset == a.Offset+a.Type.Size() {
231+
ld := loadByType2(a.Type)
232+
st := storeByType2(a.Type)
233+
if ld != obj.AXXX && st != obj.AXXX {
234+
s.FuncInfo().AddSpill(obj.RegSpill{Reg: a.Reg, Reg2: b.Reg, Addr: addr, Unspill: ld, Spill: st})
235+
i++ // b is done also, skip it.
236+
continue
237+
}
238+
}
239+
}
240+
// Pass the spill/unspill information along to the assembler.
241+
s.FuncInfo().AddSpill(obj.RegSpill{Reg: a.Reg, Addr: addr, Unspill: loadByType(a.Type), Spill: storeByType(a.Type)})
178242
}
179-
v.Block.Func.RegArgs = nil
180-
ssagen.CheckArgReg(v)
243+
181244
case ssa.OpARM64ADD,
182245
ssa.OpARM64SUB,
183246
ssa.OpARM64AND,

src/cmd/internal/obj/link.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,7 @@ type Auto struct {
11001100
type RegSpill struct {
11011101
Addr Addr
11021102
Reg int16
1103+
Reg2 int16 // If not 0, a second register to spill at Addr+regSize. Only for some archs.
11031104
Spill, Unspill As
11041105
}
11051106

@@ -1192,6 +1193,10 @@ func (fi *FuncInfo) SpillRegisterArgs(last *Prog, pa ProgAlloc) *Prog {
11921193
spill.As = ra.Spill
11931194
spill.From.Type = TYPE_REG
11941195
spill.From.Reg = ra.Reg
1196+
if ra.Reg2 != 0 {
1197+
spill.From.Type = TYPE_REGREG
1198+
spill.From.Offset = int64(ra.Reg2)
1199+
}
11951200
spill.To = ra.Addr
11961201
last = spill
11971202
}
@@ -1208,6 +1213,10 @@ func (fi *FuncInfo) UnspillRegisterArgs(last *Prog, pa ProgAlloc) *Prog {
12081213
unspill.From = ra.Addr
12091214
unspill.To.Type = TYPE_REG
12101215
unspill.To.Reg = ra.Reg
1216+
if ra.Reg2 != 0 {
1217+
unspill.To.Type = TYPE_REGREG
1218+
unspill.To.Offset = int64(ra.Reg2)
1219+
}
12111220
last = unspill
12121221
}
12131222
return last

test/codegen/spills.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// asmcheck
2+
3+
// Copyright 2024 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package codegen
8+
9+
func i64(a, b int64) int64 { // arm64:`STP\s`,`LDP\s`
10+
g()
11+
return a + b
12+
}
13+
14+
func i32(a, b int32) int32 { // arm64:`STPW`,`LDPW`
15+
g()
16+
return a + b
17+
}
18+
19+
func f64(a, b float64) float64 { // arm64:`FSTPD`,`FLDPD`
20+
g()
21+
return a + b
22+
}
23+
24+
func f32(a, b float32) float32 { // arm64:`FSTPS`,`FLDPS`
25+
g()
26+
return a + b
27+
}
28+
29+
//go:noinline
30+
func g() {
31+
}

0 commit comments

Comments
 (0)