Step-by-Step JS to WebAssembly Conversion

Guy Bedford
InstructorGuy Bedford
Share this video with your friends

Social Share Links

Send Tweet
Published 8 years ago
Updated 6 years ago

Using the previous JS example (with some polishing), we start the conversion by copying the JS code into a C file. Step-by-step we run through the principles of converting this JS code into valid C code that we can compile into Web Assembly, based on the same principles we learnt in the initial lessons. We paste the C code into WASM Fiddle to get the resulting WebAssembly binary, and wire it into the application.

Demo repo: https://github.com/guybedford/wasm-demo

Note the demo is currently only supported in Chrome Canary with the Experimental Web Platform flag (chrome://flags/#enable-experimental-web-platform-feature) enabled due to the use of ES modules and WebGL 2.0.

[00:00] We're going to take this JavaScript application that renders circles into the page and convert it into WebAssembly. All the data for the circles is stored in a single circle data typed array which contains an xy and r value for each circle that is rendered into the page.

[00:15] Our initialization functions sets that initial state of the circles and our time step function then updates the positions based on the velocities and bouncing off the edges. The details of the functions aren't actually that important.

[00:30] For the conversion into WebAssembly the important thing is how this code interfaces with JavaScript and how we manage its interface. We're going to do the conversion into C. We create a new C file, and let's just copy and paste all the JavaScript except for the render call directly into the C file.

[00:48] We can now start the conversion. The circle count is going to be a constant of our compilation process so we'll set it up as a macro. For the circle data we could just set it up as an array of floats like we've done in JavaScript, but in C we can do better than that.

[01:05] The xy and r values of each circle can be represented in a C struct, with each entry set as a float data type. It's still effectively the same representation and memory as three floats in a row we had manually in our typed array.

[01:19] The velocity data can then be stored in the same way with the x and y components of velocity as floats. To actually initialize the circle data, we need to create an array of these circle structs. The way we initialize that in C is with the struct circle and then the name of the array that we're creating.

[01:38] We're going to statically set aside the full amounts of circles, which is 1,000 times the size of the circle, which is three floats. So we end up setting aside the same amounts of memory as we did in JavaScript with our explicit initialization, but with C it knows the size of the object so we don't need to add our multiple of three. We can do the same for the circle velocity.

[02:05] Now we're ready to write our [inaudible] time step functions. Converting their signatures into valid C function signatures, both these functions have no return value so they are void. The display width and display height are both float arguments.

[02:20] To iterate over the array of circle data, we no longer need to do this complex iteration. We can simply have a single integer which is iterating over the full circle counts and incrementing by one. C can take care of the rest for us.

[02:38] Updating the indices we then can access our struct items individually. The first item is now called x. We no longer need to do a custom increment, we can just represent the y value as y and the r value as r. The same for the velocity data.

[02:59] Let's apply the same conversion to the time step as well. These constant values are all 32-bit floats. Then we just update the indices. The last part of this code that's still JavaScript is these math.random calls.

[03:15] We're going to still be calling math.random underneath because we don't have access to another random source in WebAssembly, but we're going to make it as an external function. We can just define our math.random signature as returning a float value, and then we can call it random F.

[03:33] By simply defining the signature it will be treated as an external function and we can then replace those calls. The C file should really remain the source of truth for where the circle count is defined.

[03:49] It's annoying to have to define it as well within the JavaScript so let's create a function that will simply provide that. In order to actually get our data out of WebAssembly we need to know where the address is to the circle data object. This is the thing that we want to have access to from JavaScript.

[04:17] I'm going to create a helper function that returns an integer called get circle data offset. The return value is the memory address of the circle data array, when compiled this becomes the exact WebAssembly memory address.

[04:30] To quickly compile I'm going to copy the C code into wasm fiddle. Once the build completes successfully the .wast format comes up at the bottom of the page and we can then download the WebAssembly binary file.

[04:43] Checking the .wast output, we can see that we're importing the random F function from an environment module that we're going to populate shortly.

[04:50] We're also exporting our memory as well as all of the functions that we've created in our C file in a new file that's now wiring the WebAssembly. First copy in a helper function to load the WebAssembly. I'm going to call it with the path to our WebAssembly file which I have located in a folder called libdynamics.wasm.

[05:12] The second argument contains the imports for the wasm module. We need to set the environment import which contains a random F function, which we assign from math.random. This returns a promise which resolves to our instantiated WebAssembly module.

[05:26] Let's work backwards from the arguments to our random function. First thing we want is the circle count. We can get that by calling the get circle count function that we created directly off the WebAssembly module instance.

[05:40] In the same way we can call the get circle data offset function to get the memory address of the circle data array in wasm memory. To get our circle data typed array we can use a different form of the float 32-array constructor.

[05:56] This allows us to create a typed array from any buffer source. In this case we can use the exported memory object from the wasm module accessing the buffer property. This constructor form then takes two further arguments -- the offsetting bytes within the buffer and the length in bytes within the buffer to generate the typed array from.

[06:16] The offset is exactly our exported offset and then the length is exactly the length that we had at the very beginning -- the number of circles times three, the total size of our circle position data and memory. Our [inaudible] time step methods can then be accessed directly from the instance exports, completing our WebAssembly conversion.