[llvm] [LoopUnroll] Add prepare-for-lto option to LoopUnrollPass (PR #192153)
Justin Fargnoli via llvm-commits
llvm-commits at lists.llvm.org
Tue Apr 14 15:53:31 PDT 2026
https://github.com/justinfargnoli created https://github.com/llvm/llvm-project/pull/192153
## Summary
- Expose the existing `PrepareForLTO` mechanism through `LoopUnrollPass` and `LoopFullUnrollPass` options
- When enabled, the loop unroller considers external function calls as potential inline candidates, deferring unrolling of loops that contain such calls to allow LTO to inline them first
- The underlying infrastructure already existed in `CodeMetrics` and was used by `LoopRotatePass`; this wires it up for `LoopUnrollPass` as well
- Adds pass pipeline option `prepare-for-lto` / `no-prepare-for-lto` (e.g. `loop-unroll<prepare-for-lto;O2>`)
## Test plan
- `llvm/test/Transforms/LoopUnroll/prepare-for-lto.ll` — behavioral test with auto-generated CHECK lines showing unrolling deferred for loops with extern calls under `prepare-for-lto`
- `llvm/test/Other/new-pm-print-pipeline.ll` — verifies the option round-trips through `printPipeline`
>From 680c20d3af3d7bb5ff6c0d708f30bed0de6a6832 Mon Sep 17 00:00:00 2001
From: Justin Fargnoli <jfargnoli at nvidia.com>
Date: Tue, 14 Apr 2026 22:51:00 +0000
Subject: [PATCH] [LoopUnroll] Add prepare-for-lto option to LoopUnrollPass
Expose the existing PrepareForLTO mechanism through the LoopUnrollPass
and LoopFullUnrollPass options, allowing it to be controlled via the
pass pipeline string (e.g. loop-unroll<prepare-for-lto>).
When enabled, the loop unroller considers external function calls as
potential inline candidates, deferring unrolling of loops that contain
such calls. This allows LTO to inline these calls first, potentially
enabling better unrolling decisions in the post-link phase.
The underlying infrastructure already existed in CodeMetrics and was
used by LoopRotatePass; this patch wires it up for LoopUnrollPass as
well.
---
.../llvm/Transforms/Scalar/LoopUnrollPass.h | 17 +-
.../llvm/Transforms/Utils/UnrollLoop.h | 3 +-
llvm/lib/Passes/PassBuilder.cpp | 2 +
llvm/lib/Passes/PassRegistry.def | 2 +-
llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp | 20 ++-
llvm/test/Other/new-pm-print-pipeline.ll | 4 +-
.../Transforms/LoopUnroll/prepare-for-lto.ll | 168 ++++++++++++++++++
7 files changed, 202 insertions(+), 14 deletions(-)
create mode 100644 llvm/test/Transforms/LoopUnroll/prepare-for-lto.ll
diff --git a/llvm/include/llvm/Transforms/Scalar/LoopUnrollPass.h b/llvm/include/llvm/Transforms/Scalar/LoopUnrollPass.h
index 8d8c2f254f02f..4533b38e013a4 100644
--- a/llvm/include/llvm/Transforms/Scalar/LoopUnrollPass.h
+++ b/llvm/include/llvm/Transforms/Scalar/LoopUnrollPass.h
@@ -36,11 +36,16 @@ class LoopFullUnrollPass : public PassInfoMixin<LoopFullUnrollPass> {
/// the internal SCEV records. For large loops, the former is faster.
const bool ForgetSCEV;
+ /// If true, consider calls as inline candidates and defer unrolling to allow
+ /// inlining to resolve them first (e.g. during LTO pre-link).
+ const bool PrepareForLTO;
+
public:
explicit LoopFullUnrollPass(int OptLevel = 2, bool OnlyWhenForced = false,
- bool ForgetSCEV = false)
+ bool ForgetSCEV = false,
+ bool PrepareForLTO = false)
: OptLevel(OptLevel), OnlyWhenForced(OnlyWhenForced),
- ForgetSCEV(ForgetSCEV) {}
+ ForgetSCEV(ForgetSCEV), PrepareForLTO(PrepareForLTO) {}
PreservedAnalyses run(Loop &L, LoopAnalysisManager &AM,
LoopStandardAnalysisResults &AR, LPMUpdater &U);
@@ -65,6 +70,7 @@ struct LoopUnrollOptions {
std::optional<bool> AllowUpperBound;
std::optional<bool> AllowProfileBasedPeeling;
std::optional<unsigned> FullUnrollMaxCount;
+ bool PrepareForLTO = false;
int OptLevel;
/// If false, use a cost model to determine whether unrolling of a loop is
@@ -125,6 +131,13 @@ struct LoopUnrollOptions {
FullUnrollMaxCount = O;
return *this;
}
+
+ /// If true, consider calls as inline candidates and defer unrolling to allow
+ /// inlining to resolve them first (e.g. during LTO pre-link).
+ LoopUnrollOptions &setPrepareForLTO(bool V) {
+ PrepareForLTO = V;
+ return *this;
+ }
};
/// Loop unroll pass that will support both full and partial unrolling.
diff --git a/llvm/include/llvm/Transforms/Utils/UnrollLoop.h b/llvm/include/llvm/Transforms/Utils/UnrollLoop.h
index 364908ca1fad5..8ae444cc3eeb1 100644
--- a/llvm/include/llvm/Transforms/Utils/UnrollLoop.h
+++ b/llvm/include/llvm/Transforms/Utils/UnrollLoop.h
@@ -159,7 +159,8 @@ class UnrollCostEstimator {
LLVM_ABI UnrollCostEstimator(const Loop *L, const TargetTransformInfo &TTI,
const SmallPtrSetImpl<const Value *> &EphValues,
- unsigned BEInsns);
+ unsigned BEInsns,
+ bool PrepareForLTO = false);
/// Whether it is legal to unroll this loop.
LLVM_ABI bool canUnroll() const;
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 2751961e684d2..11742713add1e 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -850,6 +850,8 @@ Expected<LoopUnrollOptions> parseLoopUnrollOptions(StringRef Params) {
UnrollOpts.setRuntime(Enable);
} else if (ParamName == "upperbound") {
UnrollOpts.setUpperBound(Enable);
+ } else if (ParamName == "prepare-for-lto") {
+ UnrollOpts.setPrepareForLTO(Enable);
} else {
return make_error<StringError>(
formatv("invalid LoopUnrollPass parameter '{}'", ParamName).str(),
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 9f7b3cf4df77b..76352394a7b91 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -630,7 +630,7 @@ FUNCTION_PASS_WITH_PARAMS(
parseLoopUnrollOptions,
"O0;O1;O2;O3;full-unroll-max=N;no-partial;partial;no-peeling;peeling;"
"no-profile-peeling;profile-peeling;no-runtime;runtime;no-upperbound;"
- "upperbound")
+ "upperbound;prepare-for-lto;no-prepare-for-lto")
FUNCTION_PASS_WITH_PARAMS(
"loop-vectorize", "LoopVectorizePass",
[](LoopVectorizeOptions Opts) { return LoopVectorizePass(Opts); },
diff --git a/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp b/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
index 885ae1f6dc02a..ebd4d2c21128b 100644
--- a/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
@@ -687,11 +687,11 @@ static std::optional<EstimatedUnrollCost> analyzeLoopUnrollCost(
UnrollCostEstimator::UnrollCostEstimator(
const Loop *L, const TargetTransformInfo &TTI,
- const SmallPtrSetImpl<const Value *> &EphValues, unsigned BEInsns) {
+ const SmallPtrSetImpl<const Value *> &EphValues, unsigned BEInsns,
+ bool PrepareForLTO) {
CodeMetrics Metrics;
for (BasicBlock *BB : L->blocks())
- Metrics.analyzeBasicBlock(BB, TTI, EphValues, /* PrepareForLTO= */ false,
- L);
+ Metrics.analyzeBasicBlock(BB, TTI, EphValues, PrepareForLTO, L);
NumInlineCandidates = Metrics.NumInlineCandidates;
NotDuplicatable = Metrics.notDuplicatable;
Convergence = Metrics.Convergence;
@@ -1245,7 +1245,7 @@ tryToUnrollLoop(Loop *L, DominatorTree &DT, LoopInfo *LI, ScalarEvolution &SE,
OptimizationRemarkEmitter &ORE, BlockFrequencyInfo *BFI,
ProfileSummaryInfo *PSI, bool PreserveLCSSA, int OptLevel,
bool OnlyFullUnroll, bool OnlyWhenForced, bool ForgetAllSCEV,
- std::optional<unsigned> ProvidedCount,
+ bool PrepareForLTO, std::optional<unsigned> ProvidedCount,
std::optional<unsigned> ProvidedThreshold,
std::optional<bool> ProvidedAllowPartial,
std::optional<bool> ProvidedRuntime,
@@ -1324,7 +1324,7 @@ tryToUnrollLoop(Loop *L, DominatorTree &DT, LoopInfo *LI, ScalarEvolution &SE,
SmallPtrSet<const Value *, 32> EphValues;
CodeMetrics::collectEphemeralValues(L, &AC, EphValues);
- UnrollCostEstimator UCE(L, TTI, EphValues, UP.BEInsns);
+ UnrollCostEstimator UCE(L, TTI, EphValues, UP.BEInsns, PrepareForLTO);
if (!UCE.canUnroll())
return LoopUnrollResult::Unmodified;
@@ -1553,7 +1553,8 @@ class LoopUnroll : public LoopPass {
LoopUnrollResult Result = tryToUnrollLoop(
L, DT, LI, SE, TTI, AC, ORE, nullptr, nullptr, PreserveLCSSA, OptLevel,
- /*OnlyFullUnroll*/ false, OnlyWhenForced, ForgetAllSCEV, ProvidedCount,
+ /*OnlyFullUnroll*/ false, OnlyWhenForced, ForgetAllSCEV,
+ /*PrepareForLTO*/ false, ProvidedCount,
ProvidedThreshold, ProvidedAllowPartial, ProvidedRuntime,
ProvidedUpperBound, ProvidedAllowPeeling,
ProvidedAllowProfileBasedPeeling, ProvidedFullUnrollMaxCount);
@@ -1625,7 +1626,8 @@ PreservedAnalyses LoopFullUnrollPass::run(Loop &L, LoopAnalysisManager &AM,
tryToUnrollLoop(&L, AR.DT, &AR.LI, AR.SE, AR.TTI, AR.AC, ORE,
/*BFI*/ nullptr, /*PSI*/ nullptr,
/*PreserveLCSSA*/ true, OptLevel, /*OnlyFullUnroll*/ true,
- OnlyWhenForced, ForgetSCEV, /*Count*/ std::nullopt,
+ OnlyWhenForced, ForgetSCEV, PrepareForLTO,
+ /*Count*/ std::nullopt,
/*Threshold*/ std::nullopt, /*AllowPartial*/ false,
/*Runtime*/ false, /*UpperBound*/ false,
/*AllowPeeling*/ true,
@@ -1753,7 +1755,7 @@ PreservedAnalyses LoopUnrollPass::run(Function &F,
&L, DT, &LI, SE, TTI, AC, ORE, BFI, PSI,
/*PreserveLCSSA*/ true, UnrollOpts.OptLevel, /*OnlyFullUnroll*/ false,
UnrollOpts.OnlyWhenForced, UnrollOpts.ForgetSCEV,
- /*Count*/ std::nullopt,
+ UnrollOpts.PrepareForLTO, /*Count*/ std::nullopt,
/*Threshold*/ std::nullopt, UnrollOpts.AllowPartial,
UnrollOpts.AllowRuntime, UnrollOpts.AllowUpperBound, LocalAllowPeeling,
UnrollOpts.AllowProfileBasedPeeling, UnrollOpts.FullUnrollMaxCount,
@@ -1795,6 +1797,8 @@ void LoopUnrollPass::printPipeline(
<< "profile-peeling;";
if (UnrollOpts.FullUnrollMaxCount != std::nullopt)
OS << "full-unroll-max=" << UnrollOpts.FullUnrollMaxCount << ';';
+ if (UnrollOpts.PrepareForLTO)
+ OS << "prepare-for-lto;";
OS << 'O' << UnrollOpts.OptLevel;
OS << '>';
}
diff --git a/llvm/test/Other/new-pm-print-pipeline.ll b/llvm/test/Other/new-pm-print-pipeline.ll
index 53a17975c512b..fb5d6cdd0c838 100644
--- a/llvm/test/Other/new-pm-print-pipeline.ll
+++ b/llvm/test/Other/new-pm-print-pipeline.ll
@@ -28,8 +28,8 @@
; RUN: opt -disable-output -disable-verify -print-pipeline-passes -passes='function(lower-matrix-intrinsics<>,lower-matrix-intrinsics<minimal>)' < %s | FileCheck %s --match-full-lines --check-prefixes=CHECK-9
; CHECK-9: function(lower-matrix-intrinsics<>,lower-matrix-intrinsics<minimal>)
-; RUN: opt -disable-output -disable-verify -print-pipeline-passes -passes='function(loop-unroll<>,loop-unroll<partial;peeling;runtime;upperbound;profile-peeling;full-unroll-max=5;O1>,loop-unroll<no-partial;no-peeling;no-runtime;no-upperbound;no-profile-peeling;full-unroll-max=7;O1>)' < %s | FileCheck %s --match-full-lines --check-prefixes=CHECK-10
-; CHECK-10: function(loop-unroll<O2>,loop-unroll<partial;peeling;runtime;upperbound;profile-peeling;full-unroll-max=5;O1>,loop-unroll<no-partial;no-peeling;no-runtime;no-upperbound;no-profile-peeling;full-unroll-max=7;O1>)
+; RUN: opt -disable-output -disable-verify -print-pipeline-passes -passes='function(loop-unroll<>,loop-unroll<partial;peeling;runtime;upperbound;profile-peeling;full-unroll-max=5;O1>,loop-unroll<no-partial;no-peeling;no-runtime;no-upperbound;no-profile-peeling;full-unroll-max=7;O1>,loop-unroll<prepare-for-lto;O2>)' < %s | FileCheck %s --match-full-lines --check-prefixes=CHECK-10
+; CHECK-10: function(loop-unroll<O2>,loop-unroll<partial;peeling;runtime;upperbound;profile-peeling;full-unroll-max=5;O1>,loop-unroll<no-partial;no-peeling;no-runtime;no-upperbound;no-profile-peeling;full-unroll-max=7;O1>,loop-unroll<prepare-for-lto;O2>)
; RUN: opt -disable-output -disable-verify -print-pipeline-passes -passes='function(gvn<>,gvn<pre;load-pre;split-backedge-load-pre;memdep;memoryssa>,gvn<no-pre;no-load-pre;no-split-backedge-load-pre;no-memdep;no-memoryssa>)' < %s | FileCheck %s --match-full-lines --check-prefixes=CHECK-11
; CHECK-11: function(gvn<>,gvn<pre;load-pre;split-backedge-load-pre;no-memdep;memoryssa>,gvn<no-pre;no-load-pre;no-split-backedge-load-pre;memdep;no-memoryssa>)
diff --git a/llvm/test/Transforms/LoopUnroll/prepare-for-lto.ll b/llvm/test/Transforms/LoopUnroll/prepare-for-lto.ll
new file mode 100644
index 0000000000000..7e7a708667d89
--- /dev/null
+++ b/llvm/test/Transforms/LoopUnroll/prepare-for-lto.ll
@@ -0,0 +1,168 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -S -passes='loop-unroll<O2>' < %s | FileCheck --check-prefix=DEFAULT %s
+; RUN: opt -S -passes='loop-unroll<prepare-for-lto;O2>' < %s | FileCheck --check-prefix=LTO %s
+
+; Test that prepare-for-lto defers unrolling of loops with calls that may be
+; inlined during the LTO stage.
+
+; a) Loop with extern declaration call — normally fully unrolled, but deferred
+; under prepare-for-lto because the call may be inlined during LTO.
+declare i32 @extern_func(i32)
+
+define i32 @extern_call(ptr %A) {
+; DEFAULT-LABEL: @extern_call(
+; DEFAULT-NEXT: entry:
+; DEFAULT-NEXT: br label [[LOOP:%.*]]
+; DEFAULT: loop:
+; DEFAULT-NEXT: [[V:%.*]] = call i32 @extern_func(i32 0)
+; DEFAULT-NEXT: [[V_1:%.*]] = call i32 @extern_func(i32 1)
+; DEFAULT-NEXT: [[ADD_1:%.*]] = add i32 [[V]], [[V_1]]
+; DEFAULT-NEXT: [[V_2:%.*]] = call i32 @extern_func(i32 2)
+; DEFAULT-NEXT: [[ADD_2:%.*]] = add i32 [[ADD_1]], [[V_2]]
+; DEFAULT-NEXT: [[V_3:%.*]] = call i32 @extern_func(i32 3)
+; DEFAULT-NEXT: [[ADD_3:%.*]] = add i32 [[ADD_2]], [[V_3]]
+; DEFAULT-NEXT: ret i32 [[ADD_3]]
+;
+; LTO-LABEL: @extern_call(
+; LTO-NEXT: entry:
+; LTO-NEXT: br label [[LOOP:%.*]]
+; LTO: loop:
+; LTO-NEXT: [[I:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[LOOP]] ]
+; LTO-NEXT: [[SUM:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[ADD:%.*]], [[LOOP]] ]
+; LTO-NEXT: [[V:%.*]] = call i32 @extern_func(i32 [[I]])
+; LTO-NEXT: [[ADD]] = add i32 [[SUM]], [[V]]
+; LTO-NEXT: [[INC]] = add i32 [[I]], 1
+; LTO-NEXT: [[CMP:%.*]] = icmp ult i32 [[INC]], 4
+; LTO-NEXT: br i1 [[CMP]], label [[LOOP]], label [[EXIT:%.*]]
+; LTO: exit:
+; LTO-NEXT: [[ADD_LCSSA:%.*]] = phi i32 [ [[ADD]], [[LOOP]] ]
+; LTO-NEXT: ret i32 [[ADD_LCSSA]]
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %inc, %loop ]
+ %sum = phi i32 [ 0, %entry ], [ %add, %loop ]
+ %v = call i32 @extern_func(i32 %i)
+ %add = add i32 %sum, %v
+ %inc = add i32 %i, 1
+ %cmp = icmp ult i32 %inc, 4
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret i32 %add
+}
+
+; b) Loop with internal single-use call — suppressed in both modes because
+; internal single-use functions are always considered inline candidates.
+define internal i32 @helper(i32 %x) {
+; DEFAULT-LABEL: @helper(
+; DEFAULT-NEXT: [[ADD:%.*]] = add i32 [[X:%.*]], 42
+; DEFAULT-NEXT: ret i32 [[ADD]]
+;
+; LTO-LABEL: @helper(
+; LTO-NEXT: [[ADD:%.*]] = add i32 [[X:%.*]], 42
+; LTO-NEXT: ret i32 [[ADD]]
+;
+ %add = add i32 %x, 42
+ ret i32 %add
+}
+
+define i32 @internal_single_use_call(ptr %A) {
+; DEFAULT-LABEL: @internal_single_use_call(
+; DEFAULT-NEXT: entry:
+; DEFAULT-NEXT: br label [[LOOP:%.*]]
+; DEFAULT: loop:
+; DEFAULT-NEXT: [[I:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[LOOP]] ]
+; DEFAULT-NEXT: [[SUM:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[ADD:%.*]], [[LOOP]] ]
+; DEFAULT-NEXT: [[V:%.*]] = call i32 @helper(i32 [[I]])
+; DEFAULT-NEXT: [[ADD]] = add i32 [[SUM]], [[V]]
+; DEFAULT-NEXT: [[INC]] = add i32 [[I]], 1
+; DEFAULT-NEXT: [[CMP:%.*]] = icmp ult i32 [[INC]], 4
+; DEFAULT-NEXT: br i1 [[CMP]], label [[LOOP]], label [[EXIT:%.*]]
+; DEFAULT: exit:
+; DEFAULT-NEXT: [[ADD_LCSSA:%.*]] = phi i32 [ [[ADD]], [[LOOP]] ]
+; DEFAULT-NEXT: ret i32 [[ADD_LCSSA]]
+;
+; LTO-LABEL: @internal_single_use_call(
+; LTO-NEXT: entry:
+; LTO-NEXT: br label [[LOOP:%.*]]
+; LTO: loop:
+; LTO-NEXT: [[I:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[INC:%.*]], [[LOOP]] ]
+; LTO-NEXT: [[SUM:%.*]] = phi i32 [ 0, [[ENTRY]] ], [ [[ADD:%.*]], [[LOOP]] ]
+; LTO-NEXT: [[V:%.*]] = call i32 @helper(i32 [[I]])
+; LTO-NEXT: [[ADD]] = add i32 [[SUM]], [[V]]
+; LTO-NEXT: [[INC]] = add i32 [[I]], 1
+; LTO-NEXT: [[CMP:%.*]] = icmp ult i32 [[INC]], 4
+; LTO-NEXT: br i1 [[CMP]], label [[LOOP]], label [[EXIT:%.*]]
+; LTO: exit:
+; LTO-NEXT: [[ADD_LCSSA:%.*]] = phi i32 [ [[ADD]], [[LOOP]] ]
+; LTO-NEXT: ret i32 [[ADD_LCSSA]]
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %inc, %loop ]
+ %sum = phi i32 [ 0, %entry ], [ %add, %loop ]
+ %v = call i32 @helper(i32 %i)
+ %add = add i32 %sum, %v
+ %inc = add i32 %i, 1
+ %cmp = icmp ult i32 %inc, 4
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret i32 %add
+}
+
+; c) Loop without calls — fully unrolled in both modes.
+define i32 @no_call(ptr %A) {
+; DEFAULT-LABEL: @no_call(
+; DEFAULT-NEXT: entry:
+; DEFAULT-NEXT: br label [[LOOP:%.*]]
+; DEFAULT: loop:
+; DEFAULT-NEXT: [[V:%.*]] = load i32, ptr [[A:%.*]], align 4
+; DEFAULT-NEXT: [[ARRAYIDX_1:%.*]] = getelementptr inbounds i32, ptr [[A]], i32 1
+; DEFAULT-NEXT: [[V_1:%.*]] = load i32, ptr [[ARRAYIDX_1]], align 4
+; DEFAULT-NEXT: [[ADD_1:%.*]] = add i32 [[V]], [[V_1]]
+; DEFAULT-NEXT: [[ARRAYIDX_2:%.*]] = getelementptr inbounds i32, ptr [[A]], i32 2
+; DEFAULT-NEXT: [[V_2:%.*]] = load i32, ptr [[ARRAYIDX_2]], align 4
+; DEFAULT-NEXT: [[ADD_2:%.*]] = add i32 [[ADD_1]], [[V_2]]
+; DEFAULT-NEXT: [[ARRAYIDX_3:%.*]] = getelementptr inbounds i32, ptr [[A]], i32 3
+; DEFAULT-NEXT: [[V_3:%.*]] = load i32, ptr [[ARRAYIDX_3]], align 4
+; DEFAULT-NEXT: [[ADD_3:%.*]] = add i32 [[ADD_2]], [[V_3]]
+; DEFAULT-NEXT: ret i32 [[ADD_3]]
+;
+; LTO-LABEL: @no_call(
+; LTO-NEXT: entry:
+; LTO-NEXT: br label [[LOOP:%.*]]
+; LTO: loop:
+; LTO-NEXT: [[V:%.*]] = load i32, ptr [[A:%.*]], align 4
+; LTO-NEXT: [[ARRAYIDX_1:%.*]] = getelementptr inbounds i32, ptr [[A]], i32 1
+; LTO-NEXT: [[V_1:%.*]] = load i32, ptr [[ARRAYIDX_1]], align 4
+; LTO-NEXT: [[ADD_1:%.*]] = add i32 [[V]], [[V_1]]
+; LTO-NEXT: [[ARRAYIDX_2:%.*]] = getelementptr inbounds i32, ptr [[A]], i32 2
+; LTO-NEXT: [[V_2:%.*]] = load i32, ptr [[ARRAYIDX_2]], align 4
+; LTO-NEXT: [[ADD_2:%.*]] = add i32 [[ADD_1]], [[V_2]]
+; LTO-NEXT: [[ARRAYIDX_3:%.*]] = getelementptr inbounds i32, ptr [[A]], i32 3
+; LTO-NEXT: [[V_3:%.*]] = load i32, ptr [[ARRAYIDX_3]], align 4
+; LTO-NEXT: [[ADD_3:%.*]] = add i32 [[ADD_2]], [[V_3]]
+; LTO-NEXT: ret i32 [[ADD_3]]
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i32 [ 0, %entry ], [ %inc, %loop ]
+ %sum = phi i32 [ 0, %entry ], [ %add, %loop ]
+ %arrayidx = getelementptr inbounds i32, ptr %A, i32 %i
+ %v = load i32, ptr %arrayidx
+ %add = add i32 %sum, %v
+ %inc = add i32 %i, 1
+ %cmp = icmp ult i32 %inc, 4
+ br i1 %cmp, label %loop, label %exit
+
+exit:
+ ret i32 %add
+}
More information about the llvm-commits
mailing list