diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -124,6 +124,7 @@ uint32_t dylibCompatibilityVersion = 0; uint32_t dylibCurrentVersion = 0; uint32_t timeTraceGranularity = 500; + unsigned optimize; std::string progName; // For `clang -arch arm64 -arch x86_64`, clang will: diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -1170,6 +1170,7 @@ symtab->addDynamicLookup(arg->getValue()); config->mapFile = args.getLastArgValue(OPT_map); + config->optimize = args::getInteger(args, OPT_O, 1); config->outputFile = args.getLastArgValue(OPT_o, "a.out"); config->finalOutput = args.getLastArgValue(OPT_final_output, config->outputFile); diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -71,6 +71,8 @@ def thinlto_cache_policy: Joined<["--"], "thinlto-cache-policy=">, HelpText<"Pruning policy for the ThinLTO cache">, Group; +def O : JoinedOrSeparate<["-"], "O">, + HelpText<"Optimize output file size">; // This is a complete Options.td compiled from Apple's ld(1) manpage // dated 2018-03-07 and cross checked with ld64 source code in repo diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -282,6 +282,7 @@ // scream instead of accidentally writing "valid" values. uint8_t opcode = 0xF0; uint64_t data = 0; + uint64_t consecutiveCount = 0; }; } // namespace @@ -297,46 +298,77 @@ OutputSegment *seg = osec->parent; uint64_t offset = osec->getSegmentOffset() + outSecOff; if (lastBinding.segment != seg) { - BindIR op = { - static_cast(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | - seg->index), // opcode - offset // data - }; - opcodes.push_back(op); + opcodes.push_back( + {static_cast(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | + seg->index), + offset}); lastBinding.segment = seg; lastBinding.offset = offset; } else if (lastBinding.offset != offset) { - BindIR op = { - static_cast(BIND_OPCODE_ADD_ADDR_ULEB), // opcode - offset - lastBinding.offset // data - }; - opcodes.push_back(op); + opcodes.push_back({BIND_OPCODE_ADD_ADDR_ULEB, offset - lastBinding.offset}); lastBinding.offset = offset; } if (lastBinding.addend != addend) { - BindIR op = { - static_cast(BIND_OPCODE_SET_ADDEND_SLEB), // opcode - static_cast(addend) // data - }; - opcodes.push_back(op); + opcodes.push_back( + {BIND_OPCODE_SET_ADDEND_SLEB, static_cast(addend)}); lastBinding.addend = addend; } - BindIR op = { - static_cast(BIND_OPCODE_DO_BIND), // opcode - 0 // data - }; - opcodes.push_back(op); + opcodes.push_back({BIND_OPCODE_DO_BIND, 0}); // DO_BIND causes dyld to both perform the binding and increment the offset lastBinding.offset += target->wordSize; } +static void optimizeOpcodes(std::vector &opcodes) { + // Pass 1: Combine bind/add pairs + size_t i; + int pWrite = 0; + for (i = 1; i < opcodes.size(); ++i, ++pWrite) { + if ((opcodes[i].opcode == BIND_OPCODE_ADD_ADDR_ULEB) && + (opcodes[i - 1].opcode == BIND_OPCODE_DO_BIND)) { + opcodes[pWrite].opcode = BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB; + opcodes[pWrite].data = opcodes[i].data; + ++i; + } else { + opcodes[pWrite] = opcodes[i - 1]; + } + } + if (i == opcodes.size()) + opcodes[pWrite] = opcodes[i - 1]; + opcodes.resize(pWrite + 1); + + // Pass 2: Compress two or more bind_add opcodes + pWrite = 0; + for (i = 1; i < opcodes.size(); ++i, ++pWrite) { + if ((opcodes[i].opcode == BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB) && + (opcodes[i - 1].opcode == BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB) && + (opcodes[i].data == opcodes[i - 1].data)) { + opcodes[pWrite].opcode = BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB; + opcodes[pWrite].consecutiveCount = 2; + opcodes[pWrite].data = opcodes[i].data; + ++i; + while (i < opcodes.size() && + (opcodes[i].opcode == BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB) && + (opcodes[i].data == opcodes[i - 1].data)) { + opcodes[pWrite].consecutiveCount++; + ++i; + } + } else { + opcodes[pWrite] = opcodes[i - 1]; + } + } + if (i == opcodes.size()) + opcodes[pWrite] = opcodes[i - 1]; + opcodes.resize(pWrite + 1); +} + static void flushOpcodes(const BindIR &op, raw_svector_ostream &os) { uint8_t opcode = op.opcode & BIND_OPCODE_MASK; switch (opcode) { case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: case BIND_OPCODE_ADD_ADDR_ULEB: + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: os << op.opcode; encodeULEB128(op.data, os); break; @@ -347,6 +379,11 @@ case BIND_OPCODE_DO_BIND: os << op.opcode; break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + os << op.opcode; + encodeULEB128(op.consecutiveCount, os); + encodeULEB128(op.data, os); + break; default: llvm_unreachable("cannot bind to an unrecognized symbol"); } @@ -446,6 +483,8 @@ encodeBinding(b.target.isec->parent, b.target.isec->getOffset(b.target.offset), b.addend, lastBinding, opcodes); + if (config->optimize > 1) + optimizeOpcodes(opcodes); for (const auto &op : opcodes) flushOpcodes(op, os); } @@ -478,6 +517,8 @@ encodeBinding(b.target.isec->parent, b.target.isec->getOffset(b.target.offset), b.addend, lastBinding, opcodes); + if (config->optimize > 1) + optimizeOpcodes(opcodes); for (const auto &op : opcodes) flushOpcodes(op, os); } diff --git a/lld/test/MachO/bind-opcodes.s b/lld/test/MachO/bind-opcodes.s --- a/lld/test/MachO/bind-opcodes.s +++ b/lld/test/MachO/bind-opcodes.s @@ -2,28 +2,83 @@ # RUN: rm -rf %t; split-file %s %t # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/foo.s -o %t/foo.o # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/test.s -o %t/test.o -# RUN: %lld -dylib %t/foo.o -o %t/libfoo.dylib -# RUN: %lld -lSystem %t/test.o %t/libfoo.dylib -o %t/test +# RUN: %lld -O2 -dylib %t/foo.o -o %t/libfoo.dylib +# RUN: %lld -O2 -lSystem %t/test.o %t/libfoo.dylib -o %t/test -## Make sure we emit exactly one BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM per -## symbol. -# RUN: obj2yaml %t/test | FileCheck %s --implicit-check-not BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM +## Test: +## 1/ We emit exactly one BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM per symbol. +## 2/ Combine BIND_OPCODE_DO_BIND and BIND_OPCODE_ADD_ADDR_ULEB pairs. +## 3/ Compact BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +# RUN: obj2yaml %t/test | FileCheck %s -# CHECK: Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM -# CHECK-NEXT: Imm: 0 -# CHECK-NEXT: Symbol: _foo - -# CHECK: Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM -# CHECK-NEXT: Imm: 0 -# CHECK-NEXT: Symbol: _bar +# CHECK: BindOpcodes: +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: _foo +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_TYPE_IMM +# CHECK-NEXT: Imm: 1 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_DYLIB_ORDINAL_IMM +# CHECK-NEXT: Imm: 2 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB +# CHECK-NEXT: Imm: 2 +# CHECK-NEXT: ULEBExtraData: [ 0x0 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: ULEBExtraData: [ 0x2, 0x8 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_ADDEND_SLEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: SLEBExtraData: [ 1 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: ULEBExtraData: [ 0x1008 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_ADDEND_SLEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: SLEBExtraData: [ 0 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: _bar +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_TYPE_IMM +# CHECK-NEXT: Imm: 1 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_ADD_ADDR_ULEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: ULEBExtraData: [ 0xFFFFFFFFFFFFEFD0 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: ULEBExtraData: [ 0x8 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: ULEBExtraData: [ 0x1008 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DONE +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: '' # RUN: llvm-objdump --macho --bind %t/test | FileCheck %s --check-prefix=BIND # BIND: Bind table: -# BIND-NEXT: segment section address type addend dylib symbol -# BIND-NEXT: __DATA __data {{.*}} pointer 0 libfoo _foo -# BIND-NEXT: __DATA __data {{.*}} pointer 0 libfoo _foo -# BIND-NEXT: __DATA __data {{.*}} pointer 0 libfoo _bar -# BIND-NEXT: __DATA __data {{.*}} pointer 0 libfoo _bar +# BIND-NEXT: segment section address type addend dylib symbol +# BIND-NEXT: __DATA __data 0x100001000 pointer 0 libfoo _foo +# BIND-NEXT: __DATA __data 0x100001010 pointer 0 libfoo _foo +# BIND-NEXT: __DATA __data 0x100001020 pointer 1 libfoo _foo +# BIND-NEXT: __DATA __data 0x100002030 pointer 0 libfoo _foo +# BIND-NEXT: __DATA __data 0x100001008 pointer 0 libfoo _bar +# BIND-NEXT: __DATA __data 0x100001018 pointer 0 libfoo _bar +# BIND-NEXT: __DATA __data 0x100002028 pointer 0 libfoo _bar # BIND-EMPTY: #--- foo.s @@ -39,6 +94,10 @@ .quad _bar .quad _foo .quad _bar +.quad _foo+1 +.zero 0x1000 +.quad _bar +.quad _foo .globl _main .text