Js_of_ocaml supports effect handlers with the --effects=cps flag. This is based on partially transforming the program to continuation-passing style. As a consequence, tail calls can be fully optimized (when CPS transformed).
This is not the default because the generated code can be slower, larger, and less readable. The transformation uses an analysis to detect parts of the code that cannot involve effects and keeps them in direct style. The analysis is especially effective on monomorphic code but less effective when higher-order functions are heavily used (Lwt, Async, Incremental).
An alternative CPS transform is provided with --effects=double-translation. It keeps a direct-style version of the transformed functions in addition to the CPS version. The choice of running the CPS version is delayed to run time.
Since CPS code is usually slower, this can avoid performance degradations. You can also ensure that some code runs in direct style using Jsoo_runtime.Effect.assume_no_perform.
Wasm_of_ocaml provides three effect handler implementations:
--effects=jspi (default) uses the JavaScript-Promise Integration extension. It does not require any code transformation but requires a runtime that supports JSPI.--effects=cps uses the same CPS transformation as js_of_ocaml.--effects=native uses the WebAssembly Stack Switching proposal (typed continuations). It provides the best performance but requires a runtime with support for the WasmFX extension (currently available, behind the --experimental-wasm-wasmfx flag, in Chrome 148 or higher, or in a recent Node.js canary release (V8 version 14.7.100 or higher)).As with C frames in the native OCaml runtime, an effect cannot be performed across a JavaScript frame. When OCaml code calls into JavaScript, which then calls back into OCaml (for instance through a callback registered with Js_of_ocaml.Js.wrap_callback), performing an effect inside the callback raises Effect.Unhandled, even if a handler is installed on the other side of the JavaScript frame. A handler installed inside the callback works as usual with --effects=cps and --effects=native.
With --effects=jspi, effect handlers cannot be used inside JavaScript callbacks at all: JSPI can only suspend Wasm code that runs on its own stack, which is the case for the main program and for fibers, but not for code entered from JavaScript through a callback. Installing a handler inside a callback (for instance with Effect.Deep.try_with) fails with a Failure "SuspendError: ..." exception. Use --effects=cps or --effects=native if callbacks need effect handlers.
Dune is aware of the --effects option. You can add it to the js_of_ocaml flags wherever you want to use it:
(js_of_ocaml (flags ...))
To enable effects for the entire workspace, add this to a dune or dune-workspace file at the root:
(env (_ (js_of_ocaml (flags (:standard --effects=double-translation)) (build_runtime_flags (:standard --effects=double-translation)))))
This setup supports both separate and whole program compilation.
To enable effect handlers for specific executables:
(executable (name main) (js_of_ocaml (flags (:standard --effects=double-translation))))
--effects modes