Skip to content

Commit

Permalink
Use flags over labels for simplicity and performance
Browse files Browse the repository at this point in the history
  • Loading branch information
lukastaegert committed Apr 16, 2023
1 parent bb923fc commit ca9d6b3
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 83 deletions.
18 changes: 12 additions & 6 deletions src/ast/ExecutionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import type { ExpressionEntity } from './nodes/shared/Expression';
import { DiscriminatedPathTracker, PathTracker } from './utils/PathTracker';
import type ThisVariable from './variables/ThisVariable';

export const UnlabeledBreak = Symbol('Unlabeled Break');
export const UnlabeledContinue = Symbol('Unlabeled Continue');
export type Label = string | typeof UnlabeledBreak | typeof UnlabeledContinue;

interface ExecutionContextIgnore {
labels: Set<Label>;
breaks: boolean;
continues: boolean;
labels: Set<string>;
returnYield: boolean;
this: boolean;
}

interface ControlFlowContext {
brokenFlow: boolean;
includedLabels: Set<Label>;
hasBreak: boolean;
hasContinue: boolean;
includedLabels: Set<string>;
}

export interface InclusionContext extends ControlFlowContext {
Expand All @@ -35,6 +35,8 @@ export interface HasEffectsContext extends ControlFlowContext {
export function createInclusionContext(): InclusionContext {
return {
brokenFlow: false,
hasBreak: false,
hasContinue: false,
includedCallArguments: new Set(),
includedLabels: new Set()
};
Expand All @@ -46,7 +48,11 @@ export function createHasEffectsContext(): HasEffectsContext {
assigned: new PathTracker(),
brokenFlow: false,
called: new DiscriminatedPathTracker(),
hasBreak: false,
hasContinue: false,
ignore: {
breaks: false,
continues: false,
labels: new Set(),
returnYield: false,
this: false
Expand Down
2 changes: 2 additions & 0 deletions src/ast/nodes/ArrowFunctionExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export default class ArrowFunctionExpression extends FunctionBase {
if (interaction.type === INTERACTION_CALLED) {
const { ignore, brokenFlow } = context;
context.ignore = {
breaks: false,
continues: false,
labels: new Set(),
returnYield: true,
this: false
Expand Down
14 changes: 9 additions & 5 deletions src/ast/nodes/BreakStatement.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type HasEffectsContext, type InclusionContext, UnlabeledBreak } from '../ExecutionContext';
import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext';
import type Identifier from './Identifier';
import type * as NodeType from './NodeType';
import { StatementBase } from './shared/Node';
Expand All @@ -8,9 +8,13 @@ export default class BreakStatement extends StatementBase {
declare type: NodeType.tBreakStatement;

hasEffects(context: HasEffectsContext): boolean {
const labelName = this.label?.name || UnlabeledBreak;
if (!context.ignore.labels.has(labelName)) return true;
context.includedLabels.add(labelName);
if (this.label) {
if (!context.ignore.labels.has(this.label.name)) return true;
context.includedLabels.add(this.label.name);
} else {
if (!context.ignore.breaks) return true;
context.hasBreak = true;
}
context.brokenFlow = true;
return false;
}
Expand All @@ -21,7 +25,7 @@ export default class BreakStatement extends StatementBase {
this.label.include();
context.includedLabels.add(this.label.name);
} else {
context.includedLabels.add(UnlabeledBreak);
context.hasBreak = true;
}
context.brokenFlow = true;
}
Expand Down
18 changes: 9 additions & 9 deletions src/ast/nodes/ContinueStatement.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
type HasEffectsContext,
type InclusionContext,
UnlabeledContinue
} from '../ExecutionContext';
import { type HasEffectsContext, type InclusionContext } from '../ExecutionContext';
import type Identifier from './Identifier';
import type * as NodeType from './NodeType';
import { StatementBase } from './shared/Node';
Expand All @@ -12,9 +8,13 @@ export default class ContinueStatement extends StatementBase {
declare type: NodeType.tContinueStatement;

hasEffects(context: HasEffectsContext): boolean {
const labelName = this.label?.name || UnlabeledContinue;
if (!context.ignore.labels.has(labelName)) return true;
context.includedLabels.add(labelName);
if (this.label) {
if (!context.ignore.labels.has(this.label.name)) return true;
context.includedLabels.add(this.label.name);
} else {
if (!context.ignore.continues) return true;
context.hasContinue = true;
}
context.brokenFlow = true;
return false;
}
Expand All @@ -25,7 +25,7 @@ export default class ContinueStatement extends StatementBase {
this.label.include();
context.includedLabels.add(this.label.name);
} else {
context.includedLabels.add(UnlabeledContinue);
context.hasContinue = true;
}
context.brokenFlow = true;
}
Expand Down
39 changes: 15 additions & 24 deletions src/ast/nodes/SwitchStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { type RenderOptions, renderStatementList } from '../../utils/renderHelpe
import {
createHasEffectsContext,
type HasEffectsContext,
type InclusionContext,
UnlabeledBreak
type InclusionContext
} from '../ExecutionContext';
import BlockScope from '../scopes/BlockScope';
import type Scope from '../scopes/Scope';
Expand All @@ -25,37 +24,31 @@ export default class SwitchStatement extends StatementBase {

hasEffects(context: HasEffectsContext): boolean {
if (this.discriminant.hasEffects(context)) return true;
const {
brokenFlow,
includedLabels,
ignore: { labels }
} = context;
const ignoreBreaks = labels.has(UnlabeledBreak);
labels.add(UnlabeledBreak);
const hasBreak = includedLabels.delete(UnlabeledBreak);
const { brokenFlow, hasBreak, ignore } = context;
const { breaks } = ignore;
ignore.breaks = true;
context.hasBreak = false;
let onlyHasBrokenFlow = true;
for (const switchCase of this.cases) {
if (switchCase.hasEffects(context)) return true;
// eslint-disable-next-line unicorn/consistent-destructuring
onlyHasBrokenFlow &&= context.brokenFlow && !includedLabels.has(UnlabeledBreak);
includedLabels.delete(UnlabeledBreak);
onlyHasBrokenFlow &&= context.brokenFlow && !context.hasBreak;
context.hasBreak = false;
context.brokenFlow = brokenFlow;
}
if (this.defaultCase !== null) {
context.brokenFlow = onlyHasBrokenFlow;
}
if (hasBreak) {
includedLabels.add(UnlabeledBreak);
}
if (!ignoreBreaks) labels.delete(UnlabeledBreak);
ignore.breaks = breaks;
context.hasBreak = hasBreak;
return false;
}

include(context: InclusionContext, includeChildrenRecursively: IncludeChildren): void {
this.included = true;
this.discriminant.include(context, includeChildrenRecursively);
const { brokenFlow, includedLabels } = context;
const hasBreak = includedLabels.delete(UnlabeledBreak);
const { brokenFlow, hasBreak } = context;
context.hasBreak = false;
let onlyHasBrokenFlow = true;
let isCaseIncluded =
includeChildrenRecursively ||
Expand All @@ -67,14 +60,14 @@ export default class SwitchStatement extends StatementBase {
}
if (!isCaseIncluded) {
const hasEffectsContext = createHasEffectsContext();
hasEffectsContext.ignore.labels.add(UnlabeledBreak);
hasEffectsContext.ignore.breaks = true;
isCaseIncluded = switchCase.hasEffects(hasEffectsContext);
}
if (isCaseIncluded) {
switchCase.include(context, includeChildrenRecursively);
// eslint-disable-next-line unicorn/consistent-destructuring
onlyHasBrokenFlow &&= context.brokenFlow && !includedLabels.has(UnlabeledBreak);
includedLabels.delete(UnlabeledBreak);
onlyHasBrokenFlow &&= context.brokenFlow && !context.hasBreak;
context.hasBreak = false;
context.brokenFlow = brokenFlow;
} else {
onlyHasBrokenFlow = brokenFlow;
Expand All @@ -83,9 +76,7 @@ export default class SwitchStatement extends StatementBase {
if (isCaseIncluded && this.defaultCase !== null) {
context.brokenFlow = onlyHasBrokenFlow;
}
if (hasBreak) {
includedLabels.add(UnlabeledBreak);
}
context.hasBreak = hasBreak;
}

initialise(): void {
Expand Down
4 changes: 2 additions & 2 deletions src/ast/nodes/TryStatement.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { NormalizedTreeshakingOptions } from '../../rollup/types';
import type { HasEffectsContext, InclusionContext, Label } from '../ExecutionContext';
import type { HasEffectsContext, InclusionContext } from '../ExecutionContext';
import type BlockStatement from './BlockStatement';
import type CatchClause from './CatchClause';
import type * as NodeType from './NodeType';
Expand All @@ -12,7 +12,7 @@ export default class TryStatement extends StatementBase {
declare type: NodeType.tTryStatement;

private directlyIncluded = false;
private includedLabelsAfterBlock: Label[] | null = null;
private includedLabelsAfterBlock: string[] | null = null;

hasEffects(context: HasEffectsContext): boolean {
return (
Expand Down
2 changes: 2 additions & 0 deletions src/ast/nodes/shared/FunctionNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ export default class FunctionNode extends FunctionBase {
);
const { brokenFlow, ignore, replacedVariableInits } = context;
context.ignore = {
breaks: false,
continues: false,
labels: new Set(),
returnYield: true,
this: interaction.withNew
Expand Down
52 changes: 15 additions & 37 deletions src/ast/nodes/shared/loops.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,18 @@
import type { HasEffectsContext, InclusionContext } from '../../ExecutionContext';
import { UnlabeledBreak, UnlabeledContinue } from '../../ExecutionContext';
import type { StatementNode } from './Node';

export function hasLoopBodyEffects(context: HasEffectsContext, body: StatementNode): boolean {
const {
brokenFlow,
includedLabels,
ignore: { labels }
} = context;
const hasBreak = includedLabels.delete(UnlabeledBreak);
const ignoreBreaks = labels.has(UnlabeledBreak);
labels.add(UnlabeledBreak);
const hasContinue = includedLabels.delete(UnlabeledContinue);
const ignoreContinues = labels.has(UnlabeledContinue);
labels.add(UnlabeledContinue);
const { brokenFlow, hasBreak, hasContinue, ignore } = context;
const { breaks, continues } = ignore;
ignore.breaks = true;
ignore.continues = true;
context.hasBreak = false;
context.hasContinue = false;
if (body.hasEffects(context)) return true;
if (!ignoreBreaks) labels.delete(UnlabeledBreak);
if (!ignoreContinues) labels.delete(UnlabeledContinue);
if (hasBreak) {
includedLabels.add(UnlabeledBreak);
} else {
includedLabels.delete(UnlabeledBreak);
}
if (hasContinue) {
includedLabels.add(UnlabeledContinue);
} else {
includedLabels.delete(UnlabeledContinue);
}
ignore.breaks = breaks;
ignore.continues = continues;
context.hasBreak = hasBreak;
context.hasContinue = hasContinue;
context.brokenFlow = brokenFlow;
return false;
}
Expand All @@ -36,19 +22,11 @@ export function includeLoopBody(
body: StatementNode,
includeChildrenRecursively: boolean | 'variables'
) {
const { brokenFlow, includedLabels } = context;
const hasBreak = includedLabels.delete(UnlabeledBreak);
const hasContinue = includedLabels.delete(UnlabeledContinue);
const { brokenFlow, hasBreak, hasContinue } = context;
context.hasBreak = false;
context.hasContinue = false;
body.include(context, includeChildrenRecursively, { asSingleStatement: true });
if (hasBreak) {
includedLabels.add(UnlabeledBreak);
} else {
includedLabels.delete(UnlabeledBreak);
}
if (hasContinue) {
includedLabels.add(UnlabeledContinue);
} else {
includedLabels.delete(UnlabeledContinue);
}
context.hasBreak = hasBreak;
context.hasContinue = hasContinue;
context.brokenFlow = brokenFlow;
}

0 comments on commit ca9d6b3

Please sign in to comment.