Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve tree-shaking by propagate const parameter #5443

Merged
merged 60 commits into from Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
58fba0d
feat: improve tree-shaking by propagate const parameter
liuly0322 Mar 23, 2024
61bbfa0
fix: update old tests (for tree-shaking const param)
liuly0322 Mar 23, 2024
7050e85
test: add test for tree-shaking by propagate const parameter
liuly0322 Mar 23, 2024
fdb84cb
feat&perf: support object param
liuly0322 Mar 23, 2024
7251468
style: update coverage
liuly0322 Mar 24, 2024
7381847
test: update tree-shake-literal-parameter
liuly0322 Mar 24, 2024
87f82de
test: update tree-shake top export
liuly0322 Mar 25, 2024
cdcf53c
refactor: tree-shaking-literal
liuly0322 Mar 25, 2024
1c91f75
fix: test indent
liuly0322 Mar 25, 2024
3c9a810
perf: remove same object SPJ
liuly0322 Mar 25, 2024
6e2e866
refactor: support iife
liuly0322 Mar 25, 2024
8087111
test: tree-shake literal iife
liuly0322 Mar 25, 2024
b64f649
fix: args but not callee should not be optimized
liuly0322 Mar 25, 2024
5339c08
refactor: some logic to function base with comment
liuly0322 Mar 26, 2024
3547388
feat&perf: support implicitly undefined
liuly0322 Mar 26, 2024
1d18ba1
test: tree-shake literal conditional
liuly0322 Mar 26, 2024
ac4794d
feat: integrate with optimizeCache
liuly0322 Mar 26, 2024
9aa6a70
test: fix
liuly0322 Mar 26, 2024
9f536bf
feat: function argument side effect
liuly0322 Mar 26, 2024
f3d8232
Merge branch 'master' into master
liuly0322 Mar 27, 2024
fd90a7b
style: revert export default change since deoptimizePath will detect
liuly0322 Mar 28, 2024
4e3aee0
feat: support foo(bar);foo(bar);
liuly0322 Mar 28, 2024
074e7e7
test: add more side-effect and top-level test
liuly0322 Mar 28, 2024
ce6a8a4
Merge branch 'master' into master
liuly0322 Mar 28, 2024
aabd6f0
4.13.2
lukastaegert Mar 28, 2024
ffe234c
Merge branch 'master' into master
liuly0322 Mar 28, 2024
912bacd
test: add export default test
liuly0322 Mar 28, 2024
e823998
Merge branch 'master' into master
liuly0322 Mar 28, 2024
e1143bc
refactor FunctionParameterState and remove initalization
liuly0322 Mar 29, 2024
495ee87
Merge branch 'master' into master
liuly0322 Mar 29, 2024
bfe6a6c
refactor IIFE
liuly0322 Mar 30, 2024
c63e745
feat: support export default anonymous
liuly0322 Mar 30, 2024
6be48de
fix: nested namespace tracking
liuly0322 Mar 30, 2024
9060ae8
feat: support define then export default
liuly0322 Mar 30, 2024
4e71e66
performance
liuly0322 Mar 30, 2024
35857ff
refactor: UNKNOWN_EXPRESSION
liuly0322 Mar 30, 2024
4d41532
refactor: reduce complexity
liuly0322 Mar 30, 2024
b79c151
Merge branch 'master' into master
liuly0322 Apr 3, 2024
de1a0ab
fix: export default function foo and foo called from same mod
liuly0322 Apr 4, 2024
d54864f
style: NodeType
liuly0322 Apr 4, 2024
a19792b
Merge branch 'master' into master
liuly0322 Apr 4, 2024
2b19f62
style: remove counter
liuly0322 Apr 5, 2024
7d6f5fb
perf: cache onlyfunctioncall result
liuly0322 Apr 5, 2024
bb28846
Merge branch 'master' into master
liuly0322 Apr 5, 2024
095e844
Merge branch 'master' into master
liuly0322 Apr 7, 2024
e13a514
style&perf: remove args slice
liuly0322 Apr 7, 2024
7d54f27
perf: export default variable
liuly0322 Apr 8, 2024
a8d2f86
perf: export default variable
liuly0322 Apr 8, 2024
97fcfad
style: small updates: naming, private...
liuly0322 Apr 18, 2024
dfe4cd3
perf: LogicalExpression deoptimize cache
liuly0322 Apr 18, 2024
047ae3e
Merge branch 'master' into master
liuly0322 Apr 18, 2024
aa8741a
style: remove a condition which is always true
liuly0322 Apr 18, 2024
40f9427
style: add protected
liuly0322 Apr 18, 2024
e02ab0d
style: remove a condition which is always true
liuly0322 Apr 18, 2024
51ffb9b
style: remove a condition
liuly0322 Apr 18, 2024
a559d2d
refactor: lazy bind variable
liuly0322 Apr 19, 2024
f73a365
fix: refresh cache if isReassigned change for ParameterVariable
liuly0322 Apr 19, 2024
ea7ffab
fix: make sure deoptimize give a final state
liuly0322 Apr 19, 2024
0f58a40
style: make coverage more happy
liuly0322 Apr 19, 2024
3f2f45e
Merge branch 'master' into master
lukastaegert Apr 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Graph.ts
Expand Up @@ -18,6 +18,7 @@ import { PluginDriver } from './utils/PluginDriver';
import Queue from './utils/Queue';
import { BuildPhase } from './utils/buildPhase';
import { analyseModuleExecution } from './utils/executionOrder';
import { functionParameterPass } from './utils/functionParameterPass';
import { LOGLEVEL_WARN } from './utils/logging';
import {
error,
Expand Down Expand Up @@ -160,6 +161,7 @@ export default class Graph {
}

private includeStatements(): void {
functionParameterPass(this.modules);
const entryModules = [...this.entryModules, ...this.implicitEntryModules];
for (const module of entryModules) {
markModuleAndImpureDependenciesAsExecuted(module);
Expand Down
1 change: 1 addition & 0 deletions src/ast/nodes/Identifier.ts
Expand Up @@ -70,6 +70,7 @@ export default class Identifier extends NodeBase implements PatternNode {
if (!this.variable && isReference(this, this.parent as NodeWithFieldDefinition)) {
this.variable = this.scope.findVariable(this.name);
this.variable.addReference(this);
this.variable.addUsedPlace(this);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/ast/nodes/MemberExpression.ts
Expand Up @@ -156,6 +156,9 @@ export default class MemberExpression
this.isUndefined = true;
} else {
this.variable = resolvedVariable;
if (this.object instanceof Identifier) {
this.variable.addUsedPlace(this);
}
this.scope.addNamespaceMemberAccess(getStringFromPath(path!), resolvedVariable);
}
} else {
Expand Down
12 changes: 12 additions & 0 deletions src/ast/variables/ExportDefaultVariable.ts
Expand Up @@ -3,6 +3,7 @@ import ClassDeclaration from '../nodes/ClassDeclaration';
import type ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration';
import FunctionDeclaration from '../nodes/FunctionDeclaration';
import Identifier, { type IdentifierWithVariable } from '../nodes/Identifier';
import type { NodeBase } from '../nodes/shared/Node';
import LocalVariable from './LocalVariable';
import UndefinedVariable from './UndefinedVariable';
import type Variable from './Variable';
Expand Down Expand Up @@ -37,6 +38,16 @@ export default class ExportDefaultVariable extends LocalVariable {
}
}

addUsedPlace(identifier: NodeBase): void {
const original = this.getOriginalVariable();
this.originalVariable = null;
if (original === this) {
super.addUsedPlace(identifier);
} else {
original.addUsedPlace(identifier);
}
}

forbidName(name: string) {
const original = this.getOriginalVariable();
if (original === this) {
Expand All @@ -57,6 +68,7 @@ export default class ExportDefaultVariable extends LocalVariable {

getDirectOriginalVariable(): Variable | null {
return this.originalId &&
this.originalId.variable &&
(this.hasId ||
!(
this.originalId.isPossibleTDZ() ||
Expand Down
26 changes: 24 additions & 2 deletions src/ast/variables/ParameterVariable.ts
@@ -1,15 +1,18 @@
import type { AstContext } from '../../Module';
import { EMPTY_ARRAY } from '../../utils/blank';
import type { DeoptimizableEntity } from '../DeoptimizableEntity';
import type { NodeInteraction } from '../NodeInteractions';
import { INTERACTION_CALLED } from '../NodeInteractions';
import type ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration';
import type Identifier from '../nodes/Identifier';
import type { ExpressionEntity } from '../nodes/shared/Expression';
import type { ExpressionEntity, LiteralValueOrUnknown } from '../nodes/shared/Expression';
import {
deoptimizeInteraction,
UNKNOWN_EXPRESSION,
UNKNOWN_RETURN_EXPRESSION
UNKNOWN_RETURN_EXPRESSION,
UnknownValue
} from '../nodes/shared/Expression';
import type { ExpressionNode } from '../nodes/shared/Node';
import type { ObjectPath, ObjectPathKey } from '../utils/PathTracker';
import {
PathTracker,
Expand Down Expand Up @@ -71,6 +74,25 @@ export default class ParameterVariable extends LocalVariable {
}
}

knownValue: ExpressionNode | null = null;
setKnownValue(value: ExpressionNode): void {
this.knownValue = value;
}

getLiteralValueAtPath(
path: ObjectPath,
recursionTracker: PathTracker,
origin: DeoptimizableEntity
): LiteralValueOrUnknown {
if (this.isReassigned) {
return UnknownValue;
}
if (this.knownValue) {
return this.knownValue.getLiteralValueAtPath(path, recursionTracker, origin);
}
return UnknownValue;
}

deoptimizeArgumentsOnInteractionAtPath(interaction: NodeInteraction, path: ObjectPath): void {
// For performance reasons, we fully deoptimize all deeper interactions
if (
Expand Down
7 changes: 7 additions & 0 deletions src/ast/variables/Variable.ts
Expand Up @@ -6,6 +6,7 @@ import type { NodeInteraction } from '../NodeInteractions';
import { INTERACTION_ACCESSED } from '../NodeInteractions';
import type Identifier from '../nodes/Identifier';
import { ExpressionEntity } from '../nodes/shared/Expression';
import type { NodeBase } from '../nodes/shared/Node';
import type { VariableKind } from '../nodes/shared/VariableKinds';
import type { ObjectPath } from '../utils/PathTracker';

Expand Down Expand Up @@ -34,6 +35,12 @@ export default class Variable extends ExpressionEntity {
*/
addReference(_identifier: Identifier): void {}

AllUsedPlaces: NodeBase[] = [];

addUsedPlace(identifier: NodeBase): void {
this.AllUsedPlaces.push(identifier);
}

/**
* Prevent this variable from being renamed to this name to avoid name
* collisions
Expand Down
104 changes: 104 additions & 0 deletions src/utils/functionParameterPass.ts
@@ -0,0 +1,104 @@
import type Module from '../Module';
import type CallExpression from '../ast/nodes/CallExpression';
import type FunctionDeclaration from '../ast/nodes/FunctionDeclaration';
import type { LiteralValue } from '../ast/nodes/Literal';
import type { ExpressionNode } from '../ast/nodes/shared/Node';
import { EMPTY_PATH, SHARED_RECURSION_TRACKER } from '../ast/utils/PathTracker';
import LocalVariable from '../ast/variables/LocalVariable';
import ParameterVariable from '../ast/variables/ParameterVariable';

function collectTopLevelFunctionCalls(modules: Module[]) {
const topLevelFunctions = new Map<FunctionDeclaration, CallExpression[]>();
modules = modules.filter(module => module.dynamicImporters.length === 0);
for (const module of modules) {
const scope = module.scope.variables;
for (const [_, v] of scope) {
if (!(v instanceof LocalVariable) || v.kind !== 'function') continue;

const allUses = v.AllUsedPlaces;
if (allUses.length === 0) continue;

const containNonCallExpression = allUses.some(use => use.parent.type !== 'CallExpression');
if (containNonCallExpression) continue;

const function_ = v.declarations[0].parent as FunctionDeclaration;
if (function_.params.length === 0) continue;

const allParameterIsIdentifier = function_.params.every(
parameter =>
parameter.type === 'Identifier' && parameter.variable instanceof ParameterVariable
);
if (!allParameterIsIdentifier) continue;

if (allUses.length === 1) {
forwardFunctionUsedOnce(function_, allUses[0].parent as CallExpression);
} else {
topLevelFunctions.set(
function_,
allUses.map(use => use.parent as CallExpression)
);
}
}
}
return topLevelFunctions;
}

function forwardFunctionUsedOnce(function_: FunctionDeclaration, call: CallExpression) {
const maxLength = Math.min(function_.params.length, call.arguments.length);
for (let index = 0; index < maxLength; index++) {
const parameterVariable = function_.params[index].variable as ParameterVariable;
parameterVariable.setKnownValue(call.arguments[index] as ExpressionNode);
}
}

function setKnownLiteralValue(topLevelFunctions: Map<FunctionDeclaration, CallExpression[]>) {
let changed = false;
const deleteFunctions: Set<FunctionDeclaration> = new Set();

for (const [function_, calls] of topLevelFunctions) {
let parameterLength = function_.params.length;
for (const call of calls) {
parameterLength = Math.min(parameterLength, call.arguments.length);
}
for (let index = 0; index < parameterLength; index++) {
const parameter = function_.params[index];
const literalValues: LiteralValue[] = [];
let allLiteral = true;
for (const call of calls) {
const literal = call.arguments[index].getLiteralValueAtPath(
EMPTY_PATH,
SHARED_RECURSION_TRACKER,
call
);
if (typeof literal === 'symbol') {
allLiteral = false;
break;
}
literalValues.push(literal);
}

if (!allLiteral) continue;

const allSame = literalValues.every(literal => literal === literalValues[0]);
if (!allSame) continue;

changed = true;
deleteFunctions.add(function_);
const parameterVariable = parameter.variable as ParameterVariable;
parameterVariable.setKnownValue(calls[0].arguments[index] as ExpressionNode);
}
}

for (const function_ of deleteFunctions) {
topLevelFunctions.delete(function_);
}
return changed;
}

export function functionParameterPass(modules: Module[]) {
const topLevelFunctions = collectTopLevelFunctionCalls(modules);
let changed = true;
while (changed) {
changed = setKnownLiteralValue(topLevelFunctions);
}
}
Expand Up @@ -5,12 +5,13 @@ define(['exports'], (function (exports) { 'use strict';
console.log('This is the output when a missing export is used internally but not reexported');

function almostUseUnused(useIt) {
if (useIt) {
{
console.log(useIt);
console.log(_missingExportShim);
}
}

almostUseUnused(false);
almostUseUnused(true);

exports.missing1 = _missingExportShim;

Expand Down
Expand Up @@ -5,11 +5,12 @@ var _missingExportShim = void 0;
console.log('This is the output when a missing export is used internally but not reexported');

function almostUseUnused(useIt) {
if (useIt) {
{
console.log(useIt);
console.log(_missingExportShim);
}
}

almostUseUnused(false);
almostUseUnused(true);

exports.missing1 = _missingExportShim;
Expand Up @@ -3,11 +3,12 @@ var _missingExportShim = void 0;
console.log('This is the output when a missing export is used internally but not reexported');

function almostUseUnused(useIt) {
if (useIt) {
{
console.log(useIt);
console.log(_missingExportShim);
}
}

almostUseUnused(false);
almostUseUnused(true);

export { _missingExportShim as missing1 };
Expand Up @@ -8,12 +8,13 @@ System.register([], (function (exports) {
console.log('This is the output when a missing export is used internally but not reexported');

function almostUseUnused(useIt) {
if (useIt) {
{
console.log(useIt);
console.log(_missingExportShim);
}
}

almostUseUnused(false);
almostUseUnused(true);

exports("missing1", _missingExportShim);

Expand Down
Expand Up @@ -2,8 +2,9 @@ console.log('This is the output when a missing export is used internally but not

function almostUseUnused(useIt) {
if (useIt) {
console.log(useIt);
console.log(_missingExportShim);
}
}

almostUseUnused(false);
almostUseUnused(true);
@@ -1,7 +1,7 @@
define(['exports'], (function (exports) { 'use strict';

function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
return x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

exports.getDefaultExportFromCjs = getDefaultExportFromCjs;
Expand Down
@@ -1,7 +1,7 @@
'use strict';

function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
return x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

exports.getDefaultExportFromCjs = getDefaultExportFromCjs;
@@ -1,5 +1,5 @@
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
return x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

export { getDefaultExportFromCjs };
Expand Up @@ -6,7 +6,7 @@ System.register([], (function (exports) {
exports("getDefaultExportFromCjs", getDefaultExportFromCjs);

function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
return x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}

})
Expand Down
1 change: 1 addition & 0 deletions test/form/samples/deopt-string-concatenation/_expected.js
Expand Up @@ -9,3 +9,4 @@ function parseInt(str, radix) {
}

console.log(parseInt('1'));
console.log(parseInt(Symbol('1')));
1 change: 1 addition & 0 deletions test/form/samples/deopt-string-concatenation/main.js
Expand Up @@ -9,3 +9,4 @@ function parseInt(str, radix) {
}

console.log(parseInt('1'));
console.log(parseInt(Symbol('1')));
Expand Up @@ -8,5 +8,6 @@ define((function () { 'use strict';
}

assert.equal( isUsed( true ), 2 );
assert.equal( isUsed( false ), 1 );

}));
Expand Up @@ -8,3 +8,4 @@ function isUsed ( x ) {
}

assert.equal( isUsed( true ), 2 );
assert.equal( isUsed( false ), 1 );
Expand Up @@ -6,3 +6,4 @@ function isUsed ( x ) {
}

assert.equal( isUsed( true ), 2 );
assert.equal( isUsed( false ), 1 );
Expand Up @@ -9,5 +9,6 @@
}

assert.equal( isUsed( true ), 2 );
assert.equal( isUsed( false ), 1 );

})();
Expand Up @@ -11,6 +11,7 @@ System.register('myBundle', [], (function () {
}

assert.equal( isUsed( true ), 2 );
assert.equal( isUsed( false ), 1 );

})
};
Expand Down
Expand Up @@ -11,5 +11,6 @@
}

assert.equal( isUsed( true ), 2 );
assert.equal( isUsed( false ), 1 );

}));