Surma - Polyglot WebAssembly - View Source 2019

Transcript

You have to endure this for the next 30 minutes. Be prepared. Hi, I'm Surma. I want to talk about polyglot WebAssembly. That's a word that I made up. Polyglot basically meaning the ability to read, write or speak a variety or several languages. And WebAssembly is a pretty good representative of that skill, really.

And since you are all here, you are all most likely polyglot. Because, you know, web development alone involves three languages, HTML, JavaScript and CSS. Welcome, you're polyglot. If you're interested in WebAssembly, you might know one of the WebAssembly languages. That's nice. But talking about WebAssembly, I tend to mention three tools or three languages that cover very different use cases on the spectrum of WebAssembly. And those are Emscripten, Rust and AssemblyScript. It's a replacement for the compiler. If you use GCC or clang, you will use MCC to compile your C code to WebAssembly.

And Rust, the language Mozilla has been developing and growing a vibrant ecosystem and community around it. Rust has native support for WebAssembly built into the main compiler which is great and works well.

And AssemblyScript is something I'm excited about. Which is a language that uses the exact same syntax as TypeScript. Not that you can take TypeScript and compile to WebAssembly, but you can use the skills with TypeScript and use AssemblyScript. And you can play with WebAssembly without having to learn a new language if you're a web developer.

Just to talk a little bit about the spectrum I was mentioning of WebAssembly, one thing I look at is the just works dimension. And Emscripten is the prime example. They aim to be a drop in replacement for your old C compiler. The problem you have been in C++ or written before WebAssembly was a thing, Emscripten aims to make that compile and make that run on the web. They do magic and heavy lifting to make it run on the web. Rust is good here as well. They don't do all that magic straight out of the box. It is a little bit more likely that code that hasn't been written specifically for the web will not just compile out of the box, and you will need to tweak a little bit. But most of the things can be made to work with WebAssembly and on the web with just a couple of support libraries.

AssemblyScript is very different. AssemblyScript is a low level language. It's a very thin layer on top of Assembly, almost, for WebAssembly. And so, you have to do a lot of things yourself. And that shouldn't necessarily scare you to take a look at it. Because it also gives you a lot of control and a lot of understanding of what WebAssembly can and cannot do. Don't let this deter you from looking at AssemblyScript. It's very enjoyable and I would recommend it.

Another I want to talk about is the output size because that's something as we all know that matters on the web. How much bytes does the user have to download to start what you wrote? And AssemblyScript is good, you are in full control. You write really, really small modules. On the order of a couple hundred bytes. Rust I put a little bit higher. And they will agree with. That's because by default Rust modules will be quite big. But Rust has the ability to generate just small modules. But that will require you to go and add meticulously crafted code to get to the small file size. You can do it, but it's not automatically that small. And Emscripten is the biggest. It does all this magic, lots of heavy lifting under the hood. If you use a file system, it will get the file system and that needs code. And I will talk about that later on.

And the last thing in terms of the spectrum is the ecosystem around it. AssemblyScript being its own language, fairly small team and new, there's not much there. But they do a lot with what they have. Please take a look. I find it enjoyable. Rust has grown incredibly in the last year or two or three. And has a huge ecosystem. I'm impressed with how many things I can find in the Rust ecosystem thrown at the web. And, of course, no one can compete with the ecosystem that's been around for decades. It's huge and most of these things you can use with Emscripten. And there's more languages that coming to WebAssembly. That's what this is about, the polyglot WebAssembly.

Honorable mentions. One of them is Go that has experimental support. You can write Go programs, compile them to WebAssembly and run them. They are very big currently. Mostly due to the fact that Go is both garbage collected and makes use of co routines which WebAssembly doesn't have native support for just yet. They have to bundle all that runtime code for themselves. Meaning it is quite big. But in the future, that will probably go down once these features come to WebAssembly. And the exact same applies to Blazor, which is a project that brings the .NET to WebAssembly. C#, they are the same problems, they have co routines and currently they are quite big. But definitely worth looking at and playing around with if you come from a C# background, for example.

And the last one is LLVM. It's a compiler back and project that Clang came from. And it's been around for quite a while. But since version 8, they have their WebAssembly support in stable. If you use Clang, you can compile C code to WebAssembly just with Clang. And switched to the new LLVM under the hood. If you use Clang yourself manually, you will not have a standard library. Which can be both cool and very intimidating. If you want to know about this, I wrote an article that you can find here. You have no Malloc, no sine function, you have to write it yourself. But it can be an interesting experience. Probably not ready for production.

And the number of languages that support WebAssembly will only grow. For example, what if some idiot went on to npm and wrote a new compiler for a language called, I can't say it on stage. But it's brain clock. If you don't know it, it looks like this. And that's what you want for the enterprise engineers to write their code in. It's very enjoyable. Basically, it's a bare bones touring machine. More of an educational device than something you would ever write production code in. But I did it anyway.

This is real. This is a compiler that you can use. You write your code in brain  clock! Is and once you write the code, you can do the JOSM code. That's my compiler. I'm now a compiler engineer.

And I want to talk about it, after reading how WebAssembly is under the hood, I want to see WebAssembly in education. When I was in university, I wanted to write assembler and write an operating system. And then learn about the process on the laptops, they're bonkers. There's no way to actually understand how they work and know every instruction that they have.

So, what I am saying, is don't be afraid to go to WebAssembly.org and just click that spec button. Because the spec is actually kind of well written. And the virtual machine is very well designed. Actually, I was able to understand just by casually reading through the spec. And just to be clear. I'm not saying that every web developer needs to do this. You don't need to do this if you want to make effective use of WebAssembly or be a good web developer. This is just if you're curious. If you're interested.

The spec can look daunting because it repeats itself four times over, once for validation, execution, the binary and text format. But if you want to get started with super low level Assembly, and I wrote a blog post. If you're curious as a secondary introduction how to write text format which look like this. And I would almost call that readable. Okay. Enough of that. Go back to polyglottism and languages. And all that stuff.

I want to talk about Squoosh. It's an app we wrote last year. It's an image come possessor PWA. A web app that works offline, has all the good PWA bits and makes heavy use of WebAssembly. This is what it looks like. The point is it can drop the image and select a PNG and a JPEG codex, and we expose them, and you can apply around and see how the changes affect the visual of your image. And we also supply a couple of different scaling algorithms.

And all these things run in the browser. 100%. There is no server side. It's all WebAssembly. And all these WebAssembly modules have been implemented with different languages. So, all these codex for images you often find on GitHub or wherever, are written in C. Because C is somewhat easy to embed in many other runtimes. So, all the codex are C or C++. We actually found a really nice image resizing library in the Rust ecosystem. That part is from Rust. And we have a piece of AssemblyScript that rotates images.

If you're wondering why we have a piece of AssemblyScript just to rotate an image, that will absolutely blow the scope of this talk. There's another article I wrote where we found out that JavaScript can be fairly unpredictable whether it comes to performance. But really the point here is that we have multiple languages working together in the browser. Which I think is kind of cool. So, WebAssembly itself only understands numbers. Like you have a couple of different types much numbers like 32 bit integers, 64 bit integers, floats. But things like strings, objects, dictionaries, arrays, WebAssembly doesn't know about them.

What most compilers do is generate a .wasm file you with the WebAssembly machine code and a wrapper in JavaScript. It's called the JavaScript glue code which turns this low level interface of the WebAssembly module into something that can accept strings and arrays and makes it a bit nicer to work with. One of the first things we notice when working with WebAssembly is the pieces of glue code can become quite big. And I say big because I personally have a super low threshold for file size. Many people will ship 500 kilo bytes or a megabyte to production and won't flinch. For me, 30 kilobytes. It's fair for what it's doing, it does a lot of heavy lifting under the hood.

As an example, Emscripten's role is to be a drop replacement for whatever C compiler you have in the project. The standard library of C is designed as an abstraction over whatever operating system you're on and so, a high level function like F put S, which writes a string to a file, has at some point under the hood mapped to something called a syscall, at least on Linux systems. And syscalls are a weird function of abstract functions from the kernel of the system. And most calls will at some level map to the syscalls. What Emscripten is doing is emulating the entire kernel interface in JavaScript and mapping it to sensible web APIs if so required. So, for example, if you open a file, it will start emulating a file system for you so that your C code can be working just fine.

If you use OpenGL in your C code, it will pull in WebGL and start piping those two things together. And, of course, there's a lot of code to do that. There's corner cases and whatnot and, yeah, so, the file size will grow over time. And on further inspection, realized that between multiple modules the glue code is almost the same barring a couple of constants, a couple of individual functions. So, and that makes sense because the way you turn a JavaScript C  JavaScript string into a C string will most likely stay the same. I'm working with the Emscripten team hoping that we can somehow make this more reusable, re sharable. Currently the user of Squoosh has to download the same code twice or three times. I'm hoping to do better in the future. But in the long term future, there's an upcoming standard from the WA SM working group that I want to talk about for a little bit. It's called WASI, the WebAssembly System Interface.

You can think of WASI kind of as a standardized kernel, almost, for WebAssembly. It's super early. So, don't bet your money on this just yet. It's being worked on. It's just a couple of READMEs really. It's exciting. And interview questions to focus on it. It's focusing on WASI Core, you can access like crypto devices. For now, it's Core. Which is mostly motivated by the use case of running WebAssembly outside the browser on a desktop machine. Because what happens currently happen is that WebAssembly was kind of conceived with the use case of running on the web. But since then, there's more use case of running it embedded in your Android or iOS app or even stand alone on your desktop. It's becoming like a universal binary format which I think is really exciting.

But, of course, in that scenario you need access to files and to network sockets. And that is really what a WASI is providing. So, since Emscripten is already emulating the entire kernel, I think WASI could be a really interesting use case. You're asking yourself, did I add WASI support to my compiler, of course, I did. And you instantiate a WebAssembly module on the web in a node, do this. You will use instantiate and pass in the WebAssembly module as the operator. And the interesting thing is WebAssembly is 100% isolated. It has a chunk of memory and it can to arithmetic on the memory, that's it. It doesn't have access to anything, not a single web API. You can only pass in any external functions through that imports object in the second parameter. And so, by default, the modules that my compiler generates expect two functions. The in function and the out function. Brain clock! Needs to communicate with the external walls.

Now, in WASI land, things like a bit different. You're basically putting all of these individual functions into the namespace that WASI has, WASI Core or WASI crypto. I'm only allowing the module to read and write from file descriptors. That's interesting because it cannot open any files. It still doesn't get access to the file system. It can only read and write to file decrypters. And only has access to standard in and out. In terms of security, this is a nice model because you have to explicitly give each individual app to use certain APIs on your host system.

And now, of course, if you use my compiler and use the WASI flag, you get out a new WebAssembly file. And wasmer and wasmtime are implementing WASI to experiment with. And it prints hello world. I thought that was kind of cool. I was proud when that happens. But, again, super earlier. It's mostly READMEs. But there are some experimental runtimes which are quite fun to play with.

Now to another part is that the tools that WebAssembly has are also polyglot because they don't even care what language you use. They just care that they can look at the WebAssembly and work on that. So, a couple of times I want to point out is wasm strip. Looks at the module and strips out everything that's unnecessary. Debug functions. It helps make the modules smaller. Wasm validate, checks that it's semantically correct. As a compiler engineer, that's helpful. And wasm opt which I want to talk about. Mostly it just aims to make your WebAssembly module fast and small.

One thing that you should always do pretty much when you run or deploy a WebAssembly module is run wasm opt because it will never make a WebAssembly module worse. But it will always make it a tiny bit better. And sometimes it's a lifesaver. For example, in the case of my wasi/wasm module, it made it smaller. It's not their fair it, it's a non on opt hissing compiler. I didn't want to make it messy by making smart optimizations. But also, I knew that wasm opt existed and that would take care of most of the obvious optimizations for me after the fact. And I think that is actually quite nice.

But there is a fun story here from Squoosh, actually. I wanted to ship HQX. Which is an image scaling algorithm specifically tailored to pixel art. And I found it in implementation in rust. And so, I ported it to WASM. It worked great, upscaling results. It looked good. But in Chrome, the CPU got locked in at like 200% for like 2 minutes after I loaded the module. Which was weird because everything was working. And so, it turns out that the WebAssembly that the Rust compiler was generating was like a nasty edge case for the optimizing compiler in v8 for WebAssembly. And so, I look at it a little bit. The file starts fairly normal and the blocks start, and they just keep going. And it's A lot of blocks. Like 3700 blocks. That's a really long blockchain. So, lift off the streaming compiler actually just looked at the generated code and ran. And that's why it worked straight up. But the optimizing was working in the background and trying for two minutes to optimize this mess. This is not good on a mobile device. Just turn in your CPU at 200%. That's not a good idea.

So, yeah. What we did instead, we figured out that wasm opt can help. It didn't know it was Rust. It looked at it. It took 20 minutes to go through the entire module. But that's build time. Build time is much more expensive than your client's mobile battery time. Less expensive. After 20 minutes, we had a module that was 100K smaller. But also, the blockchain was only 700 or 800 blocks long. We didn't see the CPU blocking on Chrome anymore. It was cool. Rust didn't have optimal code, it was fine. And wasm opt fixed it without knowing it was rust. And that's cool, I thought.

But now to one of my most exciting tools is Asyncify. Was announced back in the context of Emscripten. Most missed it as an Emscripten tool. It's part of wasm opt and therefore also language agnostic. So, what does it do?

So, as I said, Emscripten will emulate a file system for you. An example where I encountered there. You can use file operations in your C code and picture store that file system in memory or it can put it into local storage. And I had a use case where I wanted to store the file system not in local storage, but in index DB. And index DB, as some of you might know, is a promise based API. And so, that means that at some point a call like fputs would have to work with IDB which was a promise and needed to be awaited. Now, WebAssembly is inherently synchronous and doesn't understand promise. I didn't want it to return until it was written or persisted to the database. So, I was in trouble here, really.

Because, you know, there's no wait. And that's exactly what Asyncify solves for you. You can pause WebAssembly and do asynchronous work and pause stuff. And tell WebAssembly, go back, resume, execute. You can make somethings that asynchronous look synchronous to WebAssembly. So, Emscripten itself has a really nice abstraction where you can just write some inline JavaScript and make a wakeup callback and call whenever you're ready for WebAssembly to resume. But it is language agnostic. So, did I add it to my compiler. This is a very simple brain talk where we read from the inputs and print it back out. And do it over and over. It's a read write loop. Now, the thing is with input, if there's nothing available to read from the input, we would have to wait and wait is inherently asynchronous. So, how do we do that on the web? We can now do this because Asyncify.

This is pseudo code, but you can say wait, pause everything, and wait for a key press to happen. Once I have a key press, I will resume WebAssembly and return whatever key I have. When I look at that, really what I've built here is an utterly unimpressive typewriter. But the potential behind it is kind of come because if you think about often C APIs are often synchronous. If you want to talk to a USB device in C, those are synchronous. But then on the web, it's asynchronous. There was no way to plug the two APIs together. But now you can. That will open up doors for WebAssembly to marry to web APIs and make things work on the web that didn't work on the web before.

Let's get back to Squoosh for a little bit at the end. As I said, we use a bunch of different modules from different languages. Each language may or may not add some glue code. In addition to the app JavaScript code that we have that orchestrates everything. Really what our app does when you compress an image is this. You have an image data, an array buffer of pixel values. And then rotate it with AssemblyScript, resize with some Rust. And then at the end, run it through a compressor and enter through a JPEG or PNG. Looking at this, we don't really have performance problems per se. It's all off main thread. And image compression takes a bit of time. It's not going to be super snappy fast. But we can all agree that this looks a little bit unnecessary. Wouldn't it be cooler if we could do this instead?

And this is now something being worked on in the WebAssembly working group under the name of interface types. This is even earlier than the rest of the stuff I talked about. But the point of this proposal is to bring higher level types to WebAssembly. So, as I mentioned before, WebAssembly only does numbers, but with this proposal, there's now a way to add support for things like strings or lists or dictionaries or objects. But not necessarily in the way you might be thinking.

Because WebAssembly will remain to only understand numbers. So, a WebAssembly module looks kind of like this. It's a module, it has a couple of exports. These exports are most often just functions. And these functions can only work with numbers. These different types of numbers that WebAssembly has. Now, with if your language uses lists or strings or dictionaries, under the hood they have a mechanism to map those to like memory addresses and lengths and offsets. Like to numbers. But that's not really like a good user interface to use.

So, what they have been thinking up is that a module can now bundle a second definition of itself that uses these higher level types. So, now we have a function that takes a string and a function that takes an entire JSON object and then the module will bundle small tiny functions that know how to translate these higher level types to whatever language you were using under the hood and turn those into calls on the low level language.

So, this way we now have an interface of the module with high level types that we can use as developers and be comfortable with that. And the translation code block will be working with the language in the memory. This is cool for us as developers. But it also means that now multiple modules can use these higher level types to communicate. Because a C string will be different from a Go string and a C# string. But now they have an abstract concept of a string and can use it to invoke each other's functions. Which I think is going to be interesting.

Of course, there is more future stuff, SIMD is working on. Threads are in Chrome, and soon in Firefox, I hope. There's Tail calls as a proposal. That means that we are entering the world where some browsers have support for features and others not yet. And for that reason, I wrote a small library called WASM feature detect that does what it says and tell you if a browser has a support for a certain feature or doesn't. If you're excited about this, or tickles your interest, try it out. Hit me up on Twitter. I would love to hear what your ideas are. And with that, thank you for your attention.

Discover the best of web development

Sign up for the Mozilla Developer Newsletter