Skip to content

Commit f39997b

Browse files
committed
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 golang#40492. Change-Id: I584f1ec673170c5e4d2dc1e00c701964d6f14333 Reviewed-on: https://go-review.googlesource.com/c/go/+/316050 Trust: Cherry Mui <[email protected]> Reviewed-by: Than McIntosh <[email protected]>
1 parent 90d6bbb commit f39997b

4 files changed

Lines changed: 61 additions & 32 deletions

File tree

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

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func isRuntimeDepPkg(pkg string) bool {
6767
// Estimate the max size needed to hold any new trampolines created for this function. This
6868
// is used to determine when the section can be split if it becomes too large, to ensure that
6969
// the trampolines are in the same section as the function that uses them.
70-
func maxSizeTrampolinesPPC64(ldr *loader.Loader, s loader.Sym, isTramp bool) uint64 {
70+
func maxSizeTrampolines(ctxt *Link, ldr *loader.Loader, s loader.Sym, isTramp bool) uint64 {
7171
// If thearch.Trampoline is nil, then trampoline support is not available on this arch.
7272
// A trampoline does not need any dependent trampolines.
7373
if thearch.Trampoline == nil || isTramp {
@@ -82,8 +82,14 @@ func maxSizeTrampolinesPPC64(ldr *loader.Loader, s loader.Sym, isTramp bool) uin
8282
n++
8383
}
8484
}
85-
// Trampolines in ppc64 are 4 instructions.
86-
return n * 16
85+
86+
if ctxt.IsPPC64() {
87+
return n * 16 // Trampolines in PPC64 are 4 instructions.
88+
}
89+
if ctxt.IsARM64() {
90+
return n * 12 // Trampolines in ARM64 are 3 instructions.
91+
}
92+
panic("unreachable")
8793
}
8894

8995
// 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
23482354
funcsize = uint64(ldr.SymSize(s))
23492355
}
23502356

2351-
// On ppc64x a text section should not be larger than 2^26 bytes due to the size of
2352-
// call target offset field in the bl instruction. Splitting into smaller text
2353-
// sections smaller than this limit allows the GNU linker to modify the long calls
2354-
// appropriately. The limit allows for the space needed for tables inserted by the linker.
2355-
//
2356-
// If this function doesn't fit in the current text section, then create a new one.
2357+
// If we need to split text sections, and this function doesn't fit in the current
2358+
// section, then create a new one.
23572359
//
23582360
// Only break at outermost syms.
2359-
if ctxt.Arch.InFamily(sys.PPC64) && ldr.OuterSym(s) == 0 && ctxt.IsExternal() && big {
2361+
if big && splitTextSections(ctxt) && ldr.OuterSym(s) == 0 {
23602362
// For debugging purposes, allow text size limit to be cranked down,
23612363
// so as to stress test the code that handles multiple text sections.
23622364
var textSizelimit uint64 = thearch.TrampLimit
@@ -2367,18 +2369,22 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
23672369
// Sanity check: make sure the limit is larger than any
23682370
// individual text symbol.
23692371
if funcsize > textSizelimit {
2370-
panic(fmt.Sprintf("error: ppc64 text size limit %d less than text symbol %s size of %d", textSizelimit, ldr.SymName(s), funcsize))
2372+
panic(fmt.Sprintf("error: text size limit %d less than text symbol %s size of %d", textSizelimit, ldr.SymName(s), funcsize))
23712373
}
23722374

2373-
if va-sect.Vaddr+funcsize+maxSizeTrampolinesPPC64(ldr, s, isTramp) > textSizelimit {
2374-
// Align the next text section to the worst case function alignment likely
2375-
// to be encountered when processing function symbols. The start address
2376-
// is rounded against the final alignment of the text section later on in
2377-
// (*Link).address. This may happen due to usage of PCALIGN directives
2378-
// larger than Funcalign, or usage of ISA 3.1 prefixed instructions
2379-
// (see ISA 3.1 Book I 1.9).
2380-
const ppc64maxFuncalign = 64
2381-
va = uint64(Rnd(int64(va), ppc64maxFuncalign))
2375+
if va-sect.Vaddr+funcsize+maxSizeTrampolines(ctxt, ldr, s, isTramp) > textSizelimit {
2376+
sectAlign := int32(thearch.Funcalign)
2377+
if ctxt.IsPPC64() {
2378+
// Align the next text section to the worst case function alignment likely
2379+
// to be encountered when processing function symbols. The start address
2380+
// is rounded against the final alignment of the text section later on in
2381+
// (*Link).address. This may happen due to usage of PCALIGN directives
2382+
// larger than Funcalign, or usage of ISA 3.1 prefixed instructions
2383+
// (see ISA 3.1 Book I 1.9).
2384+
const ppc64maxFuncalign = 64
2385+
sectAlign = ppc64maxFuncalign
2386+
va = uint64(Rnd(int64(va), ppc64maxFuncalign))
2387+
}
23822388

23832389
// Set the length for the previous text section
23842390
sect.Length = va - sect.Vaddr
@@ -2387,7 +2393,7 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
23872393
sect = addsection(ctxt.loader, ctxt.Arch, &Segtext, ".text", 05)
23882394

23892395
sect.Vaddr = va
2390-
sect.Align = ppc64maxFuncalign
2396+
sect.Align = sectAlign
23912397
ldr.SetSymSect(s, sect)
23922398

23932399
// 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
24002406
ntext.SetType(sym.STEXT)
24012407
ntext.SetSize(int64(MINFUNC))
24022408
ntext.SetOnList(true)
2403-
ntext.SetAlign(ppc64maxFuncalign)
2409+
ntext.SetAlign(sectAlign)
24042410
ctxt.tramps = append(ctxt.tramps, ntext.Sym())
24052411

24062412
ntext.SetValue(int64(va))
@@ -2429,6 +2435,19 @@ func assignAddress(ctxt *Link, sect *sym.Section, n int, s loader.Sym, va uint64
24292435
return sect, n, va
24302436
}
24312437

2438+
// Return whether we may need to split text sections.
2439+
//
2440+
// On PPC64x whem external linking a text section should not be larger than 2^25 bytes
2441+
// due to the size of call target offset field in the bl instruction. Splitting into
2442+
// smaller text sections smaller than this limit allows the system linker to modify the long
2443+
// calls appropriately. The limit allows for the space needed for tables inserted by the
2444+
// linker.
2445+
//
2446+
// The same applies to Darwin/ARM64, with 2^27 byte threshold.
2447+
func splitTextSections(ctxt *Link) bool {
2448+
return (ctxt.IsPPC64() || (ctxt.IsARM64() && ctxt.IsDarwin())) && ctxt.IsExternal()
2449+
}
2450+
24322451
// address assigns virtual addresses to all segments and sections and
24332452
// returns all segments in file order.
24342453
func (ctxt *Link) address() []*sym.Segment {

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

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,24 +131,30 @@ func TestArchiveBuildInvokeWithExec(t *testing.T) {
131131
}
132132
}
133133

134-
func TestPPC64LargeTextSectionSplitting(t *testing.T) {
135-
// The behavior we're checking for is of interest only on ppc64.
136-
if !strings.HasPrefix(runtime.GOARCH, "ppc64") {
137-
t.Skip("test useful only for ppc64")
134+
func TestLargeTextSectionSplitting(t *testing.T) {
135+
switch runtime.GOARCH {
136+
case "ppc64", "ppc64le":
137+
case "arm64":
138+
if runtime.GOOS == "darwin" {
139+
break
140+
}
141+
fallthrough
142+
default:
143+
t.Skipf("text section splitting is not done in %s/%s", runtime.GOOS, runtime.GOARCH)
138144
}
139145

140146
testenv.MustHaveGoBuild(t)
141147
testenv.MustHaveCGO(t)
142148
t.Parallel()
143149
dir := t.TempDir()
144150

145-
// NB: the use of -ldflags=-debugppc64textsize=1048576 tells the linker to
151+
// NB: the use of -ldflags=-debugtextsize=1048576 tells the linker to
146152
// split text sections at a size threshold of 1M instead of the
147-
// architected limit of 67M. The choice of building cmd/go is
148-
// arbitrary; we just need something sufficiently large that uses
153+
// architected limit of 67M or larger. The choice of building cmd/go
154+
// is arbitrary; we just need something sufficiently large that uses
149155
// external linking.
150156
exe := filepath.Join(dir, "go.exe")
151-
out, eerr := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugppc64textsize=1048576", "cmd/go").CombinedOutput()
157+
out, eerr := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "-ldflags=-linkmode=external -debugtextsize=1048576", "cmd/go").CombinedOutput()
152158
if eerr != nil {
153159
t.Fatalf("build failure: %s\n%s\n", eerr, string(out))
154160
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1241,7 +1241,11 @@ func machoEmitReloc(ctxt *Link) {
12411241

12421242
relocSect(ctxt, Segtext.Sections[0], ctxt.Textp)
12431243
for _, sect := range Segtext.Sections[1:] {
1244-
relocSect(ctxt, sect, ctxt.datap)
1244+
if sect.Name == ".text" {
1245+
relocSect(ctxt, sect, ctxt.Textp)
1246+
} else {
1247+
relocSect(ctxt, sect, ctxt.datap)
1248+
}
12451249
}
12461250
for _, sect := range Segrelrodata.Sections {
12471251
relocSect(ctxt, sect, ctxt.datap)

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

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

0 commit comments

Comments
 (0)