Skip to content

Commit 5e36e45

Browse files
authored
[6.0][Concurrency] TaskExecutor ownership fixes (#74174)
1 parent bcb3b1d commit 5e36e45

28 files changed

+494
-177
lines changed

include/swift/ABI/Executor.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,6 @@ class TaskExecutorRef {
260260
return reinterpret_cast<const TaskExecutorWitnessTable*>(table);
261261
}
262262

263-
// /// Do we have to do any work to start running as the requested
264-
// /// executor?
265-
// bool mustSwitchToRun(TaskExecutorRef newExecutor) const {
266-
// return Identity != newExecutor.Identity;
267-
// }
268-
269263
/// Get the raw value of the Implementation field, for tracing.
270264
uintptr_t getRawImplementation() const {
271265
return Implementation & WitnessTableMask;

include/swift/ABI/MetadataValues.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2697,7 +2697,8 @@ enum class TaskOptionRecordKind : uint8_t {
26972697
/// Information about the result type of the task, used in embedded Swift.
26982698
ResultTypeInfo = 4,
26992699
/// Set the initial task executor preference of the task.
2700-
InitialTaskExecutor = 5,
2700+
InitialTaskExecutorUnowned = 5,
2701+
InitialTaskExecutorOwned = 6,
27012702
/// Request a child task for swift_task_run_inline.
27022703
RunInline = UINT8_MAX,
27032704
};

include/swift/ABI/Task.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,12 +419,16 @@ class AsyncTask : public Job {
419419

420420
/// Get the preferred task executor reference if there is one set for this
421421
/// task.
422-
TaskExecutorRef getPreferredTaskExecutor();
422+
TaskExecutorRef getPreferredTaskExecutor(bool assumeHasRecord = false);
423423

424424
/// WARNING: Only to be used during task creation, in other situations prefer
425425
/// to use `swift_task_pushTaskExecutorPreference` and
426426
/// `swift_task_popTaskExecutorPreference`.
427-
void pushInitialTaskExecutorPreference(TaskExecutorRef preferred);
427+
///
428+
/// The `owned` parameter indicates if the executor is owned by the task,
429+
/// and must be released when the task completes.
430+
void pushInitialTaskExecutorPreference(
431+
TaskExecutorRef preferred, bool owned);
428432

429433
/// WARNING: Only to be used during task completion (destroy).
430434
///

include/swift/ABI/TaskOptions.h

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,58 @@ class TaskGroupTaskOptionRecord : public TaskOptionRecord {
7777

7878
/// Task option to specify on what executor the task should be executed.
7979
///
80-
/// Not passing this option implies that an inferred (e.g. surrounding actor
81-
/// when we inherit execution context) or the default executor should be used.
80+
/// Not passing this option (or it's alternative "owned" version) implies that
81+
/// an inferred (e.g. surrounding actor when we inherit execution context)
82+
/// or the default executor should be used.
8283
///
8384
/// Lack of this option usually means that the global concurrent executor, or
8485
/// the executor of the enclosing actor will be used.
85-
class InitialTaskExecutorPreferenceTaskOptionRecord : public TaskOptionRecord {
86+
class InitialTaskExecutorRefPreferenceTaskOptionRecord : public TaskOptionRecord {
8687
const TaskExecutorRef Executor;
8788

8889
public:
89-
InitialTaskExecutorPreferenceTaskOptionRecord(TaskExecutorRef executor)
90-
: TaskOptionRecord(TaskOptionRecordKind::InitialTaskExecutor),
90+
InitialTaskExecutorRefPreferenceTaskOptionRecord(TaskExecutorRef executor)
91+
: TaskOptionRecord(TaskOptionRecordKind::InitialTaskExecutorUnowned),
9192
Executor(executor) {}
9293

9394
TaskExecutorRef getExecutorRef() const { return Executor; }
9495

9596
static bool classof(const TaskOptionRecord *record) {
96-
return record->getKind() == TaskOptionRecordKind::InitialTaskExecutor;
97+
return record->getKind() == TaskOptionRecordKind::InitialTaskExecutorUnowned;
98+
}
99+
};
100+
101+
/// This is quite similar to `InitialTaskExecutorRefPreferenceTaskOptionRecord`
102+
/// however it takes a "raw" TaskExecutor existential in the form of an Identity
103+
/// and WitnessTable - rather than the specific UnownedTaskExecutor which already
104+
/// may have specific "flags" set on it.
105+
///
106+
/// In order to use the executor in the runtime, we need to call into the type's
107+
/// `asUnownedTaskExecutor` which is done by
108+
/// `getExecutorRefFromUnownedTaskExecutor`.
109+
class InitialTaskExecutorOwnedPreferenceTaskOptionRecord
110+
: public TaskOptionRecord {
111+
112+
// These look similar to TaskExecutorRef but are NOT the same!
113+
// A TaskExecutorRef is obtained through calling user defined
114+
// `asUnownedTaskExecutor` which is what we need to do on these to get a real executor ref.
115+
HeapObject *Identity;
116+
const TaskExecutorWitnessTable *WitnessTable;
117+
118+
public:
119+
InitialTaskExecutorOwnedPreferenceTaskOptionRecord(
120+
HeapObject *executor, uintptr_t witnessTable)
121+
: TaskOptionRecord(TaskOptionRecordKind::InitialTaskExecutorOwned),
122+
Identity(executor) {
123+
WitnessTable = reinterpret_cast<const TaskExecutorWitnessTable*>(witnessTable);
124+
}
125+
126+
/// Invokes Swift implemented `asUnownedTaskExecutor` in order to obtain an
127+
/// `TaskExecutorRef` which is properly populated with any flags it might need.
128+
TaskExecutorRef getExecutorRefFromUnownedTaskExecutor() const;
129+
130+
static bool classof(const TaskOptionRecord *record) {
131+
return record->getKind() == TaskOptionRecordKind::InitialTaskExecutorOwned;
97132
}
98133
};
99134

include/swift/ABI/TaskStatus.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#ifndef SWIFT_ABI_TASKSTATUS_H
2121
#define SWIFT_ABI_TASKSTATUS_H
2222

23+
#include "swift/Basic/OptionSet.h"
2324
#include "swift/ABI/MetadataValues.h"
2425
#include "swift/ABI/Task.h"
2526
#include "swift/ABI/Executor.h"
@@ -285,15 +286,35 @@ class EscalationNotificationStatusRecord : public TaskStatusRecord {
285286
/// innermost preference takes priority.
286287
class TaskExecutorPreferenceStatusRecord : public TaskStatusRecord {
287288
private:
289+
enum class Flags : uint8_t {
290+
/// The executor was retained during this task's creation,
291+
/// and therefore must be released when this task completes.
292+
///
293+
/// The only tasks which need to manually retain/release the task executor
294+
/// are those which cannot structurally guarantee its lifetime. E.g. an async
295+
/// let does not need to do so, because it structurally always will end
296+
/// before/// we leave the scope in which it was defined -- and such scope
297+
/// must have been keeping alive the executor.
298+
HasRetainedExecutor = 1 << 0
299+
};
300+
OptionSet<Flags> flags;
288301
const TaskExecutorRef Preferred;
289302

290303
public:
291-
TaskExecutorPreferenceStatusRecord(TaskExecutorRef executor)
304+
TaskExecutorPreferenceStatusRecord(TaskExecutorRef executor, bool retainedExecutor)
292305
: TaskStatusRecord(TaskStatusRecordKind::TaskExecutorPreference),
293-
Preferred(executor) {}
306+
Preferred(executor) {
307+
if (retainedExecutor) {
308+
flags = Flags::HasRetainedExecutor;
309+
}
310+
}
294311

295312
TaskExecutorRef getPreferredExecutor() { return Preferred; }
296313

314+
bool hasRetainedExecutor() const {
315+
return flags.contains(Flags::HasRetainedExecutor);
316+
}
317+
297318
static bool classof(const TaskStatusRecord *record) {
298319
return record->getKind() == TaskStatusRecordKind::TaskExecutorPreference;
299320
}

include/swift/AST/ASTSynthesis.h

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,11 @@ inline Type synthesizeType(SynthesisContext &SC,
7878
return SC.Context.getProtocol(KnownProtocolKind::SerialExecutor)
7979
->getDeclaredInterfaceType();
8080
case _taskExecutor:
81-
return SC.Context.getProtocol(KnownProtocolKind::TaskExecutor)
82-
->getDeclaredInterfaceType();
81+
if (auto ty = SC.Context.getProtocol(KnownProtocolKind::TaskExecutor)) {
82+
return ty->getDeclaredInterfaceType();
83+
} else {
84+
return nullptr;
85+
}
8386
case _actor:
8487
return SC.Context.getProtocol(KnownProtocolKind::Actor)
8588
->getDeclaredInterfaceType();
@@ -175,6 +178,28 @@ Type synthesizeType(SynthesisContext &SC,
175178
return ExistentialType::get(synthesizeType(SC, M.Sub));
176179
}
177180

181+
/// A synthesizer which generates an existential type from a requirement type.
182+
template <class S, class FallbackS>
183+
struct BincompatIfTypeAvailableTypeSynthesizer {
184+
bool condition;
185+
S Sub;
186+
FallbackS FallbackSub;
187+
};
188+
template <class S, class FallbackS>
189+
constexpr BincompatIfTypeAvailableTypeSynthesizer<S, FallbackS> _bincompatType(
190+
bool bincompatCondition, S sub, FallbackS fallbackSub) {
191+
return {bincompatCondition, sub, {fallbackSub}};
192+
}
193+
template <class S, class FallbackS>
194+
Type synthesizeType(SynthesisContext &SC,
195+
const BincompatIfTypeAvailableTypeSynthesizer<S, FallbackS> &M) {
196+
if (M.condition) {
197+
return synthesizeType(SC, M.Sub);
198+
} else {
199+
return synthesizeType(SC, M.FallbackSub);
200+
}
201+
}
202+
178203
MetatypeRepresentation
179204
inline synthesizeMetatypeRepresentation(RepresentationSynthesizer rep) {
180205
switch (rep) {
@@ -332,6 +357,10 @@ constexpr SpecifiedParamSynthesizer<G> _owned(G sub) {
332357
return {ParamSpecifier::LegacyOwned, sub};
333358
}
334359
template <class G>
360+
constexpr SpecifiedParamSynthesizer<G> _consuming(G sub) {
361+
return {ParamSpecifier::Consuming, sub};
362+
}
363+
template <class G>
335364
constexpr SpecifiedParamSynthesizer<G> _inout(G sub) {
336365
return {ParamSpecifier::InOut, sub};
337366
}

lib/AST/Builtins.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,31 +1534,50 @@ Type swift::getAsyncTaskAndContextType(ASTContext &ctx) {
15341534
}
15351535

15361536
static ValueDecl *getCreateTask(ASTContext &ctx, Identifier id) {
1537+
auto taskExecutorIsAvailable =
1538+
ctx.getProtocol(swift::KnownProtocolKind::TaskExecutor) != nullptr;
1539+
15371540
return getBuiltinFunction(
15381541
ctx, id, _thin, _generics(_unrestricted, _conformsToDefaults(0)),
15391542
_parameters(
15401543
_label("flags", _swiftInt),
15411544
_label("initialSerialExecutor", _defaulted(_optional(_executor), _nil)),
15421545
_label("taskGroup", _defaulted(_optional(_rawPointer), _nil)),
15431546
_label("initialTaskExecutor", _defaulted(_optional(_executor), _nil)),
1544-
_label("operation", _function(_async(_throws(_sendable(_thick))),
1547+
_label("initialTaskExecutorConsuming",
1548+
_defaulted(_consuming(_optional(_bincompatType(
1549+
/*if*/ taskExecutorIsAvailable,
1550+
_existential(_taskExecutor),
1551+
/*else*/ _executor))),
1552+
_nil)),
1553+
_label("operation", _function(_async(_throws(_sendable(_thick))),
15451554
_typeparam(0), _parameters()))),
15461555
_tuple(_nativeObject, _rawPointer));
15471556
}
15481557

15491558
static ValueDecl *getCreateDiscardingTask(ASTContext &ctx, Identifier id) {
1559+
auto taskExecutorIsAvailable =
1560+
ctx.getProtocol(swift::KnownProtocolKind::TaskExecutor) != nullptr;
1561+
15501562
return getBuiltinFunction(
15511563
ctx, id, _thin,
15521564
_parameters(
15531565
_label("flags", _swiftInt),
15541566
_label("initialSerialExecutor", _defaulted(_optional(_executor), _nil)),
15551567
_label("taskGroup", _defaulted(_optional(_rawPointer), _nil)),
15561568
_label("initialTaskExecutor", _defaulted(_optional(_executor), _nil)),
1569+
_label("initialTaskExecutorConsuming",
1570+
_defaulted(_consuming(_optional(_bincompatType(
1571+
/*if*/ taskExecutorIsAvailable,
1572+
_existential(_taskExecutor),
1573+
/*else*/ _executor))),
1574+
_nil)),
15571575
_label("operation", _function(_async(_throws(_sendable(_thick))),
15581576
_void, _parameters()))),
15591577
_tuple(_nativeObject, _rawPointer));
15601578
}
15611579

1580+
// Legacy entry point, prefer `createAsyncTask`
15621581
static ValueDecl *getCreateAsyncTask(ASTContext &ctx, Identifier id,
15631582
bool inGroup, bool withTaskExecutor,
15641583
bool isDiscarding) {
@@ -1568,6 +1587,7 @@ static ValueDecl *getCreateAsyncTask(ASTContext &ctx, Identifier id,
15681587
if (inGroup) {
15691588
builder.addParameter(makeConcrete(ctx.TheRawPointerType)); // group
15701589
}
1590+
15711591
if (withTaskExecutor) {
15721592
builder.addParameter(makeConcrete(ctx.TheExecutorType)); // executor
15731593
}
@@ -3003,7 +3023,7 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
30033023

30043024
case BuiltinValueKind::FixLifetime:
30053025
return getFixLifetimeOperation(Context, Id);
3006-
3026+
30073027
case BuiltinValueKind::CanBeObjCClass:
30083028
return getCanBeObjCClassOperation(Context, Id);
30093029

lib/IRGen/GenConcurrency.cpp

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ static llvm::Value *addOptionRecord(IRGenFunction &IGF,
472472
}
473473

474474
/// Add a task option record to the options list if the given value
475-
/// is presernt.
475+
/// is present.
476476
template <class RecordTraits>
477477
static llvm::Value *maybeAddOptionRecord(IRGenFunction &IGF,
478478
llvm::Value *curRecordPointer,
@@ -645,15 +645,15 @@ struct TaskGroupRecordTraits {
645645
}
646646
};
647647

648-
struct InitialTaskExecutorRecordTraits {
648+
struct InitialTaskExecutorUnownedRecordTraits {
649649
static StringRef getLabel() {
650-
return "task_executor";
650+
return "task_executor_unowned";
651651
}
652652
static llvm::StructType *getRecordType(IRGenModule &IGM) {
653-
return IGM.SwiftInitialTaskExecutorPreferenceTaskOptionRecordTy;
653+
return IGM.SwiftInitialTaskExecutorUnownedPreferenceTaskOptionRecordTy;
654654
}
655655
static TaskOptionRecordFlags getRecordFlags() {
656-
return TaskOptionRecordFlags(TaskOptionRecordKind::InitialTaskExecutor);
656+
return TaskOptionRecordFlags(TaskOptionRecordKind::InitialTaskExecutorUnowned);
657657
}
658658
static CanType getValueType(ASTContext &ctx) {
659659
return ctx.TheExecutorType;
@@ -670,6 +670,36 @@ struct InitialTaskExecutorRecordTraits {
670670
}
671671
};
672672

673+
struct InitialTaskExecutorOwnedRecordTraits {
674+
static StringRef getLabel() {
675+
return "task_executor_owned";
676+
}
677+
static llvm::StructType *getRecordType(IRGenModule &IGM) {
678+
return IGM.SwiftInitialTaskExecutorOwnedPreferenceTaskOptionRecordTy;
679+
}
680+
static TaskOptionRecordFlags getRecordFlags() {
681+
return TaskOptionRecordFlags(TaskOptionRecordKind::InitialTaskExecutorOwned);
682+
}
683+
static CanType getValueType(ASTContext &ctx) {
684+
return OptionalType::get(ctx.getProtocol(KnownProtocolKind::TaskExecutor)
685+
->getDeclaredInterfaceType())
686+
->getCanonicalType();
687+
}
688+
689+
void initialize(IRGenFunction &IGF, Address recordAddr,
690+
Explosion &taskExecutor) const {
691+
auto executorRecord =
692+
IGF.Builder.CreateStructGEP(recordAddr, 1, 2 * IGF.IGM.getPointerSize());
693+
694+
// This relies on the fact that the HeapObject is directly followed by a
695+
// pointer to the witness table.
696+
IGF.Builder.CreateStore(taskExecutor.claimNext(),
697+
IGF.Builder.CreateStructGEP(executorRecord, 0, Size()));
698+
IGF.Builder.CreateStore(taskExecutor.claimNext(),
699+
IGF.Builder.CreateStructGEP(executorRecord, 1, Size()));
700+
}
701+
};
702+
673703
} // end anonymous namespace
674704

675705
static llvm::Value *
@@ -693,15 +723,25 @@ maybeAddInitialTaskExecutorOptionRecord(IRGenFunction &IGF,
693723
llvm::Value *prevOptions,
694724
OptionalExplosion &taskExecutor) {
695725
return maybeAddOptionRecord(IGF, prevOptions,
696-
InitialTaskExecutorRecordTraits(),
726+
InitialTaskExecutorUnownedRecordTraits(),
697727
taskExecutor);
698728
}
699729

730+
static llvm::Value *
731+
maybeAddInitialTaskExecutorOwnedOptionRecord(IRGenFunction &IGF,
732+
llvm::Value *prevOptions,
733+
OptionalExplosion &taskExecutorExistential) {
734+
return maybeAddOptionRecord(IGF, prevOptions,
735+
InitialTaskExecutorOwnedRecordTraits(),
736+
taskExecutorExistential);
737+
}
738+
700739
std::pair<llvm::Value *, llvm::Value *>
701740
irgen::emitTaskCreate(IRGenFunction &IGF, llvm::Value *flags,
702741
OptionalExplosion &serialExecutor,
703742
OptionalExplosion &taskGroup,
704-
OptionalExplosion &taskExecutor,
743+
OptionalExplosion &taskExecutorUnowned,
744+
OptionalExplosion &taskExecutorExistential,
705745
Explosion &taskFunction,
706746
SubstitutionMap subs) {
707747
llvm::Value *taskOptions =
@@ -729,8 +769,14 @@ irgen::emitTaskCreate(IRGenFunction &IGF, llvm::Value *flags,
729769
taskOptions = maybeAddTaskGroupOptionRecord(IGF, taskOptions, taskGroup);
730770

731771
// Add an option record for the initial task executor, if present.
732-
taskOptions =
733-
maybeAddInitialTaskExecutorOptionRecord(IGF, taskOptions, taskExecutor);
772+
{
773+
// Deprecated: This is the UnownedTaskExecutor? which is NOT consuming
774+
taskOptions = maybeAddInitialTaskExecutorOptionRecord(
775+
IGF, taskOptions, taskExecutorUnowned);
776+
// Take an (any TaskExecutor)? which we retain until task has completed
777+
taskOptions = maybeAddInitialTaskExecutorOwnedOptionRecord(
778+
IGF, taskOptions, taskExecutorExistential);
779+
}
734780

735781
// In embedded Swift, create and pass result type info.
736782
taskOptions = maybeAddEmbeddedSwiftResultTypeInfo(IGF, taskOptions, resultType);

lib/IRGen/GenConcurrency.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ std::pair<llvm::Value *, llvm::Value *>
104104
emitTaskCreate(IRGenFunction &IGF, llvm::Value *flags,
105105
OptionalExplosion &initialExecutor,
106106
OptionalExplosion &taskGroup,
107-
OptionalExplosion &taskExecutor,
107+
OptionalExplosion &taskExecutorUnowned,
108+
OptionalExplosion &taskExecutorExistential,
108109
Explosion &taskFunction,
109110
SubstitutionMap subs);
110111

0 commit comments

Comments
 (0)