I spent some time studying various ways the OCaml compiler can produce web assembly and interpret or depend on other web assembly libraries. The landscape is quite confusing. Does anyone have experience with OCaml and web assembly? Is there a reliable project to bet on and start developing with? What about interop with other libraries that compile to wasm and you want to reference in OCaml? Here’s a list of the projects I have discovered which seem relevant. Would appreciate insights on whether they are being maintained, or if anybody has noteworthy experience with them:
@ostera Does this imply, as least in theory, you could use the forked ocaml compiler that targets web assembly in combination with the wasm ocaml reference implementation to bind to existing arbitrary web assembly libraries?
I assume the answer is yes but it would be cumbersome without some ctypes-esque abstraction built on top of it.
@struktured: my understanding is that wasm, besides a reference parser/printer of wasm code, provides a wasm interpreter, that is a slow-ish implementation of the language. It does allow to do what you say (run a mix of OCaml and wasm code), but by interpreting the wasm fragments, which is probably not the approach you had in mind.
If you want to compile OCaml to wasm and link to wasm code and have both run on a fast (eg. jit-ed) environment, then the intepreter in wasm won’t be of use. The code-inspection aspect may be useful for some sort of binding generation. But the biggest building block you need is a solid webassembly backend for the OCaml compiler, and I don’t know how mature the existing one is.
For now I would recommend against using the wasm backend that I’m working on. It’s still a work in progress. The goal is definitely to merge with trunk at some point - but it’s not there yet.
The backend uses LLVM’s LLD to make it possible to link to other wasm object files. I’m definitely not an expert at making OCaml backends, so I wouldn’t be surprised if there are things that I’m doing that aren’t correct. I definitely try though (in spare time). Just recently I managed to correctly compile the stdlib to wasm without type errors, and actually run very basic applications.
However for now I would recommend against using OCaml when targeting webassembly as certain features: exceptions, tail calls, and gc are not yet supported. I have my eye on these features though, and will add support when these come available.
People who want to help out are of course welcome.
(I’m not sure if keeping this thread alive is better or worse than starting a fresh one in this forum, but since this is a low-traffic subject I opted for the former to keep things organized.)
In the spirtit of completeness, my recent reading on the subject led to two things not already mentioned up there:
Garbage collected languages can’t really target WASM until WASM feature proposal #16 is implemented. By the looks of related issues in the repo, built-in GC is probably still years away.
There’s a recent project called ocaml-wasm. It’s similar to ocamlrun-wasm above except that it’s still maintained. It’s a WASM version of the OCaml runtime, which I guess would interpret bytecode since the compiler itself can’t target WASM.
On the contrary, the fact that WebAssembly does not provide any GC makes it much easier for GC-based languages to be ported to it. Indeed, their runtimes do not have to be modified to accommodate any peculiarity of WebAssembly’s nonexistent GC; they can be ported as is (assuming they already support a 32-bit address space). For example, OCaml’s GC will work out of the box. The only issue is that it cannot give back memory to the host; but that is not specific to OCaml. That is a WebAssembly defect that plagues all the programming languages, be they garbage-collected or not.
This is a big reason why the most successful applications of WASM (like shopify’s third-party extension facility) demand the use of languages (like assemblyscript) that have fairly constrained runtimes that a typical e.g. OCaml or Javascript programmer would find daunting for many tasks.
Yup, we didn’t make it yet the “official” release, but it has been used by quite a few people to avoid lack of tail-call optimization is jsoo , live versions:
Of course @ejgallego is exaggerating when he says that it flies, it still runs OCaml bytecode in interpreted mode on top of the WASM JIT. Performance is pretty reasonable still, except in the case some intensive Coq tactics (in which case this is a third level of interpreter… ). The main gap right now is the standard libraries str, unix, and threads, for which I have compiled empty stubs, because dynamic loading of libraries in WASI is still immature. I have been able to compile num and it works correctly because it does not depend on anything else. I am currently investigating how to build zarith (which requires gmp) because Coq 8.13 depends on it.
So yeah, this is not at all the coveted WASM backend for ocamlc, but it’s one existing solution and you can hack on it right now. Any help or comments are welcome!
Could you expand on how you FFI with the browser APIs in that setting ? E.g. is there a reasonable path to get to use my favourite OCaml browser bindings ?
I am actually not doing anything fancy and just use what the WebAssembly runtime in the browser provides. The JS side can interact with the WASM side in two main ways: imports and exports.
Exports are C functions that are made visible from the WASM instance. Once the WASM is loaded (“instantiated”) they can be called like any JS function in the form wasm.exports.func(...args). In ocamlrun, the runtime API is exported. So I can call e.g. caml_alloc_string, caml_callback, etc. (see here). It is a tad low-level, but usually you just call OCaml code once and then let it run until it exits.
Imports are the reverse: JS functions that you bind unto C symbols that the WASM executable expects to link against. The libc implementation is provided that way. What you can do in OCaml is define primitives and provide their implementation in JS when loading the WASM. Remember that your primitives will be called with value-typed arguments from the runtime API… you might have to use API calls (via wasm.exports) to interpret them.
So basically if I want to call say alert("hey!") from a WASM OCaml I need a C stub for crossing from OCaml to C and then a C import of JavaScript alert that the stub calls.
Unless we get a good generic representation of the JavaScript values and runtime in C to be able to boil down the process to a few generic primitives, that looks like a rather unconvenient prospect.
(Aside I spent quite a bit of time staring at the browser API standards last summer and it struck me that the IDL in which they are defined is actually much more C-like and precise than the JavaScript types they end up being mapped onto.
Some of the browsers APIs are not too bad and bring you interesting functionality. It could be nice to have them as C library APIs to code against for native OS functionality. If those could then be exposed directly in your WASM to represent and affect the browser functionality you would then effectively have bridged the gap between native and browser JavaScript apps. Hand-waves)
That was indeed what I did in wacoq-bin, but perhaps there is an easier way in which you would not have to write any C code. I will have to look more into how ocamlrun resolves primitives to figure out.