diff --git a/lld/MachO/ConcatOutputSection.cpp b/lld/MachO/ConcatOutputSection.cpp --- a/lld/MachO/ConcatOutputSection.cpp +++ b/lld/MachO/ConcatOutputSection.cpp @@ -329,7 +329,7 @@ thunkName, /*file=*/nullptr, thunkInfo.isec, /*value=*/0, /*size=*/thunkSize, /*isWeakDef=*/false, /*isPrivateExtern=*/true, /*isThumb=*/false, /*isReferencedDynamically=*/false, - /*noDeadStrip=*/false); + /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false); thunkInfo.sym->used = true; target->populateThunk(thunkInfo.isec, funcSym); finalizeOne(thunkInfo.isec); diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -1465,8 +1465,16 @@ StringRef symbolName = defined->getName(); if (config->exportedSymbols.match(symbolName)) { if (defined->privateExtern) { - warn("cannot export hidden symbol " + symbolName + - "\n>>> defined in " + toString(defined->getFile())); + if (!defined->weakDefCanBeHidden) + warn("cannot export hidden symbol " + symbolName + + "\n>>> defined in " + toString(defined->getFile())); + else + // weak_def_can_be_hidden symbols behaves similarly to + // private_extern symbols in most cases, except for when + // it is explicitly exported. + // LD64 allows exporting the former, but not the latter. + // So we do the same here. + defined->privateExtern = false; } } else { defined->privateExtern = true; diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -567,16 +567,24 @@ // 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; - + // difference between a symbol that's isWeakDefCanBeHidden(autohide) or one + // that's privateExtern -- neither makes it into the dynamic symbol table, + // unless the autohide symbol is explicitly exported. + // But if a symbol is both privateExtern and autohide then it can't + // be exported. + // So we nullify the autohide flag when privateExtern is present + // and promote the symbol to privateExtern when it is not already. + if (isWeakDefCanBeHidden) { + if (isPrivateExtern) + isWeakDefCanBeHidden = false; + else + isPrivateExtern = true; + } return symtab->addDefined( name, isec->getFile(), isec, value, size, sym.n_desc & N_WEAK_DEF, isPrivateExtern, sym.n_desc & N_ARM_THUMB_DEF, - sym.n_desc & REFERENCED_DYNAMICALLY, sym.n_desc & N_NO_DEAD_STRIP); + sym.n_desc & REFERENCED_DYNAMICALLY, sym.n_desc & N_NO_DEAD_STRIP, + isWeakDefCanBeHidden); } assert(!isWeakDefCanBeHidden && "weak_def_can_be_hidden on already-hidden symbol?"); @@ -596,7 +604,8 @@ return symtab->addDefined( name, file, nullptr, sym.n_value, /*size=*/0, /*isWeakDef=*/false, sym.n_type & N_PEXT, sym.n_desc & N_ARM_THUMB_DEF, - /*isReferencedDynamically=*/false, sym.n_desc & N_NO_DEAD_STRIP); + /*isReferencedDynamically=*/false, sym.n_desc & N_NO_DEAD_STRIP, + /*isWeakDefCanBeHidden=*/false); } return make(name, file, nullptr, sym.n_value, /*size=*/0, /*isWeakDef=*/false, @@ -1422,7 +1431,8 @@ /*size=*/0, objSym.isWeak(), isPrivateExtern, /*isThumb=*/false, /*isReferencedDynamically=*/false, - /*noDeadStrip=*/false); + /*noDeadStrip=*/false, + /*isWeakDefCanBeHidden=*/false); } BitcodeFile::BitcodeFile(MemoryBufferRef mb, StringRef archiveName, diff --git a/lld/MachO/MarkLive.cpp b/lld/MachO/MarkLive.cpp --- a/lld/MachO/MarkLive.cpp +++ b/lld/MachO/MarkLive.cpp @@ -67,7 +67,7 @@ // FIXME: Instead of doing this here, maybe the Driver code doing // the matching should add them to explicitUndefineds? Then the // explicitUndefineds code below would handle this automatically. - assert(!defined->privateExtern && + assert((!defined->privateExtern || defined->weakDefCanBeHidden) && "should have been rejected by driver"); addSym(defined); continue; diff --git a/lld/MachO/SymbolTable.h b/lld/MachO/SymbolTable.h --- a/lld/MachO/SymbolTable.h +++ b/lld/MachO/SymbolTable.h @@ -40,7 +40,8 @@ Defined *addDefined(StringRef name, InputFile *, InputSection *, uint64_t value, uint64_t size, bool isWeakDef, bool isPrivateExtern, bool isThumb, - bool isReferencedDynamically, bool noDeadStrip); + bool isReferencedDynamically, bool noDeadStrip, + bool isWeakDefCanBeHidden); Symbol *addUndefined(StringRef name, InputFile *, bool isWeakRef); diff --git a/lld/MachO/SymbolTable.cpp b/lld/MachO/SymbolTable.cpp --- a/lld/MachO/SymbolTable.cpp +++ b/lld/MachO/SymbolTable.cpp @@ -49,8 +49,8 @@ InputSection *isec, uint64_t value, uint64_t size, bool isWeakDef, bool isPrivateExtern, bool isThumb, - bool isReferencedDynamically, - bool noDeadStrip) { + bool isReferencedDynamically, bool noDeadStrip, + bool isWeakDefCanBeHidden) { Symbol *s; bool wasInserted; bool overridesWeakDef = false; @@ -66,6 +66,7 @@ // See further comment in createDefined() in InputFiles.cpp if (defined->isWeakDef()) { defined->privateExtern &= isPrivateExtern; + defined->weakDefCanBeHidden |= isWeakDefCanBeHidden; defined->referencedDynamically |= isReferencedDynamically; defined->noDeadStrip |= noDeadStrip; } @@ -100,6 +101,7 @@ s, name, file, isec, value, size, isWeakDef, /*isExternal=*/true, isPrivateExtern, isThumb, isReferencedDynamically, noDeadStrip); defined->overridesWeakDef = overridesWeakDef; + defined->weakDefCanBeHidden = isWeakDefCanBeHidden; return defined; } @@ -195,10 +197,11 @@ uint64_t value, bool isPrivateExtern, bool includeInSymtab, bool referencedDynamically) { - Defined *s = addDefined(name, nullptr, isec, value, /*size=*/0, - /*isWeakDef=*/false, isPrivateExtern, - /*isThumb=*/false, referencedDynamically, - /*noDeadStrip=*/false); + Defined *s = + addDefined(name, nullptr, isec, value, /*size=*/0, + /*isWeakDef=*/false, isPrivateExtern, + /*isThumb=*/false, referencedDynamically, + /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false); s->includeInSymtab = includeInSymtab; return s; } diff --git a/lld/MachO/Symbols.h b/lld/MachO/Symbols.h --- a/lld/MachO/Symbols.h +++ b/lld/MachO/Symbols.h @@ -160,6 +160,8 @@ // to the output. bool noDeadStrip : 1; + bool weakDefCanBeHidden : 1; + private: const bool weakDef : 1; const bool external : 1; diff --git a/lld/test/MachO/export-options.s b/lld/test/MachO/export-options.s --- a/lld/test/MachO/export-options.s +++ b/lld/test/MachO/export-options.s @@ -119,6 +119,21 @@ # GLOBBY-DAG: globby_also # GLOBBY-NOT: literal_only +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/autohide.s -o %t/autohide.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/autohide-private-extern.s -o %t/autohide-private-extern.o +## Test that we can export the autohide symbol but not when it's also +## private-extern +# RUN: %lld -dylib -exported_symbol "_foo" %t/autohide.o -o %t/exp-autohide.dylib +# RUN: llvm-nm -g %t/exp-autohide.dylib | FileCheck %s --check-prefix=EXP-AUTOHIDE +# EXP-AUTOHIDE: T _foo + +# RUN: not %lld -dylib -exported_symbol "_foo" %t/autohide-private-extern.o \ +# RUN: -o /dev/null 2>&1 | FileCheck %s --check-prefix=AUTOHIDE-PRIVATE +# AUTOHIDE-PRIVATE: error: cannot export hidden symbol _foo + + #--- default.s .globl _keep_globl, _hide_globl @@ -164,3 +179,16 @@ l?ter[aeiou]l_*[^y] # comment *gl?bby_* + +#--- autohide.s +.globl _foo +.weak_def_can_be_hidden _foo +_foo: + retq + +#--- autohide-private-extern.s +.globl _foo +.weak_def_can_be_hidden _foo +.private_extern _foo +_foo: + retq