Index: lld/MachO/Driver.cpp =================================================================== --- lld/MachO/Driver.cpp +++ lld/MachO/Driver.cpp @@ -685,9 +685,6 @@ warn(Twine("unknown --icf=OPTION `") + icfLevelStr + "', defaulting to `none'"); icfLevel = ICFLevel::none; - } else if (icfLevel == ICFLevel::safe) { - warn(Twine("`--icf=safe' is not yet implemented, reverting to `none'")); - icfLevel = ICFLevel::none; } return icfLevel; } @@ -1509,8 +1506,12 @@ // ICF assumes that all literals have been folded already, so we must run // foldIdenticalLiterals before foldIdenticalSections. foldIdenticalLiterals(); - if (config->icfLevel != ICFLevel::none) + if (config->icfLevel != ICFLevel::none) { + if (config->icfLevel == ICFLevel::safe) { + markAddrSigSymbols(inputFiles, &*symtab, &*config); + } foldIdenticalSections(); + } // Write to an output file. if (target->wordSize == 8) Index: lld/MachO/ICF.h =================================================================== --- lld/MachO/ICF.h +++ lld/MachO/ICF.h @@ -9,12 +9,20 @@ #ifndef LLD_MACHO_ICF_H #define LLD_MACHO_ICF_H +#include "InputFiles.h" #include "lld/Common/LLVM.h" +#include "llvm/ADT/SetVector.h" #include namespace lld { namespace macho { +struct Configuration; +class SymbolTable; +class Symbol; +void markAddrSigSymbols(llvm::SetVector &inputFiles, + SymbolTable *symtab, Configuration *config); +void markSymAsAddrSig(Symbol *s); void foldIdenticalSections(); } // namespace macho Index: lld/MachO/ICF.cpp =================================================================== --- lld/MachO/ICF.cpp +++ lld/MachO/ICF.cpp @@ -8,11 +8,14 @@ #include "ICF.h" #include "ConcatOutputSection.h" +#include "Config.h" #include "InputSection.h" +#include "SymbolTable.h" #include "Symbols.h" #include "UnwindInfoSection.h" #include "lld/Common/CommonLinkerContext.h" +#include "llvm/Support/LEB128.h" #include "llvm/Support/Parallel.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/xxhash.h" @@ -354,6 +357,51 @@ } } +void macho::markSymAsAddrSig(Symbol *s) { + if (auto *d = dyn_cast_or_null(s)) { + if (d->isec) { + d->isec->keepUnique = true; + } + } +} + +void macho::markAddrSigSymbols(SetVector &inputFiles, + SymbolTable *symtab, Configuration *config) { + // Symbols in the dynsym could be address-significant in other executables + // or DSOs, so we conservatively mark them as address-significant. + for (Symbol *sym : symtab->getSymbols()) { + Defined *def = dyn_cast_or_null(sym); + if (def && def->referencedDynamically) { + markSymAsAddrSig(sym); + } + } + + for (InputFile *file : inputFiles) { + auto *obj = dyn_cast_or_null(file); + if (!obj) + continue; + + auto *addrSigSection = obj->addrSigSection; + if (!addrSigSection) + continue; + assert(addrSigSection->subsections.size() == 1); + + auto *subSection = &addrSigSection->subsections[0]; + auto &contents = subSection->isec->data; + + const uint8_t *pData = contents.begin(); + while (pData != contents.end()) { + unsigned size; + const char *err; + uint32_t symIndex = decodeULEB128(pData, &size, contents.end(), &err); + if (err) + fatal(toString(file) + ": could not decode addrsig section: " + err); + markSymAsAddrSig(obj->symbols[symIndex]); + pData += size; + } + } +} + void macho::foldIdenticalSections() { TimeTraceScope timeScope("Fold Identical Code Sections"); // The ICF equivalence-class segregation algorithm relies on pre-computed @@ -376,6 +424,7 @@ // FIXME: consider non-code __text sections as hashable? bool isHashable = (isCodeSection(isec) || isCfStringSection(isec) || isClassRefsSection(isec)) && + (isec->keepUnique == false) && !isec->shouldOmitFromOutput() && sectionType(isec->getFlags()) == MachO::S_REGULAR; if (isHashable) { Index: lld/MachO/InputFiles.h =================================================================== --- lld/MachO/InputFiles.h +++ lld/MachO/InputFiles.h @@ -149,6 +149,7 @@ const uint32_t modTime; std::vector debugSections; std::vector callGraph; + Section *addrSigSection = nullptr; private: Section *compactUnwindSection = nullptr; Index: lld/MachO/InputFiles.cpp =================================================================== --- lld/MachO/InputFiles.cpp +++ lld/MachO/InputFiles.cpp @@ -355,6 +355,9 @@ // spurious duplicate symbol errors, we do not parse these sections. // TODO: Evaluate whether the bitcode metadata is needed. } else { + if (name == section_names::addrSig) + addrSigSection = sections.back(); + auto *isec = make(section, data, align); if (isDebugSection(isec->getFlags()) && isec->getSegName() == segment_names::dwarf) { Index: lld/MachO/InputSection.h =================================================================== --- lld/MachO/InputSection.h +++ lld/MachO/InputSection.h @@ -71,6 +71,7 @@ public: // is address assigned? bool isFinal = false; + bool keepUnique = false; uint32_t align = 1; OutputSection *parent = nullptr; @@ -328,6 +329,7 @@ constexpr const char unwindInfo[] = "__unwind_info"; constexpr const char weakBinding[] = "__weak_binding"; constexpr const char zeroFill[] = "__zerofill"; +constexpr const char addrSig[] = "__llvm_addrsig"; } // namespace section_names Index: lld/test/MachO/icf-options.s =================================================================== --- lld/test/MachO/icf-options.s +++ lld/test/MachO/icf-options.s @@ -8,8 +8,8 @@ # RUN: | FileCheck %s --check-prefix=DIAG-EMPTY --allow-empty # RUN: %lld -lSystem -no_deduplicate -o %t/no_dedup %t/main.o 2>&1 \ # RUN: | FileCheck %s --check-prefix=DIAG-EMPTY --allow-empty -# RUN: not %lld -lSystem --icf=safe -o %t/safe %t/main.o 2>&1 \ -# RUN: | FileCheck %s --check-prefix=DIAG-SAFE +# RUN: %lld -lSystem --icf=safe -o %t/safe %t/main.o 2>&1 \ +# RUN: | FileCheck %s --check-prefix=DIAG-SAFE --allow-empty # RUN: not %lld -lSystem --icf=junk -o %t/junk %t/main.o 2>&1 \ # RUN: | FileCheck %s --check-prefix=DIAG-JUNK # RUN: %lld -lSystem --icf=all -no_deduplicate -o %t/none2 %t/main.o 2>&1 \ @@ -18,7 +18,7 @@ # RUN: | FileCheck %s --check-prefix=DIAG-EMPTY --allow-empty # DIAG-EMPTY-NOT: {{.}} -# DIAG-SAFE: `--icf=safe' is not yet implemented, reverting to `none' +# DIAG-SAFE-NOT: {{.}} # DIAG-JUNK: unknown --icf=OPTION `junk', defaulting to `none' # RUN: llvm-objdump -d --syms %t/all | FileCheck %s --check-prefix=FOLD Index: lld/test/MachO/icf-safe.s =================================================================== --- /dev/null +++ lld/test/MachO/icf-safe.s @@ -0,0 +1,105 @@ +; RUN: rm -rf %t; mkdir %t +; RUN: llc -filetype=obj %s -o %t/icf-obj.o -enable-machine-outliner=never -mtriple arm64-apple-ios -addrsig +; RUN: %lld -arch arm64 -lSystem --icf=safe -o %t/icf-safe.exe %t/icf-obj.o +; RUN: %lld -arch arm64 -lSystem --icf=all -o %t/icf-all.exe %t/icf-obj.o +; RUN: llvm-objdump %t/icf-safe.exe -d | FileCheck %s --check-prefix=ICFSAFE +; RUN: llvm-objdump %t/icf-all.exe -d | FileCheck %s --check-prefix=ICFALL + +; ICFSAFE-LABEL: <_main>: +; ICFSAFE: bl 0x[[#%x,REF_01:]] +; ICFSAFE-NEXT: bl 0x[[#%x,REF_02:]] +; ICFSAFE-NEXT: bl 0x[[#%x,REF_01:]] +; ICFSAFE-NEXT: bl 0x[[#%x,REF_01:]] +; ICFSAFE-NEXT: bl 0x[[#%x,REF_01:]] + +; ICFALL-LABEL: <_main>: +; ICFALL: bl 0x[[#%x,REF_01:]] +; ICFALL-NEXT: bl 0x[[#%x,REF_01:]] +; ICFALL-NEXT: bl 0x[[#%x,REF_01:]] +; ICFALL-NEXT: bl 0x[[#%x,REF_01:]] +; ICFALL-NEXT: bl 0x[[#%x,REF_01:]] + + +target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" +target triple = "arm64-apple-ios7.0.0" + +@nr1 = local_unnamed_addr global i32 0 +@nr2 = local_unnamed_addr global i32 0 +@nr3 = local_unnamed_addr global i32 0 + +; Function Attrs: minsize mustprogress nofree noinline norecurse nosync nounwind optsize readonly ssp willreturn uwtable +define i32 @func01() local_unnamed_addr #0 { +entry: + %0 = load i32, ptr @nr1 + %1 = load i32, ptr @nr2 + %2 = load i32, ptr @nr3 + %mul = mul nsw i32 %2, %1 + %add = add nsw i32 %mul, %0 + ret i32 %add +} + +; Function Attrs: minsize mustprogress nofree noinline norecurse nosync nounwind optsize readonly ssp willreturn uwtable +define i32 @func02() #0 { +entry: + %0 = load i32, ptr @nr1 + %1 = load i32, ptr @nr2 + %2 = load i32, ptr @nr3 + %mul = mul nsw i32 %2, %1 + %add = add nsw i32 %mul, %0 + ret i32 %add +} + +; Function Attrs: minsize mustprogress nofree noinline norecurse nosync nounwind optsize readonly ssp willreturn uwtable +define i32 @func03() local_unnamed_addr #0 { +entry: + %0 = load i32, ptr @nr1 + %1 = load i32, ptr @nr2 + %2 = load i32, ptr @nr3 + %mul = mul nsw i32 %2, %1 + %add = add nsw i32 %mul, %0 + ret i32 %add +} + +; Function Attrs: minsize mustprogress nofree noinline norecurse nosync nounwind optsize readonly ssp willreturn uwtable +define i32 @func04() local_unnamed_addr #0 { +entry: + %0 = load i32, ptr @nr1 + %1 = load i32, ptr @nr2 + %2 = load i32, ptr @nr3 + %mul = mul nsw i32 %2, %1 + %add = add nsw i32 %mul, %0 + ret i32 %add +} + +; Function Attrs: minsize mustprogress nofree noinline norecurse nosync nounwind optsize readonly ssp willreturn uwtable +define i32 @func05() local_unnamed_addr #0 { +entry: + %0 = load i32, ptr @nr1 + %1 = load i32, ptr @nr2 + %2 = load i32, ptr @nr3 + %mul = mul nsw i32 %2, %1 + %add = add nsw i32 %mul, %0 + ret i32 %add +} + +; Function Attrs: minsize nofree norecurse nounwind optsize ssp uwtable +define i32 @main() local_unnamed_addr #1 { +entry: + %call = tail call i32 @func01() #2 + %call1 = tail call i32 @func02() #2 + %call4 = tail call i32 @func03() #2 + %call6 = tail call i32 @func04() #2 + %call8 = tail call i32 @func05() #2 + ret i32 0 +} + +; Function Attrs: minsize nofree norecurse nounwind optsize ssp uwtable +define void @_start() local_unnamed_addr #1 { +entry: + %call = tail call i32 @main() #2 + ret void +} + +attributes #0 = { minsize mustprogress nofree noinline norecurse nosync nounwind optsize readonly ssp willreturn uwtable "frame-pointer"="non-leaf" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-a7" "target-features"="+aes,+crypto,+fp-armv8,+neon,+sha2,+v8a,+zcm,+zcz" } +attributes #1 = { minsize nofree norecurse nounwind optsize ssp uwtable "frame-pointer"="non-leaf" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="apple-a7" "target-features"="+aes,+crypto,+fp-armv8,+neon,+sha2,+v8a,+zcm,+zcz" } +attributes #2 = { minsize optsize }