Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Closure compiler doesn't remove unused properties

(function(){
    var num = 4 // Math.random()
    var module1 = {};

    (function(export_to){
        export_to.add1 = function(arg) { return arg+1 }
        export_to.add2 = function(arg) { return arg+2 }
        export_to.add10 = function(arg) { return arg+10 }
    })(module1)

    console.log(module1.add10(num))
})()

This is primitive inline "module" example. As long as num is constant, GCC correctly inlines add10 function and eliminates everything else with --compilation_level=ADVANCED, leaving only:

console.log(14);

as the result.

Changing // to && though make GCC behave much worse. For some reason it no longer inlines .add10 and no longer notices that .add1 and .add2 are not called at all and effectively are dead code.

(function(){var d=Math.random(),c={};(function(b){b.b=function(a){return a+1};b.c=function(a){return a+2};b.a=function(a){return a+10}})(c);console.log(c.a(d))})();

Is there a way to make GCC eliminate/inline such functions while still keeping them inside their own function scope in case I'd need some private persisten variables and neatly grouped in an Object in original source? Perhaps some annotation?

This all was tested on latest available pre-built release at the moment of posting:

Closure Compiler (http://github.com/google/closure-compiler)
Version: v20160315
Built on: 2016/03/25 11:43
like image 321
Oleg V. Volkov Avatar asked Feb 16 '26 06:02

Oleg V. Volkov


1 Answers

UPDATE: The latest version of Closure Compiler correctly minifies your code. The online Closure Compiler is still an older version (I don't know how to tell what version it is).

This shows the version of the compiler I'm using

$ java -jar ../javascript/closure-compiler/build/compiler.jar --version
Closure Compiler (http://github.com/google/closure-compiler)
Version: v20160315-213-g4787bb5
Built on: 2016/04/25 10:12

This shows the code I'm compiling:

$ cat test2.js
(function(){
    var num = Math.random();
    var module1 = {};

    (function(export_to){
        export_to.add1 = function(arg) { return arg+1 }
        export_to.add2 = function(arg) { return arg+2 }
        export_to.add10 = function(arg) { return arg+10 }
    })(module1)

    console.log(module1.add10(num))
})()

This shows the compile command and result:

$ java -jar ../javascript/closure-compiler/build/compiler.jar 
    --js test2.js --compilation_level ADVANCED
console.log(Math.random()+10);

Below is my earlier answer (now irrelevant).


You are running into some limitations of Closure Compiler. Here is a quote from Closure Compiler Issue 891: missed property collapsing opportunity

The issue is that "property" collapsing only happens once for global scope, and that is before function inlining occurs (within functions this occurs during the optimization loop). For this to work "collapse properties" would need to occur at least once more, or we would need to enhance the function local version to be able to run in global scope.

You would need to modify DefaultPassConfig to run CollapseProperties a second time. There is not a existing compiler options to do this.

So there might be ways to modify Closure Compiler to handle your case. Here is a blog post about how to get started with modifying the compiler: High-level overview of a compilation job

See also Understanding Property Removal.

I'm not clear on your requirements, you seem to be using a module pattern. If you don't need that particular pattern this code works:

(function() {
var Adder = {};
Adder.add1 = function(arg) { return arg+1; }
Adder.add2 = function(arg) { return arg+2; }
Adder.add10 = function(arg) { return arg+10; }

console.log(Adder.add10(Math.random()));
})()

The result using online Closure Compiler with Advanced Optimization is nicely minified

console.log(Math.random()+10);

But using an augmentation module pattern doesn't completely collapse the code, though it does do some inlining. Here is an example:

var module1 = {};
module1 = (function(export_to){
    export_to.add1 = function(arg) { return arg+1 }
    export_to.add2 = function(arg) { return arg+2 }
    export_to.add10 = function(arg) { return arg+10 }
    return export_to;
})(module1);

console.log(module1.add10(4))

The result with Advanced Optimization is

var b={},b=function(a){a.a=function(a){return a+1};a.c=function(a)
{return a+2};a.b=function(){return 14};return a}(b);console.log(14);

This perhaps qualifies as a bug in the compiler.

I suspect that your original code collapsing to console.log(14) is a bit of a fluke. My guess is that the compiler does some integer arithmetic during compilation and that case just happens to work out. Note that using a non-integer causes your original code to not collapse (same as when using Math.random() for the value of num)

(function(){
    var num = 4/3;
    var module1 = {};

    (function(export_to){
        export_to.add1 = function(arg) { return arg+1 }
        export_to.add2 = function(arg) { return arg+2 }
        export_to.add10 = function(arg) { return arg+10 }
    })(module1)

    console.log(module1.add10(num))
})()

Result:

(function(){var d=4/3,c={};(function(b){b.b=function(a){return a+1};
b.c=function(a){return a+2};b.a=function(a){return a+10}})(c);
console.log(c.a(d))})();
like image 83
owler Avatar answered Feb 18 '26 20:02

owler



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!