cmd/internal/obj/riscv: improve constant construction

Attempt to construct large constants that have a consecutive sequence
of ones from a small negative constant, with a logical right and/or
left shift. This allows for a large range of mask like constants to be
constructed with only two or three instructions, avoiding the need to
load from memory.

Change-Id: I35a77fecdd2df0ed3f33b772d518f85119d4ff66
Reviewed-on: https://go-review.googlesource.com/c/go/+/652778
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: Mark Ryan <markdryan@rivosinc.com>
Reviewed-by: Meng Zhuo <mengzhuo1203@gmail.com>
This commit is contained in:
Joel Sing 2025-02-27 22:54:51 +11:00
parent 1763ee199d
commit d37624881f
3 changed files with 82 additions and 40 deletions

View File

@ -572,24 +572,24 @@ start:
MOV $0x7fffffff, X5 // MOV $2147483647, X5 // b70200809b82f2ff MOV $0x7fffffff, X5 // MOV $2147483647, X5 // b70200809b82f2ff
MOV $-0x7fffffff, X5 // MOV $-2147483647, X5 // b70200809b821200 MOV $-0x7fffffff, X5 // MOV $-2147483647, X5 // b70200809b821200
// Converted to load and shift (MOV + SLLI) // Converted to load and shift(s)
MOV $0xffffffff, X5 // MOV $4294967295, X5 // 9302f0ff93d20202
MOV $0x100000000, X5 // MOV $4294967296, X5 // 9302100093920202 MOV $0x100000000, X5 // MOV $4294967296, X5 // 9302100093920202
MOV $0xfffffffffffda, X5 // MOV $4503599627370458, X5 // 9302d0fe9392d20093d2c200
MOV $0xffffffffffffe, X5 // MOV $4503599627370494, X5 // 9302f0ff9392d20093d2c200
MOV $0x7fffffff00000000, X5 // MOV $9223372032559808512, X5 // b70200809b82f2ff93920202 MOV $0x7fffffff00000000, X5 // MOV $9223372032559808512, X5 // b70200809b82f2ff93920202
MOV $0x8000000100000000, X5 // MOV $-9223372032559808512, X5 // b70200809b82120093920202 MOV $0x8000000100000000, X5 // MOV $-9223372032559808512, X5 // b70200809b82120093920202
MOV $0xffffffff00000000, X5 // MOV $-4294967296, X5 // 9302f0ff93920202 MOV $0xffffffff00000000, X5 // MOV $-4294967296, X5 // 9302f0ff93920202
MOV $0x1ffffffff0000000, X5 // MOV $2305843008945258496, X5 // 9302f0ff9392f20193d23200
MOV $0x7fffffffffffffff, X5 // MOV $9223372036854775807, X5 // 9302f0ff93d21200
// Converted to load of symbol (AUIPC + LD) // Converted to load of symbol (AUIPC + LD)
MOV $0x80000001, X5 // MOV $2147483649, X5 // 9702000083b20200 MOV $0x80000001, X5 // MOV $2147483649, X5 // 9702000083b20200
MOV $0xffffffff, X5 // MOV $4294967295, X5 // 9702000083b20200
MOV $0x100000001, X5 // MOV $4294967297, X5 // 9702000083b20200 MOV $0x100000001, X5 // MOV $4294967297, X5 // 9702000083b20200
MOV $0xfffffffffffda, X5 // MOV $4503599627370458, X5 // 9702000083b20200
MOV $0xffffffffffffe, X5 // MOV $4503599627370494, X5 // 9702000083b20200
MOV $0x0800000010000000, X5 // MOV $576460752571858944, X5 // 9702000083b20200 MOV $0x0800000010000000, X5 // MOV $576460752571858944, X5 // 9702000083b20200
MOV $0x8000000010000000, X5 // MOV $-9223372036586340352, X5 // 9702000083b20200 MOV $0x8000000010000000, X5 // MOV $-9223372036586340352, X5 // 9702000083b20200
MOV $0x0abcdabcd0000000, X5 // MOV $773733740479250432, X5 // 9702000083b20200 MOV $0x0abcdabcd0000000, X5 // MOV $773733740479250432, X5 // 9702000083b20200
MOV $0x8abcdabcd0000000, X5 // MOV $-8449638296375525376, X5 // 9702000083b20200 MOV $0x8abcdabcd0000000, X5 // MOV $-8449638296375525376, X5 // 9702000083b20200
MOV $0x1ffffffff0000000, X5 // MOV $2305843008945258496, X5 // 9702000083b20200
MOV $0x7fffffffffffffff, X5 // MOV $9223372036854775807, X5 // 9702000083b20200
MOV $0xfff0000000ffffff, X5 // MOV $-4503599610593281, X5 // 9702000083b20200 MOV $0xfff0000000ffffff, X5 // MOV $-4503599610593281, X5 // 9702000083b20200
MOV (X5), X6 // 03b30200 MOV (X5), X6 // 03b30200

View File

@ -2220,22 +2220,35 @@ func encodingForAs(as obj.As) (*encoding, error) {
return &insData.enc, nil return &insData.enc, nil
} }
// splitShiftConst attempts to split a constant into a signed 32 bit integer // splitShiftConst attempts to split a constant into a signed 12 bit or
// and a corresponding left shift. // 32 bit integer, with corresponding logical right shift and/or left shift.
func splitShiftConst(v int64) (imm int64, lsh int, ok bool) { func splitShiftConst(v int64) (imm int64, lsh int, rsh int, ok bool) {
// See if we can reconstruct this value from a signed 32 bit integer.
lsh = bits.TrailingZeros64(uint64(v)) lsh = bits.TrailingZeros64(uint64(v))
c := v >> lsh c := v >> lsh
if int64(int32(c)) != c { if int64(int32(c)) == c {
return 0, 0, false return c, lsh, 0, true
} }
return c, lsh, true
// See if we can reconstruct this value from a small negative constant.
rsh = bits.LeadingZeros64(uint64(v))
ones := bits.OnesCount64((uint64(v) >> lsh) >> 11)
c = signExtend(1<<11|((v>>lsh)&0x7ff), 12)
if rsh+ones+lsh+11 == 64 {
if lsh > 0 || c != -1 {
lsh += rsh
}
return c, lsh, rsh, true
}
return 0, 0, 0, false
} }
// isShiftConst indicates whether a constant can be represented as a signed // isShiftConst indicates whether a constant can be represented as a signed
// 32 bit integer that is left shifted. // 32 bit integer that is left shifted.
func isShiftConst(v int64) bool { func isShiftConst(v int64) bool {
_, lsh, ok := splitShiftConst(v) _, lsh, rsh, ok := splitShiftConst(v)
return ok && lsh > 0 return ok && (lsh > 0 || rsh > 0)
} }
type instruction struct { type instruction struct {
@ -2512,16 +2525,34 @@ func instructionsForMOV(p *obj.Prog) []*instruction {
// For constants larger than 32 bits in size that have trailing zeros, // For constants larger than 32 bits in size that have trailing zeros,
// use the value with the trailing zeros removed and then use a SLLI // use the value with the trailing zeros removed and then use a SLLI
// instruction to restore the original constant. // instruction to restore the original constant.
//
// For example: // For example:
// MOV $0x8000000000000000, X10 // MOV $0x8000000000000000, X10
// becomes // becomes
// MOV $1, X10 // MOV $1, X10
// SLLI $63, X10, X10 // SLLI $63, X10, X10
var insSLLI *instruction //
// Similarly, we can construct large constants that have a consecutive
// sequence of ones from a small negative constant, with a right and/or
// left shift.
//
// For example:
// MOV $0x000fffffffffffda, X10
// becomes
// MOV $-19, X10
// SLLI $13, X10
// SRLI $12, X10
//
var insSLLI, insSRLI *instruction
if err := immIFits(ins.imm, 32); err != nil { if err := immIFits(ins.imm, 32); err != nil {
if c, lsh, ok := splitShiftConst(ins.imm); ok { if c, lsh, rsh, ok := splitShiftConst(ins.imm); ok {
ins.imm = c ins.imm = c
insSLLI = &instruction{as: ASLLI, rd: ins.rd, rs1: ins.rd, imm: int64(lsh)} if lsh > 0 {
insSLLI = &instruction{as: ASLLI, rd: ins.rd, rs1: ins.rd, imm: int64(lsh)}
}
if rsh > 0 {
insSRLI = &instruction{as: ASRLI, rd: ins.rd, rs1: ins.rd, imm: int64(rsh)}
}
} }
} }
@ -2548,6 +2579,9 @@ func instructionsForMOV(p *obj.Prog) []*instruction {
if insSLLI != nil { if insSLLI != nil {
inss = append(inss, insSLLI) inss = append(inss, insSLLI)
} }
if insSRLI != nil {
inss = append(inss, insSRLI)
}
case p.From.Type == obj.TYPE_CONST && p.To.Type != obj.TYPE_REG: case p.From.Type == obj.TYPE_CONST && p.To.Type != obj.TYPE_REG:
p.Ctxt.Diag("%v: constant load must target register", p) p.Ctxt.Diag("%v: constant load must target register", p)

View File

@ -14,29 +14,30 @@ func TestSplitShiftConst(t *testing.T) {
v int64 v int64
wantImm int64 wantImm int64
wantLsh int wantLsh int
wantRsh int
wantOk bool wantOk bool
}{ }{
{0x100000000, 1, 32, true}, {0x100000000, 1, 32, 0, true},
{0xfffff001, 0, 0, false}, {0xfffff001, 0, 0, 0, false},
{0xfffff801, 0, 0, false}, {0xfffff801, -2047, 32, 32, true},
{0xfffffff1, 0, 0, false}, {0xfffffff1, -15, 32, 32, true},
{0xffffffff, 0, 0, false}, {0xffffffff, -1, 0, 32, true},
{0xfffffffe, 0x7fffffff, 1, true}, {0xfffffffe, 0x7fffffff, 1, 0, true},
{0xfffffffffffda, 0, 0, false}, {0xfffffffffffda, -19, 13, 12, true},
{0xfffffffffffde, 0, 0, false}, {0xfffffffffffde, -17, 13, 12, true},
{0x000003ffffffffff, 0, 0, false}, {0x000003ffffffffff, -1, 0, 22, true},
{0x0007ffffffffffff, 0, 0, false}, {0x0007ffffffffffff, -1, 0, 13, true},
{0x7fffffff00000000, 0x7fffffff, 32, true}, {0x7fffffff00000000, 0x7fffffff, 32, 0, true},
{0x7fffffffffffffff, 0, 0, false}, {0x7fffffffffffffff, -1, 0, 1, true},
{0x7f7f7f7f7f7f7f7f, 0, 0, false}, {0x7f7f7f7f7f7f7f7f, 0, 0, 0, false},
{0x0080000010000000, 0x8000001, 28, true}, {0x0080000010000000, 0x8000001, 28, 0, true},
{0x0abcdabcd0000000, 0, 0, false}, {0x0abcdabcd0000000, 0, 0, 0, false},
{-4503599610593281, 0, 0, false}, // 0x8abcdabcd0000000 {-4503599610593281, 0, 0, 0, false}, // 0x8abcdabcd0000000
{-7543254330000000, 0, 0, false}, // 0xfff0000000ffffff {-7543254330000000, 0, 0, 0, false}, // 0xfff0000000ffffff
} }
for _, test := range tests { for _, test := range tests {
t.Run(fmt.Sprintf("0x%x", test.v), func(t *testing.T) { t.Run(fmt.Sprintf("0x%x", test.v), func(t *testing.T) {
c, l, ok := splitShiftConst(test.v) c, l, r, ok := splitShiftConst(test.v)
if got, want := c, test.wantImm; got != want { if got, want := c, test.wantImm; got != want {
t.Errorf("Got immediate %d, want %d", got, want) t.Errorf("Got immediate %d, want %d", got, want)
@ -44,6 +45,9 @@ func TestSplitShiftConst(t *testing.T) {
if got, want := l, test.wantLsh; got != want { if got, want := l, test.wantLsh; got != want {
t.Errorf("Got left shift %d, want %d", got, want) t.Errorf("Got left shift %d, want %d", got, want)
} }
if got, want := r, test.wantRsh; got != want {
t.Errorf("Got right shift %d, want %d", got, want)
}
switch { switch {
case !ok && test.wantOk: case !ok && test.wantOk:
t.Error("Failed to split shift constant, want success") t.Error("Failed to split shift constant, want success")
@ -54,8 +58,12 @@ func TestSplitShiftConst(t *testing.T) {
return return
} }
// Reconstruct as a 32 bit signed constant. // Reconstruct as either a 12 bit or 32 bit signed constant.
v := int64(uint64(int32(test.wantImm)) << l) s := 64 - 12
v := int64((uint64(((c << s) >> s)) << l) >> r)
if test.wantImm != ((test.wantImm << s) >> s) {
v = int64((uint64(int32(test.wantImm)) << l) >> r)
}
if v != test.v { if v != test.v {
t.Errorf("Got v = %d (%x), want v = %d (%x)", v, v, test.v, test.v) t.Errorf("Got v = %d (%x), want v = %d (%x)", v, v, test.v, test.v)
} }