mirror of
https://github.com/golang/go
synced 2024-11-11 22:50:22 -07: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:
parent
90d6bbbe42
commit
f39997b2be
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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`")
|
||||
|
Loading…
Reference in New Issue
Block a user