[llvm] [LLVM] Fix use-after-free in AlwaysInliner flatten worklist (PR #194485)
Grigory Pastukhov via llvm-commits
llvm-commits at lists.llvm.org
Tue Apr 28 09:28:41 PDT 2026
https://github.com/grigorypas updated https://github.com/llvm/llvm-project/pull/194485
>From a6a6db5a3ee574bc86ed57de944167b8ffb28f90 Mon Sep 17 00:00:00 2001
From: Grigory Pastukhov <gpastukhov at meta.com>
Date: Mon, 27 Apr 2026 16:03:04 -0700
Subject: [PATCH] [AlwaysInliner] Fix use-after-free in flatten worklist
---
llvm/lib/Transforms/IPO/AlwaysInliner.cpp | 81 +++++++++++------------
llvm/test/Transforms/Inline/flatten.ll | 38 +++++++++++
2 files changed, 77 insertions(+), 42 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/AlwaysInliner.cpp b/llvm/lib/Transforms/IPO/AlwaysInliner.cpp
index 080cb8ddb33fd..bdba6a1dbca53 100644
--- a/llvm/lib/Transforms/IPO/AlwaysInliner.cpp
+++ b/llvm/lib/Transforms/IPO/AlwaysInliner.cpp
@@ -42,7 +42,6 @@ bool AlwaysInlineImpl(
SmallSetVector<CallBase *, 16> Calls;
bool Changed = false;
SmallVector<Function *, 16> InlinedComdatFunctions;
- SmallVector<Function *, 4> NeedFlattening;
auto TryInline = [&](CallBase &CB, Function &Callee,
OptimizationRemarkEmitter &ORE, const char *InlineReason,
@@ -76,52 +75,16 @@ bool AlwaysInlineImpl(
return true;
};
- for (Function &F : make_early_inc_range(M)) {
- if (F.hasFnAttribute(Attribute::Flatten))
- NeedFlattening.push_back(&F);
-
- if (F.isPresplitCoroutine())
+ for (Function &F : M) {
+ if (!F.hasFnAttribute(Attribute::Flatten))
continue;
-
- if (F.isDeclaration() || !isInlineViable(F).isSuccess())
- continue;
-
- Calls.clear();
-
- for (User *U : F.users())
- if (auto *CB = dyn_cast<CallBase>(U))
- if (CB->getCalledFunction() == &F &&
- CB->hasFnAttr(Attribute::AlwaysInline) &&
- !CB->getAttributes().hasFnAttr(Attribute::NoInline))
- Calls.insert(CB);
-
- for (CallBase *CB : Calls) {
- OptimizationRemarkEmitter ORE(CB->getCaller());
- Changed |= TryInline(*CB, F, ORE, "always inline attribute");
- }
-
- F.removeDeadConstantUsers();
- if (F.hasFnAttribute(Attribute::AlwaysInline) && F.isDefTriviallyDead()) {
- if (F.hasComdat()) {
- InlinedComdatFunctions.push_back(&F);
- } else {
- if (FAM)
- FAM->clear(F, F.getName());
- M.getFunctionList().erase(F);
- Changed = true;
- }
- }
- }
-
- // Flatten functions with the flatten attribute using a local worklist.
- for (Function *F : NeedFlattening) {
SmallVector<std::pair<CallBase *, int>, 16> Worklist;
SmallVector<std::pair<Function *, int>, 16> InlineHistory;
SmallVector<CallBase *> NewCallSites;
- OptimizationRemarkEmitter ORE(F);
+ OptimizationRemarkEmitter ORE(&F);
// Collect initial calls.
- for (BasicBlock &BB : *F) {
+ for (BasicBlock &BB : F) {
for (Instruction &I : BB) {
if (auto *CB = dyn_cast<CallBase>(&I)) {
Function *Callee = CB->getCalledFunction();
@@ -141,7 +104,7 @@ bool AlwaysInlineImpl(
continue;
// Detect recursion.
- if (Callee == F) {
+ if (Callee == &F) {
ORE.emit([&]() {
return OptimizationRemarkMissed("inline", "NotInlined",
CB->getDebugLoc(), CB->getParent())
@@ -179,6 +142,40 @@ bool AlwaysInlineImpl(
}
}
+ for (Function &F : make_early_inc_range(M)) {
+ if (F.isPresplitCoroutine())
+ continue;
+
+ if (F.isDeclaration() || !isInlineViable(F).isSuccess())
+ continue;
+
+ Calls.clear();
+
+ for (User *U : F.users())
+ if (auto *CB = dyn_cast<CallBase>(U))
+ if (CB->getCalledFunction() == &F &&
+ CB->hasFnAttr(Attribute::AlwaysInline) &&
+ !CB->getAttributes().hasFnAttr(Attribute::NoInline))
+ Calls.insert(CB);
+
+ for (CallBase *CB : Calls) {
+ OptimizationRemarkEmitter ORE(CB->getCaller());
+ Changed |= TryInline(*CB, F, ORE, "always inline attribute");
+ }
+
+ F.removeDeadConstantUsers();
+ if (F.hasFnAttribute(Attribute::AlwaysInline) && F.isDefTriviallyDead()) {
+ if (F.hasComdat()) {
+ InlinedComdatFunctions.push_back(&F);
+ } else {
+ if (FAM)
+ FAM->clear(F, F.getName());
+ M.getFunctionList().erase(F);
+ Changed = true;
+ }
+ }
+ }
+
if (!InlinedComdatFunctions.empty()) {
// Now we just have the comdat functions. Filter out the ones whose comdats
// are not actually dead.
diff --git a/llvm/test/Transforms/Inline/flatten.ll b/llvm/test/Transforms/Inline/flatten.ll
index 355739a99dac7..4e246ade6b1ae 100644
--- a/llvm/test/Transforms/Inline/flatten.ll
+++ b/llvm/test/Transforms/Inline/flatten.ll
@@ -203,5 +203,43 @@ define i32 @test_mutual_recursion() flatten {
ret i32 %r
}
+; Always-inline function with flatten that becomes dead after inlining.
+define internal i32 @alwaysinline_flatten_callee() alwaysinline flatten {
+ ret i32 5
+}
+
+define i32 @test_alwaysinline_flatten() {
+; ALWAYS-LABEL: define i32 @test_alwaysinline_flatten() {
+; ALWAYS-NEXT: ret i32 5
+;
+; INLINE-LABEL: define i32 @test_alwaysinline_flatten() {
+; INLINE-NEXT: ret i32 5
+;
+; MANDATORY-LABEL: define i32 @test_alwaysinline_flatten() {
+; MANDATORY-NEXT: ret i32 5
+;
+ %r = call i32 @alwaysinline_flatten_callee() alwaysinline
+ ret i32 %r
+}
+
+; Flatten with alwaysinline: callees are flattened first, then the
+; flattened function is always-inlined into callers.
+define internal i32 @inner() {
+ ret i32 7
+}
+
+define internal i32 @alwaysinline_flatten_two_levels() alwaysinline flatten {
+ %r = call i32 @inner()
+ ret i32 %r
+}
+
+define i32 @test_alwaysinline_flatten_two_levels() {
+; CHECK-LABEL: define i32 @test_alwaysinline_flatten_two_levels() {
+; CHECK-NEXT: ret i32 7
+;
+ %r = call i32 @alwaysinline_flatten_two_levels() alwaysinline
+ ret i32 %r
+}
+
; Check that optimization remark is emitted for recursive calls during flattening.
; REMARK: remark: {{.*}} 'test_direct_recursion' is not inlined into 'test_direct_recursion': recursive call during flattening
More information about the llvm-commits
mailing list