Skip to content

Commit

Permalink
feature(fix): function parameter tracking (#5483)
Browse files Browse the repository at this point in the history
* feat: re-init function parameter tracking

Revert "Revert function parameter tracking logic for now (#5487)"

This reverts commit 74da135.

* refactor: see #5483 comments. squashed with some style commits

style: move deoptimizeCache logic

style: naming

style: naming

remove useless comment

style: small update

* make sure every addUsedPlace has a requestTreeshakingPass

This is to make sure if onlyFunctionCallUsed becomes false,
there is a new tree-shaking pass to deoptimize it

* fix: missing reassigned guard
  • Loading branch information
liuly0322 committed Apr 27, 2024
1 parent 778066d commit d98e59d
Show file tree
Hide file tree
Showing 90 changed files with 1,069 additions and 45 deletions.
10 changes: 9 additions & 1 deletion src/ast/nodes/ArrowFunctionExpression.ts
Expand Up @@ -5,8 +5,9 @@ import type ChildScope from '../scopes/ChildScope';
import ReturnValueScope from '../scopes/ReturnValueScope';
import { type ObjectPath } from '../utils/PathTracker';
import type BlockStatement from './BlockStatement';
import type CallExpression from './CallExpression';
import Identifier from './Identifier';
import type * as NodeType from './NodeType';
import * as NodeType from './NodeType';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import FunctionBase from './shared/FunctionBase';
import type { ExpressionNode, IncludeChildren } from './shared/Node';
Expand Down Expand Up @@ -67,6 +68,13 @@ export default class ArrowFunctionExpression extends FunctionBase {
return false;
}

protected onlyFunctionCallUsed(): boolean {
const isIIFE =
this.parent.type === NodeType.CallExpression &&
(this.parent as CallExpression).callee === this;
return isIIFE || super.onlyFunctionCallUsed();
}

include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
super.include(context, includeChildrenRecursively);
for (const parameter of this.params) {
Expand Down
23 changes: 10 additions & 13 deletions src/ast/nodes/CallExpression.ts
Expand Up @@ -63,20 +63,17 @@ export default class CallExpression
}

hasEffects(context: HasEffectsContext): boolean {
try {
for (const argument of this.arguments) {
if (argument.hasEffects(context)) return true;
}
if (this.annotationPure) {
return false;
}
return (
this.callee.hasEffects(context) ||
this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context)
);
} finally {
if (!this.deoptimized) this.applyDeoptimizations();
if (!this.deoptimized) this.applyDeoptimizations();
for (const argument of this.arguments) {
if (argument.hasEffects(context)) return true;
}
if (this.annotationPure) {
return false;
}
return (
this.callee.hasEffects(context) ||
this.callee.hasEffectsOnInteractionAtPath(EMPTY_PATH, this.interaction, context)
);
}

include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
Expand Down
5 changes: 5 additions & 0 deletions src/ast/nodes/FunctionDeclaration.ts
Expand Up @@ -14,6 +14,11 @@ export default class FunctionDeclaration extends FunctionNode {
}
}

protected onlyFunctionCallUsed(): boolean {
// call super.onlyFunctionCallUsed for export default anonymous function
return this.id?.variable.getOnlyFunctionCallUsed() ?? super.onlyFunctionCallUsed();
}

parseNode(esTreeNode: GenericEsTreeNode): this {
if (esTreeNode.id !== null) {
this.id = new Identifier(this, this.scope.parent as ChildScope).parseNode(
Expand Down
9 changes: 9 additions & 0 deletions src/ast/nodes/FunctionExpression.ts
Expand Up @@ -2,6 +2,7 @@ import type MagicString from 'magic-string';
import { BLANK } from '../../utils/blank';
import type { NodeRenderOptions, RenderOptions } from '../../utils/renderHelpers';
import ChildScope from '../scopes/ChildScope';
import type CallExpression from './CallExpression';
import type { IdentifierWithVariable } from './Identifier';
import Identifier from './Identifier';
import * as NodeType from './NodeType';
Expand All @@ -25,6 +26,14 @@ export default class FunctionExpression extends FunctionNode {
return super.parseNode(esTreeNode);
}

protected onlyFunctionCallUsed(): boolean {
const isIIFE =
this.parent.type === NodeType.CallExpression &&
(this.parent as CallExpression).callee === this &&
(this.id === null || this.id.variable.getOnlyFunctionCallUsed());
return isIIFE || super.onlyFunctionCallUsed();
}

render(
code: MagicString,
options: RenderOptions,
Expand Down
6 changes: 6 additions & 0 deletions src/ast/nodes/Identifier.ts
Expand Up @@ -60,10 +60,12 @@ export default class Identifier extends NodeBase implements PatternNode {
}
}

private isReferenceVariable = false;
bind(): void {
if (!this.variable && isReference(this, this.parent as NodeWithFieldDefinition)) {
this.variable = this.scope.findVariable(this.name);
this.variable.addReference(this);
this.isReferenceVariable = true;
}
}

Expand Down Expand Up @@ -295,6 +297,10 @@ export default class Identifier extends NodeBase implements PatternNode {
this.variable.consolidateInitializers();
this.scope.context.requestTreeshakingPass();
}
if (this.isReferenceVariable) {
this.variable!.addUsedPlace(this);
this.scope.context.requestTreeshakingPass();
}
}

private getVariableRespectingTDZ(): ExpressionEntity | null {
Expand Down
4 changes: 4 additions & 0 deletions src/ast/nodes/MemberExpression.ts
Expand Up @@ -396,6 +396,10 @@ export default class MemberExpression
);
this.scope.context.requestTreeshakingPass();
}
if (this.variable) {
this.variable.addUsedPlace(this);
this.scope.context.requestTreeshakingPass();
}
}

private applyAssignmentDeoptimization(): void {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/UpdateExpression.ts
Expand Up @@ -87,7 +87,7 @@ export default class UpdateExpression extends NodeBase {
this.argument.deoptimizePath(EMPTY_PATH);
if (this.argument instanceof Identifier) {
const variable = this.scope.findVariable(this.argument.name);
variable.isReassigned = true;
variable.markReassigned();
}
this.scope.context.requestTreeshakingPass();
}
Expand Down
49 changes: 49 additions & 0 deletions src/ast/nodes/shared/FunctionBase.ts
Expand Up @@ -10,11 +10,16 @@ import {
import type ReturnValueScope from '../../scopes/ReturnValueScope';
import type { ObjectPath, PathTracker } from '../../utils/PathTracker';
import { UNKNOWN_PATH, UnknownKey } from '../../utils/PathTracker';
import { UNDEFINED_EXPRESSION } from '../../values';
import type ParameterVariable from '../../variables/ParameterVariable';
import type Variable from '../../variables/Variable';
import BlockStatement from '../BlockStatement';
import type ExportDefaultDeclaration from '../ExportDefaultDeclaration';
import Identifier from '../Identifier';
import * as NodeType from '../NodeType';
import RestElement from '../RestElement';
import type SpreadElement from '../SpreadElement';
import type VariableDeclarator from '../VariableDeclarator';
import { Flag, isFlagSet, setFlag } from './BitFlags';
import type { ExpressionEntity, LiteralValueOrUnknown } from './Expression';
import { UNKNOWN_EXPRESSION, UNKNOWN_RETURN_EXPRESSION } from './Expression';
Expand All @@ -27,6 +32,8 @@ import {
import type { ObjectEntity } from './ObjectEntity';
import type { PatternNode } from './Pattern';

type InteractionCalledArguments = NodeInteractionCalled['args'];

export default abstract class FunctionBase extends NodeBase {
declare body: BlockStatement | ExpressionNode;
declare params: PatternNode[];
Expand Down Expand Up @@ -57,6 +64,27 @@ export default abstract class FunctionBase extends NodeBase {
this.flags = setFlag(this.flags, Flag.generator, value);
}

private updateParameterVariableValues(_arguments: InteractionCalledArguments): void {
for (let position = 0; position < this.params.length; position++) {
const parameter = this.params[position];
if (!(parameter instanceof Identifier)) {
continue;
}
const parameterVariable = parameter.variable as ParameterVariable;
const argument = _arguments[position + 1] ?? UNDEFINED_EXPRESSION;
parameterVariable.updateKnownValue(argument);
}
}

private deoptimizeParameterVariableValues() {
for (const parameter of this.params) {
if (parameter instanceof Identifier) {
const parameterVariable = parameter.variable as ParameterVariable;
parameterVariable.markReassigned();
}
}
}

protected objectEntity: ObjectEntity | null = null;

deoptimizeArgumentsOnInteractionAtPath(
Expand Down Expand Up @@ -84,6 +112,7 @@ export default abstract class FunctionBase extends NodeBase {
this.addArgumentToBeDeoptimized(argument);
}
}
this.updateParameterVariableValues(args);
} else {
this.getObjectEntity().deoptimizeArgumentsOnInteractionAtPath(
interaction,
Expand All @@ -102,6 +131,7 @@ export default abstract class FunctionBase extends NodeBase {
for (const parameterList of this.scope.parameters) {
for (const parameter of parameterList) {
parameter.deoptimizePath(UNKNOWN_PATH);
parameter.markReassigned();
}
}
}
Expand Down Expand Up @@ -180,7 +210,26 @@ export default abstract class FunctionBase extends NodeBase {
return false;
}

/**
* If the function (expression or declaration) is only used as function calls
*/
protected onlyFunctionCallUsed(): boolean {
let variable: Variable | null = null;
if (this.parent.type === NodeType.VariableDeclarator) {
variable = (this.parent as VariableDeclarator).id.variable ?? null;
}
if (this.parent.type === NodeType.ExportDefaultDeclaration) {
variable = (this.parent as ExportDefaultDeclaration).variable;
}
return variable?.getOnlyFunctionCallUsed() ?? false;
}

private parameterVariableValuesDeoptimized = false;
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
if (!this.parameterVariableValuesDeoptimized && !this.onlyFunctionCallUsed()) {
this.parameterVariableValuesDeoptimized = true;
this.deoptimizeParameterVariableValues();
}
if (!this.deoptimized) this.applyDeoptimizations();
this.included = true;
const { brokenFlow } = context;
Expand Down
10 changes: 10 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,15 @@ export default class ExportDefaultVariable extends LocalVariable {
}
}

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

forbidName(name: string) {
const original = this.getOriginalVariable();
if (original === this) {
Expand Down
9 changes: 6 additions & 3 deletions src/ast/variables/GlobalVariable.ts
Expand Up @@ -13,9 +13,12 @@ import type { ObjectPath, PathTracker } from '../utils/PathTracker';
import Variable from './Variable';

export default class GlobalVariable extends Variable {
// Ensure we use live-bindings for globals as we do not know if they have
// been reassigned
isReassigned = true;
constructor(name: string) {
super(name);
// Ensure we use live-bindings for globals as we do not know if they have
// been reassigned
this.markReassigned();
}

deoptimizeArgumentsOnInteractionAtPath(
interaction: NodeInteraction,
Expand Down
4 changes: 2 additions & 2 deletions src/ast/variables/LocalVariable.ts
Expand Up @@ -91,7 +91,7 @@ export default class LocalVariable extends Variable {
return;
}
if (path.length === 0) {
this.isReassigned = true;
this.markReassigned();
const expressionsToBeDeoptimized = this.expressionsToBeDeoptimized;
this.expressionsToBeDeoptimized = EMPTY_ARRAY as unknown as DeoptimizableEntity[];
for (const expression of expressionsToBeDeoptimized) {
Expand Down Expand Up @@ -222,7 +222,7 @@ export default class LocalVariable extends Variable {
if (this.additionalInitializers === null) {
this.additionalInitializers = [this.init];
this.init = UNKNOWN_EXPRESSION;
this.isReassigned = true;
this.markReassigned();
}
return this.additionalInitializers;
}
Expand Down

0 comments on commit d98e59d

Please sign in to comment.