My Journey to Free WASM from my Browser
Speaker: Philippe Charrière
Summary
Dive into the world of WebAssembly (WASM) with Philippe Charrière and discover its potential to revolutionize web applications. Explore its integration with JavaScript, its ability to run outside the browser with WASI, and get hands-on with demos showcasing its capabilities.
Transcription
So I will speak about WASM outside my browser. I will explain why. My name is Philippe Charrière. As you can hear, I'm French. Prepare your ear to bleed. Sorry for that. I tried to lose my accent for years, but it's impossible. I'm a Customer Success Engineer at GitLab. Today, I won't speak about GitLab. It's not related. WASM is more of a passion and interest for me. I have a lot of side projects for that. Thank you to the Civo navigate team, because I'm very happy to be here, even if I'm a little bit stressed because I have to speak in English. And thank you for being here too. You can follow me with the curious handle @k33g_org on Twitter, it's the best way if you have any questions after the event.
So, WebAssembly, the nickname of which is WASM, is a binary format. It's designed to be executed on the web, inside the webpage. When I say "binary format," I mean it's a WASM file, like a JAR file for Java. The JavaScript virtual machine is responsible for executing a WASM program because the WASM runtime is inside the JavaScript virtual machine. WASM is polyglot, meaning you can develop WASM programs with Rust, C, C++, GRAN language, and others like AssemblyScript and Golang. We will have more and more languages that will be able to target WASM. And since the beginning, WASM is safe. That means the WASM program cannot do more than the security policies of the browser.
Why do WASM? WASM is not here to replace JavaScript. Some people are saying that, but it's false. It's a complement to JavaScript. Thanks to WASM, you can reach near-native speeds with your program. You can develop complex applications with high performance inside the web browser. Even now with WASM, you can do incredible things with your browser. I have two favorites example:
1. Google Earth: Google Earth lets you use it without a plugin inside your browser. I didn't install anything, you just need a recent browser, it's very fast, I can spend a day on it. I'm addicted to maps.
2. The web containers from StackBlitz. When you are on StackBlitz, you get a kind of web IDE - probably the same source code as the virtual source code. And I can run my CD samples like this. What's magic is that even if I cut the wifi, I can run my program. It's proof of what WASM is capable of. For example, you can now run AutoCAD inside your browser without installing anything.
How does WASM work? I've tried to make some nice sketches. The WASM module is loaded by the webpage, and the JavaScript engine handles the WASM runtime. This means you can develop a function like one in Golang and use it in JavaScript. But if you need to do more than compute, for example, display something in the console of the web navigator, you need to use a host function. This function, when exported, lets the WASM module use it to display something in the console. At the beginning, the WASM program was designed to only do compute. So, that's why normally you can't access outside the WASM module.
Now, the JavaScript integration with WASM is totally lovable. Especially with Rust, Go, C, C++, and AssemblyScript, every language provides developers and packages to develop easily. For example, this screenshot is Golang code. The Cisco JS package is provided by Golang. Then, you can create your own function. There is a JS value type. After, you just need to use the set method to export your function to the browser. But you can call a JavaScript function from the WASM module too. So, it's very easy to interact between the WASM file and the JS page. And it's the same thing with Rust, for example.
The primary qualities of WASM are speed, efficiency, memory footprint of a wasm file is very small. It's safe, versatile, and portable. Because of these qualities, people wanted to use WASM outside the browser. For that, there's a new specification, younger than WASM, named WASI (Web Assembly System Interface). It's a set of specifications and helpers to allow running WASM programs from an operating system or from an application.
The use cases of WASI are numerous: you can do CLI, you just need a runtime, you can create applications with plugins. So, the plugins are done with wasm. That means that you can use several languages to create a plugin for your application. For example, there is a ZEG. It's like tmx. But it's more fancy. You have LabCE. It's a source code editor. It's more Rust dedicated, but you can use it for other languages.
Some databases are starting to provide the ability to develop user-defined functions with WASM, like ScilabDB. But there is, I think a beta version, but you can already do it with PostgreSQL. Some companies like Webhook Relay allow you to create Webhooks in WASM so you are sure that it is sandboxed, and it's very important for security. You can develop filters for Envoy. Envoy is a proxy. Usually, you develop the filters with Lua, but since some months, you can do it with WASM.
And another use case is function as a service. For example, you have Fermyon that is providing a platform, Wasm Cloud 2, Shopify. In fact, Shopify provides a feature that allows you to run JavaScript inside the Wasm module. So you lose the speed, but you gain security and they are sure that their customers are not doing something bad inside the module. And there are other use cases and probably we will see a lot more use cases in the future.
So, even if the Wazi world is young, sometimes it's difficult to find your way. So I tried to do a map, an ugly map, because I have no talent for design. And I listed some territories on the map. So for me, the first territory is the WASI runtime territory. There are several projects, like WASMtime, WASMedge, WASMR, WASERO. Even Node.js is providing WASI runtime. Except for the Node Js, every runtime provides a CLI to run wasm applications and an SDK to create your own application. And your application will call wasm function or wasm plugin. You can call it as you want.
For the first demonstration, I did a simple program with golang. Is it readable for everyone? OK. So it's a very simple program. I just display a message. As you see, I love emojis. I get the arguments, I will wait for the sentence, and I will display something. For that, I use TinyGo. Because in fact, Golang cannot build a WASM file with the WASI specification. It's in progress, but right now if you want to use Golang to do WASI, you have to use TinyGo.
I already built it. So I can run it with WASMtime. WASMtime is a WASM runtime. You see, it's very easy to do. And I can change the runtime using Wasm Edge. And I can do it with all the Wasm Runtimes. Of course, the last one is with Node.js. You need to use the experimental flag, and you have to write a launcher with JavaScript. The launcher is very simple. The WASI package is already embedded inside Node.js. I create a WASI object to pass the arguments. Then I load the WASM file. I create an instance of the WASM runtime. And then I can start my WASM program. So this is this one. So you can have several runtimes with the same WASM module. And again, it's like when you use Java with a jar file.
So, creating a CLI application with WASM, compliant with WASI, is easy. But creating an application running WASM plugin is harder. Because, for example, when you start a new language, you want to do a lower word, but with WASM you cannot use a string as a function argument or as a return value of a function. So you need to use the SDK to write some helpers. You cannot access the overarching system, so you need to write some host functions. But if you use ten languages, you will need to use ten helpers. Today, there is no debug, so it's not easy to develop. This is in progress. The specification is very young.
So, if you want to create an application with WASM plugins, you need to develop all the plumbing with the SDK. I will try to explain the string issue. When I told my friends that I would speak in English in Florida, they told me to stay simple, not to do a demonstration, so I will do the opposite and we will see.
I cut the boring part, because the first time I had to understand how to do this, it took me several weeks. But now, I can reuse my helpers. So, when you create a WASM function, you think that you can do this. Just have a function with a string parameter returning a string. But with WASM, you cannot. With WASM, you can only use numbers, and you can only return one number, not several values.
So, to pass a string to a WASM function and to use the WASM function to return a string, you need to play with the WASM memory. The WASM module shares a buffer memory with the host. For that, if you want to create a function, a WASM function, and use it to pass a name, you need first to copy the name inside the memory. You will get the position and the size of the string. So you will use it as an argument for the WASM function.
Once you have that, the WASM function can read the memory because it has the position and the size. You can create your return value. In this case, you have again to put it inside the memory to return the position and the size of the string inside the memory. And you need to return this position and this size, but inside only one value. It's the moment where I was alone and I didn't know what was bit shifting. I have another talk to explain that, but it's worth noting that I did business school before computing. So, I have some lacks in low level programming. For me, it was a big discovery.
When you get the packed value, you will need to do bit masking to split the value and get the position and the size, read the memory, and get the return value. You don't have to do this every time. You just develop it once and then use it. The specification is in progress, so I hope soon we will have more complex types like strings, of course.
But, there is another territory, this is the WASI toolkits, and for that, the toolkits are doing the plumbing for you. You don't have to develop these weird things. And then, with the technical frameworks or toolkits, you can develop application plugins. For that, you have already ext. Microsoft is providing a very nice SDK. You have E2 core from Suborbital and other projects.
These projects are using the SDK runtimes. They use WASM time, WASM edge, or WASMER. E2 core which can use every WASM runtime. There is another territory. This is the host application framework. In this case, it's very opinionated because the framework is providing the host runtime application. Most of the time, it's a web application server and the WASM module is your web service. In this case, you only have to develop the plugins and not the application.
I will do another quick demo with Wasm Worker Server. It's a project from VMware Octo. It's a small database server. The Wasm workers are the Wasm model, and they're small microservices. We can call them nano services. You can run them almost everywhere. So, next demonstration.
This time with WasmWorker server, right now you can do plugins with Rust and Javascript, I think, and more in the future. So I did a very simple Rust code. In fact, you use the WasmWorker's library and you just have to create a web handler doing your payload, for example, and returning something to the HTTP client. I already built it, so I will serve it.
So in this case, I only have to develop the module. So now I just have to, where I am, nevermind.
You see, it's the beginning of a small function as a service, with only one function. But you can pop the server every time you want. For this kind of application, you have Spin from Fermyon. They are providing the same kind of thing, a web application server, but for Golang, Rust, Assembly Script, JavaScript, I think Python and PHP, but I'm not sure it's the same for E2 Core. For WASI asp server, it's only for C Sharp. And Capsule, it's my side project. It's only for Golang right now. And don't use it in production.
There is another territory. For me, it's the platform frameworks. Every project providing a host application framework most of the time provides a platform like Wasm Cloud from Cosmonautic, Extension Engine from Suborbital. So every platform is here to host the host application and the Wasm module. You have Fermyon Platform and Fermyon Cloud. Fermyon Platform is the on-premise version of Fermyon Cloud. So you can install it wherever you want, on a laptop, and it works perfectly.
And of course, the last territory is the application territory. It is everything you have in mind and you can develop with a lot of use cases. To be honest, my favorite territories are runtimes, even if my low-level programming skills are limited. I really like to use the runtimes, the runtime SDK, and the technical frameworks.
Then I will speak about my silver bullets to free WASM from the browser. I use Wasero and or Extism. Wasero is a runtime that is targeting only Golang applications. You can create a WASM module with the language you want, but for the host application, it's only with Go, and there is zero dependency.
I will show you a small demo. The source code will be provided, so I won't explain all the helpers. I explained it before with the string slides. It's a small Golang HTTP server. I did a WASM plugin with TinyGo, and the HTTP server will serve the WASM plugin. And all the plumbing was done before.
So, my web server is very simple. I will load the WASM module and start an HTTP server with Golang. It's provided by the NET HTTP package. I will trigger the http handler at every HTTP request. In my HTTP, I will read the pillar of the post request and instantiate WASM one time. The waszero was a one time. Then I will instantiate my WASM module, get the reference to my function, and copy inside the buffer memory the name I post with the post request. With that, I will be able to call the WASM function. As an argument, I will post the position and the size of the string. I will then read the memory again to get the position and size of the results and reply to the HTTP client.
The web function is simple. When I call it with TinyGo, I need to add this remark to export the name of the function to make the function visible or exportable for the host. I read the memory with the position and size, get the name, and transform the buffer into a string. I create my return message and copy it inside the memory. I then pack the position and size of the message into one value. That's why I spoke about bit shifting in the previous slide. The helper in the host application will need to do bit masking to split the value into value.
To run it, my web server is listening, and I can query it like the WasmWorker server in the previous demonstration. Once you understand how to write the helpers with Wazero, it's easy to create an application. The Wazero team is very friendly. They take the time to explain when you have a question, and the documentation is good. The source code is readable.
Xtism, It's a toolkit. With this toolkit, you have 2 SDKs to create host applications and a PDK to create wasm plugins. You can use various languages, and it's new, but you can create wasm host applications with Java. It's interesting because there's a promise to do that, but it's in progress and the documentation is almost empty.
If you want to do WASM with Java, Xtism is a good solution. For the plugin, you can create it with C, Go, Rust, Haskell, AssemblyScript, and even JavaScript. They used the QuickJS project and embedded it in the WASM module.
I'll show a new demo. It's similar to the previous one, but for the HTTP server, I use my favorite Java project, Vertex. I will run three WASM plugins: one with TinyGo, one with Rust, and one with JavaScript. I will spawn three HTTP servers to host my function.
The Java sample is more verbose because it's Java. There are packages from Xtism. Everything is ready to use. You load the WASM file, set environment variables, create an HTTP server with Vertex, a router, and then a route handler. Every time I post a request, the handler triggers. I get the value of the payload, instantiate a plugin, call the function of the plugin, and then return the value to the HTTP client. Functions are simple, and Xtism provides everything to read the memory. When you create your return value, you can allocate a place in the memory for the string, copy the string inside the memory, and return 0. The host application will read the memory, not the 0.
For the Rust version, it's simpler but has the same logic. For JavaScript, you just create a function, read the memory, write the memory, and return the value. To run it, I will start my server three times. I think it's okay. Then I can call my 3 functions. So, you can start a function as a service quickly, and with Vertex, it's a pleasure.
We're seeing convergence between the WASM runtime projects. Now, WASM Edge decided to implement the socket API specification like wasmtime. It means that you can use the socket directly inside the wasm module, and if it runs on wasm edge, it will run on wasmtime.
That is a POC was done by Wasm Edge and Docker to use the WASM file or module as a container on time. Without the classical container and only with the WASM rune time, the footprint is light. You can already enjoy using WASM in Docker and Kubernetes if you use the scratch image. For example, I use my side project Capsule. It's a WASM runner. You can use it as an HTTP server or MQTT client or NAT client. The size is around 17 megabytes. The WASM module is around 158 kilobytes, making my docker image around 18 megabytes so its very light.
Let's see if i'm able to do it in less than 3 minutes. I have a Civo cluster. I've already built my docker image, so I will deploy it now. It's pretty fast to have a running container. I will scale it 30 times. To have 30 pods on Civo is really fast because Civo is fast, and my container image is light. Now I have my 30 pods running. It's easy to do. So, I'd say start learning Wasm now. You can already deploy Wasm services on Kubernetes. If my source code is unclear, ping me on Twitter. Here's the QR code for the deck. If you have questions, ask now or after outside.
Right now, yes. But it will change because there's a spec that's changing to define specifications about complex types. At that point, it won't be a problem anymore.
It could be a potential issue with the memory. In my case, I instantiate a new module every time, so you start with new shared memory. Each time, it's a new part of the memory, so there are no issues with that.
We're talking about memory and security. Does this insulate those wasm functions from affecting the host processes? The wasm shared memory is only for the host and the Wasm module. The Wasm module can't access anything else. It's very sandboxed unless you create your host function and allow it to do anything. But usually, there are no security issues or memory issues.
Question: So, two WASM functions wouldn't share the same memory?
Answer: Every instance of wasm function has its own memory space. If you have three instances of the wasm function, you'll have three buffer memories, not just one. I think you can share 2 but it would be tricky.
Thank you.
Stay up to date
Sign up to the Navigate mailing list and stay in the loop with all the latest updates and news about the event.