google / closure-compiler

A JavaScript checker and optimizer.
https://developers.google.com/closure/compiler/
Apache License 2.0
7.41k stars 1.15k forks source link

How to optimize out constructors that do not (practically) have side effects? #3185

Open juj opened 5 years ago

juj commented 5 years ago

It looks like Closure does not optimize out redundant constructors that do not have side effects. Two such instances in my codebase are

var a = new TextDecoder('utf8');
var buffer = new ArrayBuffer(1024);
var b = new Uint8Array(buffer);

in my codebase variables a and b are not used (but buffer is), so I would expect the above to optimize to

var buffer = new ArrayBuffer(1024);

but instead it optimizes down to

new TextDecoder('utf8');
var buffer = new ArrayBuffer(1024);
new Uint8Array(buffer);

i.e. Closure is thinking that the TextDecoder and Uint8Array constructors have side effects. (Technically they do, since they could throw, but I would very much like to treat them as if they never would)

I tried different annotations with

/** @nosideeffects */
function TextDecoder() {};

and

/** @nosideeffects */
var TextDecoder;

in my closure-externs.js file, but neither of those worked.

Is there a way to make Closure optimize out ctors that should be treated to not have any side effects? Or is the @nosideeffects annotation currently limited to not apply to constructors? (or perhaps my annotation syntax is wrong?)

lauraharker commented 5 years ago

Created internal issue http://b/122537085

lauraharker commented 5 years ago

The @nosideeffects isn't effective when you add it because it conflicts with the @throws, so optimizations still treat the constructors as having side effects. If the @throws were not there, the constructor would get optimized away.

Looks like the nosideeffects annotation was intentionally removed in https://github.com/google/closure-compiler/pull/1648.

juj commented 5 years ago

Thanks for the quick turnaround @lauraharker, much appreciated!

For background, this scenario arises in Emscripten compiler toolchain, which can run compiled code output through closure, I've been investigating to make sure that closure works as efficiently there as possible. Perhaps a @ignorethrows kind of annotation could be useful here, to force a specific symbol to not throw? Then a

/** @ignorethrows */
function TextDecoder() {};

would be effective to DCE away the redundant construction?

Also, thinking about this, Emscripten compiled codebases are often very C-like in nature, and their JS code flow very often is authored from the perspective that release mode builds never have any interesting behavior with respect to throwing (such things would be caught in debugging). So additional to above kind of annotation, it might even be interesting to have a global closure command line flag --ignore-throws-side-effects that would treat all code as something that never throws (for the purposes of side effect analysis). That might help squeeze out some bytes more.