From 0e4bd3a0b35d110b05a702a3be0579968f05c376 Mon Sep 17 00:00:00 2001 From: Shankar Easwaran Date: Tue, 14 Apr 2026 13:52:04 -0500 Subject: [PATCH 1/2] Support SHF_MERGE constant-data dedup like merge-strings Support SHF_ALLOC|SHF_MERGE non-string constant-data dedup. Signed-off-by: Shankar Easwaran --- .../linker_image_size_optimizations.rst | 200 +++++++++++++++++ .../documentation/linker_map_files.rst | 23 ++ .../documentation/linker_optimizations.rst | 5 +- docs/userguide/index.rst | 1 + include/eld/Config/GeneralOptions.h | 7 + include/eld/Diagnostics/DiagVerbose.inc | 10 + include/eld/Diagnostics/DiagnosticPrinter.h | 7 +- include/eld/Driver/GnuLinkerOptions.td | 4 + include/eld/Fragment/Fragment.h | 2 + include/eld/Fragment/MergeDataFragment.h | 85 +++++++ include/eld/LayoutMap/LayoutInfo.h | 19 +- include/eld/LayoutMap/TextLayoutPrinter.h | 1 + include/eld/Object/ObjectBuilder.h | 5 + include/eld/Object/ObjectLinker.h | 2 + include/eld/Object/OutputSectionEntry.h | 37 +++ include/eld/Readers/ELFSection.h | 7 + include/eld/Target/Relocator.h | 8 + lib/Config/GeneralOptions.cpp | 4 + lib/Fragment/CMakeLists.txt | 1 + lib/Fragment/Fragment.cpp | 2 + lib/Fragment/FragmentRef.cpp | 25 +++ lib/Fragment/MergeDataFragment.cpp | 127 +++++++++++ lib/LayoutMap/LayoutInfo.cpp | 29 ++- lib/LayoutMap/TextLayoutPrinter.cpp | 34 ++- lib/LinkerWrapper/GnuLdDriver.cpp | 5 + lib/Object/ObjectBuilder.cpp | 40 ++++ lib/Object/ObjectLinker.cpp | 67 +++++- lib/Readers/Relocation.cpp | 4 +- lib/Target/GNULDBackend.cpp | 10 +- lib/Target/Relocator.cpp | 76 ++++++- .../standalone/MergeConstants/DebugInfo.test | 24 ++ .../MergeConstants/Inputs/debugvar1.c | 4 + .../MergeConstants/Inputs/debugvar2.c | 4 + .../MergeConstants/Inputs/picconst1.c | 15 ++ .../MergeConstants/Inputs/picconst2.c | 14 ++ .../standalone/MergeConstants/PIC.test | 20 ++ .../standalone/MergeConstants/DebugInfo.test | 24 ++ .../MergeConstants/Inputs/debugvar1.c | 4 + .../MergeConstants/Inputs/debugvar2.c | 4 + .../MergeConstants/Inputs/picconst1.c | 15 ++ .../MergeConstants/Inputs/picconst2.c | 14 ++ test/ARM/standalone/MergeConstants/PIC.test | 20 ++ .../MergeConstants/AlignmentOrder.test | 25 +++ .../MergeConstants/AlignmentPromotion.test | 18 ++ .../MergeConstants/DistinctPayload.test | 17 ++ .../standalone/MergeConstants/EntSize.test | 20 ++ .../standalone/MergeConstants/Inputs/1.c | 8 + .../standalone/MergeConstants/Inputs/2.c | 10 + .../standalone/MergeConstants/Inputs/3.c | 13 ++ .../standalone/MergeConstants/Inputs/4.c | 13 ++ .../standalone/MergeConstants/Inputs/5.c | 8 + .../standalone/MergeConstants/Inputs/6.c | 8 + .../standalone/MergeConstants/Inputs/7.c | 9 + .../standalone/MergeConstants/Inputs/8.c | 10 + .../standalone/MergeConstants/Inputs/9.c | 10 + .../MergeConstants/Inputs/TraceMerge1.c | 10 + .../MergeConstants/Inputs/TraceMerge2.c | 6 + .../MergeConstants/Inputs/TraceReloc1.c | 12 + .../MergeConstants/Inputs/TraceReloc2.c | 10 + .../MergeConstants/Inputs/asm-helper.h | 7 + .../MergeConstants/Inputs/debugvar1.c | 4 + .../MergeConstants/Inputs/debugvar2.c | 4 + .../Inputs/partial-link-script.t | 3 + .../standalone/MergeConstants/Inputs/pic1.c | 1 + .../standalone/MergeConstants/Inputs/pic2.c | 1 + .../MergeConstants/Inputs/picconst1.c | 6 + .../MergeConstants/Inputs/picconst2.c | 6 + .../Common/standalone/MergeConstants/Map.test | 14 ++ .../standalone/MergeConstants/NoMerge.test | 21 ++ .../Common/standalone/MergeConstants/PIC.test | 20 ++ .../MergeConstants/PartialLink.test | 31 +++ .../standalone/MergeConstants/Trace.test | 21 ++ .../standalone/MergeConstants/DebugInfo.test | 24 ++ .../MergeConstants/Inputs/debugvar1.c | 4 + .../MergeConstants/Inputs/debugvar2.c | 4 + .../MergeConstants/Inputs/picconst1.c | 15 ++ .../MergeConstants/Inputs/picconst2.c | 14 ++ .../standalone/MergeConstants/PIC.test | 20 ++ .../standalone/MergeConstants/DebugInfo.test | 24 ++ .../MergeConstants/Inputs/debugvar1.c | 4 + .../MergeConstants/Inputs/debugvar2.c | 4 + .../MergeConstants/Inputs/picconst1.c | 15 ++ .../MergeConstants/Inputs/picconst2.c | 14 ++ test/RISCV/standalone/MergeConstants/PIC.test | 20 ++ .../MergeData/mergedata.golden | 1 + .../buildAndRunTests/MergeData/mergedata.sh | 76 +++++++ .../MergeDataPic/mergedatapic.golden | 3 + .../MergeDataPic/mergedatapic.sh | 92 ++++++++ .../MergeDataStress/mergedatastress.golden | 4 + .../MergeDataStress/mergedatastress.sh | 210 ++++++++++++++++++ .../mergeable_constants_1000_stress.golden | 5 + .../mergeable_constants_1000_stress.sh | 113 ++++++++++ .../standalone/MergeConstants/DebugInfo.test | 24 ++ .../MergeConstants/Inputs/debugvar1.c | 4 + .../MergeConstants/Inputs/debugvar2.c | 4 + 95 files changed, 2031 insertions(+), 22 deletions(-) create mode 100644 docs/userguide/documentation/linker_image_size_optimizations.rst create mode 100644 include/eld/Fragment/MergeDataFragment.h create mode 100644 lib/Fragment/MergeDataFragment.cpp create mode 100644 test/AArch64/standalone/MergeConstants/DebugInfo.test create mode 100644 test/AArch64/standalone/MergeConstants/Inputs/debugvar1.c create mode 100644 test/AArch64/standalone/MergeConstants/Inputs/debugvar2.c create mode 100644 test/AArch64/standalone/MergeConstants/Inputs/picconst1.c create mode 100644 test/AArch64/standalone/MergeConstants/Inputs/picconst2.c create mode 100644 test/AArch64/standalone/MergeConstants/PIC.test create mode 100644 test/ARM/standalone/MergeConstants/DebugInfo.test create mode 100644 test/ARM/standalone/MergeConstants/Inputs/debugvar1.c create mode 100644 test/ARM/standalone/MergeConstants/Inputs/debugvar2.c create mode 100644 test/ARM/standalone/MergeConstants/Inputs/picconst1.c create mode 100644 test/ARM/standalone/MergeConstants/Inputs/picconst2.c create mode 100644 test/ARM/standalone/MergeConstants/PIC.test create mode 100644 test/Common/standalone/MergeConstants/AlignmentOrder.test create mode 100644 test/Common/standalone/MergeConstants/AlignmentPromotion.test create mode 100644 test/Common/standalone/MergeConstants/DistinctPayload.test create mode 100644 test/Common/standalone/MergeConstants/EntSize.test create mode 100644 test/Common/standalone/MergeConstants/Inputs/1.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/2.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/3.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/4.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/5.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/6.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/7.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/8.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/9.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/TraceMerge1.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/TraceMerge2.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/TraceReloc1.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/TraceReloc2.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/asm-helper.h create mode 100644 test/Common/standalone/MergeConstants/Inputs/debugvar1.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/debugvar2.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/partial-link-script.t create mode 100644 test/Common/standalone/MergeConstants/Inputs/pic1.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/pic2.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/picconst1.c create mode 100644 test/Common/standalone/MergeConstants/Inputs/picconst2.c create mode 100644 test/Common/standalone/MergeConstants/Map.test create mode 100644 test/Common/standalone/MergeConstants/NoMerge.test create mode 100644 test/Common/standalone/MergeConstants/PIC.test create mode 100644 test/Common/standalone/MergeConstants/PartialLink.test create mode 100644 test/Common/standalone/MergeConstants/Trace.test create mode 100644 test/Hexagon/standalone/MergeConstants/DebugInfo.test create mode 100644 test/Hexagon/standalone/MergeConstants/Inputs/debugvar1.c create mode 100644 test/Hexagon/standalone/MergeConstants/Inputs/debugvar2.c create mode 100644 test/Hexagon/standalone/MergeConstants/Inputs/picconst1.c create mode 100644 test/Hexagon/standalone/MergeConstants/Inputs/picconst2.c create mode 100644 test/Hexagon/standalone/MergeConstants/PIC.test create mode 100644 test/RISCV/standalone/MergeConstants/DebugInfo.test create mode 100644 test/RISCV/standalone/MergeConstants/Inputs/debugvar1.c create mode 100644 test/RISCV/standalone/MergeConstants/Inputs/debugvar2.c create mode 100644 test/RISCV/standalone/MergeConstants/Inputs/picconst1.c create mode 100644 test/RISCV/standalone/MergeConstants/Inputs/picconst2.c create mode 100644 test/RISCV/standalone/MergeConstants/PIC.test create mode 100644 test/x86_64/buildAndRunTests/MergeData/mergedata.golden create mode 100755 test/x86_64/buildAndRunTests/MergeData/mergedata.sh create mode 100644 test/x86_64/buildAndRunTests/MergeDataPic/mergedatapic.golden create mode 100755 test/x86_64/buildAndRunTests/MergeDataPic/mergedatapic.sh create mode 100644 test/x86_64/buildAndRunTests/MergeDataStress/mergedatastress.golden create mode 100755 test/x86_64/buildAndRunTests/MergeDataStress/mergedatastress.sh create mode 100644 test/x86_64/buildAndRunTests/MergeableConstants1000Stress/mergeable_constants_1000_stress.golden create mode 100755 test/x86_64/buildAndRunTests/MergeableConstants1000Stress/mergeable_constants_1000_stress.sh create mode 100644 test/x86_64/standalone/MergeConstants/DebugInfo.test create mode 100644 test/x86_64/standalone/MergeConstants/Inputs/debugvar1.c create mode 100644 test/x86_64/standalone/MergeConstants/Inputs/debugvar2.c diff --git a/docs/userguide/documentation/linker_image_size_optimizations.rst b/docs/userguide/documentation/linker_image_size_optimizations.rst new file mode 100644 index 000000000..67ed7a72e --- /dev/null +++ b/docs/userguide/documentation/linker_image_size_optimizations.rst @@ -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 #SHT_PROGBITS,SHF_ALLOC|SHF_MERGE,4 + # .rodata.cst4 0x0 + +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=``: tune LTO optimization level. +* ``--lto-partitions=`` / ``--thinlto-jobs=``: 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`` diff --git a/docs/userguide/documentation/linker_map_files.rst b/docs/userguide/documentation/linker_map_files.rst index 46a4555e1..6d25b9cb1 100644 --- a/docs/userguide/documentation/linker_map_files.rst +++ b/docs/userguide/documentation/linker_map_files.rst @@ -364,6 +364,29 @@ we can also see where each symbol of the section is placed in the output layout. Other 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 0x 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 0x file.o #SHT_PROGBITS,SHF_ALLOC|SHF_MERGE|SHF_STRINGS,1 [ Contents: ... ] + # .rodata.str2.1 0x 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. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/userguide/documentation/linker_optimizations.rst b/docs/userguide/documentation/linker_optimizations.rst index b9bdb52e4..d9376f91b 100644 --- a/docs/userguide/documentation/linker_optimizations.rst +++ b/docs/userguide/documentation/linker_optimizations.rst @@ -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` @@ -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. \ No newline at end of file + * 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. diff --git a/docs/userguide/index.rst b/docs/userguide/index.rst index 027f9f8d3..24715827f 100644 --- a/docs/userguide/index.rst +++ b/docs/userguide/index.rst @@ -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 diff --git a/include/eld/Config/GeneralOptions.h b/include/eld/Config/GeneralOptions.h index 20e19dac7..bd2d63ae8 100644 --- a/include/eld/Config/GeneralOptions.h +++ b/include/eld/Config/GeneralOptions.h @@ -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; } @@ -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 diff --git a/include/eld/Diagnostics/DiagVerbose.inc b/include/eld/Diagnostics/DiagVerbose.inc index 41e1a94da..f6d008d82 100644 --- a/include/eld/Diagnostics/DiagVerbose.inc +++ b/include/eld/Diagnostics/DiagVerbose.inc @@ -142,6 +142,8 @@ DIAG(calling_function_from_dynamic_lib, DiagnosticEngine::Verbose, DIAG(merging_strings, DiagnosticEngine::Verbose, "Merging strings") DIAG(handling_merge_strings_for_section, DiagnosticEngine::Verbose, "Handling merge string relocations for section %0 in file %1") +DIAG(handling_merge_constants_for_section, DiagnosticEngine::Verbose, + "Handling merge constant relocations for section %0 in file %1") DIAG(merging_fragments, DiagnosticEngine::Trace, "Merging fragment from file %0 in section %1\n\twith fragment " "from file %2 in section %3\n\tand content %4 to output section " @@ -150,6 +152,14 @@ DIAG(modifying_mergestr_reloc, DiagnosticEngine::Trace, "Modified relocation %0 + %1 from relocation section %2 in file %3:\n\t " "Old fragment: offset %4 in section %5 " "from file %6\n\t New fragment: offset %7 in section %8 from file %9") +DIAG(merging_constant_fragments, DiagnosticEngine::Trace, + "Merging constant from file %0 in section %1 at offset %2 with alignment " + "%3\n\twith constant from file %4 in section %5 at offset %6 with " + "alignment %7\n\tto output section %8") +DIAG(modifying_mergeconst_reloc, DiagnosticEngine::Trace, + "Modified relocation %0 + %1 from relocation section %2 in file %3:\n\t " + "Old constant: offset %4 in section %5 from file %6\n\t New constant: " + "offset %7 in section %8 from file %9") DIAG(splitting_merge_string_section, DiagnosticEngine::Verbose, "%0: created mergeable string fragment with contents %1 and align " "%2") diff --git a/include/eld/Diagnostics/DiagnosticPrinter.h b/include/eld/Diagnostics/DiagnosticPrinter.h index 8d00acb7c..ee66b2bc1 100644 --- a/include/eld/Diagnostics/DiagnosticPrinter.h +++ b/include/eld/Diagnostics/DiagnosticPrinter.h @@ -45,8 +45,9 @@ class DiagnosticPrinter { TraceSection = 0x2000, TraceDynamicLinking = 0x4000, TraceMergeStrings = 0x8000, - TraceLinkerScript = 0x10000, - TraceUntar = 0x20000, + TraceMergeConstants = 0x10000, + TraceLinkerScript = 0x20000, + TraceUntar = 0x40000, TraceSymDef = 0x100000, #ifdef ELD_ENABLE_SYMBOL_VERSIONING TraceSymbolVersioning = 0x200000 @@ -98,6 +99,8 @@ class DiagnosticPrinter { bool traceMergeStrings() { return Trace & TraceMergeStrings; } + bool traceMergeConstants() { return Trace & TraceMergeConstants; } + bool traceLinkerScript() { return (Trace & TraceLinkerScript); } bool traceUntar() { return (Trace & TraceUntar); } diff --git a/include/eld/Driver/GnuLinkerOptions.td b/include/eld/Driver/GnuLinkerOptions.td index 6f08f0db0..f0b37b8fe 100644 --- a/include/eld/Driver/GnuLinkerOptions.td +++ b/include/eld/Driver/GnuLinkerOptions.td @@ -697,6 +697,7 @@ defm trace "\t\t\t --trace=live-edges : trace reachable sections when garbage " "collection is enabled\n" "\t\t\t --trace=merge-strings : trace linker string optimization\n" + "\t\t\t --trace=merge-constants : trace linker constant-data optimization\n" "\t\t\t --trace=trampolines : trace trampolines\n" "\t\t\t --trace=untar : trace tar extraction entries\n" "\t\t\t --trace=wrap-symbols : trace symbol wrap options\n" @@ -872,6 +873,9 @@ def sframe_hdr def no_merge_strings : Flag<["--"], "no-merge-strings">, HelpText<"Disable String Merging">, Group; +def no_merge_constants : Flag<["--"], "no-merge-constants">, + HelpText<"Disable Constant Data Merging">, + Group; def no_trampolines : Flag<["--"], "no-trampolines">, HelpText<"Disable Trampolines">, Group; diff --git a/include/eld/Fragment/Fragment.h b/include/eld/Fragment/Fragment.h index 776a96273..37dc1b141 100644 --- a/include/eld/Fragment/Fragment.h +++ b/include/eld/Fragment/Fragment.h @@ -49,6 +49,7 @@ class Fragment { Timing, Null, MergeString, + MergeData, BuildID, SFrame, #ifdef ELD_ENABLE_SYMBOL_VERSIONING @@ -125,6 +126,7 @@ class Fragment { virtual void addSymbol(ResolveInfo *R) {} bool isMergeStr() const; + bool isMergeData() const; bool isNull() const { return Kind == Null; } diff --git a/include/eld/Fragment/MergeDataFragment.h b/include/eld/Fragment/MergeDataFragment.h new file mode 100644 index 000000000..a50d6606c --- /dev/null +++ b/include/eld/Fragment/MergeDataFragment.h @@ -0,0 +1,85 @@ +//===- MergeDataFragment.h-------------------------------------------------===// +// Part of the eld Project, under the BSD License +// See https://github.com/qualcomm/eld/LICENSE.txt for license information. +// SPDX-License-Identifier: BSD-3-Clause +//===----------------------------------------------------------------------===// +#ifndef ELD_FRAGMENT_MERGEDATAFRAGMENT_H +#define ELD_FRAGMENT_MERGEDATAFRAGMENT_H + +#include "eld/Fragment/Fragment.h" +#include "llvm/ADT/StringRef.h" +#include + +namespace eld { + +class LinkerConfig; +class MergeDataFragment; +class OutputSectionEntry; + +/// A mergeable fixed-size entry from a SHF_MERGE (non SHF_STRINGS) section. +struct MergeableConstant { + MergeDataFragment *Fragment; + llvm::StringRef Data; + uint32_t InputOffset; + uint32_t OutputOffset; + bool Exclude; + + MergeableConstant(MergeDataFragment *F, llvm::StringRef D, uint32_t I, + uint32_t O, bool E) + : Fragment(F), Data(D), InputOffset(I), OutputOffset(O), Exclude(E) {} + + MergeableConstant(const MergeableConstant &) = delete; + MergeableConstant &operator=(const MergeableConstant &) = delete; + MergeableConstant(MergeableConstant &&) = default; + MergeableConstant &operator=(MergeableConstant &&) = default; + + void setExcluded() { Exclude = true; } + uint64_t getSize() const { return Data.size(); } + uint32_t getAlignment() const; + bool hasOutputOffset() const { + return OutputOffset != std::numeric_limits::max(); + } +}; + +class MergeDataFragment : public Fragment { + llvm::SmallVector Constants; + +public: + explicit MergeDataFragment(ELFSection *O); + + ~MergeDataFragment() {} + + static MergeableConstant &getOrAddMergedConstant(MergeableConstant *C, + OutputSectionEntry *O); + + bool readConstants(LinkerConfig &Config); + + static bool classof(const Fragment *F) { + return F->getKind() == Fragment::MergeData; + } + + size_t size() const override; + + bool isZeroSizedFrag() const override { return Constants.empty(); } + + eld::Expected emit(MemoryRegion &Region, Module &M) override; + + llvm::SmallVectorImpl &getConstants() { return Constants; } + + const llvm::SmallVectorImpl &getConstants() const { + return Constants; + } + + const MergeableConstant *getConstantAtOffset(uint64_t Offset) const; + + void copyData(void *Dest, uint64_t Bytes, uint64_t Offset) const; + + void setOffset(uint32_t Offset) override; + +private: + void assignOutputOffsets(); +}; + +} // namespace eld + +#endif diff --git a/include/eld/LayoutMap/LayoutInfo.h b/include/eld/LayoutMap/LayoutInfo.h index 3b1fe0782..cee51f05e 100644 --- a/include/eld/LayoutMap/LayoutInfo.h +++ b/include/eld/LayoutMap/LayoutInfo.h @@ -38,6 +38,8 @@ class UpdateChunksPluginOp; class LinkerConfig; class Module; class Stats; +struct MergeableConstant; +struct MergeableString; struct LayoutFragmentInfo { LayoutFragmentInfo(InputFile *F, const ELFSection *Section) @@ -401,7 +403,7 @@ class LayoutInfo { std::vector &getComments() { return Comments; } - void buildMergedStringMap(Module &M); + void buildMergedMaps(Module &M); void addMergedStrings(MergeableString *From, MergeableString *To) { assert(From != To); @@ -418,6 +420,19 @@ class LayoutInfo { return MergedStrings.at(S); } + void addMergedConstants(MergeableConstant *From, MergeableConstant *To) { + assert(From != To); + MergedConstants[From].push_back(To); + } + + std::vector + getMergedConstants(MergeableConstant *C) const { + auto Fragments = MergedConstants.find(C); + if (Fragments == MergedConstants.end()) + return {}; + return MergedConstants.at(C); + } + static std::optional getBasepath() { return ThisBasepath; } // -------------------------- Stats --------------------------------- @@ -456,6 +471,8 @@ class LayoutInfo { std::vector Comments; std::unordered_map> MergedStrings; + std::unordered_map> + MergedConstants; LinkerConfig &ThisConfig; /// It is required to compute relative path when -MapDetail /// 'show-relative-path=...' is used. diff --git a/include/eld/LayoutMap/TextLayoutPrinter.h b/include/eld/LayoutMap/TextLayoutPrinter.h index d71045243..3d0decb40 100644 --- a/include/eld/LayoutMap/TextLayoutPrinter.h +++ b/include/eld/LayoutMap/TextLayoutPrinter.h @@ -145,6 +145,7 @@ class TextLayoutPrinter { void printInsertPlacement(const OutputSectionEntry *OS) const; void printMergeString(MergeableString *S, Module &M) const; + void printMergeConstant(MergeableConstant *C, Module &M) const; void printIsFileHeaderLoadedInfo(bool IsLoaded, bool UseColor); diff --git a/include/eld/Object/ObjectBuilder.h b/include/eld/Object/ObjectBuilder.h index 0d00584cf..2c49f3e67 100644 --- a/include/eld/Object/ObjectBuilder.h +++ b/include/eld/Object/ObjectBuilder.h @@ -30,6 +30,8 @@ class ELFSection; class Fragment; struct MergeableString; class MergeStringFragment; +struct MergeableConstant; +class MergeDataFragment; class GNULDBackend; class Input; class LinkerConfig; @@ -84,6 +86,7 @@ class ObjectBuilder { void mayChangeSectionTypeOrKind(ELFSection *, ELFSection *) const; void mergeStrings(MergeStringFragment *F, OutputSectionEntry *O); + void mergeConstants(MergeDataFragment *F, OutputSectionEntry *O); private: bool shouldSkipMergeSection(ELFSection *) const; @@ -92,6 +95,8 @@ class ObjectBuilder { void traceMergeStrings(const MergeableString *From, const MergeableString *To) const; + void traceMergeConstants(const MergeableConstant *From, + const MergeableConstant *To) const; /// Assigns output sections to internal common sections. bool assignOutputSectionsToCommonSymbols(); diff --git a/include/eld/Object/ObjectLinker.h b/include/eld/Object/ObjectLinker.h index e577e31c0..07086b0ea 100644 --- a/include/eld/Object/ObjectLinker.h +++ b/include/eld/Object/ObjectLinker.h @@ -481,6 +481,8 @@ class ObjectLinker { void mergeNonAllocStrings(std::vector, ObjectBuilder &Builder) const; + void mergeIdenticalConstants() const; + /// Redirect relocations to point directly to a deduplicated string fragment void fixMergeStringRelocations() const; diff --git a/include/eld/Object/OutputSectionEntry.h b/include/eld/Object/OutputSectionEntry.h index 2d560592f..3f9e24894 100644 --- a/include/eld/Object/OutputSectionEntry.h +++ b/include/eld/Object/OutputSectionEntry.h @@ -7,6 +7,7 @@ #ifndef ELD_OBJECT_OUTPUTSECTIONENTRY_H #define ELD_OBJECT_OUTPUTSECTIONENTRY_H +#include "eld/Fragment/MergeDataFragment.h" #include "eld/Fragment/MergeStringFragment.h" #include "eld/Object/SectionMap.h" #include "eld/Script/Assignment.h" @@ -213,6 +214,40 @@ class OutputSectionEntry { return AllStrings; } + // -------------------- Constant merging support ------------------------ + + MergeableConstant *findConstant(const MergeableConstant *C) const { + auto Const = UniqueConstants.find(C->Data); + if (Const == UniqueConstants.end()) + return nullptr; + MergeableConstant *MergedConstant = Const->second; + if (MergedConstant == C) + return nullptr; + return MergedConstant; + } + + MergeableConstant *getMergedConstant(const MergeableConstant *C) const { + MergeableConstant *MergedConstant = findConstant(C); + if (!MergedConstant) + return nullptr; + // Preserve alignment requirements: a fragment can fold into an existing + // canonical constant only when that canonical constant is at least as + // strictly aligned. + if (MergedConstant->getAlignment() < C->getAlignment()) + return nullptr; + return MergedConstant; + } + + void addConstant(MergeableConstant *C) { + AllConstants.push_back(C); + if (!C->Exclude) + UniqueConstants[C->Data] = C; + } + + const llvm::SmallVectorImpl &getMergeConstants() const { + return AllConstants; + } + private: std::string Name; OutputSectDesc *OutputSectionDesc = nullptr; @@ -229,6 +264,8 @@ class OutputSectionEntry { BranchIslandForSymbol; llvm::StringMap UniqueStrings; llvm::SmallVector AllStrings; + llvm::StringMap UniqueConstants; + llvm::SmallVector AllConstants; uint64_t Hash = 0; llvm::StringMap TrampolineNameToCountMap; uint64_t PAddr = 0; diff --git a/include/eld/Readers/ELFSection.h b/include/eld/Readers/ELFSection.h index 2f8851713..fe7373483 100644 --- a/include/eld/Readers/ELFSection.h +++ b/include/eld/Readers/ELFSection.h @@ -90,6 +90,13 @@ class ELFSectionBase : public Section { bool isMergeStr() const { return (Flags & llvm::ELF::SHF_MERGE) && (Flags & llvm::ELF::SHF_STRINGS); } + bool isMergeData() const { + // Mergeable constants are ELF SHF_MERGE sections that: + // 1) participate in the runtime image (SHF_ALLOC), and + // 2) are not string-merge sections (no SHF_STRINGS). + return (Flags & llvm::ELF::SHF_ALLOC) && (Flags & llvm::ELF::SHF_MERGE) && + !(Flags & llvm::ELF::SHF_STRINGS); + } bool isNote() const { return Type == llvm::ELF::SHT_NOTE; } protected: diff --git a/include/eld/Target/Relocator.h b/include/eld/Target/Relocator.h index a42d18b6a..31c0a38b9 100644 --- a/include/eld/Target/Relocator.h +++ b/include/eld/Target/Relocator.h @@ -27,6 +27,8 @@ class GNULDBackend; class IRBuilder; class Module; class InputFile; +struct MergeableConstant; +struct MergeableString; /** \class Relocator * \brief Relocator provides the interface of performing relocations @@ -104,11 +106,17 @@ class Relocator { const MergeableString *From, const MergeableString *To) const; + virtual void traceMergeConstants(const ELFSection *RelocationSection, + const Relocation *R, + const MergeableConstant *From, + const MergeableConstant *To) const; + virtual std::pair findFragmentForMergeStr(const ELFSection *RelocationSection, const Relocation *R, MergeStringFragment *F) const; virtual bool doMergeStrings(ELFSection *S); + virtual bool doMergeConstants(ELFSection *S); // ------ observers -----// virtual GNULDBackend &getTarget() = 0; diff --git a/lib/Config/GeneralOptions.cpp b/lib/Config/GeneralOptions.cpp index 8102d3b8c..661b80c45 100644 --- a/lib/Config/GeneralOptions.cpp +++ b/lib/Config/GeneralOptions.cpp @@ -225,6 +225,8 @@ eld::Expected GeneralOptions::setTrace(const char *PTraceType) { if (Type == SECTIONS) addMergeStrTraceSection(Arg); TraceMe = DiagEngine->getPrinter()->TraceMergeStrings; + } else if (TraceType.starts_with("merge-constants")) { + TraceMe = DiagEngine->getPrinter()->TraceMergeConstants; } else { TraceMe = llvm::StringSwitch>(TraceType) @@ -236,6 +238,8 @@ eld::Expected GeneralOptions::setTrace(const char *PTraceType) { .Case("live-edges", DiagEngine->getPrinter()->TraceGCLive) .Case("lto", DiagEngine->getPrinter()->TraceLTO) .Case("merge-strings", DiagEngine->getPrinter()->TraceMergeStrings) + .Case("merge-constants", + DiagEngine->getPrinter()->TraceMergeConstants) .Case("plugin", DiagEngine->getPrinter()->TracePlugin) .Case("threads", DiagEngine->getPrinter()->TraceThreads) .Case("trampolines", DiagEngine->getPrinter()->TraceTrampolines) diff --git a/lib/Fragment/CMakeLists.txt b/lib/Fragment/CMakeLists.txt index b1a9c685b..046b0859d 100644 --- a/lib/Fragment/CMakeLists.txt +++ b/lib/Fragment/CMakeLists.txt @@ -11,6 +11,7 @@ llvm_add_library( GNUHashFragment.cpp GOT.cpp MergeStringFragment.cpp + MergeDataFragment.cpp OutputSectDataFragment.cpp PLT.cpp RegionFragment.cpp diff --git a/lib/Fragment/Fragment.cpp b/lib/Fragment/Fragment.cpp index 88b0576d3..332d8d8e7 100644 --- a/lib/Fragment/Fragment.cpp +++ b/lib/Fragment/Fragment.cpp @@ -146,6 +146,8 @@ uint64_t Fragment::getAddr(DiagnosticEngine *DiagEngine) const { bool Fragment::isMergeStr() const { return getOwningSection()->isMergeKind(); } +bool Fragment::isMergeData() const { return Kind == Fragment::MergeData; } + bool Fragment::originatesFromPlugin(const Module &Module) const { return getOwningSection()->getInputFile() == Module.getInternalInput(Module::InternalInputType::Plugin); diff --git a/lib/Fragment/FragmentRef.cpp b/lib/Fragment/FragmentRef.cpp index 1d2a3f7ce..504e3244d 100644 --- a/lib/Fragment/FragmentRef.cpp +++ b/lib/Fragment/FragmentRef.cpp @@ -14,6 +14,7 @@ #include "eld/Core/Module.h" #include "eld/Fragment/Fragment.h" #include "eld/Fragment/GOT.h" +#include "eld/Fragment/MergeDataFragment.h" #include "eld/Fragment/OutputSectDataFragment.h" #include "eld/Fragment/PLT.h" #include "eld/Fragment/RegionFragment.h" @@ -58,6 +59,14 @@ void FragmentRef::memcpy(void *PDest, size_t PNBytes, Offset POffset) const { Strings->copyData(PDest, PNBytes, TotalOffset); return; } + case Fragment::MergeData: { + auto *Constants = static_cast(ThisFragment); + unsigned int TotalLength = Constants->size(); + if (TotalLength < (TotalOffset + PNBytes)) + PNBytes = TotalLength - TotalOffset; + Constants->copyData(PDest, PNBytes, TotalOffset); + return; + } case Fragment::Region: { RegionFragment *RegionFrag = static_cast(ThisFragment); unsigned int TotalLength = RegionFrag->getRegion().size(); @@ -157,6 +166,22 @@ FragmentRef::Offset FragmentRef::getOutputOffset(Module &M) const { assert(S->hasOutputOffset()); return S->OutputOffset + OffsetInString; } + if (ThisFragment->isMergeData()) { + auto *MDF = llvm::cast(ThisFragment); + OutputSectionEntry *O = getOutputSection(); + const MergeableConstant *C = MDF->getConstantAtOffset(ThisOffset); + assert(C); + uint32_t OffsetInConstant = ThisOffset - C->InputOffset; + if (const MergeableConstant *Merged = O->getMergedConstant(C)) { + assert(C->Exclude); + assert(!Merged->Exclude); + assert(Merged->hasOutputOffset()); + return Merged->OutputOffset + OffsetInConstant; + } + assert(!C->Exclude); + assert(C->hasOutputOffset()); + return C->OutputOffset + OffsetInConstant; + } Offset Result = 0; if (nullptr != ThisFragment) Result = ThisFragment->getOffset(M.getConfig().getDiagEngine()); diff --git a/lib/Fragment/MergeDataFragment.cpp b/lib/Fragment/MergeDataFragment.cpp new file mode 100644 index 000000000..49c4a9784 --- /dev/null +++ b/lib/Fragment/MergeDataFragment.cpp @@ -0,0 +1,127 @@ +//===- MergeDataFragment.cpp-----------------------------------------------===// +// Part of the eld Project, under the BSD License +// See https://github.com/qualcomm/eld/LICENSE.txt for license information. +// SPDX-License-Identifier: BSD-3-Clause +//===----------------------------------------------------------------------===// + +#include "eld/Fragment/MergeDataFragment.h" +#include "eld/Config/LinkerConfig.h" +#include "eld/Core/Module.h" +#include "eld/Object/OutputSectionEntry.h" +#include "eld/Readers/ELFSection.h" +#include +#include + +using namespace eld; + +MergeDataFragment::MergeDataFragment(ELFSection *O) + : Fragment(Fragment::MergeData, O, O->getAddrAlign()) {} + +MergeableConstant & +MergeDataFragment::getOrAddMergedConstant(MergeableConstant *C, + OutputSectionEntry *O) { + MergeableConstant *MergedConstant = O->getMergedConstant(C); + if (MergedConstant) { + // C is a duplicate of an existing canonical constant. + // Mark it excluded from emission but still remember the input constant so + // relocation fixups can map back from input offsets. + C->setExcluded(); + O->addConstant(C); + return *MergedConstant; + } + + // No reusable canonical constant exists. If an older canonical entry has the + // same bytes but weaker alignment, retire it and promote C as canonical. + if (MergeableConstant *Existing = O->findConstant(C)) + Existing->setExcluded(); + O->addConstant(C); + return *C; +} + +bool MergeDataFragment::readConstants(LinkerConfig &Config) { + ELFSection *S = getOwningSection(); + llvm::StringRef Contents = S->getContents(); + if (Contents.empty()) + return true; + + const uint32_t EntrySize = S->getEntSize(); + if (EntrySize == 0 || (Contents.size() % EntrySize) != 0) { + Config.raise(Diag::unexpected_section_size) + << S->size() << S->name().str() + << S->getInputFile()->getInput()->decoratedPath(); + return false; + } + + for (uint32_t Off = 0; Off < Contents.size(); Off += EntrySize) { + llvm::StringRef Data = Contents.slice(Off, Off + EntrySize); + // OutputOffset is assigned after layout; initialize as "not assigned". + Constants.emplace_back(this, Data, Off, + std::numeric_limits::max(), false); + } + return true; +} + +size_t MergeDataFragment::size() const { + size_t Size = 0; + for (const MergeableConstant &C : Constants) + Size += C.Exclude ? 0 : C.getSize(); + return Size; +} + +eld::Expected MergeDataFragment::emit(MemoryRegion &Region, Module &M) { + uint64_t Size = size(); + if (!Size) + return {}; + + [[maybe_unused]] uint64_t Emitted = 0; + uint8_t *Buf = Region.begin() + getOffset(M.getConfig().getDiagEngine()); + for (const MergeableConstant &C : Constants) { + if (C.Exclude) + continue; + std::memcpy(Buf, C.Data.begin(), C.getSize()); + Emitted += C.getSize(); + Buf += C.getSize(); + } + assert(Emitted == Size); + return {}; +} + +const MergeableConstant * +MergeDataFragment::getConstantAtOffset(uint64_t Offset) const { + if (Offset >= getOwningSection()->size()) + return nullptr; + uint32_t EntrySize = getOwningSection()->getEntSize(); + if (EntrySize == 0) + return nullptr; + return &Constants[Offset / EntrySize]; +} + +void MergeDataFragment::copyData(void *Dest, uint64_t Bytes, + uint64_t Offset) const { + const MergeableConstant *C = getConstantAtOffset(Offset); + assert(C); + uint64_t OffsetInConst = Offset - C->InputOffset; + uint64_t Size = std::min((uint64_t)Bytes, C->getSize() - OffsetInConst); + std::memcpy(Dest, C->Data.begin() + OffsetInConst, Size); +} + +void MergeDataFragment::setOffset(uint32_t Offset) { + Fragment::setOffset(Offset); + assignOutputOffsets(); +} + +void MergeDataFragment::assignOutputOffsets() { + assert(hasOffset()); + uint32_t Offset = getOffset(); + for (MergeableConstant &C : Constants) { + if (C.Exclude) + continue; + // Constants are emitted contiguously in input order for this fragment. + C.OutputOffset = Offset; + Offset += C.getSize(); + } +} + +uint32_t MergeableConstant::getAlignment() const { + return Fragment->alignment(); +} diff --git a/lib/LayoutMap/LayoutInfo.cpp b/lib/LayoutMap/LayoutInfo.cpp index e87e186f2..2711ffd94 100644 --- a/lib/LayoutMap/LayoutInfo.cpp +++ b/lib/LayoutMap/LayoutInfo.cpp @@ -11,6 +11,7 @@ #include "eld/Fragment/FillFragment.h" #include "eld/Fragment/Fragment.h" #include "eld/Fragment/FragmentRef.h" +#include "eld/Fragment/MergeDataFragment.h" #include "eld/Fragment/RegionFragment.h" #include "eld/Input/ArchiveFile.h" #include "eld/Input/ArchiveMemberInput.h" @@ -497,19 +498,23 @@ void LayoutInfo::recordUpdateLinkStats(plugin::LinkerWrapper *W, Plugins.insert(W); } -void LayoutInfo::buildMergedStringMap(Module &M) { - if (!MergedStrings.empty()) +void LayoutInfo::buildMergedMaps(Module &M) { + if (!MergedStrings.empty() || !MergedConstants.empty()) return; std::vector OutputSections; + std::unordered_set SeenOutputSections; + auto AddOutputSection = [&](OutputSectionEntry *O) { + if (!O || !SeenOutputSections.insert(O).second) + return; + OutputSections.push_back(O); + }; for (ELFSection *S : M) { if (S->isRelocationSection()) continue; - if (auto *O = S->getOutputSection()) - OutputSections.push_back(O); - } - for (OutputSectionEntry *O : M.getScript().sectionMap()) { - OutputSections.push_back(O); + AddOutputSection(S->getOutputSection()); } + for (OutputSectionEntry *O : M.getScript().sectionMap()) + AddOutputSection(O); bool GlobalMerge = M.getConfig().options().shouldGlobalStringMerge(); auto AddString = [&](OutputSectionEntry *O, MergeableString *S) { if (!S->Exclude) @@ -520,9 +525,17 @@ void LayoutInfo::buildMergedStringMap(Module &M) { ASSERT(Merged, "expected to find a merged string"); addMergedStrings(Merged, S); }; - for (OutputSectionEntry *O : OutputSections) + for (OutputSectionEntry *O : OutputSections) { for (MergeableString *S : O->getMergeStrings()) AddString(O, S); + for (MergeableConstant *C : O->getMergeConstants()) { + if (!C->Exclude) + continue; + MergeableConstant *Merged = O->findConstant(C); + ASSERT(Merged, "expected to find a merged constant"); + addMergedConstants(Merged, C); + } + } for (MergeableString *S : M.getNonAllocStrings()) AddString(nullptr, S); } diff --git a/lib/LayoutMap/TextLayoutPrinter.cpp b/lib/LayoutMap/TextLayoutPrinter.cpp index 5c58e42ed..314fdc7f0 100644 --- a/lib/LayoutMap/TextLayoutPrinter.cpp +++ b/lib/LayoutMap/TextLayoutPrinter.cpp @@ -15,6 +15,7 @@ #include "eld/Fragment/FragUtils.h" #include "eld/Fragment/Fragment.h" #include "eld/Fragment/FragmentRef.h" +#include "eld/Fragment/MergeDataFragment.h" #include "eld/Fragment/RegionFragment.h" #include "eld/Fragment/RegionFragmentEx.h" #include "eld/Fragment/TimingFragment.h" @@ -658,6 +659,24 @@ void TextLayoutPrinter::printMergeString(MergeableString *S, Module &M) const { } } +void TextLayoutPrinter::printMergeConstant(MergeableConstant *C, + Module &M) const { + if (C->Exclude) { + outputStream() << "\n"; + return; + } + outputStream() << "\n"; + for (MergeableConstant *Merged : ThisLayoutInfo->getMergedConstants(C)) { + assert(Merged->Exclude); + outputStream() << " # "; + ELFSection *S = Merged->Fragment->getOwningSection(); + outputStream() << S->getDecoratedName(M.getConfig().options()) << " 0x"; + outputStream().write_hex(Merged->InputOffset); + outputStream() << " " << getDecoratedPath(S->getInputFile()->getInput()); + outputStream() << "\n"; + } +} + void TextLayoutPrinter::printFragInfo(Fragment *Frag, LayoutFragmentInfo *Info, ELFSection *Section, Module &M) const { @@ -725,6 +744,19 @@ void TextLayoutPrinter::printFragInfo(Fragment *Frag, LayoutFragmentInfo *Info, outputStream() << "\n"; printMergeString(&S, M); } + } else if (llvm::isa(Frag) && + !M.isLinkStateBeforeLayout()) { + auto *Constants = llvm::cast(Frag); + for (MergeableConstant &C : Constants->getConstants()) { + if (C.Exclude) + continue; + if (!GC && HasFragInfo) + AddressOrOffset = + C.OutputOffset + + (Section->isAlloc() ? Section->addr() : Section->offset()); + PrintOneFragOrString(C.getSize(), AddressOrOffset); + printMergeConstant(&C, M); + } } else { if (!GC && HasFragInfo) AddressOrOffset = @@ -1246,7 +1278,7 @@ void TextLayoutPrinter::printAssignment(const Assignment &A, Module &M, // etc. If use of color for text is enabled, print text with a foreground // color. Reset the colors to terminal defaults after writing. void TextLayoutPrinter::printMapFile(eld::Module &Module) { - ThisLayoutInfo->buildMergedStringMap(Module); + ThisLayoutInfo->buildMergedMaps(Module); GNULDBackend &Backend = Module.getBackend(); bool UseColor = Backend.config().options().color() && Backend.config().options().colorMap(); diff --git a/lib/LinkerWrapper/GnuLdDriver.cpp b/lib/LinkerWrapper/GnuLdDriver.cpp index fb32c098a..ee9c9252b 100644 --- a/lib/LinkerWrapper/GnuLdDriver.cpp +++ b/lib/LinkerWrapper/GnuLdDriver.cpp @@ -641,6 +641,11 @@ bool GnuLdDriver::processOptions(llvm::opt::InputArgList &Args) { Config.addCommandLine(Table->getOptionName(T::no_merge_strings), Args.hasArg(T::no_merge_strings)); + // --no-merge-constants + Config.options().setMergeConstants(!Args.hasArg(T::no_merge_constants)); + Config.addCommandLine(Table->getOptionName(T::no_merge_constants), + Args.hasArg(T::no_merge_constants)); + // --{no-}warn-mismatch if (Args.getLastArg(T::no_warn_mismatch)) Config.options().setWarnMismatch(false); diff --git a/lib/Object/ObjectBuilder.cpp b/lib/Object/ObjectBuilder.cpp index 1dd57c789..c18232894 100644 --- a/lib/Object/ObjectBuilder.cpp +++ b/lib/Object/ObjectBuilder.cpp @@ -16,6 +16,7 @@ #include "eld/Core/LinkerScript.h" #include "eld/Core/Module.h" #include "eld/Diagnostics/DiagnosticPrinter.h" +#include "eld/Fragment/MergeDataFragment.h" #include "eld/Fragment/MergeStringFragment.h" #include "eld/Input/ArchiveMemberInput.h" #include "eld/Input/InputFile.h" @@ -186,6 +187,31 @@ void ObjectBuilder::traceMergeStrings(const MergeableString *From, assert(From->String == To->String); } +void ObjectBuilder::traceMergeConstants(const MergeableConstant *From, + const MergeableConstant *To) const { + ELFSection *OutputSection = + From->Fragment->getOwningSection()->getOutputELFSection(); + std::string OutputSectionName = + OutputSection->getDecoratedName(ThisConfig.options()); + std::string FileFrom = From->Fragment->getOwningSection() + ->getInputFile() + ->getInput() + ->decoratedPath(true); + std::string FileTo = To->Fragment->getOwningSection() + ->getInputFile() + ->getInput() + ->decoratedPath(true); + std::string SectionFrom = + From->Fragment->getOwningSection()->getDecoratedName( + ThisConfig.options()); + std::string SectionTo = + To->Fragment->getOwningSection()->getDecoratedName(ThisConfig.options()); + ThisConfig.raise(Diag::merging_constant_fragments) + << FileFrom << SectionFrom << From->InputOffset << From->getAlignment() + << FileTo << SectionTo << To->InputOffset << To->getAlignment() + << OutputSectionName; +} + void ObjectBuilder::mergeStrings(MergeStringFragment *F, OutputSectionEntry *O) { for (MergeableString &S : F->getStrings()) { @@ -199,6 +225,20 @@ void ObjectBuilder::mergeStrings(MergeStringFragment *F, } } +void ObjectBuilder::mergeConstants(MergeDataFragment *F, + OutputSectionEntry *O) { + for (MergeableConstant &C : F->getConstants()) { + // Deduplicate by payload while honoring canonical-alignment constraints. + MergeableConstant &MergedConstant = + MergeDataFragment::getOrAddMergedConstant(&C, O); + if (&MergedConstant == &C) + continue; + if (config().getPrinter()->traceMergeConstants() || + config().getPrinter()->isVerbose()) + traceMergeConstants(&C, &MergedConstant); + } +} + /// moveSection - move the fragments of pTO section data to pTo bool ObjectBuilder::moveSection(ELFSection *PFrom, ELFSection *PTo) { assert(PFrom != PTo && "Cannot move section data to itself!"); diff --git a/lib/Object/ObjectLinker.cpp b/lib/Object/ObjectLinker.cpp index c6ea12f4b..9f738ebea 100644 --- a/lib/Object/ObjectLinker.cpp +++ b/lib/Object/ObjectLinker.cpp @@ -17,6 +17,7 @@ #include "eld/Core/Module.h" #include "eld/Diagnostics/DiagnosticEngine.h" #include "eld/Driver/GnuLdDriver.h" +#include "eld/Fragment/MergeDataFragment.h" #include "eld/GarbageCollection/GarbageCollection.h" #include "eld/Input/ArchiveMemberInput.h" #include "eld/Input/BitcodeFile.h" @@ -701,7 +702,50 @@ void ObjectLinker::mergeIdenticalStrings() const { Pool->wait(); } +void ObjectLinker::mergeIdenticalConstants() const { + ObjectBuilder Builder(ThisConfig, *ThisModule); + bool UseThreads = ThisConfig.useThreads(); + llvm::ThreadPoolInterface *Pool = ThisModule->getThreadPool(); + auto MergeConstants = [&](OutputSectionEntry *O) { + for (RuleContainer *RC : *O) { + for (Fragment *F : RC->getSection()->getFragmentList()) { + auto *Constants = llvm::dyn_cast(F); + if (!Constants) + continue; + Builder.mergeConstants(Constants, O); + } + } + }; + + std::vector OutputSections; + // Collect every unique output section once (regular section table entries + + // orphan sections from the script map) and run constant dedup per-section. + for (ELFSection *S : *ThisModule) { + if (!S->getOutputSection()) + continue; + if (S->isRelocationSection()) + continue; + OutputSections.push_back(S->getOutputSection()); + } + for (OutputSectionEntry *O : ThisModule->getScript().sectionMap()) + OutputSections.push_back(O); + + for (OutputSectionEntry *O : OutputSections) { + if (UseThreads) + Pool->async(std::bind(MergeConstants, O)); + else + MergeConstants(O); + } + if (UseThreads) + Pool->wait(); +} + void ObjectLinker::fixMergeStringRelocations() const { + bool MergeStrings = ThisConfig.options().mergeStrings(); + bool MergeConstants = ThisConfig.options().getMergeConstants(); + + // Despite the historical name, this pass retargets relocations for both + // merge-strings and merge-constants after canonicalization. for (InputFile *I : ThisModule->getObjectList()) { if (I->isInternal()) continue; @@ -709,12 +753,20 @@ void ObjectLinker::fixMergeStringRelocations() const { if (!Obj) continue; for (ELFSection *S : Obj->getRelocationSections()) { - if (ThisModule->getPrinter()->isVerbose() || - ThisModule->getPrinter()->traceMergeStrings()) + if (MergeStrings && (ThisModule->getPrinter()->isVerbose() || + ThisModule->getPrinter()->traceMergeStrings())) ThisConfig.raise(Diag::handling_merge_strings_for_section) << S->getDecoratedName(ThisConfig.options()) << S->getInputFile()->getInput()->decoratedPath(true); - getTargetBackend().getRelocator()->doMergeStrings(S); + if (MergeConstants && (ThisModule->getPrinter()->isVerbose() || + ThisModule->getPrinter()->traceMergeConstants())) + ThisConfig.raise(Diag::handling_merge_constants_for_section) + << S->getDecoratedName(ThisConfig.options()) + << S->getInputFile()->getInput()->decoratedPath(true); + if (MergeStrings) + getTargetBackend().getRelocator()->doMergeStrings(S); + if (MergeConstants) + getTargetBackend().getRelocator()->doMergeConstants(S); } } } @@ -722,8 +774,13 @@ void ObjectLinker::fixMergeStringRelocations() const { void ObjectLinker::doMergeStrings() { if (ThisConfig.isLinkPartial()) return; - mergeIdenticalStrings(); - fixMergeStringRelocations(); + if (ThisConfig.options().mergeStrings()) + mergeIdenticalStrings(); + if (ThisConfig.options().getMergeConstants()) + mergeIdenticalConstants(); + if (ThisConfig.options().mergeStrings() || + ThisConfig.options().getMergeConstants()) + fixMergeStringRelocations(); } void ObjectLinker::assignOutputSections(std::vector &Inputs) { diff --git a/lib/Readers/Relocation.cpp b/lib/Readers/Relocation.cpp index c2b2861c8..a7227346b 100644 --- a/lib/Readers/Relocation.cpp +++ b/lib/Readers/Relocation.cpp @@ -15,6 +15,7 @@ #include "eld/SymbolResolver/LDSymbol.h" #include "eld/SymbolResolver/ResolveInfo.h" #include "eld/Target/Relocator.h" +#include "llvm/BinaryFormat/ELF.h" #include "llvm/Support/ManagedStatic.h" namespace eld { @@ -355,5 +356,6 @@ bool Relocation::isMergeKind() const { FragmentRef *Target = targetFragRef() ? targetFragRef() : symInfo()->outSymbol()->fragRef(); return Target && Target->frag() && - Target->frag()->getOwningSection()->isMergeKind(); + (Target->frag()->getOwningSection()->getFlags() & + llvm::ELF::SHF_MERGE); } diff --git a/lib/Target/GNULDBackend.cpp b/lib/Target/GNULDBackend.cpp index 8ec739526..cfa1e5cab 100644 --- a/lib/Target/GNULDBackend.cpp +++ b/lib/Target/GNULDBackend.cpp @@ -24,6 +24,7 @@ #include "eld/Fragment/EhFrameFragment.h" #include "eld/Fragment/FillFragment.h" #include "eld/Fragment/GNUHashFragment.h" +#include "eld/Fragment/MergeDataFragment.h" #ifdef ELD_ENABLE_SYMBOL_VERSIONING #include "eld/Fragment/GNUVerDefFragment.h" #include "eld/Fragment/GNUVerNeedFragment.h" @@ -1079,7 +1080,14 @@ void GNULDBackend::sizeSymTab() { bool GNULDBackend::readSection(InputFile &pInput, ELFSection *S) { Fragment *F = nullptr; static LayoutInfo *layoutInfo = m_Module.getLayoutInfo(); - if (!S->size() || S->isNoBits()) + // Constant-data dedup is modeled as a dedicated fragment kind only for + // non-partial links. Partial links must preserve merge inputs as-is. + if (S->isMergeData() && S->getEntSize() != 0 && !config().isLinkPartial()) { + auto *MergeData = make(S); + if (!MergeData->readConstants(config())) + return false; + F = MergeData; + } else if (!S->size() || S->isNoBits()) F = make(getModule(), 0x0, S->size(), S, S->getAddrAlign()); else { llvm::StringRef R = pInput.getSlice(S->offset(), S->size()); diff --git a/lib/Target/Relocator.cpp b/lib/Target/Relocator.cpp index cc605e644..35a4bafb7 100644 --- a/lib/Target/Relocator.cpp +++ b/lib/Target/Relocator.cpp @@ -14,8 +14,10 @@ #include "eld/Config/Config.h" #include "eld/Core/Module.h" #include "eld/Fragment/Fragment.h" +#include "eld/Fragment/MergeDataFragment.h" #include "eld/Fragment/MergeStringFragment.h" #include "eld/Input/ELFObjectFile.h" +#include "eld/Object/OutputSectionEntry.h" #include "eld/Readers/ELFSection.h" #include "eld/Support/Memory.h" #include "eld/Support/MsgHandling.h" @@ -89,6 +91,35 @@ void Relocator::traceMergeStrings(const ELFSection *RelocationSection, << OldFile << NewOffset << NewSection << NewFile; } +void Relocator::traceMergeConstants(const ELFSection *RelocationSection, + const Relocation *R, + const MergeableConstant *From, + const MergeableConstant *To) const { + std::string SymName = R->symInfo()->name(); + uint32_t Addend = R->addend(); + std::string Section = RelocationSection->getDecoratedName(m_Config.options()); + std::string File = + RelocationSection->getInputFile()->getInput()->decoratedPath(); + uint32_t OldOffset = From->InputOffset; + std::string OldSection = + From->Fragment->getOwningSection()->getDecoratedName(m_Config.options()); + std::string OldFile = From->Fragment->getOwningSection() + ->getInputFile() + ->getInput() + ->decoratedPath(); + uint32_t NewOffset = To->InputOffset; + std::string NewSection = + To->Fragment->getOwningSection()->getDecoratedName(m_Config.options()); + std::string NewFile = To->Fragment->getOwningSection() + ->getInputFile() + ->getInput() + ->decoratedPath(); + + m_Config.raise(Diag::modifying_mergeconst_reloc) + << SymName << Addend << Section << File << OldOffset << OldSection + << OldFile << NewOffset << NewSection << NewFile; +} + std::pair Relocator::findFragmentForMergeStr(const ELFSection *RelocationSection, const Relocation *R, @@ -123,8 +154,9 @@ bool Relocator::doMergeStrings(ELFSection *S) { FragmentRef *OldTarget = R->targetFragRef() ? R->targetFragRef() : R->symInfo()->outSymbol()->fragRef(); - - auto *MSF = llvm::cast(OldTarget->frag()); + auto *MSF = llvm::dyn_cast(OldTarget->frag()); + if (!MSF) + return; auto [Frag, Offset] = findFragmentForMergeStr(S, R, MSF); if (!Frag) return; @@ -138,6 +170,46 @@ bool Relocator::doMergeStrings(ELFSection *S) { return true; } +bool Relocator::doMergeConstants(ELFSection *S) { + auto DoMergeConstReloc = [&](Relocation *R) -> void { + if (!R->isMergeKind() || !R->symInfo()->isSection() || + getTarget().maySkipRelocProcessing(R)) + return; + FragmentRef *OldTarget = R->targetFragRef() + ? R->targetFragRef() + : R->symInfo()->outSymbol()->fragRef(); + auto *MDF = llvm::dyn_cast(OldTarget->frag()); + if (!MDF) + return; + + uint32_t Addend = getAddend(R); + const MergeableConstant *C = MDF->getConstantAtOffset(Addend); + if (!C) + return; + uint32_t OffsetInConstant = Addend - C->InputOffset; + OutputSectionEntry *OutputSection = + MDF->getOwningSection()->getOutputSection(); + if (const MergeableConstant *Merged = OutputSection->getMergedConstant(C)) { + if (m_Config.getPrinter()->isVerbose() || + m_Config.getPrinter()->traceMergeConstants()) + traceMergeConstants(S, R, C, Merged); + C = Merged; + } + + // Recompute architecture-specific addend before replacing the fragment + // target. Some targets encode/track addends differently (e.g. Hexagon). + adjustAddend(R); + // Point directly at the selected canonical constant while preserving the + // original intra-constant byte offset. + R->modifyRelocationFragmentRef( + make(*C->Fragment, C->InputOffset + OffsetInConstant)); + }; + + for (Relocation *R : S->getLink()->getRelocations()) + DoMergeConstReloc(R); + return true; +} + std::string Relocator::getSymbolName(const ResolveInfo *R) const { return m_Module.getNamePool().getDecoratedName( R, m_Config.options().shouldDemangle()); diff --git a/test/AArch64/standalone/MergeConstants/DebugInfo.test b/test/AArch64/standalone/MergeConstants/DebugInfo.test new file mode 100644 index 000000000..2ce99f6a4 --- /dev/null +++ b/test/AArch64/standalone/MergeConstants/DebugInfo.test @@ -0,0 +1,24 @@ +#---DebugInfo.test--------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that DWARF variable locations continue to point at the deduplicated +# constant after mergeable constant sections are merged. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -fmerge-all-constants -g -c %p/Inputs/debugvar1.c -o %t1.o +RUN: %clang %clangopts -fmerge-all-constants -g -c %p/Inputs/debugvar2.c -o %t2.o +RUN: %link %linkopts --entry=fa %t1.o %t2.o -o %t.out +RUN: %dwarfdump -debug-info %t.out | %filecheck %s --check-prefix=INFO +RUN: %dwarfdump --debug-addr %t.out | %filecheck %s --check-prefix=ADDR +#END_TEST + +#INFO: DW_AT_name ("ca") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#INFO: DW_AT_name ("cb") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: ] +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} diff --git a/test/AArch64/standalone/MergeConstants/Inputs/debugvar1.c b/test/AArch64/standalone/MergeConstants/Inputs/debugvar1.c new file mode 100644 index 000000000..4ddc4d870 --- /dev/null +++ b/test/AArch64/standalone/MergeConstants/Inputs/debugvar1.c @@ -0,0 +1,4 @@ +float fa(void) { + static const float ca = 3.5f; + return ca; +} diff --git a/test/AArch64/standalone/MergeConstants/Inputs/debugvar2.c b/test/AArch64/standalone/MergeConstants/Inputs/debugvar2.c new file mode 100644 index 000000000..3eceb6005 --- /dev/null +++ b/test/AArch64/standalone/MergeConstants/Inputs/debugvar2.c @@ -0,0 +1,4 @@ +float fb(void) { + static const float cb = 3.5f; + return cb; +} diff --git a/test/AArch64/standalone/MergeConstants/Inputs/picconst1.c b/test/AArch64/standalone/MergeConstants/Inputs/picconst1.c new file mode 100644 index 000000000..396daace9 --- /dev/null +++ b/test/AArch64/standalone/MergeConstants/Inputs/picconst1.c @@ -0,0 +1,15 @@ +extern const float mc1; + +// Emit a mergeable 4-byte constant from C so the PIC test still exercises +// SHF_MERGE constant deduplication and trace output. +__asm__(".section .rodata.cst4,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl mc1\n" + ".type mc1,@object\n" + "mc1:\n" + ".long 0x40600000\n" + ".size mc1, 4\n" + ".text\n"); + +const float *fp1(void) { return &mc1; } +float scale1(void) { return *fp1(); } diff --git a/test/AArch64/standalone/MergeConstants/Inputs/picconst2.c b/test/AArch64/standalone/MergeConstants/Inputs/picconst2.c new file mode 100644 index 000000000..cd48c5ca9 --- /dev/null +++ b/test/AArch64/standalone/MergeConstants/Inputs/picconst2.c @@ -0,0 +1,14 @@ +extern const float mc2; + +// Twin definition of mc1's bytes in a separate TU so the linker can merge it. +__asm__(".section .rodata.cst4,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl mc2\n" + ".type mc2,@object\n" + "mc2:\n" + ".long 0x40600000\n" + ".size mc2, 4\n" + ".text\n"); + +const float *fp2(void) { return &mc2; } +float scale2(void) { return *fp2(); } diff --git a/test/AArch64/standalone/MergeConstants/PIC.test b/test/AArch64/standalone/MergeConstants/PIC.test new file mode 100644 index 000000000..3e10f68a1 --- /dev/null +++ b/test/AArch64/standalone/MergeConstants/PIC.test @@ -0,0 +1,20 @@ +#---PIC.test--------------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that compiler-generated -fPIC constant-data accesses still merge when +# the constant is placed in a SHF_MERGE non-string section. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/picconst1.c -o %t1.o +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/picconst2.c -o %t2.o +RUN: %link %linkopts --entry=scale1 --trace=merge-constants %t1.o %t2.o -o %t.out 2>&1 | %filecheck %s --check-prefix=TRACE +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +#END_TEST + +#TRACE: Trace: Merging constant from file {{.*}}2.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: with constant from file {{.*}}1.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: to output section .rodata + +#SECTIONS: .rodata {{.*}} 000004 {{.*}} AM {{.*}} 4 +#SYMS-DAG: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale1 +#SYMS-DAG: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale2 diff --git a/test/ARM/standalone/MergeConstants/DebugInfo.test b/test/ARM/standalone/MergeConstants/DebugInfo.test new file mode 100644 index 000000000..2ce99f6a4 --- /dev/null +++ b/test/ARM/standalone/MergeConstants/DebugInfo.test @@ -0,0 +1,24 @@ +#---DebugInfo.test--------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that DWARF variable locations continue to point at the deduplicated +# constant after mergeable constant sections are merged. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -fmerge-all-constants -g -c %p/Inputs/debugvar1.c -o %t1.o +RUN: %clang %clangopts -fmerge-all-constants -g -c %p/Inputs/debugvar2.c -o %t2.o +RUN: %link %linkopts --entry=fa %t1.o %t2.o -o %t.out +RUN: %dwarfdump -debug-info %t.out | %filecheck %s --check-prefix=INFO +RUN: %dwarfdump --debug-addr %t.out | %filecheck %s --check-prefix=ADDR +#END_TEST + +#INFO: DW_AT_name ("ca") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#INFO: DW_AT_name ("cb") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: ] +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} diff --git a/test/ARM/standalone/MergeConstants/Inputs/debugvar1.c b/test/ARM/standalone/MergeConstants/Inputs/debugvar1.c new file mode 100644 index 000000000..4ddc4d870 --- /dev/null +++ b/test/ARM/standalone/MergeConstants/Inputs/debugvar1.c @@ -0,0 +1,4 @@ +float fa(void) { + static const float ca = 3.5f; + return ca; +} diff --git a/test/ARM/standalone/MergeConstants/Inputs/debugvar2.c b/test/ARM/standalone/MergeConstants/Inputs/debugvar2.c new file mode 100644 index 000000000..3eceb6005 --- /dev/null +++ b/test/ARM/standalone/MergeConstants/Inputs/debugvar2.c @@ -0,0 +1,4 @@ +float fb(void) { + static const float cb = 3.5f; + return cb; +} diff --git a/test/ARM/standalone/MergeConstants/Inputs/picconst1.c b/test/ARM/standalone/MergeConstants/Inputs/picconst1.c new file mode 100644 index 000000000..fdce257bb --- /dev/null +++ b/test/ARM/standalone/MergeConstants/Inputs/picconst1.c @@ -0,0 +1,15 @@ +extern const float mc1; + +// Emit a mergeable 4-byte constant from C so the PIC test still exercises +// SHF_MERGE constant deduplication and trace output. +__asm__(".section .rodata.cst4,\"aM\",%progbits,4\n" + ".p2align 2\n" + ".globl mc1\n" + ".type mc1,%object\n" + "mc1:\n" + ".long 0x40600000\n" + ".size mc1, 4\n" + ".text\n"); + +const float *fp1(void) { return &mc1; } +float scale1(void) { return *fp1(); } diff --git a/test/ARM/standalone/MergeConstants/Inputs/picconst2.c b/test/ARM/standalone/MergeConstants/Inputs/picconst2.c new file mode 100644 index 000000000..323cbf46f --- /dev/null +++ b/test/ARM/standalone/MergeConstants/Inputs/picconst2.c @@ -0,0 +1,14 @@ +extern const float mc2; + +// Twin definition of mc1's bytes in a separate TU so the linker can merge it. +__asm__(".section .rodata.cst4,\"aM\",%progbits,4\n" + ".p2align 2\n" + ".globl mc2\n" + ".type mc2,%object\n" + "mc2:\n" + ".long 0x40600000\n" + ".size mc2, 4\n" + ".text\n"); + +const float *fp2(void) { return &mc2; } +float scale2(void) { return *fp2(); } diff --git a/test/ARM/standalone/MergeConstants/PIC.test b/test/ARM/standalone/MergeConstants/PIC.test new file mode 100644 index 000000000..3e10f68a1 --- /dev/null +++ b/test/ARM/standalone/MergeConstants/PIC.test @@ -0,0 +1,20 @@ +#---PIC.test--------------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that compiler-generated -fPIC constant-data accesses still merge when +# the constant is placed in a SHF_MERGE non-string section. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/picconst1.c -o %t1.o +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/picconst2.c -o %t2.o +RUN: %link %linkopts --entry=scale1 --trace=merge-constants %t1.o %t2.o -o %t.out 2>&1 | %filecheck %s --check-prefix=TRACE +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +#END_TEST + +#TRACE: Trace: Merging constant from file {{.*}}2.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: with constant from file {{.*}}1.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: to output section .rodata + +#SECTIONS: .rodata {{.*}} 000004 {{.*}} AM {{.*}} 4 +#SYMS-DAG: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale1 +#SYMS-DAG: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale2 diff --git a/test/Common/standalone/MergeConstants/AlignmentOrder.test b/test/Common/standalone/MergeConstants/AlignmentOrder.test new file mode 100644 index 000000000..7f0a4f864 --- /dev/null +++ b/test/Common/standalone/MergeConstants/AlignmentOrder.test @@ -0,0 +1,25 @@ +#---AlignmentOrder.test---------------- Executable----------------------# +#BEGIN_COMMENT +# Check that identical SHF_MERGE constant data only merges when the canonical +# fragment has equal-or-stricter alignment, independent of input order. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/1.c -o %t1.o +RUN: %clang %clangopts -c %p/Inputs/2.c -o %t2.o +RUN: %link %linkopts --entry=c1 %t1.o %t2.o -o %t.out +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +RUN: %link %linkopts --entry=c2 %t2.o %t1.o -o %t.rev.out +RUN: %readelf -S -W %t.rev.out | %filecheck %s --check-prefix=REV-SECTIONS +RUN: %readelf -s -W %t.rev.out | %filecheck %s --check-prefix=REV-SYMS +#END_TEST + +#SECTIONS: .rodata {{.*}} 000004 {{.*}} AM {{.*}} 4 +#SECTIONS-NOT: .rodata {{.*}} AMS +#SYMS-DAG: {{[0-9]+}}: [[CVAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c1 +#SYMS-DAG: {{[0-9]+}}: [[CVAL]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 + +#REV-SECTIONS: .rodata {{.*}} 000004 {{.*}} AM {{.*}} 4 +#REV-SECTIONS-NOT: .rodata {{.*}} AMS +#REV-SYMS-DAG: {{[0-9]+}}: [[RCVAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 +#REV-SYMS-DAG: {{[0-9]+}}: [[RCVAL]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c1 diff --git a/test/Common/standalone/MergeConstants/AlignmentPromotion.test b/test/Common/standalone/MergeConstants/AlignmentPromotion.test new file mode 100644 index 000000000..690195a9b --- /dev/null +++ b/test/Common/standalone/MergeConstants/AlignmentPromotion.test @@ -0,0 +1,18 @@ +#---AlignmentPromotion.test------------ Executable----------------------# +#BEGIN_COMMENT +# Check that later duplicates with stricter alignment replace the canonical +# constant and that all references converge to the strongest alignment. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/7.c -o %t7.o +RUN: %clang %clangopts -c %p/Inputs/8.c -o %t8.o +RUN: %clang %clangopts -c %p/Inputs/9.c -o %t9.o +RUN: %link %linkopts --entry=t1 %t7.o %t8.o %t9.o -o %t.out +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +#END_TEST + +#SECTIONS: .rodata {{.*}} 000004 {{.*}} AM {{.*}} 16 +#SYMS-DAG: {{[0-9]+}}: [[TVAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} t1 +#SYMS-DAG: {{[0-9]+}}: [[TVAL]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} t2 +#SYMS-DAG: {{[0-9]+}}: [[TVAL]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} t3 diff --git a/test/Common/standalone/MergeConstants/DistinctPayload.test b/test/Common/standalone/MergeConstants/DistinctPayload.test new file mode 100644 index 000000000..9e04f04bc --- /dev/null +++ b/test/Common/standalone/MergeConstants/DistinctPayload.test @@ -0,0 +1,17 @@ +#---DistinctPayload.test--------------- Executable----------------------# +#BEGIN_COMMENT +# Check that distinct constant payloads remain distinct even when section flags +# and entry sizes match. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/5.c -o %t5.o +RUN: %clang %clangopts -c %p/Inputs/6.c -o %t6.o +RUN: %link %linkopts --entry=d1 %t5.o %t6.o -o %t.out +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +#END_TEST + +#SECTIONS: .rodata {{.*}} 000008 {{.*}} AM +#SYMS-DAG: {{[0-9]+}}: [[D1VAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} d1 +#SYMS-DAG: {{[0-9]+}}: [[D2VAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} d2 +#SYMS-NOT: [[D1VAL]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} d2 diff --git a/test/Common/standalone/MergeConstants/EntSize.test b/test/Common/standalone/MergeConstants/EntSize.test new file mode 100644 index 000000000..ccdb2b0cd --- /dev/null +++ b/test/Common/standalone/MergeConstants/EntSize.test @@ -0,0 +1,20 @@ +#---EntSize.test----------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that mergeable constants deduplicate within a single entsize class and +# do not merge across sections with different entsize values. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/3.c -o %t3.o +RUN: %clang %clangopts -c %p/Inputs/4.c -o %t4.o +RUN: %link %linkopts --entry=e4a %t3.o %t4.o -o %t.out +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +#END_TEST + +#SECTIONS: .rodata {{.*}} 00000c {{.*}} AM +#SYMS-DAG: {{[0-9]+}}: [[E4VAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} e4a +#SYMS-DAG: {{[0-9]+}}: [[E4VAL]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} e4b +#SYMS-DAG: {{[0-9]+}}: [[E8VAL:[0-9a-f]+]] 8 OBJECT GLOBAL DEFAULT {{[0-9]+}} e8a +#SYMS-DAG: {{[0-9]+}}: [[E8VAL]] 8 OBJECT GLOBAL DEFAULT {{[0-9]+}} e8b +#SYMS-NOT: [[E4VAL]] 8 OBJECT GLOBAL DEFAULT +#SYMS-NOT: [[E8VAL]] 4 OBJECT GLOBAL DEFAULT diff --git a/test/Common/standalone/MergeConstants/Inputs/1.c b/test/Common/standalone/MergeConstants/Inputs/1.c new file mode 100644 index 000000000..20f5eac98 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/1.c @@ -0,0 +1,8 @@ +#include "asm-helper.h" +// Baseline mergeable constant: 4-byte payload, no explicit alignment directive. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".globl c1\n" + ".type c1, " OBJTYPE "\n" + ".size c1, 4\n" + "c1:\n" + ".4byte 0x11223344\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/2.c b/test/Common/standalone/MergeConstants/Inputs/2.c new file mode 100644 index 000000000..de73bd16c --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/2.c @@ -0,0 +1,10 @@ +#include "asm-helper.h" + +// Same payload as 1.c, but explicitly aligned to 4 bytes. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".p2align 2\n" + ".globl c2\n" + ".type c2, " OBJTYPE "\n" + ".size c2, 4\n" + "c2:\n" + ".4byte 0x11223344\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/3.c b/test/Common/standalone/MergeConstants/Inputs/3.c new file mode 100644 index 000000000..e66b1287e --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/3.c @@ -0,0 +1,13 @@ +#include "asm-helper.h" +// Two identical constants in entsize-4 merge section; should fold to one value. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".globl e4a\n" + ".type e4a, " OBJTYPE "\n" + ".size e4a, 4\n" + "e4a:\n" + ".4byte 0x11223344\n" + ".globl e4b\n" + ".type e4b, " OBJTYPE "\n" + ".size e4b, 4\n" + "e4b:\n" + ".4byte 0x11223344\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/4.c b/test/Common/standalone/MergeConstants/Inputs/4.c new file mode 100644 index 000000000..e55497e95 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/4.c @@ -0,0 +1,13 @@ +#include "asm-helper.h" +// Same payload pattern as 3.c but in entsize-8 section; must not cross-merge. +__asm__(".section .rodata.cst8,\"aM\"," SECTYPE ",8\n" + ".globl e8a\n" + ".type e8a, " OBJTYPE "\n" + ".size e8a, 8\n" + "e8a:\n" + ".8byte 0x11223344\n" + ".globl e8b\n" + ".type e8b, " OBJTYPE "\n" + ".size e8b, 8\n" + "e8b:\n" + ".8byte 0x11223344\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/5.c b/test/Common/standalone/MergeConstants/Inputs/5.c new file mode 100644 index 000000000..a1c8ff6f1 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/5.c @@ -0,0 +1,8 @@ +#include "asm-helper.h" +// DistinctPayload input A: mergeable constant with unique data pattern. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".globl d1\n" + ".type d1, " OBJTYPE "\n" + ".size d1, 4\n" + "d1:\n" + ".4byte 0x11223344\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/6.c b/test/Common/standalone/MergeConstants/Inputs/6.c new file mode 100644 index 000000000..59327590a --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/6.c @@ -0,0 +1,8 @@ +#include "asm-helper.h" +// DistinctPayload input B: different bytes from 5.c, so it must stay separate. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".globl d2\n" + ".type d2, " OBJTYPE "\n" + ".size d2, 4\n" + "d2:\n" + ".4byte 0x55667788\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/7.c b/test/Common/standalone/MergeConstants/Inputs/7.c new file mode 100644 index 000000000..d350eab1e --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/7.c @@ -0,0 +1,9 @@ +#include "asm-helper.h" +// Alignment promotion chain start: payload shared by t1/t2/t3 with weak +// alignment. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".globl t1\n" + ".type t1, " OBJTYPE "\n" + ".size t1, 4\n" + "t1:\n" + ".4byte 0x99aabbcc\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/8.c b/test/Common/standalone/MergeConstants/Inputs/8.c new file mode 100644 index 000000000..1a87be304 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/8.c @@ -0,0 +1,10 @@ +#include "asm-helper.h" +// Same payload as 7.c with 4-byte alignment; candidate should replace weaker +// one. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".p2align 2\n" + ".globl t2\n" + ".type t2, " OBJTYPE "\n" + ".size t2, 4\n" + "t2:\n" + ".4byte 0x99aabbcc\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/9.c b/test/Common/standalone/MergeConstants/Inputs/9.c new file mode 100644 index 000000000..df2412aa6 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/9.c @@ -0,0 +1,10 @@ +#include "asm-helper.h" +// Same payload as 7.c/8.c with stricter 16-byte alignment; final canonical +// pick. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".p2align 4\n" + ".globl t3\n" + ".type t3, " OBJTYPE "\n" + ".size t3, 4\n" + "t3:\n" + ".4byte 0x99aabbcc\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/TraceMerge1.c b/test/Common/standalone/MergeConstants/Inputs/TraceMerge1.c new file mode 100644 index 000000000..97cf4c205 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/TraceMerge1.c @@ -0,0 +1,10 @@ +#include "asm-helper.h" +// Provides _start and one mergeable constant fragment used as the merge target. +__asm__(".text\n" + ".globl _start\n" + "_start:\n" + "nop\n" + ".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".p2align 2\n" + "m1:\n" + ".4byte 0x11223344\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/TraceMerge2.c b/test/Common/standalone/MergeConstants/Inputs/TraceMerge2.c new file mode 100644 index 000000000..946c7a9b7 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/TraceMerge2.c @@ -0,0 +1,6 @@ +#include "asm-helper.h" +// Second identical mergeable fragment used to trigger merge tracing output. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".p2align 2\n" + "m2:\n" + ".4byte 0x11223344\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/TraceReloc1.c b/test/Common/standalone/MergeConstants/Inputs/TraceReloc1.c new file mode 100644 index 000000000..6ad57660b --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/TraceReloc1.c @@ -0,0 +1,12 @@ +#include "asm-helper.h" +// Defines _start, a mergeable constant, and a .data relocation to that section. +__asm__(".text\n" + ".globl _start\n" + "_start:\n" + "nop\n" + ".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + "r1:\n" + ".4byte 0x11223344\n" + ".data\n" + "p1:\n" + ".4byte .rodata.cst4+0\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/TraceReloc2.c b/test/Common/standalone/MergeConstants/Inputs/TraceReloc2.c new file mode 100644 index 000000000..2ac5ad383 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/TraceReloc2.c @@ -0,0 +1,10 @@ +#include "asm-helper.h" +// Companion fragment for TraceReloc1.c; forces relocation retarget trace +// updates. +__asm__(".section .rodata.cst4,\"aM\"," SECTYPE ",4\n" + ".p2align 4\n" + "r2:\n" + ".4byte 0x11223344\n" + ".data\n" + "p2:\n" + ".4byte .rodata.cst4+0\n"); diff --git a/test/Common/standalone/MergeConstants/Inputs/asm-helper.h b/test/Common/standalone/MergeConstants/Inputs/asm-helper.h new file mode 100644 index 000000000..af0b09eaf --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/asm-helper.h @@ -0,0 +1,7 @@ +#if defined(__arm__) +#define SECTYPE "%progbits" +#define OBJTYPE "%object" +#else +#define SECTYPE "@progbits" +#define OBJTYPE "@object" +#endif diff --git a/test/Common/standalone/MergeConstants/Inputs/debugvar1.c b/test/Common/standalone/MergeConstants/Inputs/debugvar1.c new file mode 100644 index 000000000..4ddc4d870 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/debugvar1.c @@ -0,0 +1,4 @@ +float fa(void) { + static const float ca = 3.5f; + return ca; +} diff --git a/test/Common/standalone/MergeConstants/Inputs/debugvar2.c b/test/Common/standalone/MergeConstants/Inputs/debugvar2.c new file mode 100644 index 000000000..3eceb6005 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/debugvar2.c @@ -0,0 +1,4 @@ +float fb(void) { + static const float cb = 3.5f; + return cb; +} diff --git a/test/Common/standalone/MergeConstants/Inputs/partial-link-script.t b/test/Common/standalone/MergeConstants/Inputs/partial-link-script.t new file mode 100644 index 000000000..00a9f6b1c --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/partial-link-script.t @@ -0,0 +1,3 @@ +SECTIONS { + .rodata : { *(.rodata .rodata.*) } +} diff --git a/test/Common/standalone/MergeConstants/Inputs/pic1.c b/test/Common/standalone/MergeConstants/Inputs/pic1.c new file mode 100644 index 000000000..e9ede1ff0 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/pic1.c @@ -0,0 +1 @@ +float scale1(float x) { return x * 3.5f; } diff --git a/test/Common/standalone/MergeConstants/Inputs/pic2.c b/test/Common/standalone/MergeConstants/Inputs/pic2.c new file mode 100644 index 000000000..96d5ce4e0 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/pic2.c @@ -0,0 +1 @@ +float scale2(float x) { return x * 3.5f; } diff --git a/test/Common/standalone/MergeConstants/Inputs/picconst1.c b/test/Common/standalone/MergeConstants/Inputs/picconst1.c new file mode 100644 index 000000000..d6f586906 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/picconst1.c @@ -0,0 +1,6 @@ +const float *fp1(void) { + static const float c = 3.5f; + return &c; +} + +float scale1(void) { return *fp1(); } diff --git a/test/Common/standalone/MergeConstants/Inputs/picconst2.c b/test/Common/standalone/MergeConstants/Inputs/picconst2.c new file mode 100644 index 000000000..f59be8d39 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Inputs/picconst2.c @@ -0,0 +1,6 @@ +const float *fp2(void) { + static const float c = 3.5f; + return &c; +} + +float scale2(void) { return *fp2(); } diff --git a/test/Common/standalone/MergeConstants/Map.test b/test/Common/standalone/MergeConstants/Map.test new file mode 100644 index 000000000..ce6e10b1a --- /dev/null +++ b/test/Common/standalone/MergeConstants/Map.test @@ -0,0 +1,14 @@ +#---Map.test--------------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that the map file shows the surviving canonical constant and the merged +# input constant underneath it. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/1.c -o %t1.o +RUN: %clang %clangopts -c %p/Inputs/2.c -o %t2.o +RUN: %link %linkopts --entry=c1 %t1.o %t2.o -o %t.out -Map %t.map +RUN: %filecheck %s < %t.map +#END_TEST + +#CHECK: .rodata.cst4{{[[:space:]]+}}0x{{[0-9a-f]+}}{{[[:space:]]+}}0x4{{[[:space:]]+}}{{.*}}2.o{{[[:space:]]+}}#SHT_PROGBITS,SHF_ALLOC|SHF_MERGE,4 +#CHECK: {{[[:space:]]+}}# .rodata.cst4{{[[:space:]]+}}0x0{{[[:space:]]+}}{{.*}}1.o diff --git a/test/Common/standalone/MergeConstants/NoMerge.test b/test/Common/standalone/MergeConstants/NoMerge.test new file mode 100644 index 000000000..88ee0fbf6 --- /dev/null +++ b/test/Common/standalone/MergeConstants/NoMerge.test @@ -0,0 +1,21 @@ +#---NoMerge.test----------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that --no-merge-constants disables canonicalization of identical +# SHF_MERGE constant data. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/1.c -o %t1.o +RUN: %clang %clangopts -c %p/Inputs/2.c -o %t2.o +RUN: %clang %clangopts -c %p/Inputs/TraceReloc1.c -o %t.reloc1.o +RUN: %clang %clangopts -c %p/Inputs/TraceReloc2.c -o %t.reloc2.o +RUN: %link %linkopts --entry=c1 --no-merge-constants %t1.o %t2.o -o %t.out +RUN: %link %linkopts --entry=c1 --trace=merge-constants --no-merge-constants %t1.o %t2.o -o %t.trace.out 2>&1 | %not %grep "Trace: Merging constant" +RUN: %link %linkopts --entry=_start --trace=merge-constants --no-merge-constants %t.reloc1.o %t.reloc2.o -o %t.trace.reloc.out 2>&1 | %not %grep "Trace: Modified relocation" +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +#END_TEST + +#SECTIONS: .rodata {{.*}} 000008 {{.*}} AM {{.*}} 4 +#SYMS-DAG: {{[0-9]+}}: [[C1VAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c1 +#SYMS-DAG: {{[0-9]+}}: [[C2VAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 +#SYMS-NOT: [[C1VAL]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 diff --git a/test/Common/standalone/MergeConstants/PIC.test b/test/Common/standalone/MergeConstants/PIC.test new file mode 100644 index 000000000..f1b931eea --- /dev/null +++ b/test/Common/standalone/MergeConstants/PIC.test @@ -0,0 +1,20 @@ +UNSUPPORTED: riscv32, riscv64, aarch64, arm, hexagon +#---PIC.test--------------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that compiler-generated mergeable constant data referenced from PIC code +# is still deduplicated. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/pic1.c -o %t1.o +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/pic2.c -o %t2.o +RUN: %link %linkopts --entry=scale1 --trace=merge-constants %t1.o %t2.o -o %t.out 2>&1 | %filecheck %s --check-prefix=TRACE +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +#END_TEST + +#TRACE: Trace: Merging constant from file {{.*}}2.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: with constant from file {{.*}}1.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: to output section .rodata +#SECTIONS: .rodata {{.*}} 000004 {{.*}} AM {{.*}} 4 +#SYMS: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale1 +#SYMS: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale2 diff --git a/test/Common/standalone/MergeConstants/PartialLink.test b/test/Common/standalone/MergeConstants/PartialLink.test new file mode 100644 index 000000000..9547779a4 --- /dev/null +++ b/test/Common/standalone/MergeConstants/PartialLink.test @@ -0,0 +1,31 @@ +#---PartialLink.test------------------ Relocatable/Executable-----------# +#BEGIN_COMMENT +# Check that SHF_ALLOC|SHF_MERGE non-string constants are not merged during +# partial linking (-r), matching merge-string behavior. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/1.c -o %t1.o +RUN: %clang %clangopts -c %p/Inputs/2.c -o %t2.o +RUN: %link %linkopts --entry=c1 %t1.o %t2.o -o %t.full.out +RUN: %readelf -s -W %t.full.out | %filecheck %s --check-prefix=FULL +RUN: %link %linkopts --entry=c1 %t1.o %t2.o -T %p/Inputs/partial-link-script.t -o %t.full.ls.out +RUN: %readelf -s -W %t.full.ls.out | %filecheck %s --check-prefix=FULL-LS +RUN: %link -r %linkopts %t1.o %t2.o -o %t.partial.o +RUN: %readelf -s -W %t.partial.o | %filecheck %s --check-prefix=PARTIAL +RUN: %link -r %linkopts %t1.o %t2.o -T %p/Inputs/partial-link-script.t -o %t.partial.ls.o +RUN: %readelf -s -W %t.partial.ls.o | %filecheck %s --check-prefix=PARTIAL-LS +#END_TEST + +#FULL-DAG: {{[0-9]+}}: [[FULLVAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c1 +#FULL-DAG: {{[0-9]+}}: [[FULLVAL]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 + +#FULL-LS-DAG: {{[0-9]+}}: [[FULLLSVAL:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c1 +#FULL-LS-DAG: {{[0-9]+}}: [[FULLLSVAL]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 + +#PARTIAL-DAG: {{[0-9]+}}: [[PVAL1:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c1 +#PARTIAL-DAG: {{[0-9]+}}: [[PVAL2:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 +#PARTIAL-NOT: [[PVAL1]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 + +#PARTIAL-LS-DAG: {{[0-9]+}}: [[PLSVAL1:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c1 +#PARTIAL-LS-DAG: {{[0-9]+}}: [[PLSVAL2:[0-9a-f]+]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 +#PARTIAL-LS-NOT: [[PLSVAL1]] 4 OBJECT GLOBAL DEFAULT {{[0-9]+}} c2 diff --git a/test/Common/standalone/MergeConstants/Trace.test b/test/Common/standalone/MergeConstants/Trace.test new file mode 100644 index 000000000..690a50094 --- /dev/null +++ b/test/Common/standalone/MergeConstants/Trace.test @@ -0,0 +1,21 @@ +#---Trace.test------------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that --trace=merge-constants reports both canonical constant merging +# and relocation retargeting when a section relocation is updated. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -c %p/Inputs/TraceMerge1.c -o %t.merge1.o +RUN: %clang %clangopts -c %p/Inputs/TraceMerge2.c -o %t.merge2.o +RUN: %link %linkopts --entry=_start --trace=merge-constants %t.merge1.o %t.merge2.o -o %t.merge.out 2>&1 | %filecheck %s --check-prefix=MERGE +RUN: %clang %clangopts -c %p/Inputs/TraceReloc1.c -o %t.reloc1.o +RUN: %clang %clangopts -c %p/Inputs/TraceReloc2.c -o %t.reloc2.o +RUN: %link %linkopts --entry=_start --trace=merge-constants %t.reloc1.o %t.reloc2.o -o %t.reloc.out 2>&1 | %filecheck %s --check-prefix=RELOC +#END_TEST + +#MERGE: Trace: Merging constant from file {{.*}}merge2.o in section .rodata.cst4 at offset 0 with alignment 4 +#MERGE: with constant from file {{.*}}merge1.o in section .rodata.cst4 at offset 0 with alignment 4 +#MERGE: to output section .rodata + +#RELOC: Trace: Modified relocation .rodata.cst4 + 0 from relocation section .rel{{a?}}.data in file {{.*}}reloc1.o: +#RELOC: Old constant: offset 0 in section .rodata.cst4 from file {{.*}}reloc1.o +#RELOC: New constant: offset 0 in section .rodata.cst4 from file {{.*}}reloc2.o diff --git a/test/Hexagon/standalone/MergeConstants/DebugInfo.test b/test/Hexagon/standalone/MergeConstants/DebugInfo.test new file mode 100644 index 000000000..2ce99f6a4 --- /dev/null +++ b/test/Hexagon/standalone/MergeConstants/DebugInfo.test @@ -0,0 +1,24 @@ +#---DebugInfo.test--------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that DWARF variable locations continue to point at the deduplicated +# constant after mergeable constant sections are merged. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -fmerge-all-constants -g -c %p/Inputs/debugvar1.c -o %t1.o +RUN: %clang %clangopts -fmerge-all-constants -g -c %p/Inputs/debugvar2.c -o %t2.o +RUN: %link %linkopts --entry=fa %t1.o %t2.o -o %t.out +RUN: %dwarfdump -debug-info %t.out | %filecheck %s --check-prefix=INFO +RUN: %dwarfdump --debug-addr %t.out | %filecheck %s --check-prefix=ADDR +#END_TEST + +#INFO: DW_AT_name ("ca") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#INFO: DW_AT_name ("cb") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: ] +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} diff --git a/test/Hexagon/standalone/MergeConstants/Inputs/debugvar1.c b/test/Hexagon/standalone/MergeConstants/Inputs/debugvar1.c new file mode 100644 index 000000000..4ddc4d870 --- /dev/null +++ b/test/Hexagon/standalone/MergeConstants/Inputs/debugvar1.c @@ -0,0 +1,4 @@ +float fa(void) { + static const float ca = 3.5f; + return ca; +} diff --git a/test/Hexagon/standalone/MergeConstants/Inputs/debugvar2.c b/test/Hexagon/standalone/MergeConstants/Inputs/debugvar2.c new file mode 100644 index 000000000..3eceb6005 --- /dev/null +++ b/test/Hexagon/standalone/MergeConstants/Inputs/debugvar2.c @@ -0,0 +1,4 @@ +float fb(void) { + static const float cb = 3.5f; + return cb; +} diff --git a/test/Hexagon/standalone/MergeConstants/Inputs/picconst1.c b/test/Hexagon/standalone/MergeConstants/Inputs/picconst1.c new file mode 100644 index 000000000..396daace9 --- /dev/null +++ b/test/Hexagon/standalone/MergeConstants/Inputs/picconst1.c @@ -0,0 +1,15 @@ +extern const float mc1; + +// Emit a mergeable 4-byte constant from C so the PIC test still exercises +// SHF_MERGE constant deduplication and trace output. +__asm__(".section .rodata.cst4,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl mc1\n" + ".type mc1,@object\n" + "mc1:\n" + ".long 0x40600000\n" + ".size mc1, 4\n" + ".text\n"); + +const float *fp1(void) { return &mc1; } +float scale1(void) { return *fp1(); } diff --git a/test/Hexagon/standalone/MergeConstants/Inputs/picconst2.c b/test/Hexagon/standalone/MergeConstants/Inputs/picconst2.c new file mode 100644 index 000000000..cd48c5ca9 --- /dev/null +++ b/test/Hexagon/standalone/MergeConstants/Inputs/picconst2.c @@ -0,0 +1,14 @@ +extern const float mc2; + +// Twin definition of mc1's bytes in a separate TU so the linker can merge it. +__asm__(".section .rodata.cst4,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl mc2\n" + ".type mc2,@object\n" + "mc2:\n" + ".long 0x40600000\n" + ".size mc2, 4\n" + ".text\n"); + +const float *fp2(void) { return &mc2; } +float scale2(void) { return *fp2(); } diff --git a/test/Hexagon/standalone/MergeConstants/PIC.test b/test/Hexagon/standalone/MergeConstants/PIC.test new file mode 100644 index 000000000..3e10f68a1 --- /dev/null +++ b/test/Hexagon/standalone/MergeConstants/PIC.test @@ -0,0 +1,20 @@ +#---PIC.test--------------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that compiler-generated -fPIC constant-data accesses still merge when +# the constant is placed in a SHF_MERGE non-string section. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/picconst1.c -o %t1.o +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/picconst2.c -o %t2.o +RUN: %link %linkopts --entry=scale1 --trace=merge-constants %t1.o %t2.o -o %t.out 2>&1 | %filecheck %s --check-prefix=TRACE +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +#END_TEST + +#TRACE: Trace: Merging constant from file {{.*}}2.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: with constant from file {{.*}}1.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: to output section .rodata + +#SECTIONS: .rodata {{.*}} 000004 {{.*}} AM {{.*}} 4 +#SYMS-DAG: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale1 +#SYMS-DAG: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale2 diff --git a/test/RISCV/standalone/MergeConstants/DebugInfo.test b/test/RISCV/standalone/MergeConstants/DebugInfo.test new file mode 100644 index 000000000..2ce99f6a4 --- /dev/null +++ b/test/RISCV/standalone/MergeConstants/DebugInfo.test @@ -0,0 +1,24 @@ +#---DebugInfo.test--------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that DWARF variable locations continue to point at the deduplicated +# constant after mergeable constant sections are merged. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -fmerge-all-constants -g -c %p/Inputs/debugvar1.c -o %t1.o +RUN: %clang %clangopts -fmerge-all-constants -g -c %p/Inputs/debugvar2.c -o %t2.o +RUN: %link %linkopts --entry=fa %t1.o %t2.o -o %t.out +RUN: %dwarfdump -debug-info %t.out | %filecheck %s --check-prefix=INFO +RUN: %dwarfdump --debug-addr %t.out | %filecheck %s --check-prefix=ADDR +#END_TEST + +#INFO: DW_AT_name ("ca") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#INFO: DW_AT_name ("cb") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: ] +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} diff --git a/test/RISCV/standalone/MergeConstants/Inputs/debugvar1.c b/test/RISCV/standalone/MergeConstants/Inputs/debugvar1.c new file mode 100644 index 000000000..4ddc4d870 --- /dev/null +++ b/test/RISCV/standalone/MergeConstants/Inputs/debugvar1.c @@ -0,0 +1,4 @@ +float fa(void) { + static const float ca = 3.5f; + return ca; +} diff --git a/test/RISCV/standalone/MergeConstants/Inputs/debugvar2.c b/test/RISCV/standalone/MergeConstants/Inputs/debugvar2.c new file mode 100644 index 000000000..3eceb6005 --- /dev/null +++ b/test/RISCV/standalone/MergeConstants/Inputs/debugvar2.c @@ -0,0 +1,4 @@ +float fb(void) { + static const float cb = 3.5f; + return cb; +} diff --git a/test/RISCV/standalone/MergeConstants/Inputs/picconst1.c b/test/RISCV/standalone/MergeConstants/Inputs/picconst1.c new file mode 100644 index 000000000..396daace9 --- /dev/null +++ b/test/RISCV/standalone/MergeConstants/Inputs/picconst1.c @@ -0,0 +1,15 @@ +extern const float mc1; + +// Emit a mergeable 4-byte constant from C so the PIC test still exercises +// SHF_MERGE constant deduplication and trace output. +__asm__(".section .rodata.cst4,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl mc1\n" + ".type mc1,@object\n" + "mc1:\n" + ".long 0x40600000\n" + ".size mc1, 4\n" + ".text\n"); + +const float *fp1(void) { return &mc1; } +float scale1(void) { return *fp1(); } diff --git a/test/RISCV/standalone/MergeConstants/Inputs/picconst2.c b/test/RISCV/standalone/MergeConstants/Inputs/picconst2.c new file mode 100644 index 000000000..cd48c5ca9 --- /dev/null +++ b/test/RISCV/standalone/MergeConstants/Inputs/picconst2.c @@ -0,0 +1,14 @@ +extern const float mc2; + +// Twin definition of mc1's bytes in a separate TU so the linker can merge it. +__asm__(".section .rodata.cst4,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl mc2\n" + ".type mc2,@object\n" + "mc2:\n" + ".long 0x40600000\n" + ".size mc2, 4\n" + ".text\n"); + +const float *fp2(void) { return &mc2; } +float scale2(void) { return *fp2(); } diff --git a/test/RISCV/standalone/MergeConstants/PIC.test b/test/RISCV/standalone/MergeConstants/PIC.test new file mode 100644 index 000000000..3e10f68a1 --- /dev/null +++ b/test/RISCV/standalone/MergeConstants/PIC.test @@ -0,0 +1,20 @@ +#---PIC.test--------------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that compiler-generated -fPIC constant-data accesses still merge when +# the constant is placed in a SHF_MERGE non-string section. +#END_COMMENT +#START_TEST +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/picconst1.c -o %t1.o +RUN: %clang %clangopts -fPIC -g -O0 -c %p/Inputs/picconst2.c -o %t2.o +RUN: %link %linkopts --entry=scale1 --trace=merge-constants %t1.o %t2.o -o %t.out 2>&1 | %filecheck %s --check-prefix=TRACE +RUN: %readelf -S -W %t.out | %filecheck %s --check-prefix=SECTIONS +RUN: %readelf -s -W %t.out | %filecheck %s --check-prefix=SYMS +#END_TEST + +#TRACE: Trace: Merging constant from file {{.*}}2.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: with constant from file {{.*}}1.o in section .rodata.cst4 at offset 0 with alignment 4 +#TRACE: to output section .rodata + +#SECTIONS: .rodata {{.*}} 000004 {{.*}} AM {{.*}} 4 +#SYMS-DAG: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale1 +#SYMS-DAG: {{[0-9]+}}: {{[0-9a-f]+}} {{[0-9]+}} FUNC GLOBAL DEFAULT {{[0-9]+}} scale2 diff --git a/test/x86_64/buildAndRunTests/MergeData/mergedata.golden b/test/x86_64/buildAndRunTests/MergeData/mergedata.golden new file mode 100644 index 000000000..6c833762b --- /dev/null +++ b/test/x86_64/buildAndRunTests/MergeData/mergedata.golden @@ -0,0 +1 @@ +CHECK: PASS: constants merged diff --git a/test/x86_64/buildAndRunTests/MergeData/mergedata.sh b/test/x86_64/buildAndRunTests/MergeData/mergedata.sh new file mode 100755 index 000000000..693eb9d1b --- /dev/null +++ b/test/x86_64/buildAndRunTests/MergeData/mergedata.sh @@ -0,0 +1,76 @@ +#REQUIRES: linux, x86 +#!/usr/bin/env bash +set -euo pipefail + +# Debug notes: +# - lit expands %t to a per-test temp directory; all generated files live there. +# - If this test fails, rerun with: +# llvm-lit -a -v /mergedata.sh +# to see the expanded commands and tool output. +# - The runtime assertion is simple: merge_test.elf must exit 0. +# - Printed output is checked against mergedata.golden. +BUILD_DIR=%t +%mkdir -p "$BUILD_DIR" + +cat > "$BUILD_DIR/a.s" <<'AS' + .section .rodata.cst4,"aM",@progbits,4 + .globl cst_a +cst_a: + .long 0x4048f5c3 + + .text + .globl get_a +get_a: + leaq cst_a(%rip), %rax + ret +AS + +cat > "$BUILD_DIR/b.s" <<'AS' + .section .rodata.cst4,"aM",@progbits,4 + .globl cst_b +cst_b: + .long 0x4048f5c3 + + .text + .globl get_b +get_b: + leaq cst_b(%rip), %rax + ret +AS + +cat > "$BUILD_DIR/start.s" <<'AS' + .text + .globl _start +_start: + call get_a + movq %rax, %rbx + call get_b + cmpq %rax, %rbx + jne .Lfail + + movl $0, %edi + jmp .Lexit + +.Lfail: + movl $1, %edi + +.Lexit: + movl $60, %eax + syscall +AS + +%clang %clangopts -c "$BUILD_DIR/a.s" -o "$BUILD_DIR/a.o" +%clang %clangopts -c "$BUILD_DIR/b.s" -o "$BUILD_DIR/b.o" +%clang %clangopts -c "$BUILD_DIR/start.s" -o "$BUILD_DIR/start.o" + +# Link with ld.eld and then execute. Exit code 0 means constants merged. +%eld --entry=_start "$BUILD_DIR/start.o" "$BUILD_DIR/a.o" "$BUILD_DIR/b.o" -o "$BUILD_DIR/merge_test.elf" + +# Optional breadcrumb: keep this grep output in logs to inspect rodata layout. +%readelf -S -W "$BUILD_DIR/merge_test.elf" | %grep -E '\.rodata|Name|Flg' || true +{ + %run "$BUILD_DIR/merge_test.elf" + echo "PASS: constants merged" +} | tee "$BUILD_DIR/mergedata.out" +%filecheck %p/mergedata.golden < "$BUILD_DIR/mergedata.out" +#CHECK: PASS: constants merged diff --git a/test/x86_64/buildAndRunTests/MergeDataPic/mergedatapic.golden b/test/x86_64/buildAndRunTests/MergeDataPic/mergedatapic.golden new file mode 100644 index 000000000..5e2e1be57 --- /dev/null +++ b/test/x86_64/buildAndRunTests/MergeDataPic/mergedatapic.golden @@ -0,0 +1,3 @@ +CHECK: MERGE_ON_RC=0 +CHECK: MERGE_OFF_RC=1 +CHECK: PASS: -fPIC merge-constants behavior verified diff --git a/test/x86_64/buildAndRunTests/MergeDataPic/mergedatapic.sh b/test/x86_64/buildAndRunTests/MergeDataPic/mergedatapic.sh new file mode 100755 index 000000000..75a121f25 --- /dev/null +++ b/test/x86_64/buildAndRunTests/MergeDataPic/mergedatapic.sh @@ -0,0 +1,92 @@ +#REQUIRES: linux, x86 +#!/usr/bin/env bash +set -euo pipefail + +# Debug notes: +# - Artifacts are emitted under %t (expanded by lit to a temp directory). +# - Rerun with verbosity when debugging: +# llvm-lit -a -v /mergedatapic.sh +# - Expectations: +# merge_on.elf -> exit 0 +# merge_off.elf -> non-zero (checked via %not) +# - Printed output is checked against mergedatapic.golden. +BUILD_DIR=%t +%mkdir -p "$BUILD_DIR" + +cat > "$BUILD_DIR/a_const.s" <<'SRC' + .section .rodata.cst4,"aM",@progbits,4 + .globl c_a + .type c_a, @object + .size c_a, 4 +c_a: + .long 0x4048f5c3 +SRC + +cat > "$BUILD_DIR/b_const.s" <<'SRC' + .section .rodata.cst4,"aM",@progbits,4 + .globl c_b + .type c_b, @object + .size c_b, 4 +c_b: + .long 0x4048f5c3 +SRC + +cat > "$BUILD_DIR/a_get.c" <<'SRC' +extern const unsigned c_a; +const unsigned *get_a(void) { return &c_a; } +SRC + +cat > "$BUILD_DIR/b_get.c" <<'SRC' +extern const unsigned c_b; +const unsigned *get_b(void) { return &c_b; } +SRC + +cat > "$BUILD_DIR/start.s" <<'SRC' + .text + .globl _start +_start: + call get_a + movq %rax, %rbx + call get_b + cmpq %rax, %rbx + jne .Lnot_merged + + movl $0, %edi + jmp .Lexit + +.Lnot_merged: + movl $1, %edi + +.Lexit: + movl $60, %eax + syscall +SRC + +%clang %clangopts -c "$BUILD_DIR/a_const.s" -o "$BUILD_DIR/a_const.o" +%clang %clangopts -c "$BUILD_DIR/b_const.s" -o "$BUILD_DIR/b_const.o" +%clang %clangopts -fPIC -ffreestanding -c "$BUILD_DIR/a_get.c" -o "$BUILD_DIR/a_get.o" +%clang %clangopts -fPIC -ffreestanding -c "$BUILD_DIR/b_get.c" -o "$BUILD_DIR/b_get.o" +%clang %clangopts -c "$BUILD_DIR/start.s" -o "$BUILD_DIR/start.o" + +# Case 1: default merge behavior should merge equal constants. +%eld --entry=_start "$BUILD_DIR/start.o" "$BUILD_DIR/a_const.o" "$BUILD_DIR/b_const.o" "$BUILD_DIR/a_get.o" "$BUILD_DIR/b_get.o" -o "$BUILD_DIR/merge_on.elf" +%run "$BUILD_DIR/merge_on.elf" + +# Case 2: --no-merge-constants should keep equal constants distinct. +%eld --no-merge-constants --entry=_start "$BUILD_DIR/start.o" "$BUILD_DIR/a_const.o" "$BUILD_DIR/b_const.o" "$BUILD_DIR/a_get.o" "$BUILD_DIR/b_get.o" -o "$BUILD_DIR/merge_off.elf" +set +e +%run "$BUILD_DIR/merge_off.elf" +merge_off_rc=$? +set -e +if [[ "$merge_off_rc" -eq 0 ]]; then + echo "FAIL: merge_off unexpectedly succeeded" >&2 + exit 1 +fi + +{ + echo "MERGE_ON_RC=0" + echo "MERGE_OFF_RC=$merge_off_rc" + echo "PASS: -fPIC merge-constants behavior verified" +} | tee "$BUILD_DIR/mergedatapic.out" +%filecheck %p/mergedatapic.golden < "$BUILD_DIR/mergedatapic.out" +#CHECK: PASS: -fPIC merge-constants behavior verified diff --git a/test/x86_64/buildAndRunTests/MergeDataStress/mergedatastress.golden b/test/x86_64/buildAndRunTests/MergeDataStress/mergedatastress.golden new file mode 100644 index 000000000..38b7ef49a --- /dev/null +++ b/test/x86_64/buildAndRunTests/MergeDataStress/mergedatastress.golden @@ -0,0 +1,4 @@ +CHECK: RUNTIME_MERGE_ON_OK +CHECK: RUNTIME_MERGE_OFF_OK +CHECK: RUNTIME_GC_OK +CHECK: PASS: constant-data merge stress test completed diff --git a/test/x86_64/buildAndRunTests/MergeDataStress/mergedatastress.sh b/test/x86_64/buildAndRunTests/MergeDataStress/mergedatastress.sh new file mode 100755 index 000000000..2b888c38b --- /dev/null +++ b/test/x86_64/buildAndRunTests/MergeDataStress/mergedatastress.sh @@ -0,0 +1,210 @@ +#REQUIRES: linux, x86 +#!/usr/bin/env bash +set -euo pipefail + +# Debug notes: +# - %t is the per-test temp directory from lit; inspect it for generated inputs. +# - Rerun verbosely to debug command expansion and output: +# llvm-lit -a -v /mergedatastress.sh +# - This test validates four scenarios: +# 1) full link merge-on +# 2) full link merge-off +# 3) partial link (-r) must not merge +# 4) gc-sections keeps only live constant +# - Printed output is checked against mergedatastress.golden. +BUILD_DIR=%t +%mkdir -p "$BUILD_DIR" + +cat > "$BUILD_DIR/f1.c" <<'SRC' +#include +__asm__( + ".section .rodata.cst4,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl m1\n.type m1,@object\n.size m1,4\n" + "m1:\n.long 0x11223344\n" + ".globl d1\n.type d1,@object\n.size d1,4\n" + "d1:\n.long 0x55667788\n" + ".section .rodata.cst8,\"aM\",@progbits,8\n" + ".p2align 3\n" + ".globl e1\n.type e1,@object\n.size e1,8\n" + "e1:\n.quad 0x0102030405060708\n" + ".section .rodata,\"a\",@progbits\n" + ".p2align 2\n" + ".globl n1\n.type n1,@object\n.size n1,4\n" + "n1:\n.long 0x11223344\n"); +SRC + +cat > "$BUILD_DIR/f2.c" <<'SRC' +#include +__asm__( + ".section .rodata.cst4,\"aM\",@progbits,4\n" + ".p2align 4\n" + ".globl m2\n.type m2,@object\n.size m2,4\n" + "m2:\n.long 0x11223344\n" + ".globl d2\n.type d2,@object\n.size d2,4\n" + "d2:\n.long 0x99aabbcc\n" + ".section .rodata.cst8,\"aM\",@progbits,8\n" + ".p2align 3\n" + ".globl e2\n.type e2,@object\n.size e2,8\n" + "e2:\n.quad 0x0102030405060708\n" + ".section .rodata,\"a\",@progbits\n" + ".p2align 2\n" + ".globl n2\n.type n2,@object\n.size n2,4\n" + "n2:\n.long 0x11223344\n"); +SRC + +cat > "$BUILD_DIR/f3.c" <<'SRC' +#include +__asm__( + ".section .rodata.cst4,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl m3\n.type m3,@object\n.size m3,4\n" + "m3:\n.long 0x11223344\n" + ".globl x3\n.type x3,@object\n.size x3,4\n" + "x3:\n.long 0xdeadbeef\n"); +SRC + +cat > "$BUILD_DIR/runtime_common.c" <<'SRC' +#include +extern const uint32_t m1, m2, m3, d1, d2, n1, n2; +extern const uint64_t e1, e2; + +const void *get_m1(void) { return &m1; } +const void *get_m2(void) { return &m2; } +const void *get_m3(void) { return &m3; } +const void *get_d1(void) { return &d1; } +const void *get_d2(void) { return &d2; } +const void *get_n1(void) { return &n1; } +const void *get_n2(void) { return &n2; } +const void *get_e1(void) { return &e1; } +const void *get_e2(void) { return &e2; } + +static long sys_write(int fd, const void *buf, unsigned long n) { + long ret; + __asm__ volatile("syscall" : "=a"(ret) : "a"(1), "D"((long)fd), "S"(buf), "d"(n) : "rcx", "r11", "memory"); + return ret; +} + +__attribute__((noreturn)) static void sys_exit(int code) { + __asm__ volatile("syscall" : : "a"(60), "D"((long)code) : "rcx", "r11", "memory"); + __builtin_unreachable(); +} + +__attribute__((noreturn)) void _start_merge_on(void) { + if (get_m1() != get_m2() || get_m2() != get_m3() || get_e1() != get_e2() || + get_d1() == get_d2() || get_n1() == get_n2() || get_n1() == get_m1()) { + static const char msg[] = "RUNTIME_MERGE_ON_FAIL\n"; + sys_write(1, msg, sizeof(msg) - 1); + sys_exit(1); + } + static const char msg[] = "RUNTIME_MERGE_ON_OK\n"; + sys_write(1, msg, sizeof(msg) - 1); + sys_exit(0); +} + +__attribute__((noreturn)) void _start_merge_off(void) { + if (get_m1() == get_m2() || get_m2() == get_m3() || get_e1() == get_e2() || + get_d1() == get_d2() || get_n1() == get_n2()) { + static const char msg[] = "RUNTIME_MERGE_OFF_FAIL\n"; + sys_write(1, msg, sizeof(msg) - 1); + sys_exit(1); + } + static const char msg[] = "RUNTIME_MERGE_OFF_OK\n"; + sys_write(1, msg, sizeof(msg) - 1); + sys_exit(0); +} +SRC + +cat > "$BUILD_DIR/gc_live.c" <<'SRC' +#include +__asm__( + ".section .rodata.cst4.live,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl c_gc_live\n.type c_gc_live,@object\n.size c_gc_live,4\n" + "c_gc_live:\n.long 0xabcdef01\n"); +extern const uint32_t c_gc_live; +__attribute__((section(".text.get_live"))) const void *get_live(void) { return &c_gc_live; } +SRC + +cat > "$BUILD_DIR/gc_dead.c" <<'SRC' +#include +__asm__( + ".section .rodata.cst4.dead,\"aM\",@progbits,4\n" + ".p2align 2\n" + ".globl c_gc_dead\n.type c_gc_dead,@object\n.size c_gc_dead,4\n" + "c_gc_dead:\n.long 0x13572468\n"); +extern const uint32_t c_gc_dead; +__attribute__((section(".text.get_dead"))) const void *get_dead(void) { return &c_gc_dead; } +SRC + +cat > "$BUILD_DIR/start_gc.c" <<'SRC' +#include +extern const void *get_live(void); + +static long sys_write(int fd, const void *buf, unsigned long n) { + long ret; + __asm__ volatile("syscall" : "=a"(ret) : "a"(1), "D"((long)fd), "S"(buf), "d"(n) : "rcx", "r11", "memory"); + return ret; +} + +__attribute__((noreturn)) static void sys_exit(int code) { + __asm__ volatile("syscall" : : "a"(60), "D"((long)code) : "rcx", "r11", "memory"); + __builtin_unreachable(); +} + +__attribute__((noreturn)) void _start_gc(void) { + if (!get_live()) { + static const char msg[] = "RUNTIME_GC_FAIL\n"; + sys_write(1, msg, sizeof(msg) - 1); + sys_exit(1); + } + static const char msg[] = "RUNTIME_GC_OK\n"; + sys_write(1, msg, sizeof(msg) - 1); + sys_exit(0); +} +SRC + +%clang %clangopts -ffreestanding -fno-pic -c "$BUILD_DIR/f1.c" -o "$BUILD_DIR/f1.o" +%clang %clangopts -ffreestanding -fno-pic -c "$BUILD_DIR/f2.c" -o "$BUILD_DIR/f2.o" +%clang %clangopts -ffreestanding -fno-pic -c "$BUILD_DIR/f3.c" -o "$BUILD_DIR/f3.o" +%clang %clangopts -ffreestanding -fno-pic -c "$BUILD_DIR/runtime_common.c" -o "$BUILD_DIR/runtime_common.o" +%clang %clangopts -ffreestanding -fno-pic -ffunction-sections -fdata-sections -c "$BUILD_DIR/gc_live.c" -o "$BUILD_DIR/gc_live.o" +%clang %clangopts -ffreestanding -fno-pic -ffunction-sections -fdata-sections -c "$BUILD_DIR/gc_dead.c" -o "$BUILD_DIR/gc_dead.o" +%clang %clangopts -ffreestanding -fno-pic -ffunction-sections -fdata-sections -c "$BUILD_DIR/start_gc.c" -o "$BUILD_DIR/start_gc.o" + +# Full link with default merge behavior should pass runtime checks. +%eld --entry=_start_merge_on "$BUILD_DIR/runtime_common.o" "$BUILD_DIR/f1.o" "$BUILD_DIR/f2.o" "$BUILD_DIR/f3.o" -o "$BUILD_DIR/full-merge-on.elf" +merge_on_out="$BUILD_DIR/merge_on.out" +%run "$BUILD_DIR/full-merge-on.elf" | tee "$merge_on_out" + +# Full link with --no-merge-constants should also pass (with opposite expectations). +%eld --no-merge-constants --entry=_start_merge_off "$BUILD_DIR/runtime_common.o" "$BUILD_DIR/f1.o" "$BUILD_DIR/f2.o" "$BUILD_DIR/f3.o" -o "$BUILD_DIR/full-merge-off.elf" +merge_off_out="$BUILD_DIR/merge_off.out" +%run "$BUILD_DIR/full-merge-off.elf" | tee "$merge_off_out" + +# Partial link: identical constants should remain unmerged at -r stage. +%eld -r "$BUILD_DIR/f1.o" "$BUILD_DIR/f2.o" "$BUILD_DIR/f3.o" -o "$BUILD_DIR/partial-r.o" + +m1=$(%readelf -s -W "$BUILD_DIR/partial-r.o" | awk '$NF=="m1" {v=$2} END {print v}') +m2=$(%readelf -s -W "$BUILD_DIR/partial-r.o" | awk '$NF=="m2" {v=$2} END {print v}') +if [[ -n "$m1" && "$m1" == "$m2" ]]; then + echo "FAIL: partial link unexpectedly merged constants" >&2 + exit 1 +fi + +# GC case: live symbol must exist; dead symbol must be removed. +%eld --gc-sections --entry=_start_gc "$BUILD_DIR/start_gc.o" "$BUILD_DIR/gc_live.o" "$BUILD_DIR/gc_dead.o" -o "$BUILD_DIR/gc.elf" +gc_out="$BUILD_DIR/gc.out" +%run "$BUILD_DIR/gc.elf" | tee "$gc_out" + +%readelf -s -W "$BUILD_DIR/gc.elf" | %grep ' c_gc_live$' >/dev/null +%readelf -s -W "$BUILD_DIR/gc.elf" | %grep ' c_gc_dead$' && exit 1 || true + +{ + cat "$merge_on_out" + cat "$merge_off_out" + cat "$gc_out" + echo "PASS: constant-data merge stress test completed" +} | tee "$BUILD_DIR/mergedatastress.out" +%filecheck %p/mergedatastress.golden < "$BUILD_DIR/mergedatastress.out" +#CHECK: PASS: constant-data merge stress test completed diff --git a/test/x86_64/buildAndRunTests/MergeableConstants1000Stress/mergeable_constants_1000_stress.golden b/test/x86_64/buildAndRunTests/MergeableConstants1000Stress/mergeable_constants_1000_stress.golden new file mode 100644 index 000000000..87eedda27 --- /dev/null +++ b/test/x86_64/buildAndRunTests/MergeableConstants1000Stress/mergeable_constants_1000_stress.golden @@ -0,0 +1,5 @@ +CHECK: 0000 MERGE_CONST_000 0x{{[0-9a-fA-F]+}} +CHECK: 0249 MERGE_CONST_249 0x{{[0-9a-fA-F]+}} +CHECK: 0250 MERGE_CONST_000 0x{{[0-9a-fA-F]+}} +CHECK: SUMMARY total=1000 unique_addresses=250 expected_unique=250 +CHECK: PASS: mergeable constants combined across multiple files diff --git a/test/x86_64/buildAndRunTests/MergeableConstants1000Stress/mergeable_constants_1000_stress.sh b/test/x86_64/buildAndRunTests/MergeableConstants1000Stress/mergeable_constants_1000_stress.sh new file mode 100755 index 000000000..2e572ad6b --- /dev/null +++ b/test/x86_64/buildAndRunTests/MergeableConstants1000Stress/mergeable_constants_1000_stress.sh @@ -0,0 +1,113 @@ +#REQUIRES: linux, x86 +#!/usr/bin/env bash +set -euo pipefail + +# Debug notes: +# - All generated sources/objects/binary are under %t. +# - To inspect failures, rerun with: +# llvm-lit -a -v /mergeable_constants_1000_stress.sh +# - Runtime exits non-zero if unique address count != EXPECTED_UNIQUE. +# - Printed output is checked against mergeable_constants_1000_stress.golden. +BUILD_DIR=%t +%mkdir -p "$BUILD_DIR" + +N_FILES=10 +PER_FILE=100 +UNIQUE_POOL=250 +TOTAL=$((N_FILES * PER_FILE)) + +for ((f = 0; f < N_FILES; ++f)); do + src="$BUILD_DIR/constants_${f}.c" + { + for ((i = 0; i < PER_FILE; ++i)); do + id=$((f * PER_FILE + i)) + v=$((id % UNIQUE_POOL)) + printf 'const char *get_const_%d_%d(void) { return "MERGE_CONST_%03d"; }\n' "$f" "$i" "$v" + done + } > "$src" +done + +main_src="$BUILD_DIR/main.c" +{ + cat <<'HDR' +#include +#include + +typedef const char *(*getter_t)(void); +HDR + + for ((f = 0; f < N_FILES; ++f)); do + for ((i = 0; i < PER_FILE; ++i)); do + printf 'const char *get_const_%d_%d(void);\n' "$f" "$i" + done + done + + cat <<'MID' + +int main(void) { + static getter_t getters[] = { +MID + + for ((f = 0; f < N_FILES; ++f)); do + for ((i = 0; i < PER_FILE; ++i)); do + printf ' get_const_%d_%d,\n' "$f" "$i" + done + done + + cat <<'TAIL' + }; + + const size_t total = sizeof(getters) / sizeof(getters[0]); + uintptr_t addrs[10000]; + size_t unique = 0; + + for (size_t i = 0; i < total; ++i) { + const char *s = getters[i](); + uintptr_t p = (uintptr_t)s; + addrs[i] = p; + + int seen = 0; + for (size_t j = 0; j < i; ++j) { + if (addrs[j] == p) { + seen = 1; + break; + } + } + if (!seen) + ++unique; + + printf("%04zu ", i); + fputs(s, stdout); + printf(" 0x%lx\n", (unsigned long)p); + } + + printf("SUMMARY total=%zu unique_addresses=%zu expected_unique=%d\n", + total, unique, EXPECTED_UNIQUE); + + if (unique != EXPECTED_UNIQUE) + return 1; + + puts("PASS: mergeable constants combined across multiple files"); + return 0; +} +TAIL +} > "$main_src" + +sed -i "1i #define EXPECTED_UNIQUE ${UNIQUE_POOL}" "$main_src" + +# Compile each generated TU separately to stress cross-file constant merging. +objs=() +for ((f = 0; f < N_FILES; ++f)); do + src="$BUILD_DIR/constants_${f}.c" + obj="$BUILD_DIR/constants_${f}.o" + %clang %clangopts -O2 -c "$src" -o "$obj" + objs+=("$obj") +done + +# Link through ld.eld and run the runtime verifier. +%clang %clangopts -O2 "$main_src" "${objs[@]}" -o "$BUILD_DIR/mergeable_constants_stress" --ld-path=ld.eld +%run "$BUILD_DIR/mergeable_constants_stress" | tee "$BUILD_DIR/mergeable_constants_1000_stress.out" +%filecheck %p/mergeable_constants_1000_stress.golden < "$BUILD_DIR/mergeable_constants_1000_stress.out" + +echo "PASS: mergeable_constants_1000_stress" +#CHECK: PASS: mergeable_constants_1000_stress diff --git a/test/x86_64/standalone/MergeConstants/DebugInfo.test b/test/x86_64/standalone/MergeConstants/DebugInfo.test new file mode 100644 index 000000000..95dd33c68 --- /dev/null +++ b/test/x86_64/standalone/MergeConstants/DebugInfo.test @@ -0,0 +1,24 @@ +REQUIRES: x86 +#---DebugInfo.test--------------------- Executable----------------------# +#BEGIN_COMMENT +# Check that DWARF variables continue to point at the deduplicated constant +# data after mergeable constant sections are merged. +#END_COMMENT +#START_TEST +RUN: %clang -target x86_64-linux-gnu -fmerge-all-constants -g -c %p/Inputs/debugvar1.c -o %t1.o +RUN: %clang -target x86_64-linux-gnu -fmerge-all-constants -g -c %p/Inputs/debugvar2.c -o %t2.o +RUN: %link %linkopts --entry=fa %t1.o %t2.o -o %t.out +RUN: %dwarfdump -debug-info %t.out | %filecheck %s --check-prefix=INFO +RUN: %dwarfdump --debug-addr %t.out | %filecheck %s --check-prefix=ADDR +#END_TEST + +#INFO: DW_AT_name ("ca") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#INFO: DW_AT_name ("cb") +#INFO: DW_AT_location (DW_OP_addrx 0x{{[0-9]+}}) +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: Addrs: [ +#ADDR: 0x{{[0-9a-f]+}} +#ADDR: 0x{{[0-9a-f]+}} diff --git a/test/x86_64/standalone/MergeConstants/Inputs/debugvar1.c b/test/x86_64/standalone/MergeConstants/Inputs/debugvar1.c new file mode 100644 index 000000000..4ddc4d870 --- /dev/null +++ b/test/x86_64/standalone/MergeConstants/Inputs/debugvar1.c @@ -0,0 +1,4 @@ +float fa(void) { + static const float ca = 3.5f; + return ca; +} diff --git a/test/x86_64/standalone/MergeConstants/Inputs/debugvar2.c b/test/x86_64/standalone/MergeConstants/Inputs/debugvar2.c new file mode 100644 index 000000000..3eceb6005 --- /dev/null +++ b/test/x86_64/standalone/MergeConstants/Inputs/debugvar2.c @@ -0,0 +1,4 @@ +float fb(void) { + static const float cb = 3.5f; + return cb; +} From a20858ce3dbbe121922177a08debec2c59a2124a Mon Sep 17 00:00:00 2001 From: Shankar Easwaran Date: Sun, 19 Apr 2026 18:17:07 -0500 Subject: [PATCH 2/2] [NFC] clang-format file changes Signed-off-by: Shankar Easwaran --- include/eld/Config/GeneralOptions.h | 63 ++++++++++++----------- include/eld/Diagnostics/DiagVerbose.inc | 9 ++-- include/eld/LayoutMap/TextLayoutPrinter.h | 2 +- lib/Fragment/Fragment.cpp | 3 +- lib/Fragment/FragmentRef.cpp | 6 +-- lib/LayoutMap/LayoutInfo.cpp | 42 ++++++--------- lib/LayoutMap/TextLayoutPrinter.cpp | 23 ++++----- lib/LinkerWrapper/GnuLdDriver.cpp | 17 +++--- lib/Object/ObjectBuilder.cpp | 24 ++++----- lib/Object/ObjectLinker.cpp | 3 +- 10 files changed, 91 insertions(+), 101 deletions(-) diff --git a/include/eld/Config/GeneralOptions.h b/include/eld/Config/GeneralOptions.h index bd2d63ae8..348278832 100644 --- a/include/eld/Config/GeneralOptions.h +++ b/include/eld/Config/GeneralOptions.h @@ -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); @@ -1239,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 @@ -1289,7 +1289,7 @@ class GeneralOptions { std::optional 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 LTOObjPath; // --lto-obj-path= std::vector UnparsedLTOOptions; // Unparsed -flto-options, to pass to plugin. @@ -1304,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 @@ -1320,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 @@ -1350,7 +1350,8 @@ class GeneralOptions { std::string MapFile; // Mapfile std::string TarFile; // --reproduce output tarfile name std::string TimingStatsFile; - std::optional PluginActivityLogFile; // --plugin-activity-file output path + std::optional + PluginActivityLogFile; // --plugin-activity-file output path std::optional ArchiveMemberReportFile; // --archive-member-report output path std::string MappingFileName; // --Mapping-file diff --git a/include/eld/Diagnostics/DiagVerbose.inc b/include/eld/Diagnostics/DiagVerbose.inc index f6d008d82..a919ec940 100644 --- a/include/eld/Diagnostics/DiagVerbose.inc +++ b/include/eld/Diagnostics/DiagVerbose.inc @@ -127,7 +127,8 @@ DIAG(created_internal_section, DiagnosticEngine::Verbose, "entry size %4") DIAG(created_eh_frame_hdr, DiagnosticEngine::Verbose, "Created eh frame header section %0 with type %1 flag %2 alignment %3") -DIAG(verbose_initializing_plugin, DiagnosticEngine::Verbose, "Initalized plugin %0") +DIAG(verbose_initializing_plugin, DiagnosticEngine::Verbose, + "Initalized plugin %0") DIAG(adding_output_section_for_plugin, DiagnosticEngine::Verbose, "Adding outputsection %0 for Plugin %1") DIAG(adding_symbol_to_fragment, DiagnosticEngine::Verbose, @@ -163,8 +164,7 @@ DIAG(modifying_mergeconst_reloc, DiagnosticEngine::Trace, DIAG(splitting_merge_string_section, DiagnosticEngine::Verbose, "%0: created mergeable string fragment with contents %1 and align " "%2") -DIAG(mapstyles_used, DiagnosticEngine::Verbose, - "Using MapStyles %0") +DIAG(mapstyles_used, DiagnosticEngine::Verbose, "Using MapStyles %0") DIAG(verbose_using_just_symbols, DiagnosticEngine::Verbose, "Using just symbols for input %0") DIAG(verbose_inserting_section_at_fixed_addr, DiagnosticEngine::Trace, @@ -175,8 +175,7 @@ DIAG(verbose_rule_matching_cache_func_hash, DiagnosticEngine::Verbose, "Rule-matching cache-functionality hash: %0") DIAG(verbose_loaded_plugin_config, DiagnosticEngine::Verbose, "Initialized plugin config for %0") -DIAG(verbose_infer_target, DiagnosticEngine::Verbose, - "Inferred target : %0") +DIAG(verbose_infer_target, DiagnosticEngine::Verbose, "Inferred target : %0") DIAG(verbose_sframe_log, DiagnosticEngine::Verbose, "SFrame Handling : %0") DIAG(verbose_remap_input, DiagnosticEngine::Verbose, "Remapping input file %0 to %1") diff --git a/include/eld/LayoutMap/TextLayoutPrinter.h b/include/eld/LayoutMap/TextLayoutPrinter.h index 3d0decb40..c6a7d4246 100644 --- a/include/eld/LayoutMap/TextLayoutPrinter.h +++ b/include/eld/LayoutMap/TextLayoutPrinter.h @@ -87,7 +87,7 @@ class TextLayoutPrinter { void flush(); - private: +private: void printAssignment(const Assignment &A, Module &M, bool UseColor); void printChangeOutputSectionInfo(const ELFSection *S) const; diff --git a/lib/Fragment/Fragment.cpp b/lib/Fragment/Fragment.cpp index 332d8d8e7..1deaded1c 100644 --- a/lib/Fragment/Fragment.cpp +++ b/lib/Fragment/Fragment.cpp @@ -159,5 +159,6 @@ std::string Fragment::str(const GeneralOptions &Options) const { return ""; InputFile *IF = S->originalInput(); ASSERT(IF, "Input section must have an InputFile!"); - return IF->getInput()->decoratedPath() + "(" + S->getDecoratedName(Options) + ")"; + return IF->getInput()->decoratedPath() + "(" + S->getDecoratedName(Options) + + ")"; } diff --git a/lib/Fragment/FragmentRef.cpp b/lib/Fragment/FragmentRef.cpp index 504e3244d..fc13d44c2 100644 --- a/lib/Fragment/FragmentRef.cpp +++ b/lib/Fragment/FragmentRef.cpp @@ -154,9 +154,9 @@ FragmentRef::Offset FragmentRef::getOutputOffset(Module &M) const { /// The target string could be a suffix uint32_t OffsetInString = ThisOffset - S->InputOffset; bool GlobalMerge = M.getConfig().options().shouldGlobalStringMerge(); - if (const MergeableString *Merged = - (!S->isAlloc() && GlobalMerge) ? M.getMergedNonAllocString(S) - : O->getMergedString(S)) { + if (const MergeableString *Merged = (!S->isAlloc() && GlobalMerge) + ? M.getMergedNonAllocString(S) + : O->getMergedString(S)) { assert(S->Exclude); assert(!Merged->Exclude); assert(Merged->hasOutputOffset()); diff --git a/lib/LayoutMap/LayoutInfo.cpp b/lib/LayoutMap/LayoutInfo.cpp index 2711ffd94..42935f4d7 100644 --- a/lib/LayoutMap/LayoutInfo.cpp +++ b/lib/LayoutMap/LayoutInfo.cpp @@ -48,8 +48,8 @@ std::string LayoutInfo::infoForFrag(const Fragment *Frag) { } void LayoutInfo::recordFragment(InputFile *Input, - const ELFSection *InputElfSection, - const Fragment *Frag) { + const ELFSection *InputElfSection, + const Fragment *Frag) { if (!Frag) return; @@ -155,8 +155,8 @@ bool LayoutInfo::isSectionDetailedInfoAvailable(ELFSection *Section) { } void LayoutInfo::recordArchiveMember(Input *Origin, InputFile *Referred, - ArchiveFile::Symbol *ArchSym, - LDSymbol *Sym) { + ArchiveFile::Symbol *ArchSym, + LDSymbol *Sym) { ArchiveRecords.push_back(std::make_tuple(Origin, Referred, ArchSym, Sym)); } @@ -169,9 +169,8 @@ uint32_t LayoutInfo::LayoutDetail = 0; std::optional LayoutInfo::ThisBasepath; -eld::Expected -LayoutInfo::setLayoutDetail(llvm::StringRef Option, - DiagnosticEngine *DiagEngine) { +eld::Expected LayoutInfo::setLayoutDetail(llvm::StringRef Option, + DiagnosticEngine *DiagEngine) { const llvm::StringLiteral ShowRelativePathOptionStr = "relative-path"; uint32_t OptionLayoutDetail = llvm::StringSwitch(Option) @@ -342,7 +341,7 @@ std::string LayoutInfo::getStringFromLoadSequence(InputSequenceT Ist) { } void LayoutInfo::recordInputActions(InputKindPrefix Prefix, Input *Input, - std::string FileType) { + std::string FileType) { InputSequenceT IS; IS.Prefix = Prefix; IS.Input = Input; @@ -381,21 +380,15 @@ std::string LayoutInfo::getPath(const std::string &Hash) const { return ThisConfig.getFileFromHash(Hash); } -void LayoutInfo::recordLinkerScriptRule() { - LinkStats.NumLinkerScriptRules++; -} +void LayoutInfo::recordLinkerScriptRule() { LinkStats.NumLinkerScriptRules++; } void LayoutInfo::recordOrphanSection() { LinkStats.NumOrphans++; } void LayoutInfo::recordTrampolines() { LinkStats.NumTrampolines++; } -void LayoutInfo::recordRetainedSections() { - LinkStats.NumRetainedSections++; -} +void LayoutInfo::recordRetainedSections() { LinkStats.NumRetainedSections++; } -void LayoutInfo::recordNoLinkerScriptRuleMatch() { - LinkStats.NumNoRuleMatch++; -} +void LayoutInfo::recordNoLinkerScriptRuleMatch() { LinkStats.NumNoRuleMatch++; } void LayoutInfo::recordPlugin() { LinkStats.NumPlugins++; } @@ -404,39 +397,38 @@ void LayoutInfo::recordFeature(std::string Feature) { } void LayoutInfo::recordSectionOverride(plugin::LinkerWrapper *W, - ChangeOutputSectionPluginOp *O) { + ChangeOutputSectionPluginOp *O) { PluginOps[W].push_back(O); Plugins.insert(W); ChangeOutputSectionOps[O->getELFSection()].push_back(O); } -void LayoutInfo::recordAddChunk(plugin::LinkerWrapper *W, - AddChunkPluginOp *O) { +void LayoutInfo::recordAddChunk(plugin::LinkerWrapper *W, AddChunkPluginOp *O) { PluginOps[W].push_back(O); Plugins.insert(W); ChunkOps[O->getFrag()].push_back(O); } void LayoutInfo::recordResetOffset(plugin::LinkerWrapper *W, - ResetOffsetPluginOp *O) { + ResetOffsetPluginOp *O) { PluginOps[W].push_back(O); } void LayoutInfo::recordRemoveChunk(plugin::LinkerWrapper *W, - RemoveChunkPluginOp *O) { + RemoveChunkPluginOp *O) { PluginOps[W].push_back(O); Plugins.insert(W); ChunkOps[O->getFrag()].push_back(O); } void LayoutInfo::recordUpdateChunks(plugin::LinkerWrapper *W, - UpdateChunksPluginOp *O) { + UpdateChunksPluginOp *O) { PluginOps[W].push_back(O); Plugins.insert(W); } void LayoutInfo::recordRemoveSymbol(plugin::LinkerWrapper *W, - RemoveSymbolPluginOp *O) { + RemoveSymbolPluginOp *O) { PluginOps[W].push_back(O); Plugins.insert(W); RemovedSymbols[O->getRemovedSymbol()] = O; @@ -480,7 +472,7 @@ LayoutInfo::getCommonsGarbageCollected(Module &Module) { } void LayoutInfo::recordRelocationData(plugin::LinkerWrapper *W, - RelocationDataPluginOp *O) { + RelocationDataPluginOp *O) { PluginOps[W].push_back(O); Plugins.insert(W); ChunkOps[O->getFrag()].push_back(O); diff --git a/lib/LayoutMap/TextLayoutPrinter.cpp b/lib/LayoutMap/TextLayoutPrinter.cpp index 314fdc7f0..5bfa7713c 100644 --- a/lib/LayoutMap/TextLayoutPrinter.cpp +++ b/lib/LayoutMap/TextLayoutPrinter.cpp @@ -74,8 +74,8 @@ eld::Expected TextLayoutPrinter::init() { std::string LayoutFileName = ThisLayoutInfo->getConfig().options().layoutFile(); if (!SuffixExtension.empty()) - LayoutFileName = ThisLayoutInfo->getConfig().options().layoutFile() + - SuffixExtension; + LayoutFileName = + ThisLayoutInfo->getConfig().options().layoutFile() + SuffixExtension; LayoutFile = std::make_unique(LayoutFileName, Error, llvm::sys::fs::OF_None); Buffer = std::make_unique(Storage); @@ -156,8 +156,7 @@ void TextLayoutPrinter::printPluginStats( } } -void TextLayoutPrinter::printStats(LayoutInfo::Stats &L, - const Module &Module) { +void TextLayoutPrinter::printStats(LayoutInfo::Stats &L, const Module &Module) { const ObjectLinker &ObjLinker = *(Module.getLinker()->getObjectLinker()); const GNULDBackend &Backend = Module.getBackend(); if (!L.hasStats()) @@ -370,9 +369,9 @@ void TextLayoutPrinter::printGlobalPluginInfo(Module &M, bool UseColor) { std::string features; if (P.isDefaultPlugin()) features += "[autoloaded]"; - outputStream() << "\t" << "\t" << P.getName() << "\t" - << P.getLibraryName() << "\t" << P.getPluginType() << "\t" - << P.getPluginOptions() << features << "\n"; + outputStream() << "\t" << "\t" << P.getName() << "\t" << P.getLibraryName() + << "\t" << P.getPluginType() << "\t" << P.getPluginOptions() + << features << "\n"; }; if (UniversalPlugins.size()) { @@ -727,8 +726,7 @@ void TextLayoutPrinter::printFragInfo(Fragment *Frag, LayoutFragmentInfo *Info, }; std::optional AddressOrOffset; - bool HasFragInfo = - (M.getState() >= LinkState::ActBeforePerformingLayout); + bool HasFragInfo = (M.getState() >= LinkState::ActBeforePerformingLayout); if (llvm::isa(Frag) && !M.isLinkStateBeforeLayout()) { auto *Strings = llvm::cast(Frag); for (MergeableString &S : Strings->getStrings()) { @@ -864,8 +862,7 @@ void TextLayoutPrinter::printOnlyLayoutFrag(eld::Module &CurModule, /// printOnlyLayoutFrag() void TextLayoutPrinter::printFrag(eld::Module &CurModule, ELFSection *Section, Fragment *Frag, bool UseColor) { - if (ThisLayoutInfo->showOnlyLayout() || - CurModule.isLinkStateBeforeLayout()) { + if (ThisLayoutInfo->showOnlyLayout() || CurModule.isLinkStateBeforeLayout()) { printOnlyLayoutFrag(CurModule, Section, Frag, UseColor); return; } @@ -1443,9 +1440,7 @@ void TextLayoutPrinter::flush() { } } -TextLayoutPrinter::~TextLayoutPrinter() { - flush(); -} +TextLayoutPrinter::~TextLayoutPrinter() { flush(); } void TextLayoutPrinter::clearInputRecords() { ThisLayoutInfo->resetArchiveRecords(); diff --git a/lib/LinkerWrapper/GnuLdDriver.cpp b/lib/LinkerWrapper/GnuLdDriver.cpp index ee9c9252b..d3a2011b0 100644 --- a/lib/LinkerWrapper/GnuLdDriver.cpp +++ b/lib/LinkerWrapper/GnuLdDriver.cpp @@ -62,11 +62,12 @@ using namespace eld; static constexpr llvm::opt::OptTable::Info infoTable[] = { #define OPTION(PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, ID, KIND, GROUP, ALIAS, \ ALIASARGS, FLAGS, VISIBILITY, PARAM, HELPTEXT, \ - HELPTEXTSFORVARIANTS, METAVAR, VALUES, SUBCOMMANDIDS_OFFSET) \ - LLVM_CONSTRUCT_OPT_INFO( \ - PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, GnuLdOptTable::ID, KIND, \ - GnuLdOptTable::GROUP, GnuLdOptTable::ALIAS, ALIASARGS, FLAGS, \ - VISIBILITY, PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, VALUES, SUBCOMMANDIDS_OFFSET), + HELPTEXTSFORVARIANTS, METAVAR, VALUES, SUBCOMMANDIDS_OFFSET) \ + LLVM_CONSTRUCT_OPT_INFO(PREFIXES_OFFSET, PREFIXED_NAME_OFFSET, \ + GnuLdOptTable::ID, KIND, GnuLdOptTable::GROUP, \ + GnuLdOptTable::ALIAS, ALIASARGS, FLAGS, VISIBILITY, \ + PARAM, HELPTEXT, HELPTEXTSFORVARIANTS, METAVAR, \ + VALUES, SUBCOMMANDIDS_OFFSET), #include "eld/Driver/GnuLinkerOptions.inc" #undef OPTION }; @@ -601,7 +602,8 @@ bool GnuLdDriver::processOptions(llvm::opt::InputArgList &Args) { // --enable-linker-version / --disable-linker-version if (Args.hasArg(T::enable_linker_version) && Args.hasArg(T::disable_linker_version)) { - errs() << "Cannot specify enable and disable LINKER_VERSION at same time!\n"; + errs() + << "Cannot specify enable and disable LINKER_VERSION at same time!\n"; return false; } @@ -612,7 +614,8 @@ bool GnuLdDriver::processOptions(llvm::opt::InputArgList &Args) { if (Args.hasArg(T::disable_linker_version)) { Config.options().setLinkerVersionDirectiveEnabled(false); - Config.addCommandLine(Table->getOptionName(T::disable_linker_version), true); + Config.addCommandLine(Table->getOptionName(T::disable_linker_version), + true); } bool recordCommandLine = Args.hasFlag( diff --git a/lib/Object/ObjectBuilder.cpp b/lib/Object/ObjectBuilder.cpp index c18232894..469ac5b6c 100644 --- a/lib/Object/ObjectBuilder.cpp +++ b/lib/Object/ObjectBuilder.cpp @@ -508,9 +508,7 @@ void ObjectBuilder::assignOutputSections(std::vector Inputs, if (ObjFile && HasSectionsCommand && ObjFile->hasHighSectionCount()) ThisConfig.raise(Diag::more_sections) << Obj->getInput()->decoratedPath(); - Pool->async([&] { - assignInputFromOutput(Obj); - }); + Pool->async([&] { assignInputFromOutput(Obj); }); } Pool->wait(); } @@ -656,16 +654,16 @@ bool ObjectBuilder::shouldCreateNewSection(ELFSection *target, << target->name(); return false; } - std::string Str; - if (target->getLink()) - Str = target->getLink()->getInputFile()->getInput()->decoratedPath(); - else - Str = "No Available Sections"; - ThisConfig.raise(Diag::incompatible_sections) - << I->name() << I->getInputFile()->getInput()->decoratedPath() - << target->name(); - ThisModule.setFailure(true); - return false; + std::string Str; + if (target->getLink()) + Str = target->getLink()->getInputFile()->getInput()->decoratedPath(); + else + Str = "No Available Sections"; + ThisConfig.raise(Diag::incompatible_sections) + << I->name() << I->getInputFile()->getInput()->decoratedPath() + << target->name(); + ThisModule.setFailure(true); + return false; } uint64_t TargetHasGroup = target->getFlags() & llvm::ELF::SHF_GROUP; diff --git a/lib/Object/ObjectLinker.cpp b/lib/Object/ObjectLinker.cpp index 9f738ebea..946d6e2ac 100644 --- a/lib/Object/ObjectLinker.cpp +++ b/lib/Object/ObjectLinker.cpp @@ -1437,7 +1437,8 @@ void ObjectLinker::applySubAlign() { } if (owningSect && seen.insert(owningSect).second) { // Warn if SUBALIGN is reducing the section alignment - if (ThisConfig.showLinkerScriptWarnings() && F->alignment() > subAlign) { + if (ThisConfig.showLinkerScriptWarnings() && + F->alignment() > subAlign) { ThisConfig.raise(Diag::warn_subalign_less_than_section_alignment) << utility::toHex(subAlign) << utility::toHex(F->alignment()) << owningSect->getLocation(0, ThisConfig.options());