• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package riscv
6
7import (
8	"bytes"
9	"fmt"
10	"internal/testenv"
11	"os"
12	"os/exec"
13	"path/filepath"
14	"runtime"
15	"strings"
16	"testing"
17)
18
19// TestLargeBranch generates a large function with a very far conditional
20// branch, in order to ensure that it assembles successfully.
21func TestLargeBranch(t *testing.T) {
22	if testing.Short() {
23		t.Skip("Skipping test in short mode")
24	}
25	testenv.MustHaveGoBuild(t)
26
27	dir, err := os.MkdirTemp("", "testlargebranch")
28	if err != nil {
29		t.Fatalf("Could not create directory: %v", err)
30	}
31	defer os.RemoveAll(dir)
32
33	// Generate a very large function.
34	buf := bytes.NewBuffer(make([]byte, 0, 7000000))
35	genLargeBranch(buf)
36
37	tmpfile := filepath.Join(dir, "x.s")
38	if err := os.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
39		t.Fatalf("Failed to write file: %v", err)
40	}
41
42	// Assemble generated file.
43	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
44	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
45	out, err := cmd.CombinedOutput()
46	if err != nil {
47		t.Errorf("Build failed: %v, output: %s", err, out)
48	}
49}
50
51func genLargeBranch(buf *bytes.Buffer) {
52	fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
53	fmt.Fprintln(buf, "BEQ X0, X0, label")
54	for i := 0; i < 1<<19; i++ {
55		fmt.Fprintln(buf, "ADD $0, X0, X0")
56	}
57	fmt.Fprintln(buf, "label:")
58	fmt.Fprintln(buf, "ADD $0, X0, X0")
59}
60
61// TestLargeCall generates a large function (>1MB of text) with a call to
62// a following function, in order to ensure that it assembles and links
63// correctly.
64func TestLargeCall(t *testing.T) {
65	if testing.Short() {
66		t.Skip("Skipping test in short mode")
67	}
68	testenv.MustHaveGoBuild(t)
69
70	dir, err := os.MkdirTemp("", "testlargecall")
71	if err != nil {
72		t.Fatalf("could not create directory: %v", err)
73	}
74	defer os.RemoveAll(dir)
75
76	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module largecall"), 0644); err != nil {
77		t.Fatalf("Failed to write file: %v\n", err)
78	}
79	main := `package main
80func main() {
81        x()
82}
83
84func x()
85func y()
86`
87	if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
88		t.Fatalf("failed to write main: %v\n", err)
89	}
90
91	// Generate a very large function with call.
92	buf := bytes.NewBuffer(make([]byte, 0, 7000000))
93	genLargeCall(buf)
94
95	if err := os.WriteFile(filepath.Join(dir, "x.s"), buf.Bytes(), 0644); err != nil {
96		t.Fatalf("Failed to write file: %v\n", err)
97	}
98
99	// Build generated files.
100	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
101	cmd.Dir = dir
102	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
103	out, err := cmd.CombinedOutput()
104	if err != nil {
105		t.Errorf("Build failed: %v, output: %s", err, out)
106	}
107
108	if runtime.GOARCH == "riscv64" && testenv.HasCGO() {
109		cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=external")
110		cmd.Dir = dir
111		cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
112		out, err := cmd.CombinedOutput()
113		if err != nil {
114			t.Errorf("Build failed: %v, output: %s", err, out)
115		}
116	}
117}
118
119func genLargeCall(buf *bytes.Buffer) {
120	fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-0")
121	fmt.Fprintln(buf, "CALL ·y(SB)")
122	for i := 0; i < 1<<19; i++ {
123		fmt.Fprintln(buf, "ADD $0, X0, X0")
124	}
125	fmt.Fprintln(buf, "RET")
126	fmt.Fprintln(buf, "TEXT ·y(SB),0,$0-0")
127	fmt.Fprintln(buf, "ADD $0, X0, X0")
128	fmt.Fprintln(buf, "RET")
129}
130
131// TestLargeJump generates a large jump (>1MB of text) with a JMP to the
132// end of the function, in order to ensure that it assembles correctly.
133func TestLargeJump(t *testing.T) {
134	if testing.Short() {
135		t.Skip("Skipping test in short mode")
136	}
137	if runtime.GOARCH != "riscv64" {
138		t.Skip("Require riscv64 to run")
139	}
140	testenv.MustHaveGoBuild(t)
141
142	dir := t.TempDir()
143
144	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module largejump"), 0644); err != nil {
145		t.Fatalf("Failed to write file: %v\n", err)
146	}
147	main := `package main
148
149import "fmt"
150
151func main() {
152        fmt.Print(x())
153}
154
155func x() uint64
156`
157	if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
158		t.Fatalf("failed to write main: %v\n", err)
159	}
160
161	// Generate a very large jump instruction.
162	buf := bytes.NewBuffer(make([]byte, 0, 7000000))
163	genLargeJump(buf)
164
165	if err := os.WriteFile(filepath.Join(dir, "x.s"), buf.Bytes(), 0644); err != nil {
166		t.Fatalf("Failed to write file: %v\n", err)
167	}
168
169	// Build generated files.
170	cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe")
171	cmd.Dir = dir
172	out, err := cmd.CombinedOutput()
173	if err != nil {
174		t.Errorf("Build failed: %v, output: %s", err, out)
175	}
176
177	cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
178	out, err = cmd.CombinedOutput()
179	if string(out) != "1" {
180		t.Errorf(`Got test output %q, want "1"`, string(out))
181	}
182}
183
184func genLargeJump(buf *bytes.Buffer) {
185	fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-8")
186	fmt.Fprintln(buf, "MOV  X0, X10")
187	fmt.Fprintln(buf, "JMP end")
188	for i := 0; i < 1<<18; i++ {
189		fmt.Fprintln(buf, "ADD $1, X10, X10")
190	}
191	fmt.Fprintln(buf, "end:")
192	fmt.Fprintln(buf, "ADD $1, X10, X10")
193	fmt.Fprintln(buf, "MOV X10, r+0(FP)")
194	fmt.Fprintln(buf, "RET")
195}
196
197// Issue 20348.
198func TestNoRet(t *testing.T) {
199	dir, err := os.MkdirTemp("", "testnoret")
200	if err != nil {
201		t.Fatal(err)
202	}
203	defer os.RemoveAll(dir)
204	tmpfile := filepath.Join(dir, "x.s")
205	if err := os.WriteFile(tmpfile, []byte("TEXT ·stub(SB),$0-0\nNOP\n"), 0644); err != nil {
206		t.Fatal(err)
207	}
208	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
209	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
210	if out, err := cmd.CombinedOutput(); err != nil {
211		t.Errorf("%v\n%s", err, out)
212	}
213}
214
215func TestImmediateSplitting(t *testing.T) {
216	dir, err := os.MkdirTemp("", "testimmsplit")
217	if err != nil {
218		t.Fatal(err)
219	}
220	defer os.RemoveAll(dir)
221	tmpfile := filepath.Join(dir, "x.s")
222	asm := `
223TEXT _stub(SB),$0-0
224	LB	4096(X5), X6
225	LH	4096(X5), X6
226	LW	4096(X5), X6
227	LD	4096(X5), X6
228	LBU	4096(X5), X6
229	LHU	4096(X5), X6
230	LWU	4096(X5), X6
231	SB	X6, 4096(X5)
232	SH	X6, 4096(X5)
233	SW	X6, 4096(X5)
234	SD	X6, 4096(X5)
235
236	FLW	4096(X5), F6
237	FLD	4096(X5), F6
238	FSW	F6, 4096(X5)
239	FSD	F6, 4096(X5)
240
241	MOVB	4096(X5), X6
242	MOVH	4096(X5), X6
243	MOVW	4096(X5), X6
244	MOV	4096(X5), X6
245	MOVBU	4096(X5), X6
246	MOVHU	4096(X5), X6
247	MOVWU	4096(X5), X6
248
249	MOVB	X6, 4096(X5)
250	MOVH	X6, 4096(X5)
251	MOVW	X6, 4096(X5)
252	MOV	X6, 4096(X5)
253
254	MOVF	4096(X5), F6
255	MOVD	4096(X5), F6
256	MOVF	F6, 4096(X5)
257	MOVD	F6, 4096(X5)
258`
259	if err := os.WriteFile(tmpfile, []byte(asm), 0644); err != nil {
260		t.Fatal(err)
261	}
262	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
263	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
264	if out, err := cmd.CombinedOutput(); err != nil {
265		t.Errorf("%v\n%s", err, out)
266	}
267}
268
269func TestBranch(t *testing.T) {
270	if runtime.GOARCH != "riscv64" {
271		t.Skip("Requires riscv64 to run")
272	}
273
274	testenv.MustHaveGoBuild(t)
275
276	cmd := testenv.Command(t, testenv.GoToolPath(t), "test")
277	cmd.Dir = "testdata/testbranch"
278	if out, err := testenv.CleanCmdEnv(cmd).CombinedOutput(); err != nil {
279		t.Errorf("Branch test failed: %v\n%s", err, out)
280	}
281}
282
283func TestPCAlign(t *testing.T) {
284	dir := t.TempDir()
285	tmpfile := filepath.Join(dir, "x.s")
286	asm := `
287TEXT _stub(SB),$0-0
288	FENCE
289	PCALIGN	$8
290	FENCE
291	RET
292`
293	if err := os.WriteFile(tmpfile, []byte(asm), 0644); err != nil {
294		t.Fatal(err)
295	}
296	cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
297	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
298	out, err := cmd.CombinedOutput()
299	if err != nil {
300		t.Errorf("Failed to assemble: %v\n%s", err, out)
301	}
302	// The expected instruction sequence after alignment:
303	//	FENCE
304	//	NOP
305	//	FENCE
306	//	RET
307	want := "0f 00 f0 0f 13 00 00 00 0f 00 f0 0f 67 80 00 00"
308	if !strings.Contains(string(out), want) {
309		t.Errorf("PCALIGN test failed - got %s\nwant %s", out, want)
310	}
311}
312