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.

JS++ 0.7.0: JavaScript Fully Implemented

JS++ 0.7.0 has arrived. Everything that is possible to do in JavaScript is now possible to do in JS++. JS++ has fully implemented JavaScript and the JS++ Standard Library is now fully documented.

This means that every JavaScript feature you expect is now available in JS++: arrays (0.7.0 introduces the generic Array<T> class), functions, date/time, regular expressions, loops, bitwise operators, etc. This is in addition to several JS++-only features: foreach, modules, classes, imports, enums, virtual functions, integer types, optimized auto-boxing, function overloading, dead code elimination (DCE), and so on.

You can find all the current Standard Library documentation by clicking here. Every documented class, method, and field (with examples) should be available and work in 0.7.0.

System.Array<T>

The major missing feature for a while now has been arrays. JS++ 0.7.0 brings the generic System.Array<T> class to JS++. With this addition, everything you can do in JavaScript is possible in JS++.

The full documentation for System.Array<T> is available here.

Additionally, JS++ adds more features to the Array API that aren’t available in JavaScript such as clear, contains, remove, first, last, count, and more. These methods are all optimized; in other words, there is no performance overhead in using them. In addition, we benchmark each method to make sure we are always providing the fastest implementation for an abstraction you want.

System.Array.sort and IComparable<T>

It comes as no surprise that JavaScript has strange behavior. Consider this JavaScript code:

var arr = [ 1, 2, 10, 9 ];
arr.sort();
console.log(arr); // [ 1, 10, 2, 9 ]

As you can see, JavaScript did not sort the numeric array correctly. The reason for this behavior is because JavaScript performs a string sort regardless of the types in the array. Since JS++ is statically-typed, we can generate code for a correct sort with zero overhead. In JS++, the System.IComparable<T> interface provides exactly this behavior. If a class implements System.IComparable<T>, it can provide custom sorting behavior.

All Standard Library wrapper classes implement System.IComparable<T>. Thus, all JS++ numeric types will be sorted numerically when used inside an array – as you would expect from a modern, well-designed language. Here’s an example:

import System;

int[] arr = [ 1, 2, 10, 9 ]; // make sure you use an internal type like 'int[]' and not an external type like 'var'
arr.sort();

And here’s the generated code:

// Compiled with JS++ v.0.7.0
! function() {
    ! function() {
        var arr = [1, 2, 10, 9];
        arr.sort(function(a, b) {
            return (a - b);
        });
    }();
}();

As you can see, there is zero overhead – despite the complex inheritance hierarchy (int[] being auto-boxed by System.Array<int>, System.Array<int> providing a custom sort based on generic type constraints, and System.Integer32 implementing IComparable<T>).

ECMAScript 5 Array Methods

ECMAScript 5 (ES5) added several array methods:

  • indexOf
  • lastIndexOf
  • every
  • some
  • filter
  • map
  • forEach
  • reduce
  • reduceRight

However, these methods are not supported in older web browsers. JS++ aims for enterprise support and legacy web application support. However, in order to polyfill these methods, it would result in hundreds of lines of code. Thus, in the spirit of JS++ dead code elimination (DCE), the above methods are only polyfilled for incompatible web browsers if and only if the individual method is used.

System.Object

One peculiar Standard Library class you might notice is System.Object. Specifically, it does not implement the JavaScript API at all. This is because JS++ does not use prototypical inheritance like JavaScript does. JS++ uses class-based inheritance; thus, System.Object is minimal and does not provide methods that are only useful for a prototype-based language – such as hasOwnProperty and isPrototypeOf.

JS++ uses a “Unified Type System” (like Java/C#) with System.Object at the top of the inheritance hierarchy. Thus, System.Object represents all “internal types.” If you don’t know what that means, please fully read the “Getting Started” guide.

If you want to use JavaScript objects, you still have to declare external types until System.Dictionary<T> arrives:

var obj = {
    "a": 1,
    "b": 2
};

In addition, if you want the JavaScript Object prototype methods, use the Externals.JS module:

import System;
import Externals.JS;

Console.log(typeof Externals.JS.Object.prototype.hasOwnProperty == "function"); // true

Prefer the Standard Library

There are 300+ pages of Standard Library documentation (not including all the documentation for individual method overloads). This is in addition to 300+ pages of handwritten documentation, bringing the JS++ documentation to over 600 pages.

As always, I recommend that you prefer the JS++ Standard Library to writing your own JavaScript implementation. Consider clearing an array. In JS++, the code is:

arr.clear();

You might be tempted to avoid the function call overhead and try to roll your own JavaScript:

arr = [];

First of all, this can create a memory leak (e.g. if other references to the original array are being held). The JS++ Standard Library provides high-quality, fast, and well-tested functions to you. Use it.

Secondly, what looks like function call overhead on the surface is actually inlined, correct code in the final output:

arr.length = 0;

Arrays have incredibly low overhead. Here’s the full code:

import System;

int[] arr = [1,2,3];
arr.clear();

Here’s the generated output:

// Compiled with JS++ v.0.7.0

!function(){!function(){var arr=[1,2,3];arr.length=0;}();}();

As you can see, there is zero overhead. Thus, use the Standard Library. Don’t try to be fancy and write JavaScript rather than JS++ code. Your users’ garbage collectors will thank you for it in the future when you’re not leaking 500mb of RAM, and your team will thank you for it because .clear() is A LOT more readable than .length = 0 and A LOT more correct than arr = [];

Code Readability

One of the major driving changes for JS++ is code readability. We invest heavily into this from cutting-edge parsers to Standard Library design. The JS++ Standard Library allows you to write high-performance, readable code.

Consider this JavaScript code:

var abc = [ "a", "b", "c" ];
abc.splice(1, 1);

Without evaluating it, what do you think it does?

Here’s the equivalent JS++ code:

string[] abc = [ "a", "b", "c" ];
abc.remove(1);

Same code. Same performance. Many times more readable when written in JS++.

Looking Ahead

We’re going to continue to expand the JS++ Standard Library to provide useful functions and give you a “batteries included” experience. Additionally, we’ll continue to expand the language. The following features are still not implemented:

  • User-defined Generic Classes
  • Reflection API
  • System.Dictionary (hash maps)
  • Block Scoping
  • Nullable Types
  • final variables (but final classes and methods are done)

If you have a pressing need for any of these features, you may want to wait. However, everything that is possible to do in JavaScript is now possible to do in JS++.