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