Skip to content

Commit 4167077

Browse files
authored
Fix issues with GlobalMerge on Mach-O. (#110046)
As a side-effect of PR #101222, GlobalMerge started making transforms which are unsafe on Mach-O platforms. Two issues, in particular, are fixed here: 1. We must never merge symbols in the `__cfstring` section, as the linker assumes each object in this section is only ever referenced directly, and that it can split the section as it likes. Previously, we avoided this problem because CFString literals are identified by private-linkage symbols. This patch adds a list of section-names with special behavior, to avoid merging under Mach-O. 2. When GlobalMerge code was originally written, it had to be careful about emitting symbol aliases, due to issues with Mach-O's subsection splitting in the linker with `-dead_strip` enabled. The underlying cause of this problem was fixed in 2016, via creation of the `.alt_entry` assembler directive, which allows a symbol to not also imply the start of a new subsection. GlobalMerge's workaround for that issue was never removed. In the meantime, Apple's new ld-prime linker was written, and has a bug in `.alt_entry` handling. Therefore, even though the original issue was fixed, we must _continue_ to be careful not to emit any such symbol aliases. The existing workaround avoided it for InternalLinkage symbols, but after the above-mentioned PR, we also must avoid emitting aliases for PrivateLinkage symbols. I will file an Apple bug-report about this issue, so that it can be fixed in a future version of ld-prime. But, in the meantime, the workaround is sufficient for GlobalMerge, unless `-global-merge-on-externals` is enabled (which it is already not by default, on MachO platforms, due to the original issue). Fixes #104625
1 parent 3625f9f commit 4167077

File tree

3 files changed

+87
-6
lines changed

3 files changed

+87
-6
lines changed

llvm/lib/CodeGen/GlobalMerge.cpp

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -578,12 +578,18 @@ bool GlobalMergeImpl::doMerge(const SmallVectorImpl<GlobalVariable *> &Globals,
578578
Globals[k]->replaceAllUsesWith(GEP);
579579
Globals[k]->eraseFromParent();
580580

581-
// When the linkage is not internal we must emit an alias for the original
582-
// variable name as it may be accessed from another object. On non-Mach-O
583-
// we can also emit an alias for internal linkage as it's safe to do so.
584-
// It's not safe on Mach-O as the alias (and thus the portion of the
585-
// MergedGlobals variable) may be dead stripped at link time.
586-
if (Linkage != GlobalValue::InternalLinkage || !IsMachO) {
581+
// Emit an alias for the original variable name. This is necessary for an
582+
// external symbol, as it may be accessed from another object. For
583+
// internal symbols, it's not strictly required, but it's useful.
584+
//
585+
// This _should_ also work on Mach-O ever since '.alt_entry' support was
586+
// added in 2016. Unfortunately, there's a bug in ld-prime (present at
587+
// least from Xcode 15.0 through Xcode 16.0), in which -dead_strip doesn't
588+
// always honor alt_entry. To workaround this issue, we don't emit aliases
589+
// on Mach-O. Except, we _must_ do so for external symbols. That means
590+
// MergeExternal is broken with that linker. (That option is currently off
591+
// by default on MachO).
592+
if (!IsMachO || Linkage == GlobalValue::ExternalLinkage) {
587593
GlobalAlias *GA = GlobalAlias::create(Tys[StructIdxs[idx]], AddrSpace,
588594
Linkage, Name, GEP, &M);
589595
GA->setVisibility(Visibility);
@@ -640,6 +646,18 @@ void GlobalMergeImpl::setMustKeepGlobalVariables(Module &M) {
640646
}
641647
}
642648

649+
// This function returns true if the given data Section name has custom
650+
// subsection-splitting semantics in Mach-O (such as splitting by a fixed size)
651+
//
652+
// See also ObjFile::parseSections and getRecordSize in lld/MachO/InputFiles.cpp
653+
static bool isSpecialMachOSection(StringRef Section) {
654+
// Uses starts_with, since section attributes can appear at the end of the
655+
// name.
656+
return Section.starts_with("__DATA,__cfstring") ||
657+
Section.starts_with("__DATA,__objc_classrefs") ||
658+
Section.starts_with("__DATA,__objc_selrefs");
659+
}
660+
643661
bool GlobalMergeImpl::run(Module &M) {
644662
if (!EnableGlobalMerge)
645663
return false;
@@ -678,6 +696,10 @@ bool GlobalMergeImpl::run(Module &M) {
678696
unsigned AddressSpace = PT->getAddressSpace();
679697
StringRef Section = GV.getSection();
680698

699+
// On Mach-O, some section names have special semantics. Don't merge these.
700+
if (IsMachO && isSpecialMachOSection(Section))
701+
continue;
702+
681703
// Ignore all 'special' globals.
682704
if (GV.getName().starts_with("llvm.") || GV.getName().starts_with(".llvm."))
683705
continue;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
; RUN: opt -global-merge -global-merge-max-offset=100 -S -o - %s | FileCheck %s
2+
; RUN: opt -passes='global-merge<max-offset=100>' -S -o - %s | FileCheck %s
3+
4+
;; Check that we do _not_ merge globals which are in certain special
5+
;; sections under Mach-O.
6+
7+
target datalayout = "e-p:64:64"
8+
target triple = "x86_64-apple-macos11"
9+
10+
; CHECK: @cfstring1 = private global i32 1, section "__DATA,__cfstring"
11+
@cfstring1 = private global i32 1, section "__DATA,__cfstring"
12+
; CHECK: @cfstring2 = private global i32 2, section "__DATA,__cfstring"
13+
@cfstring2 = private global i32 2, section "__DATA,__cfstring"
14+
; CHECK: @objcclassrefs1 = private global i32 3, section "__DATA,__objc_classrefs,regular,no_dead_strip"
15+
@objcclassrefs1 = private global i32 3, section "__DATA,__objc_classrefs,regular,no_dead_strip"
16+
; CHECK: @objcclassrefs2 = private global i32 4, section "__DATA,__objc_classrefs,regular,no_dead_strip"
17+
@objcclassrefs2 = private global i32 4, section "__DATA,__objc_classrefs,regular,no_dead_strip"
18+
; CHECK: @objcselrefs1 = private global i32 5, section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip"
19+
@objcselrefs1 = private global i32 5, section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip"
20+
; CHECK: @objcselrefs2 = private global i32 6, section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip"
21+
@objcselrefs2 = private global i32 6, section "__DATA,__objc_selrefs,literal_pointers,no_dead_strip"
22+
23+
define void @use() {
24+
load ptr, ptr @cfstring1
25+
load ptr, ptr @cfstring2
26+
load ptr, ptr @objcclassrefs1
27+
load ptr, ptr @objcclassrefs2
28+
load ptr, ptr @objcselrefs1
29+
load ptr, ptr @objcselrefs2
30+
ret void
31+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
; RUN: opt -global-merge -global-merge-max-offset=100 -S -o - %s | FileCheck %s
2+
; RUN: opt -passes='global-merge<max-offset=100>' -S -o - %s | FileCheck %s
3+
4+
;; For Mach-O, we do not expect any alias symbols to be created for
5+
;; internal/private symbols by GlobalMerge.
6+
7+
target datalayout = "e-p:64:64"
8+
target triple = "x86_64-apple-macos11"
9+
10+
@a = private global i32 1
11+
@b = private global i32 2
12+
@c = internal global i32 3
13+
@d = internal global i32 4
14+
15+
; CHECK: @_MergedGlobals = internal global <{ i32, i32, i32, i32 }> <{ i32 1, i32 2, i32 3, i32 4 }>, align 4
16+
; CHECK-NOT: alias
17+
18+
define void @use() {
19+
; CHECK: load i32, ptr @_MergedGlobals,
20+
%x = load i32, ptr @a
21+
; CHECK: load i32, ptr getelementptr inbounds (<{ i32, i32, i32, i32 }>, ptr @_MergedGlobals, i32 0, i32 1)
22+
%y = load i32, ptr @b
23+
; CHECK: load i32, ptr getelementptr inbounds (<{ i32, i32, i32, i32 }>, ptr @_MergedGlobals, i32 0, i32 2)
24+
%z1 = load i32, ptr @c
25+
; CHECK: load i32, ptr getelementptr inbounds (<{ i32, i32, i32, i32 }>, ptr @_MergedGlobals, i32 0, i32 3)
26+
%z2 = load i32, ptr @d
27+
ret void
28+
}

0 commit comments

Comments
 (0)