11/11/2021

WASM — The possibilities of "Bring your own language"

Exploring the ways WASM can replace domain-specific languages


One of the most exciting WASM projects I have seen lately is Lunatic, "an Erlang inspired runtime for WebAssembly".

In essence, you get the actor model runtime like that of Erlang's BEAM, but you can use it with any language that compiles to WASM. This is significant because in the past, if you wanted the actor model, you had to learn Erlang or Elixir. Now you can use any language that compiles to WASM, so if the actor-model is something that makes sense for your usecase, you can use it with low barrier to entry.

This Bring Your Own Language (BYOL) approach is something we're going to be seeing a lot in the future, and one of the most exciting possibilites for WASM.

It feels like WASM is capturing the true ethos of the Java/JVM in the '90s, in the sense that it's truly going to be the way to write programs that can run anywhere, and it takes that idea one notch further by allowing you to write those same programs in any language.

The possibilities

It feels like WASM can be the general replacement for Domain-Specific languages (DSLs). You can support any language, and leverage the safety and security WASM was designed with. There are some really exciting opportunities to explore here.

Scripting/plugins is the first that comes to mind. In the past to support scripting/plugins you had to either implement your own Domain-Specific Language (examples include Emacs LiSP, VimScript), or use an existing scripting language (e.g. Lua). With WASM you can let users create plugins in any language that compiles to WASM.

This extends to smart contracts, instead of rolling out your own VM and language — like Ethereum did with the EVM and Solidity — you can allow users to write smart contracts with any language with a WASM compilation target. This is particularly important here, because WASM is designed with security and safety in mind.

Caveats

There are some drawbacks to using WASM as a replacement for a DSL, and they mostly are a result of WASM's relative infancy.

1. Language Restrictions

The first is that you are restricted to languages that support WASM as a compilation target. This isn't too terrible, because LLVM supports WASM so you do get support for languages like Rust, Zig, Swift, along with regularly compiled languages like C/C++ or Go.

This does eliminate interpreted languages from the pool (e.g. JavaScript, Python, Java, etc.), but you can get around this by compiling a language's interpreter itself to WASM. For example, to support JavaScript in WASM you could use quickjs-emscripten. And perhaps if your language runs on a bytecode VM, you could theoretically translate bytecode instructions to WASM, and stub in some of the runtime features.

2. Interop

You can only speak to WASM by passing value types, which are 32-64 bit ints or floats. This means that if you want to pass in more complex data, you have to write its binary data into the WASM module's memory buffer.

This is terrible for ergonomics, even to pass in something simple like a string, you would have to write an array of char codes into WASM's memory. You don't want your end users doing this, so you need to provide a library to abstract or hide away this "glue" code, which means you now need to write a library for every language you support.

An example of this is wasm-bindgen which uses a combination of Rust's procedural macros and WASM imports to allow the frictionless passing of data from JS <-> WASM.

This is a deal breaker and kind of defeats the versatility of WASM. The upcoming solution to this problem is the WASM Interface Types proposal, which allows you to define interface adapters.


Got any questions? Did I say something completely bogus and wrong? Hit me up on Twitter.