-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Implementing 0323 async main #39607
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
Implementing 0323 async main #39607
Conversation
Now that SILDeclRef can represent the main function, tweak TBDGen to refer to it.
Allow SILDeclRef to refer to the main program entry-point, which will either be for a main SourceFile, or a synthetic main such as an `@main` decl. Adjust the various SILDeclRef related functions to handle this new case, and change the emission to go through `emitFunctionDefinition`. This change will allow the entry-point for an `@main` decl (and eventually a main SourceFile) to be emitted on-demand from its symbol name.
This will be used to represent the entry-point for a main SourceFile.
This will make it easier to store in a SILDeclRef.
The lookupConcurrencyIntrinsic function only looked in the concurrency module. It is useful to look in other modules for intrinsics too.
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.
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.
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.
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.
A single-element struct is structurally the same as the member type itself.
This patch forces the main function to be protected behind MainActor isolation. If no actor isolation is specified, the main function will implicitly have MainActor isolation.
Putting the main function under the MainActor appears to have accidentally "fixed" the resilience tests on Windows. I've put the entry to those tests under a separate task to try and avoid accidentally hiding the bug behind the main actor.
As per the C++14 spec, $ can't be used in a portable way in an identifier name. It falls under the implementation-defined characters and is allowed by clang. Removing it.
Adding new async semantics to Swift 5.5
5.5 doesn't have any `get*Type()` interfaces, so have to get the types through some other means.
@swift-ci please test |
Build failed |
Windows appears to be missing libxml2:
Linux is crashing while emitting the asynchronous Failing test: Driver/static-stdlib-autolink-linux.swift
|
@swift-ci please test Linux platform |
@swift-ci please test Windows platform |
Pretty sure this isn't my fault. |
@swift-ci please test windows platform |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍🏼 Looks good
@swift-ci please nominate |
Cherry-picking #38604, #39503, and #39593, and applying necessary changes to make up for missing
This change relies on the
SwiftDeclRef::Kind::EntryPoint
created in #37014.This implements the semantic changes described in swiftlang/swift-evolution#1437, Async Main Semantics.
MainActor
isolation rules applied and is run synchronously up to the first suspension point.