Skip to content

Concurrency: fix inconsistent _asyncLet_get signatures #4288

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 1 commit into from
Feb 21, 2022

Conversation

kateinoigakukun
Copy link
Member

@kateinoigakukun kateinoigakukun commented Feb 20, 2022

Resolve #4250

How did I find the root issue (memo)

Given this snippet,

func getX() async -> Int { return 0 }

func yay() async {
  async let x = getX()
  _ = await x
}

Run $ swiftc -emit-ir -Xfrontend -disable-swift-specific-llvm-optzns -parse-as-library test.swift -o test.ll to check LLVM IR before CoroSplit pass. Then, it outputs the below instruction.

define hidden swifttailcc void @"$s4test10EntrypointV3yayyyYaFZ"(%swift.context* swiftasync %0) #0 {
entry:
  ...
  %21 = call { i8*, i8* } (i32, i8*, i8*, ...) @llvm.coro.suspend.async.sl_p0i8p0i8s(
    i32 0,
    i8* %18,
    i8* bitcast (i8* (i8*)* @__swift_async_resume_get_context to i8*),
    i8* bitcast (void (i8*, %swift.context*, i8*, i8*, i8*, %swift.context*)* @__swift_suspend_dispatch_5 to i8*),
    i8* bitcast (void (%swift.context*, i8*, i8*, i8*, %swift.context*)* @swift_asyncLet_get to i8*),
    %swift.context* %19,
    i8* %16,
    i8* %11,
    i8* %18,
    %swift.context* %20
  )
  ...
}

@llvm.coro.suspend.async is an LLVM intrinsic which indicates a suspension point. The CoroSplit pass splits a function into several resumption functions at the suspension points. The generated function takes the value returned by the function passed to the suspension asynchronously. ({ i8*, i8* }) Note that the asynchronously returned type is not the type returned synchronously. swift_asyncLet_get returns nothing synchronously, but it passes two i8* to the resumption function, so llvm.coro.suspend.async's result type is declared as { i8*, i8* }.

In the above example, the function and the resumption function generated from the suspension point would be transformed by CoroSplit pass:

define hidden swifttailcc void @"$s4test10EntrypointV3yayyyYaFZ"(%swift.context* swiftasync %0) #0 {
entry:
  ...
  musttail call swifttailcc void @swift_asyncLet_get(%swift.context* swiftasync %16, i8* %14, i8* %9, i8* bitcast (void (i8*)* @"$s4test10EntrypointVAAyyYaFZTY1_" to i8*), %swift.context* %17) #1
  ...
}

define internal swiftcc void @"$s4test10EntrypointVAAyyYaFZTY1_"(i8* swiftasync %0, i8* %1) #0 {
entryresume.1:
  ...
}

As I said before, the resumption function (s4test10EntrypointVAAyyYaFZTY1_) takes two parameters.
However, swift_asyncLet_get passes only an async context, so it crashes on wasm due to signature mismatch.

So, who determines the async result type of swift_asyncLet_get? The result type is computed by SignatureExpansion::expandAsyncAwaitType and its input is the intrinsic function declaration at stdlib. The Swift level declaration says that it returns async context and a pointer asynchronously, but its implementation returns only async context. That's the mismatch.

Copy link

@MaxDesiatov MaxDesiatov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@kateinoigakukun kateinoigakukun marked this pull request as ready for review February 21, 2022 04:49
@kateinoigakukun kateinoigakukun merged commit e337ef3 into swiftwasm Feb 21, 2022
@kateinoigakukun kateinoigakukun deleted the katei/fix-asynclet-get-sigs branch February 21, 2022 04:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Runtime crash when using async let variables
2 participants