Index: ELF/Config.h =================================================================== --- ELF/Config.h +++ ELF/Config.h @@ -126,6 +126,7 @@ bool HasDynamicList = false; bool HasDynSymTab; bool ICF; + bool MergeArmExidx; bool MipsN32Abi = false; bool NoGnuUnique; bool NoUndefinedVersion; Index: ELF/Driver.cpp =================================================================== --- ELF/Driver.cpp +++ ELF/Driver.cpp @@ -625,6 +625,8 @@ Config->LTOPartitions = args::getInteger(Args, OPT_lto_partitions, 1); Config->MapFile = Args.getLastArgValue(OPT_Map); Config->NoGnuUnique = Args.hasArg(OPT_no_gnu_unique); + Config->MergeArmExidx = + Args.hasFlag(OPT_merge_exidx_entries, OPT_no_merge_exidx_entries, true); Config->NoUndefinedVersion = Args.hasArg(OPT_no_undefined_version); Config->NoinhibitExec = Args.hasArg(OPT_noinhibit_exec); Config->Nostdlib = Args.hasArg(OPT_nostdlib); Index: ELF/Options.td =================================================================== --- ELF/Options.td +++ ELF/Options.td @@ -160,6 +160,9 @@ defm Map: Eq<"Map">, HelpText<"Print a link map to the specified file">; +def merge_exidx_entries: F<"merge-exidx-entries">, + HelpText<"Enable merging .ARM.exidx entries">; + def nostdlib: F<"nostdlib">, HelpText<"Only search directories specified on the command line">; @@ -193,6 +196,9 @@ def no_gnu_unique: F<"no-gnu-unique">, HelpText<"Disable STB_GNU_UNIQUE symbol binding">; +def no_merge_exidx_entries: F<"no-merge-exidx-entries">, + HelpText<"Disable merging .ARM.exidx entries">; + def no_threads: F<"no-threads">, HelpText<"Do not run the linker multi-threaded">; Index: ELF/Writer.cpp =================================================================== --- ELF/Writer.cpp +++ ELF/Writer.cpp @@ -1163,6 +1163,60 @@ return LA->OutSecOff < LB->OutSecOff; } +// This function is used by the --merge-exidx-entries to detect duplicate +// .ARM.exidx sections. It is Arm only. +// +// The .ARM.exidx section is of the form: +// | PREL31 offset to function | Unwind instructions for function | +// where the unwind instructions are either a small number of unwind +// instructions inlined into the table entry, the special CANT_UNWIND value of +// 0x1 or a PREL31 offset into a .ARM.extab Section that contains unwind +// instructions. +// +// We return true if all the unwind instructions in the .ARM.exidx entries of +// Cur can be merged into the last entry of Prev. +static bool isDuplicateArmExidxSec(InputSection *Prev, InputSection *Cur) { + + // References to .ARM.Extab Sections have bit 31 clear and are not the + // special EXIDX_CANTUNWIND bit-pattern. + auto IsExtabRef = [](uint32_t Unwind) { + return (Unwind & 0x80000000) == 0 && Unwind != 0x1; + }; + + struct ExidxEntry { + uint32_t Fn; + uint32_t Unwind; + }; + + // Get the last table Entry from the previous .ARM.exidx section. + const ExidxEntry PrevEntry = *reinterpret_cast( + Prev->Data.data() + Prev->getSize() - sizeof(ExidxEntry)); + if (IsExtabRef(PrevEntry.Unwind)) + return false; + + // We consider the unwind instructions of an .ARM.exidx table entry + // a duplicate if the previous unwind instructions if: + // - Both are the special EXIDX_CANTUNWIND. + // - Both are the same inline unwind instructions. + // We do not attempt to follow and check links into .ARM.extab tables as + // consecutive identical entries are rare and the effort to check that they + // are identical is high. + + if (isa(Cur)) + // Exidx sentinel section has implicit EXIDX_CANTUNWIND; + return PrevEntry.Unwind == 0x1; + + ArrayRef Entries( + reinterpret_cast(Cur->Data.data()), + Cur->getSize() / sizeof(ExidxEntry)); + for (const ExidxEntry Entry : Entries) + if (IsExtabRef(Entry.Unwind) || Entry.Unwind != PrevEntry.Unwind) + return false; + // All table entries in this .ARM.exidx Section can be merged into the + // previous Section. + return true; +} + template void Writer::resolveShfLinkOrder() { for (OutputSection *Sec : OutputSections) { if (!(Sec->Flags & SHF_LINK_ORDER)) @@ -1181,8 +1235,40 @@ } } std::stable_sort(Sections.begin(), Sections.end(), compareByFilePosition); + + bool RemoveSections = false; + if (Config->MergeArmExidx && !Config->Relocatable && + Config->EMachine == EM_ARM && Sec->Type == SHT_ARM_EXIDX) { + // The EHABI for the Arm Architecture permits consecutive identical + // table entries to be merged. We use a simple implementation that + // removes a .ARM.exidx Input Section if it can be merged into the + // previous one. This does not require any rewriting of InputSection + // contents but misses opportunities for fine grained deduplication where + // only a subset of the InputSection contents can be merged. + int Cur = 1; + int Prev = 0; + int N = Sections.size(); + while (Cur < N) { + if (isDuplicateArmExidxSec(Sections[Prev], Sections[Cur])) { + Sections[Cur] = nullptr; + RemoveSections = true; + } else + Prev = Cur; + ++Cur; + } + } + for (int I = 0, N = Sections.size(); I < N; ++I) *ScriptSections[I] = Sections[I]; + + // Remove the Sections we marked as duplicate earlier. + if (!RemoveSections) + continue; + for (BaseCommand *Base : Sec->SectionCommands) + if (auto *ISD = dyn_cast(Base)) + ISD->Sections.erase( + std::remove(ISD->Sections.begin(), ISD->Sections.end(), nullptr), + ISD->Sections.end()); } } Index: test/ELF/arm-exidx-dedup.s =================================================================== --- /dev/null +++ test/ELF/arm-exidx-dedup.s @@ -0,0 +1,124 @@ +// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t +// RUN: ld.lld %t --no-merge-exidx-entries -o %t2 2>&1 +// RUN: llvm-objdump -s %t2 | FileCheck --check-prefix CHECK-DUPS %s +// RUN: ld.lld %t -o %t3 2>&1 +// RUN: llvm-objdump -s %t3 | FileCheck %s +// REQUIRES: arm +// Test that lld can at least remove duplicate .ARM.exidx sections. A more +// fine grained implementation will be able to remove duplicate entries within +// a .ARM.exidx section. + +// With duplicate entries +// CHECK-DUPS: Contents of section .ARM.exidx: +// CHECK-DUPS-NEXT: 100d4 2c0f0000 01000000 280f0000 01000000 +// CHECK-DUPS-NEXT: 100e4 240f0000 01000000 200f0000 01000000 +// CHECK-DUPS-NEXT: 100f4 1c0f0000 08849780 180f0000 08849780 +// CHECK-DUPS-NEXT: 10104 140f0000 08849780 100f0000 14000000 +// CHECK-DUPS-NEXT: 10114 0c0f0000 18000000 080f0000 01000000 + +// After duplicate entry removal +// CHECK: Contents of section .ARM.exidx: +// CHECK-NEXT: 100d4 2c0f0000 01000000 340f0000 08849780 +// CHECK-NEXT: 100e4 380f0000 14000000 340f0000 18000000 +// CHECK-NEXT: 100f4 300f0000 01000000 + .syntax unified + + // Expect 1 EXIDX_CANTUNWIND entry. + .section .text.00, "ax", %progbits + .globl _start +_start: + .fnstart + bx lr + .cantunwind + .fnend + + // Expect .ARM.exidx.text.01 to be identical to .ARM.exidx.text.00 + .section .text.01, "ax", %progbits + .globl f1 +f1: + .fnstart + bx lr + .cantunwind + .fnend + + // Expect 2 EXIDX_CANTUNWIND entries, these can be duplicated into + // .ARM.exid.text.00 + .section .text.02, "ax", %progbits + .globl f2 +f2: + .fnstart + bx lr + .cantunwind + .fnend + + .globl f3 +f3: + .fnstart + bx lr + .cantunwind + .fnend + + // Expect inline unwind instructions, not a duplicate of previous entry. + .section .text.03, "ax", %progbits + .global f4 +f4: + .fnstart + bx lr + .save {r7, lr} + .setfp r7, sp, #0 + .fnend + + // Expect 2 inline unwind entries that are a duplicate of + // .ARM.exidx.text.03 + .section .text.04, "ax", %progbits + .global f5 +f5: + .fnstart + bx lr + .save {r7, lr} + .setfp r7, sp, #0 + .fnend + + .global f6 +f6: + .fnstart + bx lr + .save {r7, lr} + .setfp r7, sp, #0 + .fnend + + // Expect a section with a reference to an .ARM.extab. Not a duplicate + // of previous inline table entry. + .section .text.05, "ax",%progbits + .global f7 +f7: + .fnstart + bx lr + .personality __gxx_personality_v0 + .handlerdata + .long 0 + .fnend + + // Expect a reference to an identical .ARM.extab. We do not try to + // deduplicate references to .ARM.extab sections. + .section .text.06, "ax",%progbits + .global f8 +f8: + .fnstart + bx lr + .personality __gxx_personality_v0 + .handlerdata + .long 0 + .fnend + + // Dummy implementation of personality routines to satisfy reference from + // exception tables + .section .text.__gcc_personality_v0, "ax", %progbits + .global __gxx_personality_v0 +__gxx_personality_v0: + bx lr + + .section .text.__aeabi_unwind_cpp_pr0, "ax", %progbits + .global __aeabi_unwind_cpp_pr0 +__aeabi_unwind_cpp_pr0: + bx lr Index: test/ELF/arm-exidx-gc.s =================================================================== --- test/ELF/arm-exidx-gc.s +++ test/ELF/arm-exidx-gc.s @@ -1,5 +1,5 @@ // RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t -// RUN: ld.lld %t -o %t2 --gc-sections 2>&1 +// RUN: ld.lld %t --no-merge-exidx-entries -o %t2 --gc-sections 2>&1 // RUN: llvm-objdump -d -triple=armv7a-none-linux-gnueabi %t2 | FileCheck %s // RUN: llvm-objdump -s -triple=armv7a-none-linux-gnueabi %t2 | FileCheck -check-prefix=CHECK-EXIDX %s // REQUIRES: arm Index: test/ELF/arm-exidx-order.s =================================================================== --- test/ELF/arm-exidx-order.s +++ test/ELF/arm-exidx-order.s @@ -1,6 +1,6 @@ // RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t // RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %S/Inputs/arm-exidx-cantunwind.s -o %tcantunwind -// RUN: ld.lld %t %tcantunwind -o %t2 2>&1 +// RUN: ld.lld --no-merge-exidx-entries %t %tcantunwind -o %t2 2>&1 // RUN: llvm-objdump -d -triple=armv7a-none-linux-gnueabi %t2 | FileCheck %s // RUN: llvm-objdump -s -triple=armv7a-none-linux-gnueabi %t2 | FileCheck -check-prefix=CHECK-EXIDX %s // RUN: llvm-readobj --program-headers --sections %t2 | FileCheck -check-prefix=CHECK-PT %s @@ -8,7 +8,7 @@ // RUN: echo "SECTIONS { \ // RUN: .text 0x11000 : { *(.text*) } \ // RUN: .ARM.exidx : { *(.ARM.exidx) } } " > %t.script -// RUN: ld.lld --script %t.script %tcantunwind %t -o %t3 2>&1 +// RUN: ld.lld --no-merge-exidx-entries --script %t.script %tcantunwind %t -o %t3 2>&1 // RUN: llvm-objdump -d -triple=armv7a-none-linux-gnueabi %t3 | FileCheck -check-prefix=CHECK-SCRIPT %s // RUN: llvm-objdump -s -triple=armv7a-none-linux-gnueabi %t3 | FileCheck -check-prefix=CHECK-SCRIPT-EXIDX %s // REQUIRES: arm Index: test/ELF/arm-exidx-sentinel-orphan.s =================================================================== --- test/ELF/arm-exidx-sentinel-orphan.s +++ test/ELF/arm-exidx-sentinel-orphan.s @@ -4,7 +4,7 @@ // RUN: echo "SECTIONS { \ // RUN: .text 0x11000 : { *(.text*) } \ // RUN: } " > %t.script -// RUN: ld.lld --script %t.script %t -o %t2 +// RUN: ld.lld --no-merge-exidx-entries --script %t.script %t -o %t2 // RUN: llvm-objdump -s -triple=armv7a-none-linux-gnueabi %t2 | FileCheck %s // REQUIRES: arm Index: test/ELF/arm-static-defines.s =================================================================== --- test/ELF/arm-static-defines.s +++ test/ELF/arm-static-defines.s @@ -1,5 +1,5 @@ // RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t -// RUN: ld.lld %t --static -o %t2 2>&1 +// RUN: ld.lld --no-merge-exidx-entries %t --static -o %t2 2>&1 // RUN: llvm-readobj --symbols %t2 | FileCheck %s // REQUIRES: arm