diff --git a/README.md b/README.md index 0d1e2368..22831b12 100644 --- a/README.md +++ b/README.md @@ -689,6 +689,11 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u Specify `"strict"` to treat `foo.bar` as side-effect-free only when `foo` is certain to not throw, i.e. not `null` or `undefined`. +- `reduce_funcs` (default: `true`) -- Allows single-use functions + to be inlined as function expressions when permissible. + Enabled by default. Option depends on `reduce_vars` being enabled. + For speed critical code this option should be disabled. + - `reduce_vars` (default: `true`) -- Improve optimization on variables assigned with and used as constant values. diff --git a/lib/compress.js b/lib/compress.js index 24c5be21..af4d4929 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -75,6 +75,7 @@ function Compressor(options, false_by_default) { properties : !false_by_default, pure_getters : !false_by_default && "strict", pure_funcs : null, + reduce_funcs : !false_by_default, reduce_vars : !false_by_default, sequences : !false_by_default, side_effects : !false_by_default, @@ -320,7 +321,7 @@ merge(Compressor.prototype, { value = node.fixed_value(); if (value && ref_once(d)) { d.single_use = value instanceof AST_Lambda - || d.scope === node.scope && value.is_constant_expression(); + || d.scope === node.scope && value.is_constant_expression(compressor); } else { d.single_use = false; } @@ -2187,15 +2188,16 @@ merge(Compressor.prototype, { // determine if expression is constant (function(def){ - function all(list) { + function all(compressor, list) { for (var i = list.length; --i >= 0;) - if (!list[i].is_constant_expression()) + if (!list[i].is_constant_expression(compressor)) return false; return true; } def(AST_Node, return_false); def(AST_Constant, return_true); - def(AST_Lambda, function(scope){ + def(AST_Lambda, function(compressor, scope){ + if (!compressor.option("reduce_funcs")) return false; var self = this; var result = true; self.walk(new TreeWalker(function(node) { @@ -2218,20 +2220,21 @@ merge(Compressor.prototype, { })); return result; }); - def(AST_Unary, function(){ - return this.expression.is_constant_expression(); + def(AST_Unary, function(compressor){ + return this.expression.is_constant_expression(compressor); }); - def(AST_Binary, function(){ - return this.left.is_constant_expression() && this.right.is_constant_expression(); + def(AST_Binary, function(compressor){ + return this.left.is_constant_expression(compressor) + && this.right.is_constant_expression(compressor); }); - def(AST_Array, function(){ - return all(this.elements); + def(AST_Array, function(compressor){ + return all(compressor, this.elements); }); - def(AST_Object, function(){ - return all(this.properties); + def(AST_Object, function(compressor){ + return all(compressor, this.properties); }); - def(AST_ObjectProperty, function(){ - return this.value.is_constant_expression(); + def(AST_ObjectProperty, function(compressor){ + return this.value.is_constant_expression(compressor); }); })(function(node, func){ node.DEFMETHOD("is_constant_expression", func); @@ -3533,7 +3536,7 @@ merge(Compressor.prototype, { var stat = fn instanceof AST_Function && fn.body[0]; if (compressor.option("inline") && stat instanceof AST_Return) { var value = stat.value; - if (!value || value.is_constant_expression()) { + if (!value || value.is_constant_expression(compressor)) { var args = self.args.concat(value || make_node(AST_Undefined, self)); return make_sequence(self, args).optimize(compressor); } @@ -4260,7 +4263,7 @@ merge(Compressor.prototype, { if (d.escaped && d.scope !== self.scope || recursive_ref(compressor, d)) { d.single_use = false; } else if (d.scope !== self.scope || d.orig[0] instanceof AST_SymbolFunarg) { - d.single_use = fixed.is_constant_expression(self.scope); + d.single_use = fixed.is_constant_expression(compressor, self.scope); if (d.single_use == "f") { var scope = self.scope; do { diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 34489717..329d98f9 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -3037,6 +3037,7 @@ issue_2437: { inline: true, join_vars: true, passes: 2, + reduce_funcs: true, reduce_vars: true, side_effects: true, sequences: true, diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index ebb0b8fa..e2e4ea46 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -734,6 +734,7 @@ assign_chain: { issue_1583: { options = { keep_fargs: true, + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -1114,6 +1115,7 @@ issue_2105_1: { collapse_vars: true, inline: true, passes: 3, + reduce_funcs: true, reduce_vars: true, side_effects: true, unused: true, @@ -1156,6 +1158,7 @@ issue_2105_2: { passes: 3, properties: true, pure_getters: "strict", + reduce_funcs: true, reduce_vars: true, side_effects: true, unsafe: true, diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 64728c06..00f08947 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -663,6 +663,7 @@ call_args: { options = { evaluate: true, inline: true, + reduce_funcs: true, reduce_vars: true, toplevel: true, } @@ -686,6 +687,7 @@ call_args_drop_param: { evaluate: true, inline: true, keep_fargs: false, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, diff --git a/test/compress/functions.js b/test/compress/functions.js index c5aab171..c30283af 100644 --- a/test/compress/functions.js +++ b/test/compress/functions.js @@ -154,6 +154,7 @@ function_returning_constant_literal: { inline: true, passes: 2, properties: true, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -305,6 +306,7 @@ issue_2084: { evaluate: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, sequences: true, side_effects: true, @@ -515,6 +517,7 @@ issue_2428: { inline: true, passes: 3, pure_getters: "strict", + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, diff --git a/test/compress/hoist_props.js b/test/compress/hoist_props.js index ccfc76f8..edef696d 100644 --- a/test/compress/hoist_props.js +++ b/test/compress/hoist_props.js @@ -3,6 +3,7 @@ issue_2377_1: { evaluate: true, inline: true, hoist_props: true, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -35,6 +36,7 @@ issue_2377_2: { inline: true, hoist_props: true, passes: 2, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -66,6 +68,7 @@ issue_2377_3: { inline: true, hoist_props: true, passes: 3, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -163,6 +166,7 @@ direct_access_3: { single_use: { options = { hoist_props: true, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -293,6 +297,7 @@ contains_this_1: { hoist_props: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -318,6 +323,7 @@ contains_this_2: { hoist_props: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -345,6 +351,7 @@ contains_this_3: { hoist_props: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -376,6 +383,7 @@ new_this: { hoist_props: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, diff --git a/test/compress/issue-1787.js b/test/compress/issue-1787.js index 2b5372be..fcef6148 100644 --- a/test/compress/issue-1787.js +++ b/test/compress/issue-1787.js @@ -2,6 +2,7 @@ unary_prefix: { options = { evaluate: true, inline: true, + reduce_funcs: true, reduce_vars: true, unused: true, } diff --git a/test/compress/issue-281.js b/test/compress/issue-281.js index 65871a84..6a93136f 100644 --- a/test/compress/issue-281.js +++ b/test/compress/issue-281.js @@ -3,6 +3,7 @@ collapse_vars_constants: { collapse_vars: true, evaluate: true, inline: true, + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -240,6 +241,7 @@ negate_iife_issue_1073: { evaluate: true, inline: true, negate_iife: true, + reduce_funcs: true, reduce_vars: true, sequences: true, unused: true, @@ -267,6 +269,7 @@ issue_1288_side_effects: { evaluate: true, inline: true, negate_iife: true, + reduce_funcs: true, reduce_vars: true, side_effects: true, unused: true, @@ -299,6 +302,7 @@ inner_var_for_in_1: { options = { evaluate: true, inline: true, + reduce_funcs: true, reduce_vars: true, } input: { @@ -330,6 +334,7 @@ issue_1595_3: { evaluate: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, unused: true, } diff --git a/test/compress/properties.js b/test/compress/properties.js index ab202525..f2e59321 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -833,6 +833,7 @@ lhs_prop_2: { evaluate: true, inline: true, properties: true, + reduce_funcs: true, reduce_vars: true, side_effects: true, unused: true, @@ -879,6 +880,7 @@ prop_side_effects_1: { evaluate: true, inline: true, properties: true, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -915,6 +917,7 @@ prop_side_effects_2: { inline: true, passes: 2, properties: true, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index e84ac6cb..b8a84dbd 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -1351,6 +1351,7 @@ defun_inline_3: { evaluate: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, side_effects: true, unused: true, @@ -1373,6 +1374,7 @@ defun_inline_3: { defun_call: { options = { inline: true, + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -1400,6 +1402,7 @@ defun_call: { defun_redefine: { options = { inline: true, + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -1433,6 +1436,7 @@ defun_redefine: { func_inline: { options = { inline: true, + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -1460,6 +1464,7 @@ func_inline: { func_modified: { options = { inline: true, + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -2041,6 +2046,7 @@ redefine_arguments_2: { evaluate: true, inline: true, keep_fargs: false, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -2079,6 +2085,7 @@ redefine_arguments_3: { inline: true, keep_fargs: false, passes: 3, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -2154,6 +2161,7 @@ redefine_farg_2: { evaluate: true, inline: true, keep_fargs: false, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -2192,6 +2200,7 @@ redefine_farg_3: { inline: true, keep_fargs: false, passes: 2, + reduce_funcs: true, reduce_vars: true, sequences: true, side_effects: true, @@ -2648,6 +2657,7 @@ boolean_binary_assign: { cond_assign: { options = { evaluate: true, + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -2671,6 +2681,7 @@ cond_assign: { iife_assign: { options = { evaluate: true, + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -2701,6 +2712,7 @@ iife_assign: { issue_1850_1: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: false, unused: true, @@ -2724,6 +2736,7 @@ issue_1850_1: { issue_1850_2: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: "funcs", unused: true, @@ -2746,6 +2759,7 @@ issue_1850_2: { issue_1850_3: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: "vars", unused: true, @@ -2769,6 +2783,7 @@ issue_1850_3: { issue_1850_4: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -2792,6 +2807,7 @@ issue_1850_4: { issue_1865: { options = { evaluate: true, + reduce_funcs: true, reduce_vars: true, unsafe: true, } @@ -2895,6 +2911,7 @@ accessor_2: { options = { collapse_vars: true, evaluate: true, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -2920,6 +2937,7 @@ accessor_2: { for_in_prop: { options = { + reduce_funcs: true, reduce_vars: true, } input: { @@ -2947,6 +2965,7 @@ obj_var_1: { options = { evaluate: true, passes: 2, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -2976,6 +2995,7 @@ obj_var_2: { inline: true, passes: 2, properties: true, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -3002,6 +3022,7 @@ obj_arg_1: { evaluate: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3033,6 +3054,7 @@ obj_arg_2: { inline: true, passes: 2, properties: true, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -3060,6 +3082,7 @@ func_arg_1: { evaluate: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -3084,6 +3107,7 @@ func_arg_2: { evaluate: true, inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -3105,6 +3129,7 @@ func_arg_2: { regex_loop: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3241,6 +3266,7 @@ const_expr_1: { const_expr_2: { options = { evaluate: true, + reduce_funcs: true, reduce_vars: true, toplevel: true, unsafe: true, @@ -3277,6 +3303,7 @@ escaped_prop_1: { evaluate: true, inline: true, pure_getters: "strict", + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -3302,6 +3329,7 @@ escaped_prop_1: { escaped_prop_2: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3336,6 +3364,7 @@ escaped_prop_2: { issue_2420_1: { options = { + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -3378,6 +3407,7 @@ issue_2420_1: { issue_2420_2: { options = { + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -3420,6 +3450,7 @@ issue_2420_2: { issue_2423_1: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3444,6 +3475,7 @@ issue_2423_1: { issue_2423_2: { options = { inline: true, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3467,6 +3499,7 @@ issue_2423_2: { issue_2423_3: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3485,6 +3518,7 @@ issue_2423_3: { issue_2423_4: { options = { inline: true, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -3505,6 +3539,7 @@ issue_2423_5: { options = { inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -3543,6 +3578,7 @@ issue_2423_6: { options = { inline: true, passes: 2, + reduce_funcs: true, reduce_vars: true, side_effects: true, toplevel: true, @@ -3583,6 +3619,7 @@ issue_2423_6: { issue_2440_eval_1: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3613,6 +3650,7 @@ issue_2440_eval_1: { issue_2440_eval_2: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3643,6 +3681,7 @@ issue_2440_eval_2: { issue_2440_with_1: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3669,6 +3708,7 @@ issue_2440_with_1: { issue_2440_with_2: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3695,6 +3735,7 @@ issue_2440_with_2: { issue_2442: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3709,6 +3750,7 @@ issue_2442: { recursive_inlining_1: { options = { + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -3729,6 +3771,7 @@ recursive_inlining_1: { recursive_inlining_2: { options = { + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -3751,6 +3794,7 @@ recursive_inlining_2: { recursive_inlining_3: { options = { passes: 2, + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -3788,6 +3832,7 @@ recursive_inlining_3: { recursive_inlining_4: { options = { + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -3834,6 +3879,7 @@ recursive_inlining_4: { recursive_inlining_5: { options = { + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -3887,6 +3933,7 @@ recursive_inlining_5: { issue_2450_1: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3910,6 +3957,7 @@ issue_2450_1: { issue_2450_2: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3932,6 +3980,7 @@ issue_2450_2: { issue_2450_3: { options = { + reduce_funcs: true, reduce_vars: true, unused: true, } @@ -3962,6 +4011,7 @@ issue_2450_3: { issue_2450_4: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -3995,6 +4045,7 @@ issue_2450_4: { issue_2450_5: { options = { + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -4030,6 +4081,7 @@ issue_2450_5: { issue_2449: { options = { passes: 10, + reduce_funcs: true, reduce_vars: true, toplevel: true, unused: true, @@ -4061,3 +4113,74 @@ issue_2449: { } expect_stdout: "PASS" } + +perf_1: { + options = { + passes: 10, + reduce_funcs: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function foo(x, y, z) { + return x < y ? x * y + z : x * z - y; + } + function indirect_foo(x, y, z) { + return foo(x, y, z); + } + var sum = 0; + for (var i = 0; i < 1e7; ++i) { + sum += indirect_foo(i, i+1, i*3); + } + console.log(sum); + } + expect: { + function indirect_foo(x, y, z) { + return function(x, y, z) { + return x < y ? x * y + z : x * z - y; + }(x, y, z); + } + var sum = 0; + for (var i = 0; i < 1e7; ++i) + sum += indirect_foo(i, i + 1, 3 * i); + console.log(sum); + } + expect_stdout: "333333483333479600000" +} + +perf_2: { + options = { + passes: 10, + reduce_funcs: false, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function foo(x, y, z) { + return x < y ? x * y + z : x * z - y; + } + function indirect_foo(x, y, z) { + return foo(x, y, z); + } + var sum = 0; + for (var i = 0; i < 1e7; ++i) { + sum += indirect_foo(i, i+1, i*3); + } + console.log(sum); + } + expect: { + function foo(x, y, z) { + return x < y ? x * y + z : x * z - y; + } + function indirect_foo(x, y, z) { + return foo(x, y, z); + } + var sum = 0; + for (var i = 0; i < 1e7; ++i) + sum += indirect_foo(i, i + 1, 3 * i); + console.log(sum); + } + expect_stdout: "333333483333479600000" +}