Skip to content

Commit

Permalink
perf: make ast fields optional instead of null
Browse files Browse the repository at this point in the history
With esTreeNode, optional branches of the AST are represented by
explicit null values, and this was reflected in the Rollup AST nodes as
well since we copy these null values from the esTreeNode to the Rollup
AST node during parseNode.

This is potentially memory inefficient as nodes frequent optional fields
will take up 4 bytes of memory on each instance of that node. In
comparison, if we can avoid setting these values at all on our nodes,
we can save some additional memory.

This has the potential to create more hidden classes as each variation
of the node will have a different set of properties, but performance
does not really appear to regress much from what I can tell.
  • Loading branch information
thebanjomatic committed Sep 11, 2023
1 parent 78ef1fc commit dbac69b
Show file tree
Hide file tree
Showing 39 changed files with 127 additions and 119 deletions.
4 changes: 2 additions & 2 deletions src/ast/nodes/ArrayExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ObjectEntity, type ObjectProperty } from './shared/ObjectEntity';
export default class ArrayExpression extends NodeBase {
declare elements: readonly (ExpressionNode | SpreadElement | null)[];
declare type: NodeType.tArrayExpression;
private objectEntity: ObjectEntity | null = null;
private objectEntity?: ObjectEntity = undefined;

deoptimizeArgumentsOnInteractionAtPath(
interaction: NodeInteraction,
Expand Down Expand Up @@ -80,7 +80,7 @@ export default class ArrayExpression extends NodeBase {
}

private getObjectEntity(): ObjectEntity {
if (this.objectEntity !== null) {
if (this.objectEntity !== undefined) {
return this.objectEntity;
}
const properties: ObjectProperty[] = [
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/ArrowFunctionExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export default class ArrowFunctionExpression extends FunctionBase {
declare preventChildBlockScope: true;
declare scope: ReturnValueScope;
declare type: NodeType.tArrowFunctionExpression;
protected objectEntity: ObjectEntity | null = null;
protected objectEntity?: ObjectEntity;

createScope(parentScope: Scope): void {
this.scope = new ReturnValueScope(parentScope, this.context);
Expand Down Expand Up @@ -69,7 +69,7 @@ export default class ArrowFunctionExpression extends FunctionBase {
}

protected getObjectEntity(): ObjectEntity {
if (this.objectEntity !== null) {
if (this.objectEntity !== undefined) {
return this.objectEntity;
}
return (this.objectEntity = new ObjectEntity([], OBJECT_PROTOTYPE));
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/BreakStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type * as NodeType from './NodeType';
import { StatementBase } from './shared/Node';

export default class BreakStatement extends StatementBase {
declare label: Identifier | null;
declare label?: Identifier;
declare type: NodeType.tBreakStatement;

hasEffects(context: HasEffectsContext): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/CallExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export default class CallExpression
protected getReturnExpression(
recursionTracker: PathTracker = SHARED_RECURSION_TRACKER
): [expression: ExpressionEntity, isPure: boolean] {
if (this.returnExpression === null) {
if (this.returnExpression === undefined) {
this.returnExpression = UNKNOWN_RETURN_EXPRESSION;
return (this.returnExpression = this.callee.getReturnExpressionWhenCalledAtPath(
EMPTY_PATH,
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/CatchClause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { PatternNode } from './shared/Pattern';

export default class CatchClause extends NodeBase {
declare body: BlockStatement;
declare param: PatternNode | null;
declare param?: PatternNode;
declare preventChildBlockScope: true;
declare scope: CatchScope;
declare type: NodeType.tCatchClause;
Expand Down
6 changes: 3 additions & 3 deletions src/ast/nodes/ClassDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ import ClassNode from './shared/ClassNode';
import type { GenericEsTreeNode } from './shared/Node';

export default class ClassDeclaration extends ClassNode {
declare id: IdentifierWithVariable | null;
declare id?: IdentifierWithVariable;
declare type: NodeType.tClassDeclaration;

initialise(): void {
super.initialise();
if (this.id !== null) {
if (this.id != undefined) {
this.id.variable.isId = true;
}
}

parseNode(esTreeNode: GenericEsTreeNode): void {
if (esTreeNode.id !== null) {
if (esTreeNode.id != undefined) {
this.id = new Identifier(
esTreeNode.id,
this,
Expand Down
14 changes: 9 additions & 5 deletions src/ast/nodes/ConditionalExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz
}

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

deoptimizeArgumentsOnInteractionAtPath(
interaction: NodeInteraction,
Expand All @@ -47,9 +47,9 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz
}

deoptimizeCache(): void {
if (this.usedBranch !== null) {
if (this.usedBranch !== undefined) {
const unusedBranch = this.usedBranch === this.consequent ? this.alternate : this.consequent;
this.usedBranch = null;
this.usedBranch = undefined;
unusedBranch.deoptimizePath(UNKNOWN_PATH);
const { expressionsToBeDeoptimized } = this;
this.expressionsToBeDeoptimized = EMPTY_ARRAY as unknown as DeoptimizableEntity[];
Expand Down Expand Up @@ -141,7 +141,11 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz
include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
this.included = true;
const usedBranch = this.getUsedBranch();
if (includeChildrenRecursively || this.test.shouldBeIncluded(context) || usedBranch === null) {
if (
includeChildrenRecursively ||
this.test.shouldBeIncluded(context) ||
usedBranch === undefined
) {
this.test.include(context, includeChildrenRecursively);
this.consequent.include(context, includeChildrenRecursively);
this.alternate.include(context, includeChildrenRecursively);
Expand Down Expand Up @@ -210,7 +214,7 @@ export default class ConditionalExpression extends NodeBase implements Deoptimiz
this.isBranchResolutionAnalysed = true;
const testValue = this.test.getLiteralValueAtPath(EMPTY_PATH, SHARED_RECURSION_TRACKER, this);
return typeof testValue === 'symbol'
? null
? undefined
: (this.usedBranch = testValue ? this.consequent : this.alternate);
}
}
2 changes: 1 addition & 1 deletion src/ast/nodes/ContinueStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type * as NodeType from './NodeType';
import { StatementBase } from './shared/Node';

export default class ContinueStatement extends StatementBase {
declare label: Identifier | null;
declare label?: Identifier;
declare type: NodeType.tContinueStatement;

hasEffects(context: HasEffectsContext): boolean {
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 @@ -8,7 +8,7 @@ import { NodeBase } from './shared/Node';

export default class ExportAllDeclaration extends NodeBase {
declare assertions: ImportAttribute[];
declare exported: Identifier | null;
declare exported?: Identifier;
declare needsBoundaries: true;
declare source: Literal<string>;
declare type: NodeType.tExportAllDeclaration;
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/ExportDefaultDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default class ExportDefaultDeclaration extends NodeBase {
this.renderNamedDeclaration(
code,
declarationStart,
this.declaration.id === null
this.declaration.id === undefined
? getFunctionIdInsertPosition(code.original, declarationStart)
: null,
options
Expand All @@ -77,7 +77,7 @@ export default class ExportDefaultDeclaration extends NodeBase {
this.renderNamedDeclaration(
code,
declarationStart,
this.declaration.id === null
this.declaration.id === undefined
? findFirstOccurrenceOutsideComment(code.original, 'class', start) + 'class'.length
: null,
options
Expand Down
6 changes: 3 additions & 3 deletions src/ast/nodes/ExportNamedDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { type Node, NodeBase } from './shared/Node';

export default class ExportNamedDeclaration extends NodeBase {
declare assertions: ImportAttribute[];
declare declaration: FunctionDeclaration | ClassDeclaration | VariableDeclaration | null;
declare declaration?: FunctionDeclaration | ClassDeclaration | VariableDeclaration;
declare needsBoundaries: true;
declare source: Literal<string> | null;
declare source?: Literal<string>;
declare specifiers: readonly ExportSpecifier[];
declare type: NodeType.tExportNamedDeclaration;

Expand All @@ -33,7 +33,7 @@ export default class ExportNamedDeclaration extends NodeBase {

render(code: MagicString, options: RenderOptions, nodeRenderOptions?: NodeRenderOptions): void {
const { start, end } = nodeRenderOptions as { end: number; start: number };
if (this.declaration === null) {
if (this.declaration === undefined) {
code.remove(start, end);
} else {
code.remove(this.start, this.declaration.start);
Expand Down
6 changes: 3 additions & 3 deletions src/ast/nodes/ForStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import { hasLoopBodyEffects, includeLoopBody } from './shared/loops';

export default class ForStatement extends StatementBase {
declare body: StatementNode;
declare init: VariableDeclaration | ExpressionNode | null;
declare test: ExpressionNode | null;
declare init?: VariableDeclaration | ExpressionNode;
declare test?: ExpressionNode;
declare type: NodeType.tForStatement;
declare update: ExpressionNode | null;
declare update?: ExpressionNode;

createScope(parentScope: ChildScope): void {
this.scope = new BlockScope(parentScope, parentScope.context);
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/FunctionDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ export default class FunctionDeclaration extends FunctionNode {

initialise(): void {
super.initialise();
if (this.id !== null) {
if (this.id != null) {
this.id.variable.isId = true;
}
}

parseNode(esTreeNode: GenericEsTreeNode): void {
if (esTreeNode.id !== null) {
if (esTreeNode.id != null) {
this.id = new Identifier(
esTreeNode.id,
this,
Expand Down
18 changes: 7 additions & 11 deletions src/ast/nodes/Identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,17 @@ const tdzVariableKinds = {
export default class Identifier extends NodeBase implements PatternNode {
declare name: string;
declare type: NodeType.tIdentifier;
variable: Variable | null = null;
variable?: Variable;

private get isTDZAccess(): boolean | null {
if (!isFlagSet(this.flags, Flag.tdzAccessDefined)) {
return null;
}
return isFlagSet(this.flags, Flag.tdzAccess);
}
private set isTDZAccess(value: boolean | null) {
if (value === null) {
setFlag(this.flags, Flag.tdzAccessDefined, false);
} else {
setFlag(this.flags, Flag.tdzAccessDefined, true);
setFlag(this.flags, Flag.tdzAccess, value);
}
private set isTDZAccess(value: boolean) {
setFlag(this.flags, Flag.tdzAccessDefined, true);
setFlag(this.flags, Flag.tdzAccess, value);
}

addExportedVariables(
Expand Down Expand Up @@ -179,7 +175,7 @@ export default class Identifier extends NodeBase implements PatternNode {
switch (interaction.type) {
case INTERACTION_ACCESSED: {
return (
this.variable !== null &&
this.variable !== undefined &&
!this.isPureFunction(path) &&
this.getVariableRespectingTDZ()!.hasEffectsOnInteractionAtPath(path, interaction, context)
);
Expand All @@ -202,7 +198,7 @@ export default class Identifier extends NodeBase implements PatternNode {
if (!this.deoptimized) this.applyDeoptimizations();
if (!this.included) {
this.included = true;
if (this.variable !== null) {
if (this.variable !== undefined) {
this.context.includeVariableInModule(this.variable);
}
}
Expand Down Expand Up @@ -301,7 +297,7 @@ export default class Identifier extends NodeBase implements PatternNode {
);
}

private getVariableRespectingTDZ(): ExpressionEntity | null {
private getVariableRespectingTDZ(): ExpressionEntity | undefined {
if (this.isPossibleTDZ()) {
return UNKNOWN_EXPRESSION;
}
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/IfStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
const unset = Symbol('unset');

export default class IfStatement extends StatementBase implements DeoptimizableEntity {
declare alternate: StatementNode | null;
declare alternate?: StatementNode;
declare consequent: StatementNode;
declare test: ExpressionNode;
declare type: NodeType.tIfStatement;
Expand All @@ -43,7 +43,7 @@ export default class IfStatement extends StatementBase implements DeoptimizableE
// eslint-disable-next-line unicorn/consistent-destructuring
const consequentBrokenFlow = context.brokenFlow;
context.brokenFlow = brokenFlow;
if (this.alternate === null) return false;
if (this.alternate === undefined) return false;
if (this.alternate.hasEffects(context)) return true;
// eslint-disable-next-line unicorn/consistent-destructuring
context.brokenFlow = context.brokenFlow && consequentBrokenFlow;
Expand Down
32 changes: 16 additions & 16 deletions src/ast/nodes/ImportExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ interface DynamicImportMechanism {
// default, keys.ts should be updated
export default class ImportExpression extends NodeBase {
declare arguments: ObjectExpression[] | undefined;
inlineNamespace: NamespaceVariable | null = null;
inlineNamespace?: NamespaceVariable;
declare source: ExpressionNode;
declare type: NodeType.tImportExpression;

private assertions: string | null | true = null;
private assertions?: string | true;
private mechanism: DynamicImportMechanism | null = null;
private namespaceExportName: string | false | undefined = undefined;
private resolution: Module | ExternalModule | string | null = null;
private resolutionString: string | null = null;
private namespaceExportName?: string | false;
private resolution?: Module | ExternalModule | string;
private resolutionString?: string;

// Do not bind assertions
bind(): void {
Expand Down Expand Up @@ -226,30 +226,30 @@ export default class ImportExpression extends NodeBase {

setExternalResolution(
exportMode: 'none' | 'named' | 'default' | 'external',
resolution: Module | ExternalModule | string | null,
resolution: Module | ExternalModule | string | null | undefined,
options: NormalizedOutputOptions,
snippets: GenerateCodeSnippets,
pluginDriver: PluginDriver,
accessedGlobalsByScope: Map<ChildScope, Set<string>>,
resolutionString: string,
namespaceExportName: string | false | undefined,
assertions: string | null | true
namespaceExportName: string | false | null | undefined,
assertions: string | null | undefined | true
): void {
const { format } = options;
this.inlineNamespace = null;
this.resolution = resolution;
this.resolution = resolution ?? undefined;
this.resolutionString = resolutionString;
this.namespaceExportName = namespaceExportName;
this.assertions = assertions;
this.namespaceExportName = namespaceExportName ?? undefined;
this.assertions = assertions ?? undefined;
const accessedGlobals = [...(accessedImportGlobals[format] || [])];
let helper: string | null;
let helper: string | null = null;
({ helper, mechanism: this.mechanism } = this.getDynamicImportMechanismAndHelper(
resolution,
resolution ?? undefined,
exportMode,
options,
snippets,
pluginDriver
));

if (helper) {
accessedGlobals.push(helper);
}
Expand All @@ -265,7 +265,7 @@ export default class ImportExpression extends NodeBase {
protected applyDeoptimizations() {}

private getDynamicImportMechanismAndHelper(
resolution: Module | ExternalModule | string | null,
resolution: Module | ExternalModule | string | undefined,
exportMode: 'none' | 'named' | 'default' | 'external',
{
compact,
Expand Down Expand Up @@ -382,7 +382,7 @@ export default class ImportExpression extends NodeBase {
}

function getInteropHelper(
resolution: Module | ExternalModule | string | null,
resolution: Module | ExternalModule | string | undefined,
exportMode: 'none' | 'named' | 'default' | 'external',
interop: GetInterop
): string | null {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/Literal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default class Literal<T extends LiteralValue = LiteralValue> extends Node
if (
path.length > 0 ||
// unknown literals can also be null but do not start with an "n"
(this.value === null && this.context.code.charCodeAt(this.start) !== 110) ||
(this.value == null && this.context.code.charCodeAt(this.start) !== 110) ||
typeof this.value === 'bigint' ||
// to support shims for regular expressions
this.context.code.charCodeAt(this.start) === 47
Expand All @@ -64,7 +64,7 @@ export default class Literal<T extends LiteralValue = LiteralValue> extends Node
): boolean {
switch (interaction.type) {
case INTERACTION_ACCESSED: {
return path.length > (this.value === null ? 0 : 1);
return path.length > (this.value == null ? 0 : 1);
}
case INTERACTION_ASSIGNED: {
return true;
Expand Down

0 comments on commit dbac69b

Please sign in to comment.