WebAssembly–a hands-on guide for frontend devs

Introduction to WebAssembly

Katie Bell starts her talk by discussing WebAssembly, its relation to different programming languages, and its focus on compute speed, especially for complex tasks like image processing and game physics.

Compute Speed in Web Applications

She emphasizes the importance of compute speed in specific web applications like computer games and data processing, and how it impacts development tools.

JavaScript's Performance

Bell discusses misconceptions about JavaScript's performance, comparing it with other languages and highlighting its surprising speed despite not being designed for optimal performance.

Compiled vs. Interpreted Languages

She delves into the differences between compiled and interpreted languages, discussing their impact on the speed of different programming languages.

Introduction to ASM.JS

Katie introduces ASM.JS, a subset of JavaScript designed to improve performance, and its role in running other languages in the browser.

Evolution from ASM.JS to WebAssembly

She traces the evolution from ASM.JS to WebAssembly, explaining how the latter emerged as a more efficient and compact binary format for web applications.

Introduction to WebAssembly's Features

Bell introduces the features of WebAssembly, highlighting its strict typing and efficiency in mapping closely to CPU instructions.

WebAssembly as a Virtual Machine Language

She clarifies that WebAssembly operates as a virtual machine language, despite its close mapping to CPU instructions.

Demonstration of WebAssembly in Practical Use

Katie presents a practical demonstration of using WebAssembly in an application, specifically converting markdown to HTML using Rust and WebAssembly.

Understanding the Generated JavaScript in WebAssembly

She explains the generated JavaScript in WebAssembly, detailing the process of copying and converting strings between JavaScript and WebAssembly.

WebAssembly's Limitations in Web Applications

Bell discusses the limitations of WebAssembly in web applications, noting that it may not significantly enhance performance for average web applications.

WebAssembly in NodeJS

She explores the use of WebAssembly in NodeJS, demonstrating its practical application and ease of use in this environment.

Introduction to WASI

Katie introduces WASI (WebAssembly System Interface), explaining its role in enabling WebAssembly to perform operations like accessing the file system and networks.

Future Potential of WebAssembly and WASI

She discusses the future potential of WebAssembly and WASI, especially in cloud computing and local development tools, highlighting the security and sandboxing benefits.

Concluding Remarks on WebAssembly

Bell concludes her presentation by summarizing the readiness and applicability of WebAssembly in different contexts, encouraging its use where appropriate.

Thank you.

That was a pretty good introduction.

It's almost like you've actually seen the talk.

It's even if you don't use WebAssembly yourself, it's really good to understand it.

Okay, we're about WebAssembly, and when we talk about it, we talk about different programming languages, and we talk about the speed of different programming languages, so I want to add a disclaimer right from the beginning.

When we're talking about different programming languages, how easy it is for developers and how flexible it is for developers is really important, and I'm going to largely ignore that.

Okay.

So just bear in mind that's important.

I recognize, but we're not really going to talk about that.

We're going to talk about a lot more about speed.

How fast does your code run?

And we're not talking about does it feel fast for your users?

It was a great talk about that yesterday.

We're not talking about Oh, we have a small bundle size and our network calls are less.

It makes our app faster.

No, we're really just talking about compute speed.

If I have a whole bunch of data that I need to process quickly or I have a whole bunch of images or things that I need to calculate, and I have to calculate it really fast, because there's a lot of them, then the compute speed is important.

Now for a lot of web apps, the compute speed is probably not your biggest performance issue.

So for a lot of web apps, this isn't going to matter.

But for something like computer games, it is going to matter.

If we're going to run computer games in the browser, we need to be able to compute things quickly for your physics engine or something.

If we're doing data processing and using images or audio files or video transcoding, then compute speed is important.

But another area where compute speed is important is DevTools, who would like to have faster DevTools?

Yes, exactly.

Like when you have, especially if you have a really big code base, then the linting, the compiling, the bundling, the testing, like that stuff can add up.

And when it gets slow, your development cycle gets slow.

This is something that we care about is our DevTools getting faster, which we'll come back to.

So when we start talking about compute speed, people start saying things like.

JavaScript is slow, because you feel like groaning in this.

I actually disagree with this.

I think JavaScript is surprisingly fast.

I, work in Python a lot.

JavaScript is surprisingly fast.

And usually when we're talking about is the compute speed of this programming language slow or fast, we start to have a discussion about, oh, it's slow because it's an interpreted language and it's fast because it's a compiled language.

And then you start to have questions of like, all right, what about Java?

Is that interpreted?

Or compiled?

Is Java an interpreted language?

Is Java a compiled language?

People are very uncertain.

Essentially it's both and neither and like it doesn't make any sense and this is largely an unhelpful distinction.

This is what I tend to prefer to look at this as is it running in some kind of virtual machine?

When we're talking about Java, we talk about the Java virtual machine.

But when we're talking about JavaScript, we call it a JavaScript engine because we don't want it to sound like Java.

And, when we talk about Python, it's the Python interpreter, but ultimately It's a bunch of logic that is written.

It might be compiled into some form, but it's essentially running in another program, right?

It's running in a, an app that's running on a CPU.

For these languages on the right, C, C++, Erlang, it's actually running, like you compile it into CPU instructions for an actual piece of hardware, right?

Like you're, compiling it into the instructions that are executed on the physical CPU itself.

So that is the distinction.

It's not always the best distinction to make when talking about the speed of different languages, but it tends to mostly work.

Things that are compiled directly to a set of CPU instructions, things that are very close to CPU instructions, tend to run a bit faster than things that were designed to run in a virtual machine.

Now, the difference isn't actually that big.

And JavaScript is pretty fast.

And this is on, a sort of calculation problem of the number of like physical bodies and how the gravity affects each other.

So it's a bit of a contrived example as all benchmarks are.

But JavaScript is actually doing a pretty good job here, especially when you consider that C, Rust, Go, they were designed to closely map to what physical CPU instructions are, right?

They were designed to be easy to translate into CPU instructions so that you would be able to write code that would then run fast.

JavaScript was not at all designed for that, right?

Well not entirely.

JavaScript was never really intended to be optimized.

It was never intended to be the ultimate fast compute engine, right?

And so you have things like this where it's okay, we're adding two numbers together.

That should really be like one CPU instruction, right?

Like an integer add, is very simple for a physical CPU to compute.

But then you might have weird situations that come up that you have to account for that are going to screw you up.

So in JavaScript, if you're going to execute JavaScript in a way that is perfectly valid, there's always going to be these kind of weird dynamic situations that have to come up.

There's a sort of limit to how you can optimize it.

And in fact, it's incredibly impressive that JavaScript runs as fast as it does.

A lot of work has gone into that from a lot of different people.

But, it starts to, have diminishing returns.

How fast can we actually make JavaScript, right?

Let's forget about making it easy to develop.

Can we, cut JavaScript down to, some bits that we can make really fast?

If we never use a val, maybe we can make it fast.

And it turns out there was a project to do this.

And it's an old project, so if you cast your mind back ten years, if you remember what the Harlem Shake is, I'm sorry.

It was really, big in 2013.

And in 2013, Mozilla introduces this thing called ASM.JS.

Who remembers ASM.JS?

Yeah, a couple of people, about half and half.

It doesn't really exist anymore, so it's good that you don't know what it is, but we're gonna go through it anyway.

This is ASM.JS.

Essentially, a group at Mozilla came up with a subset of JavaScript, right?

If we write JavaScript in this really weird and specific way, we can make it run fast.

We can make our JavaScript engine optimized so that this particular kind of JavaScript can be run much more quickly than regular JavaScript.

And this gives us a little bit of an insight into okay, if we're designing a programming language to run fast, what do we do?

So firstly.

We're separating it from normal JavaScript saying, hey, this bit can be optimized by adding this little "use asm" tag.

In JavaScript, if you do a bitwise or with a zero, then it doesn't change the value of the number, but it forces it to be a 32 bit integer.

So now our JavaScript engine can optimize this and say okay, this is going to be an integer.

I can rely on this being an integer.

If this variable was a string before, it's not now.

It's an integer.

And we don't use the garbage collector anymore, we're gonna store everything in this, array of bytes and just use that to store all of our variables, and we don't, use the garbage collector at all.

Now, who would like to write code like this?

Yeah, nobody.

Excellent.

Good.

You're all sensible people.

It was never intended to be handwritten, right?

The idea with ASM.JS was that you would write your code in something else, probably C or C++ and you would compile it into ASM.JS.

And this was how we started to get things like, Oh, we can run other stuff in the browser other than just little bits of JS that manipulate the DOM.

We can, and this ended up being surprisingly fast, right?

It only had a, surprisingly small speed hit 1.5 times slower than running the same C or C++ code natively on a CPU.

And this unlocked cool things like, oh, we can now run other languages in the browser.

This is how I first got interested in it because, was working with a friend who wanted to run Python in the browser.

And you could take the Python interpreter, which is written in C.

Compile it into JavaScript, and then run it in a browser.

And the nice thing about ASM.JS is if you're running it in a browser that was not optimized, did not have that level of optimization to be able to run it quickly, it would still run, just really slowly, because it's still valid JavaScript.

And this is where we started to get things like, oh, we can run Unreal Engine in the browser and stuff.

So all of these things existed before WebAssembly did.

And after a couple of years, I started to see that there were some limitations with ASM.JS.

So it's really cool that we can run other programs on the web.

It's cool that it's still sandboxed and safe to run.

It's actually surprisingly fast.

But it ended up being constrained by what you can and can't do with JavaScript.

Like you don't want to add too many random confusing things into the JavaScript language just so that you have an extra language kind of underneath JavaScript that you can optimize.

And the other problem was that when you're compiling into JavaScript, you end up with these very large JavaScript files that are slow to download as well.

So they started playing around with a compact binary format for these ASM.JS files, and ended up a little while later creating the WebAssembly project.

So WebAssembly is actually the ASM.JS 2.

0, right?

The second version.

Starting again, but using a lot of the ideas and learnings from ASM.JS.

This is where WebAssembly came from.

Now WebAssembly is no longer trying to be JavaScript, or not cross compatible with JavaScript.

It looks like this.

Okay.

You could write it by hand, but again, you're probably not going to write it by hand.

So it's not fun to write by hand, but it is very strictly typed, right?

The, that i32 is a 32 bit integer.

So we have 2 32 bit intes.

We put them onto the stack and then we execute the 32 bit integer add operation.

So this is, designed to map very closely to the kinds of capabilities that you would see on the different kinds of CPUs that are commonly in use, right?

So it's designed to be, like, we can expect there to be an operation on the CPU to add integers together that are 32 bit integers.

Let's just assume that's going to be there and we'll put that into the language so that it's easy to implement and fast on several different kinds of CPUs, different kinds of machines.

Okay, so if we go back to our, it runs in a virtual machine or it runs as native code, Where does WebAssembly lie?

Okay, so who says virtual machine?

Okay, some hands.

Who says native code?

Who really isn't sure at this point anymore?

Yep.

Yeah, lots of you.

Cool.

So let's go to the WebAssembly dot Org website which says exactly WebAssembly, abbreviated WASM, is a binary instruction format for a stack based virtual machine.

Alright, so WebAssembly, despite how closely it's intended to match CPU instructions, and is actually a list of instructions, is still running in a virtual machine.

It's very much a virtual machine language, okay?

So that is what it is.

It's a compact binary format with a, essentially, its own little language, its own little virtual machine that is designed to be able to be run quickly, and is designed to not be written by hand, but have other languages be compiled into it.

Okay, hands on demo time.

All right, we're gonna build a little app.

Okay.

One of the things is people are like, Yeah, I've heard of WebAssembly.

It sounds great.

But I don't actually know how to use it.

What would I even do?

So we're gonna build a little app.

This is an example app.

We have a text area on the left and some HTML on the right.

Essentially, we have some markdown.

We can write the markdown in the text area and it shows up on the right in HTML form.

So if I were to, here's a question for the audience.

If I were to give you a function, where I give it, you give it a string of markdown and it gives you back a string of HTML, who reckons they could build this app?

Wow, it's less than I thought, you're all front end developers.

Come on.

It's a text area, okay?

But yeah, we are not going to go into how to build the app.

That is left as an exercise for the reader.

What we're going to do is we're gonna build essentially a function that we can call in JavaScript that takes some markdown and generates HTML and I'm pretty sure most of you could build this app in a day or so.

Like you have time to build it.

You don't even know how to build it off the top of your head.

You reckon you could build this app, if you were given this function.

So we're going to work on this function.

Okay, we're going to start with a Rust library.

Don't need to know Rust.

This is fine.

We're going to compile that into Wasm, into WebAssembly, with some JavaScript bindings.

And then the last step, build the rest of the app, is like you're going to go home and do that for homework.

I didn't write this library.

I used one that I found off the internet.

This guy wooorm, has built a Rust markdown library.

And we're going to use that.

Okay, so if I hit the right buttons.

Okay, is that big enough or does it need to be bigger?

Make it a little bit bigger.

All right.

This is the entire Rust code.

Okay, this is one Rust file.

It is the library, the Markdown library that I'm using, this Rust library, has a to_HTML function and all I've done is wrap it in another to_HTML function.

But this is a function.

Fn stands for function, in Rust, it takes a string and it returns a string.

Okay, so this is the smallest possible Rust library we're gonna, we're gonna start using in WebAssembly.

Now, when I compile this Rust code, I compile it using CargoBuild.

If I compile it using CargoBuild and I say it, set it to target WebAssembly, It will compile it, okay?

We can just compile Rust into WebAssembly, and it gives us a WASM binary file.

Now, that's not necessarily helpful if you don't know how to use that WASM binary file in your JavaScript code.

And so instead of working that out manually, I'm gonna do it the cheat way, which is to use this thing called wasm-bindgen, which we basically add this thing into the Rust code, which is a label saying this function in Rust, when you compile it to WebAssembly, I want to be able to call that from JavaScript.

So please do the work for me, which is good, because I'm lazy.

If we go in here And we add that back in, and we now have, this is, the complete Rust code, I'm not going to write any more Rust.

And I use this tool called WasmPack.

I say build, target, web, hope everything works.

Does its compiling thing.

And you'll notice that a folder appears up here.

Oh, here we go.

There's this package folder up here.

And this package folder has in it a WASM binary file, which we can convert to text and read if we want to, but, it's binary.

We don't, we don't need to do that.

But we also have not just a JavaScript file, we have type definitions, so we can use it in TypeScript as well.

And now we have this, to_HTML function, which takes a string and returns a string.

So we now have a JavaScript file, even with TypeScript types, that we can just use, in our app and share that.

I have already written the app.

So I'm gonna say yeah.

Start.

We're gonna click the link.

Cool.

And we have an app that converts, markdown into HTML.

And so I can just edit this, I don't know.

And it will update each time.

Okay, that's the steps that you need to go through, if I can.

Go back to the slides somehow, that would be good.

Let's do that.

Okay.

All right.

So we run, the build stuff, and we get the JavaScript.

And then from our JavaScript code, from our app, okay, we, there's an init function that we need to call, which asynchronously downloads the WASM file and sets things up for us.

And after we've called that, we can just straight up call our to_HTML function to convert our markdown string into HTML.

Which is great.

Really easy to use.

But the big question is, oh, actually, who reckons they could build the rest of the app now?

You feeling a little bit more confident?

Yeah, still only about half of you.

Okay, What is actually going on in those generated JavaScript files?

There's actually a lot of magic going on there that we don't get to see because the generated JavaScript files are just there and we could just use them.

But I'm gonna go through them.

What's really going on?

And this is the JavaScript you would have to write if you were, dedicated enough to write it yourself instead of having a program generate it for you.

So when you're using a WASM binary, you download it.

Now, we've downloaded it with this particular URL.

So it bundles a bit better depending on your bundler.

You can specify JavaScript functions that the WebAssembly can call.

Now, in this case, our WebAssembly isn't calling any JavaScript functions, but if you want to do anything associated with, editing the DOM, anything more than just computing things, you need to pass in JavaScript functions for the WebAssembly to call.

But yeah, once we've set up, we've got our Wasm file, we call this WebAssembly, it's actually streaming, and then we have this WebAssembly instance, and that instance has the functions that we've exported from WebAssembly that we can call.

Okay, now I've not filled in exactly how to call the to_HTML function, because that's what the other part is like.

So the generated JavaScript looks like this.

All right.

Who's feeling comfortable with this code?

Yeah, who's feeling horrified at this code?

Yeah, okay, that's more of you.

Yeah, this is pretty intense.

What the heck is going on here?

And if we look at this, from the outside, this is a pretty easy to use function.

Okay, it's a string, we give it a string, gives us back a string, cool, it just works.

What's it actually doing here?

Like when it actually calls the, like the web assembly bit here, this is the one bit where this is a bit where it's calling into the web assembly engine.

It's not passing in a string.

It's not even returning a string.

What is this doing?

So when we call this function, the first thing it does is take our JavaScript string and copy it into the memory in our WebAssembly runtime.

Okay.

Probably transcoding the string into a different format as well.

Okay, so it's copying the memory in, it takes the pointer for the start of that string, if you've ever done C and you know about pointers, and the length of that string, and that's what we're passing into WebAssembly when we actually call the function.

And we have this kind of RET pointer, which is our return pointer, which inside WebAssembly, it's creating our HTML string, and then we get back the pointer of the start and the length of that HTML string, and then we've got to go back the other way.

We've got to take that string and convert it back into a JavaScript string, okay, which probably involves transcoding it into UTF 16, which is the JavaScript string format.

So in order to actually use this WebAssembly function, we have to take our string, copy it into WebAssembly, Execute the WebAssembly and then, copy our new string back out again.

And our little markdown app is cool, but it's probably not actually any faster than, just using a JavaScript markdown library.

Getting in data into and out of the WebAssembly runtime can eat the sort of relatively small performance benefit that you might have had from using WebAssembly in the first place.

And this is how it goes for a lot of web apps, like getting things from JavaScript in and out of WebAssembly adds a performance cost, and if your WebAssembly isn't that much faster than the JavaScript was, then it's not gonna help.

So it helps a lot if you're doing intense processing like a game or like Photoshop or Figma, but for your average web app, might not actually help you at all.

WebAssembly can do pretty amazing things, but here I am just saying ah, don't use it.

One of my favorite things is that, someone has built a, an x86 emulator.

So it's actually emulating the x86 processor and the entire machine, and you can run Linux on it, you can run Windows 2000 on it.

This is my own artwork, in Paint, in Windows 2000, in a web browser.

It's pretty awesome.

WebAssembly can do insane things.

But it won't actually help the performance of most web apps.

You're more likely to get performance benefits from a smaller download size or faster network calls or something.

But not every soft not all software runs on the web.

I know this is a web conference and we're all front end developers.

But there's a lot of software that we use on a day to day basis that isn't actually running in a web browser.

And it can surprise people that you can also use WebAssembly in NodeJS, and it is surprisingly easy.

So if we go back to terminal, stop that running, go back into the Rust folder, and if I say wasm, pack, build, target, nodejs.

It builds very similar JavaScript.

It has a slightly simpler API to it now, because I don't need to call the init functions.

I don't need to fetch a wasm binary.

But I can then use this.

Where are we?

Yep.

I can use this in a NodeJS server.

If in my express server, I could use this as a command line tool.

I can use WebAssembly bundles, WebAssembly binaries, as just part of a normal NodeJS command line tool or a development tool, which is pretty cool.

This is a little app where, you know, I just, I could run this in Node and it would convert some markdown into HTML for me.

Why would we want to do that?

Why would we want to have WebAssembly in NodeJS?

And this is where we come back to development tools, because we want to have fast development tools.

Now, we already have fast development tools for some of them.

ESBuild, its selling point is largely that it's faster than the other alternatives, right?

I use ESBuild because it is a lot faster than some of the other tools build tools for dealing with JavaScript and TypeScript.

And it's fast in part, because it's written in Go and because it's written in go.

When they deploy this to Node to NPM, they actually have to deploy 22 different versions of it, right?

If you're using ESBuild, you're not using like one ESBuild package.

It's actually downloading the specific ESBuild for your specific operating system and, and CPU, which has fun times when, Apple comes out with a brand new thing called an M1 Mac and suddenly none of your dev tools work anymore, which I have experienced.

But, it works pretty well, actually, right?

This runs really fast.

It's really great.

But this is extra work that the ESBuild maintainers have to do.

Go will build into all of these different things.

They have to build and deploy 22 different packages so that it works on everybody's machine.

I don't know how many people are using Solaris or something, but, it's, supported.

Props to ESBuild.

But if we were using WebAssembly for this, we'd be able to build one binary, have native like speeds, and be able to deploy one npm package instead of 22.

Or, we could maybe have, if we really wanted to have native speeds for some CPU architectures and some operating systems, we could always have a WebAssembly version as well for, the other people that we don't support, right?

So you could cut down how many different architectures and platforms you need to support if you have a WebAssembly version of your build tool as well.

Now, doesn't this still have the performance cost, right?

If we're running it in NodeJS, we're still passing things from the JavaScript execution into WebAssembly, and because WebAssembly can't access the file system, can't access the network, we're gonna have to use JavaScript and be passing things back and forth all the time.

Which is where we start to get into the kind of new territory, that isn't widely used yet, but a lot of people are really excited about it, which is WASI.

Okay, this is the WebAssembly system interface.

Essentially, you can compile against WASI and then your WebAssembly will be able to do things that you can normally do on a computer, like access the file system, access the network, access other things in the operating system through one unified interface that will work on all of the different platforms.

So if we're going to take our, markdown example again, let's say I write a Rust command line tool.

So it's called markdown cli.

You give it a markdown file and it just spits out the HTML version of that markdown file.

I can compile this to wasm32-wasi.

And then I will be able to run it using wasmtime.

So I still need to run it in some kind of virtual machine.

So if I install wasmtime, I can run this, wasm binary directly, and have it and use it as a command line tool.

Now the bit that's confusing about this is like this dir bit here.

What is this doing here?

Now, this is where it gets really interesting.

I have to explicitly give it permission to access a specific directory in the file system.

Okay?

Part of what WASI and WasmTime is aiming to do is to keep the sandboxing and the security stuff that we're used to on the web and have that available to us in our local development, in our local programs as well.

And, we, yeah, so yeah, sandbox, sandboxing, like we have on the web, where you might need to explicitly ask for permission to do certain things, but we can have it locally as well.

Who just clicks yes to this all the time?

Yes.

We like to tell our parents, don't download and install random things from the internet, but we'll quite happily install, 50 different npm packages, and if one of them is malicious, then, we might have some problems.

Wouldn't it be great if we could have web like security and sandboxing for the stuff that we run on our own computers?

That would be really cool.

So WASI is relatively new, but, NodeJS has support for it, in an experimental state, so you can have a WASI compiled web assembly binary, and call it from NodeJS .

And if you look at the bottom there, you can say when you go

from NodeJS into WASI, you can explicitly grant it permissions there.

Okay.

This is still pretty, pretty early bleeding edge stuff.

So over the next few years, this is some, like some stuff that's very, very well established already.

We've covered in this talk, some stuff that's very new, and not quite widely used yet.

But WebAssembly, it's already pretty fast.

So this is running in Wasm time.

It's exactly the same code as the Rust code just compiled into WebAssembly, and it is a little bit slower.

But this was before WebAssembly had some new features around threading and like faster CPU instructions and stuff.

WebAssembly is in the process of getting faster.

Expect WebAssembly to show up in more places, in more different circumstances, right?

We're going to be using WebAssembly for easier cross platform, native like performance.

Using it for sandboxing, security for local dev tools.

Stay tuned for the next talk after this one, because it's about the component model and how, how WebAssembly has, added that to make it even more powerful.

And I'm hoping, personally, that as we start to use more WebAssembly based local development tools, it will make it easier for us to then be able to run those in the browser, and we could have Figma, but for developers, running in the browser without having to spend a lot of money on server side computes stuff.

The other really exciting thing, is WebAssembly for cloud computing.

Solomon Hykes the co-founder of Docker, has said, if WASM and WASI existed in 2008, we wouldn't have needed to create Docker.

That is how important it is.

Web assembly on the server is the future of computing.

Now this is all very don't believe the hype, but it is pretty powerful.

You don't need to run everything in separate virtual machines just to have security when you're running untrusted code on the cloud if you can run them in these WebAssembly virtual machines where the permissions are built in and enforced in WebAssembly without having to run it on a virtual machine.

We'll see how that goes.

Don't be afraid to use WebAssembly.

It is very mature.

It's been around for a long time.

WebAssembly in the browser and just WebAssembly modules in Node are perfectly ready to use.

Very stable.

It's not the bleeding edge anymore.

But WASI and things like that are a little bit more bleeding edge.

But otherwise, if your app doesn't need WebAssembly and you're not building development tools, you shouldn't even notice it, right?

If you don't need WebAssembly, you can quite happily use other apps on the web that use it, you won't notice it.

You can use development tools, and you won't notice it because it works on your machine and you don't need to worry about what CPU and architecture and things that you have.

WebAssembly works best, like most technology, when it's working, you should know that it's there.

Okay.

Thanks.

Bye.

Have a good time.

Disclaimer:

When talking about different programming languages development ease-of-use and flexibility is very important.

I will be mostly ignoring this.

Speed.

Compute speed.

Compute speed is important for:

  • Games
The image features a screenshot from the video game "Ghost of Tsushima" showing a character overlooking a vibrant landscape with autumn-colored trees, misty forests, and mountains in the background with the sun casting warm light through the sky.

Compute speed is important for: Data processing

The image is a screenshot of Adobe Photoshop's interface with an open document displaying a large rock formation.

Compute speed is important for: Dev tools

  • Linting
  • Compiling/Transpiling
  • Bundling
  • Testing

JavaScript is slow

JavaScript is surprisingly fast

Interpreted

  • JavaScript
  • Python

Compiled

  • C
  • C++
  • Rust
  • Golang
Slide divided into two columns. The left column, labeled "Interpreted," lists programming languages like JavaScript and Python. The right column, labeled "Compiled," lists programming languages including C, C++, Rust, and Golang.
Java?, C#?, TypeScript? are added to the slide at the right of the first column. "Unhelpful" is then stamped over this

Runs in virtual machine

  • JavaScript
  • Python
  • TypeScript
  • Java
  • C#
  • PHP

Runs as native code

  • C
  • C++
  • Rust
  • Golang

n-body problem benchmark (Input: 500000)

a table with a benchmark comparison of different programming languages on the n-body problem with an input size of 500,000. The table lists each language along with the time taken to complete the benchmark, the peak memory usage, and the version of the compiler or runtime used for the test.

The results are as follows:

  • C completed in 37 milliseconds using 1.3 MB of memory, compiled with gcc 13.1.0.
  • Rust took 43 milliseconds with 1.0 MB of memory, using rustc 1.69.0.
  • Go finished in 55 milliseconds, using 2.9 MB of memory, with go 1.20.4.
  • C# had a time of 109 milliseconds and used 27.8 MB of memory, running on dotnet 7.0.203.
  • JavaScript completed in 117 milliseconds, using 49.8 MB of memory, with node 20.0.0.
  • Java took 164 milliseconds and had a memory usage of 40.7 MB, using openjdk 20.
  • Python was the slowest, with a time of 3179 milliseconds and using 8.4 MB of memory, with cpython 3.11.3.

JS was not designed to be optimised

let a = 10
let b = 3
eval('a = "hello"')
console.log(a + b)

So how fast can we make JS?

It's March 2013...

Harlem Shake videos are everywhere...

Mozilla introduces asm.js

asm.js - a limited subset of JS

function strlen(ptr) {
  "use asm";
  ptr = ptr|0;
  var curr = 0;
  curr = ptr;
  while ((MEM8[curr>>0]|0) != 0) {
    curr = (curr + 1)|0;
  }
  return (curr - ptr)|0;
}
  • "use asm" - Signals it can be optimised
  • ptr = ptr|0; - Enforces 32bit integer type
  • var curr = 0; - Stores heap in MEM8 array

asm.js - Not intended to be hand-written

Image visually represents the workflow of compiling C and C++ code into a subset of JavaScript known as asm.js using a tool called Emscripten. There are two document icons labeled "C" and "C++" that point to another document icon labeled "JS (asm.js)," indicating the compilation process.

asm.js - a limited subset of JS

  • Approx 1.5x slower than native code
  • Perl, Ruby and Python in the browser!
  • Games ported to the web!
Screenshot of an article titled "Unreal Engine 3 ported to JavaScript and WebGL, works in any modern browser"

asm.js - a limited subset of JS

Pros:
  • Can run on the web!
  • Sandboxed!
  • Pretty fast!
Cons:
  • Constrained by compatibility with JS
  • Produces very large JS files

Enter WebAssembly!

(aka wasm)

WebAssembly (text format)

(module
  (import "console" "log" (func $log (param i32)))
  (func $main
    ;; load '10' and '3' onto the stack
    i32.const 10
    i32.const 3

    i32.add ;; add up both numbers
    call $log ;; log the result
  )
  (start $main)
)

Table of Runs in Virtual Machine and Runs as native Code reproduced from before. "WebAssembly?" appears across both columns.

WEBASSEMBLY

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine.

Table of Runs in Virtual Machine and Runs as native Code reproduced from before. "WebAssembly" appears across in the Virtual Machine column.

Runs in virtual machine

  • JavaScript
  • Python
  • TypeScript
  • Java
  • C#
  • PHP
  • WebAssembly

Runs as native code

  • C
  • C++
  • Rust
  • Golang

Demo time!

Let's build a Markdown to HTML converter

Katie demonstrates creating a markdown processing app and narrates the process.

Let's build a Markdown to HTML converter

/**
* @param {string} markdown
* @returns {string}
*/
export function to_html(markdown) {

}

Steps

  1. Start with a normal Rust library
  2. Compile to wasm + JavaScript bindings
  3. Build the rest of the app

1. Start with a normal rust library

github.com/wooorm/markdown-rs

markdown-rs

2. Compile to wasm + JavaScript bindings

$ cargo add wasm-bindgen
use wasm_bindgen: :prelude::*;
# [wasm_bindgen]
pub fn to_html (markdown: &str) -> String {
	return markdown:: to_html (markdown) ;
}

2. Compile to wasm + JavaScript bindings

import init, { to_html } from "./rust/pkg/wasm_markdown_demo.js"
// Init is async (includes fetching the wasm file)
init().then(() => {
	// Call to_html as a normal IS function
	const html = to_html('# Heading')
	console.log(html) //'<h1>Heading></h1>'
}
Katie narrates as she demos the steps

What is going on in those generated JS files?

Calling init (simplified)

// Fetch the .wasm file
const modulePromise = fetch(new URL('wasm_markdown_demo_bg.wasm', import.meta.url));

// Specify JS functions we can call from WASM
const importObj = {}

// Instantiate!
const instance = await WebAssembly.instantiateStreaming(modulePromise, importObj)

// Then you can call exported functions on wasm instance
instance.exports.to_html( /* ... */ );

    /**
    * @param {string} markdown
    * @returns {string}
    */
    export function to_html(markdown) {
      let deferred2_0;
      let deferred2_1;
      try {
        const ret_ptr = wasm.__wbindgen_add_to_stack_pointer(-16);
        const ptr0 = passStringToWasm0(markdown, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
        const len0 = WASM_VECTOR_LEN;
        wasm.to_html(ret_ptr, ptr0, len0);
        var r0 = getInt32Memory0()[ret_ptr / 4 + 0];
        var r1 = getInt32Memory0()[ret_ptr / 4 + 1];
        deferred2_0 = r0;
        deferred2_1 = r1;
        return getStringFromWasm0(r0, r1);
      } finally {
        wasm.__wbindgen_add_to_stack_pointer(16);
        wasm.__wbindgen_free(deferred2_0, deferred2_1);
      }
    }
  

Code from above with the following line highlighted and the annotation below

const ptr0 = passStringToWasm0(markdown, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);

First, copy the JS markdown string into wasm memory.

Code from above with the following line highlighted and the annotation below

wasm.to_html(ret_ptr, ptr0, len0);

Pass the start pointer and length of the markdown string to the wasm function.

Our markdown app probably isn't faster than JS

WebAssembly can do incredible things.

Windows 2000 - Virtual x86

copy.sh/v86/?profile=windows2000
Screenshot of Windows running MSPaint in a browser window.

But it won't really help the performance of most web apps.

Not all software is on the web

Using Wasm in NodeJS

$ wasm-pack build --target nodejs
Demo of running the app. Katie narrates.

Using Wasm in NodeJS

$ wasm-pack build --target nodejs

esbuild

Homepage of ESBuild

esbuild package dependencies:

a long list of ESBuild targets

Wasm - one binary to rule them all!

  • Native-ish speeds with one binary instead of 22

OR

Webassembly fallback for unsupported platforms (e.g., esbuild-wasm)

But we still have to pass data between JS and Wasm...

Wasm can't directly access the filesystem, network etc.

WebAssembly System Interface

Announced 2019

Rust CLI Markdown Converter

$ markdown-cli test.md
<h1>heading</h1>
<p>paragraph</p>
  

Compile to Wasm + WASI

$ cargo build --target wasm32-wasi

Compile to Wasm + WASI, run with wasmtime

$ cargo build --target wasm32-wasi

$ wasmtime --dir=. demo.wasm testfile.md

  <h1>heading</h1>
  <p>paragraph</p>
   
  

Web-like sandboxing for local dev!

Do you trust the authors of the files in this folder?

Code sandbox warning dialog
Page at node.org for WASI support showing experimental.

Expect WebAssembly to get faster

n-body problem benchmark (Input: 500000)

Repeat of the table from before. Shows 70ms for Wasm, faster than all bit C, Rust and Go.

Expect more WebAssembly

Expect more WebAssembly

  • Easier cross-platform performance
  • Sandboxing and security for local tools
  • Code reuse between languages
    • See the next talk on the component model!

Better in-browser development tools

Local development

Wasm-based tools

Web Browsers

?????

The image contains a slide with two boxes connected by an arrow. The first box on the left is labeled "Local development" and contains the text "Wasm-based tools". The second box on the right is labeled "Web Browsers" with "????" indicating unknown or future development.

WebAssembly for cloud computing

"If WASM+WASI existed in 2008, we wouldn't have needed to create Docker.

That's how important it is. WebAssembly on the server is the future of computing."

Solomon Hykes, co-founder of Docker

Don't be afraid to use WebAssembly.

Thanks!

You can find me at: