Implement a test framework and test case for running lowered coroutines with HHVM
Summary:
= run_hhvm.py
First off, let's talk about the `run_hhvm.py` tool. This is used in unit tests, but it's quite useful as a standalone tool.
```
$ buck run mode/dbg hphp/hack/test/facebook/coroutine:run_hhvm -- --help
Performs the HHVM execution pipeline for coroutine source files.
Given a source file using the coroutines feature and an HHI directory of the
coroutine standard library, performs the following pipeline:
1. Concatenate the HHI sources with test_path source.
2. Lower the combined source.
3. Compile the lowered combined source into hhas.
4. Execute the hhas using HHVM.
5. Print out the result.
positional arguments:
test_path
optional arguments:
-h, --help show this help message and exit
--compiler-program COMPILER_PROGRAM
Path to compiler program executable. (default:
/data/users/tingley/fbsource/fbcode/buck-out/bin/hphp/
hack/src/hh_single_compile/hh_single_compile.opt)
--coroutine-program COROUTINE_PROGRAM
Path to coroutine program executable. (default:
/data/users/tingley/fbsource/fbcode/buck-out/bin/hphp/
hack/test/facebook/coroutine/coroutine/coroutine.opt)
--hhi-dir HHI_DIR Path to (coroutines) hhi directory. (default: /data/us
ers/tingley/fbsource/fbcode/hphp/hack/hhi/coroutine)
--hhvm-program HHVM_PROGRAM
Path to hhvm program executable. (default:
/data/users/tingley/fbsource/fbcode/buck-
out/gen/hphp/hhvm/hhvm/hhvm)
--verbose
```
You'll notice that the default values for the external programs are all configured reasonably. That means you can lower a coroutine source file and it will "just work":
$ buck run mode/dbg hphp/hack/test/facebook/coroutine:run_hhvm -- $test_file
It will pull in the HHI coroutine files, combine the sources, lowered coroutines from the combined sources, compile the resulting source into hhas using Hack's `hh_single_compile`r, run the hhas using HHVM, and output the result of the execution.
- If there are errors during any of these steps, they will be output.
- It is **very** useful to add the `--verbose` flag to this script. That will cause it to output the paths of all of the intermediate representations. This way, you can manually inspect the lowered and combined source, or, if you're extremely masochistic, the compiled hhas.
- The script is modular, so you can plug in different schemes to each part. You can pull it more or fewer HHI files. You can change out the HHVM runtime, so that you could use the production HHVM runtime, instead of an internally-built version. You could even change out the compiler (source --> hhas) or coroutine lowerer (source --> lowered source), although we don't yet have good substitutes for these.
- It's worth noting that HHVM's compiler **does not** handle some of our lowered code correctly for compilation! So if we took lowered code and sent that to HHVM, it would not like all of it. For example, it rejects jumping into or out of loops, while `hh_single_compile` allows this.
- It's also worth noting that the production HHVM runtime doesn't like our lowered hhas. I have a big comment in `run_hhvm.py` about this, which also explains why it makes me (and our build times) unhappy. This has caused me to mark the test with `tags = ['slow']`. You can read more about what this does in the comment that I've added to the TARGETS file.
= sequence_sample.php
Now, let's talk about the test sample. I ported this from ericlippert's diff. This is a very basic `SequenceBuilder`, which yields two strings, `First` and `Second`, and prints them.
Some notes on organization:
1. `coroutine/classes.hhi` has been expanded, and contains the classes related to the coroutine standard library. Framework developers should use these utilities to build coroutine-utilizing frameworks.
2. `sequence_sample.php` contains code specific to the `IteratorCoroutine`. It is a customer of the coroutines feature. This code is purely test code, so no one will ever get to use it, except for our tests!
There are many, many TODOs here. Let's go through some of them:
1. **`SafeContinuation`.** `SafeContinuation` is **not** included in this diff. I had some questions about it, that I will isolate in another diff.
2. Since `SafeContinuation` isn't in this diff, the following are not, either:
1. `CreateCoroutine::withReceiver`
2. `SuspendCoroutine::suspend`
3. Since `SafeContinuation` isn't in this diff, the customer is required to use `CreateCoroutine::uncheckedWithReceiver` and `SuspendCoroutine::suspendUnchecked`.
4. **Resugaring.** Some things have been resugared. Some things have not. Why? Basically, there are lots and lots of places where we would like to write `coroutine function (Arg): Result`, as a type annotation. However, we don't desugar type annotations! Instead, I've written such an annotation out in full-form as `function (CoroutineContinuation<Result>, Arg): CoroutineResult<Result>`.
5. As an interesting consequence to the previous point, we have some very interesting situations where we are **`suspend`ing** on a non-coroutine function! We can `suspend` on an ordinary function, and the compiler will do the rewriting as appropriate. Since the function //just so happens// to have been manually desugared to the right type, this will both type check and execute correctly. Unfortunately, this only works because we're not nesting lots and lots of coroutines. This is a huge limitation, and therefore will be the thing that I work on next!
---
Reviewed By: ericlippert
Differential Revision:
D5178566
fbshipit-source-id:
6c7abede26ca6d249b443b0f211f308dcf5d86b5