Async tail-call optimization v1
commit403efec3f40f543d958dbfd085a254b844c6ccdf
authorShaunak Kishore <kshaunak@fb.com>
Tue, 24 Mar 2020 19:14:34 +0000 (24 12:14 -0700)
committerFacebook GitHub Bot <facebook-github-bot@users.noreply.github.com>
Tue, 24 Mar 2020 19:34:00 +0000 (24 12:34 -0700)
tree50fba698bbe2d8703d5ace6807eb142863ce9b1f
parenta4e4abb3706726680a7df8d11e04485dddf29ad5
Async tail-call optimization v1

Summary:
The goal of this optimization is to avoid creating new AsyncFunctionWaitHandles (AFWH) for awaits in "tail position": return await ...

If we can make this optimization, it will be a large CPU and memory win. It's tricky, though.

It's easy to identify tail awaits. Right now, I just do some simple forward analysis in HHVM, but I'll move that into HHBBC's DCE phase. It will take a bit more time to get that done, and we want to land the win earlier, so I'm putting up this version now.

The tricky part is maintaining profiling / back-tracing behavior while eliding these tail calls. One implementation of this optimization passes the AFWH to the caller instead of awaiting, but this version breaks both profiling and back-tracing completely. Dealing with profiling done through surprise checks is easy: we simply do the check before doing the optimization.

For back-tracing, we play a trick: we compress the information we need about the frame into a 2-byte ID, and we store a list of these IDs in the AFWH. When suspending, we have to do a bit more work before returning: specifically, we have to check that the incoming wait handle is an AFWH, that it we "own" it (i.e. it has few references - the correct number to check happens to be 2 - the parent and child), and that it has space in its tail frame IDs slot. AFWHPushTailFrame does this logic.

When back-tracing, to find the previous frame from an AFWH frame, we check if it has tail frames, and if so, we fall into a sub-loop using fake ActRecs, similar to inlining.

There are many follow-ups to try, which I'll get to next:
1. We can merge the "is owned AFWH" check in AFWHPushTailFrame with the state check that we do before it, saving a branch.
2. We can introduce a new AsyncTailCallWaitHandle and use that for cases where we can't merge tail frames with the existing handle.
3. We can add support for a post-await VerifyRetTypeC and handle more cases that way.

Reviewed By: jano

Differential Revision: D20589894

fbshipit-source-id: 6d6c15857b20b1f9c0de32d2620337ca7c6b52b4
17 files changed:
hphp/doc/ir.specification
hphp/runtime/base/backtrace-inl.h
hphp/runtime/base/backtrace.cpp
hphp/runtime/ext/asio/ext_async-function-wait-handle.cpp
hphp/runtime/ext/asio/ext_async-function-wait-handle.h
hphp/runtime/vm/act-rec-inl.h
hphp/runtime/vm/act-rec.h
hphp/runtime/vm/event-hook.cpp
hphp/runtime/vm/jit/dce.cpp
hphp/runtime/vm/jit/ir-opcode.cpp
hphp/runtime/vm/jit/irgen-resumable.cpp
hphp/runtime/vm/jit/irlower-resumable.cpp
hphp/runtime/vm/jit/memory-effects.cpp
hphp/runtime/vm/jit/unique-stubs.cpp
hphp/runtime/vm/unwind.cpp
hphp/test/slow/async/tail-calls.php [new file with mode: 0644]
hphp/test/slow/async/tail-calls.php.expectf [new file with mode: 0644]