State of OCaml and web assembly

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:

This is a build script for building the OCaml bytecode interpreter for WebAssembly using emscripten.

An OCaml library to read and write Web Assembly (wasm) files and manipulate their AST.

From WebAssembly to Native Code via the OCaml Backend.

A fork of the ocaml compiler with a web assembly compile target.

Grain is a language built for the modern web by leveraging the brilliant work done by the WebAssembly project.

While grain is not actually OCaml it seems to be a fork of the OCaml codebase and thus worthy of mention.

11 Likes

This is actually the reference implementation of the interpreter/encoder/decoder/validator. Repo: https://github.com/WebAssembly/spec

1 Like

@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.

2 Likes

@gasche I see, thanks for the clarity.

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.

6 Likes

Btw. the correct branch is:

4 Likes

(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.

1 Like

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.

4 Likes

Wait, what? So e.g. in Rust any allocated memory is not returned to the system even after Rust deallocates it?

1 Like

Correct, see: Wasm needs a better memory management story · Issue #1397 · WebAssembly/design · GitHub and Freeing/Shrinking memory · Issue #1300 · WebAssembly/design · GitHub

4 Likes

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.

Shachar’s ocaml-wasm works very well, at least for our use case which is a reasonably complex OCaml program [Coq]

2 Likes

So you’re actively using coq via ocaml-wasm? :flushed: Is that setup public, do you have any writeups of the experience, etc?

1 Like

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:

It literally flies.

I guess @corwin-of-amber is the right person to comment more on his superb efforts.

4 Likes

Hi there @camarick; ocaml-wasm is very much bleeding-edge but it already works surprisingly well and I have used it to run Coq, esp. for the purpose of making the interactive version of Vols. I,II from the Software Foundations textbook (see https://jscoq.github.io/ext/sf and https://jscoq.github.io/ext/sf/tools/jscoq-tester.html).

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… :man_facepalming: ). 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!

4 Likes

Thanks for the details.

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.
1 Like

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.