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