Index: lld/trunk/ELF/Config.h =================================================================== --- lld/trunk/ELF/Config.h +++ lld/trunk/ELF/Config.h @@ -127,6 +127,7 @@ bool HasDynSymTab; bool ICF; bool ICFData; + bool MergeArmExidx; bool MipsN32Abi = false; bool NoGnuUnique; bool NoUndefinedVersion; Index: lld/trunk/ELF/Driver.cpp =================================================================== --- lld/trunk/ELF/Driver.cpp +++ lld/trunk/ELF/Driver.cpp @@ -626,6 +626,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: lld/trunk/ELF/Options.td =================================================================== --- lld/trunk/ELF/Options.td +++ lld/trunk/ELF/Options.td @@ -163,6 +163,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">; @@ -196,6 +199,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: lld/trunk/ELF/Writer.cpp =================================================================== --- lld/trunk/ELF/Writer.cpp +++ lld/trunk/ELF/Writer.cpp @@ -1158,6 +1158,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 { + ulittle32_t Fn; + ulittle32_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)) @@ -1176,8 +1230,36 @@ } } std::stable_sort(Sections.begin(), Sections.end(), compareByFilePosition); + + 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; + 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. + 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: lld/trunk/test/ELF/arm-exidx-dedup.s =================================================================== --- lld/trunk/test/ELF/arm-exidx-dedup.s +++ lld/trunk/test/ELF/arm-exidx-dedup.s @@ -0,0 +1,126 @@ +// RUN: llvm-mc -filetype=obj -triple=armv7a-none-linux-gnueabi %s -o %t +// RUN: ld.lld %t --no-merge-exidx-entries -o %t2 +// RUN: llvm-objdump -s %t2 | FileCheck --check-prefix CHECK-DUPS %s +// RUN: ld.lld %t -o %t3 +// 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 +// CHECK-DUPS-NEXT: Contents of section .ARM.extab: + +// 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 +// CHECK-NEXT: Contents of section .ARM.extab: + .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: lld/trunk/test/ELF/arm-exidx-gc.s =================================================================== --- lld/trunk/test/ELF/arm-exidx-gc.s +++ lld/trunk/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: lld/trunk/test/ELF/arm-exidx-order.s =================================================================== --- lld/trunk/test/ELF/arm-exidx-order.s +++ lld/trunk/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: lld/trunk/test/ELF/arm-exidx-sentinel-orphan.s =================================================================== --- lld/trunk/test/ELF/arm-exidx-sentinel-orphan.s +++ lld/trunk/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: lld/trunk/test/ELF/arm-static-defines.s =================================================================== --- lld/trunk/test/ELF/arm-static-defines.s +++ lld/trunk/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 Index: lld/trunk/test/ELF/linkerscript/arm-exidx-sentinel-and-assignment.s =================================================================== --- lld/trunk/test/ELF/linkerscript/arm-exidx-sentinel-and-assignment.s +++ lld/trunk/test/ELF/linkerscript/arm-exidx-sentinel-and-assignment.s @@ -6,7 +6,7 @@ # RUN: }" > %t.script ## We used to crash if the last output section command for .ARM.exidx ## was anything but an input section description. -# RUN: ld.lld -T %t.script %t.o -shared -o %t.so +# RUN: ld.lld --no-merge-exidx-entries -T %t.script %t.o -shared -o %t.so # RUN: llvm-objdump -s -triple=armv7a-none-linux-gnueabi %t.so | FileCheck %s .syntax unified