From b10c2c89a9d7c86e7796b6367be7f0a797f41651 Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Wed, 22 Sep 2021 22:14:53 -0700 Subject: [PATCH 1/6] Paramterize module in lookupIntrinsic The lookupConcurrencyIntrinsic function only looked in the concurrency module. It is useful to look in other modules for intrinsics too. --- lib/SILGen/SILGen.cpp | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index dfcc2a7dc24b3..d1bf7680104e3 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -338,24 +338,15 @@ SILGenModule::getConformanceToBridgedStoredNSError(SILLocation loc, Type type) { return SwiftModule->lookupConformance(type, proto); } -static FuncDecl *lookupConcurrencyIntrinsic(ASTContext &C, - Optional &cache, - StringRef name) { +static FuncDecl *lookupIntrinsic(ModuleDecl &module, + Optional &cache, Identifier name) { if (cache) return *cache; - - auto *module = C.getLoadedModule(C.Id_Concurrency); - if (!module) { - cache = nullptr; - return nullptr; - } - - SmallVector decls; - module->lookupQualified(module, - DeclNameRef(C.getIdentifier(name)), - NL_QualifiedDefault | NL_IncludeUsableFromInline, - decls); + SmallVector decls; + module.lookupQualified(&module, DeclNameRef(name), + NL_QualifiedDefault | NL_IncludeUsableFromInline, + decls); if (decls.size() != 1) { cache = nullptr; return nullptr; @@ -365,6 +356,18 @@ static FuncDecl *lookupConcurrencyIntrinsic(ASTContext &C, return func; } +static FuncDecl *lookupConcurrencyIntrinsic(ASTContext &C, + Optional &cache, + StringRef name) { + auto *module = C.getLoadedModule(C.Id_Concurrency); + if (!module) { + cache = nullptr; + return nullptr; + } + + return lookupIntrinsic(*module, cache, C.getIdentifier(name)); +} + FuncDecl * SILGenModule::getAsyncLetStart() { return lookupConcurrencyIntrinsic(getASTContext(), From 552ae0635a84e57a809315c38d65c7f58332ffe0 Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Thu, 29 Jul 2021 17:00:27 -0700 Subject: [PATCH 2/6] Add AsyncEntryPoint SILDeclRef type The AsyncEntryPoint represents the thunk that is wrapped in a task. This thunk is used to ensure that the main function explicitly calls "exit", and to properly unwrap and report any unhandled errors returned from the user-written main. The function takes on the name `@async_main` in the emitted SIL. --- include/swift/SIL/SILDeclRef.h | 8 +++++++- lib/IRGen/GenObjC.cpp | 1 + lib/SIL/IR/SILDeclRef.cpp | 19 +++++++++++++++++-- lib/SIL/IR/SILFunctionType.cpp | 7 +++++++ lib/SIL/IR/SILPrinter.cpp | 1 + lib/SIL/IR/TypeLowering.cpp | 29 +++++++++++++++++++++++++++++ lib/SILGen/SILGen.cpp | 1 + lib/SILGen/SILGenFunction.cpp | 1 + lib/SILGen/SILGenFunction.h | 3 +++ 9 files changed, 67 insertions(+), 3 deletions(-) diff --git a/include/swift/SIL/SILDeclRef.h b/include/swift/SIL/SILDeclRef.h index b5d939eede36b..871549258c17a 100644 --- a/include/swift/SIL/SILDeclRef.h +++ b/include/swift/SIL/SILDeclRef.h @@ -156,8 +156,11 @@ struct SILDeclRef { /// The main entry-point function. This may reference a SourceFile for a /// top-level main, or a decl for e.g an @main decl. EntryPoint, + + /// The asynchronous main entry-point function. + AsyncEntryPoint, }; - + /// The AST node represented by this SILDeclRef. Loc loc; /// The Kind of this SILDeclRef. @@ -231,6 +234,9 @@ struct SILDeclRef { /// Produces a SILDeclRef for a synthetic main entry-point such as @main. static SILDeclRef getMainDeclEntryPoint(ValueDecl *decl); + /// Produces a SILDeclRef for the synthesized async main entry-point + static SILDeclRef getAsyncMainDeclEntryPoint(ValueDecl *decl); + /// Produces a SILDeclRef for the entry-point of a main FileUnit. static SILDeclRef getMainFileEntryPoint(FileUnit *file); diff --git a/lib/IRGen/GenObjC.cpp b/lib/IRGen/GenObjC.cpp index 74b9c638fd34d..3f860074818fd 100644 --- a/lib/IRGen/GenObjC.cpp +++ b/lib/IRGen/GenObjC.cpp @@ -474,6 +474,7 @@ namespace { case SILDeclRef::Kind::PropertyWrapperBackingInitializer: case SILDeclRef::Kind::PropertyWrapperInitFromProjectedValue: case SILDeclRef::Kind::EntryPoint: + case SILDeclRef::Kind::AsyncEntryPoint: llvm_unreachable("Method does not have a selector"); case SILDeclRef::Kind::Destroyer: diff --git a/lib/SIL/IR/SILDeclRef.cpp b/lib/SIL/IR/SILDeclRef.cpp index ba68115e8e92c..07a0ee1a62770 100644 --- a/lib/SIL/IR/SILDeclRef.cpp +++ b/lib/SIL/IR/SILDeclRef.cpp @@ -273,6 +273,8 @@ SILLinkage SILDeclRef::getLinkage(ForDefinition_t forDefinition) const { // The main entry-point is public. if (kind == Kind::EntryPoint) return SILLinkage::Public; + if (kind == Kind::AsyncEntryPoint) + return SILLinkage::Hidden; // Add External to the linkage (e.g. Public -> PublicExternal) if this is a // declaration not a definition. @@ -448,6 +450,15 @@ SILDeclRef SILDeclRef::getMainDeclEntryPoint(ValueDecl *decl) { return result; } +SILDeclRef SILDeclRef::getAsyncMainDeclEntryPoint(ValueDecl *decl) { + auto *file = cast(decl->getDeclContext()->getModuleScopeContext()); + assert(file->getMainDecl() == decl); + SILDeclRef result; + result.loc = decl; + result.kind = Kind::AsyncEntryPoint; + return result; +} + SILDeclRef SILDeclRef::getMainFileEntryPoint(FileUnit *file) { assert(file->hasEntryPoint() && !file->getMainDecl()); SILDeclRef result; @@ -539,7 +550,7 @@ IsSerialized_t SILDeclRef::isSerialized() const { return IsNotSerialized; } - if (kind == Kind::EntryPoint) + if (kind == Kind::EntryPoint || kind == Kind::AsyncEntryPoint) return IsNotSerialized; if (isIVarInitializerOrDestroyer()) @@ -925,6 +936,9 @@ std::string SILDeclRef::mangle(ManglingKind MKind) const { return mangler.mangleInitFromProjectedValueEntity(cast(getDecl()), SKind); + case SILDeclRef::Kind::AsyncEntryPoint: { + return "async_Main"; + } case SILDeclRef::Kind::EntryPoint: { return getASTContext().getEntryPointFunctionName(); } @@ -1257,7 +1271,8 @@ unsigned SILDeclRef::getParameterListCount() const { return 1; // Always uncurried even if the underlying function is curried. - if (kind == Kind::DefaultArgGenerator || kind == Kind::EntryPoint) + if (kind == Kind::DefaultArgGenerator || kind == Kind::EntryPoint || + kind == Kind::AsyncEntryPoint) return 1; auto *vd = getDecl(); diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index c99f8aca8e30c..02e23458ceb12 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -2526,6 +2526,9 @@ static CanSILFunctionType getNativeSILFunctionType( case SILDeclRef::Kind::Deallocator: return getSILFunctionTypeForConventions(DeallocatorConventions()); + case SILDeclRef::Kind::AsyncEntryPoint: + return getSILFunctionTypeForConventions( + DefaultConventions(NormalParameterConvention::Guaranteed)); case SILDeclRef::Kind::EntryPoint: llvm_unreachable("Handled by getSILFunctionTypeForAbstractCFunction"); } @@ -3071,6 +3074,7 @@ static ObjCSelectorFamily getObjCSelectorFamily(SILDeclRef c) { case SILDeclRef::Kind::PropertyWrapperBackingInitializer: case SILDeclRef::Kind::PropertyWrapperInitFromProjectedValue: case SILDeclRef::Kind::EntryPoint: + case SILDeclRef::Kind::AsyncEntryPoint: llvm_unreachable("Unexpected Kind of foreign SILDeclRef"); } @@ -3343,6 +3347,8 @@ TypeConverter::getDeclRefRepresentation(SILDeclRef c) { case SILDeclRef::Kind::IVarDestroyer: return SILFunctionTypeRepresentation::Method; + case SILDeclRef::Kind::AsyncEntryPoint: + return SILFunctionTypeRepresentation::Thin; case SILDeclRef::Kind::EntryPoint: return SILFunctionTypeRepresentation::CFunctionPointer; } @@ -4170,6 +4176,7 @@ static AbstractFunctionDecl *getBridgedFunction(SILDeclRef declRef) { case SILDeclRef::Kind::IVarInitializer: case SILDeclRef::Kind::IVarDestroyer: case SILDeclRef::Kind::EntryPoint: + case SILDeclRef::Kind::AsyncEntryPoint: return nullptr; } llvm_unreachable("bad SILDeclRef kind"); diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index a44cc8ad7fb33..5b7faa4d0a395 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -316,6 +316,7 @@ void SILDeclRef::print(raw_ostream &OS) const { switch (kind) { case SILDeclRef::Kind::Func: case SILDeclRef::Kind::EntryPoint: + case SILDeclRef::Kind::AsyncEntryPoint: break; case SILDeclRef::Kind::Allocator: OS << "!allocator"; diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index ae83f68cec349..5743e1b543d46 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2556,6 +2556,32 @@ getFunctionInterfaceTypeWithCaptures(TypeConverter &TC, innerExtInfo); } +static CanAnyFunctionType getAsyncEntryPoint(ASTContext &C) { + + // @main struct Main { + // static func main() async throws {} + // static func $main() async throws { try await main() } + // } + // + // func @async_main() async -> Void { + // do { + // try await Main.$main() + // exit(0) + // } catch { + // _emitErrorInMain(error) + // } + // } + // + // This generates the type signature for @async_main + // TODO: 'Never' return type would be more accurate. + + CanType returnType = C.getVoidType()->getCanonicalType(); + FunctionType::ExtInfo extInfo = + FunctionType::ExtInfoBuilder().withAsync(true).withThrows(false).build(); + return CanAnyFunctionType::get(/*genericSig*/ nullptr, {}, returnType, + extInfo); +} + static CanAnyFunctionType getEntryPointInterfaceType(ASTContext &C) { // Use standard library types if we have them; otherwise, fall back to // builtins. @@ -2675,6 +2701,8 @@ CanAnyFunctionType TypeConverter::makeConstantInterfaceType(SILDeclRef c) { case SILDeclRef::Kind::IVarDestroyer: return getIVarInitDestroyerInterfaceType(cast(vd), c.isForeign, true); + case SILDeclRef::Kind::AsyncEntryPoint: + return getAsyncEntryPoint(Context); case SILDeclRef::Kind::EntryPoint: return getEntryPointInterfaceType(Context); } @@ -2729,6 +2757,7 @@ TypeConverter::getConstantGenericSignature(SILDeclRef c) { case SILDeclRef::Kind::StoredPropertyInitializer: return vd->getDeclContext()->getGenericSignatureOfContext(); case SILDeclRef::Kind::EntryPoint: + case SILDeclRef::Kind::AsyncEntryPoint: llvm_unreachable("Doesn't have generic signature"); } diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index d1bf7680104e3..d52a592a581a2 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -1039,6 +1039,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) { postEmitFunction(constant, f); return; } + case SILDeclRef::Kind::AsyncEntryPoint: case SILDeclRef::Kind::EntryPoint: { f->setBare(IsBare); diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index a15b221755cde..ee13d7ccbb52f 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -141,6 +141,7 @@ DeclName SILGenModule::getMagicFunctionName(SILDeclRef ref) { case SILDeclRef::Kind::EnumElement: return getMagicFunctionName(cast(ref.getDecl()) ->getDeclContext()); + case SILDeclRef::Kind::AsyncEntryPoint: case SILDeclRef::Kind::EntryPoint: auto *file = ref.getDecl()->getDeclContext()->getParentSourceFile(); return getMagicFunctionName(file); diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index e5b7b9ec2900f..78d3bc8dc8eb6 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -638,6 +638,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction /// application based on a main type and optionally a main type. void emitArtificialTopLevel(Decl *mainDecl); + /// Generate code into @main for starting the async main on the main thread. + void emitAsyncMainThreadStart(SILDeclRef entryPoint); + /// Generates code for a class deallocating destructor. This /// calls the destroying destructor and then deallocates 'self'. void emitDeallocatingDestructor(DestructorDecl *dd); From d376ee99899a3d25a2cbbf390f88285547a9ca05 Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Wed, 4 Aug 2021 09:29:06 -0700 Subject: [PATCH 3/6] Run first thunk of Async main synchronously This patch updates the asynchronous main function to run the first thunk of the function synchronously through a call to `swift_job_run`. The runloop is killed by exiting or aborting the task that it is running on. As such, we need to ensure that the task contains an async function that either calls exit explicitly or aborts. The AsyncEntryPoint, that contains this code, was added in the previous patch. This patch adds the pieces for the actual implementation of this behaviour as well as adding the necessary code to start the runloop. There are now four layers of main functions before hitting the "real" code. @main: This is the actual main entrypoint of the program. This constructs the task containing @async_main, grabs the main executor, runs swift_job_run to run the first part synchronously, and finally kicks off the runloop with a call to _asyncMainDrainQueue. This is generated in the call to `emitAsyncMainThreadStart`. @async_main: This thunk exists to ensure that the main function calls `exit` at some point so that the runloop stops. It also handles emitting an error if the user-written main function throws. e.g: ``` func async_main() async -> () { do { try await Main.$main() exit(0) } catch { _errorInMain(error) } } ``` Main.$main(): This still has the same behaviour as with the synchronous case. It just calls `try await Main.main()` and exists to simplify typechecking. Main.main(): This is the actual user-specified main. It serves the same purpose as in the synchronous, allowing the programmer to write code, but it's async! The control flow in `emitFunctionDefinition` is a little confusing (to me anyway), so here it is spelled out: If the main function is synchronous, the `constant.kind` will be a `SILDeclRef::Kind::EntryPoint`, but the `decl` won't be async, so it drops down to `emitArtificalTopLevel` anyway. If the main function is async and we're generating `@main`, the `constant.kind` will be `SILDeclRef::Kind::AsyncEntryPoint`, so we also call `emitArtificalTopLevel`. `emitArtificalTopLevel` is responsible for detecting whether the decl is async and deciding whether to emit code to extract the argc/argv variables that get passed into the actual main entrypoint to the program. If we're generating the `@async_main` body, the kind will be `SILDeclRef::Kind::EntryPoint` and the `decl` will be async, so we grab the mainEntryPoint decl and call `emitAsyncMainThreadStart` to generate the wrapping code. Note; there is a curious change in `SILLocation::getSourceLoc()` where instead of simply checking `isFilenameAndLocation()`, I change it to `getStorageKind() == FilenameAndLocationKind`. This is because the SILLocation returned is to a FilenameAndLocationKind, but the actual storage returns true for the call to `isNull()` inside of the `isFilenameAndLocation()` call. This results in us incorrectly falling through to the `getASTNode()` call below that, which asserts when asked to get the AST node of a location. I also did a little bit of refactoring in the SILGenModule for grabbing intrinsics. Previously, there was only a `getConcurrencyIntrinsic` function, which would only load FuncDecls out of the concurrency module. The `exit` function is in the concurrency shims module, so I refactored the load code to take a ModuleDecl to search from. The emitBuiltinCreateAsyncTask function symbol is exposed from SILGenBuiltin so that it is available from SILGenFunction. There is a fair bit of work involved going from what is available at the SGF to what is needed for actually calling the CreateAsyncTask builtin, so in order to avoid additional maintenance, it's good to re-use that. --- lib/SIL/IR/SILLocation.cpp | 2 +- lib/SILGen/SILGen.cpp | 62 ++++++++++- lib/SILGen/SILGen.h | 14 +++ lib/SILGen/SILGenBuiltin.cpp | 7 +- lib/SILGen/SILGenFunction.cpp | 147 +++++++++++++++++++++++++-- lib/Sema/TypeCheckAttr.cpp | 46 ++++----- stdlib/public/Concurrency/Task.swift | 6 +- test/Concurrency/async_main.swift | 80 +++++++++++---- 8 files changed, 299 insertions(+), 65 deletions(-) diff --git a/lib/SIL/IR/SILLocation.cpp b/lib/SIL/IR/SILLocation.cpp index c662a49627d84..0185a1e6c8fd1 100644 --- a/lib/SIL/IR/SILLocation.cpp +++ b/lib/SIL/IR/SILLocation.cpp @@ -42,7 +42,7 @@ SourceLoc SILLocation::getSourceLoc() const { // Don't crash if the location is a FilenameAndLocation. // TODO: this is a workaround until rdar://problem/25225083 is implemented. - if (isFilenameAndLocation()) + if (getStorageKind() == FilenameAndLocationKind) return SourceLoc(); return getSourceLoc(getPrimaryASTNode()); diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index d52a592a581a2..99ba13ffbbda4 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -440,6 +440,37 @@ SILGenModule::getCheckExpectedExecutor() { "_checkExpectedExecutor"); } +FuncDecl *SILGenModule::getAsyncMainDrainQueue() { + return lookupConcurrencyIntrinsic(getASTContext(), AsyncMainDrainQueue, + "_asyncMainDrainQueue"); +} + +FuncDecl *SILGenModule::getGetMainExecutor() { + return lookupConcurrencyIntrinsic(getASTContext(), GetMainExecutor, + "_getMainExecutor"); +} + +FuncDecl *SILGenModule::getSwiftJobRun() { + return lookupConcurrencyIntrinsic(getASTContext(), SwiftJobRun, + "_swiftJobRun"); +} + +FuncDecl *SILGenModule::getExit() { + if (ExitFunc) + return *ExitFunc; + + ASTContext &C = getASTContext(); + ModuleDecl *concurrencyShims = + C.getModuleByIdentifier(C.getIdentifier("_SwiftConcurrencyShims")); + + if (!concurrencyShims) { + ExitFunc = nullptr; + return nullptr; + } + + return lookupIntrinsic(*concurrencyShims, ExitFunc, C.getIdentifier("exit")); +} + ProtocolConformance *SILGenModule::getNSErrorConformanceToError() { if (NSErrorConformanceToError) return *NSErrorConformanceToError; @@ -1050,7 +1081,27 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) { auto *decl = constant.getDecl(); auto *dc = decl->getDeclContext(); PrettyStackTraceSILFunction X("silgen emitArtificialTopLevel", f); - SILGenFunction(*this, *f, dc).emitArtificialTopLevel(decl); + // In all cases, a constant.kind == EntryPoint indicates the main entrypoint + // to the program, @main. + // In the synchronous case, the decl is not async, so emitArtificialTopLevel + // emits the error unwrapping and call to MainType.$main() into @main. + // + // In the async case, emitAsyncMainThreadStart is responsible for generating + // the contents of @main. This wraps @async_main in a task, passes that task + // to swift_job_run to execute the first thunk, and starts the runloop to + // run any additional continuations. The kind is EntryPoint, and the decl is + // async. + // When the kind is 'AsyncMain', we are generating @async_main. In this + // case, emitArtificialTopLevel emits the code for calling MaintType.$main, + // unwrapping errors, and calling exit(0) into @async_main to run the + // user-specified main function. + if (constant.kind == SILDeclRef::Kind::EntryPoint && isa(decl) && + static_cast(decl)->hasAsync()) { + SILDeclRef mainEntryPoint = SILDeclRef::getAsyncMainDeclEntryPoint(decl); + SILGenFunction(*this, *f, dc).emitAsyncMainThreadStart(mainEntryPoint); + } else { + SILGenFunction(*this, *f, dc).emitArtificialTopLevel(decl); + } postEmitFunction(constant, f); return; } @@ -2033,10 +2084,15 @@ class SILGenModuleRAII { // If the source file contains an artificial main, emit the implicit // top-level code. - if (auto *mainDecl = sf->getMainDecl()) + if (auto *mainDecl = sf->getMainDecl()) { + if (isa(mainDecl) && + static_cast(mainDecl)->hasAsync()) + emitSILFunctionDefinition( + SILDeclRef::getAsyncMainDeclEntryPoint(mainDecl)); emitSILFunctionDefinition(SILDeclRef::getMainDeclEntryPoint(mainDecl)); + } } - + void emitSILFunctionDefinition(SILDeclRef ref) { SGM.emitFunctionDefinition(ref, SGM.getFunction(ref, ForDefinition)); } diff --git a/lib/SILGen/SILGen.h b/lib/SILGen/SILGen.h index 53a823a1b39b2..d4ad5926d9c62 100644 --- a/lib/SILGen/SILGen.h +++ b/lib/SILGen/SILGen.h @@ -131,6 +131,11 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor { Optional ResumeUnsafeThrowingContinuationWithError; Optional CheckExpectedExecutor; + Optional AsyncMainDrainQueue; + Optional GetMainExecutor; + Optional SwiftJobRun; + Optional ExitFunc; + public: SILGenModule(SILModule &M, ModuleDecl *SM); @@ -517,6 +522,15 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor { /// Retrieve the _Concurrency._checkExpectedExecutor intrinsic. FuncDecl *getCheckExpectedExecutor(); + /// Retrieve the _Concurrency._asyncMainDrainQueue intrinsic. + FuncDecl *getAsyncMainDrainQueue(); + /// Retrieve the _Concurrency._getMainExecutor intrinsic. + FuncDecl *getGetMainExecutor(); + /// Retrieve the _Concurrency._swiftJobRun intrinsic. + FuncDecl *getSwiftJobRun(); + // Retrieve the _SwiftConcurrencyShims.exit intrinsic. + FuncDecl *getExit(); + SILFunction *getKeyPathProjectionCoroutine(bool isReadAccess, KeyPathTypeKind typeKind); diff --git a/lib/SILGen/SILGenBuiltin.cpp b/lib/SILGen/SILGenBuiltin.cpp index 5b44ead47142c..d19e9ca57f621 100644 --- a/lib/SILGen/SILGenBuiltin.cpp +++ b/lib/SILGen/SILGenBuiltin.cpp @@ -1402,9 +1402,10 @@ emitFunctionArgumentForAsyncTaskEntryPoint(SILGenFunction &SGF, } // Emit SIL for the named builtin: createAsyncTask. -static ManagedValue emitBuiltinCreateAsyncTask( - SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs, - ArrayRef args, SGFContext C) { +ManagedValue emitBuiltinCreateAsyncTask(SILGenFunction &SGF, SILLocation loc, + SubstitutionMap subs, + ArrayRef args, + SGFContext C) { ASTContext &ctx = SGF.getASTContext(); auto flags = args[0].forward(SGF); diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index ee13d7ccbb52f..a01a068870e47 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -19,6 +19,7 @@ #include "RValue.h" #include "SILGenFunctionBuilder.h" #include "Scope.h" +#include "swift/ABI/MetadataValues.h" #include "swift/AST/ClangModuleLoader.h" #include "swift/AST/DiagnosticsSIL.h" #include "swift/AST/FileUnit.h" @@ -563,14 +564,26 @@ void SILGenFunction::emitClosure(AbstractClosureExpr *ace) { emitEpilog(ace); } +ManagedValue emitBuiltinCreateAsyncTask(SILGenFunction &SGF, SILLocation loc, + SubstitutionMap subs, + ArrayRef args, + SGFContext C); + void SILGenFunction::emitArtificialTopLevel(Decl *mainDecl) { // Create the argc and argv arguments. auto entry = B.getInsertionBB(); auto paramTypeIter = F.getConventions() .getParameterSILTypes(getTypeExpansionContext()) .begin(); - SILValue argc = entry->createFunctionArgument(*paramTypeIter); - SILValue argv = entry->createFunctionArgument(*std::next(paramTypeIter)); + + SILValue argc; + SILValue argv; + const bool isAsyncFunc = + isa(mainDecl) && static_cast(mainDecl)->hasAsync(); + if (!isAsyncFunc) { + argc = entry->createFunctionArgument(*paramTypeIter); + argv = entry->createFunctionArgument(*std::next(paramTypeIter)); + } switch (mainDecl->getArtificialMainKind()) { case ArtificialMainKind::UIApplicationMain: { @@ -777,13 +790,32 @@ void SILGenFunction::emitArtificialTopLevel(Decl *mainDecl) { auto builtinInt32Type = SILType::getBuiltinIntegerType(32, getASTContext()); auto *exitBlock = createBasicBlock(); - B.setInsertionPoint(exitBlock); SILValue exitCode = exitBlock->createPhiArgument(builtinInt32Type, OwnershipKind::None); - auto returnType = F.getConventions().getSingleSILResultType(B.getTypeExpansionContext()); - if (exitCode->getType() != returnType) - exitCode = B.createStruct(moduleLoc, returnType, exitCode); - B.createReturn(moduleLoc, exitCode); + B.setInsertionPoint(exitBlock); + + if (!mainFunc->hasAsync()) { + auto returnType = F.getConventions().getSingleSILResultType( + B.getTypeExpansionContext()); + if (exitCode->getType() != returnType) + exitCode = B.createStruct(moduleLoc, returnType, exitCode); + B.createReturn(moduleLoc, exitCode); + } else { + FuncDecl *exitFuncDecl = SGM.getExit(); + assert(exitFuncDecl && "Failed to find exit function declaration"); + SILFunction *exitSILFunc = SGM.getFunction( + SILDeclRef(exitFuncDecl, SILDeclRef::Kind::Func, /*isForeign*/ true), + NotForDefinition); + + SILFunctionType &funcType = + *exitSILFunc->getLoweredType().getAs(); + SILType retType = SILType::getPrimitiveObjectType( + funcType.getParameters().front().getInterfaceType()); + exitCode = B.createStruct(moduleLoc, retType, exitCode); + SILValue exitCall = B.createFunctionRef(moduleLoc, exitSILFunc); + B.createApply(moduleLoc, exitCall, {}, {exitCode}); + B.createUnreachable(moduleLoc); + } if (mainFunc->hasThrows()) { auto *successBlock = createBasicBlock(); @@ -821,6 +853,107 @@ void SILGenFunction::emitArtificialTopLevel(Decl *mainDecl) { } } +void SILGenFunction::emitAsyncMainThreadStart(SILDeclRef entryPoint) { + auto moduleLoc = RegularLocation::getModuleLocation(); + auto *entryBlock = B.getInsertionBB(); + auto paramTypeIter = F.getConventions() + .getParameterSILTypes(getTypeExpansionContext()) + .begin(); + + entryBlock->createFunctionArgument(*paramTypeIter); // argc + entryBlock->createFunctionArgument(*std::next(paramTypeIter)); // argv + + // Lookup necessary functions + swift::ASTContext &ctx = entryPoint.getDecl()->getASTContext(); + + B.setInsertionPoint(entryBlock); + + /// Generates a reinterpret_cast for converting + /// Builtin.Job -> UnownedJob + /// Builtin.Executor -> UnownedSerialExecutor + /// These are used by _swiftJobRun, which, ABI-wise, could take + /// Builtin.Job or Builtin.Executor, but doesn't. + auto createExplodyCastForCall = + [this, &moduleLoc](SILValue originalValue, FuncDecl *jobRunFuncDecl, + uint32_t paramIndex) -> SILValue { + // The type coming from the _swiftJobRun function + Type apiType = jobRunFuncDecl->getParameters()->get(paramIndex)->getType(); + SILType apiSILType = + SILType::getPrimitiveObjectType(apiType->getCanonicalType()); + // If the types are the same, we don't need to do anything! + if (apiSILType == originalValue->getType()) + return originalValue; + return this->B.createUncheckedReinterpretCast(moduleLoc, originalValue, + apiSILType); + }; + + // Call CreateAsyncTask + FuncDecl *builtinDecl = cast(getBuiltinValueDecl( + getASTContext(), + ctx.getIdentifier(getBuiltinName(BuiltinValueKind::CreateAsyncTask)))); + + auto subs = SubstitutionMap::get(builtinDecl->getGenericSignature(), + {TupleType::getEmpty(ctx)}, + ArrayRef{}); + + SILValue mainFunctionRef = emitGlobalFunctionRef(moduleLoc, entryPoint); + + // Emit the CreateAsyncTask builtin + TaskCreateFlags taskCreationFlagMask(0); + SILValue taskFlags = + emitWrapIntegerLiteral(moduleLoc, getLoweredType(ctx.getIntType()), + taskCreationFlagMask.getOpaqueValue()); + + SILValue task = + emitBuiltinCreateAsyncTask(*this, moduleLoc, subs, + {ManagedValue::forUnmanaged(taskFlags), + ManagedValue::forUnmanaged(mainFunctionRef)}, + {}) + .forward(*this); + DestructureTupleInst *structure = B.createDestructureTuple(moduleLoc, task); + task = structure->getResult(0); + + // Get swiftJobRun + FuncDecl *swiftJobRunFuncDecl = SGM.getSwiftJobRun(); + SILFunction *swiftJobRunSILFunc = + SGM.getFunction(SILDeclRef(swiftJobRunFuncDecl, SILDeclRef::Kind::Func), + NotForDefinition); + SILValue swiftJobRunFunc = + B.createFunctionRefFor(moduleLoc, swiftJobRunSILFunc); + + // Convert task to job + SILType JobType = SILType::getPrimitiveObjectType( + getBuiltinType(ctx, "Job")->getCanonicalType()); + SILValue jobResult = B.createBuiltin( + moduleLoc, + ctx.getIdentifier(getBuiltinName(BuiltinValueKind::ConvertTaskToJob)), + JobType, {}, {task}); + jobResult = createExplodyCastForCall(jobResult, swiftJobRunFuncDecl, 0); + + // Get main executor + FuncDecl *getMainExecutorFuncDecl = SGM.getGetMainExecutor(); + SILFunction *getMainExeutorSILFunc = SGM.getFunction( + SILDeclRef(getMainExecutorFuncDecl, SILDeclRef::Kind::Func), + NotForDefinition); + SILValue getMainExeutorFunc = + B.createFunctionRefFor(moduleLoc, getMainExeutorSILFunc); + SILValue mainExecutor = B.createApply(moduleLoc, getMainExeutorFunc, {}, {}); + mainExecutor = createExplodyCastForCall(mainExecutor, swiftJobRunFuncDecl, 1); + + // Run first part synchronously + B.createApply(moduleLoc, swiftJobRunFunc, {}, {jobResult, mainExecutor}); + + // Start Main loop! + FuncDecl *drainQueueFuncDecl = SGM.getAsyncMainDrainQueue(); + SILFunction *drainQueueSILFunc = SGM.getFunction( + SILDeclRef(drainQueueFuncDecl, SILDeclRef::Kind::Func), NotForDefinition); + SILValue drainQueueFunc = + B.createFunctionRefFor(moduleLoc, drainQueueSILFunc); + B.createApply(moduleLoc, drainQueueFunc, {}, {}); + B.createUnreachable(moduleLoc); + return; +} + void SILGenFunction::emitGeneratorFunction(SILDeclRef function, Expr *value, bool EmitProfilerIncrement) { auto *dc = function.getDecl()->getInnermostDeclContext(); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index eb5fd9ac37c49..325d9d35c3a16 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -1888,41 +1888,29 @@ synthesizeMainBody(AbstractFunctionDecl *fn, void *arg) { Expr *returnedExpr; if (mainFunction->hasAsync()) { - // Pass main into _runAsyncMain(_ asyncFunc: () async throws -> ()) - // Resulting $main looks like: - // $main() { _runAsyncMain(main) } + // Ensure that the concurrency module is loaded auto *concurrencyModule = context.getLoadedModule(context.Id_Concurrency); if (!concurrencyModule) { context.Diags.diagnose(mainFunction->getAsyncLoc(), diag::async_main_no_concurrency); auto result = new (context) ErrorExpr(mainFunction->getSourceRange()); - SmallVector stmts; - stmts.push_back(result); - auto body = BraceStmt::create(context, SourceLoc(), stmts, - SourceLoc(), /*Implicit*/true); - + ASTNode stmts[] = {result}; + auto body = BraceStmt::create(context, SourceLoc(), stmts, SourceLoc(), + /*Implicit*/ true); return std::make_pair(body, /*typechecked*/true); } - SmallVector decls; - concurrencyModule->lookupQualified( - concurrencyModule, - DeclNameRef(context.getIdentifier("_runAsyncMain")), - NL_QualifiedDefault | NL_IncludeUsableFromInline, - decls); - assert(!decls.empty() && "Failed to find _runAsyncMain"); - FuncDecl *runner = cast(decls[0]); - - auto asyncRunnerDeclRef = ConcreteDeclRef(runner, substitutionMap); - - DeclRefExpr *funcExpr = new (context) DeclRefExpr(asyncRunnerDeclRef, - DeclNameLoc(), - /*implicit=*/true); - funcExpr->setType(runner->getInterfaceType()); - auto *argList = - ArgumentList::forImplicitUnlabeled(context, {memberRefExpr}); - auto *callExpr = CallExpr::createImplicit(context, funcExpr, argList); - returnedExpr = callExpr; + // $main() async { await main() } + Expr *awaitExpr = + new (context) AwaitExpr(callExpr->getLoc(), callExpr, + context.TheEmptyTupleType, /*implicit*/ true); + if (mainFunction->hasThrows()) { + // $main() async throws { try await main() } + awaitExpr = + new (context) TryExpr(callExpr->getLoc(), awaitExpr, + context.TheEmptyTupleType, /*implicit*/ true); + } + returnedExpr = awaitExpr; } else if (mainFunction->hasThrows()) { auto *tryExpr = new (context) TryExpr( callExpr->getLoc(), callExpr, context.TheEmptyTupleType, /*implicit=*/true); @@ -2039,7 +2027,7 @@ SynthesizeMainFunctionRequest::evaluate(Evaluator &evaluator, DeclName(context, DeclBaseName(context.Id_MainEntryPoint), ParameterList::createEmpty(context)), /*NameLoc=*/SourceLoc(), - /*Async=*/false, + /*Async=*/mainFunction->hasAsync(), /*Throws=*/mainFunction->hasThrows(), /*GenericParams=*/nullptr, ParameterList::createEmpty(context), /*FnRetType=*/TupleType::getEmpty(context), declContext); @@ -2501,7 +2489,7 @@ static FuncDecl *findSimilarAccessor(DeclNameRef replacedVarName, } assert(!isa(results[0])); - + auto *origStorage = cast(results[0]); if (forDynamicReplacement && !origStorage->isDynamic()) { Diags.diagnose(attr->getLocation(), diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index ac5447b64f6de..1d75b04d21259 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -753,7 +753,11 @@ func _enqueueJobGlobalWithDelay(_ delay: UInt64, _ task: Builtin.Job) @available(SwiftStdlib 5.5, *) @_silgen_name("swift_task_asyncMainDrainQueue") -public func _asyncMainDrainQueue() -> Never +internal func _asyncMainDrainQueue() -> Never + +@available(SwiftStdlib 5.5, *) +@_silgen_name("swift_task_getMainExecutor") +internal func _getMainExecutor() -> Builtin.Executor @available(SwiftStdlib 5.5, *) public func _runAsyncMain(@_unsafeSendable _ asyncFun: @escaping () async throws -> ()) { diff --git a/test/Concurrency/async_main.swift b/test/Concurrency/async_main.swift index b14fc3d52da6b..ee0cab3a5797c 100644 --- a/test/Concurrency/async_main.swift +++ b/test/Concurrency/async_main.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -dump-ast -disable-availability-checking -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-AST +// RUN: %target-swift-frontend -emit-sil -disable-availability-checking -parse-as-library %s | %FileCheck %s --check-prefix=CHECK-SIL // RUN: %target-build-swift -Xfrontend -disable-availability-checking -Xfrontend -parse-as-library %s -o %t_binary // RUN: %target-run %t_binary | %FileCheck %s --check-prefix=CHECK-EXEC @@ -15,29 +15,67 @@ func asyncFunc() async { } @main struct MyProgram { - static func main() async { + static func main() async throws { await asyncFunc() } } // CHECK-EXEC: Hello World! -// CHECK-AST-LABEL: "main()" interface -// CHECK-AST: (await_expr type='()' -// CHECK-AST-NEXT: (call_expr type='()' -// CHECK-AST-NEXT: (declref_expr type='() async -> ()' -// CHECK-AST-SAME: decl=async_main.(file).asyncFunc()@ - -// CHECK-AST-LABEL: (func_decl implicit "$main()" interface -// CHECK-AST: (brace_stmt -// CHECK-AST-NEXT: (return_stmt implicit -// CHECK-AST-NEXT: (call_expr implicit type='()' -// CHECK-AST-NEXT: (declref_expr implicit -// CHECK-AST-SAME: type='(@escaping () async throws -> ()) -> ()' -// CHECK-AST-SAME: decl=_Concurrency.(file)._runAsyncMain -// CHECK-AST-SAME: function_ref=single -// CHECK-AST-NEXT: (argument_list -// CHECK-AST-NEXT: (argument -// CHECK-AST-NEXT: (function_conversion_expr implicit type='() async throws -> ()' -// CHECK-AST-NEXT: (dot_syntax_call_expr -// CHECK-AST-NEXT: (autoclosure_expr implicit type='(MyProgram.Type) -> () async -> ()' +// static MyProgram.main() +// CHECK-SIL-LABEL: sil hidden @$s10async_main9MyProgramV0B0yyYaKFZ : $@convention(method) @async (@thin MyProgram.Type) -> @error Error + + +// static MyProgram.$main() +// CHECK-SIL-LABEL: sil hidden @$s10async_main9MyProgramV5$mainyyYaKFZ : $@convention(method) @async (@thin MyProgram.Type) -> @error Error + + +// async_Main +// CHECK-SIL_LABEL: sil hidden @async_Main : $@convention(thin) @async () -> () { +// call main +// CHECK-SIL: %0 = metatype $@thin MyProgram.Type // user: %2 +// CHECK-SIL-NEXT: // function_ref static MyProgram.$main() +// CHECK-SIL-NEXT: %1 = function_ref @$s10async_main9MyProgramV5$mainyyYaKFZ : $@convention(method) @async (@thin MyProgram.Type) -> @error Error // user: %2 +// CHECK-SIL-NEXT: try_apply %1(%0) : $@convention(method) @async (@thin MyProgram.Type) -> @error Error, normal bb1, error bb2 // id: %2 + +// unwrap error and exit or explode +// CHECK-SIL: bb1(%3 : $()): +// CHECK-SIL-NEXT: %4 = integer_literal $Builtin.Int32, 0 +// CHECK-SIL-NEXT: %5 = struct $Int32 (%4 : $Builtin.Int32) +// CHECK-SIL-NEXT: // function_ref exit +// CHECK-SIL-NEXT: %6 = function_ref @exit : $@convention(c) (Int32) -> Never +// CHECK-SIL-NEXT: %7 = apply %6(%5) : $@convention(c) (Int32) -> Never +// CHECK-SIL-NEXT: unreachable + +// CHECK-SIL: bb2(%9 : $Error): +// CHECK-SIL-NEXT: %10 = builtin "errorInMain"(%9 : $Error) : $() +// CHECK-SIL-NEXT: unreachable + +// main +// CHECK-SIL-LABEL: sil @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 { + +// CHECK-SIL: // function_ref async_Main +// CHECK-SIL-NEXT: %2 = function_ref @async_Main : $@convention(thin) @async () -> () +// CHECK-SIL-NEXT: %3 = integer_literal $Builtin.Int64, 21 +// CHECK-SIL-NEXT: %4 = struct $Int (%3 : $Builtin.Int64) +// CHECK-SIL-NEXT: %5 = metatype $@thick ().Type +// CHECK-SIL-NEXT: %6 = init_existential_metatype %5 : $@thick ().Type, $@thick Any.Type +// CHECK-SIL-NEXT: // function_ref thunk for @escaping @convention(thin) @async () -> () +// CHECK-SIL-NEXT: %7 = function_ref @$sIetH_yts5Error_pIegHrzo_TR : $@convention(thin) @async (@convention(thin) @async () -> ()) -> (@out (), @error Error) +// CHECK-SIL-NEXT: %8 = partial_apply [callee_guaranteed] %7(%2) : $@convention(thin) @async (@convention(thin) @async () -> ()) -> (@out (), @error Error) +// CHECK-SIL-NEXT: %9 = convert_function %8 : $@async @callee_guaranteed () -> (@out (), @error Error) to $@async @callee_guaranteed @substituted <τ_0_0> () -> (@out τ_0_0, @error Error) for <()> +// CHECK-SIL-NEXT: %10 = builtin "createAsyncTask"<()>(%4 : $Int, %6 : $@thick Any.Type, %9 : $@async @callee_guaranteed @substituted <τ_0_0> () -> (@out τ_0_0, @error Error) for <()>) : $(Builtin.NativeObject, Builtin.RawPointer) +// CHECK-SIL-NEXT: %11 = tuple_extract %10 : $(Builtin.NativeObject, Builtin.RawPointer), 0 +// CHECK-SIL-NEXT: // function_ref swift_job_run +// CHECK-SIL-NEXT: %12 = function_ref @swift_job_run : $@convention(thin) (UnownedJob, UnownedSerialExecutor) -> () +// CHECK-SIL-NEXT: %13 = builtin "convertTaskToJob"(%11 : $Builtin.NativeObject) : $Builtin.Job +// CHECK-SIL-NEXT: %14 = unchecked_trivial_bit_cast %13 : $Builtin.Job to $UnownedJob +// CHECK-SIL-NEXT: // function_ref swift_task_getMainExecutor +// CHECK-SIL-NEXT: %15 = function_ref @swift_task_getMainExecutor : $@convention(thin) () -> Builtin.Executor +// CHECK-SIL-NEXT: %16 = apply %15() : $@convention(thin) () -> Builtin.Executor +// CHECK-SIL-NEXT: %17 = unchecked_trivial_bit_cast %16 : $Builtin.Executor to $UnownedSerialExecutor +// CHECK-SIL-NEXT: %18 = apply %12(%14, %17) : $@convention(thin) (UnownedJob, UnownedSerialExecutor) -> () +// CHECK-SIL-NEXT: // function_ref swift_task_asyncMainDrainQueue +// CHECK-SIL-NEXT: %19 = function_ref @swift_task_asyncMainDrainQueue : $@convention(thin) () -> Never +// CHECK-SIL-NEXT: %20 = apply %19() : $@convention(thin) () -> Never +// CHECK-SIL-NEXT: unreachable From 493eca32726dadec144f865ce5d617bc4dc5ccbd Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Sat, 25 Sep 2021 00:49:24 -0700 Subject: [PATCH 4/6] Inherit main thread priority/context This patch changes the main task to inherit the context of the main thread. This should assign the appropriate priority based on how the program was invoked. I've also updated the tests to reflect these changes. --- lib/SILGen/SILGenFunction.cpp | 1 + test/Concurrency/Runtime/async_task_priority_current.swift | 4 ++-- test/Concurrency/async_main.swift | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index a01a068870e47..0992e7f7e1eb1 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -900,6 +900,7 @@ void SILGenFunction::emitAsyncMainThreadStart(SILDeclRef entryPoint) { // Emit the CreateAsyncTask builtin TaskCreateFlags taskCreationFlagMask(0); + taskCreationFlagMask.setInheritContext(true); SILValue taskFlags = emitWrapIntegerLiteral(moduleLoc, getLoweredType(ctx.getIntType()), taskCreationFlagMask.getOpaqueValue()); diff --git a/test/Concurrency/Runtime/async_task_priority_current.swift b/test/Concurrency/Runtime/async_task_priority_current.swift index 1ab95c82d59b8..3b8dcd33d3b77 100644 --- a/test/Concurrency/Runtime/async_task_priority_current.swift +++ b/test/Concurrency/Runtime/async_task_priority_current.swift @@ -20,7 +20,7 @@ extension TaskPriority: CustomStringConvertible { @available(SwiftStdlib 5.5, *) func test_detach() async { let a1 = Task.currentPriority - print("a1: \(a1)") // CHECK: TaskPriority(rawValue: 21) + print("a1: \(a1)") // CHECK: TaskPriority(rawValue: 25) // Note: remember to detach using a higher priority, otherwise a lower one // might be escalated by the get() and we could see `default` in the detached @@ -31,7 +31,7 @@ func test_detach() async { }.get() let a3 = Task.currentPriority - print("a3: \(a3)") // CHECK: a3: TaskPriority(rawValue: 21) + print("a3: \(a3)") // CHECK: a3: TaskPriority(rawValue: 25) } @available(SwiftStdlib 5.5, *) diff --git a/test/Concurrency/async_main.swift b/test/Concurrency/async_main.swift index ee0cab3a5797c..8bb08ff20d0ea 100644 --- a/test/Concurrency/async_main.swift +++ b/test/Concurrency/async_main.swift @@ -56,7 +56,7 @@ func asyncFunc() async { // CHECK-SIL: // function_ref async_Main // CHECK-SIL-NEXT: %2 = function_ref @async_Main : $@convention(thin) @async () -> () -// CHECK-SIL-NEXT: %3 = integer_literal $Builtin.Int64, 21 +// CHECK-SIL-NEXT: %3 = integer_literal $Builtin.Int64, 2048 // CHECK-SIL-NEXT: %4 = struct $Int (%3 : $Builtin.Int64) // CHECK-SIL-NEXT: %5 = metatype $@thick ().Type // CHECK-SIL-NEXT: %6 = init_existential_metatype %5 : $@thick ().Type, $@thick Any.Type From d9baba9a416b4bd984c7a462d80e2b34ee2005fe Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Tue, 28 Sep 2021 23:14:29 -0700 Subject: [PATCH 5/6] Fix priority test Priorities keep shifting since I first wrote this and are apparently different on different OS's and different versions of the same OS. Since the test is checking that entering and exiting tasks doesn't effect the outer-scoped priority, I've set a variable based on the main thread's priories, which should be inherited by the child. --- .../Runtime/async_task_priority_current.swift | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/test/Concurrency/Runtime/async_task_priority_current.swift b/test/Concurrency/Runtime/async_task_priority_current.swift index 3b8dcd33d3b77..8c39b4a9b703a 100644 --- a/test/Concurrency/Runtime/async_task_priority_current.swift +++ b/test/Concurrency/Runtime/async_task_priority_current.swift @@ -17,10 +17,19 @@ extension TaskPriority: CustomStringConvertible { } } +@available(SwiftStdlib 5.5, *) +@main struct Main { + static func main() async { + print("main priority: \(Task.currentPriority)") // CHECK: main priority: TaskPriority(rawValue: [[#MAIN_PRIORITY:]]) + await test_detach() + await test_multiple_lo_indirectly_escalated() + } +} + @available(SwiftStdlib 5.5, *) func test_detach() async { let a1 = Task.currentPriority - print("a1: \(a1)") // CHECK: TaskPriority(rawValue: 25) + print("a1: \(a1)") // CHECK: a1: TaskPriority(rawValue: [[#MAIN_PRIORITY]]) // Note: remember to detach using a higher priority, otherwise a lower one // might be escalated by the get() and we could see `default` in the detached @@ -30,8 +39,15 @@ func test_detach() async { print("a2: \(a2)") // CHECK: a2: TaskPriority(rawValue: 25) }.get() - let a3 = Task.currentPriority - print("a3: \(a3)") // CHECK: a3: TaskPriority(rawValue: 25) + await detach(priority: .default) { + let a3 = Task.currentPriority + // The priority of 'a3' may either be 21 (default) or elevated to that of + // the main function, whichever is greater. + print("a3: \(a3)") // CHECK: a3: TaskPriority(rawValue: [[#max(MAIN_PRIORITY,21)]] + }.get() + + let a4 = Task.currentPriority + print("a4: \(a4)") // CHECK: a4: TaskPriority(rawValue: [[#MAIN_PRIORITY]]) } @available(SwiftStdlib 5.5, *) @@ -66,10 +82,4 @@ func test_multiple_lo_indirectly_escalated() async { print("default done") // CHECK: default done } -@available(SwiftStdlib 5.5, *) -@main struct Main { - static func main() async { - await test_detach() - await test_multiple_lo_indirectly_escalated() - } -} + From bb80f9f329ea50c42bbd40b84937718dc77ab944 Mon Sep 17 00:00:00 2001 From: Evan Wilde Date: Mon, 4 Oct 2021 09:34:17 -0700 Subject: [PATCH 6/6] Replace reinterpret cast with struct A single-element struct is structurally the same as the member type itself. --- lib/SILGen/SILGenFunction.cpp | 25 ++++++++----------------- test/Concurrency/async_main.swift | 4 ++-- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index 0992e7f7e1eb1..19f8cc553986c 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -868,23 +868,14 @@ void SILGenFunction::emitAsyncMainThreadStart(SILDeclRef entryPoint) { B.setInsertionPoint(entryBlock); - /// Generates a reinterpret_cast for converting - /// Builtin.Job -> UnownedJob - /// Builtin.Executor -> UnownedSerialExecutor - /// These are used by _swiftJobRun, which, ABI-wise, could take - /// Builtin.Job or Builtin.Executor, but doesn't. - auto createExplodyCastForCall = - [this, &moduleLoc](SILValue originalValue, FuncDecl *jobRunFuncDecl, - uint32_t paramIndex) -> SILValue { - // The type coming from the _swiftJobRun function - Type apiType = jobRunFuncDecl->getParameters()->get(paramIndex)->getType(); - SILType apiSILType = - SILType::getPrimitiveObjectType(apiType->getCanonicalType()); + auto wrapCallArgs = [this, &moduleLoc](SILValue originalValue, FuncDecl *fd, + uint32_t paramIndex) -> SILValue { + Type parameterType = fd->getParameters()->get(paramIndex)->getType(); + SILType paramSILType = SILType::getPrimitiveObjectType(parameterType->getCanonicalType()); // If the types are the same, we don't need to do anything! - if (apiSILType == originalValue->getType()) + if (paramSILType == originalValue->getType()) return originalValue; - return this->B.createUncheckedReinterpretCast(moduleLoc, originalValue, - apiSILType); + return this->B.createStruct(moduleLoc, paramSILType, originalValue); }; // Call CreateAsyncTask @@ -929,7 +920,7 @@ void SILGenFunction::emitAsyncMainThreadStart(SILDeclRef entryPoint) { moduleLoc, ctx.getIdentifier(getBuiltinName(BuiltinValueKind::ConvertTaskToJob)), JobType, {}, {task}); - jobResult = createExplodyCastForCall(jobResult, swiftJobRunFuncDecl, 0); + jobResult = wrapCallArgs(jobResult, swiftJobRunFuncDecl, 0); // Get main executor FuncDecl *getMainExecutorFuncDecl = SGM.getGetMainExecutor(); @@ -939,7 +930,7 @@ void SILGenFunction::emitAsyncMainThreadStart(SILDeclRef entryPoint) { SILValue getMainExeutorFunc = B.createFunctionRefFor(moduleLoc, getMainExeutorSILFunc); SILValue mainExecutor = B.createApply(moduleLoc, getMainExeutorFunc, {}, {}); - mainExecutor = createExplodyCastForCall(mainExecutor, swiftJobRunFuncDecl, 1); + mainExecutor = wrapCallArgs(mainExecutor, swiftJobRunFuncDecl, 1); // Run first part synchronously B.createApply(moduleLoc, swiftJobRunFunc, {}, {jobResult, mainExecutor}); diff --git a/test/Concurrency/async_main.swift b/test/Concurrency/async_main.swift index 8bb08ff20d0ea..8001fdc8f2843 100644 --- a/test/Concurrency/async_main.swift +++ b/test/Concurrency/async_main.swift @@ -69,11 +69,11 @@ func asyncFunc() async { // CHECK-SIL-NEXT: // function_ref swift_job_run // CHECK-SIL-NEXT: %12 = function_ref @swift_job_run : $@convention(thin) (UnownedJob, UnownedSerialExecutor) -> () // CHECK-SIL-NEXT: %13 = builtin "convertTaskToJob"(%11 : $Builtin.NativeObject) : $Builtin.Job -// CHECK-SIL-NEXT: %14 = unchecked_trivial_bit_cast %13 : $Builtin.Job to $UnownedJob +// CHECK-SIL-NEXT: %14 = struct $UnownedJob (%13 : $Builtin.Job) // CHECK-SIL-NEXT: // function_ref swift_task_getMainExecutor // CHECK-SIL-NEXT: %15 = function_ref @swift_task_getMainExecutor : $@convention(thin) () -> Builtin.Executor // CHECK-SIL-NEXT: %16 = apply %15() : $@convention(thin) () -> Builtin.Executor -// CHECK-SIL-NEXT: %17 = unchecked_trivial_bit_cast %16 : $Builtin.Executor to $UnownedSerialExecutor +// CHECK-SIL-NEXT: %17 = struct $UnownedSerialExecutor (%16 : $Builtin.Executor) // CHECK-SIL-NEXT: %18 = apply %12(%14, %17) : $@convention(thin) (UnownedJob, UnownedSerialExecutor) -> () // CHECK-SIL-NEXT: // function_ref swift_task_asyncMainDrainQueue // CHECK-SIL-NEXT: %19 = function_ref @swift_task_asyncMainDrainQueue : $@convention(thin) () -> Never