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  }