Skip to content

Commit 3c26aef

Browse files
committed
cmd/internal/obj/riscv: improve large branch/call/jump tests
Rework these tests such that they are built on all architectures and actually executed when run on riscv64. This increases the likelihood of catching code generation issues, especially those that impact relocations. Also ensure that the generated assembly includes the instruction sequence that is expected for the large branch/call/jump. Change-Id: I15c40a439dd1d0d4ed189ab81697e93d82c4ef4d Reviewed-on: https://go-review.googlesource.com/c/go/+/721621 Reviewed-by: Mark Ryan <markdryan@rivosinc.com> Reviewed-by: Mark Freeman <markfreeman@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Junyang Shao <shaojunyang@google.com>
1 parent 31aa9f8 commit 3c26aef

File tree

1 file changed

+153
-28
lines changed

1 file changed

+153
-28
lines changed

src/cmd/internal/obj/riscv/asm_test.go

Lines changed: 153 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import (
1717
)
1818

1919
// TestLargeBranch generates a large function with a very far conditional
20-
// branch, in order to ensure that it assembles successfully.
20+
// branch, in order to ensure that it assembles correctly. This requires
21+
// inverting the branch and using a jump to reach the target.
2122
func TestLargeBranch(t *testing.T) {
2223
if testing.Short() {
2324
t.Skip("Skipping test in short mode")
@@ -26,6 +27,23 @@ func TestLargeBranch(t *testing.T) {
2627

2728
dir := t.TempDir()
2829

30+
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module largecall"), 0644); err != nil {
31+
t.Fatalf("Failed to write file: %v\n", err)
32+
}
33+
main := `package main
34+
35+
import "fmt"
36+
37+
func main() {
38+
fmt.Print(x())
39+
}
40+
41+
func x() uint64
42+
`
43+
if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
44+
t.Fatalf("failed to write main: %v\n", err)
45+
}
46+
2947
// Generate a very large function.
3048
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
3149
genLargeBranch(buf)
@@ -36,27 +54,62 @@ func TestLargeBranch(t *testing.T) {
3654
}
3755

3856
// Assemble generated file.
39-
cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
57+
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
4058
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
4159
out, err := cmd.CombinedOutput()
60+
if err != nil {
61+
t.Errorf("Failed to assemble: %v\n%s", err, out)
62+
}
63+
64+
// The expected instruction sequence for the long branch is:
65+
// BNEZ
66+
// AUIPC $..., X31
67+
// JALR X0, $..., X31
68+
want := regexp.MustCompile(`\sBNEZ\s.*\s.*\n.*\n.*AUIPC\s\$\d+, X31.*\n.*JALR\sX0, \$\d+, ?X31`)
69+
if !want.Match(out) {
70+
t.Error("Missing assembly instructions")
71+
}
72+
73+
// Build generated files.
74+
cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe", "-ldflags=-linkmode=internal")
75+
cmd.Dir = dir
76+
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
77+
out, err = cmd.CombinedOutput()
4278
if err != nil {
4379
t.Errorf("Build failed: %v, output: %s", err, out)
4480
}
81+
82+
if runtime.GOARCH == "riscv64" && runtime.GOOS == "linux" {
83+
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
84+
out, err = cmd.CombinedOutput()
85+
if err != nil {
86+
t.Errorf("Failed to run test binary: %v", err)
87+
}
88+
if string(out) != "1" {
89+
t.Errorf(`Got test output %q, want "2"`, string(out))
90+
}
91+
}
4592
}
4693

4794
func genLargeBranch(buf *bytes.Buffer) {
48-
fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
49-
fmt.Fprintln(buf, "BEQ X0, X0, label")
50-
for i := 0; i < 1<<19; i++ {
95+
fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-8")
96+
fmt.Fprintln(buf, "MOV X0, X10")
97+
fmt.Fprintln(buf, "BEQZ X10, label")
98+
for i := 0; i < 1<<18; i++ {
99+
// Use a non-compressable instruction.
51100
fmt.Fprintln(buf, "ADD $0, X5, X0")
52101
}
102+
fmt.Fprintln(buf, "ADD $1, X10, X10")
53103
fmt.Fprintln(buf, "label:")
54-
fmt.Fprintln(buf, "ADD $0, X5, X0")
104+
fmt.Fprintln(buf, "ADD $1, X10, X10")
105+
fmt.Fprintln(buf, "MOV X10, r+0(FP)")
106+
fmt.Fprintln(buf, "RET")
55107
}
56108

57109
// TestLargeCall generates a large function (>1MB of text) with a call to
58110
// a following function, in order to ensure that it assembles and links
59-
// correctly.
111+
// correctly. This requires the use of AUIPC+JALR instruction sequences,
112+
// which are fixed up by the linker.
60113
func TestLargeCall(t *testing.T) {
61114
if testing.Short() {
62115
t.Skip("Skipping test in short mode")
@@ -69,12 +122,15 @@ func TestLargeCall(t *testing.T) {
69122
t.Fatalf("Failed to write file: %v\n", err)
70123
}
71124
main := `package main
125+
126+
import "fmt"
127+
72128
func main() {
73-
x()
129+
fmt.Print(x())
74130
}
75131
76-
func x()
77-
func y()
132+
func x() uint64
133+
func y() uint64
78134
`
79135
if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
80136
t.Fatalf("failed to write main: %v\n", err)
@@ -84,51 +140,94 @@ func y()
84140
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
85141
genLargeCall(buf)
86142

87-
if err := os.WriteFile(filepath.Join(dir, "x.s"), buf.Bytes(), 0644); err != nil {
143+
tmpfile := filepath.Join(dir, "x.s")
144+
if err := os.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
88145
t.Fatalf("Failed to write file: %v\n", err)
89146
}
90147

148+
// Assemble generated file.
149+
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
150+
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
151+
out, err := cmd.CombinedOutput()
152+
if err != nil {
153+
t.Errorf("Failed to assemble: %v\n%s", err, out)
154+
}
155+
156+
// The expected instruction sequence for the long call is:
157+
// AUIPC $0, $0, X31
158+
// JALR X.., X31
159+
want := regexp.MustCompile(`\sAUIPC\s\$0, \$0, X31.*\n.*\sJALR\sX.*, X31`)
160+
if !want.Match(out) {
161+
t.Error("Missing assembly instructions")
162+
}
163+
91164
// Build generated files.
92-
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
165+
cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe", "-ldflags=-linkmode=internal")
93166
cmd.Dir = dir
94167
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
95-
out, err := cmd.CombinedOutput()
168+
out, err = cmd.CombinedOutput()
96169
if err != nil {
97170
t.Errorf("Build failed: %v, output: %s", err, out)
98171
}
99172

173+
if runtime.GOARCH == "riscv64" && runtime.GOOS == "linux" {
174+
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
175+
out, err = cmd.CombinedOutput()
176+
if err != nil {
177+
t.Errorf("Failed to run test binary: %v", err)
178+
}
179+
if string(out) != "2" {
180+
t.Errorf(`Got test output %q, want "2"`, string(out))
181+
}
182+
}
183+
100184
if runtime.GOARCH == "riscv64" && testenv.HasCGO() {
101-
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=external")
185+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe", "-ldflags=-linkmode=external")
102186
cmd.Dir = dir
103187
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
104188
out, err := cmd.CombinedOutput()
105189
if err != nil {
106190
t.Errorf("Build failed: %v, output: %s", err, out)
107191
}
192+
193+
if runtime.GOARCH == "riscv64" && runtime.GOOS == "linux" {
194+
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
195+
out, err = cmd.CombinedOutput()
196+
if err != nil {
197+
t.Errorf("Failed to run test binary: %v", err)
198+
}
199+
if string(out) != "2" {
200+
t.Errorf(`Got test output %q, want "2"`, string(out))
201+
}
202+
}
108203
}
109204
}
110205

111206
func genLargeCall(buf *bytes.Buffer) {
112-
fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-0")
207+
fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-8")
208+
fmt.Fprintln(buf, "MOV X0, X10")
113209
fmt.Fprintln(buf, "CALL ·y(SB)")
114-
for i := 0; i < 1<<19; i++ {
210+
fmt.Fprintln(buf, "ADD $1, X10, X10")
211+
fmt.Fprintln(buf, "MOV X10, r+0(FP)")
212+
fmt.Fprintln(buf, "RET")
213+
for i := 0; i < 1<<18; i++ {
214+
// Use a non-compressable instruction.
115215
fmt.Fprintln(buf, "ADD $0, X5, X0")
116216
}
217+
fmt.Fprintln(buf, "ADD $1, X10, X10")
117218
fmt.Fprintln(buf, "RET")
118219
fmt.Fprintln(buf, "TEXT ·y(SB),0,$0-0")
119-
fmt.Fprintln(buf, "ADD $0, X5, X0")
220+
fmt.Fprintln(buf, "ADD $1, X10, X10")
120221
fmt.Fprintln(buf, "RET")
121222
}
122223

123224
// TestLargeJump generates a large jump (>1MB of text) with a JMP to the
124225
// end of the function, in order to ensure that it assembles correctly.
226+
// This requires the use of AUIPC+JALR instruction sequences.
125227
func TestLargeJump(t *testing.T) {
126228
if testing.Short() {
127229
t.Skip("Skipping test in short mode")
128230
}
129-
if runtime.GOARCH != "riscv64" {
130-
t.Skip("Require riscv64 to run")
131-
}
132231
testenv.MustHaveGoBuild(t)
133232

134233
dir := t.TempDir()
@@ -154,22 +253,46 @@ func x() uint64
154253
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
155254
genLargeJump(buf)
156255

157-
if err := os.WriteFile(filepath.Join(dir, "x.s"), buf.Bytes(), 0644); err != nil {
256+
tmpfile := filepath.Join(dir, "x.s")
257+
if err := os.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
158258
t.Fatalf("Failed to write file: %v\n", err)
159259
}
160260

261+
// Assemble generated file.
262+
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
263+
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
264+
out, err := cmd.CombinedOutput()
265+
if err != nil {
266+
t.Errorf("Failed to assemble: %v\n%s", err, out)
267+
}
268+
269+
// The expected instruction sequence for the long call is:
270+
// AUIPC $..., X31
271+
// JALR X0, $.., X31
272+
want := regexp.MustCompile(`\sAUIPC\s\$\d+, X31.*\n.*\sJALR\sX0, \$\d+, ?X31`)
273+
if !want.Match(out) {
274+
t.Error("Missing assembly instructions")
275+
t.Errorf("%s", out)
276+
}
277+
161278
// Build generated files.
162-
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe")
279+
cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe")
163280
cmd.Dir = dir
164-
out, err := cmd.CombinedOutput()
281+
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
282+
out, err = cmd.CombinedOutput()
165283
if err != nil {
166284
t.Errorf("Build failed: %v, output: %s", err, out)
167285
}
168286

169-
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
170-
out, err = cmd.CombinedOutput()
171-
if string(out) != "1" {
172-
t.Errorf(`Got test output %q, want "1"`, string(out))
287+
if runtime.GOARCH == "riscv64" && runtime.GOOS == "linux" {
288+
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
289+
out, err = cmd.CombinedOutput()
290+
if err != nil {
291+
t.Errorf("Failed to run test binary: %v", err)
292+
}
293+
if string(out) != "1" {
294+
t.Errorf(`Got test output %q, want "1"`, string(out))
295+
}
173296
}
174297
}
175298

@@ -178,8 +301,10 @@ func genLargeJump(buf *bytes.Buffer) {
178301
fmt.Fprintln(buf, "MOV X0, X10")
179302
fmt.Fprintln(buf, "JMP end")
180303
for i := 0; i < 1<<18; i++ {
181-
fmt.Fprintln(buf, "ADD $1, X10, X10")
304+
// Use a non-compressable instruction.
305+
fmt.Fprintln(buf, "ADD $0, X5, X0")
182306
}
307+
fmt.Fprintln(buf, "ADD $1, X10, X10")
183308
fmt.Fprintln(buf, "end:")
184309
fmt.Fprintln(buf, "ADD $1, X10, X10")
185310
fmt.Fprintln(buf, "MOV X10, r+0(FP)")

0 commit comments

Comments
 (0)