Compare commits
13 Commits
pr33026
...
gh/mvitous
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adf93657ac | ||
|
|
26dce8c6c1 | ||
|
|
7347f50ea6 | ||
|
|
5013f9bf45 | ||
|
|
6541dd860d | ||
|
|
7f9b7dac5c | ||
|
|
82daf0e397 | ||
|
|
be0a027cbf | ||
|
|
3154d4fd5f | ||
|
|
ba8fb01ad0 | ||
|
|
10d8b63bac | ||
|
|
0836ca3740 | ||
|
|
6a77fd2ff7 |
@@ -101,6 +101,7 @@ import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes';
|
||||
import {lowerContextAccess} from '../Optimization/LowerContextAccess';
|
||||
import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects';
|
||||
import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement';
|
||||
import {inferFunctionEffects} from '../Inference/InferFunctionEffects';
|
||||
import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
@@ -210,9 +211,12 @@ function* runWithEnvironment(
|
||||
analyseFunctions(hir);
|
||||
yield log({kind: 'hir', name: 'AnalyseFunctions', value: hir});
|
||||
|
||||
inferReferenceEffects(hir);
|
||||
const referenceAliases = inferReferenceEffects(hir);
|
||||
yield log({kind: 'hir', name: 'InferReferenceEffects', value: hir});
|
||||
|
||||
inferFunctionEffects(hir, referenceAliases);
|
||||
yield log({kind: 'hir', name: 'InferFunctionEffects', value: hir});
|
||||
|
||||
validateLocalsNotReassignedAfterRender(hir);
|
||||
|
||||
// Note: Has to come after infer reference effects because "dead" code may still affect inference
|
||||
|
||||
@@ -82,6 +82,7 @@ export function lower(
|
||||
kind: 'Identifier',
|
||||
identifier: builder.resolveBinding(ref),
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc: ref.loc ?? GeneratedSource,
|
||||
});
|
||||
@@ -113,6 +114,7 @@ export function lower(
|
||||
kind: 'Identifier',
|
||||
identifier: binding.identifier,
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc: param.node.loc ?? GeneratedSource,
|
||||
};
|
||||
@@ -126,6 +128,7 @@ export function lower(
|
||||
kind: 'Identifier',
|
||||
identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource),
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc: param.node.loc ?? GeneratedSource,
|
||||
};
|
||||
@@ -144,6 +147,7 @@ export function lower(
|
||||
kind: 'Identifier',
|
||||
identifier: builder.makeTemporary(param.node.loc ?? GeneratedSource),
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc: param.node.loc ?? GeneratedSource,
|
||||
};
|
||||
@@ -460,6 +464,7 @@ function lowerStatement(
|
||||
});
|
||||
const place: Place = {
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
identifier: identifier.identifier,
|
||||
kind: 'Identifier',
|
||||
reactive: false,
|
||||
@@ -853,6 +858,7 @@ function lowerStatement(
|
||||
} else {
|
||||
const place: Place = {
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
identifier: binding.identifier,
|
||||
kind: 'Identifier',
|
||||
reactive: false,
|
||||
@@ -1264,6 +1270,7 @@ function lowerStatement(
|
||||
handlerBindingPath.node.loc ?? GeneratedSource,
|
||||
),
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc: handlerBindingPath.node.loc ?? GeneratedSource,
|
||||
};
|
||||
@@ -3428,6 +3435,7 @@ function lowerIdentifier(
|
||||
kind: 'Identifier',
|
||||
identifier: binding.identifier,
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc: exprLoc,
|
||||
};
|
||||
@@ -3449,6 +3457,7 @@ function buildTemporaryPlace(builder: HIRBuilder, loc: SourceLocation): Place {
|
||||
kind: 'Identifier',
|
||||
identifier: builder.makeTemporary(loc),
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc,
|
||||
};
|
||||
@@ -3511,6 +3520,7 @@ function lowerIdentifierForAssignment(
|
||||
kind: 'Identifier',
|
||||
identifier: binding.identifier,
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc,
|
||||
};
|
||||
|
||||
@@ -761,6 +761,7 @@ function _staticInvariantInstructionValueHasLocation(
|
||||
export type Phi = {
|
||||
kind: 'Phi';
|
||||
id: Identifier;
|
||||
abstractValue: AbstractValue | null;
|
||||
operands: Map<BlockId, Identifier>;
|
||||
};
|
||||
|
||||
@@ -1110,6 +1111,7 @@ export type Place = {
|
||||
kind: 'Identifier';
|
||||
identifier: Identifier;
|
||||
effect: Effect;
|
||||
abstractValue: AbstractValue | null;
|
||||
reactive: boolean;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
@@ -895,6 +895,7 @@ export function createTemporaryPlace(
|
||||
kind: 'Identifier',
|
||||
identifier: makeTemporaryIdentifier(env.nextIdentifierId, loc),
|
||||
reactive: false,
|
||||
abstractValue: null,
|
||||
effect: Effect.Unknown,
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
|
||||
@@ -86,6 +86,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
kind: 'Identifier',
|
||||
identifier: phi.id,
|
||||
effect: Effect.ConditionallyMutate,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
@@ -95,6 +96,7 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void {
|
||||
kind: 'Identifier',
|
||||
identifier: operand,
|
||||
effect: Effect.Read,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
|
||||
@@ -833,6 +833,8 @@ export function printPattern(pattern: Pattern | Place | SpreadPattern): string {
|
||||
|
||||
export function printPlace(place: Place): string {
|
||||
const items = [
|
||||
place.abstractValue?.kind,
|
||||
place.abstractValue ? ' ' : '',
|
||||
place.effect,
|
||||
' ',
|
||||
printIdentifier(place.identifier),
|
||||
|
||||
@@ -20,6 +20,7 @@ import {deadCodeElimination} from '../Optimization';
|
||||
import {inferReactiveScopeVariables} from '../ReactiveScopes';
|
||||
import {rewriteInstructionKindsBasedOnReassignment} from '../SSA';
|
||||
import {logHIRFunction} from '../Utils/logger';
|
||||
import {inferFunctionEffects} from './InferFunctionEffects';
|
||||
import {inferMutableContextVariables} from './InferMutableContextVariables';
|
||||
import {inferMutableRanges} from './InferMutableRanges';
|
||||
import inferReferenceEffects from './InferReferenceEffects';
|
||||
@@ -106,7 +107,8 @@ export default function analyseFunctions(func: HIRFunction): void {
|
||||
|
||||
function lower(func: HIRFunction): void {
|
||||
analyseFunctions(func);
|
||||
inferReferenceEffects(func, {isFunctionExpression: true});
|
||||
const aliases = inferReferenceEffects(func, {isFunctionExpression: true});
|
||||
inferFunctionEffects(func, aliases, {isFunctionExpression: true});
|
||||
deadCodeElimination(func);
|
||||
inferMutableRanges(func);
|
||||
rewriteInstructionKindsBasedOnReassignment(func);
|
||||
|
||||
@@ -268,6 +268,7 @@ function getManualMemoizationReplacement(
|
||||
kind: 'Identifier',
|
||||
identifier: fn.identifier,
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc,
|
||||
},
|
||||
@@ -420,6 +421,7 @@ export function dropManualMemoization(func: HIRFunction): void {
|
||||
kind: 'Identifier',
|
||||
identifier: fnPlace.identifier,
|
||||
effect: Effect.Unknown,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
loc: fnPlace.loc,
|
||||
};
|
||||
|
||||
@@ -9,29 +9,178 @@ import {CompilerError, ErrorSeverity, ValueKind} from '..';
|
||||
import {
|
||||
AbstractValue,
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
Effect,
|
||||
Environment,
|
||||
FunctionEffect,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
InstructionValue,
|
||||
Place,
|
||||
ValueReason,
|
||||
getHookKind,
|
||||
isRefOrRefValue,
|
||||
} from '../HIR';
|
||||
import {eachInstructionOperand, eachTerminalOperand} from '../HIR/visitors';
|
||||
import {
|
||||
eachInstructionLValue,
|
||||
eachInstructionOperand,
|
||||
eachTerminalOperand,
|
||||
} from '../HIR/visitors';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
|
||||
interface State {
|
||||
kind(place: Place): AbstractValue;
|
||||
values(place: Place): Array<InstructionValue>;
|
||||
isDefined(place: Place): boolean;
|
||||
type State = {
|
||||
values: Map<IdentifierId, AbstractValue>;
|
||||
nestedEffects: Map<IdentifierId, Set<FunctionEffect>>;
|
||||
aliases: DisjointSet<IdentifierId>;
|
||||
};
|
||||
|
||||
export function inferFunctionEffects(
|
||||
fn: HIRFunction,
|
||||
aliases: DisjointSet<IdentifierId>,
|
||||
options: {isFunctionExpression: boolean} = {isFunctionExpression: false},
|
||||
): void {
|
||||
const state: State = {values: new Map(), nestedEffects: new Map(), aliases};
|
||||
|
||||
for (const param of fn.params) {
|
||||
let place;
|
||||
if (param.kind === 'Identifier') {
|
||||
place = param;
|
||||
} else {
|
||||
place = param.place;
|
||||
}
|
||||
CompilerError.invariant(place.abstractValue != null, {
|
||||
reason: 'Expected lvalue to have a kind',
|
||||
loc: place.loc,
|
||||
});
|
||||
state.values.set(place.identifier.id, place.abstractValue);
|
||||
}
|
||||
|
||||
// Build an environment mapping identifiers to AbstractValues
|
||||
for (const cx of fn.context) {
|
||||
CompilerError.invariant(cx.abstractValue != null, {
|
||||
reason: 'Expected context to have a kind',
|
||||
loc: cx.loc,
|
||||
});
|
||||
state.values.set(cx.identifier.id, cx.abstractValue);
|
||||
}
|
||||
let blocksSeen: Set<BlockId> = new Set();
|
||||
let backEdgeSeen = false;
|
||||
let lastNested: Map<IdentifierId, Set<FunctionEffect>>;
|
||||
|
||||
do {
|
||||
lastNested = new Map(state.nestedEffects);
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
blocksSeen.add(blockId);
|
||||
for (const phi of block.phis) {
|
||||
CompilerError.invariant(phi.abstractValue != null, {
|
||||
reason: 'Expected phi to have a kind',
|
||||
loc: phi.id.loc,
|
||||
});
|
||||
const phiRoot = state.aliases.find(phi.id.id) ?? phi.id.id;
|
||||
for (const [predId, operand] of phi.operands) {
|
||||
if (!blocksSeen.has(predId)) {
|
||||
backEdgeSeen = true;
|
||||
}
|
||||
const operandRoot = state.aliases.find(operand.id) ?? operand.id;
|
||||
if (state.nestedEffects.has(operandRoot)) {
|
||||
state.nestedEffects.set(
|
||||
phiRoot,
|
||||
state.nestedEffects.get(operandRoot) ?? new Set(),
|
||||
);
|
||||
}
|
||||
}
|
||||
state.values.set(phi.id.id, phi.abstractValue);
|
||||
}
|
||||
for (const instr of block.instructions) {
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' ||
|
||||
instr.value.kind === 'ObjectMethod'
|
||||
) {
|
||||
/**
|
||||
* If this function references other functions, propagate the referenced function's
|
||||
* effects to this function.
|
||||
*
|
||||
* ```
|
||||
* let f = () => global = true;
|
||||
* let g = () => f();
|
||||
* g();
|
||||
* ```
|
||||
*
|
||||
* In this example, because `g` references `f`, we propagate the GlobalMutation from
|
||||
* `f` to `g`. Thus, referencing `g` in `g()` will evaluate the GlobalMutation in the outer
|
||||
* function effect context and report an error. But if instead we do:
|
||||
*
|
||||
* ```
|
||||
* let f = () => global = true;
|
||||
* let g = () => f();
|
||||
* useEffect(() => g(), [g])
|
||||
* ```
|
||||
*
|
||||
* Now `g`'s effects will be discarded since they're in a useEffect.
|
||||
*/
|
||||
let propagatedEffects = [
|
||||
...(instr.value.loweredFunc.func.effects ?? []),
|
||||
];
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
propagatedEffects.push(
|
||||
...inferFunctionInstrEffects(state, operand),
|
||||
);
|
||||
}
|
||||
/*
|
||||
* Since we infer the effects of nested functions from function
|
||||
* instructions, we need to make sure we can go from identifiers
|
||||
* aliased to function definitions to the function's effects
|
||||
*/
|
||||
const root =
|
||||
state.aliases.find(instr.lvalue.identifier.id) ??
|
||||
instr.lvalue.identifier.id;
|
||||
CompilerError.invariant(root != null, {
|
||||
reason: 'Expected lvalue to have a root',
|
||||
loc: instr.loc,
|
||||
});
|
||||
const curEffects = state.nestedEffects.get(root) ?? new Set();
|
||||
if (propagatedEffects.some(eff => !curEffects.has(eff))) {
|
||||
const nested = new Set([...curEffects, ...propagatedEffects]);
|
||||
state.nestedEffects.set(root, nested);
|
||||
}
|
||||
instr.value.loweredFunc.func.effects = propagatedEffects;
|
||||
}
|
||||
for (const lvalue of eachInstructionLValue(instr)) {
|
||||
lvalue.abstractValue &&
|
||||
state.values.set(lvalue.identifier.id, lvalue.abstractValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Loop until we have propagated all nested effects backwards through backedges, if they exist
|
||||
} while (
|
||||
backEdgeSeen &&
|
||||
![...state.nestedEffects].every(([id, effs]) => lastNested.get(id) === effs)
|
||||
);
|
||||
|
||||
const functionEffects: Array<FunctionEffect> = [];
|
||||
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
for (const instr of block.instructions) {
|
||||
functionEffects.push(
|
||||
...inferInstructionFunctionEffects(fn.env, state, instr),
|
||||
);
|
||||
}
|
||||
functionEffects.push(...inferTerminalFunctionEffects(state, block));
|
||||
}
|
||||
|
||||
if (options.isFunctionExpression) {
|
||||
fn.effects = functionEffects;
|
||||
} else {
|
||||
raiseFunctionEffectErrors(functionEffects);
|
||||
}
|
||||
}
|
||||
|
||||
function inferOperandEffect(state: State, place: Place): null | FunctionEffect {
|
||||
const value = state.kind(place);
|
||||
const value = place.abstractValue;
|
||||
CompilerError.invariant(value != null, {
|
||||
reason: 'Expected operand to have a kind',
|
||||
description: `${place.identifier.id}`,
|
||||
loc: null,
|
||||
});
|
||||
|
||||
@@ -82,7 +231,6 @@ function inheritFunctionEffects(
|
||||
place: Place,
|
||||
): Array<FunctionEffect> {
|
||||
const effects = inferFunctionInstrEffects(state, place);
|
||||
|
||||
return effects
|
||||
.flatMap(effect => {
|
||||
if (effect.kind === 'GlobalMutation' || effect.kind === 'ReactMutation') {
|
||||
@@ -106,11 +254,13 @@ function inheritFunctionEffects(
|
||||
* more detailed effect to the current function context.
|
||||
*/
|
||||
for (const place of effect.places) {
|
||||
if (state.isDefined(place)) {
|
||||
const abstractValue = state.values.get(place.identifier.id);
|
||||
if (abstractValue != null) {
|
||||
const replayedEffect = inferOperandEffect(state, {
|
||||
...place,
|
||||
loc: effect.loc,
|
||||
effect: effect.effect,
|
||||
abstractValue,
|
||||
});
|
||||
if (replayedEffect != null) {
|
||||
if (replayedEffect.kind === 'ContextMutation') {
|
||||
@@ -120,7 +270,8 @@ function inheritFunctionEffects(
|
||||
// Case 3, immutable value so propagate the more precise effect
|
||||
effects.push(replayedEffect);
|
||||
}
|
||||
} // else case 2, local mutable value so this effect was fine
|
||||
}
|
||||
// else case 2, local mutable value so this effect was fine
|
||||
}
|
||||
}
|
||||
return effects;
|
||||
@@ -134,21 +285,13 @@ function inferFunctionInstrEffects(
|
||||
place: Place,
|
||||
): Array<FunctionEffect> {
|
||||
const effects: Array<FunctionEffect> = [];
|
||||
const instrs = state.values(place);
|
||||
CompilerError.invariant(instrs != null, {
|
||||
reason: 'Expected operand to have instructions',
|
||||
loc: null,
|
||||
const root = state.aliases.find(place.identifier.id) ?? place.identifier.id;
|
||||
CompilerError.invariant(root != null, {
|
||||
reason: 'Expected operand to have a root',
|
||||
loc: place.loc,
|
||||
});
|
||||
|
||||
for (const instr of instrs) {
|
||||
if (
|
||||
(instr.kind === 'FunctionExpression' || instr.kind === 'ObjectMethod') &&
|
||||
instr.loweredFunc.func.effects != null
|
||||
) {
|
||||
effects.push(...instr.loweredFunc.func.effects);
|
||||
}
|
||||
}
|
||||
|
||||
const instrs = state.nestedEffects.get(root) ?? [];
|
||||
effects.push(...instrs);
|
||||
return effects;
|
||||
}
|
||||
|
||||
@@ -193,34 +336,6 @@ export function inferInstructionFunctionEffects(
|
||||
}
|
||||
case 'ObjectMethod':
|
||||
case 'FunctionExpression': {
|
||||
/**
|
||||
* If this function references other functions, propagate the referenced function's
|
||||
* effects to this function.
|
||||
*
|
||||
* ```
|
||||
* let f = () => global = true;
|
||||
* let g = () => f();
|
||||
* g();
|
||||
* ```
|
||||
*
|
||||
* In this example, because `g` references `f`, we propagate the GlobalMutation from
|
||||
* `f` to `g`. Thus, referencing `g` in `g()` will evaluate the GlobalMutation in the outer
|
||||
* function effect context and report an error. But if instead we do:
|
||||
*
|
||||
* ```
|
||||
* let f = () => global = true;
|
||||
* let g = () => f();
|
||||
* useEffect(() => g(), [g])
|
||||
* ```
|
||||
*
|
||||
* Now `g`'s effects will be discarded since they're in a useEffect.
|
||||
*/
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
instr.value.loweredFunc.func.effects ??= [];
|
||||
instr.value.loweredFunc.func.effects.push(
|
||||
...inferFunctionInstrEffects(state, operand),
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'MethodCall':
|
||||
@@ -285,7 +400,7 @@ export function inferTerminalFunctionEffects(
|
||||
return functionEffects;
|
||||
}
|
||||
|
||||
export function raiseFunctionEffectErrors(
|
||||
function raiseFunctionEffectErrors(
|
||||
functionEffects: Array<FunctionEffect>,
|
||||
): void {
|
||||
functionEffects.forEach(eff => {
|
||||
|
||||
@@ -13,15 +13,16 @@ import {
|
||||
BlockId,
|
||||
CallExpression,
|
||||
Effect,
|
||||
FunctionEffect,
|
||||
GeneratedSource,
|
||||
HIRFunction,
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
InstructionKind,
|
||||
InstructionValue,
|
||||
MethodCall,
|
||||
Phi,
|
||||
Place,
|
||||
SourceLocation,
|
||||
SpreadPattern,
|
||||
Type,
|
||||
ValueKind,
|
||||
@@ -45,12 +46,8 @@ import {
|
||||
eachTerminalOperand,
|
||||
eachTerminalSuccessor,
|
||||
} from '../HIR/visitors';
|
||||
import DisjointSet from '../Utils/DisjointSet';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {
|
||||
inferTerminalFunctionEffects,
|
||||
inferInstructionFunctionEffects,
|
||||
raiseFunctionEffectErrors,
|
||||
} from './InferFunctionEffects';
|
||||
|
||||
const UndefinedValue: InstructionValue = {
|
||||
kind: 'Primitive',
|
||||
@@ -103,7 +100,7 @@ const UndefinedValue: InstructionValue = {
|
||||
export default function inferReferenceEffects(
|
||||
fn: HIRFunction,
|
||||
options: {isFunctionExpression: boolean} = {isFunctionExpression: false},
|
||||
): void {
|
||||
): DisjointSet<IdentifierId> {
|
||||
/*
|
||||
* Initial state contains function params
|
||||
* TODO: include module declarations here as well
|
||||
@@ -127,12 +124,14 @@ export default function inferReferenceEffects(
|
||||
properties: [],
|
||||
loc: ref.loc,
|
||||
};
|
||||
initialState.initialize(value, {
|
||||
const valueKind: AbstractValue = {
|
||||
kind: ValueKind.Context,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set([ref]),
|
||||
});
|
||||
};
|
||||
initialState.initialize(value, valueKind);
|
||||
initialState.define(ref, value);
|
||||
ref.abstractValue = valueKind;
|
||||
}
|
||||
|
||||
const paramKind: AbstractValue = options.isFunctionExpression
|
||||
@@ -177,12 +176,14 @@ export default function inferReferenceEffects(
|
||||
loc: ref.place.loc,
|
||||
};
|
||||
}
|
||||
initialState.initialize(value, {
|
||||
const valueKind: AbstractValue = {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
});
|
||||
};
|
||||
initialState.initialize(value, valueKind);
|
||||
initialState.define(place, value);
|
||||
place.abstractValue = valueKind;
|
||||
}
|
||||
} else {
|
||||
for (const param of fn.params) {
|
||||
@@ -219,7 +220,7 @@ export default function inferReferenceEffects(
|
||||
}
|
||||
queue(fn.body.entry, initialState);
|
||||
|
||||
const functionEffects: Array<FunctionEffect> = fn.effects ?? [];
|
||||
const finishedStates: Map<BlockId, InferenceState> = new Map();
|
||||
|
||||
while (queuedStates.size !== 0) {
|
||||
for (const [blockId, block] of fn.body.blocks) {
|
||||
@@ -231,19 +232,24 @@ export default function inferReferenceEffects(
|
||||
|
||||
statesByBlock.set(blockId, incomingState);
|
||||
const state = incomingState.clone();
|
||||
inferBlock(fn.env, state, block, functionEffects);
|
||||
finishedStates.set(blockId, state);
|
||||
inferBlock(fn.env, state, block);
|
||||
|
||||
for (const nextBlockId of eachTerminalSuccessor(block.terminal)) {
|
||||
queue(nextBlockId, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
CompilerError.invariant(finishedStates.size > 0, {
|
||||
reason: 'Expected to have processed at least one block',
|
||||
loc: null,
|
||||
});
|
||||
|
||||
if (options.isFunctionExpression) {
|
||||
fn.effects = functionEffects;
|
||||
} else {
|
||||
raiseFunctionEffectErrors(functionEffects);
|
||||
}
|
||||
const summaryState = Array(...finishedStates.values()).reduce(
|
||||
(acc, state) => acc.merge(state) ?? acc,
|
||||
);
|
||||
|
||||
return summaryState.aliases;
|
||||
}
|
||||
|
||||
type FreezeAction = {values: Set<InstructionValue>; reason: Set<ValueReason>};
|
||||
@@ -261,18 +267,26 @@ class InferenceState {
|
||||
*/
|
||||
#variables: Map<IdentifierId, Set<InstructionValue>>;
|
||||
|
||||
#aliases: DisjointSet<IdentifierId>;
|
||||
|
||||
constructor(
|
||||
env: Environment,
|
||||
values: Map<InstructionValue, AbstractValue>,
|
||||
variables: Map<IdentifierId, Set<InstructionValue>>,
|
||||
aliases: DisjointSet<IdentifierId>,
|
||||
) {
|
||||
this.#env = env;
|
||||
this.#values = values;
|
||||
this.#variables = variables;
|
||||
this.#aliases = aliases;
|
||||
}
|
||||
|
||||
get aliases(): DisjointSet<IdentifierId> {
|
||||
return this.#aliases;
|
||||
}
|
||||
|
||||
static empty(env: Environment): InferenceState {
|
||||
return new InferenceState(env, new Map(), new Map());
|
||||
return new InferenceState(env, new Map(), new Map(), new DisjointSet());
|
||||
}
|
||||
|
||||
// (Re)initializes a @param value with its default @param kind.
|
||||
@@ -290,7 +304,7 @@ class InferenceState {
|
||||
values(place: Place): Array<InstructionValue> {
|
||||
const values = this.#variables.get(place.identifier.id);
|
||||
CompilerError.invariant(values != null, {
|
||||
reason: `[hoisting] Expected value kind to be initialized`,
|
||||
reason: `[hoisting] Expected value kind to be initialized in call to values()`,
|
||||
description: `${printPlace(place)}`,
|
||||
loc: place.loc,
|
||||
suggestions: null,
|
||||
@@ -299,11 +313,11 @@ class InferenceState {
|
||||
}
|
||||
|
||||
// Lookup the kind of the given @param value.
|
||||
kind(place: Place): AbstractValue {
|
||||
kind(place: {identifier: Identifier; loc: SourceLocation}): AbstractValue {
|
||||
const values = this.#variables.get(place.identifier.id);
|
||||
CompilerError.invariant(values != null, {
|
||||
reason: `[hoisting] Expected value kind to be initialized`,
|
||||
description: `${printPlace(place)}`,
|
||||
reason: `[hoisting] Expected value kind to be initialized in call to kind()`,
|
||||
description: `${place.identifier.id}`,
|
||||
loc: place.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
@@ -315,7 +329,7 @@ class InferenceState {
|
||||
}
|
||||
CompilerError.invariant(mergedKind !== null, {
|
||||
reason: `InferReferenceEffects::kind: Expected at least one value`,
|
||||
description: `No value found at \`${printPlace(place)}\``,
|
||||
description: `No value found at \`${place.identifier.id}\``,
|
||||
loc: place.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
@@ -332,6 +346,8 @@ class InferenceState {
|
||||
suggestions: null,
|
||||
});
|
||||
this.#variables.set(place.identifier.id, new Set(values));
|
||||
this.#aliases.union([place.identifier.id, value.identifier.id]);
|
||||
place.abstractValue = value.abstractValue;
|
||||
}
|
||||
|
||||
// Defines (initializing or updating) a variable with a specific kind of value.
|
||||
@@ -370,6 +386,7 @@ class InferenceState {
|
||||
reason: ValueReason,
|
||||
): void {
|
||||
const values = this.#variables.get(place.identifier.id);
|
||||
let valueKind: AbstractValue = this.kind(place);
|
||||
if (values === undefined) {
|
||||
CompilerError.invariant(effectKind !== Effect.Store, {
|
||||
reason: '[InferReferenceEffects] Unhandled store reference effect',
|
||||
@@ -381,10 +398,12 @@ class InferenceState {
|
||||
effectKind === Effect.ConditionallyMutate
|
||||
? Effect.ConditionallyMutate
|
||||
: Effect.Read;
|
||||
place.abstractValue = valueKind;
|
||||
return;
|
||||
}
|
||||
|
||||
const action = this.reference(place, effectKind, reason);
|
||||
place.abstractValue = valueKind;
|
||||
action && freezeActions.push(action);
|
||||
}
|
||||
|
||||
@@ -431,7 +450,7 @@ class InferenceState {
|
||||
loc: place.loc,
|
||||
suggestions: null,
|
||||
});
|
||||
let valueKind: AbstractValue | null = this.kind(place);
|
||||
let valueKind: AbstractValue = this.kind(place);
|
||||
let effect: Effect | null = null;
|
||||
let freeze: null | FreezeAction = null;
|
||||
switch (effectKind) {
|
||||
@@ -539,6 +558,7 @@ class InferenceState {
|
||||
merge(other: InferenceState): InferenceState | null {
|
||||
let nextValues: Map<InstructionValue, AbstractValue> | null = null;
|
||||
let nextVariables: Map<IdentifierId, Set<InstructionValue>> | null = null;
|
||||
let nextAliases: DisjointSet<IdentifierId> | null = null;
|
||||
|
||||
for (const [id, thisValue] of this.#values) {
|
||||
const otherValue = other.#values.get(id);
|
||||
@@ -583,13 +603,21 @@ class InferenceState {
|
||||
nextVariables.set(id, new Set(otherValues));
|
||||
}
|
||||
|
||||
if (nextVariables === null && nextValues === null) {
|
||||
if (!this.#aliases.equals(other.#aliases)) {
|
||||
nextAliases = this.#aliases.copy();
|
||||
for (const otherAliasSet of other.#aliases.buildSets()) {
|
||||
nextAliases.union(Array(...otherAliasSet));
|
||||
}
|
||||
}
|
||||
|
||||
if (nextVariables === null && nextValues === null && nextAliases === null) {
|
||||
return null;
|
||||
} else {
|
||||
return new InferenceState(
|
||||
this.#env,
|
||||
nextValues ?? new Map(this.#values),
|
||||
nextVariables ?? new Map(this.#variables),
|
||||
nextAliases ?? this.#aliases.copy(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -604,6 +632,7 @@ class InferenceState {
|
||||
this.#env,
|
||||
new Map(this.#values),
|
||||
new Map(this.#variables),
|
||||
this.#aliases.copy(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -634,10 +663,13 @@ class InferenceState {
|
||||
|
||||
inferPhi(phi: Phi): void {
|
||||
const values: Set<InstructionValue> = new Set();
|
||||
let valueKind;
|
||||
for (const [_, operand] of phi.operands) {
|
||||
const operandValues = this.#variables.get(operand.id);
|
||||
// This is a backedge that will be handled later by State.merge
|
||||
if (operandValues === undefined) continue;
|
||||
const kind = this.kind({identifier: operand, loc: GeneratedSource});
|
||||
valueKind = valueKind ? mergeAbstractValues(valueKind, kind) : kind;
|
||||
for (const v of operandValues) {
|
||||
values.add(v);
|
||||
}
|
||||
@@ -645,6 +677,7 @@ class InferenceState {
|
||||
|
||||
if (values.size > 0) {
|
||||
this.#variables.set(phi.id.id, values);
|
||||
phi.abstractValue = valueKind!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -673,6 +706,7 @@ function inferParam(
|
||||
}
|
||||
initialState.initialize(value, paramKind);
|
||||
initialState.define(place, value);
|
||||
place.abstractValue = paramKind;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -812,14 +846,11 @@ function mergeAbstractValues(
|
||||
return {kind, reason, context};
|
||||
}
|
||||
|
||||
type Continuation =
|
||||
| {
|
||||
kind: 'initialize';
|
||||
valueKind: AbstractValue;
|
||||
effect: {kind: Effect; reason: ValueReason} | null;
|
||||
lvalueEffect?: Effect;
|
||||
}
|
||||
| {kind: 'funeffects'};
|
||||
type Continuation = null | {
|
||||
valueKind: AbstractValue;
|
||||
effect: {kind: Effect; reason: ValueReason} | null;
|
||||
lvalueEffect?: Effect;
|
||||
};
|
||||
|
||||
/*
|
||||
* Iterates over the given @param block, defining variables and
|
||||
@@ -829,7 +860,6 @@ function inferBlock(
|
||||
env: Environment,
|
||||
state: InferenceState,
|
||||
block: BasicBlock,
|
||||
functionEffects: Array<FunctionEffect>,
|
||||
): void {
|
||||
for (const phi of block.phis) {
|
||||
state.inferPhi(phi);
|
||||
@@ -843,7 +873,6 @@ function inferBlock(
|
||||
switch (instrValue.kind) {
|
||||
case 'BinaryExpression': {
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Primitive,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
@@ -869,7 +898,6 @@ function inferBlock(
|
||||
context: new Set(),
|
||||
};
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind,
|
||||
effect: {kind: Effect.Capture, reason: ValueReason.Other},
|
||||
lvalueEffect: Effect.Store,
|
||||
@@ -913,8 +941,9 @@ function inferBlock(
|
||||
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
instr.lvalue.effect = Effect.ConditionallyMutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'ObjectExpression': {
|
||||
@@ -972,13 +1001,13 @@ function inferBlock(
|
||||
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
instr.lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'UnaryExpression': {
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Primitive,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
@@ -991,7 +1020,6 @@ function inferBlock(
|
||||
case 'UnsupportedNode': {
|
||||
// TODO: handle other statement kinds
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
@@ -1038,19 +1066,20 @@ function inferBlock(
|
||||
}
|
||||
}
|
||||
|
||||
state.initialize(instrValue, {
|
||||
const valueKind: AbstractValue = {
|
||||
kind: ValueKind.Frozen,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
});
|
||||
};
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
instr.lvalue.effect = Effect.ConditionallyMutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'JsxFragment': {
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Frozen,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
@@ -1069,7 +1098,6 @@ function inferBlock(
|
||||
* an immutable string
|
||||
*/
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Primitive,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
@@ -1082,7 +1110,6 @@ function inferBlock(
|
||||
case 'RegExpLiteral': {
|
||||
// RegExp instances are mutable objects
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
@@ -1097,11 +1124,10 @@ function inferBlock(
|
||||
}
|
||||
case 'MetaProperty': {
|
||||
if (instrValue.meta !== 'import' || instrValue.property !== 'meta') {
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Global,
|
||||
reason: new Set([ValueReason.Global]),
|
||||
@@ -1113,7 +1139,6 @@ function inferBlock(
|
||||
}
|
||||
case 'LoadGlobal':
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Global,
|
||||
reason: new Set([ValueReason.Global]),
|
||||
@@ -1126,7 +1151,6 @@ function inferBlock(
|
||||
case 'JSXText':
|
||||
case 'Primitive': {
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Primitive,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
@@ -1156,14 +1180,16 @@ function inferBlock(
|
||||
* If a closure did not capture any mutable values, then we can consider it to be
|
||||
* frozen, which allows it to be independently memoized.
|
||||
*/
|
||||
state.initialize(instrValue, {
|
||||
const valueKind: AbstractValue = {
|
||||
kind: hasMutableOperand ? ValueKind.Mutable : ValueKind.Frozen,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
});
|
||||
};
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
instr.lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'TaggedTemplateExpression': {
|
||||
@@ -1204,8 +1230,9 @@ function inferBlock(
|
||||
);
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = returnValueKind;
|
||||
instr.lvalue.effect = Effect.ConditionallyMutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'CallExpression': {
|
||||
@@ -1271,10 +1298,11 @@ function inferBlock(
|
||||
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = returnValueKind;
|
||||
instr.lvalue.effect = hasCaptureArgument
|
||||
? Effect.Store
|
||||
: Effect.ConditionallyMutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'MethodCall': {
|
||||
@@ -1336,11 +1364,12 @@ function inferBlock(
|
||||
);
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = returnValueKind;
|
||||
instr.lvalue.effect =
|
||||
instrValue.receiver.effect === Effect.Capture
|
||||
? Effect.Store
|
||||
: Effect.ConditionallyMutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1390,10 +1419,11 @@ function inferBlock(
|
||||
|
||||
state.initialize(instrValue, returnValueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = returnValueKind;
|
||||
instr.lvalue.effect = hasCaptureArgument
|
||||
? Effect.Store
|
||||
: Effect.ConditionallyMutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'PropertyStore': {
|
||||
@@ -1417,13 +1447,12 @@ function inferBlock(
|
||||
const lvalue = instr.lvalue;
|
||||
state.alias(lvalue, instrValue.value);
|
||||
lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'PropertyDelete': {
|
||||
// `delete` returns a boolean (immutable) and modifies the object
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
valueKind: {
|
||||
kind: ValueKind.Primitive,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
@@ -1441,10 +1470,12 @@ function inferBlock(
|
||||
ValueReason.Other,
|
||||
);
|
||||
const lvalue = instr.lvalue;
|
||||
const valueKind = state.kind(instrValue.object);
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
state.initialize(instrValue, state.kind(instrValue.object));
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(lvalue, instrValue);
|
||||
continuation = {kind: 'funeffects'};
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'ComputedStore': {
|
||||
@@ -1474,7 +1505,7 @@ function inferBlock(
|
||||
const lvalue = instr.lvalue;
|
||||
state.alias(lvalue, instrValue.value);
|
||||
lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'ComputedDelete': {
|
||||
@@ -1490,14 +1521,16 @@ function inferBlock(
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
);
|
||||
state.initialize(instrValue, {
|
||||
const valueKind: AbstractValue = {
|
||||
kind: ValueKind.Primitive,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
});
|
||||
};
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
instr.lvalue.effect = Effect.Mutate;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'ComputedLoad': {
|
||||
@@ -1513,11 +1546,13 @@ function inferBlock(
|
||||
Effect.Read,
|
||||
ValueReason.Other,
|
||||
);
|
||||
const valueKind = state.kind(instrValue.object);
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
state.initialize(instrValue, state.kind(instrValue.object));
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(lvalue, instrValue);
|
||||
continuation = {kind: 'funeffects'};
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'Await': {
|
||||
@@ -1536,7 +1571,7 @@ function inferBlock(
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
state.alias(lvalue, instrValue.value);
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'TypeCastExpression': {
|
||||
@@ -1558,7 +1593,7 @@ function inferBlock(
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
state.alias(lvalue, instrValue.value);
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'StartMemoize':
|
||||
@@ -1580,15 +1615,17 @@ function inferBlock(
|
||||
);
|
||||
}
|
||||
}
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
state.initialize(instrValue, {
|
||||
const valueKind: AbstractValue = {
|
||||
kind: ValueKind.Frozen,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
});
|
||||
};
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(lvalue, instrValue);
|
||||
continuation = {kind: 'funeffects'};
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'LoadLocal': {
|
||||
@@ -1607,7 +1644,7 @@ function inferBlock(
|
||||
lvalue.effect = Effect.ConditionallyMutate;
|
||||
// direct aliasing: `a = b`;
|
||||
state.alias(lvalue, instrValue.place);
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'LoadContext': {
|
||||
@@ -1622,13 +1659,13 @@ function inferBlock(
|
||||
const valueKind = state.kind(instrValue.place);
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(lvalue, instrValue);
|
||||
continuation = {kind: 'funeffects'};
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'DeclareLocal': {
|
||||
const value = UndefinedValue;
|
||||
state.initialize(
|
||||
value,
|
||||
const valueKind: AbstractValue =
|
||||
// Catch params may be aliased to mutable values
|
||||
instrValue.lvalue.kind === InstructionKind.Catch
|
||||
? {
|
||||
@@ -1640,20 +1677,27 @@ function inferBlock(
|
||||
kind: ValueKind.Primitive,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
},
|
||||
);
|
||||
};
|
||||
state.initialize(value, valueKind);
|
||||
state.define(instrValue.lvalue.place, value);
|
||||
continuation = {kind: 'funeffects'};
|
||||
instrValue.lvalue.place.abstractValue = valueKind;
|
||||
state.define(instr.lvalue, value);
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'DeclareContext': {
|
||||
state.initialize(instrValue, {
|
||||
const valueKind: AbstractValue = {
|
||||
kind: ValueKind.Mutable,
|
||||
reason: new Set([ValueReason.Other]),
|
||||
context: new Set(),
|
||||
});
|
||||
};
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(instrValue.lvalue.place, instrValue);
|
||||
continuation = {kind: 'funeffects'};
|
||||
instrValue.lvalue.place.abstractValue = valueKind;
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'PostfixUpdate':
|
||||
@@ -1681,7 +1725,7 @@ function inferBlock(
|
||||
* replacing it
|
||||
*/
|
||||
instrValue.lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'StoreLocal': {
|
||||
@@ -1708,7 +1752,7 @@ function inferBlock(
|
||||
* replacing it
|
||||
*/
|
||||
instrValue.lvalue.place.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'StoreContext': {
|
||||
@@ -1728,7 +1772,7 @@ function inferBlock(
|
||||
const lvalue = instr.lvalue;
|
||||
state.alias(lvalue, instrValue.value);
|
||||
lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'StoreGlobal': {
|
||||
@@ -1740,7 +1784,11 @@ function inferBlock(
|
||||
);
|
||||
const lvalue = instr.lvalue;
|
||||
lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
const valueKind = state.kind(instrValue.value);
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'Destructure': {
|
||||
@@ -1774,7 +1822,7 @@ function inferBlock(
|
||||
*/
|
||||
place.effect = Effect.Store;
|
||||
}
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'GetIterator': {
|
||||
@@ -1817,7 +1865,6 @@ function inferBlock(
|
||||
valueKind = state.kind(instrValue.collection);
|
||||
}
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
effect,
|
||||
valueKind,
|
||||
lvalueEffect: Effect.Store,
|
||||
@@ -1853,15 +1900,16 @@ function inferBlock(
|
||||
Effect.Capture,
|
||||
ValueReason.Other,
|
||||
);
|
||||
state.initialize(instrValue, state.kind(instrValue.collection));
|
||||
const valueKind = state.kind(instrValue.collection);
|
||||
state.initialize(instrValue, valueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = valueKind;
|
||||
instr.lvalue.effect = Effect.Store;
|
||||
continuation = {kind: 'funeffects'};
|
||||
continuation = null;
|
||||
break;
|
||||
}
|
||||
case 'NextPropertyOf': {
|
||||
continuation = {
|
||||
kind: 'initialize',
|
||||
effect: {kind: Effect.Read, reason: ValueReason.Other},
|
||||
lvalueEffect: Effect.Store,
|
||||
valueKind: {
|
||||
@@ -1877,7 +1925,7 @@ function inferBlock(
|
||||
}
|
||||
}
|
||||
|
||||
if (continuation.kind === 'initialize') {
|
||||
if (continuation !== null) {
|
||||
for (const operand of eachInstructionOperand(instr)) {
|
||||
CompilerError.invariant(continuation.effect != null, {
|
||||
reason: `effectKind must be set for instruction value \`${instrValue.kind}\``,
|
||||
@@ -1895,10 +1943,10 @@ function inferBlock(
|
||||
|
||||
state.initialize(instrValue, continuation.valueKind);
|
||||
state.define(instr.lvalue, instrValue);
|
||||
instr.lvalue.abstractValue = continuation.valueKind;
|
||||
instr.lvalue.effect = continuation.lvalueEffect ?? defaultLvalueEffect;
|
||||
}
|
||||
|
||||
functionEffects.push(...inferInstructionFunctionEffects(env, state, instr));
|
||||
freezeActions.forEach(({values, reason}) =>
|
||||
state.freezeValues(values, reason),
|
||||
);
|
||||
@@ -1926,7 +1974,6 @@ function inferBlock(
|
||||
ValueReason.Other,
|
||||
);
|
||||
}
|
||||
functionEffects.push(...inferTerminalFunctionEffects(state, block));
|
||||
terminalFreezeActions.forEach(({values, reason}) =>
|
||||
state.freezeValues(values, reason),
|
||||
);
|
||||
|
||||
@@ -237,6 +237,7 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
place: {
|
||||
kind: 'Identifier',
|
||||
effect: Effect.ConditionallyMutate,
|
||||
abstractValue: null,
|
||||
loc,
|
||||
reactive: true,
|
||||
identifier: earlyReturnValue.value,
|
||||
@@ -308,6 +309,7 @@ class Transform extends ReactiveFunctionTransform<State> {
|
||||
kind: 'Identifier',
|
||||
identifier: earlyReturnValue.value,
|
||||
effect: Effect.Capture,
|
||||
abstractValue: null,
|
||||
loc,
|
||||
reactive: true,
|
||||
},
|
||||
|
||||
@@ -191,6 +191,7 @@ class SSABuilder {
|
||||
const phi: Phi = {
|
||||
kind: 'Phi',
|
||||
id: newId,
|
||||
abstractValue: null,
|
||||
operands: predDefs,
|
||||
};
|
||||
|
||||
|
||||
@@ -124,6 +124,54 @@ export default class DisjointSet<T> {
|
||||
return [...sets.values()];
|
||||
}
|
||||
|
||||
copy(): DisjointSet<T> {
|
||||
const copy = new DisjointSet<T>();
|
||||
copy.#entries = new Map(this.#entries);
|
||||
return copy;
|
||||
}
|
||||
|
||||
equals(other: DisjointSet<T>): boolean {
|
||||
if (this.size !== other.size) {
|
||||
return false;
|
||||
}
|
||||
const rootMap = new Map<T, T>();
|
||||
for (const thisGroupId of this.#entries.values()) {
|
||||
const otherGroupId = other.find(thisGroupId);
|
||||
if (otherGroupId === null || this.find(otherGroupId) !== thisGroupId) {
|
||||
return false;
|
||||
}
|
||||
rootMap.set(thisGroupId, otherGroupId);
|
||||
}
|
||||
|
||||
for (const otherGroupId of other.#entries.values()) {
|
||||
if (!new Set(rootMap.values()).has(otherGroupId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of this.#entries.keys()) {
|
||||
const otherRoot = other.find(item);
|
||||
if (otherRoot === null) {
|
||||
return false;
|
||||
}
|
||||
const thisRoot = this.find(item);
|
||||
CompilerError.invariant(thisRoot != null, {
|
||||
reason: 'Expected item to be in set',
|
||||
loc: null,
|
||||
});
|
||||
if (rootMap.get(thisRoot) !== otherRoot) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const item of other.#entries.keys()) {
|
||||
const thisRoot = this.find(item);
|
||||
if (thisRoot === null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.#entries.size;
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@ function validateInferredDep(
|
||||
identifier: dep.identifier,
|
||||
loc: GeneratedSource,
|
||||
effect: Effect.Read,
|
||||
abstractValue: null,
|
||||
reactive: false,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -116,4 +116,45 @@ describe('DisjointSet', () => {
|
||||
|
||||
identifiers.forEach((_, group) => expect(group).toBe(z));
|
||||
});
|
||||
|
||||
it('`.equals` is false when it should be', () => {
|
||||
const x = new DisjointSet<TestIdentifier>();
|
||||
const y = new DisjointSet<TestIdentifier>();
|
||||
|
||||
const [a, b] = makeIdentifiers('a', 'b');
|
||||
x.union([a, b]);
|
||||
y.union([a]);
|
||||
y.union([b]);
|
||||
|
||||
expect(x.equals(y)).toBe(false);
|
||||
expect(y.equals(x)).toBe(false);
|
||||
});
|
||||
|
||||
it('`.equals` is true when it should be', () => {
|
||||
const x = new DisjointSet<TestIdentifier>();
|
||||
const y = new DisjointSet<TestIdentifier>();
|
||||
|
||||
const [a, b, c] = makeIdentifiers('a', 'b', 'c');
|
||||
x.union([a, b, c]);
|
||||
y.union([a, b]);
|
||||
y.union([b, c]);
|
||||
|
||||
expect(x.equals(y)).toBe(true);
|
||||
expect(y.equals(x)).toBe(true);
|
||||
});
|
||||
|
||||
it('`.copy` doesnt mutate the underlying', () => {
|
||||
const x = new DisjointSet<TestIdentifier>();
|
||||
|
||||
const [a, b] = makeIdentifiers('a', 'b');
|
||||
x.union([a]);
|
||||
x.union([b]);
|
||||
|
||||
const y = x.copy();
|
||||
|
||||
y.union([a, b]);
|
||||
|
||||
expect(x.find(a) !== x.find(b)).toBe(true);
|
||||
expect(y.find(a) === y.find(b)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ function Component(props) {
|
||||
7 | return hasErrors;
|
||||
8 | }
|
||||
> 9 | return hasErrors();
|
||||
| ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9)
|
||||
| ^^^^^^^^^ Invariant: [hoisting] Expected value kind to be initialized in call to kind(). 16 (9:9)
|
||||
10 | }
|
||||
11 |
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user