[llvm] [Attributor] Do not use a recursive callee's values as the call result (PR #229015)

via llvm-commits llvm-commits at lists.llvm.org
Mon Oct 5 02:07:53 PDT 2026


https://github.com/xermicus created https://github.com/llvm/llvm-project/pull/229015

`AAPotentialValuesCallSiteReturned::updateImpl` maps the values a callee
returns to the call site. Callee arguments are translated to the call site
operands and constants are kept, while any other returned value is kept
untranslated. If that value is an instruction or argument of the caller
itself, which happens for a direct self-recursive call or for mutual
recursion within the CGSCC or interprocedural scope, `AA::isValidInScope`
accepts it and the value is treated as the caller's instance although it
belongs to a deeper recursive frame. Manifest then replaces the call result
with it.

For example, in

```
  walk(d, a) = d == 0 ? a : walk(d - 1, a + 1)
```

the result of the recursive call was replaced by `a + 1`, so walk(3, 5)
returned 6 instead of 8, both with `-passes=attributor` and
`-passes=attributor-cgscc`. Mutual recursion (ping/pong in the new test)
miscompiles in the same way with -passes=attributor-cgscc.

Give up when the untranslated value is valid in the caller scope but not
dynamically unique. #107323 fixed the analogous issue for returned
arguments.

Test changes: returned.ll (recBad) and align.ll (test10a, test10b) lose
deductions that were only correct by coincidence through the same frame
confusion. memory_locations.ll gains a correct noalias on a recursive call
returning malloc.

The bug was found through a miscompile in the revive Solidity compiler
(paritytech/revive).

AI tool use: Claude Code investigated and fixed the bug, I reviewed it.


>From 2d8a50fde489315e0aafd8948085787cc7ebbeb9 Mon Sep 17 00:00:00 2001
From: xermicus <cyrill at parity.io>
Date: Mon, 5 Oct 2026 10:44:17 +0200
Subject: [PATCH] [Attributor] Do not use a recursive callee's values as the
 call result

AAPotentialValuesCallSiteReturned::updateImpl maps the values a callee
returns to the call site. Callee arguments are translated to the call site
operands and constants are kept, while any other returned value is kept
untranslated. If that value is an instruction or argument of the caller
itself, which happens for a direct self-recursive call or for mutual
recursion within the CGSCC or interprocedural scope, AA::isValidInScope
accepts it and the value is treated as the caller's instance although it
belongs to a deeper recursive frame. Manifest then replaces the call result
with it.

For example, in

  walk(d, a) = d == 0 ? a : walk(d - 1, a + 1)

the result of the recursive call was replaced by `a + 1`, so walk(3, 5)
returned 6 instead of 8, both with -passes=attributor and
-passes=attributor-cgscc. Mutual recursion (ping/pong in the new test)
miscompiles in the same way with -passes=attributor-cgscc.

Give up when the untranslated value is valid in the caller scope but not
dynamically unique. #107323 fixed the analogous issue for returned
arguments.

Test changes: returned.ll (recBad) and align.ll (test10a, test10b) lose
deductions that were only correct by coincidence through the same frame
confusion. memory_locations.ll gains a correct noalias on a recursive call
returning malloc.

The bug was found through a miscompile in the revive Solidity compiler
(paritytech/revive).

Assisted-by: Claude Code (Claude Opus 5.5)
---
 .../Transforms/IPO/AttributorAttributes.cpp   |   5 +
 llvm/test/Transforms/Attributor/align.ll      |  34 +++--
 .../Transforms/Attributor/memory_locations.ll |   2 +-
 .../Attributor/recursive-call-returned.ll     | 119 ++++++++++++++++++
 llvm/test/Transforms/Attributor/returned.ll   |   4 +-
 5 files changed, 143 insertions(+), 21 deletions(-)
 create mode 100644 llvm/test/Transforms/Attributor/recursive-call-returned.ll

diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
index 3bf737743749a..0bf55fa52ea92 100644
--- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
+++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp
@@ -11869,6 +11869,11 @@ struct AAPotentialValuesCallSiteReturned : AAPotentialValuesImpl {
           // Nothing to do as long as no value was determined.
           continue;
         }
+        // A caller value that reaches the call through the callee's returned
+        // values may be the instance of a deeper, recursive frame.
+        if (!*CallerV && AA::isValidInScope(*V, Caller) &&
+            !AA::isDynamicallyUnique(A, *this, *V))
+          return false;
         V = *CallerV ? *CallerV : V;
         if (*CallerV && AA::isDynamicallyUnique(A, *this, *V)) {
           if (recurseForValue(A, IRPosition::value(*V), S))
diff --git a/llvm/test/Transforms/Attributor/align.ll b/llvm/test/Transforms/Attributor/align.ll
index 904233408c144..f9e6f7b212e24 100644
--- a/llvm/test/Transforms/Attributor/align.ll
+++ b/llvm/test/Transforms/Attributor/align.ll
@@ -416,14 +416,14 @@ define void @test9_traversal(i1 %cnd, ptr align 4 %B, ptr align 8 %C) {
 ; FIXME: This will work with an upcoming patch (D66618 or similar)
 ;             store i32 -1, ptr %g1, align 32
 define ptr @test10a(ptr align 32 %p) {
-; TUNIT: Function Attrs: nofree nosync nounwind memory(argmem: readwrite)
+; TUNIT: Function Attrs: nofree nosync nounwind
 ; TUNIT-LABEL: define {{[^@]+}}@test10a
 ; TUNIT-SAME: (ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P:%.*]]) #[[ATTR3:[0-9]+]] {
 ; TUNIT-NEXT:    [[L:%.*]] = load i32, ptr [[P]], align 32
 ; TUNIT-NEXT:    [[C:%.*]] = icmp eq i32 [[L]], 0
 ; TUNIT-NEXT:    br i1 [[C]], label [[T:%.*]], label [[F:%.*]]
 ; TUNIT:       t:
-; TUNIT-NEXT:    [[R:%.*]] = call align 32 ptr @test10a(ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P]]) #[[ATTR13:[0-9]+]]
+; TUNIT-NEXT:    [[R:%.*]] = call align 32 ptr @test10a(ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P]]) #[[ATTR3]]
 ; TUNIT-NEXT:    store i32 1, ptr [[R]], align 32
 ; TUNIT-NEXT:    [[G0:%.*]] = getelementptr i32, ptr [[P]], i32 8
 ; TUNIT-NEXT:    br label [[E:%.*]]
@@ -435,14 +435,14 @@ define ptr @test10a(ptr align 32 %p) {
 ; TUNIT-NEXT:    [[PHI:%.*]] = phi ptr [ [[G0]], [[T]] ], [ [[G1]], [[F]] ]
 ; TUNIT-NEXT:    ret ptr [[PHI]]
 ;
-; CGSCC: Function Attrs: nofree nosync nounwind memory(argmem: readwrite)
+; CGSCC: Function Attrs: nofree nosync nounwind
 ; CGSCC-LABEL: define {{[^@]+}}@test10a
 ; CGSCC-SAME: (ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P:%.*]]) #[[ATTR4:[0-9]+]] {
 ; CGSCC-NEXT:    [[L:%.*]] = load i32, ptr [[P]], align 32
 ; CGSCC-NEXT:    [[C:%.*]] = icmp eq i32 [[L]], 0
 ; CGSCC-NEXT:    br i1 [[C]], label [[T:%.*]], label [[F:%.*]]
 ; CGSCC:       t:
-; CGSCC-NEXT:    [[R:%.*]] = call align 32 ptr @test10a(ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P]]) #[[ATTR16:[0-9]+]]
+; CGSCC-NEXT:    [[R:%.*]] = call align 32 ptr @test10a(ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P]]) #[[ATTR4]]
 ; CGSCC-NEXT:    store i32 1, ptr [[R]], align 32
 ; CGSCC-NEXT:    [[G0:%.*]] = getelementptr i32, ptr [[P]], i32 8
 ; CGSCC-NEXT:    br label [[E:%.*]]
@@ -478,14 +478,14 @@ e:
 ; FIXME: This will work with an upcoming patch (D66618 or similar)
 ;             store i32 -1, ptr %g1, align 32
 define ptr @test10b(ptr align 32 %p) {
-; TUNIT: Function Attrs: nofree nosync nounwind memory(argmem: readwrite)
+; TUNIT: Function Attrs: nofree nosync nounwind
 ; TUNIT-LABEL: define {{[^@]+}}@test10b
 ; TUNIT-SAME: (ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P:%.*]]) #[[ATTR3]] {
 ; TUNIT-NEXT:    [[L:%.*]] = load i32, ptr [[P]], align 32
 ; TUNIT-NEXT:    [[C:%.*]] = icmp eq i32 [[L]], 0
 ; TUNIT-NEXT:    br i1 [[C]], label [[T:%.*]], label [[F:%.*]]
 ; TUNIT:       t:
-; TUNIT-NEXT:    [[R:%.*]] = call align 32 ptr @test10b(ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P]]) #[[ATTR13]]
+; TUNIT-NEXT:    [[R:%.*]] = call align 32 ptr @test10b(ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P]]) #[[ATTR3]]
 ; TUNIT-NEXT:    store i32 1, ptr [[R]], align 32
 ; TUNIT-NEXT:    [[G0:%.*]] = getelementptr i32, ptr [[P]], i32 8
 ; TUNIT-NEXT:    br label [[E:%.*]]
@@ -497,14 +497,14 @@ define ptr @test10b(ptr align 32 %p) {
 ; TUNIT-NEXT:    [[PHI:%.*]] = phi ptr [ [[G0]], [[T]] ], [ [[G1]], [[F]] ]
 ; TUNIT-NEXT:    ret ptr [[PHI]]
 ;
-; CGSCC: Function Attrs: nofree nosync nounwind memory(argmem: readwrite)
+; CGSCC: Function Attrs: nofree nosync nounwind
 ; CGSCC-LABEL: define {{[^@]+}}@test10b
 ; CGSCC-SAME: (ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P:%.*]]) #[[ATTR4]] {
 ; CGSCC-NEXT:    [[L:%.*]] = load i32, ptr [[P]], align 32
 ; CGSCC-NEXT:    [[C:%.*]] = icmp eq i32 [[L]], 0
 ; CGSCC-NEXT:    br i1 [[C]], label [[T:%.*]], label [[F:%.*]]
 ; CGSCC:       t:
-; CGSCC-NEXT:    [[R:%.*]] = call align 32 ptr @test10b(ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P]]) #[[ATTR16]]
+; CGSCC-NEXT:    [[R:%.*]] = call align 32 ptr @test10b(ptr nofree noundef nonnull align 32 dereferenceable(4) "no-capture-maybe-returned" [[P]]) #[[ATTR4]]
 ; CGSCC-NEXT:    store i32 1, ptr [[R]], align 32
 ; CGSCC-NEXT:    [[G0:%.*]] = getelementptr i32, ptr [[P]], i32 8
 ; CGSCC-NEXT:    br label [[E:%.*]]
@@ -946,7 +946,7 @@ define i32 @musttail_caller_1(ptr %p) {
 ; TUNIT-NEXT:    [[C:%.*]] = load i1, ptr @cnd, align 1
 ; TUNIT-NEXT:    br i1 [[C]], label [[MT:%.*]], label [[EXIT:%.*]]
 ; TUNIT:       mt:
-; TUNIT-NEXT:    [[V:%.*]] = musttail call i32 @musttail_callee_1(ptr nofree noundef readonly captures(none) [[P]]) #[[ATTR14:[0-9]+]]
+; TUNIT-NEXT:    [[V:%.*]] = musttail call i32 @musttail_callee_1(ptr nofree noundef readonly captures(none) [[P]]) #[[ATTR13:[0-9]+]]
 ; TUNIT-NEXT:    ret i32 [[V]]
 ; TUNIT:       exit:
 ; TUNIT-NEXT:    ret i32 0
@@ -957,7 +957,7 @@ define i32 @musttail_caller_1(ptr %p) {
 ; CGSCC-NEXT:    [[C:%.*]] = load i1, ptr @cnd, align 1
 ; CGSCC-NEXT:    br i1 [[C]], label [[MT:%.*]], label [[EXIT:%.*]]
 ; CGSCC:       mt:
-; CGSCC-NEXT:    [[V:%.*]] = musttail call i32 @musttail_callee_1(ptr nofree noundef nonnull readonly captures(none) dereferenceable(4) [[P]]) #[[ATTR17:[0-9]+]]
+; CGSCC-NEXT:    [[V:%.*]] = musttail call i32 @musttail_callee_1(ptr nofree noundef nonnull readonly captures(none) dereferenceable(4) [[P]]) #[[ATTR16:[0-9]+]]
 ; CGSCC-NEXT:    ret i32 [[V]]
 ; CGSCC:       exit:
 ; CGSCC-NEXT:    ret i32 0
@@ -1089,7 +1089,7 @@ define ptr @aligned_8_return_caller(ptr align(16) %a, i1 %c1, i1 %c2) {
 ; TUNIT: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
 ; TUNIT-LABEL: define {{[^@]+}}@aligned_8_return_caller
 ; TUNIT-SAME: (ptr nofree readnone align 16 "no-capture-maybe-returned" [[A:%.*]], i1 [[C1:%.*]], i1 [[C2:%.*]]) #[[ATTR10]] {
-; TUNIT-NEXT:    [[R:%.*]] = call align 8 ptr @aligned_8_return(ptr noalias nofree readnone align 16 "no-capture-maybe-returned" [[A]], i1 noundef [[C1]], i1 [[C2]]) #[[ATTR15:[0-9]+]]
+; TUNIT-NEXT:    [[R:%.*]] = call align 8 ptr @aligned_8_return(ptr noalias nofree readnone align 16 "no-capture-maybe-returned" [[A]], i1 noundef [[C1]], i1 [[C2]]) #[[ATTR14:[0-9]+]]
 ; TUNIT-NEXT:    ret ptr [[R]]
 ;
 ; CGSCC: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(none)
@@ -1221,7 +1221,7 @@ attributes #2 = { null_pointer_is_valid }
 ; TUNIT: attributes #[[ATTR0]] = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable }
 ; TUNIT: attributes #[[ATTR1]] = { mustprogress nofree noinline nosync nounwind willreturn memory(none) uwtable }
 ; TUNIT: attributes #[[ATTR2]] = { nounwind }
-; TUNIT: attributes #[[ATTR3]] = { nofree nosync nounwind memory(argmem: readwrite) }
+; TUNIT: attributes #[[ATTR3]] = { nofree nosync nounwind }
 ; TUNIT: attributes #[[ATTR4]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }
 ; TUNIT: attributes #[[ATTR5]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) }
 ; TUNIT: attributes #[[ATTR6]] = { nounwind willreturn }
@@ -1231,15 +1231,14 @@ attributes #2 = { null_pointer_is_valid }
 ; TUNIT: attributes #[[ATTR10]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) }
 ; TUNIT: attributes #[[ATTR11]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(read) }
 ; TUNIT: attributes #[[ATTR12]] = { mustprogress norecurse nounwind willreturn memory(argmem: readwrite) }
-; TUNIT: attributes #[[ATTR13]] = { nofree nosync nounwind }
-; TUNIT: attributes #[[ATTR14]] = { nofree nosync nounwind willreturn memory(read) }
-; TUNIT: attributes #[[ATTR15]] = { nofree norecurse nosync nounwind willreturn }
+; TUNIT: attributes #[[ATTR13]] = { nofree nosync nounwind willreturn memory(read) }
+; TUNIT: attributes #[[ATTR14]] = { nofree norecurse nosync nounwind willreturn }
 ;.
 ; CGSCC: attributes #[[ATTR0]] = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(none) uwtable }
 ; CGSCC: attributes #[[ATTR1]] = { mustprogress nofree noinline nosync nounwind willreturn memory(none) uwtable }
 ; CGSCC: attributes #[[ATTR2]] = { noinline nounwind uwtable }
 ; CGSCC: attributes #[[ATTR3]] = { nounwind }
-; CGSCC: attributes #[[ATTR4]] = { nofree nosync nounwind memory(argmem: readwrite) }
+; CGSCC: attributes #[[ATTR4]] = { nofree nosync nounwind }
 ; CGSCC: attributes #[[ATTR5]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }
 ; CGSCC: attributes #[[ATTR6]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) }
 ; CGSCC: attributes #[[ATTR7]] = { nounwind willreturn }
@@ -1251,8 +1250,7 @@ attributes #2 = { null_pointer_is_valid }
 ; CGSCC: attributes #[[ATTR13]] = { mustprogress nofree nosync nounwind willreturn memory(none) }
 ; CGSCC: attributes #[[ATTR14]] = { mustprogress norecurse nounwind willreturn memory(argmem: readwrite) }
 ; CGSCC: attributes #[[ATTR15]] = { nofree nosync willreturn }
-; CGSCC: attributes #[[ATTR16]] = { nofree nosync nounwind }
-; CGSCC: attributes #[[ATTR17]] = { nofree willreturn memory(read) }
+; CGSCC: attributes #[[ATTR16]] = { nofree willreturn memory(read) }
 ;.
 ; TUNIT: [[META0]] = !{}
 ;.
diff --git a/llvm/test/Transforms/Attributor/memory_locations.ll b/llvm/test/Transforms/Attributor/memory_locations.ll
index 936b8a0b34b15..61b74d38e5acb 100644
--- a/llvm/test/Transforms/Attributor/memory_locations.ll
+++ b/llvm/test/Transforms/Attributor/memory_locations.ll
@@ -35,7 +35,7 @@ define dso_local ptr @internal_only_rec(i32 %arg) {
 ; CHECK-NEXT:    br i1 [[CMP]], label [[IF_THEN:%.*]], label [[IF_END:%.*]]
 ; CHECK:       if.then:
 ; CHECK-NEXT:    [[DIV:%.*]] = sdiv i32 [[ARG]], 2
-; CHECK-NEXT:    [[CALL:%.*]] = call ptr @internal_only_rec(i32 [[DIV]])
+; CHECK-NEXT:    [[CALL:%.*]] = call noalias ptr @internal_only_rec(i32 [[DIV]])
 ; CHECK-NEXT:    br label [[RETURN:%.*]]
 ; CHECK:       if.end:
 ; CHECK-NEXT:    [[CONV:%.*]] = sext i32 [[ARG]] to i64
diff --git a/llvm/test/Transforms/Attributor/recursive-call-returned.ll b/llvm/test/Transforms/Attributor/recursive-call-returned.ll
new file mode 100644
index 0000000000000..f896996f7d19a
--- /dev/null
+++ b/llvm/test/Transforms/Attributor/recursive-call-returned.ll
@@ -0,0 +1,119 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals
+; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -S < %s | FileCheck %s --check-prefixes=CHECK,TUNIT
+; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC
+
+; The value a recursive call returns must not be replaced by an instruction of
+; the caller: the callee returns its own instance of that instruction.
+
+define internal i64 @walk(i64 %d, i64 %a) {
+; CHECK: Function Attrs: nofree nosync nounwind memory(none)
+; CHECK-LABEL: define {{[^@]+}}@walk
+; CHECK-SAME: (i64 [[D:%.*]], i64 [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[ISZERO:%.*]] = icmp eq i64 [[D]], 0
+; CHECK-NEXT:    br i1 [[ISZERO]], label [[RETURN:%.*]], label [[RECURSE:%.*]]
+; CHECK:       recurse:
+; CHECK-NEXT:    [[D1:%.*]] = add i64 [[D]], -1
+; CHECK-NEXT:    [[A1:%.*]] = add i64 [[A]], 1
+; CHECK-NEXT:    [[R:%.*]] = call i64 @walk(i64 [[D1]], i64 [[A1]]) #[[ATTR0]]
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       return:
+; CHECK-NEXT:    [[RESULT:%.*]] = phi i64 [ [[R]], [[RECURSE]] ], [ [[A]], [[ENTRY:%.*]] ]
+; CHECK-NEXT:    ret i64 [[RESULT]]
+;
+entry:
+  %iszero = icmp eq i64 %d, 0
+  br i1 %iszero, label %return, label %recurse
+
+recurse:
+  %d1 = add i64 %d, -1
+  %a1 = add i64 %a, 1
+  %r = call i64 @walk(i64 %d1, i64 %a1)
+  br label %return
+
+return:
+  %result = phi i64 [ %r, %recurse ], [ %a, %entry ]
+  ret i64 %result
+}
+
+define i64 @walk_caller(i64 %d, i64 %a) {
+; TUNIT: Function Attrs: nofree norecurse nosync nounwind memory(none)
+; TUNIT-LABEL: define {{[^@]+}}@walk_caller
+; TUNIT-SAME: (i64 [[D:%.*]], i64 [[A:%.*]]) #[[ATTR1:[0-9]+]] {
+; TUNIT-NEXT:    [[R:%.*]] = call i64 @walk(i64 [[D]], i64 [[A]]) #[[ATTR0]]
+; TUNIT-NEXT:    ret i64 [[R]]
+;
+; CGSCC: Function Attrs: nofree nosync nounwind memory(none)
+; CGSCC-LABEL: define {{[^@]+}}@walk_caller
+; CGSCC-SAME: (i64 [[D:%.*]], i64 [[A:%.*]]) #[[ATTR0]] {
+; CGSCC-NEXT:    [[R:%.*]] = call i64 @walk(i64 [[D]], i64 [[A]]) #[[ATTR0]]
+; CGSCC-NEXT:    ret i64 [[R]]
+;
+  %r = call i64 @walk(i64 %d, i64 %a)
+  ret i64 %r
+}
+
+define internal i64 @ping(i64 %d, i64 %a) {
+; CHECK: Function Attrs: nofree nosync nounwind memory(none)
+; CHECK-LABEL: define {{[^@]+}}@ping
+; CHECK-SAME: (i64 [[D:%.*]], i64 [[A:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[ISZERO:%.*]] = icmp eq i64 [[D]], 0
+; CHECK-NEXT:    br i1 [[ISZERO]], label [[RETURN:%.*]], label [[RECURSE:%.*]]
+; CHECK:       recurse:
+; CHECK-NEXT:    [[D1:%.*]] = add i64 [[D]], -1
+; CHECK-NEXT:    [[A1:%.*]] = add i64 [[A]], 1
+; CHECK-NEXT:    [[R:%.*]] = call i64 @pong(i64 [[D1]], i64 [[A1]]) #[[ATTR0]]
+; CHECK-NEXT:    br label [[RETURN]]
+; CHECK:       return:
+; CHECK-NEXT:    [[RESULT:%.*]] = phi i64 [ [[R]], [[RECURSE]] ], [ [[A]], [[ENTRY:%.*]] ]
+; CHECK-NEXT:    ret i64 [[RESULT]]
+;
+entry:
+  %iszero = icmp eq i64 %d, 0
+  br i1 %iszero, label %return, label %recurse
+
+recurse:
+  %d1 = add i64 %d, -1
+  %a1 = add i64 %a, 1
+  %r = call i64 @pong(i64 %d1, i64 %a1)
+  br label %return
+
+return:
+  %result = phi i64 [ %r, %recurse ], [ %a, %entry ]
+  ret i64 %result
+}
+
+define internal i64 @pong(i64 %d, i64 %a) {
+; CHECK: Function Attrs: nofree nosync nounwind memory(none)
+; CHECK-LABEL: define {{[^@]+}}@pong
+; CHECK-SAME: (i64 [[D:%.*]], i64 [[A:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[R:%.*]] = call i64 @ping(i64 [[D]], i64 [[A]]) #[[ATTR0]]
+; CHECK-NEXT:    ret i64 [[R]]
+;
+  %r = call i64 @ping(i64 %d, i64 %a)
+  ret i64 %r
+}
+
+define i64 @ping_caller(i64 %d, i64 %a) {
+; TUNIT: Function Attrs: nofree norecurse nosync nounwind memory(none)
+; TUNIT-LABEL: define {{[^@]+}}@ping_caller
+; TUNIT-SAME: (i64 [[D:%.*]], i64 [[A:%.*]]) #[[ATTR1]] {
+; TUNIT-NEXT:    [[R:%.*]] = call i64 @ping(i64 [[D]], i64 [[A]]) #[[ATTR0]]
+; TUNIT-NEXT:    ret i64 [[R]]
+;
+; CGSCC: Function Attrs: nofree nosync nounwind memory(none)
+; CGSCC-LABEL: define {{[^@]+}}@ping_caller
+; CGSCC-SAME: (i64 [[D:%.*]], i64 [[A:%.*]]) #[[ATTR0]] {
+; CGSCC-NEXT:    [[R:%.*]] = call i64 @ping(i64 [[D]], i64 [[A]]) #[[ATTR0]]
+; CGSCC-NEXT:    ret i64 [[R]]
+;
+  %r = call i64 @ping(i64 %d, i64 %a)
+  ret i64 %r
+}
+;.
+; TUNIT: attributes #[[ATTR0]] = { nofree nosync nounwind memory(none) }
+; TUNIT: attributes #[[ATTR1]] = { nofree norecurse nosync nounwind memory(none) }
+;.
+; CGSCC: attributes #[[ATTR0]] = { nofree nosync nounwind memory(none) }
+;.
diff --git a/llvm/test/Transforms/Attributor/returned.ll b/llvm/test/Transforms/Attributor/returned.ll
index 32a8a16b75883..73b6da99f7c68 100644
--- a/llvm/test/Transforms/Attributor/returned.ll
+++ b/llvm/test/Transforms/Attributor/returned.ll
@@ -1461,7 +1461,7 @@ define internal i32 @recBad(i1 %c, i32 %arg) {
 ; TUNIT-NEXT:    br i1 [[C]], label [[T:%.*]], label [[F:%.*]]
 ; TUNIT:       t:
 ; TUNIT-NEXT:    [[R:%.*]] = call i32 @recBad(i1 noundef false, i32 [[ADD]]) #[[ATTR8]]
-; TUNIT-NEXT:    ret i32 [[ADD]]
+; TUNIT-NEXT:    ret i32 [[R]]
 ; TUNIT:       f:
 ; TUNIT-NEXT:    ret i32 [[ARG]]
 ;
@@ -1472,7 +1472,7 @@ define internal i32 @recBad(i1 %c, i32 %arg) {
 ; CGSCC-NEXT:    br i1 [[C]], label [[T:%.*]], label [[F:%.*]]
 ; CGSCC:       t:
 ; CGSCC-NEXT:    [[R:%.*]] = call i32 @recBad(i1 noundef false, i32 [[ADD]]) #[[ATTR7]]
-; CGSCC-NEXT:    ret i32 [[ADD]]
+; CGSCC-NEXT:    ret i32 [[R]]
 ; CGSCC:       f:
 ; CGSCC-NEXT:    ret i32 [[ARG]]
 ;



More information about the llvm-commits mailing list