aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMihai Bazon <mihai@bazon.net>2012-11-14 12:06:07 +0200
committerMihai Bazon <mihai@bazon.net>2012-11-14 12:06:07 +0200
commitdba8da48005956e151a097e85896b161b4224782 (patch)
tree87acf134a3218f4d26eb7602a3154f2c9ae8091e
parent60c0f40250f0f68ac78f8ddab177336040243579 (diff)
downloadtracifyjs-dba8da48005956e151a097e85896b161b4224782.tar.gz
tracifyjs-dba8da48005956e151a097e85896b161b4224782.zip
optimize constant switch blocks
ref. mishoo/UglifyJS#441
-rw-r--r--lib/compress.js74
-rw-r--r--test/compress/switch.js186
2 files changed, 258 insertions, 2 deletions
diff --git a/lib/compress.js b/lib/compress.js
index ed664a28..89be0cea 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -826,10 +826,12 @@ merge(Compressor.prototype, {
(function(def){
def(AST_Statement, function(){ return null });
def(AST_Jump, function(){ return this });
- def(AST_BlockStatement, function(){
+ function block_aborts(){
var n = this.body.length;
return n > 0 && aborts(this.body[n - 1]);
- });
+ };
+ def(AST_BlockStatement, block_aborts);
+ def(AST_SwitchBranch, block_aborts);
def(AST_If, function(){
return this.alternative && aborts(this.body) && aborts(this.alternative);
});
@@ -1360,6 +1362,74 @@ merge(Compressor.prototype, {
if (stat instanceof AST_Break && loop_body(compressor.loopcontrol_target(stat.label)) === self)
last_branch.body.pop();
}
+ var exp = self.expression.evaluate(compressor);
+ out: if (exp.length == 2) try {
+ // constant expression
+ self.expression = exp[0];
+ if (!compressor.option("dead_code")) break out;
+ var value = exp[1];
+ var in_substat = false;
+ var started = false;
+ var stopped = false;
+ var ruined = false;
+ var tt = new TreeTransformer(function(node, descend, in_list){
+ if (node instanceof AST_Lambda || node instanceof AST_SimpleStatement) {
+ // no need to descend these node types
+ return node;
+ }
+ else if (node instanceof AST_Switch) {
+ if (node === self) {
+ node = node.clone();
+ descend(node, this);
+ return ruined ? node : make_node(AST_BlockStatement, node, {
+ body: node.body.reduce(function(a, branch){
+ return a.concat(branch.body);
+ }, [])
+ }).transform(compressor);
+ }
+ }
+ else if (node instanceof AST_StatementWithBody || node instanceof AST_Switch || node instanceof AST_Try) {
+ var save_substat = in_substat;
+ in_substat = true;
+ descend(node, this);
+ in_substat = save_substat;
+ return node;
+ }
+ else if (node instanceof AST_Break && this.loopcontrol_target(node.label) === self) {
+ if (in_substat) {
+ // won't handle situations like if (foo) break;
+ ruined = true;
+ return node;
+ } else {
+ stopped = true;
+ }
+ return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
+ }
+ else if (node instanceof AST_SwitchBranch && this.parent() === self) {
+ if (stopped) return MAP.skip;
+ if (node instanceof AST_Case) {
+ var exp = node.expression.evaluate(compressor);
+ if (exp.length < 2) {
+ // got a case with non-constant expression, baling out
+ throw self;
+ }
+ if (exp[1] === value || started) {
+ started = true;
+ if (aborts(node)) stopped = true;
+ descend(node, this);
+ return node;
+ }
+ return MAP.skip;
+ }
+ descend(node, this);
+ return node;
+ }
+ });
+ tt.stack = compressor.stack; // so that's able to see parent nodes
+ self = self.transform(tt);
+ } catch(ex) {
+ if (ex !== self) throw ex;
+ }
return self;
});
diff --git a/test/compress/switch.js b/test/compress/switch.js
new file mode 100644
index 00000000..7c1e021c
--- /dev/null
+++ b/test/compress/switch.js
@@ -0,0 +1,186 @@
+constant_switch_1: {
+ options = { dead_code: true, evaluate: true };
+ input: {
+ switch (1+1) {
+ case 1: foo(); break;
+ case 1+1: bar(); break;
+ case 1+1+1: baz(); break;
+ }
+ }
+ expect: {
+ bar();
+ }
+}
+
+constant_switch_2: {
+ options = { dead_code: true, evaluate: true };
+ input: {
+ switch (1) {
+ case 1: foo();
+ case 1+1: bar(); break;
+ case 1+1+1: baz();
+ }
+ }
+ expect: {
+ foo();
+ bar();
+ }
+}
+
+constant_switch_3: {
+ options = { dead_code: true, evaluate: true };
+ input: {
+ switch (10) {
+ case 1: foo();
+ case 1+1: bar(); break;
+ case 1+1+1: baz();
+ default:
+ def();
+ }
+ }
+ expect: {
+ def();
+ }
+}
+
+constant_switch_4: {
+ options = { dead_code: true, evaluate: true };
+ input: {
+ switch (2) {
+ case 1:
+ x();
+ if (foo) break;
+ y();
+ break;
+ case 1+1:
+ bar();
+ default:
+ def();
+ }
+ }
+ expect: {
+ bar();
+ def();
+ }
+}
+
+constant_switch_5: {
+ options = { dead_code: true, evaluate: true };
+ input: {
+ switch (1) {
+ case 1:
+ x();
+ if (foo) break;
+ y();
+ break;
+ case 1+1:
+ bar();
+ default:
+ def();
+ }
+ }
+ expect: {
+ // the break inside the if ruins our job
+ // we can still get rid of irrelevant cases.
+ switch (1) {
+ case 1:
+ x();
+ if (foo) break;
+ y();
+ }
+ // XXX: we could optimize this better by inventing an outer
+ // labeled block, but that's kinda tricky.
+ }
+}
+
+constant_switch_6: {
+ options = { dead_code: true, evaluate: true };
+ input: {
+ OUT: {
+ foo();
+ switch (1) {
+ case 1:
+ x();
+ if (foo) break OUT;
+ y();
+ case 1+1:
+ bar();
+ break;
+ default:
+ def();
+ }
+ }
+ }
+ expect: {
+ OUT: {
+ foo();
+ x();
+ if (foo) break OUT;
+ y();
+ bar();
+ }
+ }
+}
+
+constant_switch_7: {
+ options = { dead_code: true, evaluate: true };
+ input: {
+ OUT: {
+ foo();
+ switch (1) {
+ case 1:
+ x();
+ if (foo) break OUT;
+ for (var x = 0; x < 10; x++) {
+ if (x > 5) break; // this break refers to the for, not to the switch; thus it
+ // shouldn't ruin our optimization
+ console.log(x);
+ }
+ y();
+ case 1+1:
+ bar();
+ break;
+ default:
+ def();
+ }
+ }
+ }
+ expect: {
+ OUT: {
+ foo();
+ x();
+ if (foo) break OUT;
+ for (var x = 0; x < 10; x++) {
+ if (x > 5) break;
+ console.log(x);
+ }
+ y();
+ bar();
+ }
+ }
+}
+
+constant_switch_8: {
+ options = { dead_code: true, evaluate: true };
+ input: {
+ OUT: switch (1) {
+ case 1:
+ x();
+ for (;;) break OUT;
+ y();
+ break;
+ case 1+1:
+ bar();
+ default:
+ def();
+ }
+ }
+ expect: {
+ OUT: switch (1) {
+ case 1:
+ x();
+ for (;;) break OUT;
+ y();
+ }
+ }
+}