[lld] [LLD][ELF] add bp-* options in ELF (PR #120514)

via llvm-commits llvm-commits at lists.llvm.org
Wed Dec 18 19:17:25 PST 2024


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lld-elf

@llvm/pr-subscribers-lld

Author: Max (Colibrow)

<details>
<summary>Changes</summary>

Add new ELF linker options for profile-guided section ordering optimizations:

- `--irpgo-profile=<file>`: Read IRPGO profile data for use with startup and compression optimizations
- `--bp-startup-sort={none,function}`: Order sections based on profile data to improve star tup time
- `--bp-compression-sort={none,function,data,both}`: Order sections using balanced partitioning to improve compressed size
- `--bp-compression-sort-startup-functions`: Additionally optimize startup functions for compression
- `--verbose-bp-section-orderer`: Print statistics about balanced partitioning section ordering

Thanks to the @<!-- -->ellishg, @<!-- -->thevinster, and their team's work.

---

Patch is 24.95 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/120514.diff


10 Files Affected:

- (added) lld/ELF/BPSectionOrderer.cpp (+61) 
- (added) lld/ELF/BPSectionOrderer.h (+134) 
- (modified) lld/ELF/CMakeLists.txt (+1) 
- (modified) lld/ELF/Config.h (+6) 
- (modified) lld/ELF/Driver.cpp (+49) 
- (modified) lld/ELF/Options.td (+18) 
- (modified) lld/ELF/Writer.cpp (+11) 
- (added) lld/test/ELF/bp-section-orderer-stress.s (+104) 
- (added) lld/test/ELF/bp-section-orderer.s (+317) 
- (modified) lld/test/ELF/incompatible.s (+13) 


``````````diff
diff --git a/lld/ELF/BPSectionOrderer.cpp b/lld/ELF/BPSectionOrderer.cpp
new file mode 100644
index 00000000000000..1fc6036e5dd9df
--- /dev/null
+++ b/lld/ELF/BPSectionOrderer.cpp
@@ -0,0 +1,61 @@
+//===- BPSectionOrderer.cpp------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "BPSectionOrderer.h"
+#include "Config.h"
+#include "InputFiles.h"
+#include "InputSection.h"
+#include "lld/Common/BPSectionOrdererBase.h"
+#include "llvm/ADT/DenseMap.h"
+
+#include "SymbolTable.h"
+#include "Symbols.h"
+
+using namespace llvm;
+using namespace lld::elf;
+
+llvm::DenseMap<const lld::elf::InputSectionBase *, int>
+lld::elf::runBalancedPartitioning(Ctx &ctx, llvm::StringRef profilePath,
+                                  bool forFunctionCompression,
+                                  bool forDataCompression,
+                                  bool compressionSortStartupFunctions,
+                                  bool verbose) {
+  size_t highestAvailablePriority = std::numeric_limits<int>::max();
+  // Collect all InputSectionBase objects from symbols and wrap them as
+  // BPSectionELF instances for balanced partitioning which follow the way
+  // '--symbol-ordering-file' does.
+  SmallVector<std::unique_ptr<BPSectionBase>> sections;
+
+  for (Symbol *sym : ctx.symtab->getSymbols())
+    if (sym->getSize() > 0)
+      if (auto *d = dyn_cast<Defined>(sym))
+        if (auto *sec = dyn_cast_or_null<InputSectionBase>(d->section))
+          sections.emplace_back(std::make_unique<BPSectionELF>(
+              sec, std::make_unique<BPSymbolELF>(d)));
+
+  for (ELFFileBase *file : ctx.objectFiles)
+    for (Symbol *sym : file->getLocalSymbols())
+      if (sym->getSize() > 0)
+        if (auto *d = dyn_cast<Defined>(sym))
+          if (auto *sec = dyn_cast_or_null<InputSectionBase>(d->section))
+            sections.emplace_back(std::make_unique<BPSectionELF>(
+                sec, std::make_unique<BPSymbolELF>(d)));
+
+  auto reorderedSections = BPSectionBase::reorderSectionsByBalancedPartitioning(
+      highestAvailablePriority, profilePath, forFunctionCompression,
+      forDataCompression, compressionSortStartupFunctions, verbose, sections);
+
+  DenseMap<const InputSectionBase *, int> result;
+  for (const auto &[sec, priority] : reorderedSections) {
+    auto *elfSection = cast<BPSectionELF>(sec);
+    result.try_emplace(
+        static_cast<const InputSectionBase *>(elfSection->getSection()),
+        static_cast<int>(priority));
+  }
+  return result;
+}
diff --git a/lld/ELF/BPSectionOrderer.h b/lld/ELF/BPSectionOrderer.h
new file mode 100644
index 00000000000000..9a55c19f159a91
--- /dev/null
+++ b/lld/ELF/BPSectionOrderer.h
@@ -0,0 +1,134 @@
+//===- BPSectionOrderer.h -------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// This file uses Balanced Partitioning to order sections to improve startup
+/// time and compressed size.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLD_ELF_BPSECTION_ORDERER_H
+#define LLD_ELF_BPSECTION_ORDERER_H
+
+#include "InputFiles.h"
+#include "InputSection.h"
+#include "Relocations.h"
+#include "Symbols.h"
+#include "lld/Common/BPSectionOrdererBase.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/BinaryFormat/ELF.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/xxhash.h"
+
+namespace lld::elf {
+
+class InputSection;
+
+class BPSymbolELF : public BPSymbol {
+  const Symbol *sym;
+
+public:
+  explicit BPSymbolELF(const Symbol *s) : sym(s) {}
+
+  llvm::StringRef getName() const override { return sym->getName(); }
+
+  const Defined *asDefined() const {
+    return llvm::dyn_cast_or_null<Defined>(sym);
+  }
+
+  std::optional<uint64_t> getValue() const override {
+    if (auto *d = asDefined())
+      return d->value;
+    return {};
+  }
+
+  std::optional<uint64_t> getSize() const override {
+    if (auto *d = asDefined())
+      return d->size;
+    return {};
+  }
+
+  InputSectionBase *getInputSection() const {
+    if (auto *d = asDefined())
+      return llvm::dyn_cast_or_null<InputSectionBase>(d->section);
+    return nullptr;
+  }
+
+  const Symbol *getSymbol() const { return sym; }
+};
+
+class BPSectionELF : public BPSectionBase {
+  const InputSectionBase *isec;
+  std::unique_ptr<BPSymbolELF> symbol;
+
+public:
+  explicit BPSectionELF(const InputSectionBase *sec,
+                        std::unique_ptr<BPSymbolELF> sym)
+      : isec(sec), symbol(std::move(sym)) {}
+
+  const void *getSection() const override { return isec; }
+
+  uint64_t getSize() const override { return isec->getSize(); }
+
+  bool isCodeSection() const override {
+    return isec->flags & llvm::ELF::SHF_EXECINSTR;
+  }
+
+  bool hasValidData() const override {
+    return isec && !isec->content().empty();
+  }
+
+  SmallVector<std::unique_ptr<BPSymbol>> getSymbols() const override {
+    SmallVector<std::unique_ptr<BPSymbol>> symbols;
+    if (auto *d = symbol->asDefined())
+      symbols.emplace_back(std::make_unique<BPSymbolELF>(d));
+    return symbols;
+  }
+
+  std::optional<StringRef>
+  getResolvedLinkageName(llvm::StringRef name) const override {
+    return {};
+  }
+
+  void getSectionHashes(llvm::SmallVectorImpl<uint64_t> &hashes,
+                        const llvm::DenseMap<const void *, uint64_t>
+                            &sectionToIdx) const override {
+    constexpr unsigned windowSize = 4;
+
+    // Calculate content hashes
+    size_t size = isec->content().size();
+    for (size_t i = 0; i < size; i++) {
+      auto window = isec->content().drop_front(i).take_front(windowSize);
+      hashes.push_back(xxHash64(window));
+    }
+
+    // TODO: Calculate relocation hashes.
+    // Since in ELF, relocations are complex, but the effect without them are
+    // good enough, we just use 0 as their hash.
+
+    llvm::sort(hashes);
+    hashes.erase(std::unique(hashes.begin(), hashes.end()), hashes.end());
+  }
+
+  static bool classof(const BPSectionBase *s) { return true; }
+};
+
+/// Run Balanced Partitioning to find the optimal function and data order to
+/// improve startup time and compressed size.
+///
+/// It is important that -ffunction-sections and -fdata-sections are used to
+/// ensure functions and data are in their own sections and thus can be
+/// reordered.
+llvm::DenseMap<const lld::elf::InputSectionBase *, int>
+runBalancedPartitioning(Ctx &ctx, llvm::StringRef profilePath,
+                        bool forFunctionCompression, bool forDataCompression,
+                        bool compressionSortStartupFunctions, bool verbose);
+} // namespace lld::elf
+
+#endif
diff --git a/lld/ELF/CMakeLists.txt b/lld/ELF/CMakeLists.txt
index 83d816ddb0601e..298443cd6ea42c 100644
--- a/lld/ELF/CMakeLists.txt
+++ b/lld/ELF/CMakeLists.txt
@@ -37,6 +37,7 @@ add_lld_library(lldELF
   Arch/X86.cpp
   Arch/X86_64.cpp
   ARMErrataFix.cpp
+  BPSectionOrderer.cpp
   CallGraphSort.cpp
   DWARF.cpp
   Driver.cpp
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index 5b6b332cd597df..0a82eeecd93fce 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -264,6 +264,12 @@ struct Config {
   bool armBe8 = false;
   BsymbolicKind bsymbolic = BsymbolicKind::None;
   CGProfileSortKind callGraphProfileSort;
+  llvm::StringRef irpgoProfilePath;
+  bool bpStartupFunctionSort = false;
+  bool bpCompressionSortStartupFunctions = false;
+  bool bpFunctionOrderForCompression = false;
+  bool bpDataOrderForCompression = false;
+  bool bpVerboseSectionOrderer = false;
   bool checkSections;
   bool checkDynamicRelocs;
   std::optional<llvm::DebugCompressionType> compressDebugSections;
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 9240f29d98d614..084abb3f5dc7f5 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -1255,6 +1255,55 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
       ctx.arg.bsymbolic = BsymbolicKind::All;
   }
   ctx.arg.callGraphProfileSort = getCGProfileSortKind(ctx, args);
+  ctx.arg.irpgoProfilePath = args.getLastArgValue(OPT_irpgo_profile);
+  ctx.arg.bpCompressionSortStartupFunctions =
+      args.hasFlag(OPT_bp_compression_sort_startup_functions,
+                   OPT_no_bp_compression_sort_startup_functions, false);
+  if (auto *arg = args.getLastArg(OPT_bp_startup_sort)) {
+    StringRef startupSortStr = arg->getValue();
+    if (startupSortStr == "function") {
+      ctx.arg.bpStartupFunctionSort = true;
+    } else if (startupSortStr != "none") {
+      ErrAlways(ctx) << "unknown value '" + startupSortStr + "' for " +
+                            arg->getSpelling();
+    }
+    if (startupSortStr != "none")
+      if (args.hasArg(OPT_call_graph_ordering_file))
+        ErrAlways(ctx) << "--bp-startup-sort=function is incompatible with "
+                          "--call-graph-ordering-file";
+  }
+  if (ctx.arg.irpgoProfilePath.empty()) {
+    if (ctx.arg.bpStartupFunctionSort)
+      ErrAlways(ctx) << "--bp-startup-sort=function must be used with "
+                        "--irpgo-profile";
+    if (ctx.arg.bpCompressionSortStartupFunctions)
+      ErrAlways(ctx)
+          << "--bp-compression-sort-startup-functions must be used with "
+             "--irpgo-profile";
+  }
+
+  if (auto *arg = args.getLastArg(OPT_bp_compression_sort)) {
+    StringRef compressionSortStr = arg->getValue();
+    if (compressionSortStr == "function") {
+      ctx.arg.bpFunctionOrderForCompression = true;
+    } else if (compressionSortStr == "data") {
+      ctx.arg.bpDataOrderForCompression = true;
+    } else if (compressionSortStr == "both") {
+      ctx.arg.bpFunctionOrderForCompression = true;
+      ctx.arg.bpDataOrderForCompression = true;
+    } else if (compressionSortStr != "none") {
+      ErrAlways(ctx) << "unknown value '" + compressionSortStr + "' for " +
+                            arg->getSpelling();
+    }
+    if (ctx.arg.bpDataOrderForCompression ||
+        ctx.arg.bpFunctionOrderForCompression) {
+      if (args.getLastArg(OPT_call_graph_ordering_file) != nullptr) {
+        ErrAlways(ctx) << "--bp-compression-sort is incompatible with "
+                          "--call-graph-ordering-file";
+      }
+    }
+  }
+  ctx.arg.bpVerboseSectionOrderer = args.hasArg(OPT_verbose_bp_section_orderer);
   ctx.arg.checkSections =
       args.hasFlag(OPT_check_sections, OPT_no_check_sections, true);
   ctx.arg.chroot = args.getLastArgValue(OPT_chroot);
diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td
index c31875305952fb..1948a5a524170c 100644
--- a/lld/ELF/Options.td
+++ b/lld/ELF/Options.td
@@ -141,6 +141,24 @@ def call_graph_profile_sort: JJ<"call-graph-profile-sort=">,
 def : FF<"no-call-graph-profile-sort">, Alias<call_graph_profile_sort>, AliasArgs<["none"]>,
   Flags<[HelpHidden]>;
 
+defm irpgo_profile: Eq<"irpgo-profile",
+    "Read the IRPGO profile for use with -bp-startup-sort and other profile-guided optimizations">;
+
+def bp_startup_sort: JJ<"bp-startup-sort=">,
+    MetaVarName<"[none,function]">,
+    HelpText<"Order sections based on profile data to improve startup time">;
+
+defm bp_compression_sort_startup_functions: BB<"bp-compression-sort-startup-functions",
+    "Order startup functions by balanced partition to improve compressed size in addition to startup time",
+    "Do not order startup function for compression">;
+    
+def bp_compression_sort: JJ<"bp-compression-sort=">,
+    MetaVarName<"[none,function,data,both]">,
+    HelpText<"Order sections by balanced partition to improve compressed size">;
+
+def verbose_bp_section_orderer: FF<"verbose-bp-section-orderer">,
+    HelpText<"Print information on how many sections were ordered by balanced partitioning and a measure of the expected number of page faults">;
+    
 // --chroot doesn't have a help text because it is an internal option.
 def chroot: Separate<["--"], "chroot">;
 
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index d5581ca3e1c921..26c4916bfcccf6 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -9,6 +9,7 @@
 #include "Writer.h"
 #include "AArch64ErrataFix.h"
 #include "ARMErrataFix.h"
+#include "BPSectionOrderer.h"
 #include "CallGraphSort.h"
 #include "Config.h"
 #include "InputFiles.h"
@@ -1083,6 +1084,16 @@ static void maybeShuffle(Ctx &ctx,
 // Builds section order for handling --symbol-ordering-file.
 static DenseMap<const InputSectionBase *, int> buildSectionOrder(Ctx &ctx) {
   DenseMap<const InputSectionBase *, int> sectionOrder;
+  if (ctx.arg.bpStartupFunctionSort || ctx.arg.bpFunctionOrderForCompression ||
+      ctx.arg.bpDataOrderForCompression) {
+    TimeTraceScope timeScope("Balanced Partitioning Section Orderer");
+    sectionOrder = runBalancedPartitioning(
+        ctx, ctx.arg.bpStartupFunctionSort ? ctx.arg.irpgoProfilePath : "",
+        ctx.arg.bpFunctionOrderForCompression,
+        ctx.arg.bpDataOrderForCompression,
+        ctx.arg.bpCompressionSortStartupFunctions,
+        ctx.arg.bpVerboseSectionOrderer);
+  }
   // Use the rarely used option --call-graph-ordering-file to sort sections.
   if (!ctx.arg.callGraphProfile.empty())
     return computeCallGraphProfileOrder(ctx);
diff --git a/lld/test/ELF/bp-section-orderer-stress.s b/lld/test/ELF/bp-section-orderer-stress.s
new file mode 100644
index 00000000000000..a25afafdadca28
--- /dev/null
+++ b/lld/test/ELF/bp-section-orderer-stress.s
@@ -0,0 +1,104 @@
+# REQUIRES: aarch64
+
+# Generate a large test case and check that the output is deterministic.
+
+# RUN: %python %s %t.s %t.proftext
+
+# RUN: llvm-mc -filetype=obj -triple=aarch64 %t.s -o %t.o
+# RUN: llvm-profdata merge %t.proftext -o %t.profdata
+
+# RUN: ld.lld -e _main --icf=all -o - %t.o --irpgo-profile=%t.profdata --bp-startup-sort=function --bp-compression-sort-startup-functions --bp-compression-sort=both | llvm-nm --numeric-sort --format=just-symbols - > %t.order1.txt
+# RUN: ld.lld -e _main --icf=all -o - %t.o --irpgo-profile=%t.profdata --bp-startup-sort=function --bp-compression-sort-startup-functions --bp-compression-sort=both | llvm-nm --numeric-sort --format=just-symbols - > %t.order2.txt
+# RUN: diff %t.order1.txt %t.order2.txt
+
+import random
+import sys
+
+assembly_filepath = sys.argv[1]
+proftext_filepath = sys.argv[2]
+
+random.seed(1234)
+num_functions = 1000
+num_data = 100
+num_traces = 10
+
+function_names = [f"f{n}" for n in range(num_functions)]
+data_names = [f"d{n}" for n in range(num_data)]
+profiled_functions = function_names[: int(num_functions / 2)]
+
+function_contents = [
+    f"""
+{name}:
+  add w0, w0, #{i % 4096}
+  add w1, w1, #{i % 10}
+  add w2, w0, #{i % 20}
+  adrp x3, {name}
+  ret
+"""
+    for i, name in enumerate(function_names)
+]
+
+data_contents = [
+      f"""
+{name}:
+  .ascii "s{i % 2}-{i % 3}-{i % 5}"
+  .xword {name}
+"""
+    for i, name in enumerate(data_names)
+]
+
+trace_contents = [
+    f"""
+# Weight
+1
+{", ".join(random.sample(profiled_functions, len(profiled_functions)))}
+"""
+    for i in range(num_traces)
+]
+
+profile_contents = [
+    f"""
+{name}
+# Func Hash:
+{i}
+# Num Counters:
+1
+# Counter Values:
+1
+"""
+    for i, name in enumerate(profiled_functions)
+]
+
+with open(assembly_filepath, "w") as f:
+    f.write(
+        f"""
+.text
+.globl _main
+
+_main:
+  ret
+
+{"".join(function_contents)}
+
+.data
+{"".join(data_contents)}
+
+"""
+    )
+
+with open(proftext_filepath, "w") as f:
+    f.write(
+        f"""
+:ir
+:temporal_prof_traces
+
+# Num Traces
+{num_traces}
+# Trace Stream Size:
+{num_traces}
+
+{"".join(trace_contents)}
+
+{"".join(profile_contents)}
+"""
+    )
diff --git a/lld/test/ELF/bp-section-orderer.s b/lld/test/ELF/bp-section-orderer.s
new file mode 100644
index 00000000000000..470c01f6bce5f2
--- /dev/null
+++ b/lld/test/ELF/bp-section-orderer.s
@@ -0,0 +1,317 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=aarch64 a.s -o a.o
+# RUN: llvm-profdata merge a.proftext -o a.profdata
+# RUN: ld.lld -e main -o a.out a.o --irpgo-profile=a.profdata --bp-startup-sort=function --verbose-bp-section-orderer --icf=all 2>&1 | FileCheck %s --check-prefix=STARTUP
+
+# STARTUP: Ordered 3 sections using balanced partitioning
+
+# RUN: ld.lld -e main -o - a.o --symbol-ordering-file a.orderfile --irpgo-profile=a.profdata --bp-startup-sort=function | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=ORDERFILE
+# RUN: ld.lld -e main -o - a.o --symbol-ordering-file a.orderfile --bp-compression-sort=both | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=ORDERFILE
+
+# Rodata
+# ORDERFILE: s2
+# ORDERFILE: s1
+# ORDERFILE-DAG: s3
+
+# Functions
+# ORDERFILE: A
+# ORDERFILE: F
+# ORDERFILE: E
+# ORDERFILE: D
+# ORDERFILE-DAG: main
+# ORDERFILE-DAG: B
+# ORDERFILE-DAG: C
+
+# Data
+# ORDERFILE: r3
+# ORDERFILE: r2
+# ORDERFILE-DAG: r1
+# ORDERFILE-DAG: r4
+
+# RUN: ld.lld -e main -o a.out a.o --verbose-bp-section-orderer --bp-compression-sort=function 2>&1 | FileCheck %s --check-prefix=COMPRESSION-FUNC
+# RUN: ld.lld -e main -o a.out a.o --verbose-bp-section-orderer --bp-compression-sort=data 2>&1 | FileCheck %s --check-prefix=COMPRESSION-DATA
+# RUN: ld.lld -e main -o a.out a.o --verbose-bp-section-orderer --bp-compression-sort=both 2>&1 | FileCheck %s --check-prefix=COMPRESSION-BOTH
+# RUN: ld.lld -e main -o a.out a.o --verbose-bp-section-orderer --bp-compression-sort=both --irpgo-profile=a.profdata --bp-startup-sort=function 2>&1 | FileCheck %s --check-prefix=COMPRESSION-BOTH
+
+# COMPRESSION-FUNC: Ordered 7 sections using balanced partitioning
+# COMPRESSION-DATA: Ordered 7 sections using balanced partitioning
+# COMPRESSION-BOTH: Ordered 14 sections using balanced partitioning
+
+#--- a.proftext
+:ir
+:temporal_prof_traces
+# Num Traces
+1
+# Trace Stream Size:
+1
+# Weight
+1
+A, B, C
+
+A
+# Func Hash:
+1111
+# Num Counters:
+1
+# Counter Values:
+1
+
+B
+# Func Hash:
+2222
+# Num Counters:
+1
+# Counter Values:
+1
+
+C
+# Func Hash:
+3333
+# Num Counters:
+1
+# Counter Values:
+1
+
+D
+# Func Hash:
+4444
+# Num Counters:
+1
+# Counter Values:
+1
+
+#--- a.orderfile
+A
+F
+E
+D
+s2
+s1
+r3
+r2
+
+#--- a.c
+const char s1[] = "hello world";
+const char s2[] = "i am a string";
+const char s3[] = "this is s3";
+const char* r1 = s1;
+const char** r2 = &r1;
+const char*** r3 = &r2;
+const char* r4 = s2;
+void A() {
+    return;
+}
+
+int B(int a) {
+    A();
+    return a + 1;
+}
+
+int C(int a) {
+    A();
+    return a + 2;
+}
+
+int D(int a) {
+    return B(a + 2);
+}
+
+int E(int a) {
+    return C(a + 2);
+}
+
+int F(int a) {
+    return C(a + 3);
+}
+
+int main() {
+    return 0;
+}
+#--- gen
+clang --target=aarch64-linux-gnu -O0 -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti -fno-asynchronous-unwind-tables -S a.c -o -
+;--- a.s
+	.text
+	.file	"a.c"
+	.section	.text.A,"ax", at progbits
+	.globl	A                               // -- Begin function A
+	.p2align	2
+	.type	A, at function
+A:                                      // @A
+// %bb.0:
+	ret
+.Lfunc_end0:
+	.size	A, .Lfunc_end0-A
+                                        // -- End function
+	.section	.text.B,"ax", at progbits
+	.globl	B                               // -- Begin function B
+	.p2align	2
+	.type	B, at function
+B:                                      // @B
+// %bb.0:
+	sub	sp, sp, #32
+	stp	x29, x30, [sp, #16]             // 16-byte Folded Spill
+	add	x29, sp, #16
+	stur	w0, [x29, #-4]
+	bl	A
+	ldur	w8, [x29, #-4]
+	add	w0, w8, #1
+	ldp	x29, x30, [sp, #16]             // 16-byte Folded Reload
+	add	sp, sp, #32
+	ret
+.Lfunc_end1:
+	.size	B, .Lfunc_end1-B
+                                        // -- End function
+	.section	.text.C,"ax", at progbits
+	.globl	C                               // -- Begin function C
+	.p2align	2
+	.type	C, at function
+C:                                  ...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/120514


More information about the llvm-commits mailing list