Skip to content

Commit 14f3ca5

Browse files
committed
cmd/internal/obj: ARM, use immediates instead of constant pool entries
When a constant doesn't fit in a single instruction, use two paired instructions instead of the constant pool. For example ADD $0xaa00bb, R0, R1 Used to rewrite to: MOV ?(IP), R11 ADD R11, R0, R1 Instead, do: ADD $0xaa0000, R0, R1 ADD $0xbb, R1, R1 Same number of instructions. Good: 4 less bytes (no constant pool entry) One less load. Bad: Critical path is one instruction longer. It's probably worth it to avoid the loads, they are expensive. Dave Cheney got us some performance numbers: https://perf.golang.org/search?q=upload:20170426.1 TL;DR mean 1.37% improvement. Change-Id: Ib206836161fdc94a3962db6f9caa635c87d57cf1 Reviewed-on: https://go-review.googlesource.com/41612 Run-TryBot: Keith Randall <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Cherry Zhang <[email protected]>
1 parent c120e44 commit 14f3ca5

4 files changed

Lines changed: 191 additions & 5 deletions

File tree

src/cmd/internal/obj/arm/a.out.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,10 @@ const (
121121
C_PSR
122122
C_FCR
123123

124-
C_RCON /* 0xff rotated */
125-
C_NCON /* ~RCON */
126-
C_SCON /* 0xffff */
124+
C_RCON /* 0xff rotated */
125+
C_NCON /* ~RCON */
126+
C_RCON2 /* OR of two disjoint C_RCON constants */
127+
C_SCON /* 0xffff */
127128
C_LCON
128129
C_LCONADDR
129130
C_ZFCON

src/cmd/internal/obj/arm/anames5.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ var cnames5 = []string{
1616
"FCR",
1717
"RCON",
1818
"NCON",
19+
"RCON2",
1920
"SCON",
2021
"LCON",
2122
"LCONADDR",

src/cmd/internal/obj/arm/asm5.go

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,22 @@ var optab = []Optab{
8686
{obj.ATEXT, C_ADDR, C_NONE, C_TEXTSIZE, 0, 0, 0, 0, 0},
8787
{AADD, C_REG, C_REG, C_REG, 1, 4, 0, 0, 0},
8888
{AADD, C_REG, C_NONE, C_REG, 1, 4, 0, 0, 0},
89+
{AAND, C_REG, C_REG, C_REG, 1, 4, 0, 0, 0},
90+
{AAND, C_REG, C_NONE, C_REG, 1, 4, 0, 0, 0},
8991
{AMOVW, C_REG, C_NONE, C_REG, 1, 4, 0, 0, 0},
9092
{AMVN, C_REG, C_NONE, C_REG, 1, 4, 0, 0, 0},
9193
{ACMP, C_REG, C_REG, C_NONE, 1, 4, 0, 0, 0},
9294
{AADD, C_RCON, C_REG, C_REG, 2, 4, 0, 0, 0},
9395
{AADD, C_RCON, C_NONE, C_REG, 2, 4, 0, 0, 0},
96+
{AAND, C_RCON, C_REG, C_REG, 2, 4, 0, 0, 0},
97+
{AAND, C_RCON, C_NONE, C_REG, 2, 4, 0, 0, 0},
9498
{AMOVW, C_RCON, C_NONE, C_REG, 2, 4, 0, 0, 0},
9599
{AMVN, C_RCON, C_NONE, C_REG, 2, 4, 0, 0, 0},
96100
{ACMP, C_RCON, C_REG, C_NONE, 2, 4, 0, 0, 0},
97101
{AADD, C_SHIFT, C_REG, C_REG, 3, 4, 0, 0, 0},
98102
{AADD, C_SHIFT, C_NONE, C_REG, 3, 4, 0, 0, 0},
103+
{AAND, C_SHIFT, C_REG, C_REG, 3, 4, 0, 0, 0},
104+
{AAND, C_SHIFT, C_NONE, C_REG, 3, 4, 0, 0, 0},
99105
{AMVN, C_SHIFT, C_NONE, C_REG, 3, 4, 0, 0, 0},
100106
{ACMP, C_SHIFT, C_REG, C_NONE, 3, 4, 0, 0, 0},
101107
{AMOVW, C_RACON, C_NONE, C_REG, 4, 4, REGSP, 0, 0},
@@ -128,14 +134,22 @@ var optab = []Optab{
128134
{AMOVW, C_LCONADDR, C_NONE, C_REG, 12, 4, 0, LFROM | LPCREL, 4},
129135
{AADD, C_NCON, C_REG, C_REG, 13, 8, 0, 0, 0},
130136
{AADD, C_NCON, C_NONE, C_REG, 13, 8, 0, 0, 0},
137+
{AAND, C_NCON, C_REG, C_REG, 13, 8, 0, 0, 0},
138+
{AAND, C_NCON, C_NONE, C_REG, 13, 8, 0, 0, 0},
131139
{AMVN, C_NCON, C_NONE, C_REG, 13, 8, 0, 0, 0},
132140
{ACMP, C_NCON, C_REG, C_NONE, 13, 8, 0, 0, 0},
133141
{AADD, C_SCON, C_REG, C_REG, 13, 8, 0, 0, 0},
134142
{AADD, C_SCON, C_NONE, C_REG, 13, 8, 0, 0, 0},
143+
{AAND, C_SCON, C_REG, C_REG, 13, 8, 0, 0, 0},
144+
{AAND, C_SCON, C_NONE, C_REG, 13, 8, 0, 0, 0},
135145
{AMVN, C_SCON, C_NONE, C_REG, 13, 8, 0, 0, 0},
136146
{ACMP, C_SCON, C_REG, C_NONE, 13, 8, 0, 0, 0},
147+
{AADD, C_RCON2, C_REG, C_REG, 106, 8, 0, 0, 0},
148+
// TODO: RCON2: how to do AND and BIC?
137149
{AADD, C_LCON, C_REG, C_REG, 13, 8, 0, LFROM, 0},
138150
{AADD, C_LCON, C_NONE, C_REG, 13, 8, 0, LFROM, 0},
151+
{AAND, C_LCON, C_REG, C_REG, 13, 8, 0, LFROM, 0},
152+
{AAND, C_LCON, C_NONE, C_REG, 13, 8, 0, LFROM, 0},
139153
{AMVN, C_LCON, C_NONE, C_REG, 13, 8, 0, LFROM, 0},
140154
{ACMP, C_LCON, C_REG, C_NONE, 13, 8, 0, LFROM, 0},
141155
{AMOVB, C_REG, C_NONE, C_REG, 1, 4, 0, 0, 0},
@@ -957,6 +971,21 @@ func immrot(v uint32) int32 {
957971
return 0
958972
}
959973

974+
// immrot2 returns bits encoding the immediate constant fields of two instructions,
975+
// such that the encoded constants x, y satisfy x|y==v, x&y==0.
976+
// Returns 0,0 if no such decomposition of v exists.
977+
func immrot2(v uint32) (uint32, uint32) {
978+
for i := uint(1); i < 32; i++ {
979+
m := uint32(1<<i - 1)
980+
if x, y := immrot(v&m), immrot(v&^m); x != 0 && y != 0 {
981+
return uint32(x), uint32(y)
982+
}
983+
}
984+
// TODO: handle some more cases, like where
985+
// the wraparound from the rotate could help.
986+
return 0, 0
987+
}
988+
960989
func immaddr(v int32) int32 {
961990
if v >= 0 && v <= 0xfff {
962991
return v&0xfff | 1<<24 | 1<<23 /* pre indexing */ /* pre indexing, up */
@@ -1131,6 +1160,9 @@ func (c *ctxt5) aclass(a *obj.Addr) int {
11311160
if uint32(c.instoffset) <= 0xffff && objabi.GOARM == 7 {
11321161
return C_SCON
11331162
}
1163+
if x, y := immrot2(uint32(c.instoffset)); x != 0 && y != 0 {
1164+
return C_RCON2
1165+
}
11341166
return C_LCON
11351167

11361168
case obj.NAME_EXTERN,
@@ -1195,6 +1227,16 @@ func (c *ctxt5) oplook(p *obj.Prog) *Optab {
11951227
a2 = C_REG
11961228
}
11971229

1230+
// If Scond != 0, we must use the constant pool instead of
1231+
// splitting the instruction in two. The most common reason is
1232+
// .S (flag updating) instructions. There may be others.
1233+
if a1 == C_RCON2 && p.Scond != 0 {
1234+
a1 = C_LCON
1235+
}
1236+
if a3 == C_RCON2 && p.Scond != 0 {
1237+
a3 = C_LCON
1238+
}
1239+
11981240
if false { /*debug['O']*/
11991241
fmt.Printf("oplook %v %v %v %v\n", p.As, DRconv(a1), DRconv(a2), DRconv(a3))
12001242
fmt.Printf("\t\t%d %d\n", p.From.Type, p.To.Type)
@@ -1225,7 +1267,7 @@ func cmp(a int, b int) bool {
12251267
}
12261268
switch a {
12271269
case C_LCON:
1228-
if b == C_RCON || b == C_NCON || b == C_SCON {
1270+
if b == C_RCON || b == C_NCON || b == C_SCON || b == C_RCON2 {
12291271
return true
12301272
}
12311273

@@ -1365,14 +1407,16 @@ func buildop(ctxt *obj.Link) {
13651407
log.Fatalf("bad code")
13661408

13671409
case AADD:
1368-
opset(AAND, r0)
13691410
opset(AEOR, r0)
13701411
opset(ASUB, r0)
13711412
opset(ARSB, r0)
13721413
opset(AADC, r0)
13731414
opset(ASBC, r0)
13741415
opset(ARSC, r0)
13751416
opset(AORR, r0)
1417+
1418+
case AAND:
1419+
opset(AAND, r0)
13761420
opset(ABIC, r0)
13771421

13781422
case ACMP:
@@ -1563,6 +1607,33 @@ func (c *ctxt5) asmout(p *obj.Prog, o *Optab, out []uint32) {
15631607
}
15641608
o1 |= (uint32(r)&15)<<16 | (uint32(rt)&15)<<12
15651609

1610+
case 106: /* op $I,R,R where I can be decomposed into 2 immediates */
1611+
c.aclass(&p.From)
1612+
r := int(p.Reg)
1613+
rt := int(p.To.Reg)
1614+
x, y := immrot2(uint32(c.instoffset))
1615+
var as2 obj.As
1616+
switch p.As {
1617+
case AADD, ASUB, AORR, AEOR:
1618+
as2 = p.As // ADD, SUB, ORR, EOR
1619+
case ARSB:
1620+
as2 = AADD // RSB -> RSB/ADD pair
1621+
case AADC:
1622+
as2 = AADD // ADC -> ADC/ADD pair
1623+
case ASBC:
1624+
as2 = ASUB // SBC -> SBC/SUB pair
1625+
case ARSC:
1626+
as2 = AADD // RSC -> RSC/ADD pair
1627+
default:
1628+
c.ctxt.Diag("unknown second op for %v", p)
1629+
}
1630+
o1 = c.oprrr(p, p.As, int(p.Scond))
1631+
o2 = c.oprrr(p, as2, int(p.Scond))
1632+
o1 |= (uint32(r)&15)<<16 | (uint32(rt)&15)<<12
1633+
o2 |= (uint32(rt)&15)<<16 | (uint32(rt)&15)<<12
1634+
o1 |= x
1635+
o2 |= y
1636+
15661637
case 3: /* add R<<[IR],[R],R */
15671638
o1 = c.mov(p)
15681639

test/armimm.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// run
2+
3+
// Copyright 2017 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+
// This file tests the splitting of constants into
8+
// multiple immediates on arm.
9+
10+
package main
11+
12+
import "fmt"
13+
14+
const c32 = 0xaa00dd
15+
const c64 = 0xaa00dd55000066
16+
17+
//go:noinline
18+
func add32(x uint32) uint32 {
19+
return x + c32
20+
}
21+
22+
//go:noinline
23+
func sub32(x uint32) uint32 {
24+
return x - c32
25+
}
26+
27+
//go:noinline
28+
func or32(x uint32) uint32 {
29+
return x | c32
30+
}
31+
32+
//go:noinline
33+
func xor32(x uint32) uint32 {
34+
return x ^ c32
35+
}
36+
37+
//go:noinline
38+
func subr32(x uint32) uint32 {
39+
return c32 - x
40+
}
41+
42+
//go:noinline
43+
func add64(x uint64) uint64 {
44+
return x + c64
45+
}
46+
47+
//go:noinline
48+
func sub64(x uint64) uint64 {
49+
return x - c64
50+
}
51+
52+
//go:noinline
53+
func or64(x uint64) uint64 {
54+
return x | c64
55+
}
56+
57+
//go:noinline
58+
func xor64(x uint64) uint64 {
59+
return x ^ c64
60+
}
61+
62+
//go:noinline
63+
func subr64(x uint64) uint64 {
64+
return c64 - x
65+
}
66+
67+
// Note: x-c gets rewritten to x+(-c), so SUB and SBC are not directly testable.
68+
// I disabled that rewrite rule before running this test.
69+
70+
func main() {
71+
test32()
72+
test64()
73+
}
74+
75+
func test32() {
76+
var a uint32 = 0x11111111
77+
var want, got uint32
78+
if want, got = a+c32, add32(a); got != want {
79+
panic(fmt.Sprintf("add32(%x) = %x, want %x", a, got, want))
80+
}
81+
if want, got = a-c32, sub32(a); got != want {
82+
panic(fmt.Sprintf("sub32(%x) = %x, want %x", a, got, want))
83+
}
84+
if want, got = a|c32, or32(a); got != want {
85+
panic(fmt.Sprintf("or32(%x) = %x, want %x", a, got, want))
86+
}
87+
if want, got = a^c32, xor32(a); got != want {
88+
panic(fmt.Sprintf("xor32(%x) = %x, want %x", a, got, want))
89+
}
90+
if want, got = c32-a, subr32(a); got != want {
91+
panic(fmt.Sprintf("subr32(%x) = %x, want %x", a, got, want))
92+
}
93+
}
94+
95+
func test64() {
96+
var a uint64 = 0x1111111111111111
97+
var want, got uint64
98+
if want, got = a+c64, add64(a); got != want {
99+
panic(fmt.Sprintf("add64(%x) = %x, want %x", a, got, want))
100+
}
101+
if want, got = a-c64, sub64(a); got != want {
102+
panic(fmt.Sprintf("sub64(%x) = %x, want %x", a, got, want))
103+
}
104+
if want, got = a|c64, or64(a); got != want {
105+
panic(fmt.Sprintf("or64(%x) = %x, want %x", a, got, want))
106+
}
107+
if want, got = a^c64, xor64(a); got != want {
108+
panic(fmt.Sprintf("xor64(%x) = %x, want %x", a, got, want))
109+
}
110+
if want, got = c64-a, subr64(a); got != want {
111+
panic(fmt.Sprintf("subr64(%x) = %x, want %x", a, got, want))
112+
}
113+
}

0 commit comments

Comments
 (0)