mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/internal/obj/riscv: improve large branch/call/jump tests
Rework these tests such that they are built on all architectures and actually executed when run on riscv64. This increases the likelihood of catching code generation issues, especially those that impact relocations. Also ensure that the generated assembly includes the instruction sequence that is expected for the large branch/call/jump. Change-Id: I15c40a439dd1d0d4ed189ab81697e93d82c4ef4d Reviewed-on: https://go-review.googlesource.com/c/go/+/721621 Reviewed-by: Mark Ryan <markdryan@rivosinc.com> Reviewed-by: Mark Freeman <markfreeman@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Junyang Shao <shaojunyang@google.com>
This commit is contained in:
parent
31aa9f800b
commit
3c26aef8fb
1 changed files with 155 additions and 30 deletions
|
|
@ -17,7 +17,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestLargeBranch generates a large function with a very far conditional
|
// TestLargeBranch generates a large function with a very far conditional
|
||||||
// branch, in order to ensure that it assembles successfully.
|
// branch, in order to ensure that it assembles correctly. This requires
|
||||||
|
// inverting the branch and using a jump to reach the target.
|
||||||
func TestLargeBranch(t *testing.T) {
|
func TestLargeBranch(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("Skipping test in short mode")
|
t.Skip("Skipping test in short mode")
|
||||||
|
|
@ -26,6 +27,23 @@ func TestLargeBranch(t *testing.T) {
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module largecall"), 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to write file: %v\n", err)
|
||||||
|
}
|
||||||
|
main := `package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Print(x())
|
||||||
|
}
|
||||||
|
|
||||||
|
func x() uint64
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
|
||||||
|
t.Fatalf("failed to write main: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Generate a very large function.
|
// Generate a very large function.
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
|
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
|
||||||
genLargeBranch(buf)
|
genLargeBranch(buf)
|
||||||
|
|
@ -36,27 +54,62 @@ func TestLargeBranch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assemble generated file.
|
// Assemble generated file.
|
||||||
cmd := testenv.Command(t, testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
|
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
|
||||||
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to assemble: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The expected instruction sequence for the long branch is:
|
||||||
|
// BNEZ
|
||||||
|
// AUIPC $..., X31
|
||||||
|
// JALR X0, $..., X31
|
||||||
|
want := regexp.MustCompile(`\sBNEZ\s.*\s.*\n.*\n.*AUIPC\s\$\d+, X31.*\n.*JALR\sX0, \$\d+, ?X31`)
|
||||||
|
if !want.Match(out) {
|
||||||
|
t.Error("Missing assembly instructions")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build generated files.
|
||||||
|
cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe", "-ldflags=-linkmode=internal")
|
||||||
|
cmd.Dir = dir
|
||||||
|
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Build failed: %v, output: %s", err, out)
|
t.Errorf("Build failed: %v, output: %s", err, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOARCH == "riscv64" && runtime.GOOS == "linux" {
|
||||||
|
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to run test binary: %v", err)
|
||||||
|
}
|
||||||
|
if string(out) != "1" {
|
||||||
|
t.Errorf(`Got test output %q, want "2"`, string(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func genLargeBranch(buf *bytes.Buffer) {
|
func genLargeBranch(buf *bytes.Buffer) {
|
||||||
fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
|
fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-8")
|
||||||
fmt.Fprintln(buf, "BEQ X0, X0, label")
|
fmt.Fprintln(buf, "MOV X0, X10")
|
||||||
for i := 0; i < 1<<19; i++ {
|
fmt.Fprintln(buf, "BEQZ X10, label")
|
||||||
|
for i := 0; i < 1<<18; i++ {
|
||||||
|
// Use a non-compressable instruction.
|
||||||
fmt.Fprintln(buf, "ADD $0, X5, X0")
|
fmt.Fprintln(buf, "ADD $0, X5, X0")
|
||||||
}
|
}
|
||||||
|
fmt.Fprintln(buf, "ADD $1, X10, X10")
|
||||||
fmt.Fprintln(buf, "label:")
|
fmt.Fprintln(buf, "label:")
|
||||||
fmt.Fprintln(buf, "ADD $0, X5, X0")
|
fmt.Fprintln(buf, "ADD $1, X10, X10")
|
||||||
|
fmt.Fprintln(buf, "MOV X10, r+0(FP)")
|
||||||
|
fmt.Fprintln(buf, "RET")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLargeCall generates a large function (>1MB of text) with a call to
|
// TestLargeCall generates a large function (>1MB of text) with a call to
|
||||||
// a following function, in order to ensure that it assembles and links
|
// a following function, in order to ensure that it assembles and links
|
||||||
// correctly.
|
// correctly. This requires the use of AUIPC+JALR instruction sequences,
|
||||||
|
// which are fixed up by the linker.
|
||||||
func TestLargeCall(t *testing.T) {
|
func TestLargeCall(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("Skipping test in short mode")
|
t.Skip("Skipping test in short mode")
|
||||||
|
|
@ -69,12 +122,15 @@ func TestLargeCall(t *testing.T) {
|
||||||
t.Fatalf("Failed to write file: %v\n", err)
|
t.Fatalf("Failed to write file: %v\n", err)
|
||||||
}
|
}
|
||||||
main := `package main
|
main := `package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
x()
|
fmt.Print(x())
|
||||||
}
|
}
|
||||||
|
|
||||||
func x()
|
func x() uint64
|
||||||
func y()
|
func y() uint64
|
||||||
`
|
`
|
||||||
if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte(main), 0644); err != nil {
|
||||||
t.Fatalf("failed to write main: %v\n", err)
|
t.Fatalf("failed to write main: %v\n", err)
|
||||||
|
|
@ -84,51 +140,94 @@ func y()
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
|
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
|
||||||
genLargeCall(buf)
|
genLargeCall(buf)
|
||||||
|
|
||||||
if err := os.WriteFile(filepath.Join(dir, "x.s"), buf.Bytes(), 0644); err != nil {
|
tmpfile := filepath.Join(dir, "x.s")
|
||||||
|
if err := os.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
|
||||||
t.Fatalf("Failed to write file: %v\n", err)
|
t.Fatalf("Failed to write file: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build generated files.
|
// Assemble generated file.
|
||||||
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
|
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
|
||||||
cmd.Dir = dir
|
|
||||||
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to assemble: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The expected instruction sequence for the long call is:
|
||||||
|
// AUIPC $0, $0, X31
|
||||||
|
// JALR X.., X31
|
||||||
|
want := regexp.MustCompile(`\sAUIPC\s\$0, \$0, X31.*\n.*\sJALR\sX.*, X31`)
|
||||||
|
if !want.Match(out) {
|
||||||
|
t.Error("Missing assembly instructions")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build generated files.
|
||||||
|
cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe", "-ldflags=-linkmode=internal")
|
||||||
|
cmd.Dir = dir
|
||||||
|
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Build failed: %v, output: %s", err, out)
|
t.Errorf("Build failed: %v, output: %s", err, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOARCH == "riscv64" && runtime.GOOS == "linux" {
|
||||||
|
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to run test binary: %v", err)
|
||||||
|
}
|
||||||
|
if string(out) != "2" {
|
||||||
|
t.Errorf(`Got test output %q, want "2"`, string(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if runtime.GOARCH == "riscv64" && testenv.HasCGO() {
|
if runtime.GOARCH == "riscv64" && testenv.HasCGO() {
|
||||||
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=external")
|
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe", "-ldflags=-linkmode=external")
|
||||||
cmd.Dir = dir
|
cmd.Dir = dir
|
||||||
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Build failed: %v, output: %s", err, out)
|
t.Errorf("Build failed: %v, output: %s", err, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOARCH == "riscv64" && runtime.GOOS == "linux" {
|
||||||
|
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to run test binary: %v", err)
|
||||||
|
}
|
||||||
|
if string(out) != "2" {
|
||||||
|
t.Errorf(`Got test output %q, want "2"`, string(out))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func genLargeCall(buf *bytes.Buffer) {
|
func genLargeCall(buf *bytes.Buffer) {
|
||||||
fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-0")
|
fmt.Fprintln(buf, "TEXT ·x(SB),0,$0-8")
|
||||||
|
fmt.Fprintln(buf, "MOV X0, X10")
|
||||||
fmt.Fprintln(buf, "CALL ·y(SB)")
|
fmt.Fprintln(buf, "CALL ·y(SB)")
|
||||||
for i := 0; i < 1<<19; i++ {
|
fmt.Fprintln(buf, "ADD $1, X10, X10")
|
||||||
|
fmt.Fprintln(buf, "MOV X10, r+0(FP)")
|
||||||
|
fmt.Fprintln(buf, "RET")
|
||||||
|
for i := 0; i < 1<<18; i++ {
|
||||||
|
// Use a non-compressable instruction.
|
||||||
fmt.Fprintln(buf, "ADD $0, X5, X0")
|
fmt.Fprintln(buf, "ADD $0, X5, X0")
|
||||||
}
|
}
|
||||||
|
fmt.Fprintln(buf, "ADD $1, X10, X10")
|
||||||
fmt.Fprintln(buf, "RET")
|
fmt.Fprintln(buf, "RET")
|
||||||
fmt.Fprintln(buf, "TEXT ·y(SB),0,$0-0")
|
fmt.Fprintln(buf, "TEXT ·y(SB),0,$0-0")
|
||||||
fmt.Fprintln(buf, "ADD $0, X5, X0")
|
fmt.Fprintln(buf, "ADD $1, X10, X10")
|
||||||
fmt.Fprintln(buf, "RET")
|
fmt.Fprintln(buf, "RET")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLargeJump generates a large jump (>1MB of text) with a JMP to the
|
// TestLargeJump generates a large jump (>1MB of text) with a JMP to the
|
||||||
// end of the function, in order to ensure that it assembles correctly.
|
// end of the function, in order to ensure that it assembles correctly.
|
||||||
|
// This requires the use of AUIPC+JALR instruction sequences.
|
||||||
func TestLargeJump(t *testing.T) {
|
func TestLargeJump(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("Skipping test in short mode")
|
t.Skip("Skipping test in short mode")
|
||||||
}
|
}
|
||||||
if runtime.GOARCH != "riscv64" {
|
|
||||||
t.Skip("Require riscv64 to run")
|
|
||||||
}
|
|
||||||
testenv.MustHaveGoBuild(t)
|
testenv.MustHaveGoBuild(t)
|
||||||
|
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
@ -154,22 +253,46 @@ func x() uint64
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
|
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
|
||||||
genLargeJump(buf)
|
genLargeJump(buf)
|
||||||
|
|
||||||
if err := os.WriteFile(filepath.Join(dir, "x.s"), buf.Bytes(), 0644); err != nil {
|
tmpfile := filepath.Join(dir, "x.s")
|
||||||
|
if err := os.WriteFile(tmpfile, buf.Bytes(), 0644); err != nil {
|
||||||
t.Fatalf("Failed to write file: %v\n", err)
|
t.Fatalf("Failed to write file: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build generated files.
|
// Assemble generated file.
|
||||||
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe")
|
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), "-S", tmpfile)
|
||||||
cmd.Dir = dir
|
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to assemble: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The expected instruction sequence for the long call is:
|
||||||
|
// AUIPC $..., X31
|
||||||
|
// JALR X0, $.., X31
|
||||||
|
want := regexp.MustCompile(`\sAUIPC\s\$\d+, X31.*\n.*\sJALR\sX0, \$\d+, ?X31`)
|
||||||
|
if !want.Match(out) {
|
||||||
|
t.Error("Missing assembly instructions")
|
||||||
|
t.Errorf("%s", out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build generated files.
|
||||||
|
cmd = testenv.Command(t, testenv.GoToolPath(t), "build", "-o", "x.exe")
|
||||||
|
cmd.Dir = dir
|
||||||
|
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
||||||
|
out, err = cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Build failed: %v, output: %s", err, out)
|
t.Errorf("Build failed: %v, output: %s", err, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
|
if runtime.GOARCH == "riscv64" && runtime.GOOS == "linux" {
|
||||||
out, err = cmd.CombinedOutput()
|
cmd = testenv.Command(t, filepath.Join(dir, "x.exe"))
|
||||||
if string(out) != "1" {
|
out, err = cmd.CombinedOutput()
|
||||||
t.Errorf(`Got test output %q, want "1"`, string(out))
|
if err != nil {
|
||||||
|
t.Errorf("Failed to run test binary: %v", err)
|
||||||
|
}
|
||||||
|
if string(out) != "1" {
|
||||||
|
t.Errorf(`Got test output %q, want "1"`, string(out))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,8 +301,10 @@ func genLargeJump(buf *bytes.Buffer) {
|
||||||
fmt.Fprintln(buf, "MOV X0, X10")
|
fmt.Fprintln(buf, "MOV X0, X10")
|
||||||
fmt.Fprintln(buf, "JMP end")
|
fmt.Fprintln(buf, "JMP end")
|
||||||
for i := 0; i < 1<<18; i++ {
|
for i := 0; i < 1<<18; i++ {
|
||||||
fmt.Fprintln(buf, "ADD $1, X10, X10")
|
// Use a non-compressable instruction.
|
||||||
|
fmt.Fprintln(buf, "ADD $0, X5, X0")
|
||||||
}
|
}
|
||||||
|
fmt.Fprintln(buf, "ADD $1, X10, X10")
|
||||||
fmt.Fprintln(buf, "end:")
|
fmt.Fprintln(buf, "end:")
|
||||||
fmt.Fprintln(buf, "ADD $1, X10, X10")
|
fmt.Fprintln(buf, "ADD $1, X10, X10")
|
||||||
fmt.Fprintln(buf, "MOV X10, r+0(FP)")
|
fmt.Fprintln(buf, "MOV X10, r+0(FP)")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue