Skip to content

Commit

Permalink
Tree shaking fixes (#2315)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Jul 17, 2018
1 parent d13aca5 commit b403e6f
Show file tree
Hide file tree
Showing 105 changed files with 1,426 additions and 533 deletions.
4 changes: 2 additions & 2 deletions src/Graph.ts
Expand Up @@ -60,7 +60,7 @@ export default class Graph {
onwarn: WarningHandler;
plugins: Plugin[];
pluginContext: PluginContext;
reassignmentTracker: EntityPathTracker;
deoptimizationTracker: EntityPathTracker;
resolveDynamicImport: ResolveDynamicImportHook;
resolveId: (id: string, parent: string) => Promise<string | boolean | void>;
scope: GlobalScope;
Expand All @@ -78,7 +78,7 @@ export default class Graph {

constructor(options: InputOptions, watcher?: Watcher) {
this.curChunkIndex = 0;
this.reassignmentTracker = new EntityPathTracker();
this.deoptimizationTracker = new EntityPathTracker();
this.cachedModules = new Map();
if (options.cache) {
if (options.cache.modules) {
Expand Down
8 changes: 4 additions & 4 deletions src/Module.ts
Expand Up @@ -90,7 +90,7 @@ export interface AstContext {
moduleContext: string;
nodeConstructors: { [name: string]: typeof NodeBase };
propertyReadSideEffects: boolean;
reassignmentTracker: EntityPathTracker;
deoptimizationTracker: EntityPathTracker;
requestTreeshakingPass: () => void;
traceExport: (name: string) => Variable;
traceVariable: (name: string) => Variable;
Expand Down Expand Up @@ -275,7 +275,7 @@ export default class Module {
nodeConstructors,
propertyReadSideEffects:
!this.graph.treeshake || this.graph.treeshakingOptions.propertyReadSideEffects,
reassignmentTracker: this.graph.reassignmentTracker,
deoptimizationTracker: this.graph.deoptimizationTracker,
requestTreeshakingPass: () => (this.needsTreeshakingPass = true),
traceExport: this.traceExport.bind(this),
traceVariable: this.traceVariable.bind(this),
Expand Down Expand Up @@ -450,7 +450,7 @@ export default class Module {
const variable = this.traceExport(exportName);

variable.exportName = exportName;
variable.reassignPath(UNKNOWN_PATH);
variable.deoptimizePath(UNKNOWN_PATH);
variable.include();

if (variable.isNamespace) {
Expand All @@ -467,7 +467,7 @@ export default class Module {
variable.reexported = (<ExternalVariable>variable).module.reexported = true;
} else {
variable.include();
variable.reassignPath(UNKNOWN_PATH);
variable.deoptimizePath(UNKNOWN_PATH);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/ast/DeoptimizableEntity.ts
@@ -0,0 +1,3 @@
export interface DeoptimizableEntity {
deoptimizeCache(): void;
}
9 changes: 4 additions & 5 deletions src/ast/Entity.ts
Expand Up @@ -10,10 +10,9 @@ export interface WritableEntity extends Entity {

/**
* Reassign a given path of an object.
* E.g., node.reassignPath(['x', 'y']) is called when something
* is assigned to node.x.y.
* The default noop implementation is ok as long as hasEffectsWhenAssignedAtPath
* always returns true for this node. Otherwise it should be overridden.
* E.g., node.deoptimizePath(['x', 'y']) is called when something
* is assigned to node.x.y. If the path is [UNKNOWN_KEY], then the return
* expression of this node is reassigned as well.
*/
reassignPath(path: ObjectPath): void;
deoptimizePath(path: ObjectPath): void;
}
2 changes: 1 addition & 1 deletion src/ast/nodes/ArrayExpression.ts
Expand Up @@ -19,7 +19,7 @@ export default class ArrayExpression extends NodeBase {
bind() {
super.bind();
for (const element of this.elements) {
if (element !== null) element.reassignPath(UNKNOWN_PATH);
if (element !== null) element.deoptimizePath(UNKNOWN_PATH);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/ArrayPattern.ts
Expand Up @@ -26,11 +26,11 @@ export default class ArrayPattern extends NodeBase implements PatternNode {
return false;
}

reassignPath(path: ObjectPath) {
deoptimizePath(path: ObjectPath) {
if (path.length === 0) {
for (const element of this.elements) {
if (element !== null) {
element.reassignPath(path);
element.deoptimizePath(path);
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/ast/nodes/ArrowFunctionExpression.ts
Expand Up @@ -2,7 +2,7 @@ import CallOptions from '../CallOptions';
import { ExecutionPathOptions } from '../ExecutionPathOptions';
import ReturnValueScope from '../scopes/ReturnValueScope';
import Scope from '../scopes/Scope';
import { ObjectPath, UNKNOWN_EXPRESSION } from '../values';
import { ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_KEY, UNKNOWN_PATH } from '../values';
import BlockStatement from './BlockStatement';
import * as NodeType from './NodeType';
import { ExpressionNode, GenericEsTreeNode, NodeBase } from './shared/Node';
Expand Down Expand Up @@ -72,6 +72,14 @@ export default class ArrowFunctionExpression extends NodeBase {
}
super.parseNode(esTreeNode);
}

deoptimizePath(path: ObjectPath) {
// A reassignment of UNKNOWN_PATH is considered equivalent to having lost track
// which means the return expression needs to be reassigned
if (path.length === 1 && path[0] === UNKNOWN_KEY) {
this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH);
}
}
}

ArrowFunctionExpression.prototype.preventChildBlockScope = true;
20 changes: 17 additions & 3 deletions src/ast/nodes/AssignmentExpression.ts
Expand Up @@ -6,14 +6,28 @@ import { PatternNode } from './shared/Pattern';

export default class AssignmentExpression extends NodeBase {
type: NodeType.tAssignmentExpression;
operator:
| '='
| '+='
| '-='
| '*='
| '/='
| '%='
| '<<='
| '>>='
| '>>>='
| '|='
| '^='
| '&='
| '**=';
left: PatternNode | ExpressionNode;
right: ExpressionNode;

bind() {
super.bind();
this.left.reassignPath(EMPTY_PATH);
// We can not propagate mutations of the new binding to the old binding with certainty
this.right.reassignPath(UNKNOWN_PATH);
this.left.deoptimizePath(EMPTY_PATH);
// We cannot propagate mutations of the new binding to the old binding with certainty
this.right.deoptimizePath(UNKNOWN_PATH);
}

hasEffects(options: ExecutionPathOptions): boolean {
Expand Down
8 changes: 4 additions & 4 deletions src/ast/nodes/AssignmentPattern.ts
Expand Up @@ -12,8 +12,8 @@ export default class AssignmentPattern extends NodeBase implements PatternNode {

bind() {
super.bind();
this.left.reassignPath(EMPTY_PATH);
this.right.reassignPath(UNKNOWN_PATH);
this.left.deoptimizePath(EMPTY_PATH);
this.right.deoptimizePath(UNKNOWN_PATH);
}

declare(kind: string, init: ExpressionEntity) {
Expand All @@ -24,7 +24,7 @@ export default class AssignmentPattern extends NodeBase implements PatternNode {
return path.length > 0 || this.left.hasEffectsWhenAssignedAtPath(EMPTY_PATH, options);
}

reassignPath(path: ObjectPath) {
path.length === 0 && this.left.reassignPath(path);
deoptimizePath(path: ObjectPath) {
path.length === 0 && this.left.deoptimizePath(path);
}
}
11 changes: 0 additions & 11 deletions src/ast/nodes/AssignmentProperty.ts

This file was deleted.

8 changes: 5 additions & 3 deletions src/ast/nodes/BinaryExpression.ts
@@ -1,3 +1,4 @@
import { DeoptimizableEntity } from '../DeoptimizableEntity';
import { ExecutionPathOptions } from '../ExecutionPathOptions';
import { ImmutableEntityPathTracker } from '../utils/ImmutableEntityPathTracker';
import { EMPTY_PATH, LiteralValueOrUnknown, ObjectPath, UNKNOWN_VALUE } from '../values';
Expand Down Expand Up @@ -40,13 +41,14 @@ export default class BinaryExpression extends NodeBase {

getLiteralValueAtPath(
path: ObjectPath,
recursionTracker: ImmutableEntityPathTracker
recursionTracker: ImmutableEntityPathTracker,
origin: DeoptimizableEntity
): LiteralValueOrUnknown {
if (path.length > 0) return UNKNOWN_VALUE;
const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, recursionTracker);
const leftValue = this.left.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin);
if (leftValue === UNKNOWN_VALUE) return UNKNOWN_VALUE;

const rightValue = this.right.getLiteralValueAtPath(EMPTY_PATH, recursionTracker);
const rightValue = this.right.getLiteralValueAtPath(EMPTY_PATH, recursionTracker, origin);
if (rightValue === UNKNOWN_VALUE) return UNKNOWN_VALUE;

const operatorFn = binaryOperators[this.operator];
Expand Down
81 changes: 69 additions & 12 deletions src/ast/nodes/CallExpression.ts
@@ -1,23 +1,35 @@
import CallOptions from '../CallOptions';
import { DeoptimizableEntity } from '../DeoptimizableEntity';
import { ExecutionPathOptions } from '../ExecutionPathOptions';
import {
EMPTY_IMMUTABLE_TRACKER,
ImmutableEntityPathTracker
} from '../utils/ImmutableEntityPathTracker';
import { EMPTY_PATH, ObjectPath, UNKNOWN_EXPRESSION, UNKNOWN_PATH } from '../values';
import {
EMPTY_PATH,
LiteralValueOrUnknown,
ObjectPath,
UNKNOWN_EXPRESSION,
UNKNOWN_PATH,
UNKNOWN_VALUE
} from '../values';
import Identifier from './Identifier';
import * as NodeType from './NodeType';
import { ExpressionEntity } from './shared/Expression';
import { ExpressionNode, NodeBase } from './shared/Node';
import SpreadElement from './SpreadElement';

export default class CallExpression extends NodeBase {
export default class CallExpression extends NodeBase implements DeoptimizableEntity {
type: NodeType.tCallExpression;
callee: ExpressionNode;
arguments: (ExpressionNode | SpreadElement)[];

private callOptions: CallOptions;

// Caching and deoptimization:
// We collect deoptimization information if returnExpression !== UNKNOWN_EXPRESSION
private returnExpression: ExpressionEntity | null;
private expressionsToBeDeoptimized: DeoptimizableEntity[];

bind() {
super.bind();
Expand Down Expand Up @@ -48,31 +60,74 @@ export default class CallExpression extends NodeBase {
if (this.returnExpression === null) {
this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath(
EMPTY_PATH,
EMPTY_IMMUTABLE_TRACKER
EMPTY_IMMUTABLE_TRACKER,
this
);
}
for (const argument of this.arguments) {
// This will make sure all properties of parameters behave as "unknown"
argument.reassignPath(UNKNOWN_PATH);
argument.deoptimizePath(UNKNOWN_PATH);
}
}

deoptimizeCache() {
if (this.returnExpression !== UNKNOWN_EXPRESSION) {
this.returnExpression = UNKNOWN_EXPRESSION;
for (const expression of this.expressionsToBeDeoptimized) {
expression.deoptimizeCache();
}
}
}

getLiteralValueAtPath(
path: ObjectPath,
recursionTracker: ImmutableEntityPathTracker,
origin: DeoptimizableEntity
): LiteralValueOrUnknown {
if (this.returnExpression === null) {
this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath(
EMPTY_PATH,
recursionTracker,
this
);
}
if (
this.returnExpression === UNKNOWN_EXPRESSION ||
recursionTracker.isTracked(this.returnExpression, path)
) {
return UNKNOWN_VALUE;
}
this.expressionsToBeDeoptimized.push(origin);
return this.returnExpression.getLiteralValueAtPath(
path,
recursionTracker.track(this.returnExpression, path),
origin
);
}

getReturnExpressionWhenCalledAtPath(
path: ObjectPath,
recursionTracker: ImmutableEntityPathTracker
recursionTracker: ImmutableEntityPathTracker,
origin: DeoptimizableEntity
) {
if (this.returnExpression === null) {
this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath(
EMPTY_PATH,
recursionTracker
recursionTracker,
this
);
}
if (recursionTracker.isTracked(this.returnExpression, path)) {
if (
this.returnExpression === UNKNOWN_EXPRESSION ||
recursionTracker.isTracked(this.returnExpression, path)
) {
return UNKNOWN_EXPRESSION;
}
this.expressionsToBeDeoptimized.push(origin);
return this.returnExpression.getReturnExpressionWhenCalledAtPath(
path,
recursionTracker.track(this.returnExpression, path)
recursionTracker.track(this.returnExpression, path),
origin
);
}

Expand Down Expand Up @@ -140,17 +195,19 @@ export default class CallExpression extends NodeBase {
args: this.arguments,
callIdentifier: this
});
this.expressionsToBeDeoptimized = [];
}

reassignPath(path: ObjectPath) {
if (path.length > 0 && !this.context.reassignmentTracker.track(this, path)) {
deoptimizePath(path: ObjectPath) {
if (path.length > 0 && !this.context.deoptimizationTracker.track(this, path)) {
if (this.returnExpression === null) {
this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath(
EMPTY_PATH,
EMPTY_IMMUTABLE_TRACKER
EMPTY_IMMUTABLE_TRACKER,
this
);
}
this.returnExpression.reassignPath(path);
this.returnExpression.deoptimizePath(path);
}
}
}

0 comments on commit b403e6f

Please sign in to comment.