diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -416,17 +416,62 @@ InputSection *isec, uint64_t value, uint64_t size) { // Symbol scope is determined by sym.n_type & (N_EXT | N_PEXT): - // N_EXT: Global symbols - // N_EXT | N_PEXT: Linkage unit (think: dylib) scoped + // N_EXT: Global symbols. These go in the symbol table during the link, + // and also in the export table of the output so that the dynamic + // linker sees them. + // N_EXT | N_PEXT: Linkage unit (think: dylib) scoped. These go in the + // symbol table during the link so that duplicates are + // either reported (for non-weak symbols) or merged + // (for weak symbols), but they do not go in the export + // table of the output. // N_PEXT: Does not occur in input files in practice, // a private extern must be external. - // 0: Translation-unit scoped. These are not in the symbol table. + // 0: Translation-unit scoped. These are not in the symbol table during + // link, and not in the export table of the output either. + + bool isWeakDefCanBeHidden = + (sym.n_desc & (N_WEAK_DEF | N_WEAK_REF)) == (N_WEAK_DEF | N_WEAK_REF); if (sym.n_type & (N_EXT | N_PEXT)) { assert((sym.n_type & N_EXT) && "invalid input"); + bool isPrivateExtern = sym.n_type & N_PEXT; + + // lld's behavior for merging symbols is slightly different from ld64: + // ld64 picks the winning symbol based on several criteria (see + // pickBetweenRegularAtoms() in ld64's SymbolTable.cpp), while lld + // just merges metadata and keeps the contents of the first symbol + // with that name (see SymbolTable::addDefined). For: + // * inline function F in a TU built with -fvisibility-inlines-hidden + // * and inline function F in another TU built without that flag + // ld64 will pick the one from the file built without + // -fvisibility-inlines-hidden. + // lld will instead pick the one listed first on the link command line and + // give it visibility as if the function was built without + // -fvisibility-inlines-hidden. + // If both functions have the same contents, this will have the same + // behavior. If not, it won't, but the input had an ODR violation in + // that case. + // + // Similarly, merging a symbol + // that's isPrivateExtern and not isWeakDefCanBeHidden with one + // that's not isPrivateExtern but isWeakDefCanBeHidden technically + // should produce one + // that's not isPrivateExtern but isWeakDefCanBeHidden. That matters + // with ld64's semantics, because it means the non-private-extern + // definition will continue to take priority if more private extern + // definitions are encountered. With lld's semantics there's no observable + // difference between a symbol that's isWeakDefCanBeHidden or one that's + // privateExtern -- neither makes it into the dynamic symbol table. So just + // promote isWeakDefCanBeHidden to isPrivateExtern here. + if (isWeakDefCanBeHidden) + isPrivateExtern = true; + return symtab->addDefined(name, isec->file, isec, value, size, - sym.n_desc & N_WEAK_DEF, sym.n_type & N_PEXT); + sym.n_desc & N_WEAK_DEF, isPrivateExtern); } + + assert(!isWeakDefCanBeHidden && + "weak_def_can_be_hidden on already-hidden symbol?"); return make(name, isec->file, isec, value, size, sym.n_desc & N_WEAK_DEF, /*isExternal=*/false, /*isPrivateExtern=*/false); diff --git a/lld/test/MachO/weak-def-can-be-hidden.s b/lld/test/MachO/weak-def-can-be-hidden.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/weak-def-can-be-hidden.s @@ -0,0 +1,137 @@ +# REQUIRES: x86 +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/weak-foo.s -o %t/weak-foo.o + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/weak-autohide-foo.s -o %t/weak-autohide-foo.o + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/weak-foo-pe.s -o %t/weak-foo-pe.o + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/weak-autohide-foo-pe.s -o %t/weak-autohide-foo-pe.o + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/ref-foo.s -o %t/ref-foo.o + +## Basics: A weak_def_can_be_hidden symbol should not be in the output's +## export table, nor in the weak bind table, and references to it from +## within the dylib should not use weak indirect lookups. +## Think: Inline function compiled without any -fvisibility flags, inline +## function does not have its address taken. In -O2 compiles, GlobalOpt will +## upgrade the inline function from .weak_definition to .weak_def_can_be_hidden. +# RUN: %lld -dylib -o %t/weak-autohide-foo.dylib \ +# RUN: %t/weak-autohide-foo.o %t/ref-foo.o +# RUN: llvm-objdump --syms --exports-trie %t/weak-autohide-foo.dylib | \ +# RUN: FileCheck --check-prefix=EXPORTS %s +# RUN: llvm-nm -m %t/weak-autohide-foo.dylib | \ +# RUN: FileCheck --check-prefix=EXPORTS-NM %s +# RUN: llvm-objdump --macho --bind --weak-bind %t/weak-autohide-foo.dylib | \ +# RUN: FileCheck --check-prefix=WEAKBIND %s +# RUN: llvm-objdump --macho --private-header %t/weak-autohide-foo.dylib | \ +# RUN: FileCheck --check-prefix=HEADERS %s + +## ld64 doesn't treat .weak_def_can_be_hidden as weak symbols in the +## exports trie or bind tables, but it claims they are weak in the symbol +## table. lld marks them as local in the symbol table, see also test +## weak-private-extern.s. These FileCheck lines match both outputs. +# EXPORTS-LABEL: SYMBOL TABLE: +# EXPORTS-DAG: [[#%x, FOO_ADDR:]] {{.*}} _foo +# EXPORTS-LABEL: Exports trie: +# EXPORTS-NOT: 0x{{0*}}[[#%X, FOO_ADDR]] _foo + +## nm output for .weak_def_can_be_hidden says "was a private external" even +## though it wasn't .private_extern: It was just .weak_def_can_be_hidden. +## This matches ld64. +# EXPORTS-NM: (__TEXT,__text) non-external (was a private external) _foo + +# WEAKBIND-NOT: __got +# WEAKBIND-NOT: __la_symbol_ptr + +## ld64 sets WEAKBIND and BINDS_TO_WEAK in the mach-o header even though there +## are no weak bindings or weak definitions after processing the autohide. That +## looks like a bug in ld64 (?) If you change lit.local.cfg to set %lld to ld to +## test compatibility, you have to add some arbitrary suffix to these two lines: +# HEADERS-NOT: WEAK_DEFINES +# HEADERS-NOT: BINDS_TO_WEAK + +## Same behavior for a symbol that's both .weak_def_can_be_hidden and +## .private_extern. Think: Inline function compiled with +## -fvisibility-inlines-hidden. +# RUN: %lld -dylib -o %t/weak-autohide-foo-pe.dylib \ +# RUN: %t/weak-autohide-foo-pe.o %t/ref-foo.o +# RUN: llvm-objdump --syms --exports-trie %t/weak-autohide-foo-pe.dylib | \ +# RUN: FileCheck --check-prefix=EXPORTS %s +# RUN: llvm-nm -m %t/weak-autohide-foo-pe.dylib | \ +# RUN: FileCheck --check-prefix=EXPORTS-NM %s +# RUN: llvm-objdump --macho --bind --weak-bind %t/weak-autohide-foo-pe.dylib | \ +# RUN: FileCheck --check-prefix=WEAKBIND %s +# RUN: llvm-objdump --macho --private-header %t/weak-autohide-foo-pe.dylib | \ +# RUN: FileCheck --check-prefix=HEADERS %s + +## In fact, a regular weak symbol that's .private_extern behaves the same +## as well. +# RUN: %lld -dylib -o %t/weak-foo-pe.dylib %t/weak-foo-pe.o %t/ref-foo.o +# RUN: llvm-objdump --syms --exports-trie %t/weak-foo-pe.dylib | \ +# RUN: FileCheck --check-prefix=EXPORTS %s +# RUN: llvm-nm -m %t/weak-foo-pe.dylib | \ +# RUN: FileCheck --check-prefix=EXPORTS-NM %s +# RUN: llvm-objdump --macho --bind --weak-bind %t/weak-foo-pe.dylib | \ +# RUN: FileCheck --check-prefix=WEAKBIND %s +# RUN: llvm-objdump --macho --private-header %t/weak-foo-pe.dylib | \ +# RUN: FileCheck --check-prefix=HEADERS %s + +## Combining a regular weak_definition with a weak_def_can_be_hidden produces +## a regular weak external. +# RUN: %lld -dylib -o %t/weak-foo.dylib -lSystem \ +# RUN: %t/weak-autohide-foo.o %t/weak-foo.o %t/ref-foo.o +# RUN: llvm-objdump --syms --exports-trie %t/weak-foo.dylib | \ +# RUN: FileCheck --check-prefix=WEAK %s +# RUN: llvm-nm -m %t/weak-foo.dylib | \ +# RUN: FileCheck --check-prefix=WEAK-NM %s +# RUN: llvm-objdump --macho --bind --weak-bind %t/weak-foo.dylib | \ +# RUN: FileCheck --check-prefix=WEAK-WEAKBIND %s +# RUN: llvm-objdump --macho --private-header %t/weak-foo.dylib | \ +# RUN: FileCheck --check-prefix=WEAK-HEADERS %s +# WEAK-LABEL: SYMBOL TABLE: +# WEAK-DAG: [[#%x, FOO_ADDR:]] w {{.*}} _foo +# WEAK-LABEL: Exports trie: +# WEAK-DAG: 0x{{0*}}[[#%X, FOO_ADDR]] _foo +# WEAK-NM: (__TEXT,__text) weak external _foo +# WEAK-WEAKBIND: __la_symbol_ptr 0x{{.*}} pointer 0 _foo +# WEAK-HEADERS: WEAK_DEFINES +# WEAK-HEADERS: BINDS_TO_WEAK + +#--- weak-foo.s +.globl _foo +.weak_definition _foo +_foo: + retq + +#--- weak-autohide-foo.s +.globl _foo +.weak_def_can_be_hidden _foo +_foo: + retq + +#--- weak-foo-pe.s +.private_extern _foo +.globl _foo +.weak_definition _foo +_foo: + retq + +#--- weak-autohide-foo-pe.s +.private_extern _foo +.globl _foo +.weak_def_can_be_hidden _foo +_foo: + retq + +#--- ref-foo.s +.globl _bar +_bar: + callq _foo + retq