The CPU in your browser: WebAssembly demystified
In the recent years we saw an explosion of usage of Python in the browser: Pyodide, CPython on WASM, PyScript, etc. All of this is possible thanks to the powerful functionalities of the underlying platform, WebAssembly.
In this talk we will examine what is exactly WebAssembly, what are the strong and weak points, what are the limitations and what the future will bring us. We will also see why and how WebAssembly is useful and used outside the browser.
This talk is targeted to an intermediate/advanced audience: no prior knowledge of WebAssembly is required, but it is required to have a basic understanding of what is a compiler, an interpreter and the concept of bytecode.
The introduction will cover the basics to make sure that the talk is understandable also by people who are completely new to the WebAssembly world, but after that we will dive into the low-level technical details, with a special focus on those who are relevant to the Python world, such WASI vs emscripten, dynamic linking, JIT compilation, interoperability with other languages, etc.
This session took place in track Python & PyData Friends and was classified suitable for intermediate domain / novice python by the speaker.
Transcript (auto)
Auto-generated from the recording utilizing Open-Source AI. Speaker labels (Speaker 1, Speaker 2) reflect diarization, not identity. Timestamps refer to the recording.
Speaker 1 [00:03]
Hello, everybody. Thank you for the introduction. Actually, I prepared a slide for introducing me, but it seems that it's not needed. So, yes, I work at Anaconda. This is my Twitter and GitHub handle. These are some of the projects which I am involved with. I'm working at Anaconda on PyScript. PyScript is Python in the browser. Maybe we'll talk about it a bit more later. And that's why I became interested in WebAssembly in the last year, more or less. And so, yes, This is why I decided to do this talk, because it's a nice technology, it's very promising, everybody talks about it, let's try to understand what it is. So WebAssembly, it's a cool technology, apart from that it's not web and not assembly. But apart from this, we will see why, in which sense it's not web and not assembly, but it's not really for the web. It's used in the web, it's useful for the web, but it's much broader than this, and that's why I'm excited about the technology, because I think it can be a revolution and will be used in many, many fields. And actually it's not an assembly, usually with assembly you mean a language, but it's not a language that you write in. So what is it? I did what everybody does these days, I asked the chat GPT. And so WebAssembly is a binary instruction format for a stack-based virtual machine. So this is the important part. So it's a virtual machine, and virtual machine means basically that it's a CPU. That's why the title. It's a virtual CPU which can execute binary code for a specific instruction set. And of course, it is available in the browsers. So it's a web standard, and all the browsers, all modern browsers, including the ones in the mobile, can support WebAssembly, so basically you have a virtual machine which runs in your browser and can run low-level code. And this makes it the most ubiquitous virtual machine ever, because if I compile something on WebAssembly, I can run it literally everywhere. And this is like one of the reasons why it's so cool, honestly. Another important property of WebAssembly is that it's completely sandboxed and saved. We will see later what it means. But generally speaking, it means that you can execute arbitrary code, untrusted code of WebAssembly in your browser or on your server, and you can be sure that this arbitrary code cannot make any harm to your system. It's a virtual machine, and if you want to execute a program which was compiled to WebAssembly, you need a runtime to execute it. As I said, all the browsers include WebAssembly virtual machine nowadays, but they are not alone. So you can use WebAssembly in Node, for example, Node.js, so like without the browser. But you can also use other runtimes, for example, there is Wasm time, which is one of the most popular ones, there are a lot of them. Like yesterday I was looking on Wikipedia and there are like 20 of them. But yes, each of them with pros and cons and various different properties. I hinted at the fact that WebAssembly, we can think of it as a CPU, so we can compile languages to it. And the interesting fact is that WebAssembly was designed with the idea that you can compile C code and C++ code to it. And it's designed to be fast, so that it's a bit slightly higher level than, for example, x86 or ARM, but it's low level enough that the WebAssembly runtime, which can be, for example, a JIT compiler, can generate machine code which is almost as fast as native code. So, like, this is why it's also important, because you can get near native performance, and again since it's ubiquitous, it's like compile once, run everywhere. And if you think about it, it is kind of the same promises that Java said 20 years ago, like compile once, run everywhere. But the big difference between Java and WebAssembly, like the JVM and WebAssembly, is that WebAssembly is a low-level virtual machine. It talks about pointers and integers and floating points. It doesn't have any notion of objects or interfaces or things like this. And this is a good target for C, and that's why you can take a lot of legacy code and compile it to WebAssembly and with enough effort, it works. So it's a low-level virtual machine, which would be be nice to call it LLVM, except that we cannot, because LLVM was already taken by something which is not a virtual machine, it's a compiler, because this reminds me that naming is very hard in computer science, and as a computer science we are not very good at it, but anyway. Web assembly. I could decide in many different directions for this talk, and at first I would like to go a bit low level and understand exactly what it is under the hood. I cannot go too much in depth because I don't have enough time, but let's start. We have a very simple function in C, which computes the factorial number. I hope it's correct. And we can compile WebAssembly. This is just the common line. Don't spend too much looking at it. We can can use C Lang, which has a WebAssembly back-end, so that it can meet WebAssembly code, and I pass various options, and I get a factory.wasm, which is the output file. And let's see what is inside. And now I'm going to show you a disassembled version of the binary. So the compiler emits the binary code, but then there is, like, we can disassemble it and see in a human readable way what's inside. This format is called WebAssemblyTextFormat, and it's abbreviated as Watt. And this is the main reason why I want to talk about this, because I want to include this slide. And it looks like this. So this is like the factorial function that we saw before, and you get a WebAssembly module, So it's like the what format is showed in S expression. So it's similar to Lisp, but the actual syntax is not very important, but it's important to understand what are the underlying properties. So in a WebAssembly module, we have one or many functions, and in this case, we have this one, which has a type, which was declared somewhere else, but basically a type of factorial is you take an integer and return an integer, and you see that you take an i32 parameters and it returns an i32. So it's like straightforward translation from C. And then you have this kind of thing, so you can have a local variable, which is, again, an i32. We have another one. This is, I suppose, this is what in C was the variable res, which holds the result, and this is probably the variable i for the for loop. And then you have this kind of instruction. So you can see, like, it's low-level enough so that if you're a C compiler, you can emit code easily, map into it. And if you are a WebAssembly runtime, you can easily, with enough quotes, emit assembly code for it in a fast and efficient way, because this local variable becomes a CPU register, and this becomes a move, and things like this. And this is the core of the logical factorial, so we have a loop, and at the end of the loop we branch back to the label L1, which is the start of the loop, and inside the loop you can see that, for example, this is i plus 1. And so we get the local variable L2, which was i, we add it with 1, and then we multiply it with res, and we store it again, and blah, blah, blah. So this is WebAssembly. I'm not going more in detail than this, because we need two hours if I go more in detail. But at least you have a brief understanding of what it is. And once I have a WebAssembly module, I can run it. And as I said, we can run it in different contexts. The most used context nowadays is inside a browser, so we can use JavaScript to fetch, instantiate a WebAssembly module and get a reference to the functions in it. So this is the code. It's not important to go in detail, so this is, yes, the JavaScript API to instantiate the WebAssembly module, and then once we have it, we can get the exports which are the functions which are available from the outside, and then I get the reference to the factorial, and then I can, for example, ask JavaScript to call the WebAssembly function which was written in C and ask for the result, and let's see if the live demo works. It works. Good. But the other cool thing is that, as I said, it's ubiquitous, so you don't have to use JavaScript or the browser. Like, we have the C function, we compile it to WebAssembly, we run it in the browser, but we can also store it somewhere on our machine and run it from Python, and for running it from Python, we need a WebAssembly run time. In this case, I'm using the WASM time module from Python. You can just pip install one time or condensate one time, since I work for Anaconda, and then this is the API. Again, I don't know. I just copy and paste. It's not important, the details. But then you get a Python function that you can call, passing the numbers, and it's fast, much faster than Python, so it's a good way for embedding code which for some reason maybe you don't want to, you need it faster than Python, but maybe for some reason you don't want to allow arbitrary, untrusted code written in C, because arbitrary code written in C can be unsafe. So for example, it's very common to use WebAssembly for plugins. If you have a big application, you can use WebAssembly, like you can tell people that they can write plugins in WebAssembly and you can run them in a safe way and knowing that they cannot get other data and crash your application or whatever. I'm not going to demo this. It works and it prints 120, but yes. So very brief overview of what is WebAssembly. We can compile C code or assembly script or whatever to WebAssembly and we can run it. What you can do with WebAssembly? The first thing you can do is computation. You can burn your CPU because you can do a lot of Factorias, Fibonacci and a lot of computation blah, blah, blah, with integers and numbers and floats, and that's it. You cannot do anything else by default. And WebAssembly itself can only compute numbers, and you don't have access to the file system, you cannot do any network call, you cannot do any IEO, get input from the user, you cannot access any system call, you don't have even things that you can get for granted, like Like the libc, you don't have the abs function from the libc or things like this. Which I mean, what's happening here? Why everybody is using WebAssembly? And the tricky part, like the escape edge is what are called imports. So in a WebAssembly module, you can declare functions which are imported from the outside, And they can be called from WebAssembly, again, in a fast way, and they can be used to augment the functionality of WebAssembly, which by default is, as we saw, very, very limited. And so we have a host that in our example where JavaScript in the first case and Python in the second case, and we can provide function, like the host can provide function to the WebAssembly module. So for example, yes, I can let's see in the next slide, a common example is the file system. So suppose I am a programmer and I want to open a file or write to disk, and at the C level what you need to do is to call open and read and write, which are system calls, but by default they don't work, like there is no way of calling them. But the host can provide these functions to the WebAssembly module, and it's completely over-controlled. The host completely controls the behaviour of this function. So one possible implementation is just forward it to the operating system and let the WebAssembly module see the real file system. So if I open ATC pass-vd, then I can get it. But another option is that the host can fake a completely virtual face system. So like intercept the open system call, and they say, yes, the file blah blah blah contains these bytes. And if you write to something, instead of writing to the disk, you write to some buffer in memory, and then I can later read it. And you can see why you can do sandboxing with WebAssembly. Because again, it's similar to having a container, but it's much faster, much less convoluted, and smaller footprint, yes. Files are just an example, but you can apply this concept to everything, like including network calls. So what we had so far is WebAssembly, which by itself can only do computations. Then you can have imports that can expose, like fill the gaps. But if you have to do everything manually, then it's impossible. You cannot reimplement all the system calls of FosX and Linux every time you want to compile something. So there are solutions for this. And there are two big solutions. The first is Emscripten. Emscripten is a project which aims to make it possible to take legacy code which expects a lot of functionalities from the system, so the libc files, networks, various libraries, and package everything into an SDK which you can use to compile things to WebAssembly. So instead of calling GCC, you call EMCC, and Emscripten does a lot of things for you, and from the point of view of your C program, you are in a normal environment. You can open files, you can do network, and blah, blah, blah. But for example, the file system is completely virtual, which means that when you instantiate your WebAssembly application from JavaScript, you decide what is inside the file system. And this is a way, for example, to run in Python, we will see it later. And Emscripten implements most of this runtime library for emulating the operating system in JavaScript, which means that if you compile something with Emscripten, you can run it in the browser, where you have JavaScript, of course, or with Node.js, because Node.js is a JavaScript machine, so it can run the JavaScript code, but, for example, not with one time, because you don't have the JavaScript interpreter for the runtime system. Another alternative is WASI, which is Web Assembly System Interface, I think. We can think of it as a standard library of imports. So what happens is that forget Emscripten for a moment, and you are writing your C program and you want access to the file system or to the network and et cetera, and you see that you are writing the same imports again and again. So a group of people start to sit down and decide a standard and say that we should have this function for opening the file, this function for writing, this function for reading, this function for HTTP, this function for writing to the network, and blah, blah, blah. And the result is WASI, which contains many subsections, and it's a convention, it's a standard so that the user program and the host in the runtime can talk to each other in a standard way so that whenever I want to open a file, I know how to do it. And yes, WASI contains many parts, and for example, a host can decide to support only some part or only some other, depending on what the user module needs, or the user module can request certain capabilities and things like this. Again, it's provided by the host, and the host can decide if you want that, for example, the file system is the real one or the virtual one. It gives you a lot of flexibility. It can still be used in the browser, Because since WebAssembly WASI is just a set of imports, you can re-implement them in JavaScript, similar to what Emscripten did, and have a polyfiled version of WASI for the browser. So this is a very brief, very quick explanation of WebAssembly so far. But there are many, many more features coming in the next months and probably years. Because what happened with WebAssembly is that they developed what is called the MVP, Minimal Viable Product, which is the minimal set of features which was useful. And it was useful for legacy application, standalone, self-contained. But then they started to add more and more features, and the WebAssembly virtual machine is becoming higher level, like it's still low level for performance, but it's getting higher level features. For example, exception handling, or garbage collection, so that you can integrate the garbage collection of JavaScript, or it's getting the instructions for vectorization and things like this. And I think, this is my personal opinion, I think that what will happen is that WebAssembly will become a standard virtual machine for multi-language interoperations. So imagine that you can throw an exception from C++ and catch it from Python or Rust or other things. These are things that currently are not possible, but if everybody speaks the same low-level language, which is WebAssembly, they should become possible. So I think that this is what will happen in the next years, but we are not there yet. Since we are at Python conference, what is the relationship between Wasp and Python, and you can compile Python to Wasp, which means that Python is written in C, it's an interpreter, you can compile the Python interpreter to Wasp, and then run your Python code in a virtual machine which runs inside Wasp. So you have a virtual machine on top of another virtual machine. And this is like you can take vanilla C Python and compile it to Wasp and it works, but you you don't have access to the browser, for example, because it's just Wasp. And then you have Pyodide, which is a Python distribution for WebAssembly, which included Python, plus tons of already compiled scientific libraries, like PyPanda, a lot of libraries work out of the box with Pyodide, and then you have ways to interoperate with JavaScript. So from Python, you can run document.getElementById and do all the web stuff. You have micropython, which is for microcontrollers, but it's also compilable to WebAssembly, and it has a much smaller footprint, and it's faster to load. And all of this is the foundation of our PyScript, which is what I am working on, and it's a a way to run easily Python in the browser, and we, in the last weeks, as Anaconda, we launched PySuite.com, which is basically a hosting service for this kind of things. It's different than normal Python and web, because normally Python, like if you use Python for the web, you use it in the back end. Here we're using it in the front end, like the computation runs on the browser, which is completely a game changer. So this is the last slide. How much time do I have? Perfect. I think that we can go to questions.
Speaker 2 [23:30]
So, thank you, Antoni, for this super interesting insight into Basen.
Speaker 1 [23:34]
And...
Speaker 2 [23:36]
Well, I think we got questions. Are there already some questions in the room? Because we got one on Slido, but we're not in the room. No? Then we'll just start with the Slido one when it opens. So how is it possible for Wasm to be fast if provided with slow Python functions, databases, data classes?
Speaker 1 [23:56]
I mean, it's as fast as like Python is, it cannot be faster on Wasp than on x86. What happens is that you compile Python virtual machine to Wasp and use it to interpret your Python program. So your Python program is still interpreted, and actually it's a bit slower because WebAssembly is near native performance because it still has to do a bit of check about memory to make sure that you can access memory outside what what is provided so yes I don't know if I answered the question if the author is in the audience let's let me know if I didn't answer the question about this
Speaker 2 [24:40]
Question from the room, otherwise we proceed with Slido. Has anybody a question in person? Then suppose I'm compiling a piece of Python that reads from a file. Does the compilation simply crashes?
Speaker 1 [24:54]
Okay, I think there is again a confusion because you don't compile a piece of Python which reads a file. The Python program is interpreted by the Python virtual machine which is compiled to us. So what happens is that if you have your open statement in Python, you ask C Python, open the file, and see Python internally implement it using fopen, and then fopen is implemented by the runtime, depending on what you use for compiling. So if you compile it using mscripten, which is what Pyodide does, well, the fopen is implemented by the mscripten runtime, so you get access to a virtual file system which you control, You like the web page control. So for PyScript, what happens is that when you want to start Python inside a browser, we set up a virtual file system before the start, and in this virtual file system, we have all the standard library, we have all your application files, and so when you start Python and try to import main.py, then open it from the virtual file system and read it as normal. So that's how it works. And of course you can put also your own file, so you can put image.jpg, and if you try to open image.jpg, which is in the virtual file system, it just works. But yes, I hope I answered the question. I realized just now that I forgot an important project in this list, which is Jupyter Lite, which is like Jupyter, but completely running inside the browser, and it's based on Pyodide as well. So, yes, sorry for forgetting it.
Speaker 2 [26:53]
No problem.
Speaker 1 [26:53]
No problem.
Speaker 2 [26:55]
So let's proceed to the next question. So is there an advantage of using VASM in Python over directly calling C++ Rust code using tools like Py03?
Speaker 1 [27:06]
It depends what is your Well, yes, it really depends on what is what is the goal so if we are on the server or on your on my laptop What is the advantage of Yes, yeah Yes, it depends it depends because I can imagine different use cases So one use case is I run CPython on my CPU and I import a function compiled to wasp ASP, like the factorial example. And the advantage of this is safety. I can run untrusted code, and if I try to run native code which is untrusted, then I go into troubles, because untrusted code written in C can do everything that it was on my machine. So this is the main use case. Then the other use case could be that I compile Python to ASP, like CPython to ASP, So I run Pyodide in my machine. And in this case, the advantage can be that I can sandbox the file system, for example. So it's always about safety. If I am on the server. If I am in the browser, well, the point is that you can. Because you can run Pyodide in the browser, and without it, you cannot. So, I feel that these are the three biggest use case. So, I'm not familiar with how popular this technique you're mentioning is for web applications to have a WASM interpreter and then use Python built to WASM and so on. But don't you feel that the natural progression of all of this for browsers to start implementing the Python interpreter instead of JavaScript interpreter? No, actually, it's the opposite, is that until a few years ago, if you wanted to run something in the browser, you had to browser, you needed browser support. And now you can do it without the browser, and this makes it possible a lot more experimentation, And a lot of things were, apart from Python, but a lot of web standards now start in Web Assembly because they experiment with a new face recognition API. And you can write in Web Assembly now, and you could not write it five years ago. And then, after a while, the people sit down and say, okay, this is useful, let's make it a web standard and move it to the browser. So yes, in this sense, this can happen. But the point is that since now you can do it without the browser support, you have much broader possibilities. Did I answer your question? Okay. With WebAssembly, it's almost like we can now implement new standards. Yes. Exactly. WebAssembly is like we can implement new standards, yes, without waiting for the standard. We'll be right back.
Speaker 2 [30:12]
So the 30 minutes are now over. I have to say thank you to Antonio. Thank you. Thank you to the audience. Applause for Antonio, I think.