mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/internal/obj/riscv: handle call, jmp and branch
Also provide REG_LR to more clearly define the link register. Based on the riscv-go port. Updates #27532 Change-Id: I0805f373682f93b3918a01c21d4ef34eb3817c75 Reviewed-on: https://go-review.googlesource.com/c/go/+/204627 Reviewed-by: Cherry Zhang <cherryyz@google.com> Run-TryBot: Cherry Zhang <cherryyz@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
372efbbf31
commit
7cab55dfd3
4 changed files with 331 additions and 2 deletions
15
src/cmd/asm/internal/asm/testdata/riscvenc.s
vendored
15
src/cmd/asm/internal/asm/testdata/riscvenc.s
vendored
|
|
@ -267,3 +267,18 @@ start:
|
||||||
MOVD 4(X5), F0 // 07b04200
|
MOVD 4(X5), F0 // 07b04200
|
||||||
MOVD F0, 4(X5) // 27b20200
|
MOVD F0, 4(X5) // 27b20200
|
||||||
MOVD F0, F1 // d3000022
|
MOVD F0, F1 // d3000022
|
||||||
|
|
||||||
|
// These jumps can get printed as jumps to 2 because they go to the
|
||||||
|
// second instruction in the function (the first instruction is an
|
||||||
|
// invisible stack pointer adjustment).
|
||||||
|
JMP start // JMP 2 // 6ff05fcd
|
||||||
|
JMP (X5) // 67800200
|
||||||
|
JMP 4(X5) // 67804200
|
||||||
|
|
||||||
|
// JMP and CALL to symbol are encoded as:
|
||||||
|
// AUIPC $0, TMP
|
||||||
|
// JALR $0, TMP
|
||||||
|
// with a R_RISCV_PCREL_ITYPE relocation - the linker resolves the
|
||||||
|
// real address and updates the immediates for both instructions.
|
||||||
|
CALL asmtest(SB) // 970f0000
|
||||||
|
JMP asmtest(SB) // 970f0000
|
||||||
|
|
|
||||||
79
src/cmd/internal/obj/riscv/asm_test.go
Normal file
79
src/cmd/internal/obj/riscv/asm_test.go
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright 2019 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.
|
||||||
|
|
||||||
|
package riscv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"internal/testenv"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestLarge generates a very large file to verify that large
|
||||||
|
// program builds successfully, in particular, too-far
|
||||||
|
// conditional branches are fixed.
|
||||||
|
func TestLarge(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("Skip in short mode")
|
||||||
|
}
|
||||||
|
testenv.MustHaveGoBuild(t)
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "testlarge")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not create directory: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// Generate a very large function.
|
||||||
|
buf := bytes.NewBuffer(make([]byte, 0, 7000000))
|
||||||
|
gen(buf)
|
||||||
|
|
||||||
|
tmpfile := filepath.Join(dir, "x.s")
|
||||||
|
err = ioutil.WriteFile(tmpfile, buf.Bytes(), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't write output: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build generated file.
|
||||||
|
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
|
||||||
|
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Build failed: %v, output: %s", err, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gen generates a very large program, with a very far conditional branch.
|
||||||
|
func gen(buf *bytes.Buffer) {
|
||||||
|
fmt.Fprintln(buf, "TEXT f(SB),0,$0-0")
|
||||||
|
fmt.Fprintln(buf, "BEQ X0, X0, label")
|
||||||
|
for i := 0; i < 1<<19; i++ {
|
||||||
|
fmt.Fprintln(buf, "ADD $0, X0, X0")
|
||||||
|
}
|
||||||
|
fmt.Fprintln(buf, "label:")
|
||||||
|
fmt.Fprintln(buf, "ADD $0, X0, X0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue 20348.
|
||||||
|
func TestNoRet(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "testnoret")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
tmpfile := filepath.Join(dir, "x.s")
|
||||||
|
if err := ioutil.WriteFile(tmpfile, []byte("TEXT ·stub(SB),$0-0\nNOP\n"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cmd := exec.Command(testenv.GoToolPath(t), "tool", "asm", "-o", filepath.Join(dir, "x.o"), tmpfile)
|
||||||
|
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
t.Errorf("%v\n%s", err, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -141,6 +141,7 @@ const (
|
||||||
// Go runtime register names.
|
// Go runtime register names.
|
||||||
REG_G = REG_TP // G pointer.
|
REG_G = REG_TP // G pointer.
|
||||||
REG_CTXT = REG_S4 // Context for closures.
|
REG_CTXT = REG_S4 // Context for closures.
|
||||||
|
REG_LR = REG_RA // Link register.
|
||||||
REG_TMP = REG_T6 // Reserved for assembler use.
|
REG_TMP = REG_T6 // Reserved for assembler use.
|
||||||
|
|
||||||
// ABI names for floating point registers.
|
// ABI names for floating point registers.
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ package riscv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmd/internal/obj"
|
"cmd/internal/obj"
|
||||||
|
"cmd/internal/objabi"
|
||||||
"cmd/internal/sys"
|
"cmd/internal/sys"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
@ -31,6 +32,45 @@ var RISCV64DWARFRegisters = map[int16]int16{}
|
||||||
|
|
||||||
func buildop(ctxt *obj.Link) {}
|
func buildop(ctxt *obj.Link) {}
|
||||||
|
|
||||||
|
// jalrToSym replaces p with a set of Progs needed to jump to the Sym in p.
|
||||||
|
// lr is the link register to use for the JALR.
|
||||||
|
// p must be a CALL, JMP or RET.
|
||||||
|
func jalrToSym(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc, lr int16) *obj.Prog {
|
||||||
|
if p.As != obj.ACALL && p.As != obj.AJMP && p.As != obj.ARET {
|
||||||
|
ctxt.Diag("unexpected Prog in jalrToSym: %v", p)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jsing): Consider using a single JAL instruction and teaching
|
||||||
|
// the linker to provide trampolines for the case where the destination
|
||||||
|
// offset is too large. This would potentially reduce instructions for
|
||||||
|
// the common case, but would require three instructions to go via the
|
||||||
|
// trampoline.
|
||||||
|
|
||||||
|
to := p.To
|
||||||
|
|
||||||
|
// This offset isn't really encoded with either instruction. It will be
|
||||||
|
// extracted for a relocation later.
|
||||||
|
p.As = AAUIPC
|
||||||
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: to.Offset, Sym: to.Sym}
|
||||||
|
p.Reg = 0
|
||||||
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
||||||
|
p.Mark |= NEED_PCREL_ITYPE_RELOC
|
||||||
|
p = obj.Appendp(p, newprog)
|
||||||
|
|
||||||
|
// Leave p.To.Sym only for the CALL reloc in assemble.
|
||||||
|
p.As = AJALR
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = lr
|
||||||
|
p.Reg = 0
|
||||||
|
p.To.Type = obj.TYPE_REG
|
||||||
|
p.To.Reg = REG_TMP
|
||||||
|
p.To.Sym = to.Sym
|
||||||
|
lowerJALR(p)
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
// lowerJALR normalizes a JALR instruction.
|
// lowerJALR normalizes a JALR instruction.
|
||||||
func lowerJALR(p *obj.Prog) {
|
func lowerJALR(p *obj.Prog) {
|
||||||
if p.As != AJALR {
|
if p.As != AJALR {
|
||||||
|
|
@ -106,6 +146,42 @@ func progedit(ctxt *obj.Link, p *obj.Prog, newprog obj.ProgAlloc) {
|
||||||
p.Ctxt.Diag("%v\tmemory required for destination", p)
|
p.Ctxt.Diag("%v\tmemory required for destination", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case obj.AJMP:
|
||||||
|
// Turn JMP into JAL ZERO or JALR ZERO.
|
||||||
|
// p.From is actually an _output_ for this instruction.
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = REG_ZERO
|
||||||
|
|
||||||
|
switch p.To.Type {
|
||||||
|
case obj.TYPE_BRANCH:
|
||||||
|
p.As = AJAL
|
||||||
|
case obj.TYPE_MEM:
|
||||||
|
switch p.To.Name {
|
||||||
|
case obj.NAME_NONE:
|
||||||
|
p.As = AJALR
|
||||||
|
lowerJALR(p)
|
||||||
|
case obj.NAME_EXTERN:
|
||||||
|
// Handled in preprocess.
|
||||||
|
default:
|
||||||
|
ctxt.Diag("progedit: unsupported name %d for %v", p.To.Name, p)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unhandled type %+v", p.To.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
case obj.ACALL:
|
||||||
|
switch p.To.Type {
|
||||||
|
case obj.TYPE_MEM:
|
||||||
|
// Handled in preprocess.
|
||||||
|
case obj.TYPE_REG:
|
||||||
|
p.As = AJALR
|
||||||
|
p.From.Type = obj.TYPE_REG
|
||||||
|
p.From.Reg = REG_LR
|
||||||
|
lowerJALR(p)
|
||||||
|
default:
|
||||||
|
ctxt.Diag("unknown destination type %+v in CALL: %v", p.To.Type, p)
|
||||||
|
}
|
||||||
|
|
||||||
case AJALR:
|
case AJALR:
|
||||||
lowerJALR(p)
|
lowerJALR(p)
|
||||||
|
|
||||||
|
|
@ -390,6 +466,26 @@ func rewriteMOV(ctxt *obj.Link, newprog obj.ProgAlloc, p *obj.Prog) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// invertBranch inverts the condition of a conditional branch.
|
||||||
|
func invertBranch(i obj.As) obj.As {
|
||||||
|
switch i {
|
||||||
|
case ABEQ:
|
||||||
|
return ABNE
|
||||||
|
case ABNE:
|
||||||
|
return ABEQ
|
||||||
|
case ABLT:
|
||||||
|
return ABGE
|
||||||
|
case ABGE:
|
||||||
|
return ABLT
|
||||||
|
case ABLTU:
|
||||||
|
return ABGEU
|
||||||
|
case ABGEU:
|
||||||
|
return ABLTU
|
||||||
|
default:
|
||||||
|
panic("invertBranch: not a branch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// setPCs sets the Pc field in all instructions reachable from p.
|
// setPCs sets the Pc field in all instructions reachable from p.
|
||||||
// It uses pc as the initial value.
|
// It uses pc as the initial value.
|
||||||
func setPCs(p *obj.Prog, pc int64) {
|
func setPCs(p *obj.Prog, pc int64) {
|
||||||
|
|
@ -483,6 +579,22 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
|
||||||
// progedit, as SP offsets need to be applied before we split
|
// progedit, as SP offsets need to be applied before we split
|
||||||
// up some of the Addrs.
|
// up some of the Addrs.
|
||||||
rewriteMOV(ctxt, newprog, p)
|
rewriteMOV(ctxt, newprog, p)
|
||||||
|
|
||||||
|
case obj.ACALL:
|
||||||
|
switch p.To.Type {
|
||||||
|
case obj.TYPE_MEM:
|
||||||
|
jalrToSym(ctxt, p, newprog, REG_LR)
|
||||||
|
}
|
||||||
|
|
||||||
|
case obj.AJMP:
|
||||||
|
switch p.To.Type {
|
||||||
|
case obj.TYPE_MEM:
|
||||||
|
switch p.To.Name {
|
||||||
|
case obj.NAME_EXTERN:
|
||||||
|
// JMP to symbol.
|
||||||
|
jalrToSym(ctxt, p, newprog, REG_ZERO)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -596,9 +708,71 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPCs(cursym.Func.Text, 0)
|
// Compute instruction addresses. Once we do that, we need to check for
|
||||||
|
// overextended jumps and branches. Within each iteration, Pc differences
|
||||||
|
// are always lower bounds (since the program gets monotonically longer,
|
||||||
|
// a fixed point will be reached). No attempt to handle functions > 2GiB.
|
||||||
|
for {
|
||||||
|
rescan := false
|
||||||
|
setPCs(cursym.Func.Text, 0)
|
||||||
|
|
||||||
// Resolve branch and jump targets.
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
||||||
|
switch p.As {
|
||||||
|
case ABEQ, ABNE, ABLT, ABGE, ABLTU, ABGEU:
|
||||||
|
if p.To.Type != obj.TYPE_BRANCH {
|
||||||
|
panic("assemble: instruction with branch-like opcode lacks destination")
|
||||||
|
}
|
||||||
|
offset := p.Pcond.Pc - p.Pc
|
||||||
|
if offset < -4096 || 4096 <= offset {
|
||||||
|
// Branch is long. Replace it with a jump.
|
||||||
|
jmp := obj.Appendp(p, newprog)
|
||||||
|
jmp.As = AJAL
|
||||||
|
jmp.From = obj.Addr{Type: obj.TYPE_REG, Reg: REG_ZERO}
|
||||||
|
jmp.To = obj.Addr{Type: obj.TYPE_BRANCH}
|
||||||
|
jmp.Pcond = p.Pcond
|
||||||
|
|
||||||
|
p.As = invertBranch(p.As)
|
||||||
|
p.Pcond = jmp.Link
|
||||||
|
|
||||||
|
// We may have made previous branches too long,
|
||||||
|
// so recheck them.
|
||||||
|
rescan = true
|
||||||
|
}
|
||||||
|
case AJAL:
|
||||||
|
if p.Pcond == nil {
|
||||||
|
panic("intersymbol jumps should be expressed as AUIPC+JALR")
|
||||||
|
}
|
||||||
|
offset := p.Pcond.Pc - p.Pc
|
||||||
|
if offset < -(1<<20) || (1<<20) <= offset {
|
||||||
|
// Replace with 2-instruction sequence. This assumes
|
||||||
|
// that TMP is not live across J instructions, since
|
||||||
|
// it is reserved by SSA.
|
||||||
|
jmp := obj.Appendp(p, newprog)
|
||||||
|
jmp.As = AJALR
|
||||||
|
jmp.From = obj.Addr{Type: obj.TYPE_CONST, Offset: 0}
|
||||||
|
jmp.To = p.From
|
||||||
|
jmp.Reg = REG_TMP
|
||||||
|
|
||||||
|
// p.From is not generally valid, however will be
|
||||||
|
// fixed up in the next loop.
|
||||||
|
p.As = AAUIPC
|
||||||
|
p.From = obj.Addr{Type: obj.TYPE_BRANCH, Sym: p.From.Sym}
|
||||||
|
p.Reg = 0
|
||||||
|
p.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}
|
||||||
|
|
||||||
|
rescan = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rescan {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that there are no long branches, resolve branch and jump targets.
|
||||||
|
// At this point, instruction rewriting which changes the number of
|
||||||
|
// instructions will break everything--don't do it!
|
||||||
for p := cursym.Func.Text; p != nil; p = p.Link {
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
||||||
switch p.As {
|
switch p.As {
|
||||||
case AJAL, ABEQ, ABNE, ABLT, ABLTU, ABGE, ABGEU:
|
case AJAL, ABEQ, ABNE, ABLT, ABLTU, ABGE, ABGEU:
|
||||||
|
|
@ -608,6 +782,16 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
|
||||||
case obj.TYPE_MEM:
|
case obj.TYPE_MEM:
|
||||||
panic("unhandled type")
|
panic("unhandled type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case AAUIPC:
|
||||||
|
if p.From.Type == obj.TYPE_BRANCH {
|
||||||
|
low, high, err := Split32BitImmediate(p.Pcond.Pc - p.Pc)
|
||||||
|
if err != nil {
|
||||||
|
ctxt.Diag("%v: jump displacement %d too large", p, p.Pcond.Pc-p.Pc)
|
||||||
|
}
|
||||||
|
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: high, Sym: cursym}
|
||||||
|
p.Link.From.Offset = low
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -864,6 +1048,13 @@ func validateB(p *obj.Prog) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateU(p *obj.Prog) {
|
func validateU(p *obj.Prog) {
|
||||||
|
if p.As == AAUIPC && p.Mark&(NEED_PCREL_ITYPE_RELOC|NEED_PCREL_STYPE_RELOC) != 0 {
|
||||||
|
// TODO(sorear): Hack. The Offset is being used here to temporarily
|
||||||
|
// store the relocation addend, not as an actual offset to assemble,
|
||||||
|
// so it's OK for it to be out of range. Is there a more valid way
|
||||||
|
// to represent this state?
|
||||||
|
return
|
||||||
|
}
|
||||||
wantImmU(p, "from", p.From, 20)
|
wantImmU(p, "from", p.From, 20)
|
||||||
wantIntRegAddr(p, "to", &p.To)
|
wantIntRegAddr(p, "to", &p.To)
|
||||||
}
|
}
|
||||||
|
|
@ -1281,6 +1472,49 @@ func encodingForProg(p *obj.Prog) encoding {
|
||||||
func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
|
func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
|
||||||
var symcode []uint32
|
var symcode []uint32
|
||||||
for p := cursym.Func.Text; p != nil; p = p.Link {
|
for p := cursym.Func.Text; p != nil; p = p.Link {
|
||||||
|
switch p.As {
|
||||||
|
case AJALR:
|
||||||
|
if p.To.Sym != nil {
|
||||||
|
// This is a CALL/JMP. We add a relocation only
|
||||||
|
// for linker stack checking. No actual
|
||||||
|
// relocation is needed.
|
||||||
|
rel := obj.Addrel(cursym)
|
||||||
|
rel.Off = int32(p.Pc)
|
||||||
|
rel.Siz = 4
|
||||||
|
rel.Sym = p.To.Sym
|
||||||
|
rel.Add = p.To.Offset
|
||||||
|
rel.Type = objabi.R_CALLRISCV
|
||||||
|
}
|
||||||
|
case AAUIPC:
|
||||||
|
var rt objabi.RelocType
|
||||||
|
if p.Mark&NEED_PCREL_ITYPE_RELOC == NEED_PCREL_ITYPE_RELOC {
|
||||||
|
rt = objabi.R_RISCV_PCREL_ITYPE
|
||||||
|
} else if p.Mark&NEED_PCREL_STYPE_RELOC == NEED_PCREL_STYPE_RELOC {
|
||||||
|
rt = objabi.R_RISCV_PCREL_STYPE
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if p.Link == nil {
|
||||||
|
ctxt.Diag("AUIPC needing PC-relative reloc missing following instruction")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if p.From.Sym == nil {
|
||||||
|
ctxt.Diag("AUIPC needing PC-relative reloc missing symbol")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// The relocation offset can be larger than the maximum
|
||||||
|
// size of an AUIPC, so zero p.From.Offset to avoid any
|
||||||
|
// attempt to assemble it.
|
||||||
|
rel := obj.Addrel(cursym)
|
||||||
|
rel.Off = int32(p.Pc)
|
||||||
|
rel.Siz = 8
|
||||||
|
rel.Sym = p.From.Sym
|
||||||
|
rel.Add = p.From.Offset
|
||||||
|
p.From.Offset = 0
|
||||||
|
rel.Type = rt
|
||||||
|
}
|
||||||
|
|
||||||
enc := encodingForProg(p)
|
enc := encodingForProg(p)
|
||||||
if enc.length > 0 {
|
if enc.length > 0 {
|
||||||
symcode = append(symcode, enc.encode(p))
|
symcode = append(symcode, enc.encode(p))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue