Skip to content

Task Executors: Prepare for new TaskExecutor protocol #69568

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 99 additions & 14 deletions include/swift/ABI/Executor.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ class AsyncTask;
class DefaultActor;
class Job;
class SerialExecutorWitnessTable;
class TaskExecutorWitnessTable;

/// An unmanaged reference to an executor.
/// An unmanaged reference to a serial executor.
///
/// This type corresponds to the type Optional<Builtin.Executor> in
/// Swift. The representation of nil in Optional<Builtin.Executor>
Expand All @@ -53,7 +54,7 @@ class SerialExecutorWitnessTable;
/// bits off before accessing the witness table, so setting them
/// in the future should back-deploy as long as the witness table
/// reference is still present.
class ExecutorRef {
class SerialExecutorRef {
HeapObject *Identity; // Not necessarily Swift reference-countable
uintptr_t Implementation;

Expand All @@ -79,7 +80,7 @@ class ExecutorRef {

static_assert(static_cast<uintptr_t>(ExecutorKind::Ordinary) == 0);

constexpr ExecutorRef(HeapObject *identity, uintptr_t implementation)
constexpr SerialExecutorRef(HeapObject *identity, uintptr_t implementation)
: Identity(identity), Implementation(implementation) {}

public:
Expand All @@ -88,35 +89,35 @@ class ExecutorRef {
/// environment, it's presumed to be okay to switch synchronously
/// to an actor. As an executor request, this represents a request
/// to drop whatever the current actor is.
constexpr static ExecutorRef generic() {
return ExecutorRef(nullptr, 0);
constexpr static SerialExecutorRef generic() {
return SerialExecutorRef(nullptr, 0);
}

/// Given a pointer to a default actor, return an executor reference
/// for it.
static ExecutorRef forDefaultActor(DefaultActor *actor) {
static SerialExecutorRef forDefaultActor(DefaultActor *actor) {
assert(actor);
return ExecutorRef(actor, 0);
return SerialExecutorRef(actor, 0);
}

/// Given a pointer to a serial executor and its SerialExecutor
/// conformance, return an executor reference for it.
static ExecutorRef forOrdinary(HeapObject *identity,
static SerialExecutorRef forOrdinary(HeapObject *identity,
const SerialExecutorWitnessTable *witnessTable) {
assert(identity);
assert(witnessTable);
auto wtable = reinterpret_cast<uintptr_t>(witnessTable) |
static_cast<uintptr_t>(ExecutorKind::Ordinary);
return ExecutorRef(identity, wtable);
return SerialExecutorRef(identity, wtable);
}

static ExecutorRef forComplexEquality(HeapObject *identity,
static SerialExecutorRef forComplexEquality(HeapObject *identity,
const SerialExecutorWitnessTable *witnessTable) {
assert(identity);
assert(witnessTable);
auto wtable = reinterpret_cast<uintptr_t>(witnessTable) |
static_cast<uintptr_t>(ExecutorKind::ComplexEquality);
return ExecutorRef(identity, wtable);
return SerialExecutorRef(identity, wtable);
}

HeapObject *getIdentity() const {
Expand Down Expand Up @@ -162,7 +163,7 @@ class ExecutorRef {

/// Do we have to do any work to start running as the requested
/// executor?
bool mustSwitchToRun(ExecutorRef newExecutor) const {
bool mustSwitchToRun(SerialExecutorRef newExecutor) const {
return Identity != newExecutor.Identity;
}

Expand All @@ -174,10 +175,94 @@ class ExecutorRef {
return Implementation & WitnessTableMask;
}

bool operator==(ExecutorRef other) const {
bool operator==(SerialExecutorRef other) const {
return Identity == other.Identity;
}
bool operator!=(ExecutorRef other) const {
bool operator!=(SerialExecutorRef other) const {
return !(*this == other);
}
};

class TaskExecutorRef {
HeapObject *Identity; // Not necessarily Swift reference-countable
uintptr_t Implementation;

// We future-proof the ABI here by masking the low bits off the
// implementation pointer before using it as a witness table.
//
// We have 3 bits for future use remaining here.
enum: uintptr_t {
WitnessTableMask = ~uintptr_t(alignof(void*) - 1)
};

/// The kind is stored in the free bits in the `Implementation` witness table reference.
enum class TaskExecutorKind : uintptr_t {
/// Ordinary executor.
Ordinary = 0b00,
};

static_assert(static_cast<uintptr_t>(TaskExecutorKind::Ordinary) == 0);

constexpr TaskExecutorRef(HeapObject *identity, uintptr_t implementation)
: Identity(identity), Implementation(implementation) {}

public:

constexpr static TaskExecutorRef undefined() {
return TaskExecutorRef(nullptr, 0);
}

/// Given a pointer to a serial executor and its TaskExecutor
/// conformance, return an executor reference for it.
static TaskExecutorRef forOrdinary(HeapObject *identity,
const SerialExecutorWitnessTable *witnessTable) {
assert(identity);
assert(witnessTable);
auto wtable = reinterpret_cast<uintptr_t>(witnessTable) |
static_cast<uintptr_t>(TaskExecutorKind::Ordinary);
return TaskExecutorRef(identity, wtable);
}

HeapObject *getIdentity() const {
return Identity;
}

/// Is this the generic executor reference?
bool isUndefined() const {
return Identity == 0;
}

TaskExecutorKind getExecutorKind() const {
return static_cast<TaskExecutorKind>(Implementation & ~WitnessTableMask);
}

/// Is this an ordinary executor reference?
/// These executor references are the default kind, and have no special treatment elsewhere in the system.
bool isOrdinary() const {
return getExecutorKind() == TaskExecutorKind::Ordinary;
}

const TaskExecutorWitnessTable *getTaskExecutorWitnessTable() const {
assert(!isUndefined());
auto table = Implementation & WitnessTableMask;
return reinterpret_cast<const TaskExecutorWitnessTable*>(table);
}

// /// Do we have to do any work to start running as the requested
// /// executor?
// bool mustSwitchToRun(TaskExecutorRef newExecutor) const {
// return Identity != newExecutor.Identity;
// }

/// Get the raw value of the Implementation field, for tracing.
uintptr_t getRawImplementation() const {
return Implementation & WitnessTableMask;
}

bool operator==(TaskExecutorRef other) const {
return Identity == other.Identity;
}
bool operator!=(TaskExecutorRef other) const {
return !(*this == other);
}
};
Expand Down
4 changes: 2 additions & 2 deletions include/swift/ABI/Task.h
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ class AsyncTask : public Job {
public:
/// Flag that the task is to be enqueued on the provided executor and actually
/// enqueue it
void flagAsAndEnqueueOnExecutor(ExecutorRef newExecutor);
void flagAsAndEnqueueOnExecutor(SerialExecutorRef newExecutor);

/// Flag that this task is now completed. This normally does not do anything
/// but can be used to locally insert logging.
Expand Down Expand Up @@ -790,7 +790,7 @@ class ContinuationAsyncContext : public AsyncContext {

/// The executor that should be resumed to.
/// Public ABI.
ExecutorRef ResumeToExecutor;
SerialExecutorRef ResumeToExecutor;

#if defined(SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY)
/// In a task-to-thread model, instead of voluntarily descheduling the task
Expand Down
6 changes: 3 additions & 3 deletions include/swift/ABI/TaskOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,14 @@ class TaskGroupTaskOptionRecord : public TaskOptionRecord {
/// executor should be used instead, most often this may mean the global
/// concurrent executor, or the enclosing actor's executor.
class ExecutorTaskOptionRecord : public TaskOptionRecord {
const ExecutorRef Executor;
const SerialExecutorRef Executor;

public:
ExecutorTaskOptionRecord(ExecutorRef executor)
ExecutorTaskOptionRecord(SerialExecutorRef executor)
: TaskOptionRecord(TaskOptionRecordKind::Executor),
Executor(executor) {}

ExecutorRef getExecutor() const {
SerialExecutorRef getExecutor() const {
return Executor;
}

Expand Down
6 changes: 3 additions & 3 deletions include/swift/ABI/TaskStatus.h
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ class TaskDependencyStatusRecord : public TaskStatusRecord {
// (potentially intrusively), so that the appropriate escalation effect
// (which may be different for each type of executor) can happen if a task
// is escalated while enqueued.
ExecutorRef Executor;
SerialExecutorRef Executor;
} DependentOn;

// Enum specifying the type of dependency this task has
Expand Down Expand Up @@ -359,7 +359,7 @@ class TaskDependencyStatusRecord : public TaskStatusRecord {
DependentOn.TaskGroup = taskGroup;
}

TaskDependencyStatusRecord(AsyncTask *waitingTask, ExecutorRef executor) :
TaskDependencyStatusRecord(AsyncTask *waitingTask, SerialExecutorRef executor) :
TaskStatusRecord(TaskStatusRecordKind::TaskDependency),
DependencyKind(EnqueuedOnExecutor), WaitingTask(waitingTask) {
DependentOn.Executor = executor;
Expand All @@ -369,7 +369,7 @@ class TaskDependencyStatusRecord : public TaskStatusRecord {
return record->getKind() == TaskStatusRecordKind::TaskDependency;
}

void updateDependencyToEnqueuedOn(ExecutorRef executor) {
void updateDependencyToEnqueuedOn(SerialExecutorRef executor) {
DependencyKind = EnqueuedOnExecutor;
DependentOn.Executor = executor;
}
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/ASTSynthesis.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ enum SingletonTypeSynthesizer {
_void,
_word,
_serialExecutor,
_taskExecutor,
};
inline Type synthesizeType(SynthesisContext &SC,
SingletonTypeSynthesizer kind) {
Expand All @@ -69,6 +70,9 @@ inline Type synthesizeType(SynthesisContext &SC,
case _serialExecutor:
return SC.Context.getProtocol(KnownProtocolKind::SerialExecutor)
->getDeclaredInterfaceType();
case _taskExecutor:
return SC.Context.getProtocol(KnownProtocolKind::TaskExecutor)
->getDeclaredInterfaceType();
}
}

Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/Builtins.def
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,7 @@ BUILTIN_MISC_OPERATION_WITH_SILGEN(Alignof, "alignof", "n", Special)

// getCurrentExecutor: () async -> Builtin.Executor?
//
// Retrieve the ExecutorRef on which the current asynchronous
// Retrieve the SerialExecutorRef on which the current asynchronous
// function is executing, or nil if the function isn't running
// anywhere in particular.
BUILTIN_MISC_OPERATION_WITH_SILGEN(GetCurrentExecutor, "getCurrentExecutor", "", Special)
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownProtocols.def
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ PROTOCOL(FixedWidthInteger)
PROTOCOL(RangeReplaceableCollection)
PROTOCOL(Executor)
PROTOCOL(SerialExecutor)
PROTOCOL(TaskExecutor)
PROTOCOL(GlobalActor)

PROTOCOL_(BridgedNSError)
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownSDKTypes.def
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ KNOWN_SDK_TYPE_DECL(Concurrency, Job, StructDecl, 0) // TODO: remove in favor of
KNOWN_SDK_TYPE_DECL(Concurrency, ExecutorJob, StructDecl, 0)
KNOWN_SDK_TYPE_DECL(Concurrency, UnownedJob, StructDecl, 0)
KNOWN_SDK_TYPE_DECL(Concurrency, Executor, NominalTypeDecl, 0)
KNOWN_SDK_TYPE_DECL(Concurrency, TaskExecutor, NominalTypeDecl, 0)
KNOWN_SDK_TYPE_DECL(Concurrency, SerialExecutor, NominalTypeDecl, 0)
KNOWN_SDK_TYPE_DECL(Concurrency, UnownedSerialExecutor, NominalTypeDecl, 0)

Expand Down
2 changes: 1 addition & 1 deletion include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -1693,7 +1693,7 @@ class BuiltinRawUnsafeContinuationType : public BuiltinType {
DEFINE_EMPTY_CAN_TYPE_WRAPPER(BuiltinRawUnsafeContinuationType, BuiltinType)

/// BuiltinExecutorType - The builtin executor-ref type. In C, this
/// is the ExecutorRef struct type.
/// is the SerialExecutorRef struct type.
class BuiltinExecutorType : public BuiltinType {
friend class ASTContext;
BuiltinExecutorType(const ASTContext &C)
Expand Down
1 change: 1 addition & 0 deletions include/swift/Demangling/StandardTypesMangling.def
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ STANDARD_TYPE_CONCURRENCY(Protocol, F, Executor)
STANDARD_TYPE_CONCURRENCY(Protocol, f, SerialExecutor)
STANDARD_TYPE_CONCURRENCY(Structure, G, TaskGroup)
STANDARD_TYPE_CONCURRENCY(Structure, g, ThrowingTaskGroup)
STANDARD_TYPE_CONCURRENCY(Protocol, h, TaskExecutor)
STANDARD_TYPE_CONCURRENCY(Protocol, I, AsyncIteratorProtocol)
STANDARD_TYPE_CONCURRENCY(Protocol, i, AsyncSequence)
STANDARD_TYPE_CONCURRENCY(Structure, J, UnownedJob)
Expand Down
18 changes: 9 additions & 9 deletions include/swift/Runtime/Concurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ void swift_task_localsCopyTo(AsyncTask* target);
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swiftasync)
void swift_task_switch(SWIFT_ASYNC_CONTEXT AsyncContext *resumeToContext,
TaskContinuationFunction *resumeFunction,
ExecutorRef newExecutor);
SerialExecutorRef newExecutor);

/// Mark a task for enqueue on a new executor and then enqueue it.
///
Expand All @@ -671,7 +671,7 @@ void swift_task_switch(SWIFT_ASYNC_CONTEXT AsyncContext *resumeToContext,
/// synchronously when possible.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void
swift_task_enqueueTaskOnExecutor(AsyncTask *task, ExecutorRef executor);
swift_task_enqueueTaskOnExecutor(AsyncTask *task, SerialExecutorRef executor);

/// Enqueue the given job to run asynchronously on the given executor.
///
Expand All @@ -681,7 +681,7 @@ swift_task_enqueueTaskOnExecutor(AsyncTask *task, ExecutorRef executor);
/// Generally you should call swift_task_switch to switch execution
/// synchronously when possible.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_task_enqueue(Job *job, ExecutorRef executor);
void swift_task_enqueue(Job *job, SerialExecutorRef executor);

/// Enqueue the given job to run asynchronously on the global
/// execution pool.
Expand Down Expand Up @@ -716,7 +716,7 @@ bool swift_task_isOnExecutor(
const SerialExecutorWitnessTable *wtable);

SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_executor_isComplexEquality(ExecutorRef ref);
bool swift_executor_isComplexEquality(SerialExecutorRef ref);

/// Return the 64bit TaskID (if the job is an AsyncTask),
/// or the 32bits of the job Id otherwise.
Expand Down Expand Up @@ -886,27 +886,27 @@ void swift_task_asyncMainDrainQueue [[noreturn]]();
/// Establish that the current thread is running as the given
/// executor, then run a job.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_job_run(Job *job, ExecutorRef executor);
void swift_job_run(Job *job, SerialExecutorRef executor);

/// Return the current thread's active task reference.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
AsyncTask *swift_task_getCurrent(void);

/// Return the current thread's active executor reference.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
ExecutorRef swift_task_getCurrentExecutor(void);
SerialExecutorRef swift_task_getCurrentExecutor(void);

/// Return the main-actor executor reference.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
ExecutorRef swift_task_getMainExecutor(void);
SerialExecutorRef swift_task_getMainExecutor(void);

SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_task_isCurrentExecutor(ExecutorRef executor);
bool swift_task_isCurrentExecutor(SerialExecutorRef executor);

SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_task_reportUnexpectedExecutor(
const unsigned char *file, uintptr_t fileLength, bool fileIsASCII,
uintptr_t line, ExecutorRef executor);
uintptr_t line, SerialExecutorRef executor);

SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
JobPriority swift_task_getCurrentThreadPriority(void);
Expand Down
6 changes: 3 additions & 3 deletions include/swift/Runtime/RuntimeFunctions.def
Original file line number Diff line number Diff line change
Expand Up @@ -2310,7 +2310,7 @@ FUNCTION(TaskCreate,

// void swift_task_switch(AsyncContext *resumeContext,
// TaskContinuationFunction *resumeFunction,
// ExecutorRef newExecutor);
// SerialExecutorRef newExecutor);
FUNCTION(TaskSwitchFunc,
swift_task_switch, SwiftAsyncCC,
ConcurrencyAvailability,
Expand Down Expand Up @@ -2372,7 +2372,7 @@ FUNCTION(ContinuationThrowingResumeWithError,
EFFECT(Concurrency),
UNKNOWN_MEMEFFECTS)

// ExecutorRef swift_task_getCurrentExecutor();
// SerialExecutorRef swift_task_getCurrentExecutor();
FUNCTION(TaskGetCurrentExecutor,
swift_task_getCurrentExecutor, SwiftCC,
ConcurrencyAvailability,
Expand All @@ -2382,7 +2382,7 @@ FUNCTION(TaskGetCurrentExecutor,
EFFECT(Concurrency),
MEMEFFECTS(ArgMemOnly))

// ExecutorRef swift_task_getMainExecutor();
// SerialExecutorRef swift_task_getMainExecutor();
FUNCTION(TaskGetMainExecutor,
swift_task_getMainExecutor, SwiftCC,
ConcurrencyAvailability,
Expand Down
Loading