1
0
mirror of https://github.com/golang/go synced 2024-09-24 05:10:13 -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 // 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 // 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. // 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. // If thearch.Trampoline is nil, then trampoline support is not available on this arch.
// A trampoline does not need any dependent trampolines. // A trampoline does not need any dependent trampolines.
if thearch.Trampoline == nil || isTramp { if thearch.Trampoline == nil || isTramp {
@ -82,8 +82,14 @@ func maxSizeTrampolinesPPC64(ldr *loader.Loader, s loader.Sym, isTramp bool) uin
n++ 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 // 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)) funcsize = uint64(ldr.SymSize(s))
} }
// On ppc64x a text section should not be larger than 2^26 bytes due to the size of // If we need to split text sections, and this function doesn't fit in the current
// call target offset field in the bl instruction. Splitting into smaller text // section, then create a new one.
// 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.
// //
// Only break at outermost syms. // 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, // For debugging purposes, allow text size limit to be cranked down,
// so as to stress test the code that handles multiple text sections. // so as to stress test the code that handles multiple text sections.
var textSizelimit uint64 = thearch.TrampLimit 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 // Sanity check: make sure the limit is larger than any
// individual text symbol. // individual text symbol.
if funcsize > textSizelimit { 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 { if va-sect.Vaddr+funcsize+maxSizeTrampolines(ctxt, ldr, s, isTramp) > textSizelimit {
// Align the next text section to the worst case function alignment likely sectAlign := int32(thearch.Funcalign)
// to be encountered when processing function symbols. The start address if ctxt.IsPPC64() {
// is rounded against the final alignment of the text section later on in // Align the next text section to the worst case function alignment likely
// (*Link).address. This may happen due to usage of PCALIGN directives // to be encountered when processing function symbols. The start address
// larger than Funcalign, or usage of ISA 3.1 prefixed instructions // is rounded against the final alignment of the text section later on in
// (see ISA 3.1 Book I 1.9). // (*Link).address. This may happen due to usage of PCALIGN directives
const ppc64maxFuncalign = 64 // larger than Funcalign, or usage of ISA 3.1 prefixed instructions
va = uint64(Rnd(int64(va), ppc64maxFuncalign)) // (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 // Set the length for the previous text section
sect.Length = va - sect.Vaddr 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 = addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05)
sect.Vaddr = va sect.Vaddr = va
sect.Align = ppc64maxFuncalign sect.Align = sectAlign
ldr.SetSymSect(s, sect) ldr.SetSymSect(s, sect)
// Create a symbol for the start of the secondary text sections // 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.SetType(sym.STEXT)
ntext.SetSize(int64(MINFUNC)) ntext.SetSize(int64(MINFUNC))
ntext.SetOnList(true) ntext.SetOnList(true)
ntext.SetAlign(ppc64maxFuncalign) ntext.SetAlign(sectAlign)
ctxt.tramps = append(ctxt.tramps, ntext.Sym()) ctxt.tramps = append(ctxt.tramps, ntext.Sym())
ntext.SetValue(int64(va)) 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 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 // address assigns virtual addresses to all segments and sections and
// returns all segments in file order. // returns all segments in file order.
func (ctxt *Link) address() []*sym.Segment { func (ctxt *Link) address() []*sym.Segment {

View File

@ -131,10 +131,16 @@ func TestArchiveBuildInvokeWithExec(t *testing.T) {
} }
} }
func TestPPC64LargeTextSectionSplitting(t *testing.T) { func TestLargeTextSectionSplitting(t *testing.T) {
// The behavior we're checking for is of interest only on ppc64. switch runtime.GOARCH {
if !strings.HasPrefix(runtime.GOARCH, "ppc64") { case "ppc64", "ppc64le":
t.Skip("test useful only for ppc64") 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) testenv.MustHaveGoBuild(t)
@ -142,13 +148,13 @@ func TestPPC64LargeTextSectionSplitting(t *testing.T) {
t.Parallel() t.Parallel()
dir := t.TempDir() 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 // split text sections at a size threshold of 1M instead of the
// architected limit of 67M. The choice of building cmd/go is // architected limit of 67M or larger. The choice of building cmd/go
// arbitrary; we just need something sufficiently large that uses // is arbitrary; we just need something sufficiently large that uses
// external linking. // external linking.
exe := filepath.Join(dir, "go.exe") 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 { if eerr != nil {
t.Fatalf("build failure: %s\n%s\n", eerr, string(out)) 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) relocSect(ctxt, Segtext.Sections[0], ctxt.Textp)
for _, sect := range Segtext.Sections[1:] { 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 { for _, sect := range Segrelrodata.Sections {
relocSect(ctxt, sect, ctxt.datap) relocSect(ctxt, sect, ctxt.datap)

View File

@ -88,7 +88,7 @@ var (
flag8 bool // use 64-bit addresses in symbol table flag8 bool // use 64-bit addresses in symbol table
flagInterpreter = flag.String("I", "", "use `linker` as ELF dynamic linker") flagInterpreter = flag.String("I", "", "use `linker` as ELF dynamic linker")
FlagDebugTramp = flag.Int("debugtramp", 0, "debug trampolines") 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).") 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`") FlagRound = flag.Int("R", -1, "set address rounding `quantum`")
FlagTextAddr = flag.Int64("T", -1, "set text segment `address`") FlagTextAddr = flag.Int64("T", -1, "set text segment `address`")