Skip to content

Commit

Permalink
cmd/internal/obj/riscv,cmd/link: rework riscv64 call relocations
Browse files Browse the repository at this point in the history
The riscv64 assembler and linker generate three types of calls.
Most calls are made via a single JAL instruction, however this is
limited to +/-1MB of text. In the case where a call target is
unreachable (or unknown), the JAL targets an AUIPC+JALR trampoline.
All other cases use AUIPC+JALR pairs, including the case where a
single function exceeds 1MB in text size, potentially making it
impossible to reach trampolines.

Currently, the single instruction JAL call is marked with R_RISCV_CALL
and the two instruction AUIPC+JALR call is marked with
R_RISCV_PCREL_ITYPE, which is also used for memory load instructions.
This means that we have no way to identify that the latter is a call.

Switch to using R_RISCV_CALL to mark the AUIPC+JALR pair (aligning
somewhat with the elf.R_RISCV_CALL, which is deprecated in favour of
elf.R_RISCV_CALL_PLT). Add R_RISCV_JAL and use this to mark the single
instruction JAL direct calls. This is clearer and allows us to map
elf.R_RISCV_CALL_PLT to Go's R_RISCV_CALL.

Add all three types to IsDirectCall, so that direct calls are correctly
identified when a function exceeds 1MB of text.

Fixes #62465

Change-Id: Id3eea09688a2b7d6e481eae9ed0aa0d1f9a3a48f
Reviewed-on: https://go-review.googlesource.com/c/go/+/520095
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Joel Sing <joel@sing.id.au>
Reviewed-by: Than McIntosh <thanm@google.com>
  • Loading branch information
4a6f656c committed Sep 19, 2023
1 parent 3c4f12a commit bda5e6c
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 74 deletions.
2 changes: 1 addition & 1 deletion src/cmd/asm/internal/asm/testdata/riscv64.s
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ start:
JMP 4(X5) // 67804200

// CALL and JMP to symbol are encoded as JAL (using LR or ZERO
// respectively), with a R_RISCV_CALL relocation. The linker resolves
// respectively), with a R_RISCV_JAL relocation. The linker resolves
// the real address and updates the immediate, using a trampoline in
// the case where the address is not directly reachable.
CALL asmtest(SB) // ef000000
Expand Down
9 changes: 7 additions & 2 deletions src/cmd/internal/obj/riscv/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,13 @@ const (
// corresponding *obj.Prog uses the temporary register.
USES_REG_TMP = 1 << iota

// NEED_CALL_RELOC is set on JAL instructions to indicate that a
// R_RISCV_CALL relocation is needed.
// NEED_JAL_RELOC is set on JAL instructions to indicate that a
// R_RISCV_JAL relocation is needed.
NEED_JAL_RELOC

// NEED_CALL_RELOC is set on an AUIPC instruction to indicate that it
// is the first instruction in an AUIPC + JAL pair that needs a
// R_RISCV_CALL relocation.
NEED_CALL_RELOC

// NEED_PCREL_ITYPE_RELOC is set on AUIPC instructions to indicate that
Expand Down
15 changes: 9 additions & 6 deletions src/cmd/internal/obj/riscv/obj.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func jalToSym(ctxt *obj.Link, p *obj.Prog, lr int16) {
}

p.As = AJAL
p.Mark |= NEED_CALL_RELOC
p.Mark |= NEED_JAL_RELOC
p.From.Type = obj.TYPE_REG
p.From.Reg = lr
p.Reg = obj.REG_NONE
Expand Down Expand Up @@ -610,7 +610,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
var callCount int
for p := cursym.Func().Text; p != nil; p = p.Link {
markRelocs(p)
if p.Mark&NEED_CALL_RELOC == NEED_CALL_RELOC {
if p.Mark&NEED_JAL_RELOC == NEED_JAL_RELOC {
callCount++
}
}
Expand Down Expand Up @@ -664,7 +664,7 @@ func preprocess(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
jmp.To = obj.Addr{Type: obj.TYPE_REG, Reg: REG_TMP}

p.As = AAUIPC
p.Mark = (p.Mark &^ NEED_CALL_RELOC) | NEED_PCREL_ITYPE_RELOC
p.Mark = (p.Mark &^ NEED_JAL_RELOC) | NEED_CALL_RELOC
p.AddRestSource(obj.Addr{Type: obj.TYPE_CONST, Offset: p.To.Offset, Sym: p.To.Sym})
p.From = obj.Addr{Type: obj.TYPE_CONST, Offset: 0}
p.Reg = obj.REG_NONE
Expand Down Expand Up @@ -2345,13 +2345,13 @@ func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
for p := cursym.Func().Text; p != nil; p = p.Link {
switch p.As {
case AJAL:
if p.Mark&NEED_CALL_RELOC == NEED_CALL_RELOC {
if p.Mark&NEED_JAL_RELOC == NEED_JAL_RELOC {
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_RISCV_CALL
rel.Type = objabi.R_RISCV_JAL
}
case AJALR:
if p.To.Sym != nil {
Expand All @@ -2361,7 +2361,10 @@ func assemble(ctxt *obj.Link, cursym *obj.LSym, newprog obj.ProgAlloc) {
case AAUIPC, AMOV, AMOVB, AMOVH, AMOVW, AMOVBU, AMOVHU, AMOVWU, AMOVF, AMOVD:
var addr *obj.Addr
var rt objabi.RelocType
if p.Mark&NEED_PCREL_ITYPE_RELOC == NEED_PCREL_ITYPE_RELOC {
if p.Mark&NEED_CALL_RELOC == NEED_CALL_RELOC {
rt = objabi.R_RISCV_CALL
addr = &p.From
} else if p.Mark&NEED_PCREL_ITYPE_RELOC == NEED_PCREL_ITYPE_RELOC {
rt = objabi.R_RISCV_PCREL_ITYPE
addr = &p.From
} else if p.Mark&NEED_PCREL_STYPE_RELOC == NEED_PCREL_STYPE_RELOC {
Expand Down
26 changes: 15 additions & 11 deletions src/cmd/internal/objabi/reloctype.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,28 +259,31 @@ const (

// RISC-V.

// R_RISCV_CALL relocates a J-type instruction with a 21 bit PC-relative
// address.
R_RISCV_CALL
// R_RISCV_JAL resolves a 20 bit offset for a J-type instruction.
R_RISCV_JAL

// R_RISCV_CALL_TRAMP is the same as R_RISCV_CALL but denotes the use of a
// R_RISCV_JAL_TRAMP is the same as R_RISCV_JAL but denotes the use of a
// trampoline, which we may be able to avoid during relocation. These are
// only used by the linker and are not emitted by the compiler or assembler.
R_RISCV_CALL_TRAMP
R_RISCV_JAL_TRAMP

// R_RISCV_PCREL_ITYPE resolves a 32 bit PC-relative address using an
// R_RISCV_CALL resolves a 32 bit PC-relative address for an AUIPC + JALR
// instruction pair.
R_RISCV_CALL

// R_RISCV_PCREL_ITYPE resolves a 32 bit PC-relative address for an
// AUIPC + I-type instruction pair.
R_RISCV_PCREL_ITYPE

// R_RISCV_PCREL_STYPE resolves a 32 bit PC-relative address using an
// R_RISCV_PCREL_STYPE resolves a 32 bit PC-relative address for an
// AUIPC + S-type instruction pair.
R_RISCV_PCREL_STYPE

// R_RISCV_TLS_IE resolves a 32 bit TLS initial-exec address using an
// R_RISCV_TLS_IE resolves a 32 bit TLS initial-exec address for an
// AUIPC + I-type instruction pair.
R_RISCV_TLS_IE

// R_RISCV_TLS_LE resolves a 32 bit TLS local-exec address using an
// R_RISCV_TLS_LE resolves a 32 bit TLS local-exec address for a
// LUI + I-type instruction sequence.
R_RISCV_TLS_LE

Expand Down Expand Up @@ -387,12 +390,13 @@ const (

// IsDirectCall reports whether r is a relocation for a direct call.
// A direct call is a CALL instruction that takes the target address
// as an immediate. The address is embedded into the instruction, possibly
// as an immediate. The address is embedded into the instruction(s), possibly
// with limited width. An indirect call is a CALL instruction that takes
// the target address in register or memory.
func (r RelocType) IsDirectCall() bool {
switch r {
case R_CALL, R_CALLARM, R_CALLARM64, R_CALLLOONG64, R_CALLMIPS, R_CALLPOWER, R_RISCV_CALL, R_RISCV_CALL_TRAMP:
case R_CALL, R_CALLARM, R_CALLARM64, R_CALLLOONG64, R_CALLMIPS, R_CALLPOWER,
R_RISCV_CALL, R_RISCV_JAL, R_RISCV_JAL_TRAMP:
return true
}
return false
Expand Down
63 changes: 32 additions & 31 deletions src/cmd/internal/objabi/reloctype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 26 additions & 23 deletions src/cmd/link/internal/riscv64/asm.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
ldr.Errorf(s, "unknown symbol %s in RISCV call", ldr.SymName(targ))
}
su := ldr.MakeSymbolUpdater(s)
su.SetRelocType(rIdx, objabi.R_RISCV_PCREL_ITYPE)
su.SetRelocType(rIdx, objabi.R_RISCV_CALL)
return true

case objabi.ElfRelocOffset + objabi.RelocType(elf.R_RISCV_GOT_HI20):
Expand Down Expand Up @@ -130,7 +130,7 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
r = relocs.At(rIdx)

switch r.Type() {
case objabi.R_RISCV_PCREL_ITYPE:
case objabi.R_RISCV_CALL:
if targType != sym.SDYNIMPORT {
// nothing to do, the relocation will be laid out in reloc
return true
Expand Down Expand Up @@ -228,12 +228,12 @@ func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym,
}
out.Write64(uint64(r.Xadd))

case objabi.R_RISCV_CALL, objabi.R_RISCV_CALL_TRAMP:
case objabi.R_RISCV_JAL, objabi.R_RISCV_JAL_TRAMP:
out.Write64(uint64(sectoff))
out.Write64(uint64(elf.R_RISCV_JAL) | uint64(elfsym)<<32)
out.Write64(uint64(r.Xadd))

case objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE, objabi.R_RISCV_TLS_IE:
case objabi.R_RISCV_CALL, objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE, objabi.R_RISCV_TLS_IE:
// Find the text symbol for the AUIPC instruction targeted
// by this relocation.
relocs := ldr.Relocs(s)
Expand All @@ -256,7 +256,7 @@ func elfreloc1(ctxt *ld.Link, out *ld.OutBuf, ldr *loader.Loader, s loader.Sym,
//
var hiRel, loRel elf.R_RISCV
switch r.Type {
case objabi.R_RISCV_PCREL_ITYPE:
case objabi.R_RISCV_CALL, objabi.R_RISCV_PCREL_ITYPE:
hiRel, loRel = elf.R_RISCV_PCREL_HI20, elf.R_RISCV_PCREL_LO12_I
case objabi.R_RISCV_PCREL_STYPE:
hiRel, loRel = elf.R_RISCV_PCREL_HI20, elf.R_RISCV_PCREL_LO12_S
Expand Down Expand Up @@ -399,20 +399,20 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade
// If the call points to a trampoline, see if we can reach the symbol
// directly. This situation can occur when the relocation symbol is
// not assigned an address until after the trampolines are generated.
if r.Type() == objabi.R_RISCV_CALL_TRAMP {
if r.Type() == objabi.R_RISCV_JAL_TRAMP {
relocs := ldr.Relocs(rs)
if relocs.Count() != 1 {
ldr.Errorf(s, "trampoline %v has %d relocations", ldr.SymName(rs), relocs.Count())
}
tr := relocs.At(0)
if tr.Type() != objabi.R_RISCV_PCREL_ITYPE {
if tr.Type() != objabi.R_RISCV_CALL {
ldr.Errorf(s, "trampoline %v has unexpected relocation %v", ldr.SymName(rs), tr.Type())
}
trs := tr.Sym()
if ldr.SymValue(trs) != 0 && ldr.SymType(trs) != sym.SDYNIMPORT && ldr.SymType(trs) != sym.SUNDEFEXT {
trsOff := ldr.SymValue(trs) + tr.Add() - pc
if trsOff >= -(1<<20) && trsOff < (1<<20) {
r.SetType(objabi.R_RISCV_CALL)
r.SetType(objabi.R_RISCV_JAL)
r.SetSym(trs)
r.SetAdd(tr.Add())
rs = trs
Expand All @@ -423,10 +423,10 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade

if target.IsExternal() {
switch r.Type() {
case objabi.R_RISCV_CALL, objabi.R_RISCV_CALL_TRAMP:
case objabi.R_RISCV_JAL, objabi.R_RISCV_JAL_TRAMP:
return val, 1, true

case objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE, objabi.R_RISCV_TLS_IE, objabi.R_RISCV_TLS_LE:
case objabi.R_RISCV_CALL, objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE, objabi.R_RISCV_TLS_IE, objabi.R_RISCV_TLS_LE:
return val, 2, true
}

Expand All @@ -436,11 +436,11 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade
off := ldr.SymValue(rs) + r.Add() - pc

switch r.Type() {
case objabi.R_RISCV_CALL, objabi.R_RISCV_CALL_TRAMP:
case objabi.R_RISCV_JAL, objabi.R_RISCV_JAL_TRAMP:
// Generate instruction immediates.
imm, err := riscv.EncodeJImmediate(off)
if err != nil {
ldr.Errorf(s, "cannot encode R_RISCV_CALL relocation offset for %s: %v", ldr.SymName(rs), err)
ldr.Errorf(s, "cannot encode J-type instruction relocation offset for %s: %v", ldr.SymName(rs), err)
}
immMask := int64(riscv.JTypeImmMask)

Expand Down Expand Up @@ -574,31 +574,31 @@ func archreloc(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, r loade
ins = (ins &^ immMask) | int64(uint32(imm))
return ins, 0, true

case objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE:
case objabi.R_RISCV_CALL, objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE:
// Generate AUIPC and second instruction immediates.
low, high, err := riscv.Split32BitImmediate(off)
if err != nil {
ldr.Errorf(s, "R_RISCV_PCREL_ relocation does not fit in 32 bits: %d", off)
ldr.Errorf(s, "pc-relative relocation does not fit in 32 bits: %d", off)
}

auipcImm, err := riscv.EncodeUImmediate(high)
if err != nil {
ldr.Errorf(s, "cannot encode R_RISCV_PCREL_ AUIPC relocation offset for %s: %v", ldr.SymName(rs), err)
ldr.Errorf(s, "cannot encode AUIPC relocation offset for %s: %v", ldr.SymName(rs), err)
}

var secondImm, secondImmMask int64
switch r.Type() {
case objabi.R_RISCV_PCREL_ITYPE:
case objabi.R_RISCV_CALL, objabi.R_RISCV_PCREL_ITYPE:
secondImmMask = riscv.ITypeImmMask
secondImm, err = riscv.EncodeIImmediate(low)
if err != nil {
ldr.Errorf(s, "cannot encode R_RISCV_PCREL_ITYPE I-type instruction relocation offset for %s: %v", ldr.SymName(rs), err)
ldr.Errorf(s, "cannot encode I-type instruction relocation offset for %s: %v", ldr.SymName(rs), err)
}
case objabi.R_RISCV_PCREL_STYPE:
secondImmMask = riscv.STypeImmMask
secondImm, err = riscv.EncodeSImmediate(low)
if err != nil {
ldr.Errorf(s, "cannot encode R_RISCV_PCREL_STYPE S-type instruction relocation offset for %s: %v", ldr.SymName(rs), err)
ldr.Errorf(s, "cannot encode S-type instruction relocation offset for %s: %v", ldr.SymName(rs), err)
}
default:
panic(fmt.Sprintf("unknown relocation type: %v", r.Type()))
Expand All @@ -623,10 +623,10 @@ func archrelocvariant(*ld.Target, *loader.Loader, loader.Reloc, sym.RelocVariant

func extreloc(target *ld.Target, ldr *loader.Loader, r loader.Reloc, s loader.Sym) (loader.ExtReloc, bool) {
switch r.Type() {
case objabi.R_RISCV_CALL, objabi.R_RISCV_CALL_TRAMP:
case objabi.R_RISCV_JAL, objabi.R_RISCV_JAL_TRAMP:
return ld.ExtrelocSimple(ldr, r), true

case objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE, objabi.R_RISCV_TLS_IE, objabi.R_RISCV_TLS_LE:
case objabi.R_RISCV_CALL, objabi.R_RISCV_PCREL_ITYPE, objabi.R_RISCV_PCREL_STYPE, objabi.R_RISCV_TLS_IE, objabi.R_RISCV_TLS_LE:
return ld.ExtrelocViaOuterSym(ldr, r, s), true
}
return loader.ExtReloc{}, false
Expand All @@ -637,7 +637,7 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) {
r := relocs.At(ri)

switch r.Type() {
case objabi.R_RISCV_CALL:
case objabi.R_RISCV_JAL:
pc := ldr.SymValue(s) + int64(r.Off())
off := ldr.SymValue(rs) + r.Add() - pc

Expand Down Expand Up @@ -691,13 +691,16 @@ func trampoline(ctxt *ld.Link, ldr *loader.Loader, ri int, rs, s loader.Sym) {
// address, so we have to assume a trampoline is required. Mark
// this as a call via a trampoline so that we can potentially
// switch to a direct call during relocation.
sb.SetRelocType(ri, objabi.R_RISCV_CALL_TRAMP)
sb.SetRelocType(ri, objabi.R_RISCV_JAL_TRAMP)
}
relocs := sb.Relocs()
r := relocs.At(ri)
r.SetSym(tramp)
r.SetAdd(0)

case objabi.R_RISCV_CALL:
// Nothing to do, already using AUIPC+JALR.

default:
ctxt.Errorf(s, "trampoline called with non-jump reloc: %d (%s)", r.Type(), sym.RelocName(ctxt.Arch, r.Type()))
}
Expand All @@ -707,7 +710,7 @@ func genCallTramp(arch *sys.Arch, linkmode ld.LinkMode, ldr *loader.Loader, tram
tramp.AddUint32(arch, 0x00000f97) // AUIPC $0, X31
tramp.AddUint32(arch, 0x000f8067) // JALR X0, (X31)

r, _ := tramp.AddRel(objabi.R_RISCV_PCREL_ITYPE)
r, _ := tramp.AddRel(objabi.R_RISCV_CALL)
r.SetSiz(8)
r.SetSym(target)
r.SetAdd(offset)
Expand Down

0 comments on commit bda5e6c

Please sign in to comment.