Skip to content

Commit d861a9f

Browse files
committed
[Concurrency] More work on the custom executor implementation.
Added an `-executor-factory` argument to the compiler to let you safely specify the executors you wish to use (by naming a type that returns them). Also added some tests of the new functionality. rdar://141348916
1 parent aeae636 commit d861a9f

26 files changed

+709
-183
lines changed

include/swift/AST/DiagnosticsSIL.def

+8
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,14 @@ NOTE(rbi_add_generic_parameter_sendable_conformance,none,
11331133
"consider making generic parameter %0 conform to the 'Sendable' protocol",
11341134
(Type))
11351135

1136+
// Concurrency related diagnostics
1137+
ERROR(cannot_find_executor_factory_type, none,
1138+
"the specified executor factory '%0' could not be found", (StringRef))
1139+
ERROR(executor_factory_must_conform, none,
1140+
"the executor factory '%0' does not conform to 'ExecutorFactory'", (Type))
1141+
ERROR(executor_factory_not_supported, none,
1142+
"deployment target too low for executor factory specification", ())
1143+
11361144
//===----------------------------------------------------------------------===//
11371145
// MARK: Misc Diagnostics
11381146
//===----------------------------------------------------------------------===//

include/swift/AST/FeatureAvailability.def

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ FEATURE(IsolatedDeinit, (6, 1))
8080

8181
FEATURE(ValueGenericType, (6, 2))
8282
FEATURE(InitRawStructMetadata2, (6, 2))
83+
FEATURE(CustomExecutors, (6, 2))
8384

8485
FEATURE(TaskExecutor, FUTURE)
8586
FEATURE(Differentiation, FUTURE)

include/swift/AST/KnownProtocols.def

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ PROTOCOL(Executor)
9797
PROTOCOL(SerialExecutor)
9898
PROTOCOL(TaskExecutor)
9999
PROTOCOL(GlobalActor)
100+
PROTOCOL(ExecutorFactory)
100101

101102
PROTOCOL_(BridgedNSError)
102103
PROTOCOL_(BridgedStoredNSError)

include/swift/Basic/LangOptions.h

+4
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ namespace swift {
397397
/// Specifies how strict concurrency checking will be.
398398
StrictConcurrency StrictConcurrencyLevel = StrictConcurrency::Minimal;
399399

400+
/// Specifies the name of the executor factory to use to create the
401+
/// default executors for Swift Concurrency.
402+
std::optional<std::string> ExecutorFactory;
403+
400404
/// Enable experimental concurrency model.
401405
bool EnableExperimentalConcurrency = false;
402406

include/swift/Option/Options.td

+10
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,16 @@ def strict_concurrency : Joined<["-"], "strict-concurrency=">,
985985
"concurrency model, or 'complete' ('Sendable' and other checking is "
986986
"enabled for all code in the module)">;
987987

988+
def executor_factory : JoinedOrSeparate<["-"], "executor-factory">,
989+
Flags<[FrontendOption]>,
990+
HelpText<"Specify the factory to use to create the default executors for "
991+
"Swift Concurrency. This must be a type conforming to the "
992+
"'ExecutorFactory' protocol.">,
993+
MetaVarName<"<factory-type>">;
994+
def executor_factory_EQ : Joined<["-"], "executor-factory=">,
995+
Flags<[FrontendOption]>,
996+
Alias<executor_factory>;
997+
988998
def enable_experimental_feature :
989999
Separate<["-"], "enable-experimental-feature">,
9901000
Flags<[FrontendOption, ModuleInterfaceOption]>,

lib/AST/ASTContext.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
14381438
case KnownProtocolKind::Executor:
14391439
case KnownProtocolKind::TaskExecutor:
14401440
case KnownProtocolKind::SerialExecutor:
1441+
case KnownProtocolKind::ExecutorFactory:
14411442
M = getLoadedModule(Id_Concurrency);
14421443
break;
14431444
case KnownProtocolKind::DistributedActor:

lib/ClangImporter/SortedCFDatabase.def.gyb

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ with codecs.open(CFDatabaseFile, encoding='utf-8', errors='strict') as f:
3939
continue
4040

4141
# Otherwise, check for lines like FOO(BAR)
42-
m = re.match('^\w+\((\w+)\)', line)
42+
m = re.match(r'^\w+\((\w+)\)', line)
4343
if m:
4444
lineForName[m.group(1)] = line
4545
}%

lib/Driver/ToolChains.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,10 @@ void ToolChain::addCommonFrontendArgs(const OutputInfo &OI,
377377
arguments.push_back(inputArgs.MakeArgString(globalRemapping));
378378
}
379379

380+
if (inputArgs.hasArg(options::OPT_executor_factory)) {
381+
inputArgs.AddLastArg(arguments, options::OPT_executor_factory);
382+
}
383+
380384
// Pass through the values passed to -Xfrontend.
381385
inputArgs.AddAllArgValues(arguments, options::OPT_Xfrontend);
382386

lib/Frontend/CompilerInvocation.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,12 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
12751275
Opts.enableFeature(Feature::RegionBasedIsolation);
12761276
}
12771277

1278+
// Get the executor factory name
1279+
if (const Arg *A = Args.getLastArg(OPT_executor_factory)) {
1280+
printf("Got executor-factory option\n");
1281+
Opts.ExecutorFactory = A->getValue();
1282+
}
1283+
12781284
Opts.WarnImplicitOverrides =
12791285
Args.hasArg(OPT_warn_implicit_overrides);
12801286

lib/IRGen/GenMeta.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -7016,6 +7016,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
70167016
case KnownProtocolKind::Executor:
70177017
case KnownProtocolKind::SerialExecutor:
70187018
case KnownProtocolKind::TaskExecutor:
7019+
case KnownProtocolKind::ExecutorFactory:
70197020
case KnownProtocolKind::Sendable:
70207021
case KnownProtocolKind::UnsafeSendable:
70217022
case KnownProtocolKind::RangeReplaceableCollection:

lib/SILGen/SILGen.cpp

+44
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,10 @@ FuncDecl *SILGenModule::getDeinitOnExecutor() {
458458
return lookupConcurrencyIntrinsic(getASTContext(), "_deinitOnExecutor");
459459
}
460460

461+
FuncDecl *SILGenModule::getCreateExecutors() {
462+
return lookupConcurrencyIntrinsic(getASTContext(), "_createExecutors");
463+
}
464+
461465
FuncDecl *SILGenModule::getExit() {
462466
ASTContext &C = getASTContext();
463467

@@ -507,6 +511,46 @@ FuncDecl *SILGenModule::getExit() {
507511
return exitFunction;
508512
}
509513

514+
Type SILGenModule::getExecutorFactory() {
515+
auto &ctx = getASTContext();
516+
517+
ModuleDecl *module;
518+
519+
// Parse the executor factory name
520+
StringRef qualifiedName = *ctx.LangOpts.ExecutorFactory;
521+
StringRef typeName;
522+
523+
auto parts = qualifiedName.split('.');
524+
525+
if (parts.second.empty()) {
526+
// This was an unqualified name; assume it's relative to the main module
527+
module = ctx.MainModule;
528+
typeName = qualifiedName;
529+
} else {
530+
Identifier moduleName = ctx.getIdentifier(parts.first);
531+
module = ctx.getModuleByIdentifier(moduleName);
532+
typeName = parts.second;
533+
}
534+
535+
return ctx.getNamedSwiftType(module, typeName);
536+
}
537+
538+
Type SILGenModule::getDefaultExecutorFactory() {
539+
auto &ctx = getASTContext();
540+
541+
ModuleDecl *module = ctx.getModuleByIdentifier(ctx.Id_Concurrency);
542+
if (!module)
543+
return Type();
544+
545+
return ctx.getNamedSwiftType(module, "DefaultExecutorFactory");
546+
}
547+
548+
ProtocolDecl *SILGenModule::getExecutorFactoryProtocol() {
549+
auto &ctx = getASTContext();
550+
551+
return ctx.getProtocol(KnownProtocolKind::ExecutorFactory);
552+
}
553+
510554
ProtocolConformance *SILGenModule::getNSErrorConformanceToError() {
511555
if (NSErrorConformanceToError)
512556
return *NSErrorConformanceToError;

lib/SILGen/SILGen.h

+12
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,18 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
563563
// Retrieve the _SwiftConcurrencyShims.exit intrinsic.
564564
FuncDecl *getExit();
565565

566+
/// Get the ExecutorFactory type.
567+
Type getExecutorFactory();
568+
569+
/// Get the DefaultExecutorFactory type.
570+
Type getDefaultExecutorFactory();
571+
572+
/// Get the ExecutorFactory protocol.
573+
ProtocolDecl *getExecutorFactoryProtocol();
574+
575+
/// Get the swift_createExecutors function.
576+
FuncDecl *getCreateExecutors();
577+
566578
SILFunction *getKeyPathProjectionCoroutine(bool isReadAccess,
567579
KeyPathTypeKind typeKind);
568580

lib/SILGen/SILGenFunction.cpp

+70-1
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ SILGenFunction::getOrCreateScope(const ast_scope::ASTScopeImpl *ASTScope,
486486
// Collapse BraceStmtScopes whose parent is a .*BodyScope.
487487
if (auto Parent = ASTScope->getParent().getPtrOrNull())
488488
if (Parent->getSourceRangeOfThisASTNode() ==
489-
ASTScope->getSourceRangeOfThisASTNode())
489+
ASTScope->getSourceRangeOfThisASTNode())
490490
return cache(getOrCreateScope(Parent, FnScope, InlinedAt));
491491

492492
// The calls to defer closures have cleanup source locations pointing to the
@@ -1431,6 +1431,28 @@ void SILGenFunction::emitArtificialTopLevel(Decl *mainDecl) {
14311431
}
14321432
}
14331433

1434+
static bool isCreateExecutorsFunctionAvailable(SILGenModule &SGM) {
1435+
FuncDecl *createExecutorsFuncDecl = SGM.getCreateExecutors();
1436+
if (!createExecutorsFuncDecl)
1437+
return false;
1438+
1439+
auto &ctx = createExecutorsFuncDecl->getASTContext();
1440+
1441+
if (ctx.LangOpts.hasFeature(Feature::Embedded))
1442+
return true;
1443+
1444+
if (!ctx.LangOpts.DisableAvailabilityChecking) {
1445+
auto deploymentAvailability = AvailabilityRange::forDeploymentTarget(ctx);
1446+
auto runtimeAvailability = AvailabilityRange::forRuntimeTarget(ctx);
1447+
auto declAvailability = ctx.getCustomExecutorsAvailability();
1448+
auto declRtAvailability = ctx.getCustomExecutorsRuntimeAvailability();
1449+
return deploymentAvailability.isContainedIn(declAvailability)
1450+
&& runtimeAvailability.isContainedIn(declRtAvailability);
1451+
}
1452+
1453+
return true;
1454+
}
1455+
14341456
void SILGenFunction::emitAsyncMainThreadStart(SILDeclRef entryPoint) {
14351457
auto moduleLoc = entryPoint.getAsRegularLocation();
14361458
auto *entryBlock = B.getInsertionBB();
@@ -1446,6 +1468,53 @@ void SILGenFunction::emitAsyncMainThreadStart(SILDeclRef entryPoint) {
14461468

14471469
B.setInsertionPoint(entryBlock);
14481470

1471+
// If we're using a new enough deployment target, call swift_createExecutors()
1472+
if (ctx.LangOpts.ExecutorFactory) {
1473+
printf("Executor factory is %s\n", ctx.LangOpts.ExecutorFactory->c_str());
1474+
1475+
if (!isCreateExecutorsFunctionAvailable(SGM)) {
1476+
ctx.Diags.diagnose(SourceLoc(), diag::executor_factory_not_supported);
1477+
} else {
1478+
CanType factoryTy = SGM.getExecutorFactory()->getCanonicalType();
1479+
1480+
if (!factoryTy) {
1481+
ctx.Diags.diagnose(SourceLoc(), diag::cannot_find_executor_factory_type,
1482+
*ctx.LangOpts.ExecutorFactory);
1483+
}
1484+
1485+
ProtocolDecl *executorFactoryProtocol = SGM.getExecutorFactoryProtocol();
1486+
auto conformance = lookupConformance(factoryTy, executorFactoryProtocol);
1487+
1488+
if (conformance.isInvalid()) {
1489+
// If this type doesn't conform, ignore it and use the default factory
1490+
SourceLoc loc = extractNearestSourceLoc(factoryTy);
1491+
1492+
ctx.Diags.diagnose(loc, diag::executor_factory_must_conform, factoryTy);
1493+
1494+
factoryTy = SGM.getDefaultExecutorFactory()->getCanonicalType();
1495+
conformance = lookupConformance(factoryTy, executorFactoryProtocol);
1496+
1497+
assert(!conformance.isInvalid());
1498+
}
1499+
1500+
FuncDecl *createExecutorsFuncDecl = SGM.getCreateExecutors();
1501+
assert(createExecutorsFuncDecl
1502+
&& "Failed to find swift_createExecutors function decl");
1503+
SILFunction *createExecutorsSILFunc =
1504+
SGM.getFunction(SILDeclRef(createExecutorsFuncDecl, SILDeclRef::Kind::Func),
1505+
NotForDefinition);
1506+
SILValue createExecutorsFunc =
1507+
B.createFunctionRefFor(moduleLoc, createExecutorsSILFunc);
1508+
MetatypeType *factoryThickMetaTy
1509+
= MetatypeType::get(factoryTy, MetatypeRepresentation::Thick);
1510+
SILValue factorySILMetaTy
1511+
= B.createMetatype(moduleLoc, getLoweredType(factoryThickMetaTy));
1512+
auto ceSubs = SubstitutionMap::getProtocolSubstitutions(
1513+
conformance.getRequirement(), factoryTy, conformance);
1514+
B.createApply(moduleLoc, createExecutorsFunc, ceSubs, { factorySILMetaTy });
1515+
}
1516+
}
1517+
14491518
auto wrapCallArgs = [this, &moduleLoc](SILValue originalValue, FuncDecl *fd,
14501519
uint32_t paramIndex) -> SILValue {
14511520
Type parameterType = fd->getParameters()->get(paramIndex)->getTypeInContext();

stdlib/public/Concurrency/DispatchExecutor.swift

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import Swift
2727
public class DispatchMainExecutor: RunLoopExecutor, @unchecked Sendable {
2828
var threaded = false
2929

30+
public init() {}
31+
3032
public func run() throws {
3133
if threaded {
3234
fatalError("DispatchMainExecutor does not support recursion")
@@ -43,6 +45,7 @@ public class DispatchMainExecutor: RunLoopExecutor, @unchecked Sendable {
4345

4446
@available(SwiftStdlib 6.2, *)
4547
extension DispatchMainExecutor: SerialExecutor {
48+
4649
public func enqueue(_ job: consuming ExecutorJob) {
4750
_dispatchEnqueueMain(UnownedJob(job))
4851
}
@@ -112,6 +115,9 @@ extension DispatchMainExecutor: MainExecutor {}
112115

113116
@available(SwiftStdlib 6.2, *)
114117
public class DispatchTaskExecutor: TaskExecutor, @unchecked Sendable {
118+
119+
public init() {}
120+
115121
public func enqueue(_ job: consuming ExecutorJob) {
116122
_dispatchEnqueueGlobal(UnownedJob(job))
117123
}

stdlib/public/Concurrency/DispatchGlobalExecutor.cpp

+11-28
Original file line numberDiff line numberDiff line change
@@ -241,32 +241,6 @@ void swift_dispatchEnqueueGlobal(SwiftJob *job) {
241241
}
242242

243243

244-
SWIFT_CC(swift)
245-
void swift_task_enqueueGlobalWithDelayImpl(SwiftJobDelay delay,
246-
SwiftJob *job) {
247-
assert(job && "no job provided");
248-
249-
dispatch_function_t dispatchFunction = &__swift_run_job;
250-
void *dispatchContext = job;
251-
252-
SwiftJobPriority priority = swift_job_getPriority(job);
253-
254-
auto queue = getTimerQueue(priority);
255-
256-
job->schedulerPrivate[SwiftJobDispatchQueueIndex] =
257-
DISPATCH_QUEUE_GLOBAL_EXECUTOR;
258-
259-
// dispatch_time takes a signed int64_t. SwiftJobDelay is unsigned, so
260-
// extremely large values get interpreted as negative numbers, which results
261-
// in zero delay. Clamp the value to INT64_MAX. That's about 292 years, so
262-
// there should be no noticeable difference.
263-
if (delay > (SwiftJobDelay)INT64_MAX)
264-
delay = INT64_MAX;
265-
266-
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, delay);
267-
dispatch_after_f(when, queue, dispatchContext, dispatchFunction);
268-
}
269-
270244
#define DISPATCH_UP_OR_MONOTONIC_TIME_MASK (1ULL << 63)
271245
#define DISPATCH_WALLTIME_MASK (1ULL << 62)
272246
#define DISPATCH_TIME_MAX_VALUE (DISPATCH_WALLTIME_MASK - 1)
@@ -359,11 +333,20 @@ void swift_dispatchEnqueueWithDeadline(bool global,
359333
job->schedulerPrivate[SwiftJobDispatchQueueIndex] = queue;
360334
}
361335

362-
uint64_t deadline = sec * NSEC_PER_SEC + nsec;
336+
uint64_t deadline;
337+
if (__builtin_mul_overflow(sec, NSEC_PER_SEC, &deadline)
338+
|| __builtin_add_overflow(nsec, deadline, &deadline)) {
339+
deadline = UINT64_MAX;
340+
}
341+
363342
dispatch_time_t when = clock_and_value_to_time(clock, deadline);
364343

365344
if (tnsec != -1) {
366-
uint64_t leeway = tsec * NSEC_PER_SEC + tnsec;
345+
uint64_t leeway;
346+
if (__builtin_mul_overflow(tsec, NSEC_PER_SEC, &leeway)
347+
|| __builtin_add_overflow(tnsec, deadline, &leeway)) {
348+
leeway = UINT64_MAX;
349+
}
367350

368351
dispatch_source_t source =
369352
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

0 commit comments

Comments
 (0)