From dba8da48005956e151a097e85896b161b4224782 Mon Sep 17 00:00:00 2001 From: Mihai Bazon Date: Wed, 14 Nov 2012 12:06:07 +0200 Subject: [PATCH] optimize constant switch blocks ref. mishoo/UglifyJS#441 --- lib/compress.js | 74 +++++++++++++++- test/compress/switch.js | 186 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+), 2 deletions(-) create mode 100644 test/compress/switch.js 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(); + } + } +} -- 2.34.1