Emscripten Experience Part II – Optimizing the GLSL-Optimizer

There are many ways to love Emscripten. You could take an existing piece of C code or library and turn that into Javascript code that has benchmark speeds close to compiling and running native (thanks non garbage collected asm.js). You could use it to code with the type-safety of C. You could bring an application which previously requires compilation or installation to anyone on the internet simply by loading a webpage. You could even use browser developer tools to profile C++ code.

One reason I’ve been intrigued by Emscripten is the possibilities that happen with an application’s portability by the JS cross-compilation target. That has also spark off more interest in me to learn more about languages, C, LLVM or even Javascript itself.

Despite all the interest, I had a slow start with Emscripten – which was the topic in my last post. Overcoming some of the difficulties was I able to enjoy some fun and success with it. Still challenges lurked along the journey, and at end of the previous post I had 2 problems to solve:

a) getting link-time optimizations on glsl-optimizer

b) difficultly of porting a wavetable midi synthesizer to the web browser

One day @thespite mentioned to incorporate my emscripten port of GLSL-optimizer into his cool WebGL Shader Editor extension for Chrome, and that prompted me to revisit my project and Emscripten. So I continued the journey with a couple more tales to tell but as these stories become slightly unwieldy, I decided to cover the second section in another post.

Emulation

Have you tried playing old games on emulators? A decade or more ago, I used to follow development on emulators (eg. playstation) to check out what additional new game compatibility they have made with new releases. Somehow the idea of being able to emulate and play many games from a different platform amazed me.

This draws a parallel to application of Emscripten. Huge codebase originally written in C or C++ could be “emulated” for the web. It excites me to see stuff like server libraries, desktop applications, graphics platform, codecs, even programming languages ported to JS that runs either on node.js or in the browser. 2 examples of projects I thought was really interesting is emscripten-QT and pypy.js! This wiki page has a great list of projects to check out!

Excess Code

While Emscripten has been known to work of huge amount of existing code, it might also mean that it could potentially generate huge amount of code too. Well, like many other cross-compilers or machine generated code, this isn’t new.

But is that really a big deal? Considering being able re-use most or all of an existing codebase for javascript with relatively little effort and time (compared to re-writing in js). Considering that the equivalent binaries aren’t that small either, especially if you include about shared libaries and dependencies already installed on your system. Considering that different binaries are needed for different platforms, now they would now run on the most universal platform (the web) with additional compilation. Considering without binaries, no installation is needed and runs almost instantaneously. Considering without the web as a platform, how much more difficult it is to acquire users who needs to go the hassle try the software on their computer. Considering these could justify the download wait.

Yet a casual visitor on your website loading up that fat javascript file wouldn’t appreciate that. The javascript may be so huge the browser is taking time downloading it. The browser may be look it has stop responding taking additional time to parse, interpret or compile the huge script files. The visitor may be wondering if the site is broken, the network is down, whether the code works, and it’s definitely not the best experience.

Which is why emscripten isn’t always the magic pill, there are other alternatives like hand porting or using other tools – bonsai-c, cheerp (though from some of my initial tests cheerp seems to be generating a bigger code ).

GLSL Optimizer Releases

When I first announced my emscripten port of GLSL-Optimizer, the build was 8MB. That’s almost the size of 10 floppy disks. These days though, homes have 1Gbp internet, but running a huge javascript file still takes additional time to get the code running.

Of course the file size wasn’t satisfactory. One thing I wasn’t doing was to run it at -O1, -O2 or -O3 optimizations. Running with those flags can activate link-time optimizations and runs the resulting code through JS minification which would improve overall file size and performance. Strangely, using these optimization flags resulted in infinite loops during runtime.

Why weren’t the builds optimized
One factor I suspected why optimizations were failing was because I was using Embind for bridging JS and C world. I observed that for some strange reasons I was able to run at -O1 instead of -O0 when I had embind disabled, so I decided to revert to non-embind bindings and use function return values to pass back successful and failure values back to JS land.

But that seem only to be able to get me as far as -O1 optimizations. The resulting JS weren’t even minified so I wanted to check if I could do closure minification without O2. I ended up filing an issue in github because the flags didn’t allow me to do that with emscripten. So even though that might be a bug, minification without link-time optimizations would also have limited effectiveness (besides, running huge code through the minifier tends to end up with crashes).

Alon Zakai aka kripken asked why the compiler optimizations and pointed me to some compiler settings I could use to trace memory segmentation faults. Those settings turns out to be really useful to debug emscripten code in general.

Tweaking around the flags, I still couldn’t solve the problem. I started thinking that there’s a possibility it was a bug emscripten. I lay this matter to rest till some time later, there were new versions of emscripten and I decided to give it another shot. I upgraded to 1.30.0, no luck. I decided to git clone master version and try again, still it did not work run time problems.

Emterpretor

Since I was on the master branch, I decided to check out the latest developments. Emterpretor was in. Asyncify was out. Interesting, but what was that?

I ran Emscripten + Emterpretor anyways and O2 still fails. On the bright side, through the network pane I found the resulting code to be much more compact. Wow! 8.7MB→1.8MB (1.7MB→720KB gzipped) So what happened?

“We take C/C++ code and convert it to asm.js a subset of JavaScript. Then we convert the asm.js to byte code and spit out an interpreter for it written in JavaScript.

The JavaScript interpreter is loaded in the usual way into the web page and the bytecode is loaded as data. The big advantage of this scheme is that the byte code doesn’t have to be parsed and this speeds up the entire load process.” (source)

So from my understanding the Emterpretor is like a virtual machine which interpret and runs your emscripten code. It load code as a binary / bytecode format (like JVM) that is more compressed and concise than Javascript itself. It also allows code to be run even before the entire code can be parsed (unlike asm.js or JS). The virtual machine would also possibly allow some cool stuff like saving the state of the machine, and running virtual threads (green threads). The idea of an interpreted language running interpreted stuff is cool, isn’t it? There are drawbacks though, like preventing AOT optimizations (because instructions would interpreted evaluated at runtime). There’re ways around that, eg using whitelisting, something I may bring up in the next post.

While uploading the tinier builds to github, I realized something. Github pages weren’t enabling gzip compression with emscripten.js.mem. As a hack, I renamed that to mem.js, make emscripten load the custom memory file sogithub-pages would pull a gzip served asset. Yah!

WebAssembly

At this point, I’m tempted again to derail my topic to talk about the not-too-long ago announced WebAssembly/WebASM/WASM. It has some of the ideas of Emterpretor, AST byte code format solving faster load times and allowing optimizations in the JS engine. I think it’s an exciting topic. It seems like a natural progression for asm.js -> emterpretor -> WebASM which is at the same time backward compatible with polyfills. It’s even greater that all vendors agree on this standard. I believe it is also something that can make the people asm.js bothers happy. It is also awesome that emscripten can also support that with a flick of a switch. But let me go back on topic.

Real Fix
Up to now, I’ve been trying to get around fixing the optimization problem looking everywhere, except one place – the code base (one of my emscripten lessons I shared was to understand the original code base, apparently I didn’t heed my own advice). In an issue in three.js, Ben Adams mentioned my project in a thread that leads @tschw to the original optimizing issue I’ve open for glsl-optimizer. With his sorcery (which he denies), he was able to trace to an issue upstream from glsl-optimizer to the MESA glsl compiler that was causing how the optimizations not to work. Whoever tschw is, awesome work there!

Finally we can perform -O3 optimizations!!!!

If we stop here for a moment, I think we have a happy ending. The GLSL-optimizer has also been added to the Shader Editor Extension.

So here we have a little milestone, but I believe there’s more work that can be done. thespite suggested for a tree-shaking pruning (aka dead-code elimination) feature that doesn’t alters the original GLSL variables. I think that’s a absolutely good feature to have, unfortunately it may be difficult to alter MESA GLSL parser or GLSL optimizer to do so. Others have mentioned other tools: peg.js, glsl-unit, glslprep.js, glsl-simulator and the StackGL set of tools: glsl-tokenizer, glsl-parser etc. These suggestions are great, someone just have to look into them and apply them. Maybe one day when I have too much time on my own, I might also write my own parser in JS to play around with the GLSL code. Well, maybe as always.

So that’s all for “Optimizing the GLSL-Optimizer” in this post, and I’ll try to write about “Setting WildMidi Wild on the Web” into the next post, till then feel free to drop me comments @blurspline 😀

Related Links / Readings

https://kripken.github.io/mloc_emscripten_talk/gindex.html – Slides on “Compiling C/C++ To Javascript GDC 2013” by Alon Zakai
https://brendaneich.com/2015/06/from-asm-js-to-webassembly/ From asm.js to Web Assembly
The Emterpreter: Run code before it can be parsed
https://twitter.com/BlurSpline/status/568271236632956929 Original announcement of glsl-optimizer on twitter
– Web Assembly (Google, Microsoft, Mozilla And Others Team up, Design FAQ, prototype)
Javascript is C, not assembly of the Web
https://twitter.com/search?q=emscripten Twitter News on Emscripten