Metaprogramming: Programmable DCE (Dead Code Elimination) in JS++

Problem:

You’re writing a library in JS++. You need it to support as many web browsers as possible.

Some of your users’ visitors are on latest Chrome and support the latest HTML5 features. Some of your users’ visitors are from developing nations with limited mobile devices. Some of your users’ visitors are corporate users with legacy web browsers running intranet applications, and, if it isn’t broken, they won’t “fix” it.

In other words, you need to support as many web browsers as possible. However, adding polyfills for all browsers will slow down the library for all users: more code to deliver over the network, more parsing and initialization time on mobile, more code to execute at runtime, and so on.

Solution:

Programmable Dead Code Elimination (DCE).

In the same way that template metaprogramming was “accidentally” discovered in C++, programmable DCE was a technique that we never originally intended but stumbled upon. I’m filing under advanced JS/JS++ tips & tricks. You should know how to use bitmasks, getters, classes, and JS++ types (because JS++ is not a linter like TypeScript).


What is Dead Code Elimination (DCE)?

Dead code elimination (DCE) means that if you don’t use a module, class, or function, it will not be compiled into your final output.

Here’s a simple example:

void A() {}
void B() {}

A();

In the above example, only the function A gets called. The function B never gets called and never gets executed. Therefore, it is safe for the compiler to not include B in the final compiled output.

A real-world example of the need for dead code elimination in JavaScript (but not JS++) is jQuery. You have to include the entire jQuery library even if you only use one function.

Live Example: JS++ Sockets (Real-time Streaming)

The JS++ Sockets library provides low-overhead real-time streaming even for legacy web browsers. It’s “low overhead” because we avoid long polling in almost every case except for the very first Android phones (HTC Dream and the like). Therefore, you get real-time streaming without the HTTP header overhead from constant polling, and you don’t have to deal with the delays of polling. (This can be useful for multiplayer games, corporate finance applications on legacy browsers where excessive latency is undesirable, or just to save a pretty penny on your bandwidth bill.)

JS++ has had support for dead code elimination since October 2016 (version 0.4.2). However, it wasn’t until we worked on JS++ Sockets that we encountered the need for “programmable” dead code elimination. In addition, JS++ users have been asking us to stop shipping polyfills for old browsers they don’t need because all their customers are on modern browsers.

JS++ Sockets can be instantiated like so:

new Socket(
    "127.0.0.1",
    8081,
    Socket.WEBSOCKETS | Socket.IE6 | Socket.IE7
);

It’s in the bitmask where all the magic happens. If your customers don’t use IE6, don’t include the Socket.IE6 option. If your customers use ONLY IE7 (like some big companies supporting legacy code), you can specify only the IE7 option. This gives the users of your library the flexibility; they’re not shipping thousands of lines of code they don’t need.

What’s interesting is that the options in the bitmask are actually getter functions. It only looks like an enum-based bitmask. We could have just as easily made them “regular” functions, and it becomes easier to visualize how “programmable DCE” happens:

new Socket(
    "127.0.0.1",
    8081,
    Socket.WEBSOCKETS() | Socket.IE6() | Socket.IE7()
);

Remember the basic principle of DCE: if a function isn’t called, it isn’t included in the final output. When you view the above code as function calls – rather than bitmasks – you can begin to visualize how we can enable the user to customize the polyfills she wants. If the user did not call Socket.IE6(), the IE6 polyfills will not be included.

Even if your function is a 1000+ line monster with complex polyfill code, if it isn’t called, it isn’t compiled.

The Programmable DCE Pattern

In a nutshell, programmable DCE happens like this:

  1. Define a private static polyfill method.
  2. Define a public static getter that returns a bitmask-compatible value and invokes the relevant private static polyfill method.
  3. Repeat steps 1-2 for each browser/polyfill you need to support.
  4. Define a constructor that accepts bitmasked options.

Everything is perfectly encapsulated. When the user instantiates your class, they will need to use the bitmask. The bitmask values invoke getters with custom logic. The custom logic invokes the polyfills the user needs.

Roger PoonRoger Poon
JS++ Designer and Project Lead. Follow me on Twitter or GitHub.