Skip to content

Commit d1ef967

Browse files
committed
cmd/link/internal/ld: assign temporary addresses to per-package text
If trampolines may be required, the current text addressing second pass resets all assigned addresses, before assigning addresses and laying down trampolines in a linear fashion. However, this approach means that intra-package calls are to a symbol that has not yet been assigned an address, when the symbol is ahead of the current function. In the case of RISC-V the JAL instruction is limited to +/-1MiB. As such, if a call is to a symbol with no address currently assigned, we have to assume that a trampoline will be required. During the relocation phase we can fix up and avoid trampolines in some cases, however this results in unused trampolines that are still present in the binary (since removing them would change text addresses). In order to significantly reduce the number of unused trampolines, assign temporary addresses to functions within the same package, based on the maximum number of trampolines that may be required by a function. This allows for better decisions to be made regarding the requirement for intra-package trampolines, as we reset the addressing for a function, assign its final address and lay down any resulting trampolines. This results in ~2,300 unused trampolines being removed from the Go binary and ~5,600 unused trampolines being removed from the compile binary, on linux/riscv64. This reapplies CL 349650, however does not pass big to assignAddress when assigning temporary addresses, as this can result in side effects such as section splitting. Change-Id: Id7febdb65d962d6b1297a91294a8dc27c94d8696 Reviewed-on: https://go-review.googlesource.com/c/go/+/534760 Reviewed-by: Cherry Mui <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]> Run-TryBot: Joel Sing <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Than McIntosh <[email protected]>
1 parent cc13161 commit d1ef967

File tree

2 files changed

+132
-25
lines changed

2 files changed

+132
-25
lines changed

src/cmd/link/internal/ld/data.go

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,15 @@ func maxSizeTrampolines(ctxt *Link, ldr *loader.Loader, s loader.Sym, isTramp bo
8484
}
8585
}
8686

87-
if ctxt.IsARM() {
87+
switch {
88+
case ctxt.IsARM():
8889
return n * 20 // Trampolines in ARM range from 3 to 5 instructions.
89-
}
90-
if ctxt.IsPPC64() {
91-
return n * 16 // Trampolines in PPC64 are 4 instructions.
92-
}
93-
if ctxt.IsARM64() {
90+
case ctxt.IsARM64():
9491
return n * 12 // Trampolines in ARM64 are 3 instructions.
92+
case ctxt.IsPPC64():
93+
return n * 16 // Trampolines in PPC64 are 4 instructions.
94+
case ctxt.IsRISCV64():
95+
return n * 8 // Trampolines in RISCV64 are 2 instructions.
9596
}
9697
panic("unreachable")
9798
}
@@ -118,18 +119,21 @@ func trampoline(ctxt *Link, s loader.Sym) {
118119
continue // something is wrong. skip it here and we'll emit a better error later
119120
}
120121

121-
// RISC-V is only able to reach +/-1MiB via a JAL instruction,
122-
// which we can readily exceed in the same package. As such, we
123-
// need to generate trampolines when the address is unknown.
124-
if ldr.SymValue(rs) == 0 && !ctxt.Target.IsRISCV64() && ldr.SymType(rs) != sym.SDYNIMPORT && ldr.SymType(rs) != sym.SUNDEFEXT {
122+
if ldr.SymValue(rs) == 0 && ldr.SymType(rs) != sym.SDYNIMPORT && ldr.SymType(rs) != sym.SUNDEFEXT {
123+
// Symbols in the same package are laid out together.
124+
// Except that if SymPkg(s) == "", it is a host object symbol
125+
// which may call an external symbol via PLT.
125126
if ldr.SymPkg(s) != "" && ldr.SymPkg(rs) == ldr.SymPkg(s) {
126-
// Symbols in the same package are laid out together.
127-
// Except that if SymPkg(s) == "", it is a host object symbol
128-
// which may call an external symbol via PLT.
129-
continue
127+
// RISC-V is only able to reach +/-1MiB via a JAL instruction.
128+
// We need to generate a trampoline when an address is
129+
// currently unknown.
130+
if !ctxt.Target.IsRISCV64() {
131+
continue
132+
}
130133
}
134+
// Runtime packages are laid out together.
131135
if isRuntimeDepPkg(ldr.SymPkg(s)) && isRuntimeDepPkg(ldr.SymPkg(rs)) {
132-
continue // runtime packages are laid out together
136+
continue
133137
}
134138
}
135139
thearch.Trampoline(ctxt, ldr, ri, rs, s)
@@ -2435,8 +2439,8 @@ func (ctxt *Link) textaddress() {
24352439
limit = 1
24362440
}
24372441

2438-
// First pass: assign addresses assuming the program is small and
2439-
// don't generate trampolines.
2442+
// First pass: assign addresses assuming the program is small and will
2443+
// not require trampoline generation.
24402444
big := false
24412445
for _, s := range ctxt.Textp {
24422446
sect, n, va = assignAddress(ctxt, sect, n, s, va, false, big)
@@ -2451,21 +2455,45 @@ func (ctxt *Link) textaddress() {
24512455
if big {
24522456
// reset addresses
24532457
for _, s := range ctxt.Textp {
2454-
if ldr.OuterSym(s) != 0 || s == text {
2455-
continue
2456-
}
2457-
oldv := ldr.SymValue(s)
2458-
for sub := s; sub != 0; sub = ldr.SubSym(sub) {
2459-
ldr.SetSymValue(sub, ldr.SymValue(sub)-oldv)
2458+
if s != text {
2459+
resetAddress(ctxt, s)
24602460
}
24612461
}
24622462
va = start
24632463

24642464
ntramps := 0
2465-
for _, s := range ctxt.Textp {
2465+
var curPkg string
2466+
for i, s := range ctxt.Textp {
2467+
// When we find the first symbol in a package, perform a
2468+
// single iteration that assigns temporary addresses to all
2469+
// of the text in the same package, using the maximum possible
2470+
// number of trampolines. This allows for better decisions to
2471+
// be made regarding reachability and the need for trampolines.
2472+
if symPkg := ldr.SymPkg(s); symPkg != "" && curPkg != symPkg {
2473+
curPkg = symPkg
2474+
vaTmp := va
2475+
for j := i; j < len(ctxt.Textp); j++ {
2476+
curSym := ctxt.Textp[j]
2477+
if symPkg := ldr.SymPkg(curSym); symPkg == "" || curPkg != symPkg {
2478+
break
2479+
}
2480+
// We do not pass big to assignAddress here, as this
2481+
// can result in side effects such as section splitting.
2482+
sect, n, vaTmp = assignAddress(ctxt, sect, n, curSym, vaTmp, false, false)
2483+
vaTmp += maxSizeTrampolines(ctxt, ldr, curSym, false)
2484+
}
2485+
}
2486+
2487+
// Reset address for current symbol.
2488+
if s != text {
2489+
resetAddress(ctxt, s)
2490+
}
2491+
2492+
// Assign actual address for current symbol.
24662493
sect, n, va = assignAddress(ctxt, sect, n, s, va, false, big)
24672494

2468-
trampoline(ctxt, s) // resolve jumps, may add trampolines if jump too far
2495+
// Resolve jumps, adding trampolines if they are needed.
2496+
trampoline(ctxt, s)
24692497

24702498
// lay down trampolines after each function
24712499
for ; ntramps < len(ctxt.tramps); ntramps++ {
@@ -2613,6 +2641,17 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
26132641
return sect, n, va
26142642
}
26152643

2644+
func resetAddress(ctxt *Link, s loader.Sym) {
2645+
ldr := ctxt.loader
2646+
if ldr.OuterSym(s) != 0 {
2647+
return
2648+
}
2649+
oldv := ldr.SymValue(s)
2650+
for sub := s; sub != 0; sub = ldr.SubSym(sub) {
2651+
ldr.SetSymValue(sub, ldr.SymValue(sub)-oldv)
2652+
}
2653+
}
2654+
26162655
// Return whether we may need to split text sections.
26172656
//
26182657
// On PPC64x, when external linking, a text section should not be

src/cmd/link/internal/ld/ld_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,3 +344,71 @@ func main() {
344344
})
345345
}
346346
}
347+
348+
func TestRISCVTrampolines(t *testing.T) {
349+
testenv.MustHaveGoBuild(t)
350+
t.Parallel()
351+
352+
tmpDir := t.TempDir()
353+
tmpFile := filepath.Join(tmpDir, "x.s")
354+
355+
// Calling b from a or c should not use trampolines, however
356+
// calling from d to a will require one.
357+
buf := new(bytes.Buffer)
358+
fmt.Fprintf(buf, "TEXT a(SB),$0-0\n")
359+
for i := 0; i < 1<<17; i++ {
360+
fmt.Fprintf(buf, "\tADD $0, X0, X0\n")
361+
}
362+
fmt.Fprintf(buf, "\tCALL b(SB)\n")
363+
fmt.Fprintf(buf, "\tRET\n")
364+
fmt.Fprintf(buf, "TEXT b(SB),$0-0\n")
365+
fmt.Fprintf(buf, "\tRET\n")
366+
fmt.Fprintf(buf, "TEXT c(SB),$0-0\n")
367+
fmt.Fprintf(buf, "\tCALL b(SB)\n")
368+
fmt.Fprintf(buf, "\tRET\n")
369+
fmt.Fprintf(buf, "TEXT ·d(SB),0,$0-0\n")
370+
for i := 0; i < 1<<17; i++ {
371+
fmt.Fprintf(buf, "\tADD $0, X0, X0\n")
372+
}
373+
fmt.Fprintf(buf, "\tCALL a(SB)\n")
374+
fmt.Fprintf(buf, "\tCALL c(SB)\n")
375+
fmt.Fprintf(buf, "\tRET\n")
376+
if err := os.WriteFile(tmpFile, buf.Bytes(), 0644); err != nil {
377+
t.Fatalf("Failed to write assembly file: %v", err)
378+
}
379+
380+
if err := os.WriteFile(filepath.Join(tmpDir, "go.mod"), []byte("module riscvtramp"), 0644); err != nil {
381+
t.Fatalf("Failed to write file: %v\n", err)
382+
}
383+
main := `package main
384+
func main() {
385+
d()
386+
}
387+
388+
func d()
389+
`
390+
if err := os.WriteFile(filepath.Join(tmpDir, "x.go"), []byte(main), 0644); err != nil {
391+
t.Fatalf("failed to write main: %v\n", err)
392+
}
393+
cmd := testenv.Command(t, testenv.GoToolPath(t), "build", "-ldflags=-linkmode=internal")
394+
cmd.Dir = tmpDir
395+
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
396+
out, err := cmd.CombinedOutput()
397+
if err != nil {
398+
t.Fatalf("Build failed: %v, output: %s", err, out)
399+
}
400+
401+
// Check what trampolines exist.
402+
cmd = testenv.Command(t, testenv.GoToolPath(t), "tool", "nm", filepath.Join(tmpDir, "riscvtramp"))
403+
cmd.Env = append(os.Environ(), "GOARCH=riscv64", "GOOS=linux")
404+
out, err = cmd.CombinedOutput()
405+
if err != nil {
406+
t.Fatalf("nm failure: %s\n%s\n", err, string(out))
407+
}
408+
if !bytes.Contains(out, []byte(" T a-tramp0")) {
409+
t.Errorf("Trampoline a-tramp0 is missing")
410+
}
411+
if bytes.Contains(out, []byte(" T b-tramp0")) {
412+
t.Errorf("Trampoline b-tramp0 exists unnecessarily")
413+
}
414+
}

0 commit comments

Comments
 (0)