Skip to content

Commit 7e9013d

Browse files
authored
Merge pull request #69090 from kubamracek/embedded-actors-and-async-let
[embedded] Add support for actors and async let into the embedded Concurrency runtime
2 parents 71e64bb + 90e1d20 commit 7e9013d

File tree

12 files changed

+232
-43
lines changed

12 files changed

+232
-43
lines changed

lib/IRGen/GenCall.cpp

Lines changed: 45 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4484,44 +4484,9 @@ void irgen::emitTaskCancel(IRGenFunction &IGF, llvm::Value *task) {
44844484
call->setCallingConv(IGF.IGM.SwiftCC);
44854485
}
44864486

4487-
llvm::Value *irgen::emitTaskCreate(IRGenFunction &IGF, llvm::Value *flags,
4488-
llvm::Value *taskGroup,
4489-
llvm::Value *futureResultType,
4490-
llvm::Value *taskFunction,
4491-
llvm::Value *localContextInfo,
4492-
SubstitutionMap subs) {
4493-
// Start with empty task options.
4494-
llvm::Value *taskOptions =
4495-
llvm::ConstantInt::get(IGF.IGM.SwiftTaskOptionRecordPtrTy, 0);
4496-
4497-
// If there is a task group, emit a task group option structure to contain it.
4498-
if (taskGroup) {
4499-
TaskOptionRecordFlags optionsFlags(TaskOptionRecordKind::TaskGroup);
4500-
llvm::Value *optionsFlagsVal = llvm::ConstantInt::get(
4501-
IGF.IGM.SizeTy, optionsFlags.getOpaqueValue());
4502-
4503-
auto optionsRecord = IGF.createAlloca(
4504-
IGF.IGM.SwiftTaskGroupTaskOptionRecordTy, Alignment(),
4505-
"task_group_options");
4506-
auto optionsBaseRecord = IGF.Builder.CreateStructGEP(
4507-
optionsRecord, 0, Size());
4508-
4509-
// Flags
4510-
IGF.Builder.CreateStore(
4511-
optionsFlagsVal,
4512-
IGF.Builder.CreateStructGEP(optionsBaseRecord, 0, Size()));
4513-
// Parent
4514-
IGF.Builder.CreateStore(
4515-
taskOptions, IGF.Builder.CreateStructGEP(optionsBaseRecord, 1, Size()));
4516-
// TaskGroup
4517-
IGF.Builder.CreateStore(
4518-
taskGroup, IGF.Builder.CreateStructGEP(optionsRecord, 1, Size()));
4519-
4520-
taskOptions = IGF.Builder.CreateBitOrPointerCast(
4521-
optionsRecord.getAddress(), IGF.IGM.SwiftTaskOptionRecordPtrTy);
4522-
}
4523-
4524-
// In embedded Swift, create and pass result type info.
4487+
llvm::Value *irgen::addEmbeddedSwiftResultTypeInfo(IRGenFunction &IGF,
4488+
llvm::Value *taskOptions,
4489+
SubstitutionMap subs) {
45254490
if (IGF.IGM.Context.LangOpts.hasFeature(Feature::Embedded)) {
45264491
auto optionsRecord =
45274492
IGF.createAlloca(IGF.IGM.SwiftResultTypeInfoTaskOptionRecordTy,
@@ -4575,6 +4540,48 @@ llvm::Value *irgen::emitTaskCreate(IRGenFunction &IGF, llvm::Value *flags,
45754540
taskOptions = IGF.Builder.CreateBitOrPointerCast(
45764541
optionsRecord.getAddress(), IGF.IGM.SwiftTaskOptionRecordPtrTy);
45774542
}
4543+
return taskOptions;
4544+
}
4545+
4546+
llvm::Value *irgen::emitTaskCreate(IRGenFunction &IGF, llvm::Value *flags,
4547+
llvm::Value *taskGroup,
4548+
llvm::Value *futureResultType,
4549+
llvm::Value *taskFunction,
4550+
llvm::Value *localContextInfo,
4551+
SubstitutionMap subs) {
4552+
// Start with empty task options.
4553+
llvm::Value *taskOptions =
4554+
llvm::ConstantInt::get(IGF.IGM.SwiftTaskOptionRecordPtrTy, 0);
4555+
4556+
// If there is a task group, emit a task group option structure to contain it.
4557+
if (taskGroup) {
4558+
TaskOptionRecordFlags optionsFlags(TaskOptionRecordKind::TaskGroup);
4559+
llvm::Value *optionsFlagsVal = llvm::ConstantInt::get(
4560+
IGF.IGM.SizeTy, optionsFlags.getOpaqueValue());
4561+
4562+
auto optionsRecord = IGF.createAlloca(
4563+
IGF.IGM.SwiftTaskGroupTaskOptionRecordTy, Alignment(),
4564+
"task_group_options");
4565+
auto optionsBaseRecord = IGF.Builder.CreateStructGEP(
4566+
optionsRecord, 0, Size());
4567+
4568+
// Flags
4569+
IGF.Builder.CreateStore(
4570+
optionsFlagsVal,
4571+
IGF.Builder.CreateStructGEP(optionsBaseRecord, 0, Size()));
4572+
// Parent
4573+
IGF.Builder.CreateStore(
4574+
taskOptions, IGF.Builder.CreateStructGEP(optionsBaseRecord, 1, Size()));
4575+
// TaskGroup
4576+
IGF.Builder.CreateStore(
4577+
taskGroup, IGF.Builder.CreateStructGEP(optionsRecord, 1, Size()));
4578+
4579+
taskOptions = IGF.Builder.CreateBitOrPointerCast(
4580+
optionsRecord.getAddress(), IGF.IGM.SwiftTaskOptionRecordPtrTy);
4581+
}
4582+
4583+
// In embedded Swift, create and pass result type info.
4584+
taskOptions = addEmbeddedSwiftResultTypeInfo(IGF, taskOptions, subs);
45784585

45794586
assert(futureResultType && "no future?!");
45804587
llvm::CallInst *result = IGF.Builder.CreateCall(

lib/IRGen/GenCall.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ namespace irgen {
217217

218218
void emitTaskCancel(IRGenFunction &IGF, llvm::Value *task);
219219

220+
llvm::Value *addEmbeddedSwiftResultTypeInfo(IRGenFunction &IGF,
221+
llvm::Value *taskOptions,
222+
SubstitutionMap subs);
223+
220224
/// Emit a call to swift_task_create[_f] with the given flags, options, and
221225
/// task function.
222226
llvm::Value *emitTaskCreate(

lib/IRGen/GenConcurrency.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "BitPatternBuilder.h"
2121
#include "ExtraInhabitants.h"
22+
#include "GenCall.h"
2223
#include "GenProto.h"
2324
#include "GenType.h"
2425
#include "IRGenDebugInfo.h"
@@ -226,7 +227,13 @@ llvm::Value *irgen::emitBuiltinStartAsyncLet(IRGenFunction &IGF,
226227
assert(subs.getReplacementTypes().size() == 1 &&
227228
"startAsyncLet should have a type substitution");
228229
auto futureResultType = subs.getReplacementTypes()[0]->getCanonicalType();
229-
auto futureResultTypeMetadata = IGF.emitAbstractTypeMetadataRef(futureResultType);
230+
231+
llvm::Value *futureResultTypeMetadata =
232+
llvm::ConstantPointerNull::get(IGF.IGM.Int8PtrTy);
233+
if (!IGF.IGM.Context.LangOpts.hasFeature(Feature::Embedded)) {
234+
futureResultTypeMetadata =
235+
IGF.emitAbstractTypeMetadataRef(futureResultType);
236+
}
230237

231238
// The concurrency runtime for older Apple OSes has a bug in task formation
232239
// for `async let`s that may manifest when trying to use room in the
@@ -269,6 +276,9 @@ llvm::Value *irgen::emitBuiltinStartAsyncLet(IRGenFunction &IGF,
269276
IGF.IGM.markAsyncFunctionPointerForPadding(taskAsyncFunctionPointer);
270277
}
271278
}
279+
280+
// In embedded Swift, create and pass result type info.
281+
taskOptions = addEmbeddedSwiftResultTypeInfo(IGF, taskOptions, subs);
272282

273283
llvm::CallInst *call;
274284
if (localResultBuffer) {

stdlib/public/Concurrency/AsyncLet.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,11 @@ static void _asyncLet_finish_continuation(
393393

394394
// Destroy the error, or the result that was stored to the buffer.
395395
if (error) {
396+
#if SWIFT_CONCURRENCY_EMBEDDED
397+
swift_unreachable("untyped error used in embedded Swift");
398+
#else
396399
swift_errorRelease((SwiftError*)error);
400+
#endif
397401
} else {
398402
alet->getTask()->futureFragment()->getResultType().vw_destroy(resultBuffer);
399403
}

stdlib/public/Concurrency/CMakeLists.txt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB AND SWIFT_SHOULD_BUILD_EMBEDDED_CONCURRENC
190190
set(SWIFT_STDLIB_STABLE_ABI OFF)
191191
set(SWIFT_STDLIB_ENABLE_OBJC_INTEROP OFF)
192192
set(SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY TRUE)
193+
set(SWIFT_STDLIB_CONCURRENCY_TRACING FALSE)
193194

194195
foreach(entry ${EMBEDDED_STDLIB_TARGET_TRIPLES})
195196
string(REGEX REPLACE "[ \t]+" ";" list "${entry}")
@@ -212,12 +213,33 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB AND SWIFT_SHOULD_BUILD_EMBEDDED_CONCURRENC
212213
IS_STDLIB IS_FRAGILE
213214

214215
${SWIFT_RUNTIME_CONCURRENCY_C_SOURCES}
215-
# TODO: Only a handful of Swift Concurrency .swift sources, for now.
216-
Task.swift
216+
# TODO: Only a subset of Swift Concurrency .swift sources, for now.
217+
Actor.swift
217218
AsyncLet.swift
218-
Executor.swift
219+
CheckedContinuation.swift
219220
Errors.swift
221+
Executor.swift
222+
ExecutorAssertions.swift
223+
AsyncCompactMapSequence.swift
224+
AsyncDropFirstSequence.swift
225+
AsyncDropWhileSequence.swift
226+
AsyncFilterSequence.swift
227+
AsyncFlatMapSequence.swift
228+
AsyncIteratorProtocol.swift
229+
AsyncMapSequence.swift
230+
AsyncPrefixSequence.swift
231+
AsyncPrefixWhileSequence.swift
232+
AsyncSequence.swift
233+
AsyncThrowingCompactMapSequence.swift
234+
AsyncThrowingDropWhileSequence.swift
235+
AsyncThrowingFilterSequence.swift
236+
AsyncThrowingFlatMapSequence.swift
237+
AsyncThrowingMapSequence.swift
238+
AsyncThrowingPrefixWhileSequence.swift
239+
GlobalActor.swift
220240
PartialAsyncTask.swift
241+
Task.swift
242+
TaskCancellation.swift
221243

222244
SWIFT_COMPILE_FLAGS
223245
-Xcc -D__MACH__ -Xcc -D__APPLE__ -Xcc -ffreestanding -enable-experimental-feature Embedded

stdlib/public/Concurrency/CheckedContinuation.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal func logFailedCheck(_ message: UnsafeRawPointer)
1919
/// Implementation class that holds the `UnsafeContinuation` instance for
2020
/// a `CheckedContinuation`.
2121
@available(SwiftStdlib 5.1, *)
22+
@_unavailableInEmbedded
2223
internal final class CheckedContinuationCanary: @unchecked Sendable {
2324
// The instance state is stored in tail-allocated raw memory, so that
2425
// we can atomically check the continuation state.
@@ -119,6 +120,7 @@ internal final class CheckedContinuationCanary: @unchecked Sendable {
119120
/// you can replace one with the other in most circumstances,
120121
/// without making other changes.
121122
@available(SwiftStdlib 5.1, *)
123+
@_unavailableInEmbedded
122124
public struct CheckedContinuation<T, E: Error>: Sendable {
123125
private let canary: CheckedContinuationCanary
124126

@@ -187,6 +189,7 @@ public struct CheckedContinuation<T, E: Error>: Sendable {
187189
}
188190

189191
@available(SwiftStdlib 5.1, *)
192+
@_unavailableInEmbedded
190193
extension CheckedContinuation {
191194
/// Resume the task awaiting the continuation by having it either
192195
/// return normally or throw an error based on the state of the given
@@ -281,6 +284,7 @@ extension CheckedContinuation {
281284
@available(SwiftStdlib 5.1, *)
282285
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
283286
@inlinable
287+
@_unavailableInEmbedded
284288
public func withCheckedContinuation<T>(
285289
function: String = #function,
286290
_ body: (CheckedContinuation<T, Never>) -> Void
@@ -321,6 +325,7 @@ public func withCheckedContinuation<T>(
321325
@available(SwiftStdlib 5.1, *)
322326
@_unsafeInheritExecutor // ABI compatibility with Swift 5.1
323327
@inlinable
328+
@_unavailableInEmbedded
324329
public func withCheckedThrowingContinuation<T>(
325330
function: String = #function,
326331
_ body: (CheckedContinuation<T, Error>) -> Void

stdlib/public/Concurrency/ExecutorAssertions.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ extension SerialExecutor {
3636
/// never called. Failure to satisfy that assumption is a serious
3737
/// programming error.
3838
@available(SwiftStdlib 5.9, *)
39+
@_unavailableInEmbedded
3940
public func preconditionIsolated(
4041
_ message: @autoclosure () -> String = String(),
4142
file: StaticString = #fileID, line: UInt = #line
@@ -72,6 +73,7 @@ extension Actor {
7273
/// never called. Failure to satisfy that assumption is a serious
7374
/// programming error.
7475
@available(SwiftStdlib 5.9, *)
76+
@_unavailableInEmbedded
7577
public nonisolated func preconditionIsolated(
7678
_ message: @autoclosure () -> String = String(),
7779
file: StaticString = #fileID, line: UInt = #line
@@ -108,6 +110,7 @@ extension GlobalActor {
108110
/// never called. Failure to satisfy that assumption is a serious
109111
/// programming error.
110112
@available(SwiftStdlib 5.9, *)
113+
@_unavailableInEmbedded
111114
public static func preconditionIsolated(
112115
_ message: @autoclosure () -> String = String(),
113116
file: StaticString = #fileID, line: UInt = #line
@@ -134,6 +137,7 @@ extension SerialExecutor {
134137
/// may assume that it *always* evaluates to `true`. Failure to satisfy that
135138
/// assumption is a serious programming error.
136139
@available(SwiftStdlib 5.9, *)
140+
@_unavailableInEmbedded
137141
public func assertIsolated(
138142
_ message: @autoclosure () -> String = String(),
139143
file: StaticString = #fileID, line: UInt = #line
@@ -167,6 +171,7 @@ extension Actor {
167171
/// may assume that it *always* evaluates to `true`. Failure to satisfy that
168172
/// assumption is a serious programming error.
169173
@available(SwiftStdlib 5.9, *)
174+
@_unavailableInEmbedded
170175
public nonisolated func assertIsolated(
171176
_ message: @autoclosure () -> String = String(),
172177
file: StaticString = #fileID, line: UInt = #line
@@ -201,6 +206,7 @@ extension GlobalActor {
201206
/// may assume that it *always* evaluates to `true`. Failure to satisfy that
202207
/// assumption is a serious programming error.
203208
@available(SwiftStdlib 5.9, *)
209+
@_unavailableInEmbedded
204210
public static func assertIsolated(
205211
_ message: @autoclosure () -> String = String(),
206212
file: StaticString = #fileID, line: UInt = #line
@@ -229,6 +235,7 @@ extension Actor {
229235
/// perspective, the serial executor guarantees mutual exclusion of those two actors.
230236
@available(SwiftStdlib 5.9, *)
231237
@_unavailableFromAsync(message: "express the closure as an explicit function declared on the specified 'actor' instead")
238+
@_unavailableInEmbedded
232239
public nonisolated func assumeIsolated<T>(
233240
_ operation: (isolated Self) throws -> T,
234241
file: StaticString = #fileID, line: UInt = #line

stdlib/public/Concurrency/Task.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,11 +1236,15 @@ void swift_task_future_wait_throwingImpl(
12361236
}
12371237

12381238
case FutureFragment::Status::Error: {
1239+
#if SWIFT_CONCURRENCY_EMBEDDED
1240+
swift_unreachable("untyped error used in embedded Swift");
1241+
#else
12391242
// Run the task with an error result.
12401243
auto future = task->futureFragment();
12411244
auto error = future->getError();
12421245
swift_errorRetain(error);
12431246
return resumeFunction(callerContext, error);
1247+
#endif
12441248
}
12451249
}
12461250
}

stdlib/public/Concurrency/Task.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -840,7 +840,6 @@ extension Task where Failure == Error {
840840
// ==== Voluntary Suspension -----------------------------------------------------
841841

842842
@available(SwiftStdlib 5.1, *)
843-
@_unavailableInEmbedded
844843
extension Task where Success == Never, Failure == Never {
845844

846845
/// Suspends the current task and allows other tasks to execute.

stdlib/public/Concurrency/TaskCancellation.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ extension Task where Success == Never, Failure == Never {
9393
/// The error is always an instance of `CancellationError`.
9494
///
9595
/// - SeeAlso: `isCancelled()`
96+
@_unavailableInEmbedded
9697
public static func checkCancellation() throws {
9798
if Task<Never, Never>.isCancelled {
9899
throw _Concurrency.CancellationError()
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -target %target-cpu-apple-macos14 -enable-experimental-feature Embedded -parse-as-library %s %S/Inputs/print.swift -c -o %t/a.o
3+
// RUN: %target-clang -x c -c %S/Inputs/print.c -o %t/print.o
4+
// RUN: %target-clang %t/a.o %t/print.o -o %t/a.out %swift_obj_root/lib/swift/embedded/%target-cpu-apple-macos/libswift_Concurrency.a -dead_strip
5+
// RUN: %target-run %t/a.out | %FileCheck %s
6+
7+
// REQUIRES: swift_in_compiler
8+
// REQUIRES: optimized_stdlib
9+
// REQUIRES: VENDOR=apple
10+
// REQUIRES: OS=macosx
11+
12+
import _Concurrency
13+
14+
actor Number {
15+
var val: Int
16+
var task: Task<Void, Never>?
17+
18+
func increment() {
19+
print("did increment")
20+
val += 1
21+
}
22+
23+
func fib(n: Int) -> Int {
24+
if n < 2 {
25+
return n
26+
}
27+
return fib(n: n-1) + fib(n: n-2)
28+
}
29+
30+
init() async {
31+
val = 0
32+
33+
task = Task.detached(priority: .high) { await self.increment() }
34+
35+
// do some synchronous work
36+
let ans = fib(n: 37)
37+
guard ans == 24157817 else {
38+
fatalError("miscomputation?")
39+
}
40+
41+
// make sure task didn't modify me!
42+
guard val == 0 else {
43+
fatalError("race!")
44+
}
45+
46+
print("finished init()")
47+
}
48+
49+
init(iterations: Int) async {
50+
var iter = iterations
51+
repeat {
52+
val = iter
53+
iter -= 1
54+
} while iter > 0
55+
}
56+
}
57+
58+
@main struct Main {
59+
static func main() async {
60+
61+
// CHECK: finished init()
62+
// CHECK-NEXT: did increment
63+
64+
let n1 = await Number()
65+
await n1.task!.value
66+
67+
let n2 = await Number(iterations: 1000)
68+
let val = await n2.val
69+
guard val == 1 else {
70+
fatalError("wrong val setting")
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)