I haven't been following this pitch closely in its previous iterations, but I've been in this same area as part of the effort to improve concurrency for Embedded Swift as well as building out the platform abstraction layer for it.
My primary concern is that the mechanism proposed introduces additional indirection that will be prohibitive for Embedded Swift clients, as well as adding overhead for the non-embedded case. The most direct place this shows up is in the definition of the ExecutorFactory protocol, which is using existentials for defining custom executors:
/// An ExecutorFactory is used to create the default main and task
/// executors.
public protocol ExecutorFactory {
/// Constructs and returns the main executor, which is started implicitly
/// by the `async main` entry point and owns the "main" thread.
static var mainExecutor: any MainExecutor { get }
/// Constructs and returns the default or global executor, which is the
/// default place in which we run tasks.
static var defaultExecutor: any TaskExecutor { get }
}
The use of existentials means we will always have indirect calls whenever we're scheduling work on an executor, which is both a direct cost and an optimization barrier. Instead, this protocol could capture the two executor types in an associated type:
public protocol ExecutorFactory {
associatedtype MainExecutorType: MainExecutor
static var mainExecutor: MainExecutorType { get }
associatedtype DefaultExecutorType: TaskExecutor
static var defaultExecutor: DefaultExecutorType { get }
}
The canonical way to conform to this protocol would be something like this:
struct MyExecutorFactory: ExecutorFactory {
static var mainExecutor = MyMainExecutor()
static var defaultExecutor = MyTaskExecutor()
}
where we now have the ability to globally allocate the instances that back the main and default executor.
The proposal also notes that the existing "hook functions" will still be there for use by Embedded Swift:
As we are not proposing to remove the existing "hook function" API from Concurrency at this point, it will still be possible to implement an executor for Embedded Swift by implementing the Impl functions in C/C++.
I understand the desire to not bring these hooks into scope, but we should understand how the layers fit together before we add another potentially-incompatible one.
The existing hook mechanism is not great for Embedded Swift, or in general for static builds: you end up compiling in the default executors for the platform, then assigning some global function pointers to override them at runtime. So you pay an indirection (always) as well as the code-size cost for the default executor, and it's subject to mistakes if you assign at the wrong time.
The platform abstraction layer takes a different approach: the Swift code calls a set of pre-declared C entrypoints that aren't implemented in the Swift runtime at all. Instead, one links in an implementation of these C functions in the final binary. You get direct calls, no dead code from unused implementations, duplicate/conflicting overrides turn into link errors, and the ability to use LTO to "see through" the calls. The proposed mechanism for overriding the main and default executors can work along with this approach, by effectively spitting out @c entrypoints that call through the DefaultExecutorFactory where the DefaultExecutorFactory typealias is declared, e.g.,
@c
func _swift_task_enqueueGlobal(job: COpaquePointer) -> Void {
DefaultExecutorFactory.defaultExecutor.enqueue(ExecutorJob(UnownedJob(job)))
}
The existing executors should be able to be linked in with the concurrency runtime and go through these hooks, or else we haven't expressed the whole of the interface.
For non-static builds, the Swift standard library has load-time mechanisms for replacing functions, and we should consider using it (or Swift's own dynamic-replacement machinery) rather than assigning to global function pointers, even if it means deprecating/removing the existing concurrency hooks. The module/translation unit defining the DefaultExecutorFactory for non-static builds should hook that mechanism.
Doug