Skip to content

Commit 9b4f7d6

Browse files
committed
Make sure we tail call optimize a call in concurrency runtime's switch_task_impl.
Without this hack the call will leave a stack frame around (not tail call optimized) and blow the stack if we call switch_task often enough. Ideally, clang would emit this call as `musttail` but currently it does not. rdar://76652421
1 parent f69dfc2 commit 9b4f7d6

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1800,6 +1800,13 @@ static bool tryAssumeThreadForSwitch(ExecutorRef newExecutor,
18001800
return false;
18011801
}
18021802

1803+
__attribute__((noinline))
1804+
SWIFT_CC(swiftasync)
1805+
static void force_tail_call_hack(AsyncTask *task) {
1806+
// This *should* be executed as a tail call.
1807+
return task->runInFullyEstablishedContext();
1808+
}
1809+
18031810
/// Given that we've assumed control of an executor on this thread,
18041811
/// continue to run the given task on it.
18051812
SWIFT_CC(swiftasync)
@@ -1813,7 +1820,9 @@ static void runOnAssumedThread(AsyncTask *task, ExecutorRef executor,
18131820
oldTracking->setActiveExecutor(executor);
18141821

18151822
// FIXME: force tail call
1816-
return task->runInFullyEstablishedContext();
1823+
// return task->runInFullyEstablishedContext();
1824+
// This hack "ensures" that this call gets executed as a tail call.
1825+
return force_tail_call_hack(task);
18171826
}
18181827

18191828
// Otherwise, set up tracking info.

test/Interpreter/async_fib.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -Xfrontend -enable-experimental-concurrency %s -parse-as-library -module-name main -o %t/main
3+
// RUN: %target-codesign %t/main
4+
// RUN: %target-run %t/main | %FileCheck %s
5+
6+
// REQUIRES: concurrency
7+
// REQUIRES: executable_test
8+
// UNSUPPORTED: use_os_stdlib
9+
// UNSUPPORTED: back_deployment_runtime
10+
11+
var gg = 0
12+
13+
@inline(never)
14+
public func identity<T>(_ x: T) -> T {
15+
gg += 1
16+
return x
17+
}
18+
19+
actor Actor {
20+
var x: Int = 0
21+
init(x: Int) { self.x = x }
22+
23+
@inline(never)
24+
func get(_ i: Int ) async -> Int {
25+
return i + x
26+
}
27+
}
28+
29+
// Used to crash with an stack overflow with m >= 18
30+
let m = 22
31+
32+
@inline(never)
33+
func asyncFib(_ n: Int, _ a1: Actor, _ a2: Actor) async -> Int {
34+
if n == 0 {
35+
return await a1.get(n)
36+
}
37+
if n == 1 {
38+
return await a2.get(n)
39+
}
40+
41+
let first = await asyncFib(n-2, a1, a2)
42+
let second = await asyncFib(n-1, a1, a2)
43+
44+
let result = first + second
45+
46+
return result
47+
}
48+
49+
@main struct Main {
50+
static func main() async {
51+
let a1 = Actor(x: 0)
52+
let a2 = Actor(x: 0)
53+
_ = await asyncFib(identity(m), a1, a2)
54+
55+
// CHECK: result: 0
56+
await print("result: \(a1.x)");
57+
await print(a2.x)
58+
}
59+
}

0 commit comments

Comments
 (0)