Skip to content

Commit

Permalink
perf: pack booleans in bitfield
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
thebanjomatic committed Oct 10, 2023
1 parent f52a2eb commit a9d0d38
Show file tree
Hide file tree
Showing 31 changed files with 292 additions and 82 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];
}
1 change: 0 additions & 1 deletion 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 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, parentScope.context);
}

hasEffects(context: HasEffectsContext): boolean {
Expand Down
9 changes: 8 additions & 1 deletion 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,9 +25,15 @@ 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) {
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
6 changes: 3 additions & 3 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, parentScope.context);
}

hasEffects(context: HasEffectsContext): boolean {
Expand Down
15 changes: 11 additions & 4 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, parentScope.context);
}

hasEffects(): boolean {
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 @@ -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, parentScope.context);
}

hasEffects(context: HasEffectsContext): boolean {
Expand Down
16 changes: 14 additions & 2 deletions src/ast/nodes/Identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import LocalVariable from '../variables/LocalVariable';
import type Variable from '../variables/Variable';
import * as NodeType from './NodeType';
import type SpreadElement from './SpreadElement';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import {
type ExpressionEntity,
type LiteralValueOrUnknown,
Expand All @@ -42,7 +43,17 @@ export default class Identifier extends NodeBase implements PatternNode {
declare name: string;
declare type: NodeType.tIdentifier;
variable: Variable | null = null;
private isTDZAccess: boolean | null = null;

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

addExportedVariables(
variables: Variable[],
Expand Down Expand Up @@ -198,7 +209,8 @@ export default class Identifier extends NodeBase implements PatternNode {

isPossibleTDZ(): boolean {
// return cached value to avoid issues with the next tree-shaking pass
if (this.isTDZAccess !== null) return this.isTDZAccess;
const cachedTdzAccess = this.isTDZAccess;
if (cachedTdzAccess !== null) return cachedTdzAccess;

if (
!(
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 @@ -67,14 +67,14 @@ export default class IfStatement extends StatementBase implements DeoptimizableE
}

parseNode(esTreeNode: GenericEsTreeNode): void {
this.consequentScope = new TrackingScope(this.scope);
this.consequentScope = new TrackingScope(this.scope, this.context);
this.consequent = new (this.context.getNodeConstructor(esTreeNode.consequent.type))(
esTreeNode.consequent,
this,
this.consequentScope
);
if (esTreeNode.alternate) {
this.alternateScope = new TrackingScope(this.scope);
this.alternateScope = new TrackingScope(this.scope, this.context);
this.alternate = new (this.context.getNodeConstructor(esTreeNode.alternate.type))(
esTreeNode.alternate,
this,
Expand Down
10 changes: 9 additions & 1 deletion src/ast/nodes/LogicalExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
UNKNOWN_PATH
} from '../utils/PathTracker';
import type * as NodeType from './NodeType';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import {
type ExpressionEntity,
type LiteralValueOrUnknown,
Expand All @@ -34,9 +35,16 @@ export default class LogicalExpression extends NodeBase implements Deoptimizable
declare right: ExpressionNode;
declare type: NodeType.tLogicalExpression;

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

// We collect deoptimization information if usedBranch !== null
private expressionsToBeDeoptimized: DeoptimizableEntity[] = [];
private isBranchResolutionAnalysed = false;
private usedBranch: ExpressionNode | null = null;

deoptimizeArgumentsOnInteractionAtPath(
Expand Down
41 changes: 36 additions & 5 deletions src/ast/nodes/MemberExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type * as NodeType from './NodeType';
import type PrivateIdentifier from './PrivateIdentifier';
import type SpreadElement from './SpreadElement';
import type Super from './Super';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import {
deoptimizeInteraction,
type ExpressionEntity,
Expand Down Expand Up @@ -95,19 +96,49 @@ export default class MemberExpression
extends NodeBase
implements DeoptimizableEntity, ChainElement
{
declare computed: boolean;
declare object: ExpressionNode | Super;
declare optional: boolean;
declare property: ExpressionNode | PrivateIdentifier;
declare propertyKey: ObjectPathKey | null;
declare type: NodeType.tMemberExpression;
variable: Variable | null = null;
protected declare assignmentInteraction: NodeInteractionAssigned;
private declare accessInteraction: NodeInteractionAccessed;
private assignmentDeoptimized = false;
private bound = false;
private expressionsToBeDeoptimized: DeoptimizableEntity[] = [];
private isUndefined = false;

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

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

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

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

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

bind(): void {
this.bound = true;
Expand Down
19 changes: 17 additions & 2 deletions src/ast/nodes/Property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { HasEffectsContext } from '../ExecutionContext';
import { UnknownKey } from '../utils/PathTracker';
import type LocalVariable from '../variables/LocalVariable';
import type * as NodeType from './NodeType';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import { type ExpressionEntity, UNKNOWN_EXPRESSION } from './shared/Expression';
import MethodBase from './shared/MethodBase';
import type { ExpressionNode } from './shared/Node';
Expand All @@ -13,11 +14,25 @@ import type { PatternNode } from './shared/Pattern';
export default class Property extends MethodBase implements PatternNode {
declare key: ExpressionNode;
declare kind: 'init' | 'get' | 'set';
declare method: boolean;
declare shorthand: boolean;
declare type: NodeType.tProperty;
private declarationInit: ExpressionEntity | null = null;

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

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

declare(kind: string, init: ExpressionEntity): LocalVariable[] {
this.declarationInit = init;
return (this.value as PatternNode).declare(kind, UNKNOWN_EXPRESSION);
Expand Down
9 changes: 8 additions & 1 deletion src/ast/nodes/PropertyDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { NodeInteraction, NodeInteractionCalled } from '../NodeInteractions
import type { ObjectPath, PathTracker } from '../utils/PathTracker';
import type * as NodeType from './NodeType';
import type PrivateIdentifier from './PrivateIdentifier';
import { Flag, isFlagSet, setFlag } from './shared/BitFlags';
import {
type ExpressionEntity,
type LiteralValueOrUnknown,
Expand All @@ -13,12 +14,18 @@ import {
import { type ExpressionNode, NodeBase } from './shared/Node';

export default class PropertyDefinition extends NodeBase {
declare computed: boolean;
declare key: ExpressionNode | PrivateIdentifier;
declare static: boolean;
declare type: NodeType.tPropertyDefinition;
declare value: ExpressionNode | null;

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

deoptimizeArgumentsOnInteractionAtPath(
interaction: NodeInteraction,
path: ObjectPath,
Expand Down

0 comments on commit a9d0d38

Please sign in to comment.