1
0
mirror of https://github.com/golang/go synced 2024-09-24 03:10:16 -06:00

cmd/link: split large text sections on Darwin/ARM64 when external linking

The Darwin linker does not like text sections that are larger
than the jump limit (even if we already inserted trampolines).
Split the text section to multiple smaller sections.

Now external linking very large binaries works on Darwin/ARM64.

Updates #40492.

Change-Id: I584f1ec673170c5e4d2dc1e00c701964d6f14333
Reviewed-on: https://go-review.googlesource.com/c/go/+/316050
Trust: Cherry Mui <cherryyz@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
Cherry Mui 2021-05-06 12:24:32 -04:00
parent 90d6bbbe42
commit f39997b2be
4 changed files with 61 additions and 32 deletions

View File

@ -67,7 +67,7 @@ func isRuntimeDepPkg(pkg string) bool {
// Estimate the max size needed to hold any new trampolines created for this function. This
// is used to determine when the section can be split if it becomes too large, to ensure that
// the trampolines are in the same section as the function that uses them.
func maxSizeTrampolinesPPC64(ldr *loader.Loader, s loader.Sym, isTramp bool) uint64 {
func maxSizeTrampolines(ctxt *Link, ldr *loader.Loader, s loader.Sym, isTramp bool) uint64 {
// If thearch.Trampoline is nil, then trampoline support is not available on this arch.
// A trampoline does not need any dependent trampolines.
if thearch.Trampoline == nil || isTramp {
@ -82,8 +82,14 @@ func maxSizeTrampolinesPPC64(ldr *loader.Loader, s loader.Sym, isTramp bool) uin
n++
}
}
// Trampolines in ppc64 are 4 instructions.
return n * 16
if ctxt.IsPPC64() {
return n * 16 // Trampolines in PPC64 are 4 instructions.
}
if ctxt.IsARM64() {
return n * 12 // Trampolines in ARM64 are 3 instructions.
}
panic("unreachable")
}
// detect too-far jumps in function s, and add trampolines if necessary
@ -2348,15 +2354,11 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
funcsize = uint64(ldr.SymSize(s))
}
// On ppc64x a text section should not be larger than 2^26 bytes due to the size of
// call target offset field in the bl instruction. Splitting into smaller text
// sections smaller than this limit allows the GNU linker to modify the long calls
// appropriately. The limit allows for the space needed for tables inserted by the linker.
//
// If this function doesn't fit in the current text section, then create a new one.
// If we need to split text sections, and this function doesn't fit in the current
// section, then create a new one.
//
// Only break at outermost syms.
if ctxt.Arch.InFamily(sys.PPC64) && ldr.OuterSym(s) == 0 && ctxt.IsExternal() && big {
if big && splitTextSections(ctxt) && ldr.OuterSym(s) == 0 {
// For debugging purposes, allow text size limit to be cranked down,
// so as to stress test the code that handles multiple text sections.
var textSizelimit uint64 = thearch.TrampLimit
@ -2367,18 +2369,22 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
// Sanity check: make sure the limit is larger than any
// individual text symbol.
if funcsize > textSizelimit {
panic(fmt.Sprintf("error: ppc64 text size limit %d less than text symbol %s size of %d", textSizelimit, ldr.SymName(s), funcsize))
panic(fmt.Sprintf("error: text size limit %d less than text symbol %s size of %d", textSizelimit, ldr.SymName(s), funcsize))
}
if va-sect.Vaddr+funcsize+maxSizeTrampolinesPPC64(ldr, s, isTramp) > textSizelimit {
// Align the next text section to the worst case function alignment likely
// to be encountered when processing function symbols. The start address
// is rounded against the final alignment of the text section later on in
// (*Link).address. This may happen due to usage of PCALIGN directives
// larger than Funcalign, or usage of ISA 3.1 prefixed instructions
// (see ISA 3.1 Book I 1.9).
const ppc64maxFuncalign = 64
va = uint64(Rnd(int64(va), ppc64maxFuncalign))
if va-sect.Vaddr+funcsize+maxSizeTrampolines(ctxt, ldr, s, isTramp) > textSizelimit {
sectAlign := int32(thearch.Funcalign)
if ctxt.IsPPC64() {
// Align the next text section to the worst case function alignment likely
// to be encountered when processing function symbols. The start address
// is rounded against the final alignment of the text section later on in
// (*Link).address. This may happen due to usage of PCALIGN directives
// larger than Funcalign, or usage of ISA 3.1 prefixed instructions
// (see ISA 3.1 Book I 1.9).
const ppc64maxFuncalign = 64
sectAlign = ppc64maxFuncalign
va = uint64(Rnd(int64(va), ppc64maxFuncalign))
}
// Set the length for the previous text section
sect.Length = va - sect.Vaddr
@ -2387,7 +2393,7 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
sect = addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05)
sect.Vaddr = va
sect.Align = ppc64maxFuncalign
sect.Align = sectAlign
ldr.SetSymSect(s, sect)
// Create a symbol for the start of the secondary text sections
@ -2400,7 +2406,7 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
ntext.SetType(sym.STEXT)
ntext.SetSize(int64(MINFUNC))
ntext.SetOnList(true)
ntext.SetAlign(ppc64maxFuncalign)
ntext.SetAlign(sectAlign)
ctxt.tramps = append(ctxt.tramps, ntext.Sym())
ntext.SetValue(int64(va))
@ -2429,6 +2435,19 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
return sect, n, va
}
// Return whether we may need to split text sections.
//
// On PPC64x whem external linking a text section should not be larger than 2^25 bytes
// due to the size of call target offset field in the bl instruction. Splitting into
// smaller text sections smaller than this limit allows the system linker to modify the long
// calls appropriately. The limit allows for the space needed for tables inserted by the
// linker.
//
// The same applies to Darwin/ARM64, with 2^27 byte threshold.
func splitTextSections(ctxt *Link) bool {
return (ctxt.IsPPC64() || (ctxt.IsARM64() && ctxt.IsDarwin())) && ctxt.IsExternal()
}
// address assigns virtual addresses to all segments and sections and
// returns all segments in file order.
func (ctxt *Link) address() []*sym.Segment {

View File

@ -131,10 +131,16 @@ func TestArchiveBuildInvokeWithExec(t *testing.T) {
}
}
func TestPPC64LargeTextSectionSplitting(t *testing.T) {
// The behavior we're checking for is of interest only on ppc64.
if !strings.HasPrefix(runtime.GOARCH, "ppc64") {
t.Skip("test useful only for ppc64")
func TestLargeTextSectionSplitting(t *testing.T) {
switch runtime.GOARCH {
case "ppc64", "ppc64le":
case "arm64":
if runtime.GOOS == "darwin" {
break
}
fallthrough
default:
t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH)
}
testenv.MustHaveGoBuild(t)
@ -142,13 +148,13 @@ func TestPPC64LargeTextSectionSplitting(t *testing.T) {
t.Parallel()
dir := t.TempDir()
// NB: the use of -ldflags=-debugppc64textsize=1048576 tells the linker to
// NB: the use of -ldflags=-debugtextsize=1048576 tells the linker to
// split text sections at a size threshold of 1M instead of the
// architected limit of 67M. The choice of building cmd/go is
// arbitrary; we just need something sufficiently large that uses
// architected limit of 67M or larger. The choice of building cmd/go
// is arbitrary; we just need something sufficiently large that uses
// external linking.
exe := filepath.Join(dir, "go.exe")
out, eerr := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugppc64textsize=1048576", "cmd/go").CombinedOutput()
out, eerr := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugtextsize=1048576", "cmd/go").CombinedOutput()
if eerr != nil {
t.Fatalf("build failure: %s\n%s\n", eerr, string(out))
}

View File

@ -1241,7 +1241,11 @@ func machoEmitReloc(ctxt *Link) {
relocSect(ctxt, Segtext.Sections[0], ctxt.Textp)
for _, sect := range Segtext.Sections[1:] {
relocSect(ctxt, sect, ctxt.datap)
if sect.Name == ".text" {
relocSect(ctxt, sect, ctxt.Textp)
} else {
relocSect(ctxt, sect, ctxt.datap)
}
}
for _, sect := range Segrelrodata.Sections {
relocSect(ctxt, sect, ctxt.datap)

View File

@ -88,7 +88,7 @@ var (
flag8 bool // use 64-bit addresses in symbol table
flagInterpreter = flag.String("I", "", "use `linker` as ELF dynamic linker")
FlagDebugTramp = flag.Int("debugtramp", 0, "debug trampolines")
FlagDebugTextSize = flag.Int("debugppc64textsize", 0, "debug PPC64 text section max")
FlagDebugTextSize = flag.Int("debugtextsize", 0, "debug text section max size")
FlagStrictDups = flag.Int("strictdups", 0, "sanity check duplicate symbol contents during object file reading (1=warn 2=err).")
FlagRound = flag.Int("R", -1, "set address rounding `quantum`")
FlagTextAddr = flag.Int64("T", -1, "set text segment `address`")