Skip to content
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

Effect handlers #1340

Merged
merged 26 commits into from
Dec 15, 2022
Merged

Effect handlers #1340

merged 26 commits into from
Dec 15, 2022

Conversation

vouillon
Copy link
Member

This pull request adds support for effect handlers. This is a joint work with Olivier Nicole (@OlivierNicole) based on previous work from Armaël Guéneau (@Armael) and Patrick Ferris (@patricoferris).

The implementation is based on a CPS transform of the whole code. This a thorough implementation. In particular, you can perform effects from within the OCaml toplevel in a browser.

The graph below shows the performance impact on some short programs from the Js_of_ocaml benchmark. Short recursive functions that did not perform any allocation, such as the Fibonacci function, become much slower (up to 9 time slower). For other benchmarks, we are more between 1.5 and 2 times slower. Interestingly, code that makes heavy use of exceptions can become faster (boyer).
time

The code size is larger, within a factor 2.5. For a much larger example, the Ocsigen Start demo, the generated code is 80% larger. But only 26% larger when compressed with bzip2 (we are adding a lot of function and return that compress well).
size

We hope to be able to reduce the performance gap by performing a partial CPS transform based on a static analysis to detect code that does not perform any effects.

The compatibility with existing code should be rather high. However, the calling convention mismatch between JavaScript and OCaml is now larger since OCaml functions take a continuation as additional parameter. So, it is no longer possible to call a JavaScript function as if it was an OCaml function (and conversely). When calling a JavaScript from OCaml, you have to use Js.Unsafe.call. For using an OCaml function from JavaScript, you have to properly wrap it using Js.wrap_callback or one of the Js.Unsafe.callback variants.

@TyOverby
Copy link
Collaborator

@pmwhite is looking at running Jane Streets codebase under this patch

@TyOverby
Copy link
Collaborator

In the past, large applications have demonstrated surprising performance charactaristics when compiled with JSOO, like this issue where testing a gameboy emulator showed some scenarios where inlining hurt runtime performance. I wonder if it might be worth it to build and benchmark that gameboy emulator with these changes in order to see how it performs on larger programs.

@dbuenzli dbuenzli mentioned this pull request Nov 30, 2022
3 tasks
vouillon added a commit to vouillon/brr that referenced this pull request Dec 1, 2022
This is ncessary to deal with the calling conventin mismatch introduced
by the upcoming effect support (ocsigen/js_of_ocaml#1340)
vouillon added a commit to vouillon/brr that referenced this pull request Dec 1, 2022
This is ncessary to deal with the calling conventin mismatch introduced
by the upcoming effect support (ocsigen/js_of_ocaml#1340)
vouillon added a commit to vouillon/brr that referenced this pull request Dec 1, 2022
This is necessary to deal with the calling convention mismatch
introduced by the upcoming effect support (ocsigen/js_of_ocaml#1340)
@vouillon
Copy link
Member Author

vouillon commented Dec 1, 2022

In the past, large applications have demonstrated surprising performance charactaristics when compiled with JSOO, like this issue where testing a gameboy emulator showed some scenarios where inlining hurt runtime performance. I wonder if it might be worth it to build and benchmark that gameboy emulator with these changes in order to see how it performs on larger programs.

That's a good idea, indeed. It is about 7 times slower in Chrome. I guess that should be expected for this kind of code.

With support for effect handlers:

ROM path: ./tobu.gb
  Frames: 1500
Duration: 13.999800
     FPS: 107.144388

Without:

ROM path: ./tobu.gb
  Frames: 1500
Duration: 2.048000
     FPS: 732.421875

Interestingly, the generated code is only 30% larger (5% larger when compressed). So, it is possible that we can do way better for the Ocsigen Start demo I mentioned above (the lambda lifting phase currently generates a few functions with thousands of parameters, which might well have a large impact on code size...)

runtime/jslib.js Show resolved Hide resolved
compiler/lib/config.ml Outdated Show resolved Hide resolved
compiler/lib/specialize.ml Outdated Show resolved Hide resolved
compiler/lib/code.mli Outdated Show resolved Hide resolved
runtime/jslib.js Show resolved Hide resolved
compiler/lib/effects.ml Outdated Show resolved Hide resolved
@hhugo
Copy link
Member

hhugo commented Dec 2, 2022

The implementation is based on a CPS transform of the whole code.

What's the story for separate compilation and dynlink ? I wonder if we should have the jsoo linker check that all js files have been compiled with compatible options. (use-js-string and effects)

@hhugo
Copy link
Member

hhugo commented Dec 2, 2022

Iv'e done a first pass of review without really looking at the commit implementing effects yet.

compiler/lib/generate.ml Outdated Show resolved Hide resolved
compiler/lib/generate.ml Outdated Show resolved Hide resolved
compiler/lib/generate.ml Outdated Show resolved Hide resolved
compiler/lib/code.ml Outdated Show resolved Hide resolved
@hhugo
Copy link
Member

hhugo commented Dec 2, 2022

Can you include the benchmark changes used to generate the graphs.

vouillon and others added 16 commits December 15, 2022 14:53
… tests

We are doing a lot of generated code comparison. I'm not sure it is
worth it to change the tests.
This is a joint work with Olivier Nicole (@OlivierNicole) based on
previous work from Armaël Guéneau (@Armael) and Patrick Ferris
(@patricoferris), with some help from Hugo Heuzard (@hhugo)
With 'make graphseff'
…osition

No longer do this while parsing the bytecode. This is more robust, since
the optimization phases do not have to preserve this invariant. This is
also more efficient, since do not have to deal with so many blocks
during optimization phases.
@vouillon
Copy link
Member Author

Ok, I'm pretty happy with the state of this PR. I think ti's ready to be merged. I'm not sure what to do with the history. It doesn't have to be perfect but a little bit of cleanup could be good.

@hhugo I have cleaned up the history. Thanks a lot for your review and all the work you put into it. That was tremendously useful!

@hhugo hhugo merged commit 3a615d6 into master Dec 15, 2022
@hhugo hhugo deleted the effects branch December 15, 2022 14:56
@hhugo hhugo mentioned this pull request Dec 15, 2022
@hhugo
Copy link
Member

hhugo commented Dec 15, 2022

Thanks everyone for the work!!!

@hhugo
Copy link
Member

hhugo commented Dec 15, 2022

@vouillon, how is your effects-optim branch progressing. Do you think we should wait for it before the next release ?

@vouillon
Copy link
Member Author

@hhugo It probably still needs several weeks of work. So I don't think we should wait for it.

@hhugo
Copy link
Member

hhugo commented Dec 16, 2022

@vouillon, changing the baseline of lambda_lifting from 1 to 2 seem to reduce the amount of free variable quiet a bit.
Compiling ocamlc, the size goes from 3M to 2.7M

Using es6 lambda could reduce the size (uncompressed) a bit
(function (x) { return ... }) => (x => ...)

@vouillon
Copy link
Member Author

@vouillon, changing the baseline of lambda_lifting from 1 to 2 seem to reduce the amount of free variable quiet a bit. Compiling ocamlc, the size goes from 3M to 2.7M

Well spotted! See #1360 for an alternative fix.

Using es6 lambda could reduce the size (uncompressed) a bit (function (x) { return ... }) => (x => ...)

Yes, indeed.

@hhugo
Copy link
Member

hhugo commented Dec 16, 2022

Using es6 lambda could reduce the size (uncompressed) a bit
(function (x) { return ... }) => (x => ...)

I did a quick experiment, ocamlc compiled with effects goes from 2.7M to ~2.4M.

nojb pushed a commit to LexiFi/gen_js_api that referenced this pull request Mar 10, 2023
To support effect handlers, we are changing the calling convention of
OCaml functions. See ocsigen/js_of_ocaml#1340.
Wrapping Ocaml functions is now mandatory to use them from JavaScript.
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.

6 participants