Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions docs/userguide/documentation/linker_image_size_optimizations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
Linker Image Size Optimizations
===============================

.. contents::
:local:

Overview
--------
This chapter focuses on image-size reduction techniques that are controlled at
link time in ELD. In practice, best results come from using compiler and linker
options together:

* Compile with section granularity so the linker can drop unused code/data.
* Enable linker optimizations that deduplicate or discard content.
* Validate size deltas using map files and ELF section/symbol inspection.

Recommended baseline flow
-------------------------
For size-sensitive builds, start with:

1. Compile with ``-ffunction-sections -fdata-sections``.
2. Link with ``--gc-sections``.
3. Use ``--print-gc-sections`` during tuning to confirm what is removed.
4. Check output with ``readelf -S/-s`` and ``-Map`` reports.

Section garbage collection
--------------------------
``--gc-sections`` removes input sections that are not reachable from retained
roots (entry, undefined symbols requested on the command line, and other
link-relevant roots). This is usually the single highest-impact size option for
large applications and firmware images.

Useful companion options:

* ``--print-gc-sections``: print what got collected.
* ``--no-gc-sections``: disable GC (for bring-up/comparison).

Example:
::

clang -c a.c -o a.o -ffunction-sections -fdata-sections
clang -c b.c -o b.o -ffunction-sections -fdata-sections
ld.eld --gc-sections --print-gc-sections a.o b.o -o app.elf

String and constant merging controls
------------------------------------
ELD performs merge optimizations for mergeable data. These typically reduce
``.rodata`` size when payloads are duplicated across translation units.

What merge-strings does
^^^^^^^^^^^^^^^^^^^^^^^
String merging operates on mergeable string input sections (typically sections
with ``SHF_MERGE|SHF_STRINGS`` flags such as ``.rodata.str*``).

At a high level, ELD:

* builds mergeable string fragments from input sections,
* picks canonical string storage when contents are identical, and
* retargets merge-kind relocations to the canonical string location.

This reduces duplicated string bytes in the final image while preserving
semantic references.

What merge-data (merge-constants) does
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Constant-data merging operates on non-string mergeable constant sections
(for example, ``.rodata.cst*`` with ``SHF_MERGE``).

At a high level, ELD:

* identifies identical constant payloads (respecting merge semantics such as
entry size/alignment constraints),
* keeps one canonical constant payload, and
* retargets eligible merge-kind relocations to that canonical payload.

This reduces duplicate constant bytes in ``.rodata`` and related mergeable
constant sections.

Map file view for merge optimizations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In text map files, merged content is represented as a surviving canonical input
section entry plus comment lines for merged contributors.

Typical merged-constant pattern:
::

.rodata.cst4 <out_off> <size> <file2.o> #SHT_PROGBITS,SHF_ALLOC|SHF_MERGE,4
# .rodata.cst4 0x0 <file1.o>

Here, the non-comment line is the canonical contributor kept in layout order.
Comment-prefixed entries indicate inputs merged into that canonical storage.

For merge-strings, ``--MapDetail=show-strings`` adds string-content-oriented
details (for example, merged string contributors under the canonical line).
Use this when auditing why/where string payloads were coalesced.

For broader map-file structure and navigation details, see
:doc:`linker_map_files`.

How garbage collection affects merge data
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Garbage collection runs before merge optimizations in the normal final-link
flow. As a result:

* discarded input sections do not contribute merge-data/merge-string candidates,
* only live sections participate in canonicalization, and
* relocation retargeting is applied for live relocation paths, while discarded
sections/relocations do not affect final merged placement.

Practical implication: enabling ``--gc-sections`` can reduce both direct code/
data size and secondary merged footprint, because dead contributors are removed
before merge-data and merge-strings canonicalization.

Available controls:

* ``--no-merge-strings``: disable string merging.
* ``--no-merge-constants``: disable mergeable constant-data canonicalization.

When to disable:

* Debugging layout/address-sensitive issues where deterministic one-to-one input
retention is preferred over deduplication.
* A/B measurement to quantify the exact contribution of each merge pass.

Example A/B run:
::

ld.eld foo.o bar.o -o app.default.elf
ld.eld --no-merge-constants foo.o bar.o -o app.nomergeconst.elf
readelf -S -W app.default.elf app.nomergeconst.elf

Strip and symbol-table reduction
--------------------------------
For release images, symbol/debug stripping can significantly reduce file size.

* ``--strip-debug`` (or ``-S``): drop debug information.
* ``--strip-all`` (or ``-s``): strip all symbols.
* ``--discard-all`` (or ``-x``): discard all local symbols.
* ``--discard-locals`` (or ``-X``): discard local temporary symbols.

Pick the least aggressive mode that still satisfies post-link tooling needs
(debuggers, profilers, crash pipelines, symbolizers).

Unwind data format choice
-------------------------
If your target and runtime support it, ``--sframe-hdr`` can reduce unwind
metadata size compared to traditional unwind metadata in some workloads.
Validate this per target by comparing section sizes before and after.

LTO for size-oriented builds
----------------------------
LTO can reduce final image size through cross-module dead stripping and
inlining decisions that are impossible in file-local compilation.

Typical knobs:

* ``-flto``: enable LTO when bitcode is available.
* ``--lto-O=<level>``: tune LTO optimization level.
* ``--lto-partitions=<N>`` / ``--thinlto-jobs=<N>``: tune ThinLTO scale.

For details, see :doc:`lto_support`.

Layout and segment-size considerations
--------------------------------------
Image file size and memory footprint are not always the same metric:

* File size depends on what bytes are materialized in file-backed segments.
* Memory size depends on load addresses, segment alignment, and NOBITS handling.

Some layout options (for example, page alignment and BSS conversion behavior)
can trade runtime/load behavior against output file size. See :doc:`layout` and
:doc:`linker_script` when tuning these constraints.

Measure and iterate
-------------------
A repeatable size-optimization loop:

1. Establish a baseline:
``ld.eld ... -Map baseline.map -o baseline.elf``
2. Enable one optimization at a time.
3. Compare:
* ``readelf -S -W`` for section size deltas.
* map-file totals and per-section contributors.
* symbol-level changes via ``readelf -s -W``.
4. Keep only changes that improve size without breaking runtime/debug goals.

Practical profiles
------------------
Debug profile (size-aware, debuggable):

* ``--gc-sections``
* keep symbols/debug data
* optional merge pass disabling for diagnostics

Release profile (minimum file size focus):

* ``--gc-sections``
* default merge passes enabled
* ``--strip-debug`` or ``--strip-all`` (as allowed by deployment pipeline)
* optional LTO with tuned ``--lto-O``
23 changes: 23 additions & 0 deletions docs/userguide/documentation/linker_map_files.rst
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,29 @@ we can also see where each symbol of the section is placed in the output layout.
Other <OutputSectionAndLayout> substructures
---------------------------------------------

Merged string/constant entries in text map files
------------------------------------------------
When ELD merges mergeable string or constant inputs, the text map records the
canonical contributor as a normal input-section line and records merged
contributors as comment-prefixed lines below it.

Merged constants example pattern::

.rodata.cst4 0x<off> 0x<size> file2.o #SHT_PROGBITS,SHF_ALLOC|SHF_MERGE,4
# .rodata.cst4 0x0 file1.o

Merged strings example pattern (with ``--MapDetail=show-strings``)::

.rodata.str1.1 0x<off> 0x<size> file.o #SHT_PROGBITS,SHF_ALLOC|SHF_MERGE|SHF_STRINGS,1 [ Contents: ... ]
# .rodata.str2.1 0x<in_off> file.o

Interpretation:

* The non-comment line is the canonical storage selected by the linker.
* Comment lines are contributors that were merged into the canonical storage.
* If ``--gc-sections`` discards an input section, it does not appear as a live
contributor in the final merged layout.

<LinkerScriptExpression>
^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
5 changes: 4 additions & 1 deletion docs/userguide/documentation/linker_optimizations.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
Linker Optimization Features
============================

For image-size-focused guidance and workflows, see
:doc:`linker_image_size_optimizations`.

* Plugins
More Information about linker plugins can be found at :doc:`linker_plugin`

Expand All @@ -16,4 +19,4 @@ Linker Optimization Features

* Note that when building shared libraries, the linker must assume that any visible symbol is referenced.

* If the linker performs a partial link (-r linker option), then you will need to provide the entry point using the -e / --entry linker option.
* If the linker performs a partial link (-r linker option), then you will need to provide the entry point using the -e / --entry linker option.
1 change: 1 addition & 0 deletions docs/userguide/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ This document describes usage of ELD
documentation/linker_plugins_updated.rst
documentation/linker_plugin.rst
documentation/linker_optimizations.rst
documentation/linker_image_size_optimizations.rst
documentation/elf_tools.rst
documentation/lto_support.rst
documentation/getting_image_details.rst
Expand Down
70 changes: 39 additions & 31 deletions include/eld/Config/GeneralOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ class GeneralOptions {
~GeneralOptions();

GeneralOptions() = delete;
GeneralOptions(const GeneralOptions&) = delete;
GeneralOptions(GeneralOptions&&) = delete;
GeneralOptions(const GeneralOptions &) = delete;
GeneralOptions(GeneralOptions &&) = delete;

/// stats
void setStats(llvm::StringRef Stats);
Expand Down Expand Up @@ -639,6 +639,12 @@ class GeneralOptions {

bool mergeStrings() const { return BMergeStrings; }

void setMergeConstants(bool MergeConstants) {
BMergeConstants = MergeConstants;
}

bool getMergeConstants() const { return BMergeConstants; }

void setLinkerVersionDirectiveEnabled(bool Enable = true) {
EnableLinkerVersionDirective = Enable;
}
Expand Down Expand Up @@ -1233,17 +1239,17 @@ class GeneralOptions {
bool BColor = true; // --color[=true,false,auto]
bool BCreateEhFrameHdr = false; // --eh-frame-hdr
bool BCreateEhFrameHdrSet = false;
bool BCreateSFrameHdr = false; // --sframe-hdr
bool BOMagic = false; // -N, --omagic
bool BNMagic = false; // -n, --nmagic
bool BStripDebug = false; // -S, --strip-debug
bool BExportDynamic = false; //-E, --export-dynamic
bool BWarnSharedTextrel = false; // --warn-shared-textrel
bool BWarnCommon = false; // --warn-common
bool BDefineCommon = false; // -d, -dc, -dp
bool BFatalWarnings = false; // --fatal-warnings
bool BWarningsAsErrors = false; // -Werror
bool BLTOOptRemarksFile = false; // --opt-record-file
bool BCreateSFrameHdr = false; // --sframe-hdr
bool BOMagic = false; // -N, --omagic
bool BNMagic = false; // -n, --nmagic
bool BStripDebug = false; // -S, --strip-debug
bool BExportDynamic = false; //-E, --export-dynamic
bool BWarnSharedTextrel = false; // --warn-shared-textrel
bool BWarnCommon = false; // --warn-common
bool BDefineCommon = false; // -d, -dc, -dp
bool BFatalWarnings = false; // --fatal-warnings
bool BWarningsAsErrors = false; // -Werror
bool BLTOOptRemarksFile = false; // --opt-record-file
bool BLTOOptRemarksDisplayHotness = false; // --display-hotness-remarks
bool BNoStdlib = false; // -nostdlib
bool BPrintMap = false; // --print-map
Expand All @@ -1260,6 +1266,7 @@ class GeneralOptions {
bool NoGnuStack = false; //--nognustack
bool BNoTrampolines = false; //--no-trampolines
bool BMergeStrings = true; //--merge-strings
bool BMergeConstants = true; //--merge-constants
bool BEmitRelocs = false; //--emit-relocs
bool BEmitGNUCompatRelocs = false; // --emit-gnu-compat-relocs
bool BCref = false; // --cref
Expand All @@ -1282,7 +1289,7 @@ class GeneralOptions {
std::optional<std::string> SaveTempsDir; // -save-temps=
bool Rosegment = false; // merge read only with readonly/execute segments.
SeparateSegmentKind SeparateSegments =
SeparateSegmentKind::None; // -z separate-code
SeparateSegmentKind::None; // -z separate-code
std::optional<std::string> LTOObjPath; // --lto-obj-path=
std::vector<std::string>
UnparsedLTOOptions; // Unparsed -flto-options, to pass to plugin.
Expand All @@ -1297,14 +1304,14 @@ class GeneralOptions {
std::string SymDefFile; // --symdef-file
llvm::StringRef SymDefFileStyle; // --symdef-style
bool BAllowBSSMixing = false; // --disable-bss-mixing
bool BAllowBSSConversion = false; // --disable-bss-conversion
bool BFixCortexA53Errata843419 = false; // --fix-cortex-a53-843419
bool Compact = false; // --compact
bool BRWPI = false; // --frwpi
bool BROPI = false; // --fropi
bool BAllowBSSConversion = false; // --disable-bss-conversion
bool BFixCortexA53Errata843419 = false; // --fix-cortex-a53-843419
bool Compact = false; // --compact
bool BRWPI = false; // --frwpi
bool BROPI = false; // --fropi
Target2Policy Target2 = Target2Policy::GotRel; // --target2
bool BExecuteOnly = false; // --execute-only
bool BPrintTimeStats = false; // --print-stats
bool BExecuteOnly = false; // --execute-only
bool BPrintTimeStats = false; // --print-stats
bool BPrintAllUserPluginTimeStats = false;
bool BDemangle = true; // --demangle-style
bool ValidateArchOpts = false; // check -mabi with backend
Expand All @@ -1313,21 +1320,21 @@ class GeneralOptions {
bool BRiscvRelax = true; // enable riscv relaxation
bool RiscvZeroRelax = true; // Zero-page relaxation
bool RiscvGPRelax = true; // GP relaxation
bool BRiscvRelaxToC = true; // enable riscv relax to compressed code
bool BRiscvRelaxXqci = false; // enable riscv relaxations for xqci
bool BRiscvRelaxToC = true; // enable riscv relax to compressed code
bool BRiscvRelaxXqci = false; // enable riscv relaxations for xqci
bool BRiscvRelaxTLSDESC = true; // enable riscv relaxations for TLSDESC
bool AllowIncompatibleSectionsMix = false; // Allow incompatibleSections;
bool ProgressBar = false; // Show progressbar.
bool RecordInputFiles = false; // --reproduce
bool RecordInputFilesOnFail = false; // --reproduce-on-fail
// FIXME: Change the name to CompressReproduceTar
bool CompressTar = false; // --reproduce-compressed
bool DisplaySummary = false; // display linker run summary
bool HasMappingFile = false; // --Mapping-file
bool DumpMappings = false; // --Dump-Mapping-file
bool DumpResponse = false; // --Dump-Response-file
bool InsertTimingStats = false; // -emit-timing-stats-in-output
bool FatalInternalErrors = false; // --fatal-internal-errors
bool CompressTar = false; // --reproduce-compressed
bool DisplaySummary = false; // display linker run summary
bool HasMappingFile = false; // --Mapping-file
bool DumpMappings = false; // --Dump-Mapping-file
bool DumpResponse = false; // --Dump-Response-file
bool InsertTimingStats = false; // -emit-timing-stats-in-output
bool FatalInternalErrors = false; // --fatal-internal-errors
bool EnableLinkerVersionDirective = false; // --enable/disable-linker-version
bool RecordCommandLine = false; // --{no-,}record-command-line

Expand All @@ -1343,7 +1350,8 @@ class GeneralOptions {
std::string MapFile; // Mapfile
std::string TarFile; // --reproduce output tarfile name
std::string TimingStatsFile;
std::optional<std::string> PluginActivityLogFile; // --plugin-activity-file output path
std::optional<std::string>
PluginActivityLogFile; // --plugin-activity-file output path
std::optional<std::string>
ArchiveMemberReportFile; // --archive-member-report output path
std::string MappingFileName; // --Mapping-file
Expand Down
Loading
Loading