 Are you ready, Jake, to get your acting phase on, and... I'm going to pretend to be surprised by all the content, and it's totally not the third time we've tried recording this. Tech-knowlogy. So, assembly script. We've actually talked about it before, like we mentioned it, and kind of introduced it a little bit. That was in our episode about loops. Loop tiling. Yeah, I think that's where we talked about it. This is an assembly, a web assembly topic. This assembly script is a language for WebAssembly. And we've talked about WebAssembly in general as well before, but I think most of the times I showed WebAssembly a bit like this, which is the WebAssembly human-readable text format. And I think you pointed out that many humans might not enjoy reading this at all, even though it's, you know, letters. The common file extension for this file format is what? And it's very appropriate. I mean, to be fair, as far as assembly languages go, it is a good one. I actually, I think it's more readable. That's like saying, as far as the Kray twins go, Ronnie is the best, right? You know, it's a low bar. It's a low bar, sure. But it's there. It can be up to what I agree. Like, this is not how you want to write code. It's not how you want to read code. This is not usually where you're going to put everything in production like this. And I think the folks behind assembly script kind of agree because the alternatives currently are there to use C and C++ or Rust, which are, I mean, there's more languages now compiling to WebAssembly, but I would argue C and Rust are the most predominant ones, the most well-known ones. And I think part of that is that, you know, as a web developer, we live our lives. We live and breathe JavaScript and TypeScript. And none of these languages like C and Rust are necessarily in our daily work. I mean, maybe you know them, but if you don't and you're a web developer and you only use JavaScript and CSS and HTML, WebAssembly seems a bit inaccessible. And that's exactly, I think, what the assembly script folks wanted to change because it's a language designed for WebAssembly. So in contrast to like C and Rust, which are languages that already existed and that then added support for WebAssembly, assembly script was designed for WebAssembly specifically. It is, you know, it is still fairly low level. That's why they're called assembly script. And as you can see here on the landing page of the website, they're saying they're using the familiar TypeScript syntax, which is really nice because I think most of us are familiar with the TypeScript syntax because it is pretty much the JavaScript syntax with a couple of additions here and there. Yeah. And I would say even if you're watching this and you're not familiar with TypeScript, yeah, the important bit there is it's additions to JavaScript. So like you'll look at TypeScript and understand most of it and you'll be able to see like there's just a couple of extra bits. Yeah. Exactly. Yeah. And although one thing we should be clear about and they are very clear about this on the repository, it is definitely not a TypeScript to WebAssembly compiler. It just reuses the syntax and the language and as far as they can, the semantics. So code that you know, you read in assembly script looks like TypeScript. You think you know what it does. It is very, very likely that that's also what it actually does once you compile it. But it is not the case. You can take just existing TypeScript, throw it in the assembly script and get working WebAssembly out the other side. And when you say that, it sounds like you're going to have this huge uncanny valley problem because it's, oh, it looks like TypeScript, but it's not TypeScript. It's not going to work like you expect TypeScript to work. It's going to work like something else. But in practice, it's fine. It actually works really well. Yeah. And I think that's the thing. They try to mimic the semantics of TypeScript as far as they can so that, you know, if it looks like TypeScript, it will behave like TypeScript as far as, you know, WebAssembly allows. It's just TypeScript in general has things that just don't work in WebAssembly. Like the same variable cannot hold a string, a number, an object, a class. You don't know until runtime those things don't work in WebAssembly. And so they had to add some restrictions to the language and also a different type system. And that's kind of what I wanted to talk about a little bit. But before, I think it's kind of important to talk about why would you even use WebAssembly? And I think there's four major reasons why you would be looking into WebAssembly. And the main reason we have seen so far in the WebAssembly ecosystem is making use of existing code. So we have MScripten, the compiler for that brings C and C++ to WebAssembly, Rust supports WebAssembly. And so that allows you to make use of the entire ecosystem of these two languages and bring them to the Web even though those libraries might not have been written with a Web in mind. And that's really cool because not everything is available in a Web-compatible language. And I think that's something that we kind of exploited almost with Scooch because yeah, there might be, you know, a JPEG encoder written in JavaScript. There might be a PNG encoder in JavaScript. But I don't think anyone has written down a set down yet and wrote a AVIF encoder in JavaScript or a JPEG XL encoder. So we were at a loss until either the browser shipped those or we sat down and wrote them. But instead with WebAssembly, we can make use of these libraries written in C and C++ and just bring them to the Web. And that's really cool. However, as I just said, with AssemblyScript, that's kind of the case because, you know, there's now some AssemblyScript specific libraries out there, but it wasn't targeting an existing language. So the whole, you know, bring legacy code to the Web thing wasn't really an argument for AssemblyScript to begin with. But it is one of the major use cases that we see for people to look into WebAssembly. The next point, which, you know, many people bring up is performance. And even though, like, I've been very, very vocal about the fact that WebAssembly and JavaScript for the longest time had the same peak performance. They were optimized by the same engine. They both generated, you know, machine code under the hood. And so really, they have the same peak performance they could reach. The difference is that for JavaScript to get optimized, it needs to run. It needs to be observed because just from the code, you cannot tell what kind of type a variable has, which you need to generate machine code. While with WebAssembly, that is already encoded in the WebAssembly file. So the engine for JavaScript needs to run the JavaScript, observe it and then starts optimizing as it runs the code. And many JavaScript optimization experts have, you know, have experienced this warm-up phase, as they often call it, when your code runs slower at the start and then gets faster, which is often also seen in benchmarks. And they run a couple of loops, which they don't measure because warm-up. In WebAssembly, the optimizing compiler kicks in immediately. So we have a fast compiler, which is called lift-off, which generates code really, really fast at the cost of not generating optimal code. And then the WebAssembly can start running as fast as possible. But the second that compiler is done, the optimizing compiler called Torbofan kicks in and starts optimizing one function at a time and switches them out bit by bit, even if you haven't even run your WebAssembly. So just with a bit of waiting, you will just get optimized WebAssembly code under the hood. Yeah, I think it was actually Williams who said the difference between JavaScript and WebAssembly is that the performance is reliable with WebAssembly, as in, you're in control of when things like garbage collection happen and that sort of thing, and yeah, a lot of the optimizations happen up front. So it's easier to measure the performance of WebAssembly code and it's not going to change in between runs. Exactly. I think that's really well put. It makes just WebAssembly is just much more predictable in how it will behave because in JavaScript, you also have this phenomenon that, you know, you can call the same function with a string and with an object and with an array. As I said, like the engine observes, if this function gets called with a string all the time, then it generates machine code for handling a string. But the first time you call it with a different type, it then has to fall back to interpreting because the machine code is wrong now if you call it with an array all of a sudden. And that's called a deoptimization, a deopt, which will slow you back down. And so while you can reach peak performance with JavaScript, it can also be kind of fall back to becoming slower with WebAssembly. That's not possible because everything is strongly typed. The third point, and there's actually something that you already kind of mentioned now, which is there's more performance. On the one hand, WebAssembly is getting increasing access to things that JavaScript doesn't have access to. Like actual shared memory concurrency with threads, SIMD, which there was proposals back in the day for JavaScript, but they've banned them all in favor of WebAssembly. And what you just said, because you are or the language decides what part of the runtime to ship thinks like garbage control can be under your control. So in JavaScript, there is a garbage collector and it will run. And sometimes you don't want it to run, but there's not much you can do about that except, you know, bend your code backwards to prevent garbage collection in the first base by keeping references around. But in WebAssembly, you can actually change the garbage collector for something that works more in how you like it. And that is like something I think game engines, for example, would really, really like because they often like to decide when there is a good time for garbage collection and when it is not a good time for garbage collection. And lastly, and this is going to be interesting, is binary size because the spec says that one of the design goals is that the WebAssembly binary format, the virtual, the bytecode file is a very small binary representation of the code that it contains. But then, I mean, the codecs we have in Squoosh are not small, but is that just because they're a big code or is that down to more of the like the standard library sort of stuff that C and Rust need? It's all of the above, really. On the one hand, an image encoder will have a lot of logic. And I think if you wrote in JavaScript, it would also be quite big. But at the same time, it is what you said that, you know, WebAssembly is a pure abstract VM. And in JavaScript, we already have, you know, people often say JavaScript doesn't have a standard library, but the web platform does provide a huge amount of code that is just in the browser. And you can handle strings, you can handle arrays, you can iterate over them, you can map them, you can filter them. You can split strings, join strings, filter strings. All these things are just there. If you do use that logic in WebAssembly or in a language compiling to WebAssembly, that code needs to get compiled and put into the binary as well. And then lastly, there's also the aspect that WebAssembly has a strictly numerical interface to JavaScript. So whenever you want to pass anything that is not a number, an object, an array, a string from JavaScript to WebAssembly and back, there needs to be a bit of JavaScript, which is called the glue code often that converts between what the WebAssembly file understands and what JavaScript understands. And that is also code that we actually ship over and over in Squoosh. So we have actually not made great experiences with binary size, although I will say, A, that WebAssembly compresses really, really well with broadly in Gzip. It is very compressible. Often, you know, 80% of the files that just goes away and you're left with 20% of the original size. But also that you can make these modules very, very small at times, but with tools like MScript and Rust, it is quite hard to remove. Like it just assumes that you need a lot of standard library even if you don't. And it sometimes takes a lot of work to get rid of all these bits that you could sometimes do in a different way. But we've seen, and I think we talked about this in our loop tiling episode where we made the WebAssembly actually be smaller than the comparable JavaScript code. Yes, yes, we did. All right, I think that was enough waffling. It is time for some codey bits, at least something that is close to code, which is an empty install command. It's closer. It will lead us to the code, Jake. You don't worry. You have seen this slide before three times. You know you don't have to worry. So with this command, you install assembly. It is actually kind of nice that it's just on NPM. It uses WebAssembly itself under the hood, which I thought is kind of interesting, but you don't really notice when you use it. If this is too much of a commitment for any of our viewers, you can also just go to WebAssembly.studio where you can try out MScripten with C and C++, Rust, and AssemblyScript in a little IDE in the browser that compiles in the browser. You can just play around without having to install anything. So both of these paths are completely valid to play around with WebAssembly a little bit. All right, time for our first AssemblyScript code. This is a TypeScript file. In this case, it's an AssemblyScript file. It exports a function called main and returns 42. Anyone who knows TypeScript should be fairly comfortable with reading this. We can compile it. So the AssemblyScript compiler is called ASC because it's the AssemblyScript compiler, very intuitive. And we pass it our main.ts file and with dash B, we say the binary output file should be main.wasm. And now the only thing left to do is really load this file somehow in the browser, you know, probably with an HTML page and that would look like this. We just use WebAssembly.InstantiateStreaming and throw the fetch in there of the Wasm file. It will give us back an instance and every exported function will be on instance.exports. So you really get a WebAssembly module instance and that module has exports just like the modules we have been writing in that AssemblyScript file. And so there's like a really nice clear mapping between WebAssembly modules and code modules if you want to talk about them that way. And this would pretty much exactly log the number 42 into your console. Success. If you're into it, the compiler can also generate this human readable text format I showed at the start that can be helpful sometimes to see if certain exports are present or suddenly have a different name or maybe even you want to see what the function looks like. It can sometimes be helpful, but it's definitely not required to use this at all. What is important is that the AssemblyScript compiler by default uses no optimizations to make it a fast but also to make source maps work really clearly. There's a very clear mapping from WebAssembly code blocks to individual commands in the AssemblyScript file which means you can step through your AssemblyScript code in DevTools which is really, really nice for a debugging experience even though one thing I should note something that doesn't work is like inspecting variables like you can't hover over a variable to see its current value because source maps cannot contain that kind of mapping but there is something in the works there as far as I know. But if you end up shipping your AssemblyScript WebAssembly to production, make sure you use the dash O flag which is an optimization path which will not only make your AssemblyScript code even faster but probably also significantly smaller. So this is quite important, but this is pretty much it. This is how you use AssemblyScript. And as you already pointed out the return type is a bit weird because if you know TypeScript you also know that this is not a TypeScript type. And I think this is so VS code if you use it will probably put a nice red squiggly line under it. But this is one of the differences between AssemblyScript and TypeScript in that they changed the types from, you know Java has numbers and string while WebAssembly only has unsigned 32-bit integers which is what this has a couple more. I won't get into that. But you know these types are a direct mapping from what WebAssembly supports to the language that you're writing which is actually quite nice. So this is where it's different to TypeScript because in TypeScript the types are used as like validation as part of the compilation step but in AssemblyScript here like these are runtime types. Exactly, well they're both they're also validation at compile time but yeah that's exactly right that these carry over to the runtime and actually impact what kind of code is being generated. As I said you assemble VS code by default will probably put a red squiggly line under it. To fix that they do provide a default TS config JSON that you can just extend and then it will actually the red squiggly lines will go away and VS code will think this is kind of valid TypeScript so things like refactoring, jump to definition all those things will work and that's actually really neat. All right, let's make our AssemblyScript code a bit more interesting. We are now going to use an import we're going to import a function from a different module you can split up your code into multiple modules and import and export stuff like you're used to from JavaScript and TypeScript and the compiler will take care of like bundling it all together and you know linking and recognizing which function you're actually calling so that shouldn't be very surprising. So in this case I also create a utils.ts file and now we want to look at the implementation of alert obviously but there is none and this is really interesting because the declare keyword also exists in TypeScript and it tells the TypeScript validator basically that I assure you this function exists once you run this code I just haven't implemented it myself. Yeah, so it's common to use this like if there's an API that the browser supports but TypeScript doesn't know about it yet this is you use declare for that to say look it's here it looks like this. Exactly, and so you were saying you know we're declaring its function exists and that this module will export it which kind of doesn't make sense but you know we're kind of saying this is what reality will look like in the end and so what this means that now the AssemblyScript compiler will say all right I'm just going to you know note down this function that takes a U32 and I will be provided a function for this later and so if we now used our instantiate streaming call this would actually throw because it says hey the utils module expected an alert function but you didn't give me one I can't run this and so what you need to do is you actually need to provide a so-called import object for the instantiate function and it looks like this we're saying okay it's an object of modules and we can just provide functions and what you see here I'm literally just passing through window.alert which we all know and learn from our good old debugging days so this way it is really cool that we can not only call from JavaScript into WebAssembly or a main function but our main function can now call back into JavaScript through this imports object that's amazing so like so here we're passing a JavaScript function into AssemblyScript which we can call but presumably you could even you could compile some WebAssembly from C++ or Rust and take its exports and make them imports into AssemblyScript so you're using AssemblyScript to call Rust which is calling C++ which is calling JavaScript and that just all works that that's exactly you could do that and that's actually kind of fascinating I think is something that even we in Scooch don't do yet and we could look into that but yeah you can base just anything that is available in the host environment which in the browser is JavaScript you can just use that as an import and then that will just kind of work as long as you stick to the types that WebAssembly understands or that WebAssembly knows how to convert convert to JavaScript and vice versa let's take it a bit more interesting Jake I've asked you this quiz before but I will ask it to you again what do you think is different about this example than all our other ones? I don't think Array.push will work because this is not TypeScript it's not JavaScript this is AssemblyScript so like Arrays work but maybe not like Array methods work things like the prototype chain that sort of stuff is not going to be there right and no that is not right because this is actually what people mean when they say a language is shipping its own runtime AssemblyScript actually has an implementation for these growable arrays so all these methods do exist and they will cause code to be added to your WebAssembly binary and this is what I mean like it starts to chip more and more runtime the more you start using it but this works they actually have memory management and I think this for me is the big difference in this we are now in memory management territory because we can push into arrays as much as we want which means our memory usage can grow which also means at some point we can run out of memory and so the second you use this kind of code your instantiate streaming will start to fail again because we'll say hey I am expecting you to provide me in a board function because something we need something that gets called when things go wrong or AssemblyScript needs something that it can call when things go wrong and so it expects for this U32 array to have an abort method in case the push doesn't succeed because it might you know try to grow the memory and it is not available and the board function actually takes parameters so you can figure out which actual function from which actual file caused this abort so the way I'm handling the error here is probably not very elegant but I just wanted to explain why this is suddenly there overall I would actually recommend to make use of the AssemblyScript loader which takes care of all of this for you not only does it provide an abort function but it also generates the appropriate error messages to tell you in this file this function call on this line caused the abort and here is the actual error description fix your code, right? So the AssemblyScript loader takes care of all these little details under the hood for you gives you a proper abort function that's you know gives you in this file on this line something went wrong here's the error message fix your code so most of the time I would recommend using the AssemblyScript loader because it is actually not that big and just takes care of the basic fundamentals of like good error messages for you Right, what's not that big? Like that like Well, what are we talking here? Because the glue code for a script in and Rust can be quite big so Right, so the good news about this code is that the loader is not module specific with MScript you get glue code generated for each WebAssembly file and so even though they share some code you will you usually will end up double loading it the AssemblyScript loader you only need once for doesn't matter how many AssemblyScript modules you have and I think even then if you use everything it provides I think we're talking about 1K GZIP so it's definitely not something that should hurt you that much to load into your bundle which I think is really really nice Alright, let's make it a bit more interesting because as I said the interface WebAssembly supports is pretty strictly numerical but as developers we know that we work with more than just numbers most notably strings are usually a primitive that we often use and so you know I turned our main function now into a function that takes a name as a string and our alert function now also takes a string which you know in JavaScript land is true and so far that conversion just happened to work but now we want to pass an actual string value one thing that we will need to do is now to export the runtime because strings are runtime values they are not you know something I can just be passed along as a parameter but that needs to be a pointer to a string and so by exporting the runtime we can create those strings through the API that the module exposes and this is something again that the assembly script load I can take care of for you so it exposes two functions among many others a new string function that creates a new string in assembly script land that assembly script can then understand and a get string function that turns a number a message pointer back into a string in JavaScript land this is not very nice I think I kind of like the approach that they give you a non-automatic interface so that you are in control when the conversion happens and you exactly know you know no cost here is hidden from you you can see that there's extra function calls happening to make this interface work but I definitely feel that you know you have to know your interface very well and it is a lot of extra work and makes it very noisy we can't just pass through alert anymore we have to write a wrapper alert function there is something that does it better which is called AS bind which is a library from one of the core maintainers of assembly script that tries to do all of this automatically for you and that can be really really nice so if you want to use that I would recommend looking at a read me and even this library I actually don't know how big it is but it is still way below the glue code that we usually see in some of the the more established languages all right one last thing I want to talk about which I mentioned earlier is the runtime in self by default the runtime that is used is called incremental so I have it explicitly as a flag here but you don't need to specify because that's the default that is a full automatic garbage collector that every now and then will just run and collect all the memory and freed up of things you don't use anymore that will add about three to four kilobytes of GZIP web assembly to your web assembly file depending on how many runtime functions you use and that's not too bad either I mean like we start to get concerned about that kind of file size when it's blocking your initial render or you know blocking fetching some data or something like that but we tend to use web assembly in a way that's it's lazy loaded right it's you get an initial render down and then the web assembly comes to do something else so in terms of that a few K is nothing exactly I think it's an absolutely acceptable payload but it is in garbage collector that will just run every now and then I think they just inject garbage collect calls at the end of functions or something I don't know but as I said you know sometimes when you have a really high performance use case like a game that wants to ship 60 frames a second that might be unacceptable and for that they provide a second runtime which is called minimal it is still a full garbage collector with memory management and everything but it doesn't garbage collect until you explicitly call the function to tell it to garbage collect and that can be really nice so sometimes it might be worth your while to you know build up some unused memory that is still allocated and then once you know the level is over you're showing your high score at the ends where the frame rate isn't as important anymore or you know there's not much input happening that's where you run the garbage collector and I think that's actually really interesting there even is a stop runtime which we used in the past for our image rotation example because we actually just we exactly knew what kind of memory we need how much we need we didn't want to free up any memory and that basically reduces the 3k almost completely from the file I think it's about 500 600 bytes gzip that are left because you can still allocate memory but you can't free it anymore but this is completely adequate for these modules that you instantiate you run them and you throw them away which is exactly what we did with the image rotation we just create a new instance put in the image waited for it to rotate the image got it back out and then the module gets destroyed and if we want to rotate again we create a new one and in that case you could actually use a sample script even for render critical code I probably wouldn't recommend that because you still need to load it and instantiate it but yeah you can you can really squash the file size down you could even this flag even accepts a file so if you wanted to you could write your own runtime but we're not going to get into that because that obviously needs a bit more detailed knowledge about some about web assembly and memory management and let's start easy but the design here is brilliant right and it's something that a lot of you know JavaScript projects JavaScript libraries JavaScript frameworks could really learn from this layered approach because it's great to know that well okay so we've given you the the easiest thing but here's a flag you can swap it out for something simpler you can even write your own like you can control the whole stack if you want I really like this design I also think it's it's a very nice approach and it's very extensible in general and they have a discord linked on their main website which is linked here which is super helpful community so if you are trying to get started you're getting stuck there's always someone in that discord channel willing to help but this was basically my assembly script speedrun introduction yes I would say like if you're unfamiliar with lower level programming languages like like me so I don't have a lot of background in C or C++ I found assembly scripts really easy to get started with because it was in that familiar JavaScript-esque TypeScript-esque environments but it was just introducing the more manual memory management stuff and sort of lower level types I found it really easy to get going with and write some really optimized WebAssembly code well there's no better way to end them with that if the light falls over it will change the shot quite dramatically and it'll knock all kinds of shit over it's right okay it'll be funny if it falls over won't it? breaks your nose blood everywhere humor