[llvm] [InlineCost] Use store-to-load forwarding to resolve call arguments (PR #190607)

Jiří Filek via llvm-commits llvm-commits at lists.llvm.org
Sun Apr 19 03:19:26 PDT 2026


https://github.com/fileho updated https://github.com/llvm/llvm-project/pull/190607

>From 41169e693445fbbd246f3a394a08eed8efc25292 Mon Sep 17 00:00:00 2001
From: Jiri Filek <jiri.filek at gmail.com>
Date: Mon, 6 Apr 2026 14:35:59 +0200
Subject: [PATCH 1/4] [InlineCost] Use store-to-load forwarding to resolve call
 arguments

Use FindAvailableLoadedValue to resolve load instructions in call arguments to constants before inline cost analysis. This gives inliner more precise cost estimate
---
 llvm/lib/Analysis/InlineCost.cpp              | 24 +++++-
 .../Transforms/Inline/inline_store_to_load.ll | 73 +++++++++++++++++++
 2 files changed, 94 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Transforms/Inline/inline_store_to_load.ll

diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp
index 06ff9cc80f638..34d781ae29338 100644
--- a/llvm/lib/Analysis/InlineCost.cpp
+++ b/llvm/lib/Analysis/InlineCost.cpp
@@ -23,6 +23,7 @@
 #include "llvm/Analysis/DomConditionCache.h"
 #include "llvm/Analysis/EphemeralValuesCache.h"
 #include "llvm/Analysis/InstructionSimplify.h"
+#include "llvm/Analysis/Loads.h"
 #include "llvm/Analysis/LoopInfo.h"
 #include "llvm/Analysis/MemoryBuiltins.h"
 #include "llvm/Analysis/OptimizationRemarkEmitter.h"
@@ -2931,11 +2932,28 @@ InlineResult CallAnalyzer::analyze() {
   auto CAI = CandidateCall.arg_begin();
   for (Argument &FAI : F.args()) {
     assert(CAI != CandidateCall.arg_end());
-    SimplifiedValues[&FAI] = *CAI;
-    if (isa<Constant>(*CAI))
+    Value *CallerArg = *CAI;
+
+    // Simple store-to-load forwarding of arguments in the caller.
+    // This can greatly reduce the function cost via early returns.
+    if (auto *LI = dyn_cast<LoadInst>(CallerArg)) {
+      BasicBlock::iterator ScanFrom = LI->getIterator();
+      if (Value *Loaded =
+              FindAvailableLoadedValue(LI, LI->getParent(), ScanFrom, 0)) {
+        if (auto *C = dyn_cast<Constant>(Loaded)) {
+          if (C->getType() == LI->getType())
+            CallerArg = C;
+          else if (C->isNullValue())
+            CallerArg = Constant::getNullValue(LI->getType());
+        }
+      }
+    }
+
+    SimplifiedValues[&FAI] = CallerArg;
+    if (isa<Constant>(CallerArg))
       ++NumConstantArgs;
 
-    Value *PtrArg = *CAI;
+    Value *PtrArg = CallerArg;
     if (ConstantInt *C = stripAndComputeInBoundsConstantOffsets(PtrArg)) {
       ConstantOffsetPtrs[&FAI] = std::make_pair(PtrArg, C->getValue());
 
diff --git a/llvm/test/Transforms/Inline/inline_store_to_load.ll b/llvm/test/Transforms/Inline/inline_store_to_load.ll
new file mode 100644
index 0000000000000..919dc9aafa403
--- /dev/null
+++ b/llvm/test/Transforms/Inline/inline_store_to_load.ll
@@ -0,0 +1,73 @@
+; RUN: opt < %s -passes=inline -inline-threshold=20 -S | FileCheck %s
+
+; Test that the inliner can use store-to-load forwarding to resolve call
+; arguments to constants. We use -inline-threshold=20 so that @callee is
+; only inlined when the constant argument enables dead-branch elimination.
+
+target datalayout = "p:64:64"
+
+; Two paths: mode==0 is trivial (ret), otherwise too expensive to inline.
+define i32 @callee(i32 %mode, ptr %p) {
+entry:
+  %cmp = icmp eq i32 %mode, 0
+  br i1 %cmp, label %fast, label %slow
+fast:
+  %v = load i32, ptr %p
+  ret i32 %v
+slow:
+  %a1 = load volatile i32, ptr %p
+  %a2 = load volatile i32, ptr %p
+  %x1 = add i32 %a1, %a2
+  %a3 = load volatile i32, ptr %p
+  %x2 = add i32 %x1, %a3
+  %a4 = load volatile i32, ptr %p
+  %x3 = add i32 %x2, %a4
+  %a5 = load volatile i32, ptr %p
+  %x4 = add i32 %x3, %a5
+  ret i32 %x4
+}
+
+; Trivial when called with null, otherwise too expensive to inline.
+define void @recursive_callee(ptr %x) {
+entry:
+  %cmp = icmp eq ptr %x, null
+  br i1 %cmp, label %done, label %recurse
+recurse:
+  %next = load ptr, ptr %x
+  call void @recursive_callee(ptr %next)
+  %v = load volatile i32, ptr %x
+  br label %done
+done:
+  ret void
+}
+
+; Store-to-load forwarding resolves %mode to 0, making only the fast path
+; reachable and the callee cheap enough to inline.
+; CHECK-LABEL: define i32 @caller_store_forward(
+; CHECK: %cmp.i = icmp eq i32 %mode, 0
+; CHECK: callee.exit:
+; CHECK-NEXT: %{{.*}} = phi i32
+; CHECK-NEXT: ret i32
+define i32 @caller_store_forward() {
+entry:
+  %p = alloca i32
+  store i32 0, ptr %p
+  %mode = load i32, ptr %p
+  %r = call i32 @callee(i32 %mode, ptr %p)
+  ret i32 %r
+}
+
+; Memset-to-load forwarding converts the zero-filled integer to a null
+; pointer, making the null check take the early exit.
+; CHECK-LABEL: define void @caller_memset_ptr(
+; CHECK: %cmp.i = icmp eq ptr %x, null
+; CHECK: recursive_callee.exit:
+; CHECK-NEXT: ret void
+define void @caller_memset_ptr() {
+entry:
+  %p = alloca ptr
+  call void @llvm.memset.p0.i64(ptr %p, i8 0, i64 8, i1 false)
+  %x = load ptr, ptr %p
+  call void @recursive_callee(ptr %x)
+  ret void
+}

>From a9eb1692ec01e4ddd3a4eb1d0794f3a8bfecc610 Mon Sep 17 00:00:00 2001
From: Jiri Filek <jiri.filek at gmail.com>
Date: Mon, 13 Apr 2026 20:37:51 +0200
Subject: [PATCH 2/4] revert changes in InlineCost

---
 llvm/lib/Analysis/InlineCost.cpp | 24 +++---------------------
 1 file changed, 3 insertions(+), 21 deletions(-)

diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp
index 34d781ae29338..06ff9cc80f638 100644
--- a/llvm/lib/Analysis/InlineCost.cpp
+++ b/llvm/lib/Analysis/InlineCost.cpp
@@ -23,7 +23,6 @@
 #include "llvm/Analysis/DomConditionCache.h"
 #include "llvm/Analysis/EphemeralValuesCache.h"
 #include "llvm/Analysis/InstructionSimplify.h"
-#include "llvm/Analysis/Loads.h"
 #include "llvm/Analysis/LoopInfo.h"
 #include "llvm/Analysis/MemoryBuiltins.h"
 #include "llvm/Analysis/OptimizationRemarkEmitter.h"
@@ -2932,28 +2931,11 @@ InlineResult CallAnalyzer::analyze() {
   auto CAI = CandidateCall.arg_begin();
   for (Argument &FAI : F.args()) {
     assert(CAI != CandidateCall.arg_end());
-    Value *CallerArg = *CAI;
-
-    // Simple store-to-load forwarding of arguments in the caller.
-    // This can greatly reduce the function cost via early returns.
-    if (auto *LI = dyn_cast<LoadInst>(CallerArg)) {
-      BasicBlock::iterator ScanFrom = LI->getIterator();
-      if (Value *Loaded =
-              FindAvailableLoadedValue(LI, LI->getParent(), ScanFrom, 0)) {
-        if (auto *C = dyn_cast<Constant>(Loaded)) {
-          if (C->getType() == LI->getType())
-            CallerArg = C;
-          else if (C->isNullValue())
-            CallerArg = Constant::getNullValue(LI->getType());
-        }
-      }
-    }
-
-    SimplifiedValues[&FAI] = CallerArg;
-    if (isa<Constant>(CallerArg))
+    SimplifiedValues[&FAI] = *CAI;
+    if (isa<Constant>(*CAI))
       ++NumConstantArgs;
 
-    Value *PtrArg = CallerArg;
+    Value *PtrArg = *CAI;
     if (ConstantInt *C = stripAndComputeInBoundsConstantOffsets(PtrArg)) {
       ConstantOffsetPtrs[&FAI] = std::make_pair(PtrArg, C->getValue());
 

>From 5f97c3474be44c92d640c92b8806e44b735f6f94 Mon Sep 17 00:00:00 2001
From: Jiri Filek <jiri.filek at gmail.com>
Date: Mon, 13 Apr 2026 20:42:24 +0200
Subject: [PATCH 3/4] [Inliner] Add store-to-load forwarding to inliner  -
 forward only when some inlining already happended  - add phase ordering test

---
 llvm/lib/Transforms/IPO/Inliner.cpp           | 37 +++++++++
 .../Transforms/Inline/inline_store_to_load.ll | 49 +++++++-----
 .../PhaseOrdering/inline-store-to-load.ll     | 77 +++++++++++++++++++
 3 files changed, 145 insertions(+), 18 deletions(-)
 create mode 100644 llvm/test/Transforms/PhaseOrdering/inline-store-to-load.ll

diff --git a/llvm/lib/Transforms/IPO/Inliner.cpp b/llvm/lib/Transforms/IPO/Inliner.cpp
index fb376562f6781..9c5c5788b69a6 100644
--- a/llvm/lib/Transforms/IPO/Inliner.cpp
+++ b/llvm/lib/Transforms/IPO/Inliner.cpp
@@ -30,6 +30,7 @@
 #include "llvm/Analysis/InlineAdvisor.h"
 #include "llvm/Analysis/InlineCost.h"
 #include "llvm/Analysis/LazyCallGraph.h"
+#include "llvm/Analysis/Loads.h"
 #include "llvm/Analysis/OptimizationRemarkEmitter.h"
 #include "llvm/Analysis/ProfileSummaryInfo.h"
 #include "llvm/Analysis/ReplayInlineAdvisor.h"
@@ -80,6 +81,14 @@ static cl::opt<int> IntraSCCCostMultiplier(
         "multiplied by intra-scc-cost-multiplier). This is to prevent tons of "
         "inlining through a child SCC which can cause terrible compile times"));
 
+static cl::opt<unsigned> InlinerForwardingScanLimit(
+    "inliner-forwarding-scan-limit", cl::init(16), cl::Hidden,
+    cl::desc("Maximum number of instructions to scan backward for "
+             "store-to-load forwarding in subsequent inlining decisions. "
+             "DefMaxInstsToScan=6 is not enough and misses inlining "
+             "opportunities (e.g. when class stores into mutiple members in "
+             "ctor and afterwards calls a function reading those members)"));
+
 /// A flag for test, so we can print the content of the advisor when running it
 /// as part of the default (e.g. -O3) pipeline.
 static cl::opt<bool> KeepAdvisorForPrinting("keep-inline-advisor-for-printing",
@@ -358,6 +367,34 @@ PreservedAnalyses InlinerPass::run(LazyCallGraph::SCC &InitialC,
         continue;
       }
 
+      // Store-to-load forwarding, loads can be sometimes simplified to
+      // constants from stores introduced by previous inlining
+      if (DidInline) {
+        for (Value *Arg : CB->args()) {
+          auto *LI = dyn_cast<LoadInst>(Arg);
+          if (!LI || !LI->isSimple())
+            continue;
+          BasicBlock::iterator BBI = LI->getIterator();
+          Value *Available = FindAvailableLoadedValue(
+              LI, LI->getParent(), BBI, InlinerForwardingScanLimit);
+          if (!Available)
+            continue;
+          auto *C = dyn_cast<Constant>(Available);
+          if (!C)
+            continue;
+          // Handle type mismatches from memset forwarding (e.g. memset
+          // writes i64 0 but the load type is ptr).
+          if (C->getType() != LI->getType()) {
+            if (C->isNullValue())
+              C = Constant::getNullValue(LI->getType());
+            else
+              continue;
+          }
+          LI->replaceAllUsesWith(C);
+          LI->eraseFromParent();
+        }
+      }
+
       std::unique_ptr<InlineAdvice> Advice =
           Advisor.getAdvice(*CB, OnlyMandatory);
 
diff --git a/llvm/test/Transforms/Inline/inline_store_to_load.ll b/llvm/test/Transforms/Inline/inline_store_to_load.ll
index 919dc9aafa403..151e138cee789 100644
--- a/llvm/test/Transforms/Inline/inline_store_to_load.ll
+++ b/llvm/test/Transforms/Inline/inline_store_to_load.ll
@@ -1,8 +1,10 @@
-; RUN: opt < %s -passes=inline -inline-threshold=20 -S | FileCheck %s
+; RUN: opt -S -passes="cgscc(inline)" < %s | FileCheck %s
 
-; Test that the inliner can use store-to-load forwarding to resolve call
-; arguments to constants. We use -inline-threshold=20 so that @callee is
-; only inlined when the constant argument enables dead-branch elimination.
+; Test that the CGSCC inliner performs store-to-load forwarding for call
+; arguments after inlining a function in the same caller. The first call
+; (@init or @init_memset) is inlined, producing stores. Forwarding then
+; resolves the subsequent load to a constant, enabling inlining of the
+; second callee.
 
 target datalayout = "p:64:64"
 
@@ -41,17 +43,29 @@ done:
   ret void
 }
 
-; Store-to-load forwarding resolves %mode to 0, making only the fast path
-; reachable and the callee cheap enough to inline.
-; CHECK-LABEL: define i32 @caller_store_forward(
-; CHECK: %cmp.i = icmp eq i32 %mode, 0
-; CHECK: callee.exit:
-; CHECK-NEXT: %{{.*}} = phi i32
-; CHECK-NEXT: ret i32
+; Trivially cheap — inlined first, producing a store.
+define internal void @init_i32(ptr %p) {
+  store i32 0, ptr %p
+  ret void
+}
+
+; Trivially cheap — inlined first, producing a memset.
+define internal void @init_memset(ptr %p) {
+  call void @llvm.memset.p0.i64(ptr %p, i8 0, i64 8, i1 false)
+  ret void
+}
+
+declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
+
+; After inlining @init_i32: store 0 → %p.
+; Forwarding resolves %mode to 0, making only the fast path reachable.
+; CHECK-LABEL: @caller_store_forward(
+; CHECK-NOT: call i32 @callee
+; CHECK: ret i32
 define i32 @caller_store_forward() {
 entry:
   %p = alloca i32
-  store i32 0, ptr %p
+  call void @init_i32(ptr %p)
   %mode = load i32, ptr %p
   %r = call i32 @callee(i32 %mode, ptr %p)
   ret i32 %r
@@ -59,14 +73,13 @@ entry:
 
 ; Memset-to-load forwarding converts the zero-filled integer to a null
 ; pointer, making the null check take the early exit.
-; CHECK-LABEL: define void @caller_memset_ptr(
-; CHECK: %cmp.i = icmp eq ptr %x, null
-; CHECK: recursive_callee.exit:
-; CHECK-NEXT: ret void
-define void @caller_memset_ptr() {
+; CHECK-LABEL: @caller_memset_forward(
+; CHECK-NOT: call void @recursive_callee
+; CHECK: ret void
+define void @caller_memset_forward() {
 entry:
   %p = alloca ptr
-  call void @llvm.memset.p0.i64(ptr %p, i8 0, i64 8, i1 false)
+  call void @init_memset(ptr %p)
   %x = load ptr, ptr %p
   call void @recursive_callee(ptr %x)
   ret void
diff --git a/llvm/test/Transforms/PhaseOrdering/inline-store-to-load.ll b/llvm/test/Transforms/PhaseOrdering/inline-store-to-load.ll
new file mode 100644
index 0000000000000..5f8d4755c357a
--- /dev/null
+++ b/llvm/test/Transforms/PhaseOrdering/inline-store-to-load.ll
@@ -0,0 +1,77 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -passes="default<O3>" < %s | FileCheck %s
+
+; Test that the CGSCC inliner forwards stores to load arguments after
+; inlining. This addresses a phase ordering issue: the constructor is
+; inlined first (producing stores), and then the destructor's load
+; arguments should see the stored constants. Without this forwarding,
+; the recursive erase function appears too expensive to inline.
+;
+; This models the std::set<int>{} pattern where the constructor stores
+; null to the root pointer and the destructor checks it before recursive
+; tree deletion (_Rb_tree::_M_erase).
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+declare void @free(ptr)
+declare void @use(i64)
+
+; Models _Rb_tree constructor: stores null root + zero node count.
+define internal void @ctor(ptr %this) {
+  store ptr null, ptr %this, align 8
+  %count = getelementptr inbounds i8, ptr %this, i64 8
+  store i64 0, ptr %count, align 8
+  ret void
+}
+
+; Models ~_Rb_tree: loads root and calls the recursive eraser.
+define internal void @dtor(ptr %this) {
+  %root = load ptr, ptr %this, align 8
+  call void @erase(ptr %root)
+  ret void
+}
+
+; Models _Rb_tree::_M_erase — recursive node deletion.
+; With null: icmp + branch to done → trivially cheap.
+; With unknown ptr: recursive calls + external calls → too expensive.
+define internal void @erase(ptr %node) {
+  %is_null = icmp eq ptr %node, null
+  br i1 %is_null, label %done, label %recurse
+
+recurse:
+  %left_val = load ptr, ptr %node, align 8
+  call void @erase(ptr %left_val)
+  %right_ptr = getelementptr inbounds i8, ptr %node, i64 8
+  %right_val = load ptr, ptr %right_ptr, align 8
+  call void @erase(ptr %right_val)
+  %d1 = getelementptr inbounds i8, ptr %node, i64 16
+  %v1 = load i64, ptr %d1, align 8
+  call void @use(i64 %v1)
+  %d2 = getelementptr inbounds i8, ptr %node, i64 24
+  %v2 = load i64, ptr %d2, align 8
+  call void @use(i64 %v2)
+  %d3 = getelementptr inbounds i8, ptr %node, i64 32
+  %v3 = load i64, ptr %d3, align 8
+  call void @use(i64 %v3)
+  %d4 = getelementptr inbounds i8, ptr %node, i64 40
+  %v4 = load i64, ptr %d4, align 8
+  call void @use(i64 %v4)
+  %d5 = getelementptr inbounds i8, ptr %node, i64 48
+  %v5 = load i64, ptr %d5, align 8
+  call void @use(i64 %v5)
+  call void @free(ptr %node)
+  br label %done
+
+done:
+  ret void
+}
+
+; CHECK-LABEL: @test_empty_tree(
+; CHECK-NEXT: ret void
+define void @test_empty_tree() {
+  %tree = alloca ptr, align 8
+  call void @ctor(ptr %tree)
+  call void @dtor(ptr %tree)
+  ret void
+}

>From 9398928068f3dd433d526062c1572df7e5edcfe8 Mon Sep 17 00:00:00 2001
From: Jiri Filek <jiri.filek at gmail.com>
Date: Sun, 19 Apr 2026 12:19:06 +0200
Subject: [PATCH 4/4] Patch tests by update_test_checks.py

---
 .../Transforms/Inline/inline_store_to_load.ll | 37 +++++++++++++++----
 .../PhaseOrdering/inline-store-to-load.ll     |  6 ++-
 2 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/llvm/test/Transforms/Inline/inline_store_to_load.ll b/llvm/test/Transforms/Inline/inline_store_to_load.ll
index 151e138cee789..ca8d8bc0930e0 100644
--- a/llvm/test/Transforms/Inline/inline_store_to_load.ll
+++ b/llvm/test/Transforms/Inline/inline_store_to_load.ll
@@ -1,3 +1,4 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; RUN: opt -S -passes="cgscc(inline)" < %s | FileCheck %s
 
 ; Test that the CGSCC inliner performs store-to-load forwarding for call
@@ -9,7 +10,7 @@
 target datalayout = "p:64:64"
 
 ; Two paths: mode==0 is trivial (ret), otherwise too expensive to inline.
-define i32 @callee(i32 %mode, ptr %p) {
+define internal i32 @callee(i32 %mode, ptr %p) {
 entry:
   %cmp = icmp eq i32 %mode, 0
   br i1 %cmp, label %fast, label %slow
@@ -30,7 +31,20 @@ slow:
 }
 
 ; Trivial when called with null, otherwise too expensive to inline.
-define void @recursive_callee(ptr %x) {
+define internal void @recursive_callee(ptr %x) {
+; CHECK-LABEL: define internal void @recursive_callee(
+; CHECK-SAME: ptr [[X:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq ptr [[X]], null
+; CHECK-NEXT:    br i1 [[CMP]], label %[[DONE:.*]], label %[[RECURSE:.*]]
+; CHECK:       [[RECURSE]]:
+; CHECK-NEXT:    [[NEXT:%.*]] = load ptr, ptr [[X]], align 8
+; CHECK-NEXT:    call void @recursive_callee(ptr [[NEXT]])
+; CHECK-NEXT:    [[V:%.*]] = load volatile i32, ptr [[X]], align 4
+; CHECK-NEXT:    br label %[[DONE]]
+; CHECK:       [[DONE]]:
+; CHECK-NEXT:    ret void
+;
 entry:
   %cmp = icmp eq ptr %x, null
   br i1 %cmp, label %done, label %recurse
@@ -59,10 +73,14 @@ declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
 
 ; After inlining @init_i32: store 0 → %p.
 ; Forwarding resolves %mode to 0, making only the fast path reachable.
-; CHECK-LABEL: @caller_store_forward(
-; CHECK-NOT: call i32 @callee
-; CHECK: ret i32
 define i32 @caller_store_forward() {
+; CHECK-LABEL: define i32 @caller_store_forward() {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[P:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    store i32 0, ptr [[P]], align 4
+; CHECK-NEXT:    [[V_I:%.*]] = load i32, ptr [[P]], align 4
+; CHECK-NEXT:    ret i32 [[V_I]]
+;
 entry:
   %p = alloca i32
   call void @init_i32(ptr %p)
@@ -73,10 +91,13 @@ entry:
 
 ; Memset-to-load forwarding converts the zero-filled integer to a null
 ; pointer, making the null check take the early exit.
-; CHECK-LABEL: @caller_memset_forward(
-; CHECK-NOT: call void @recursive_callee
-; CHECK: ret void
 define void @caller_memset_forward() {
+; CHECK-LABEL: define void @caller_memset_forward() {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[P:%.*]] = alloca ptr, align 8
+; CHECK-NEXT:    call void @llvm.memset.p0.i64(ptr [[P]], i8 0, i64 8, i1 false)
+; CHECK-NEXT:    ret void
+;
 entry:
   %p = alloca ptr
   call void @init_memset(ptr %p)
diff --git a/llvm/test/Transforms/PhaseOrdering/inline-store-to-load.ll b/llvm/test/Transforms/PhaseOrdering/inline-store-to-load.ll
index 5f8d4755c357a..59bc38a7a2c24 100644
--- a/llvm/test/Transforms/PhaseOrdering/inline-store-to-load.ll
+++ b/llvm/test/Transforms/PhaseOrdering/inline-store-to-load.ll
@@ -67,9 +67,11 @@ done:
   ret void
 }
 
-; CHECK-LABEL: @test_empty_tree(
-; CHECK-NEXT: ret void
 define void @test_empty_tree() {
+; CHECK-LABEL: define void @test_empty_tree(
+; CHECK-SAME: ) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    ret void
+;
   %tree = alloca ptr, align 8
   call void @ctor(ptr %tree)
   call void @dtor(ptr %tree)



More information about the llvm-commits mailing list