Enforcing parameter types in JavaScript
The Extensible Web Community Group is currently working on his first project: a polyfill code generator. The goal of the project is to generate a stub JavaScript code from some WebIDL definition. To ensure maximum compatibility, we need to follow the WebIDL type conversion rules: but how do we do this efficiently?
What if you wanted to create a Typed Array polyfill? Typed Arrays are one of the most interesting WebIDL API because it covers a lot of input and output arguments types: octet, float, double, …
All those types have no direct equivalent in ECMAScript and browsers need to ‘convert’ the input arguments from ECMAScript to their C++ equivalent, and back. Therefore, if you want to make a polyfill, you need to perform the exact same conversions.
In this experiment, I’ll consider some ways to perform this conversion in a seamless way for the developer, and asses their performance.
By using a wrapper function
Like it’s the case for many dynamic languages, ECMAScript functions are recompiled one or more times based on the types of their input arguments. Therefore, if you want to avoid the recompilation of a function if the developers don’t give the expected arguments types, you may want to create a ‘wrapper’ function that will perform the conversions and call the ‘inner’ function with the right parameter types.
To make it clear, the ‘inner’ function will always get called with the planned types and will never need to be recompiled again. In case the developer pass an invalid argument type (or one that needs a conversion), the only thing that must be recompiled is the ‘wrapper’ function that performs the conversion, something that make sense.
function funcInner(booleanArgX, integerArgX) {
return booleanArgX ? integerArgX-1 : 0;
}
function func(booleanArg, integerArg) {
return funcInner(!!booleanArg, +integerArg|0);
}
The disadvantage of this technique is that it create an indirection layer and may make some other code optimizations less effective.
By using an inner variable
The second option is to create an inner variable which has the right type.
function outer(booleanArg, integerArg) { var booleanArgX = !!booleanArg; var integerArgX = +integerArg|0; return booleanArgX ? integerArgX-1 : 0; }
This second option takes advantage that the type of the booleanArgX and integerArgX variables are possible to know at compilation time, which can save some work to the browser. It’s also simpler for the browser to optimize the code because there’s not an intermediary function call.
By fiddling with the parameters
The last option is to do the exact same thing as in the previous case, but to “erase” the value of the parameters instead of creating a new local variable.
function outer(booleanArg, integerArg) { booleanArg = !!booleanArg; integerArg = +integerArg|0; return booleanArg ? integerArg-1 : 0; }
The disadvantage of this solution is that the ‘booleanArg’ variable will change of type if a conversion occurs, something which may disable some optimizations.
Performance results
I tested the following options in the latest version of all browser available in Windows (IE10, Chrome Canary, Mozilla Aurora and Opera Snapshot). I will share precise results per browser, but the big picture is that the second option is the most optimized if you expect a conversion to happen. If you take the bet that the user will always use the right type for the arguments (and will not omit them), you may win slightly if you use the last option.
Interestingly, it seems that in IE10 and Chrome some function inlining magic seems to happen so the performance of all options are about the same. In all the other cases, the first option (inner function) is way worse than the other ones.
In Opera, however, the performance of the conversion is not good at all and the performance of the third option (fiddling with parameters) is better by 6% over the second one (creating locals). In FireFox, this is the inverse (fiddling with parameters is slightly more costly).
Conclusion
Albeit the results tend to indicate the second option is better on average, I think I’ll go for the third option in the code generator (mainly because the code looks better but also because I expect authors to send in the right type of data in which case browser can optimize the conversion code by deleting it).