Under some conditions it is possible to safely replace a function call with the body of the called function.
I believe the following conditions must be met:
new.apply).arguments object in an unpredicable way, i.e. using loops or passing it around.I believe this might have a pretty big impact on file size, because:
(() => {
function noop() {
// Begin function body
// End function body
}
// More stuff going on hereā¦
noop();
})()
(() => {
// More stuff going on hereā¦
// Begin function body
// End function body
})()
(() => {
let a;
function fn() {
// Begin function body
a++;
// End function body
}
// More stuff going on hereā¦
fn();
})()
(() => {
let a;
// More stuff going on hereā¦
// Begin function body
a++;
// End function body
})()
(() => {
let a;
const fn = fn() {
// Begin function body
a++;
// End function body
}
// More stuff going on hereā¦
fn();
})()
(() => {
let a;
// More stuff going on hereā¦
// Begin function body
a++;
// End function body
})()
(() => {
let a;
const fn = () => {
// Begin function body
a++;
// End function body
}
// More stuff going on hereā¦
fn();
})()
(() => {
let a;
// More stuff going on hereā¦
// Begin function body
a++;
// End function body
})()
(() => {
let a;
function fn(b, c) {
// Begin function body
// Simple function argument
a += b
// This is the same as using b
a *= arguments[0]
// End function body
}
// More stuff going on hereā¦
fn(42);
})()
(() => {
let a;
// More stuff going on hereā¦
// Function arguments
let __fn_argument_0 = 42,
__fn_argument_1;
// Begin function body
// Simple function argument
a += __fn_argument_0
// This is the same as using b
a *= __fn_argument_0
// End function body
})()
(() => {
let a;
function fn(b, ...params) {
// Begin function body
// Simple function argument
a += b
// This is the same as using b
a -= params[0];
a *= params[1];
a /= params[2];
a -= arguments[0];
a *= arguments[1];
a /= arguments[2];
// End function body
}
// More stuff going on hereā¦
fn(1, 2, 3);
})()
(() => {
let a;
// More stuff going on hereā¦
// Function arguments
let __fn_argument_0 = 1,
__fn_rest_params = [2, 3];
// Begin function body
// Simple function argument
a += __fn_argument_0
// This is the same as using b
a -= __fn_rest_params[0];
a *= __fn_rest_params[1];
a /= __fn_rest_params[2];
a -= __fn_argument_0;
a *= __fn_rest_params[0];
a /= __fn_rest_params[1];
// End function body
})()
(() => {
let a;
function fn(b) {
// Begin function body
// Simple function argument
a += b
// This is the same as using b
// End function body
}
// More stuff going on hereā¦
fn(1, 2, 3);
})()
(() => {
let a;
// More stuff going on hereā¦
// Function arguments
let __fn_argument_0 = 1;
2;
3;
// Begin function body
// Simple function argument
a += __fn_argument_0
// This is the same as using b
// End function body
})()
(() => {
let a;
function fn(b) {
// Begin function body
// Simple function argument
b = 1337
// This is the same as using b
a *= arguments[0]
// End function body
}
// More stuff going on hereā¦
fn(42);
})()
(() => {
let a;
// More stuff going on hereā¦
// Function arguments
let __fn_argument_0 = 42;
// Simple function argument
__fn_argument_0 = 1337
// This is the same as using b
a *= __fn_argument_0
// End function body
})()
(() => {
let a;
function fn() {
// Begin function body
return 1337;
// End function body
}
// More stuff going on hereā¦
a = fn();
})()
(() => {
let a;
// More stuff going on hereā¦
// Simple function argument
// This is the same as using b
a = 1337;
// End function body
})()
(() => {
let a;
(function(b) {
// Begin function body
a += b
// End function body
})(6)
})()
(() => {
let a;
// Function arguments
let __fn_argument_0 = 6;
// Begin function body
a += __fn_argument_0;
// End function body
})()
.call()(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on hereā¦
fn.call({foo: 'bar'}, 'b');
})()
(() => {
let a;
// More stuff going on hereā¦
// Function arguments
let __fn_this = {foo: 'bar'};
__fn_argument_0 = 'b';
// Begin function body
__fn_this.b = __fn_argument_0;
// End function body
})()
.apply()(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on hereā¦
fn.apply({foo: 'bar'}, ['b']);
})()
(() => {
let a;
// More stuff going on hereā¦
// Function arguments
let __fn_this = {foo: 'bar'};
__fn_argument_0 = 'b';
// Begin function body
__fn_this.b = __fn_argument_0;
// End function body
})()
new(() => {
function fn() {
// Begin function body
// End function body
}
// More stuff going on hereā¦
new fn();
})()
(() => {
function fn() {
// Begin function body
if (condition) {
return 'so true'
}
// Do more stuff
// End function body
}
// More stuff going on hereā¦
fn();
})()
apply() arguments.(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on hereā¦
fn.apply({foo: 'bar'}, params);
})()
(() => {
function fn(b) {
// Begin function body
this.b = b;
// End function body
}
// More stuff going on hereā¦
fn.apply({foo: 'bar'}, ['b', ...params]);
})()
(() => {
function fn(b) {
// Begin function body
Array.prototype.map.apply(arguments);
// End function body
}
// More stuff going on hereā¦
fn();
})()
(() => {
function fn(b) {
// Begin function body
console.log(arguments[i]);
// End function body
}
// More stuff going on hereā¦
fn();
})()
I believe it is safe to remove assignments to functions if they are used in this simple way.
(() => {
function fn(b) {
// Begin function body
// End function body
}
fn.prop = foo();
// More stuff going on hereā¦
fn();
})()
(() => {
foo();
// More stuff going on hereā¦
// Begin function body
// End function body
})()
Those look like great optimizations! Just 1 question for you:
// What should this transform to?
(() => {
let a;
function fn() {
let a = 2; // shadowing!
}
fn();
})
The shadowing variable should be renamed. I'm not very familiar with Babel internals, but I do believe I've seen function for creating unique variable names somewhere.
This situation is very similar:
(() => {
let __fn_argument_0;
function fn(b) {
}
fn();
})()
The function may only be used once.
If it is used twice, we can just inline it twice! :stuck_out_tongue_winking_eye:
@qm3ster But then it would be longer, since weāre inserting the function body twice, instead of just once as happens without inlining.
@qm3ster @j-f1
This would really depend on the function body and how much it could be further optimized.
An obvious case in which the function could be inlined multiple times, is if the function is a no-op.
For other cases it would be hard to determine whether this would make the code longer or shorter.
It could be interesting to eventually transform this:
(() => {
let x = 0;
function add(a, b) {
return a + b;
}
x += add(1, 2);
x += add(3, 4);
})()
Into this:
(() => {
let x = 0;
x += 1 + 2;
x += 3 + 4;
})()
And then this:
(() => {
let x = 10;
})()
However, until someone finds a solution to determine this, I think it's better not to inline the function body twice.
Comment in passing - the requirements in the OP say 'It may not return conditionally', but that's easy enough to deal with:
function f() {
if (Math.random() < .5) return;
console.log('hit');
}
f();
->
f : {
if (Math.random() < .5) break f;
console.log('hit');
}
'Course, this probably isn't worth worrying about for the moment. Though that's true also for a lot of the weirder cases - to get this started, e.g., I'd strongly suggest ignoring all functions which use this or arguments at all, or which are invoked with .call.
Most helpful comment
@qm3ster @j-f1
This would really depend on the function body and how much it could be further optimized.
An obvious case in which the function could be inlined multiple times, is if the function is a no-op.
For other cases it would be hard to determine whether this would make the code longer or shorter.
It could be interesting to eventually transform this:
Into this:
And then this:
However, until someone finds a solution to determine this, I think it's better not to inline the function body twice.