1
1
mirror of https://github.com/tweag/asterius.git synced 2024-09-21 05:48:04 +03:00
asterius/docs/rts-api.md
2019-06-20 18:30:08 +02:00

45 lines
4.5 KiB
Markdown

## Invoking RTS API in JavaScript
For the brave souls who prefer to play with raw pointers instead of syntactic sugar, it's possible to invoke RTS API directly in JavaScript. This grants us the ability to:
* Allocate memory, create and inspect Haskell closures on the heap.
* Trigger Haskell evaluation, then retrieve the results back into JavaScript.
* Use raw Cmm symbols to summon any function, not limited to the "foreign exported" ones.
Here is a simple example. Suppose we have a `Main.fact` function:
```Haskell
fact :: Int -> Int
fact 0 = 1
fact n = n * fact (n - 1)
```
The first step is ensuring `fact` is actually contained in the final WebAssembly binary produced by `ahc-link`. `ahc-link` performs aggressive dead-code elimination (or more precisely, live-code discovery) by starting from a set of "root symbols" (usually `Main_main_closure` which corresponds to `Main.main`), repeatedly traversing ASTs and including any discovered symbols. So if `Main.main` does not have a transitive dependency on `fact`, `fact` won't be included into the binary. In order to include `fact`, either use it in some way in `main`, or supply `--extra-root-symbol=Main_fact_closure` flag to `ahc-link` when compiling.
The next step is locating the pointer of `fact`. The "asterius instance" type we mentioned before contains two "symbol map" fields: `staticsSymbolMap` maps static data symbols to linear memory absolute addresses, and `functionSymbolMap` maps function symbols to WebAssembly function table indices. In this case, we can use `i.staticsSymbolMap.Main_fact_closure` as the pointer value of `Main_fact_closure`. For a Haskell top-level function, there're also pointers to the info table/entry function, but we don't need those two in this example.
Since we'd like to call `fact`, we need to apply it to an argument, build a thunk representing the result, then evaluate the thunk to WHNF and retrieve the result. Assuming we're passing `--asterius-instance-callback=i=>{ ... }` to `ahc-link`, in the callback body, we can use RTS API like this:
```JavaScript
i.exports.hs_init();
const argument = i.exports.rts_mkInt(5);
const thunk = i.exports.rts_apply(i.staticsSymbolMap.Main_fact_closure, argument);
const tid = i.exports.rts_eval(thunk);
console.log(i.exports.rts_getInt(i.exports.getTSOret(tid)));
```
A line-by-line explanation follows:
* As usual, the first step is calling `hs_init` to initialize the runtime.
* Assuming we'd like to calculate `fact 5`, we need to build an `Int` object which value is `5`. We can't directly pass the JavaScript `5`, instead we should call `rts_mkInt`, which properly allocates a heap object and sets up the info pointer of an `Int` value. When we need to pass a value of basic type (e.g. `Int`, `StablePtr`, etc), we should always call `rts_mk*` and use the returned pointers to the allocated heap object.
* Then we can apply `fact` to `5` by using `rts_apply`. It builds a thunk without triggering evaluation. If we are dealing with a curried multiple-arguments function, we should chain `rts_apply` repeatedly until we get a thunk representing the final result.
* Finally, we call `rts_eval`, which enters the runtime and perform all the evaluation for us. There are different types of evaluation functions:
* `rts_eval` evaluates a thunk of type `a` to WHNF.
* `rts_evalIO` evaluates the result of `IO a` to WHNF.
* `rts_evalLazyIO` evaluates `IO a`, without forcing the result to WHNF. It is also the default evaluator used by the runtime to run `Main.main`.
* All `rts_eval*` functions initiate a new Haskell thread for evaluation, and they return a thread ID. The thread ID is useful for inspecting whether or not evaluation succeeded and what the result is.
* If we need to retrieve the result back to JavaScript, we must pick an evaluator function which forces the result to WHNF. The `rts_get*` functions assume the objects are evaluated and won't trigger evaluation.
* Assuming we stored the thread ID to `tid`, we can use `getTSOret(tid)` to retrieve the result. The result is always a pointer to the Haskell heap, so additionally we need to use `rts_getInt` to retrieve the unboxed `Int` content to JavaScript.
Most users probably don't need to use RTS API manually, since the `foreign import`/`export` syntactic sugar and the `makeHaskellCallback` interface should be sufficient for typical use cases of Haskell/JavaScript interaction. Though it won't hurt to know what is hidden beneath the syntactic sugar, `foreign import`/`export` is implemented by automatically generating stub WebAssembly functions which calls RTS API for you.