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

     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  	"internal/testenv"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"regexp"
    15  	"runtime"
    16  	"testing"
    17  )
    18  
    19  // TestLargeBranch generates a large function with a very far conditional
    20  // branch, in order to ensure that it assembles correctly. This requires
    21  // inverting the branch and using a jump to reach the target.
    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 := t.TempDir()
    29  
    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  
    47  	// Generate a very large function.
    48  	buf := bytes.NewBuffer(make([]byte, 0, 7000000))
    49  	genLargeBranch(buf)
    50  
    51  	tmpfile := filepath.Join(dir, "x.s")
    52  	if err := os.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
    53  		t.Fatalf("Failed to write file: %v", err)
    54  	}
    55  
    56  	// Assemble generated file.
    57  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
    58  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
    59  	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()
    78  	if err != nil {
    79  		t.Errorf("Build failed: %v, output: %s", err, out)
    80  	}
    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  	}
    92  }
    93  
    94  func genLargeBranch(buf *bytes.Buffer) {
    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.
   100  		fmt.Fprintln(buf, "ADD $0, X5, X0")
   101  	}
   102  	fmt.Fprintln(buf, "ADD $1, X10, X10")
   103  	fmt.Fprintln(buf, "label:")
   104  	fmt.Fprintln(buf, "ADD $1, X10, X10")
   105  	fmt.Fprintln(buf, "MOV X10, r+0(FP)")
   106  	fmt.Fprintln(buf, "RET")
   107  }
   108  
   109  // TestLargeCall generates a large function (>1MB of text) with a call to
   110  // a following function, in order to ensure that it assembles and links
   111  // correctly. This requires the use of AUIPC+JALR instruction sequences,
   112  // which are fixed up by the linker.
   113  func TestLargeCall(t *testing.T) {
   114  	if testing.Short() {
   115  		t.Skip("Skipping test in short mode")
   116  	}
   117  	testenv.MustHaveGoBuild(t)
   118  
   119  	dir := t.TempDir()
   120  
   121  	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module largecall"), 0644); err != nil {
   122  		t.Fatalf("Failed to write file: %v\n", err)
   123  	}
   124  	main := `package main
   125  
   126  import "fmt"
   127  
   128  func main() {
   129          fmt.Print(x())
   130  }
   131  
   132  func x() uint64
   133  func y() uint64
   134  `
   135  	if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
   136  		t.Fatalf("failed to write main: %v\n", err)
   137  	}
   138  
   139  	// Generate a very large function with call.
   140  	buf := bytes.NewBuffer(make([]byte, 0, 7000000))
   141  	genLargeCall(buf)
   142  
   143  	tmpfile := filepath.Join(dir, "x.s")
   144  	if err := os.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
   145  		t.Fatalf("Failed to write file: %v\n", err)
   146  	}
   147  
   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  
   164  	// Build generated files.
   165  	cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe", "-ldflags=-linkmode=internal")
   166  	cmd.Dir = dir
   167  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   168  	out, err = cmd.CombinedOutput()
   169  	if err != nil {
   170  		t.Errorf("Build failed: %v, output: %s", err, out)
   171  	}
   172  
   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  
   184  	if runtime.GOARCH == "riscv64" && testenv.HasCGO() {
   185  		cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe", "-ldflags=-linkmode=external")
   186  		cmd.Dir = dir
   187  		cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   188  		out, err := cmd.CombinedOutput()
   189  		if err != nil {
   190  			t.Errorf("Build failed: %v, output: %s", err, out)
   191  		}
   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  		}
   203  	}
   204  }
   205  
   206  func genLargeCall(buf *bytes.Buffer) {
   207  	fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-8")
   208  	fmt.Fprintln(buf, "MOV  X0, X10")
   209  	fmt.Fprintln(buf, "CALL ·y(SB)")
   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.
   215  		fmt.Fprintln(buf, "ADD $0, X5, X0")
   216  	}
   217  	fmt.Fprintln(buf, "ADD $1, X10, X10")
   218  	fmt.Fprintln(buf, "RET")
   219  	fmt.Fprintln(buf, "TEXT ·y(SB),0,$0-0")
   220  	fmt.Fprintln(buf, "ADD $1, X10, X10")
   221  	fmt.Fprintln(buf, "RET")
   222  }
   223  
   224  // TestLargeJump generates a large jump (>1MB of text) with a JMP to the
   225  // end of the function, in order to ensure that it assembles correctly.
   226  // This requires the use of AUIPC+JALR instruction sequences.
   227  func TestLargeJump(t *testing.T) {
   228  	if testing.Short() {
   229  		t.Skip("Skipping test in short mode")
   230  	}
   231  	testenv.MustHaveGoBuild(t)
   232  
   233  	dir := t.TempDir()
   234  
   235  	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module largejump"), 0644); err != nil {
   236  		t.Fatalf("Failed to write file: %v\n", err)
   237  	}
   238  	main := `package main
   239  
   240  import "fmt"
   241  
   242  func main() {
   243          fmt.Print(x())
   244  }
   245  
   246  func x() uint64
   247  `
   248  	if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
   249  		t.Fatalf("failed to write main: %v\n", err)
   250  	}
   251  
   252  	// Generate a very large jump instruction.
   253  	buf := bytes.NewBuffer(make([]byte, 0, 7000000))
   254  	genLargeJump(buf)
   255  
   256  	tmpfile := filepath.Join(dir, "x.s")
   257  	if err := os.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
   258  		t.Fatalf("Failed to write file: %v\n", err)
   259  	}
   260  
   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  
   278  	// Build generated files.
   279  	cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe")
   280  	cmd.Dir = dir
   281  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   282  	out, err = cmd.CombinedOutput()
   283  	if err != nil {
   284  		t.Errorf("Build failed: %v, output: %s", err, out)
   285  	}
   286  
   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  		}
   296  	}
   297  }
   298  
   299  func genLargeJump(buf *bytes.Buffer) {
   300  	fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-8")
   301  	fmt.Fprintln(buf, "MOV  X0, X10")
   302  	fmt.Fprintln(buf, "JMP end")
   303  	for i := 0; i < 1<<18; i++ {
   304  		// Use a non-compressable instruction.
   305  		fmt.Fprintln(buf, "ADD $0, X5, X0")
   306  	}
   307  	fmt.Fprintln(buf, "ADD $1, X10, X10")
   308  	fmt.Fprintln(buf, "end:")
   309  	fmt.Fprintln(buf, "ADD $1, X10, X10")
   310  	fmt.Fprintln(buf, "MOV X10, r+0(FP)")
   311  	fmt.Fprintln(buf, "RET")
   312  }
   313  
   314  // Issue 20348.
   315  func TestNoRet(t *testing.T) {
   316  	dir := t.TempDir()
   317  	tmpfile := filepath.Join(dir, "x.s")
   318  	if err := os.WriteFile(tmpfile, []byte("TEXT ·stub(SB),$0-0\nNOP\n"), 0644); err != nil {
   319  		t.Fatal(err)
   320  	}
   321  	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
   322  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   323  	if out, err := cmd.CombinedOutput(); err != nil {
   324  		t.Errorf("%v\n%s", err, out)
   325  	}
   326  }
   327  
   328  func TestImmediateSplitting(t *testing.T) {
   329  	dir := t.TempDir()
   330  	tmpfile := filepath.Join(dir, "x.s")
   331  	asm := `
   332  TEXT _stub(SB),$0-0
   333  	LB	4096(X5), X6
   334  	LH	4096(X5), X6
   335  	LW	4096(X5), X6
   336  	LD	4096(X5), X6
   337  	LBU	4096(X5), X6
   338  	LHU	4096(X5), X6
   339  	LWU	4096(X5), X6
   340  	SB	X6, 4096(X5)
   341  	SH	X6, 4096(X5)
   342  	SW	X6, 4096(X5)
   343  	SD	X6, 4096(X5)
   344  
   345  	FLW	4096(X5), F6
   346  	FLD	4096(X5), F6
   347  	FSW	F6, 4096(X5)
   348  	FSD	F6, 4096(X5)
   349  
   350  	MOVB	4096(X5), X6
   351  	MOVH	4096(X5), X6
   352  	MOVW	4096(X5), X6
   353  	MOV	4096(X5), X6
   354  	MOVBU	4096(X5), X6
   355  	MOVHU	4096(X5), X6
   356  	MOVWU	4096(X5), X6
   357  
   358  	MOVB	X6, 4096(X5)
   359  	MOVH	X6, 4096(X5)
   360  	MOVW	X6, 4096(X5)
   361  	MOV	X6, 4096(X5)
   362  
   363  	MOVF	4096(X5), F6
   364  	MOVD	4096(X5), F6
   365  	MOVF	F6, 4096(X5)
   366  	MOVD	F6, 4096(X5)
   367  `
   368  	if err := os.WriteFile(tmpfile, []byte(asm), 0644); err != nil {
   369  		t.Fatal(err)
   370  	}
   371  	cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
   372  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   373  	if out, err := cmd.CombinedOutput(); err != nil {
   374  		t.Errorf("%v\n%s", err, out)
   375  	}
   376  }
   377  
   378  func TestBranch(t *testing.T) {
   379  	if runtime.GOARCH != "riscv64" {
   380  		t.Skip("Requires riscv64 to run")
   381  	}
   382  
   383  	testenv.MustHaveGoBuild(t)
   384  
   385  	cmd := testenv.Command(t, testenv.GoToolPath(t), "test")
   386  	cmd.Dir = "testdata/testbranch"
   387  	if out, err := testenv.CleanCmdEnv(cmd).CombinedOutput(); err != nil {
   388  		t.Errorf("Branch test failed: %v\n%s", err, out)
   389  	}
   390  }
   391  
   392  func TestMinMax(t *testing.T) {
   393  	if runtime.GOARCH != "riscv64" {
   394  		t.Skip("Requires riscv64 to run")
   395  	}
   396  
   397  	testenv.MustHaveGoBuild(t)
   398  
   399  	cmd := testenv.Command(t, testenv.GoToolPath(t), "test")
   400  	cmd.Dir = "testdata/testminmax"
   401  	if out, err := testenv.CleanCmdEnv(cmd).CombinedOutput(); err != nil {
   402  		t.Errorf("Min max test failed: %v\n%s", err, out)
   403  	}
   404  }
   405  
   406  func TestPCAlign(t *testing.T) {
   407  	dir := t.TempDir()
   408  	tmpfile := filepath.Join(dir, "x.s")
   409  	asm := `
   410  TEXT _stub(SB),$0-0
   411  	FENCE
   412  	PCALIGN	$8
   413  	FENCE
   414  	RET
   415  `
   416  	if err := os.WriteFile(tmpfile, []byte(asm), 0644); err != nil {
   417  		t.Fatal(err)
   418  	}
   419  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
   420  	cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
   421  	out, err := cmd.CombinedOutput()
   422  	if err != nil {
   423  		t.Errorf("Failed to assemble: %v\n%s", err, out)
   424  	}
   425  	// The expected instruction sequence after alignment:
   426  	//	FENCE
   427  	//	NOP
   428  	//	FENCE
   429  	//	RET	(CJALR or JALR)
   430  	want := regexp.MustCompile("0x0000 0f 00 f0 0f 13 00 00 00 0f 00 f0 0f (82 80|67 80 00 00) ")
   431  	if !want.Match(out) {
   432  		t.Errorf("PCALIGN test failed - got %s\nwant %s", out, want)
   433  	}
   434  }
   435  

View as plain text