Skip to content

Commit

Permalink
perf: reducing ast node memory overhead (#5133)
Browse files Browse the repository at this point in the history
* perf: pack booleans in bitfield

Booleans are represented by numbers and take up 4 bytes each at the time
of this commit in v8. This commit packs all the boolean fields of the
AST nodes into a single 32-bit integer which ammortizes the cost of the
boolean fields. The end result is smaller AST nodes.

* chore: use bit shifts instead of binary literals

* chore: remove context wrapper accessor from node

We now just access through `this.scope.context` directly.

* chore: cleaning up keys assignment

---------

Co-authored-by: Lukas Taegert-Atkinson <lukastaegert@users.noreply.github.com>
  • Loading branch information
thebanjomatic and lukastaegert committed Oct 14, 2023
1 parent e2f947f commit 72c6639
Show file tree
Hide file tree
Showing 57 changed files with 411 additions and 183 deletions.
5 changes: 2 additions & 3 deletions src/ast/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ export const keys: {
Program: ['body']
};

export function getAndCreateKeys(esTreeNode: GenericEsTreeNode): string[] {
keys[esTreeNode.type] = Object.keys(esTreeNode).filter(
export function createKeysForNode(esTreeNode: GenericEsTreeNode): string[] {
return Object.keys(esTreeNode).filter(
key => typeof esTreeNode[key] === 'object' && key.charCodeAt(0) !== 95 /* _ */
);
return keys[esTreeNode.type];
}
2 changes: 1 addition & 1 deletion src/ast/nodes/ArrayExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default class ArrayExpression extends NodeBase {
element.deoptimizePath(UNKNOWN_PATH);
}
}
this.context.requestTreeshakingPass();
this.scope.context.requestTreeshakingPass();
}

private getObjectEntity(): ObjectEntity {
Expand Down
3 changes: 1 addition & 2 deletions src/ast/nodes/ArrowFunctionExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { OBJECT_PROTOTYPE } from './shared/ObjectPrototype';
import type { PatternNode } from './shared/Pattern';

export default class ArrowFunctionExpression extends FunctionBase {
declare async: boolean;
declare body: BlockStatement | ExpressionNode;
declare params: readonly PatternNode[];
declare preventChildBlockScope: true;
Expand All @@ -23,7 +22,7 @@ export default class ArrowFunctionExpression extends FunctionBase {
protected objectEntity: ObjectEntity | null = null;

createScope(parentScope: Scope): void {
this.scope = new ReturnValueScope(parentScope, this.context);
this.scope = new ReturnValueScope(parentScope, this.scope.context);
}

hasEffects(): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/AssignmentExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,6 @@ export default class AssignmentExpression extends NodeBase {
this.deoptimized = true;
this.left.deoptimizePath(EMPTY_PATH);
this.right.deoptimizePath(UNKNOWN_PATH);
this.context.requestTreeshakingPass();
this.scope.context.requestTreeshakingPass();
}
}
2 changes: 1 addition & 1 deletion src/ast/nodes/AssignmentPattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ export default class AssignmentPattern extends NodeBase implements PatternNode {
this.deoptimized = true;
this.left.deoptimizePath(EMPTY_PATH);
this.right.deoptimizePath(UNKNOWN_PATH);
this.context.requestTreeshakingPass();
this.scope.context.requestTreeshakingPass();
}
}
4 changes: 2 additions & 2 deletions src/ast/nodes/AwaitExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ export default class AwaitExpression extends NodeBase {
if (!this.deoptimized) this.applyDeoptimizations();
if (!this.included) {
this.included = true;
checkTopLevelAwait: if (!this.context.usesTopLevelAwait) {
checkTopLevelAwait: if (!this.scope.context.usesTopLevelAwait) {
let parent = this.parent;
do {
if (parent instanceof FunctionNode || parent instanceof ArrowFunctionExpression)
break checkTopLevelAwait;
} while ((parent = (parent as Node).parent as Node));
this.context.usesTopLevelAwait = true;
this.scope.context.usesTopLevelAwait = true;
}
}
this.argument.include(context, includeChildrenRecursively);
Expand Down
21 changes: 16 additions & 5 deletions src/ast/nodes/BlockStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,29 @@ import { type RenderOptions, renderStatementList } from '../../utils/renderHelpe
import type { HasEffectsContext, InclusionContext } from '../ExecutionContext';
import BlockScope from '../scopes/BlockScope';
import type ChildScope from '../scopes/ChildScope';
import type Scope from '../scopes/Scope';
import ExpressionStatement from './ExpressionStatement';
import * as NodeType from './NodeType';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import { UNKNOWN_EXPRESSION } from './shared/Expression';
import { type IncludeChildren, type Node, StatementBase, type StatementNode } from './shared/Node';

export default class BlockStatement extends StatementBase {
declare body: readonly StatementNode[];
declare type: NodeType.tBlockStatement;

private declare deoptimizeBody: boolean;
private directlyIncluded = false;
private get deoptimizeBody(): boolean {
return isFlagSet(this.flags, Flag.deoptimizeBody);
}
private set deoptimizeBody(value: boolean) {
this.flags = setFlag(this.flags, Flag.deoptimizeBody, value);
}

private get directlyIncluded(): boolean {
return isFlagSet(this.flags, Flag.directlyIncluded);
}
private set directlyIncluded(value: boolean) {
this.flags = setFlag(this.flags, Flag.directlyIncluded, value);
}

addImplicitReturnExpressionToScope(): void {
const lastStatement = this.body[this.body.length - 1];
Expand All @@ -23,10 +34,10 @@ export default class BlockStatement extends StatementBase {
}
}

createScope(parentScope: Scope): void {
createScope(parentScope: ChildScope): void {
this.scope = (this.parent as Node).preventChildBlockScope
? (parentScope as ChildScope)
: new BlockScope(parentScope);
: new BlockScope(parentScope, this.scope.context);
}

hasEffects(context: HasEffectsContext): boolean {
Expand Down
15 changes: 11 additions & 4 deletions src/ast/nodes/CallExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import MemberExpression from './MemberExpression';
import type * as NodeType from './NodeType';
import type SpreadElement from './SpreadElement';
import type Super from './Super';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import CallExpressionBase from './shared/CallExpressionBase';
import { type ExpressionEntity, UNKNOWN_RETURN_EXPRESSION } from './shared/Expression';
import type { ChainElement, ExpressionNode, IncludeChildren } from './shared/Node';
Expand All @@ -24,20 +25,26 @@ export default class CallExpression
{
declare arguments: (ExpressionNode | SpreadElement)[];
declare callee: ExpressionNode | Super;
declare optional: boolean;
declare type: NodeType.tCallExpression;

get optional(): boolean {
return isFlagSet(this.flags, Flag.optional);
}
set optional(value: boolean) {
this.flags = setFlag(this.flags, Flag.optional, value);
}

bind(): void {
super.bind();
if (this.callee instanceof Identifier) {
const variable = this.scope.findVariable(this.callee.name);

if (variable.isNamespace) {
this.context.log(LOGLEVEL_WARN, logCannotCallNamespace(this.callee.name), this.start);
this.scope.context.log(LOGLEVEL_WARN, logCannotCallNamespace(this.callee.name), this.start);
}

if (this.callee.name === 'eval') {
this.context.log(LOGLEVEL_WARN, logEval(this.context.module.id), this.start);
this.scope.context.log(LOGLEVEL_WARN, logEval(this.scope.context.module.id), this.start);
}
}
this.interaction = {
Expand Down Expand Up @@ -114,7 +121,7 @@ export default class CallExpression
EMPTY_PATH,
SHARED_RECURSION_TRACKER
);
this.context.requestTreeshakingPass();
this.scope.context.requestTreeshakingPass();
}

protected getReturnExpression(
Expand Down
8 changes: 4 additions & 4 deletions src/ast/nodes/CatchClause.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import CatchScope from '../scopes/CatchScope';
import type Scope from '../scopes/Scope';
import type ChildScope from '../scopes/ChildScope';
import type BlockStatement from './BlockStatement';
import type * as NodeType from './NodeType';
import { UNKNOWN_EXPRESSION } from './shared/Expression';
Expand All @@ -13,8 +13,8 @@ export default class CatchClause extends NodeBase {
declare scope: CatchScope;
declare type: NodeType.tCatchClause;

createScope(parentScope: Scope): void {
this.scope = new CatchScope(parentScope, this.context);
createScope(parentScope: ChildScope): void {
this.scope = new CatchScope(parentScope, this.scope.context);
}

parseNode(esTreeNode: GenericEsTreeNode): void {
Expand All @@ -23,7 +23,7 @@ export default class CatchClause extends NodeBase {
// name instead of the variable
const { param } = esTreeNode;
if (param) {
(this.param as GenericEsTreeNode) = new (this.context.getNodeConstructor(param.type))(
(this.param as GenericEsTreeNode) = new (this.scope.context.getNodeConstructor(param.type))(
param,
this,
this.scope
Expand Down
11 changes: 6 additions & 5 deletions src/ast/nodes/ClassBody.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { InclusionContext } from '../ExecutionContext';
import type ChildScope from '../scopes/ChildScope';
import ClassBodyScope from '../scopes/ClassBodyScope';
import type Scope from '../scopes/Scope';

import type MethodDefinition from './MethodDefinition';
import type * as NodeType from './NodeType';
import type PropertyDefinition from './PropertyDefinition';
Expand All @@ -12,13 +13,13 @@ export default class ClassBody extends NodeBase {
declare scope: ClassBodyScope;
declare type: NodeType.tClassBody;

createScope(parentScope: Scope): void {
this.scope = new ClassBodyScope(parentScope, this.parent as ClassNode, this.context);
createScope(parentScope: ChildScope): void {
this.scope = new ClassBodyScope(parentScope, this.parent as ClassNode, this.scope.context);
}

include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
this.included = true;
this.context.includeVariableInModule(this.scope.thisVariable);
this.scope.context.includeVariableInModule(this.scope.thisVariable);
for (const definition of this.body) {
definition.include(context, includeChildrenRecursively);
}
Expand All @@ -28,7 +29,7 @@ export default class ClassBody extends NodeBase {
const body: NodeBase[] = (this.body = []);
for (const definition of esTreeNode.body) {
body.push(
new (this.context.getNodeConstructor(definition.type))(
new (this.scope.context.getNodeConstructor(definition.type))(
definition,
this,
definition.static ? this.scope : this.scope.instanceScope
Expand Down
9 changes: 8 additions & 1 deletion src/ast/nodes/ConditionalExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { ObjectPath, PathTracker } from '../utils/PathTracker';
import { EMPTY_PATH, SHARED_RECURSION_TRACKER, UNKNOWN_PATH } from '../utils/PathTracker';
import type * as NodeType from './NodeType';
import type SpreadElement from './SpreadElement';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import type { ExpressionEntity, LiteralValueOrUnknown } from './shared/Expression';
import { UnknownValue } from './shared/Expression';
import { MultiExpression } from './shared/MultiExpression';
Expand All @@ -25,8 +26,14 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz
declare test: ExpressionNode;
declare type: NodeType.tConditionalExpression;

get isBranchResolutionAnalysed(): boolean {
return isFlagSet(this.flags, Flag.isBranchResolutionAnalysed);
}
set isBranchResolutionAnalysed(value: boolean) {
this.flags = setFlag(this.flags, Flag.isBranchResolutionAnalysed, value);
}

private expressionsToBeDeoptimized: DeoptimizableEntity[] = [];
private isBranchResolutionAnalysed = false;
private usedBranch: ExpressionNode | null = null;

deoptimizeArgumentsOnInteractionAtPath(
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/ExportAllDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default class ExportAllDeclaration extends NodeBase {
}

initialise(): void {
this.context.addExport(this);
this.scope.context.addExport(this);
}

render(code: MagicString, _options: RenderOptions, nodeRenderOptions?: NodeRenderOptions): void {
Expand Down
8 changes: 4 additions & 4 deletions src/ast/nodes/ExportDefaultDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default class ExportDefaultDeclaration extends NodeBase {
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
super.include(context, includeChildrenRecursively);
if (includeChildrenRecursively) {
this.context.includeVariableInModule(this.variable);
this.scope.context.includeVariableInModule(this.variable);
}
}

Expand All @@ -53,11 +53,11 @@ export default class ExportDefaultDeclaration extends NodeBase {
this.declarationName =
(declaration.id && declaration.id.name) || (this.declaration as Identifier).name;
this.variable = this.scope.addExportDefaultDeclaration(
this.declarationName || this.context.getModuleName(),
this.declarationName || this.scope.context.getModuleName(),
this,
this.context
this.scope.context
);
this.context.addExport(this);
this.scope.context.addExport(this);
}

removeAnnotations(code: MagicString) {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/ExportNamedDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class ExportNamedDeclaration extends NodeBase {
}

initialise(): void {
this.context.addExport(this);
this.scope.context.addExport(this);
}

removeAnnotations(code: MagicString) {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/ExpressionStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export default class ExpressionStatement extends StatementBase {
this.directive !== 'use strict' &&
this.parent.type === NodeType.Program
) {
this.context.log(
this.scope.context.log(
LOGLEVEL_WARN,
// This is necessary, because either way (deleting or not) can lead to errors.
logModuleLevelDirective(this.directive, this.context.module.id),
logModuleLevelDirective(this.directive, this.scope.context.module.id),
this.start
);
}
Expand Down
8 changes: 4 additions & 4 deletions src/ast/nodes/ForInStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type MagicString from 'magic-string';
import { NO_SEMICOLON, type RenderOptions } from '../../utils/renderHelpers';
import type { HasEffectsContext, InclusionContext } from '../ExecutionContext';
import BlockScope from '../scopes/BlockScope';
import type Scope from '../scopes/Scope';
import type ChildScope from '../scopes/ChildScope';
import { EMPTY_PATH } from '../utils/PathTracker';
import type MemberExpression from './MemberExpression';
import type * as NodeType from './NodeType';
Expand All @@ -23,8 +23,8 @@ export default class ForInStatement extends StatementBase {
declare right: ExpressionNode;
declare type: NodeType.tForInStatement;

createScope(parentScope: Scope): void {
this.scope = new BlockScope(parentScope);
createScope(parentScope: ChildScope): void {
this.scope = new BlockScope(parentScope, this.scope.context);
}

hasEffects(context: HasEffectsContext): boolean {
Expand Down Expand Up @@ -60,6 +60,6 @@ export default class ForInStatement extends StatementBase {
protected applyDeoptimizations(): void {
this.deoptimized = true;
this.left.deoptimizePath(EMPTY_PATH);
this.context.requestTreeshakingPass();
this.scope.context.requestTreeshakingPass();
}
}
17 changes: 12 additions & 5 deletions src/ast/nodes/ForOfStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import type MagicString from 'magic-string';
import { NO_SEMICOLON, type RenderOptions } from '../../utils/renderHelpers';
import type { InclusionContext } from '../ExecutionContext';
import BlockScope from '../scopes/BlockScope';
import type Scope from '../scopes/Scope';
import type ChildScope from '../scopes/ChildScope';
import { EMPTY_PATH, UNKNOWN_PATH } from '../utils/PathTracker';
import type MemberExpression from './MemberExpression';
import type * as NodeType from './NodeType';
import type VariableDeclaration from './VariableDeclaration';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import { UNKNOWN_EXPRESSION } from './shared/Expression';
import {
type ExpressionNode,
Expand All @@ -18,14 +19,20 @@ import type { PatternNode } from './shared/Pattern';
import { includeLoopBody } from './shared/loops';

export default class ForOfStatement extends StatementBase {
declare await: boolean;
declare body: StatementNode;
declare left: VariableDeclaration | PatternNode | MemberExpression;
declare right: ExpressionNode;
declare type: NodeType.tForOfStatement;

createScope(parentScope: Scope): void {
this.scope = new BlockScope(parentScope);
get await(): boolean {
return isFlagSet(this.flags, Flag.await);
}
set await(value: boolean) {
this.flags = setFlag(this.flags, Flag.await, value);
}

createScope(parentScope: ChildScope): void {
this.scope = new BlockScope(parentScope, this.scope.context);
}

hasEffects(): boolean {
Expand Down Expand Up @@ -61,6 +68,6 @@ export default class ForOfStatement extends StatementBase {
this.deoptimized = true;
this.left.deoptimizePath(EMPTY_PATH);
this.right.deoptimizePath(UNKNOWN_PATH);
this.context.requestTreeshakingPass();
this.scope.context.requestTreeshakingPass();
}
}
6 changes: 3 additions & 3 deletions src/ast/nodes/ForStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type MagicString from 'magic-string';
import { NO_SEMICOLON, type RenderOptions } from '../../utils/renderHelpers';
import type { HasEffectsContext, InclusionContext } from '../ExecutionContext';
import BlockScope from '../scopes/BlockScope';
import type Scope from '../scopes/Scope';
import type ChildScope from '../scopes/ChildScope';
import type * as NodeType from './NodeType';
import type VariableDeclaration from './VariableDeclaration';
import {
Expand All @@ -20,8 +20,8 @@ export default class ForStatement extends StatementBase {
declare type: NodeType.tForStatement;
declare update: ExpressionNode | null;

createScope(parentScope: Scope): void {
this.scope = new BlockScope(parentScope);
createScope(parentScope: ChildScope): void {
this.scope = new BlockScope(parentScope, this.scope.context);
}

hasEffects(context: HasEffectsContext): boolean {
Expand Down

0 comments on commit 72c6639

Please sign in to comment.