[llvm] [BOLT][AArch64] Add indirect call promotion support (PR #184733)

Haibo Jiang via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 30 05:19:31 PDT 2026


https://github.com/Jianghibo updated https://github.com/llvm/llvm-project/pull/184733

>From c38ee65c1ae2eff9c53547abc9b3a4075524dfaf Mon Sep 17 00:00:00 2001
From: Haibo Jiang <jianghaibo9 at huawei.com>
Date: Thu, 11 Jun 2026 21:27:48 +0800
Subject: [PATCH 1/2] [BOLT][AArch64] Support call ICP

Add AArch64 code generation for indirect call promotion. The implementation
scavenges a temporary register for the callee address and emits compare/branch
sequences for promoted direct call and tail call targets.

Keep jump-table ICP disabled on AArch64 until BOLT can reliably recover the
jump table.
---
 bolt/include/bolt/Core/MCPlusBuilder.h        |  12 +-
 bolt/lib/Passes/IndirectCallPromotion.cpp     |  33 +++-
 .../Target/AArch64/AArch64MCPlusBuilder.cpp   | 147 ++++++++++++++++++
 bolt/lib/Target/X86/X86MCPlusBuilder.cpp      |   2 +-
 bolt/test/AArch64/icp-inline.s                | 118 ++++++++++++++
 bolt/test/AArch64/unsupported-passes.test     |   4 +-
 6 files changed, 306 insertions(+), 10 deletions(-)
 create mode 100644 bolt/test/AArch64/icp-inline.s

diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index 76b4a5fe778c0..5ffeb61d21590 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -2077,6 +2077,16 @@ class MCPlusBuilder {
     return {};
   }
 
+  /// Create a sequence of instructions to compare contents of a register
+  /// \p Reg1 to a register \p Reg2 and jump to \p Target if they are different.
+  virtual InstructionListType createCmpJNEWithReg(MCPhysReg Reg1,
+                                                  MCPhysReg Reg2,
+                                                  const MCSymbol *Target,
+                                                  MCContext *Ctx) const {
+    llvm_unreachable("not implemented");
+    return {};
+  }
+
   /// Find memcpy size in bytes by using preceding instructions.
   /// Returns std::nullopt if size cannot be determined (no-op for most
   /// targets).
@@ -2501,7 +2511,7 @@ class MCPlusBuilder {
   };
 
   virtual BlocksVectorTy indirectCallPromotion(
-      const MCInst &CallInst,
+      const MCInst &CallInst, MCPhysReg Reg,
       const std::vector<std::pair<MCSymbol *, uint64_t>> &Targets,
       const std::vector<std::pair<MCSymbol *, uint64_t>> &VtableSyms,
       const std::vector<MCInst *> &MethodFetchInsns,
diff --git a/bolt/lib/Passes/IndirectCallPromotion.cpp b/bolt/lib/Passes/IndirectCallPromotion.cpp
index 54fd31e37a313..39ae4cda145c4 100644
--- a/bolt/lib/Passes/IndirectCallPromotion.cpp
+++ b/bolt/lib/Passes/IndirectCallPromotion.cpp
@@ -1142,20 +1142,27 @@ Error IndirectCallPromotion::runOnFunctions(BinaryContext &BC) {
   if (opts::ICP == ICP_NONE)
     return Error::success();
 
-  if (!BC.isX86()) {
-    BC.errs() << "BOLT-ERROR: " << getName() << " is supported only on X86\n";
+  if (!BC.isX86() && !BC.isAArch64()) {
+    BC.errs() << "BOLT-ERROR: " << getName()
+              << " is supported only on X86 and AArch64\n";
     exit(1);
   }
 
   auto &BFs = BC.getBinaryFunctions();
 
   const bool OptimizeCalls = (opts::ICP == ICP_CALLS || opts::ICP == ICP_ALL);
+  if (BC.isAArch64() && opts::ICP == ICP_JUMP_TABLES) {
+    BC.errs()
+        << "BOLT-ERROR: ICP jump table promotion is disabled on AArch64\n";
+    exit(1);
+  }
+
   const bool OptimizeJumpTables =
-      (opts::ICP == ICP_JUMP_TABLES || opts::ICP == ICP_ALL);
+      (opts::ICP == ICP_JUMP_TABLES || opts::ICP == ICP_ALL) && !BC.isAArch64();
 
   std::unique_ptr<RegAnalysis> RA;
   std::unique_ptr<BinaryFunctionCallGraph> CG;
-  if (OptimizeJumpTables) {
+  if (OptimizeJumpTables || BC.isAArch64()) {
     CG.reset(new BinaryFunctionCallGraph(buildCallGraph(BC)));
     RA.reset(new RegAnalysis(BC, &BFs, &*CG));
   }
@@ -1369,14 +1376,28 @@ Error IndirectCallPromotion::runOnFunctions(BinaryContext &BC) {
           MethodInfo.second.push_back(TargetFetchInst);
         }
 
+        MCPhysReg Reg = 0;
+        if (BC.isAArch64()) {
+          Reg = Info.getLivenessAnalysis().scavengeRegAfter(&Inst);
+          LLVM_DEBUG({
+            dbgs() << "BOLT-DEBUG: ICP ";
+            if (Reg)
+              dbgs() << "found the free register " << BC.MRI->getName(Reg);
+            else
+              dbgs() << "could not find a free register";
+            dbgs() << " to save function address.\n";
+          });
+        }
+
         // Generate new promoted call code for this callsite.
         MCPlusBuilder::BlocksVectorTy ICPcode =
             (IsJumpTable && !opts::ICPJumpTablesByTarget)
                 ? BC.MIB->jumpTablePromotion(Inst, SymTargets,
                                              MethodInfo.second, BC.Ctx.get())
                 : BC.MIB->indirectCallPromotion(
-                      Inst, SymTargets, MethodInfo.first, MethodInfo.second,
-                      opts::ICPOldCodeSequence, BC.Ctx.get());
+                      Inst, Reg, SymTargets, MethodInfo.first,
+                      MethodInfo.second, opts::ICPOldCodeSequence,
+                      BC.Ctx.get());
 
         if (ICPcode.empty()) {
           if (opts::Verbosity >= 1)
diff --git a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
index b4a9dff9d25b2..26004c94acdd0 100644
--- a/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
+++ b/bolt/lib/Target/AArch64/AArch64MCPlusBuilder.cpp
@@ -2281,6 +2281,23 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
     return Code;
   }
 
+  // This helper function creates the snippet of code that compares a register
+  // Reg1 with a register Reg2, and jumps to Target if they are not equal.
+  InstructionListType createCmpJNEWithReg(MCPhysReg Reg1, MCPhysReg Reg2,
+                                          const MCSymbol *Target,
+                                          MCContext *Ctx) const override {
+    InstructionListType Code;
+    Code.emplace_back(MCInstBuilder(AArch64::SUBSXrs)
+                          .addReg(AArch64::XZR)
+                          .addReg(Reg1)
+                          .addReg(Reg2)
+                          .addImm(0));
+    Code.emplace_back(MCInstBuilder(AArch64::Bcc)
+                          .addImm(AArch64CC::NE)
+                          .addExpr(MCSymbolRefExpr::create(Target, *Ctx)));
+    return Code;
+  }
+
   void createTailCall(MCInst &Inst, const MCSymbol *Target,
                       MCContext *Ctx) override {
     return createDirectCall(Inst, Target, Ctx, /*IsTailCall*/ true);
@@ -3329,6 +3346,136 @@ class AArch64MCPlusBuilder : public MCPlusBuilder {
     return Insts;
   }
 
+  BlocksVectorTy indirectCallPromotion(
+      const MCInst &CallInst, MCPhysReg Reg,
+      const std::vector<std::pair<MCSymbol *, uint64_t>> &Targets,
+      const std::vector<std::pair<MCSymbol *, uint64_t>> &VtableSyms,
+      const std::vector<MCInst *> &MethodFetchInsns,
+      const bool MinimizeCodeSize, MCContext *Ctx) override {
+    const bool IsTailCall = isTailCall(CallInst);
+    BlocksVectorTy Results;
+    if (getJumpTable(CallInst))
+      return Results;
+
+    if (!Reg)
+      return Results;
+
+    // Label for the current code block.
+    MCSymbol *NextTarget = nullptr;
+
+    // The join block which contains all the instructions following CallInst.
+    // MergeBlock remains null if CallInst is a tail call.
+    MCSymbol *MergeBlock = nullptr;
+
+    MCPhysReg FuncAddrReg = Reg;
+
+    const bool LoadElim = !VtableSyms.empty();
+    assert((!LoadElim || VtableSyms.size() == Targets.size()) &&
+           "There must be a vtable entry for every method "
+           "in the targets vector.");
+
+    // The compact old-style sequence used by X86 is not implemented for
+    // AArch64 regular call ICP. AArch64 currently emits the default sequence
+    // that branches or calls directly to the hot target.
+    if (MinimizeCodeSize && !LoadElim)
+      return Results;
+
+    assert(CallInst.getOperand(0).isReg() &&
+           "No register was found for indirect call.");
+    const MCPhysReg TargetReg = CallInst.getOperand(0).getReg();
+
+    const auto jumpToMergeBlock = [&](InstructionListType &NewCall) {
+      assert(MergeBlock);
+      NewCall.push_back(CallInst);
+      MCInst &Merge = NewCall.back();
+      Merge.clear();
+      createUncondBranch(Merge, MergeBlock, Ctx);
+    };
+
+    for (unsigned int i = 0; i < Targets.size(); ++i) {
+      Results.emplace_back(NextTarget, InstructionListType());
+      InstructionListType *NewCall = &Results.back().second;
+
+      // Compare current call target to a specific address.
+      assert(Targets[i].first && "All ICP targets must be to known symbols");
+      const MCSymbol *Sym = LoadElim ? VtableSyms[i].first : Targets[i].first;
+      const uint64_t Addend = LoadElim ? VtableSyms[i].second : 0;
+
+      // Materialize the promoted target address.
+      InstructionListType Adr =
+          materializeAddress(Sym, Ctx, FuncAddrReg, Addend);
+      NewCall->insert(NewCall->end(), Adr.begin(), Adr.end());
+
+      // Generate a label for the next compare block.
+      NextTarget = Ctx->createNamedTempSymbol();
+
+      // Jump to the next compare if the target address does not match.
+      InstructionListType CmpJmp =
+          createCmpJNEWithReg(TargetReg, FuncAddrReg, NextTarget, Ctx);
+      NewCall->insert(NewCall->end(), CmpJmp.begin(), CmpJmp.end());
+
+      // Call the promoted target directly.
+      Results.emplace_back(Ctx->createNamedTempSymbol(), InstructionListType());
+      NewCall = &Results.back().second;
+      NewCall->push_back(CallInst);
+      MCInst &CallOrJmp = NewCall->back();
+
+      CallOrJmp.clear();
+      CallOrJmp.setOpcode(IsTailCall ? AArch64::B : AArch64::BL);
+      CallOrJmp.addOperand(MCOperand::createExpr(getTargetExprFor(
+          CallOrJmp, MCSymbolRefExpr::create(Targets[i].first, *Ctx), *Ctx,
+          0)));
+
+      if (IsTailCall) {
+        setTailCall(CallOrJmp);
+      } else {
+        if (std::optional<uint32_t> Offset = getOffset(CallInst))
+          // Annotated as duplicated call.
+          setOffset(CallOrJmp, *Offset);
+      }
+
+      if (isInvoke(CallInst) && !isInvoke(CallOrJmp)) {
+        // Copy over any EH or GNU args size information from the original call.
+        std::optional<MCPlus::MCLandingPad> EHInfo = getEHInfo(CallInst);
+        if (EHInfo)
+          addEHInfo(CallOrJmp, *EHInfo);
+        int64_t GnuArgsSize = getGnuArgsSize(CallInst);
+        if (GnuArgsSize >= 0)
+          addGnuArgsSize(CallOrJmp, GnuArgsSize);
+      }
+
+      if (!IsTailCall) {
+        // The fallthrough block for the most common target should be the merge
+        // block.
+        if (i == 0) {
+          // Fallthrough to merge block. The CFG will be fixed in the ICP pass.
+          MergeBlock = Ctx->createNamedTempSymbol();
+        } else {
+          // Insert jump to the merge block if we are not doing a fallthrough.
+          jumpToMergeBlock(*NewCall);
+        }
+      }
+    }
+
+    // Cold call block
+    Results.emplace_back(NextTarget, InstructionListType());
+    InstructionListType &NewCall = Results.back().second;
+    for (const MCInst *Inst : MethodFetchInsns)
+      if (Inst != &CallInst)
+        NewCall.push_back(*Inst);
+    NewCall.push_back(CallInst);
+
+    // Jump to merge block from cold call block
+    if (!IsTailCall) {
+      jumpToMergeBlock(NewCall);
+
+      // Record merge block
+      Results.emplace_back(MergeBlock, InstructionListType());
+    }
+
+    return Results;
+  }
+
   void createBTI(MCInst &Inst, BTIKind BTI) const override {
     Inst.setOpcode(AArch64::HINT);
     Inst.clear();
diff --git a/bolt/lib/Target/X86/X86MCPlusBuilder.cpp b/bolt/lib/Target/X86/X86MCPlusBuilder.cpp
index 923de64be58c8..ff7a5524ac791 100644
--- a/bolt/lib/Target/X86/X86MCPlusBuilder.cpp
+++ b/bolt/lib/Target/X86/X86MCPlusBuilder.cpp
@@ -3284,7 +3284,7 @@ class X86MCPlusBuilder : public MCPlusBuilder {
   }
 
   BlocksVectorTy indirectCallPromotion(
-      const MCInst &CallInst,
+      const MCInst &CallInst, MCPhysReg Reg,
       const std::vector<std::pair<MCSymbol *, uint64_t>> &Targets,
       const std::vector<std::pair<MCSymbol *, uint64_t>> &VtableSyms,
       const std::vector<MCInst *> &MethodFetchInsns,
diff --git a/bolt/test/AArch64/icp-inline.s b/bolt/test/AArch64/icp-inline.s
new file mode 100644
index 0000000000000..0889587c64348
--- /dev/null
+++ b/bolt/test/AArch64/icp-inline.s
@@ -0,0 +1,118 @@
+## This test verifies the effect of icp on aarch64 and inline after icp.
+
+## The assembly was produced from C code compiled with clang -O1 -S:
+
+# int foo(int x) { return x + 1; }
+# int bar(int x) { return x*100 + 42; }
+# typedef int (*const fn)(int);
+# fn funcs[] = { foo, bar };
+#
+# int indirectTailCall(int argc) {
+#   fn func = funcs[argc];
+#   return func(0);
+# }
+# int indirectCall(int argc) {
+#   fn func = funcs[argc];
+#   int result = func(0);
+#   __asm__ volatile("" ::: "memory");
+#   return result;
+# }
+
+# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
+# RUN: link_fdata %s %t.o %t.fdata
+# RUN: llvm-strip --strip-unneeded %t.o
+# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib -pie
+
+# AArch64 jump table ICP is disabled until BOLT can reliably recover the
+# jump table index register and limits.
+# RUN: not llvm-bolt %t.exe --icp=jump-tables -o %t.null --lite=0 --assume-abi \
+# RUN:   --data %t.fdata \
+# RUN:   2>&1 | FileCheck %s --check-prefix=CHECK-ICP-JT-DISABLED
+# CHECK-ICP-JT-DISABLED: BOLT-ERROR: ICP jump table promotion is disabled on AArch64
+
+# Indirect call promotion without inline
+# RUN: llvm-bolt %t.exe --icp=calls --icp-calls-topn=1 \
+# RUN:   -o %t.null --lite=0 --assume-abi --print-icp --data %t.fdata \
+# RUN:   | FileCheck %s --check-prefix=CHECK-ICP-NO-INLINE
+# CHECK-ICP-NO-INLINE: Binary Function "indirectTailCall" after indirect-call-promotion
+# CHECK-ICP-NO-INLINE: b    bar
+# CHECK-ICP-NO-INLINE: End of Function "indirectTailCall"
+# CHECK-ICP-NO-INLINE: Binary Function "indirectCall" after indirect-call-promotion
+# CHECK-ICP-NO-INLINE: bl    bar
+# CHECK-ICP-NO-INLINE: End of Function "indirectCall"
+
+# Indirect call promotion with inline
+# RUN: llvm-bolt %t.exe --icp=calls --icp-calls-topn=1 --inline-small-functions \
+# RUN:   -o %t.null --lite=0 --assume-abi --inline-small-functions-bytes=12 \
+# RUN:   --print-inline --data %t.fdata \
+# RUN:   | FileCheck %s --check-prefix=CHECK-ICP-WITH-INLINE
+# CHECK-ICP-WITH-INLINE:     Binary Function "indirectTailCall" after inlining
+# CHECK-ICP-WITH-INLINE:     br    x1
+# CHECK-ICP-WITH-INLINE-NOT: b    bar
+# CHECK-ICP-WITH-INLINE:     End of Function "indirectTailCall"
+    .globl  foo
+    .type   foo, at function
+foo:
+    .cfi_startproc
+    add     w0, w0, #1
+    ret
+    .Lfunc_end0:
+    .size   foo, .Lfunc_end0-foo
+    .cfi_endproc
+
+    .globl  bar
+    .type   bar, at function
+bar:
+    .cfi_startproc
+    mov     w8, #100
+    mov     x9, #42
+    madd    w0, w0, w8, w9
+    ret
+.Lfunc_end1:
+    .size   bar, .Lfunc_end1-bar
+    .cfi_endproc
+
+    .globl  indirectTailCall
+    .type   indirectTailCall, at function
+indirectTailCall:
+    .cfi_startproc
+    adrp    x8, funcs
+    add     x8, x8, :lo12:funcs
+    ldr     x1, [x8, w0, sxtw #3]
+    mov     w0, wzr
+    br     x1
+# FDATA: 1 indirectTailCall 10 1 foo 0 0 1
+# FDATA: 1 indirectTailCall 10 1 bar 0 0 2
+.Lfunc_end2:
+    .size   indirectTailCall, .Lfunc_end2-indirectTailCall
+    .cfi_endproc
+
+    .globl  indirectCall
+    .type   indirectCall, at function
+indirectCall:
+    .cfi_startproc
+    stp     x29, x30, [sp, #-16]!
+    .cfi_def_cfa_offset 16
+    .cfi_offset w29, -16
+    .cfi_offset w30, -8
+    mov     x29, sp
+    adrp    x8, funcs
+    add     x8, x8, :lo12:funcs
+    ldr     x1, [x8, w0, sxtw #3]
+    mov     w0, wzr
+    blr     x1
+# FDATA: 1 indirectCall 18 1 foo 0 0 1
+# FDATA: 1 indirectCall 18 1 bar 0 0 2
+    ldp     x29, x30, [sp], #16
+    ret
+.Lfunc_end3:
+    .size   indirectCall, .Lfunc_end3-indirectCall
+    .cfi_endproc
+
+    .type   funcs, at object
+    .section    .data.rel.ro,"aw", at progbits
+    .globl  funcs
+funcs:
+    .xword  foo
+    .xword  bar
+    .size   funcs, 16
diff --git a/bolt/test/AArch64/unsupported-passes.test b/bolt/test/AArch64/unsupported-passes.test
index 5e5eac19447ae..dcf8a9ba6660d 100644
--- a/bolt/test/AArch64/unsupported-passes.test
+++ b/bolt/test/AArch64/unsupported-passes.test
@@ -6,13 +6,13 @@ RUN: %clang %cflags %p/../Inputs/hello.c -o %t -Wl,-q,-z,undefs
 RUN: not llvm-bolt %t -o %t.bolt --frame-opt=all 2>&1 | FileCheck %s --check-prefix=CHECK-FRAME-OPT
 RUN: not llvm-bolt %t -o %t.bolt --three-way-branch 2>&1 | FileCheck %s --check-prefix=CHECK-THREE-WAY
 RUN: not llvm-bolt %t -o %t.bolt --jt-footprint-reduction 2>&1 | FileCheck %s --check-prefix=CHECK-JT-FOOTPRINT-REDUCTION
-RUN: not llvm-bolt %t -o %t.bolt --indirect-call-promotion=all 2>&1 | FileCheck %s --check-prefix=CHECK-INDIRECT-CALL-PROMOTION
+RUN: not llvm-bolt %t -o %t.bolt --indirect-call-promotion=jump-tables 2>&1 | FileCheck %s --check-prefix=CHECK-INDIRECT-CALL-PROMOTION
 RUN: not llvm-bolt %t -o %t.bolt --memcpy1-spec=main 2>&1 | FileCheck %s --check-prefix=CHECK-MEMCPY1-SPEC
 
 CHECK-FRAME-OPT: BOLT-ERROR: frame-optimizer is supported only on X86
 CHECK-THREE-WAY: BOLT-ERROR: three way branch is supported only on X86
 CHECK-JT-FOOTPRINT-REDUCTION: BOLT-ERROR: jt-footprint-reduction is supported only on X86
-CHECK-INDIRECT-CALL-PROMOTION: BOLT-ERROR: indirect-call-promotion is supported only on X86
+CHECK-INDIRECT-CALL-PROMOTION: BOLT-ERROR: ICP jump table promotion is disabled on AArch64
 CHECK-MEMCPY1-SPEC: BOLT-ERROR: specialize-memcpy is currently supported only on X86
 
 // Passes specific to X86 arch

>From 045cdfb239db018dee8673023fe389b4e7e1af61 Mon Sep 17 00:00:00 2001
From: Haibo Jiang <jianghaibo9 at huawei.com>
Date: Tue, 30 Jun 2026 20:05:43 +0800
Subject: [PATCH 2/2] [BOLT][AArch64] Split ICP tests

---
 bolt/test/AArch64/icp-inline.s |  30 ++-------
 bolt/test/AArch64/icp.s        | 107 +++++++++++++++++++++++++++++++++
 2 files changed, 113 insertions(+), 24 deletions(-)
 create mode 100644 bolt/test/AArch64/icp.s

diff --git a/bolt/test/AArch64/icp-inline.s b/bolt/test/AArch64/icp-inline.s
index 0889587c64348..72d3808d8dea9 100644
--- a/bolt/test/AArch64/icp-inline.s
+++ b/bolt/test/AArch64/icp-inline.s
@@ -1,4 +1,4 @@
-## This test verifies the effect of icp on aarch64 and inline after icp.
+## This test verifies inlining after indirect call promotion on AArch64.
 
 ## The assembly was produced from C code compiled with clang -O1 -S:
 
@@ -23,33 +23,15 @@
 # RUN: llvm-strip --strip-unneeded %t.o
 # RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib -pie
 
-# AArch64 jump table ICP is disabled until BOLT can reliably recover the
-# jump table index register and limits.
-# RUN: not llvm-bolt %t.exe --icp=jump-tables -o %t.null --lite=0 --assume-abi \
-# RUN:   --data %t.fdata \
-# RUN:   2>&1 | FileCheck %s --check-prefix=CHECK-ICP-JT-DISABLED
-# CHECK-ICP-JT-DISABLED: BOLT-ERROR: ICP jump table promotion is disabled on AArch64
-
-# Indirect call promotion without inline
-# RUN: llvm-bolt %t.exe --icp=calls --icp-calls-topn=1 \
-# RUN:   -o %t.null --lite=0 --assume-abi --print-icp --data %t.fdata \
-# RUN:   | FileCheck %s --check-prefix=CHECK-ICP-NO-INLINE
-# CHECK-ICP-NO-INLINE: Binary Function "indirectTailCall" after indirect-call-promotion
-# CHECK-ICP-NO-INLINE: b    bar
-# CHECK-ICP-NO-INLINE: End of Function "indirectTailCall"
-# CHECK-ICP-NO-INLINE: Binary Function "indirectCall" after indirect-call-promotion
-# CHECK-ICP-NO-INLINE: bl    bar
-# CHECK-ICP-NO-INLINE: End of Function "indirectCall"
-
 # Indirect call promotion with inline
 # RUN: llvm-bolt %t.exe --icp=calls --icp-calls-topn=1 --inline-small-functions \
 # RUN:   -o %t.null --lite=0 --assume-abi --inline-small-functions-bytes=12 \
 # RUN:   --print-inline --data %t.fdata \
-# RUN:   | FileCheck %s --check-prefix=CHECK-ICP-WITH-INLINE
-# CHECK-ICP-WITH-INLINE:     Binary Function "indirectTailCall" after inlining
-# CHECK-ICP-WITH-INLINE:     br    x1
-# CHECK-ICP-WITH-INLINE-NOT: b    bar
-# CHECK-ICP-WITH-INLINE:     End of Function "indirectTailCall"
+# RUN:   | FileCheck %s
+# CHECK:     Binary Function "indirectTailCall" after inlining
+# CHECK:     br    x1
+# CHECK-NOT: b    bar
+# CHECK:     End of Function "indirectTailCall"
     .globl  foo
     .type   foo, at function
 foo:
diff --git a/bolt/test/AArch64/icp.s b/bolt/test/AArch64/icp.s
new file mode 100644
index 0000000000000..f61bf33646a7b
--- /dev/null
+++ b/bolt/test/AArch64/icp.s
@@ -0,0 +1,107 @@
+## This test verifies basic indirect call promotion on AArch64.
+
+## The assembly models the following C code:
+
+# void hello(void) {}
+#
+# void execute(void (*callback)(void)) {
+#   callback();
+# }
+#
+# int exit_success(void) {
+#   return 0;
+# }
+#
+# int executeTailCall(int (*callback)(void)) {
+#   return callback();
+# }
+#
+# int main(void) {
+#   execute(hello);
+#   return executeTailCall(exit_success);
+# }
+
+# RUN: llvm-mc -filetype=obj -triple aarch64-unknown-unknown %s -o %t.o
+# RUN: link_fdata %s %t.o %t.fdata
+# RUN: llvm-strip --strip-unneeded %t.o
+# RUN: %clang %cflags %t.o -o %t.exe -Wl,-q -nostdlib -pie
+
+# RUN: llvm-bolt %t.exe --icp=calls --icp-calls-topn=1 \
+# RUN:   -o %t.null --lite=0 --assume-abi --print-icp --data %t.fdata \
+# RUN:   | FileCheck %s
+# CHECK: Binary Function "execute" after indirect-call-promotion
+# CHECK: bl    hello
+# CHECK: End of Function "execute"
+# CHECK: Binary Function "executeTailCall" after indirect-call-promotion
+# CHECK: b    exit_success
+# CHECK: End of Function "executeTailCall"
+
+    .text
+    .globl  hello
+    .type   hello, at function
+hello:
+    .cfi_startproc
+    ret
+.Lfunc_end0:
+    .size   hello, .Lfunc_end0-hello
+    .cfi_endproc
+
+    .globl  execute
+    .type   execute, at function
+execute:
+    .cfi_startproc
+    stp     x29, x30, [sp, #-16]!
+    .cfi_def_cfa_offset 16
+    .cfi_offset w29, -16
+    .cfi_offset w30, -8
+    mov     x29, sp
+execute_call:
+    blr     x0
+# FDATA: 1 execute #execute_call# 1 hello 0 0 1
+    ldp     x29, x30, [sp], #16
+    ret
+.Lfunc_end1:
+    .size   execute, .Lfunc_end1-execute
+    .cfi_endproc
+
+    .globl  exit_success
+    .type   exit_success, at function
+exit_success:
+    .cfi_startproc
+    mov     w0, wzr
+    ret
+.Lfunc_end2:
+    .size   exit_success, .Lfunc_end2-exit_success
+    .cfi_endproc
+
+    .globl  executeTailCall
+    .type   executeTailCall, at function
+executeTailCall:
+    .cfi_startproc
+execute_tail_call:
+    br      x0
+# FDATA: 1 executeTailCall #execute_tail_call# 1 exit_success 0 0 1
+.Lfunc_end3:
+    .size   executeTailCall, .Lfunc_end3-executeTailCall
+    .cfi_endproc
+
+    .globl  main
+    .type   main, at function
+main:
+    .cfi_startproc
+    stp     x29, x30, [sp, #-16]!
+    .cfi_def_cfa_offset 16
+    .cfi_offset w29, -16
+    .cfi_offset w30, -8
+    mov     x29, sp
+    adrp    x0, hello
+    add     x0, x0, :lo12:hello
+    bl      execute
+    adrp    x0, exit_success
+    add     x0, x0, :lo12:exit_success
+    bl      executeTailCall
+    ldp     x29, x30, [sp], #16
+    ret
+.Lfunc_end4:
+    .size   main, .Lfunc_end4-main
+    .cfi_endproc



More information about the llvm-commits mailing list