cmd/asm,cmd/internal/obj/riscv: provide branch pseudo-instructions

Implement various branch pseudo-instructions for riscv64. These make it easier
to read/write assembly and will also make it easier for the compiler to generate
optimised code.

Change-Id: Ic31a7748c0e1495522ebecf34b440842b8d12c04
Reviewed-on: https://go-review.googlesource.com/c/go/+/226397
Run-TryBot: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/src/cmd/asm/internal/arch/arch.go b/src/cmd/asm/internal/arch/arch.go
index d9ba667..2e5d0ff 100644
--- a/src/cmd/asm/internal/arch/arch.go
+++ b/src/cmd/asm/internal/arch/arch.go
@@ -88,7 +88,8 @@
 
 func jumpRISCV(word string) bool {
 	switch word {
-	case "BEQ", "BNE", "BLT", "BGE", "BLTU", "BGEU", "CALL", "JAL", "JALR", "JMP":
+	case "BEQ", "BEQZ", "BGE", "BGEU", "BGEZ", "BGT", "BGTU", "BGTZ", "BLE", "BLEU", "BLEZ",
+		"BLT", "BLTU", "BLTZ", "BNE", "BNEZ", "CALL", "JAL", "JALR", "JMP":
 		return true
 	}
 	return false
diff --git a/src/cmd/asm/internal/asm/testdata/riscvenc.s b/src/cmd/asm/internal/asm/testdata/riscvenc.s
index 74bc43d..8d301f2 100644
--- a/src/cmd/asm/internal/asm/testdata/riscvenc.s
+++ b/src/cmd/asm/internal/asm/testdata/riscvenc.s
@@ -330,6 +330,19 @@
 	CALL	asmtest(SB)				// 970f0000
 	JMP	asmtest(SB)				// 970f0000
 
+	// Branch pseudo-instructions
+	BEQZ	X5, start	// BEQZ	X5, 2		// e38a02c2
+	BGEZ	X5, start	// BGEZ	X5, 2		// e3d802c2
+	BGT	X5, X6, start	// BGT	X5, X6, 2	// e3c662c2
+	BGTU	X5, X6, start	// BGTU	X5, X6, 2	// e3e462c2
+	BGTZ	X5, start	// BGTZ	X5, 2		// e34250c2
+	BLE	X5, X6, start	// BLE	X5, X6, 2	// e3d062c2
+	BLEU	X5, X6, start	// BLEU	X5, X6, 2	// e3fe62c0
+	BLEZ	X5, start	// BLEZ	X5, 2		// e35c50c0
+	BLTZ	X5, start	// BLTZ	X5, 2		// e3ca02c0
+	BNEZ	X5, start	// BNEZ	X5, 2		// e39802c0
+
+	// Set pseudo-instructions
 	SEQZ	X15, X15				// 93b71700
 	SNEZ	X15, X15				// b337f000
 
diff --git a/src/cmd/internal/obj/riscv/anames.go b/src/cmd/internal/obj/riscv/anames.go
index fa236d8..6581bb3 100644
--- a/src/cmd/internal/obj/riscv/anames.go
+++ b/src/cmd/internal/obj/riscv/anames.go
@@ -226,6 +226,16 @@
 	"HFENCEGVMA",
 	"HFENCEVVMA",
 	"WORD",
+	"BEQZ",
+	"BGEZ",
+	"BGT",
+	"BGTU",
+	"BGTZ",
+	"BLE",
+	"BLEU",
+	"BLEZ",
+	"BLTZ",
+	"BNEZ",
 	"FNEGD",
 	"FNEGS",
 	"FNED",
diff --git a/src/cmd/internal/obj/riscv/asm_test.go b/src/cmd/internal/obj/riscv/asm_test.go
index 849a87b..f8f7b4f 100644
--- a/src/cmd/internal/obj/riscv/asm_test.go
+++ b/src/cmd/internal/obj/riscv/asm_test.go
@@ -12,6 +12,7 @@
 	"os"
 	"os/exec"
 	"path/filepath"
+	"runtime"
 	"testing"
 )
 
@@ -131,3 +132,20 @@
 		t.Errorf("%v\n%s", err, out)
 	}
 }
+
+func TestBranch(t *testing.T) {
+	if testing.Short() {
+		t.Skip("Skipping in short mode")
+	}
+	if runtime.GOARCH != "riscv64" {
+		t.Skip("Requires riscv64 to run")
+	}
+
+	testenv.MustHaveGoBuild(t)
+
+	cmd := exec.Command(testenv.GoToolPath(t), "test")
+	cmd.Dir = "testdata/testbranch"
+	if out, err := testenv.CleanCmdEnv(cmd).CombinedOutput(); err != nil {
+		t.Errorf("Branch test failed: %v\n%s", err, out)
+	}
+}
diff --git a/src/cmd/internal/obj/riscv/cpu.go b/src/cmd/internal/obj/riscv/cpu.go
index 76457dd..482f9e0 100644
--- a/src/cmd/internal/obj/riscv/cpu.go
+++ b/src/cmd/internal/obj/riscv/cpu.go
@@ -576,6 +576,16 @@
 
 	// Pseudo-instructions.  These get translated by the assembler into other
 	// instructions, based on their operands.
+	ABEQZ
+	ABGEZ
+	ABGT
+	ABGTU
+	ABGTZ
+	ABLE
+	ABLEU
+	ABLEZ
+	ABLTZ
+	ABNEZ
 	AFNEGD
 	AFNEGS
 	AFNED
diff --git a/src/cmd/internal/obj/riscv/obj.go b/src/cmd/internal/obj/riscv/obj.go
index ed5d533..73fe8c2 100644
--- a/src/cmd/internal/obj/riscv/obj.go
+++ b/src/cmd/internal/obj/riscv/obj.go
@@ -406,20 +406,40 @@
 }
 
 // InvertBranch inverts the condition of a conditional branch.
-func InvertBranch(i obj.As) obj.As {
-	switch i {
+func InvertBranch(as obj.As) obj.As {
+	switch as {
 	case ABEQ:
 		return ABNE
-	case ABNE:
-		return ABEQ
-	case ABLT:
-		return ABGE
+	case ABEQZ:
+		return ABNEZ
 	case ABGE:
 		return ABLT
-	case ABLTU:
-		return ABGEU
 	case ABGEU:
 		return ABLTU
+	case ABGEZ:
+		return ABLTZ
+	case ABGT:
+		return ABLE
+	case ABGTU:
+		return ABLEU
+	case ABGTZ:
+		return ABLEZ
+	case ABLE:
+		return ABGT
+	case ABLEU:
+		return ABGTU
+	case ABLEZ:
+		return ABGTZ
+	case ABLT:
+		return ABGE
+	case ABLTU:
+		return ABGEU
+	case ABLTZ:
+		return ABGEZ
+	case ABNE:
+		return ABEQ
+	case ABNEZ:
+		return ABEQZ
 	default:
 		panic("InvertBranch: not a branch")
 	}
@@ -860,7 +880,7 @@
 
 		for p := cursym.Func.Text; p != nil; p = p.Link {
 			switch p.As {
-			case ABEQ, ABNE, ABLT, ABGE, ABLTU, ABGEU:
+			case ABEQ, ABEQZ, ABGE, ABGEU, ABGEZ, ABGT, ABGTU, ABGTZ, ABLE, ABLEU, ABLEZ, ABLT, ABLTU, ABLTZ, ABNE, ABNEZ:
 				if p.To.Type != obj.TYPE_BRANCH {
 					panic("assemble: instruction with branch-like opcode lacks destination")
 				}
@@ -917,7 +937,7 @@
 	// instructions will break everything--don't do it!
 	for p := cursym.Func.Text; p != nil; p = p.Link {
 		switch p.As {
-		case AJAL, ABEQ, ABNE, ABLT, ABLTU, ABGE, ABGEU:
+		case ABEQ, ABEQZ, ABGE, ABGEU, ABGEZ, ABGT, ABGTU, ABGTZ, ABLE, ABLEU, ABLEZ, ABLT, ABLTU, ABLTZ, ABNE, ABNEZ, AJAL:
 			switch p.To.Type {
 			case obj.TYPE_BRANCH:
 				p.To.Type, p.To.Offset = obj.TYPE_CONST, p.Pcond.Pc-p.Pc
@@ -1778,7 +1798,29 @@
 		ins.rd, ins.rs2 = uint32(p.From.Reg), obj.REG_NONE
 		ins.imm = p.To.Offset
 
-	case ABEQ, ABNE, ABLT, ABGE, ABLTU, ABGEU:
+	case ABEQ, ABEQZ, ABGE, ABGEU, ABGEZ, ABGT, ABGTU, ABGTZ, ABLE, ABLEU, ABLEZ, ABLT, ABLTU, ABLTZ, ABNE, ABNEZ:
+		switch ins.as {
+		case ABEQZ:
+			ins.as, ins.rs1, ins.rs2 = ABEQ, REG_ZERO, uint32(p.From.Reg)
+		case ABGEZ:
+			ins.as, ins.rs1, ins.rs2 = ABGE, REG_ZERO, uint32(p.From.Reg)
+		case ABGT:
+			ins.as, ins.rs1, ins.rs2 = ABLT, uint32(p.Reg), uint32(p.From.Reg)
+		case ABGTU:
+			ins.as, ins.rs1, ins.rs2 = ABLTU, uint32(p.Reg), uint32(p.From.Reg)
+		case ABGTZ:
+			ins.as, ins.rs1, ins.rs2 = ABLT, uint32(p.From.Reg), REG_ZERO
+		case ABLE:
+			ins.as, ins.rs1, ins.rs2 = ABGE, uint32(p.Reg), uint32(p.From.Reg)
+		case ABLEU:
+			ins.as, ins.rs1, ins.rs2 = ABGEU, uint32(p.Reg), uint32(p.From.Reg)
+		case ABLEZ:
+			ins.as, ins.rs1, ins.rs2 = ABGE, uint32(p.From.Reg), REG_ZERO
+		case ABLTZ:
+			ins.as, ins.rs1, ins.rs2 = ABLT, REG_ZERO, uint32(p.From.Reg)
+		case ABNEZ:
+			ins.as, ins.rs1, ins.rs2 = ABNE, REG_ZERO, uint32(p.From.Reg)
+		}
 		ins.imm = p.To.Offset
 
 	case ALW, ALWU, ALH, ALHU, ALB, ALBU, ALD, AFLW, AFLD:
diff --git a/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go
new file mode 100644
index 0000000..b0ab5f7
--- /dev/null
+++ b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.go
@@ -0,0 +1,94 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build riscv64
+
+package testbranch
+
+import (
+	"testing"
+)
+
+func testBEQZ(a int64) (r bool)
+func testBGEZ(a int64) (r bool)
+func testBGT(a, b int64) (r bool)
+func testBGTU(a, b int64) (r bool)
+func testBGTZ(a int64) (r bool)
+func testBLE(a, b int64) (r bool)
+func testBLEU(a, b int64) (r bool)
+func testBLEZ(a int64) (r bool)
+func testBLTZ(a int64) (r bool)
+func testBNEZ(a int64) (r bool)
+
+func TestBranchCondition(t *testing.T) {
+	tests := []struct{
+		ins string
+		a int64
+		b int64
+		fn func(a, b int64) bool
+		want bool
+	}{
+		{"BGT", 0, 1, testBGT, true},
+		{"BGT", 0, 0, testBGT, false},
+		{"BGT", 0, -1, testBGT, false},
+		{"BGT", -1, 0, testBGT, true},
+		{"BGT", 1, 0, testBGT, false},
+		{"BGTU", 0, 1, testBGTU, true},
+		{"BGTU", 0, -1, testBGTU, true},
+		{"BGTU", -1, 0, testBGTU, false},
+		{"BGTU", 1, 0, testBGTU, false},
+		{"BLE", 0, 1, testBLE, false},
+		{"BLE", 0, -1, testBLE, true},
+		{"BLE", 0, 0, testBLE, true},
+		{"BLE", -1, 0, testBLE, false},
+		{"BLE", 1, 0, testBLE, true},
+		{"BLEU", 0, 1, testBLEU, false},
+		{"BLEU", 0, -1, testBLEU, false},
+		{"BLEU", 0, 0, testBLEU, true},
+		{"BLEU", -1, 0, testBLEU, true},
+		{"BLEU", 1, 0, testBLEU, true},
+	}
+	for _, test := range tests {
+		t.Run(test.ins, func(t *testing.T) {
+			if got := test.fn(test.a, test.b); got != test.want {
+				t.Errorf("%v %v, %v = %v, want %v", test.ins, test.a, test.b, got, test.want)
+			}
+		})
+	}
+}
+
+func TestBranchZero(t *testing.T) {
+	tests := []struct{
+		ins string
+		a int64
+		fn func(a int64) bool
+		want bool
+	}{
+		{"BEQZ", -1, testBEQZ, false},
+		{"BEQZ", 0, testBEQZ, true},
+		{"BEQZ", 1, testBEQZ, false},
+		{"BGEZ", -1, testBGEZ, false},
+		{"BGEZ", 0, testBGEZ, true},
+		{"BGEZ", 1, testBGEZ, true},
+		{"BGTZ", -1, testBGTZ, false},
+		{"BGTZ", 0, testBGTZ, false},
+		{"BGTZ", 1, testBGTZ, true},
+		{"BLEZ", -1, testBLEZ, true},
+		{"BLEZ", 0, testBLEZ, true},
+		{"BLEZ", 1, testBLEZ, false},
+		{"BLTZ", -1, testBLTZ, true},
+		{"BLTZ", 0, testBLTZ, false},
+		{"BLTZ", 1, testBLTZ, false},
+		{"BNEZ", -1, testBNEZ, true},
+		{"BNEZ", 0, testBNEZ, false},
+		{"BNEZ", 1, testBNEZ, true},
+	}
+	for _, test := range tests {
+		t.Run(test.ins, func(t *testing.T) {
+			if got := test.fn(test.a); got != test.want {
+				t.Errorf("%v %v = %v, want %v", test.ins, test.a, got, test.want)
+			}
+		})
+	}
+}
diff --git a/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.s b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.s
new file mode 100644
index 0000000..6cff235
--- /dev/null
+++ b/src/cmd/internal/obj/riscv/testdata/testbranch/branch_test.s
@@ -0,0 +1,111 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build riscv64
+
+#include "textflag.h"
+
+// func testBEQZ(a int64) (r bool)
+TEXT ·testBEQZ(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	$1, X6
+	BEQZ	X5, b
+	MOV	$0, X6
+b:
+	MOV	X6, r+8(FP)
+	RET
+
+// func testBGEZ(a int64) (r bool)
+TEXT ·testBGEZ(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	$1, X6
+	BGEZ	X5, b
+	MOV	$0, X6
+b:
+	MOV	X6, r+8(FP)
+	RET
+
+// func testBGT(a, b int64) (r bool)
+TEXT ·testBGT(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	b+8(FP), X6
+	MOV	$1, X7
+	BGT	X5, X6, b
+	MOV	$0, X7
+b:
+	MOV	X7, r+16(FP)
+	RET
+
+// func testBGTU(a, b int64) (r bool)
+TEXT ·testBGTU(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	b+8(FP), X6
+	MOV	$1, X7
+	BGTU	X5, X6, b
+	MOV	$0, X7
+b:
+	MOV	X7, r+16(FP)
+	RET
+
+// func testBGTZ(a int64) (r bool)
+TEXT ·testBGTZ(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	$1, X6
+	BGTZ	X5, b
+	MOV	$0, X6
+b:
+	MOV	X6, r+8(FP)
+	RET
+
+// func testBLE(a, b int64) (r bool)
+TEXT ·testBLE(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	b+8(FP), X6
+	MOV	$1, X7
+	BLE	X5, X6, b
+	MOV	$0, X7
+b:
+	MOV	X7, r+16(FP)
+	RET
+
+// func testBLEU(a, b int64) (r bool)
+TEXT ·testBLEU(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	b+8(FP), X6
+	MOV	$1, X7
+	BLEU	X5, X6, b
+	MOV	$0, X7
+b:
+	MOV	X7, r+16(FP)
+	RET
+
+// func testBLEZ(a int64) (r bool)
+TEXT ·testBLEZ(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	$1, X6
+	BLEZ	X5, b
+	MOV	$0, X6
+b:
+	MOV	X6, r+8(FP)
+	RET
+
+// func testBLTZ(a int64) (r bool)
+TEXT ·testBLTZ(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	$1, X6
+	BLTZ	X5, b
+	MOV	$0, X6
+b:
+	MOV	X6, r+8(FP)
+	RET
+
+// func testBNEZ(a int64) (r bool)
+TEXT ·testBNEZ(SB),NOSPLIT,$0-0
+	MOV	a+0(FP), X5
+	MOV	$1, X6
+	BNEZ	X5, b
+	MOV	$0, X6
+b:
+	MOV	X6, r+8(FP)
+	RET